# 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) |