Initial commit
This commit is contained in:
199
skills/charts/references/d3.md
Executable file
199
skills/charts/references/d3.md
Executable file
@@ -0,0 +1,199 @@
|
||||
# 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
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{{TITLE}}</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { background: #FFFFFF; font-family: system-ui, 'SimHei', sans-serif; }
|
||||
svg { display: block; margin: 40px auto; }
|
||||
|
||||
/* Design tokens */
|
||||
:root {
|
||||
--text: #111827;
|
||||
--text-sub: #6B7280;
|
||||
--text-muted: #9CA3AF;
|
||||
--axis: #E5E7EB;
|
||||
--grid: #F3F4F6;
|
||||
--blue: #3B82F6;
|
||||
--cyan: #06B6D4;
|
||||
--purple: #8B5CF6;
|
||||
--amber: #F59E0B;
|
||||
--red: #EF4444;
|
||||
--green: #10B981;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<svg id="chart"></svg>
|
||||
<script>
|
||||
// D3 code
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## 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) |
|
||||
Reference in New Issue
Block a user