# D3.js Template Library
> **⚠️ Before writing any code, read [`_rules.md`](references/_rules.md) — three non-negotiable rules on overlap, hierarchy, and color.**
D3.js is the "ultimate weapon" in visualization — highest freedom, but steepest learning curve.
Suitable for: visualizations requiring fully custom interactions, data journalism, art-grade infographics.
**If your needs can be met by ECharts/matplotlib, don't use D3.** D3's value lies in "charts others can't make".
## HTML Universal Shell
```html
{{TITLE}}
```
## Colors and Constants
```javascript
const COLORS = ['#3B82F6', '#06B6D4', '#8B5CF6', '#F59E0B', '#EF4444', '#10B981'];
const GRAY = { 200: '#E5E7EB', 300: '#D1D5DB', 400: '#9CA3AF', 500: '#6B7280', 900: '#111827' };
const margin = { top: 60, right: 40, bottom: 50, left: 60 };
const width = 900 - margin.left - margin.right;
const height = 500 - margin.top - margin.bottom;
```
---
## Template 1: Insight Bar Chart
```javascript
const svg = d3.select('#chart')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`);
// Title (insight-driven)
svg.append('text')
.attr('x', 0).attr('y', -30)
.attr('fill', GRAY[900])
.attr('font-size', 16).attr('font-weight', 'bold')
.text('产品C销售额领先,达到89万');
// Scales
const x = d3.scaleBand().domain(data.map(d => d.name)).range([0, width]).padding(0.35);
const y = d3.scaleLinear().domain([0, d3.max(data, d => d.value) * 1.15]).range([height, 0]);
// Y axis (minimal: no tick marks, light gray)
svg.append('g')
.call(d3.axisLeft(y).ticks(5).tickSize(0).tickFormat(d3.format(',')))
.call(g => g.select('.domain').attr('stroke', GRAY[200]).attr('stroke-width', 0.8))
.call(g => g.selectAll('.tick text').attr('fill', GRAY[500]).attr('font-size', 9));
// X axis
svg.append('g')
.attr('transform', `translate(0,${height})`)
.call(d3.axisBottom(x).tickSize(0))
.call(g => g.select('.domain').attr('stroke', GRAY[200]).attr('stroke-width', 0.8))
.call(g => g.selectAll('.tick text').attr('fill', GRAY[500]).attr('font-size', 9));
// Bars (gray + highlight)
svg.selectAll('.bar')
.data(data)
.join('rect')
.attr('x', d => x(d.name))
.attr('width', x.bandwidth())
.attr('y', d => y(d.value))
.attr('height', d => height - y(d.value))
.attr('fill', d => d.highlight ? '#3B82F6' : GRAY[200])
.attr('rx', 3);
// Value labels
svg.selectAll('.label')
.data(data)
.join('text')
.attr('x', d => x(d.name) + x.bandwidth()/2)
.attr('y', d => y(d.value) - 6)
.attr('text-anchor', 'middle')
.attr('fill', d => d.highlight ? GRAY[900] : GRAY[400])
.attr('font-size', d => d.highlight ? 12 : 10)
.attr('font-weight', d => d.highlight ? 'bold' : 'normal')
.text(d => d3.format(',')(d.value));
```
---
## Template 2: Force-Directed Graph
This is D3's killer feature — other frameworks can hardly achieve the same effect.
```javascript
const simulation = d3.forceSimulation(nodes)
.force('link', d3.forceLink(links).id(d => d.id).distance(80))
.force('charge', d3.forceManyBody().strength(-200))
.force('center', d3.forceCenter(width/2, height/2))
.force('collision', d3.forceCollide(20));
const link = svg.selectAll('.link')
.data(links).join('line')
.attr('stroke', GRAY[200]).attr('stroke-width', 1);
const node = svg.selectAll('.node')
.data(nodes).join('circle')
.attr('r', d => Math.sqrt(d.value) * 3)
.attr('fill', (d, i) => COLORS[d.group % COLORS.length])
.attr('stroke', '#fff').attr('stroke-width', 1.5)
.call(d3.drag()
.on('start', (e, d) => { if (!e.active) simulation.alphaTarget(0.3).restart(); d.fx = d.x; d.fy = d.y; })
.on('drag', (e, d) => { d.fx = e.x; d.fy = e.y; })
.on('end', (e, d) => { if (!e.active) simulation.alphaTarget(0); d.fx = null; d.fy = null; })
);
// Labels
const labels = svg.selectAll('.label')
.data(nodes).join('text')
.text(d => d.name)
.attr('font-size', 9).attr('fill', GRAY[500])
.attr('text-anchor', 'middle').attr('dy', -15);
simulation.on('tick', () => {
link.attr('x1', d => d.source.x).attr('y1', d => d.source.y)
.attr('x2', d => d.target.x).attr('y2', d => d.target.y);
node.attr('cx', d => d.x).attr('cy', d => d.y);
labels.attr('x', d => d.x).attr('y', d => d.y);
});
```
---
## Template 3: Treemap
```javascript
const root = d3.hierarchy(treeData).sum(d => d.value);
d3.treemap().size([width, height]).padding(2)(root);
svg.selectAll('.cell')
.data(root.leaves())
.join('rect')
.attr('x', d => d.x0).attr('y', d => d.y0)
.attr('width', d => d.x1 - d.x0)
.attr('height', d => d.y1 - d.y0)
.attr('fill', (d, i) => COLORS[i % COLORS.length])
.attr('rx', 2).attr('opacity', 0.85);
svg.selectAll('.cell-label')
.data(root.leaves().filter(d => (d.x1-d.x0) > 40 && (d.y1-d.y0) > 20))
.join('text')
.attr('x', d => d.x0 + 4).attr('y', d => d.y0 + 14)
.text(d => d.data.name)
.attr('font-size', 10).attr('fill', 'white').attr('font-weight', 'bold');
```
---
## D3 Use Cases (vs Overkill)
| ✅ D3 Best | ❌ Overkill |
|-----------|-------------|
| Force-directed relationship graph | Regular bar chart (use matplotlib) |
| Custom geographic visualization | Standard map (use ECharts) |
| Data-driven animation | Static report chart (use matplotlib) |
| Treemap / Sunburst | Standard pie chart (use matplotlib) |
| Complex interactions (brushing/linking/drill-down) | Simple tooltip (use ECharts) |
| Data journalism/narrative visualization | Dashboard (use ECharts) |