$npx -y skills add calesthio/OpenMontage --skill d3-vizCreating interactive data visualisations using d3.js. This skill should be used when creating custom charts, graphs, network diagrams, geographic visualisations, or any complex SVG-based data visualisation that requires fine-grained control over visual elements, transitions, or i
| 1 | # D3.js Visualisation |
| 2 | |
| 3 | ## Overview |
| 4 | |
| 5 | This skill provides guidance for creating sophisticated, interactive data visualisations using d3.js. D3.js (Data-Driven Documents) excels at binding data to DOM elements and applying data-driven transformations to create custom, publication-quality visualisations with precise control over every visual element. The techniques work across any JavaScript environment, including vanilla JavaScript, React, Vue, Svelte, and other frameworks. |
| 6 | |
| 7 | ## When to use d3.js |
| 8 | |
| 9 | **Use d3.js for:** |
| 10 | - Custom visualisations requiring unique visual encodings or layouts |
| 11 | - Interactive explorations with complex pan, zoom, or brush behaviours |
| 12 | - Network/graph visualisations (force-directed layouts, tree diagrams, hierarchies, chord diagrams) |
| 13 | - Geographic visualisations with custom projections |
| 14 | - Visualisations requiring smooth, choreographed transitions |
| 15 | - Publication-quality graphics with fine-grained styling control |
| 16 | - Novel chart types not available in standard libraries |
| 17 | |
| 18 | **Consider alternatives for:** |
| 19 | - 3D visualisations - use Three.js instead |
| 20 | |
| 21 | ## Core workflow |
| 22 | |
| 23 | ### 1. Set up d3.js |
| 24 | |
| 25 | Import d3 at the top of your script: |
| 26 | |
| 27 | ```javascript |
| 28 | import * as d3 from 'd3'; |
| 29 | ``` |
| 30 | |
| 31 | Or use the CDN version (7.x): |
| 32 | |
| 33 | ```html |
| 34 | <script src="https://d3js.org/d3.v7.min.js"></script> |
| 35 | ``` |
| 36 | |
| 37 | All modules (scales, axes, shapes, transitions, etc.) are accessible through the `d3` namespace. |
| 38 | |
| 39 | ### 2. Choose the integration pattern |
| 40 | |
| 41 | **Pattern A: Direct DOM manipulation (recommended for most cases)** |
| 42 | Use d3 to select DOM elements and manipulate them imperatively. This works in any JavaScript environment: |
| 43 | |
| 44 | ```javascript |
| 45 | function drawChart(data) { |
| 46 | if (!data || data.length === 0) return; |
| 47 | |
| 48 | const svg = d3.select('#chart'); // Select by ID, class, or DOM element |
| 49 | |
| 50 | // Clear previous content |
| 51 | svg.selectAll("*").remove(); |
| 52 | |
| 53 | // Set up dimensions |
| 54 | const width = 800; |
| 55 | const height = 400; |
| 56 | const margin = { top: 20, right: 30, bottom: 40, left: 50 }; |
| 57 | |
| 58 | // Create scales, axes, and draw visualisation |
| 59 | // ... d3 code here ... |
| 60 | } |
| 61 | |
| 62 | // Call when data changes |
| 63 | drawChart(myData); |
| 64 | ``` |
| 65 | |
| 66 | **Pattern B: Declarative rendering (for frameworks with templating)** |
| 67 | Use d3 for data calculations (scales, layouts) but render elements via your framework: |
| 68 | |
| 69 | ```javascript |
| 70 | function getChartElements(data) { |
| 71 | const xScale = d3.scaleLinear() |
| 72 | .domain([0, d3.max(data, d => d.value)]) |
| 73 | .range([0, 400]); |
| 74 | |
| 75 | return data.map((d, i) => ({ |
| 76 | x: 50, |
| 77 | y: i * 30, |
| 78 | width: xScale(d.value), |
| 79 | height: 25 |
| 80 | })); |
| 81 | } |
| 82 | |
| 83 | // In React: {getChartElements(data).map((d, i) => <rect key={i} {...d} fill="steelblue" />)} |
| 84 | // In Vue: v-for directive over the returned array |
| 85 | // In vanilla JS: Create elements manually from the returned data |
| 86 | ``` |
| 87 | |
| 88 | Use Pattern A for complex visualisations with transitions, interactions, or when leveraging d3's full capabilities. Use Pattern B for simpler visualisations or when your framework prefers declarative rendering. |
| 89 | |
| 90 | ### 3. Structure the visualisation code |
| 91 | |
| 92 | Follow this standard structure in your drawing function: |
| 93 | |
| 94 | ```javascript |
| 95 | function drawVisualization(data) { |
| 96 | if (!data || data.length === 0) return; |
| 97 | |
| 98 | const svg = d3.select('#chart'); // Or pass a selector/element |
| 99 | svg.selectAll("*").remove(); // Clear previous render |
| 100 | |
| 101 | // 1. Define dimensions |
| 102 | const width = 800; |
| 103 | const height = 400; |
| 104 | const margin = { top: 20, right: 30, bottom: 40, left: 50 }; |
| 105 | const innerWidth = width - margin.left - margin.right; |
| 106 | const innerHeight = height - margin.top - margin.bottom; |
| 107 | |
| 108 | // 2. Create main group with margins |
| 109 | const g = svg.append("g") |
| 110 | .attr("transform", `translate(${margin.left},${margin.top})`); |
| 111 | |
| 112 | // 3. Create scales |
| 113 | const xScale = d3.scaleLinear() |
| 114 | .domain([0, d3.max(data, d => d.x)]) |
| 115 | .range([0, innerWidth]); |
| 116 | |
| 117 | const yScale = d3.scaleLinear() |
| 118 | .domain([0, d3.max(data, d => d.y)]) |
| 119 | .range([innerHeight, 0]); // Note: inverted for SVG coordinates |
| 120 | |
| 121 | // 4. Create and append axes |
| 122 | const xAxis = d3.axisBottom(xScale); |
| 123 | const yAxis = d3.axisLeft(yScale); |
| 124 | |
| 125 | g.append("g") |
| 126 | .attr("transform", `translate(0,${innerHeight})`) |
| 127 | .call(xAxis); |
| 128 | |
| 129 | g.append("g") |
| 130 | .call(yAxis); |
| 131 | |
| 132 | // 5. Bind data and create visual elements |
| 133 | g.selectAll("circle") |
| 134 | .data(data) |
| 135 | .join("circle") |
| 136 | .attr("cx", d => xScale(d.x)) |
| 137 | .attr("cy", d => yScale(d.y)) |
| 138 | .attr("r", 5) |
| 139 | .attr("fill", "steelblue"); |
| 140 | } |
| 141 | |
| 142 | // Call when data changes |
| 143 | drawVisualization(myData); |
| 144 | ``` |
| 145 | |
| 146 | ### 4. Implement responsive sizing |
| 147 | |
| 148 | Make visualisations responsive to container size: |
| 149 | |
| 150 | ```javascript |
| 151 | function setupR |