求基于D3.js的Canvas堆叠图表实现示例
D3.js Canvas 堆叠图表示例
为什么Canvas版本示例少见?
D3最初的设计更偏向SVG的声明式DOM操作,SVG元素可直接与数据绑定,上手门槛更低,社区示例也更丰富。而Canvas是命令式绘制,需要手动编写绘制逻辑,无法直接复用SVG版本的DOM绑定逻辑,所以相关示例相对稀缺。
完整可运行示例
下面是基于D3.js v7的Canvas堆叠图表示例,包含数据处理、比例尺定义、堆叠区域绘制和坐标轴渲染:
// 1. 模拟堆叠数据 const dataset = [ { year: "2018", A: 30, B: 40, C: 20 }, { year: "2019", A: 35, B: 45, C: 25 }, { year: "2020", A: 40, B: 50, C: 30 }, { year: "2021", A: 45, B: 55, C: 35 }, { year: "2022", A: 50, B: 60, C: 40 } ]; // 2. 设置Canvas基础参数 const width = 600; const height = 400; const margin = { top: 20, right: 20, bottom: 50, left: 50 }; const canvas = document.getElementById("stacked-canvas"); canvas.width = width; canvas.height = height; const ctx = canvas.getContext("2d"); // 3. 处理堆叠数据 const keys = ["A", "B", "C"]; const stack = d3.stack().keys(keys); const stackedData = stack(dataset); // 4. 定义比例尺 const xScale = d3.scaleBand() .domain(dataset.map(d => d.year)) .range([margin.left, width - margin.right]) .padding(0.2); const yScale = d3.scaleLinear() .domain([0, d3.max(stackedData, d => d3.max(d, d => d[1]))]) .range([height - margin.bottom, margin.top]); // 颜色比例尺 const colorScale = d3.scaleOrdinal() .domain(keys) .range(["#FF6B6B", "#4ECDC4", "#FFE66D"]); // 5. 绘制堆叠柱形 stackedData.forEach((layer, index) => { ctx.fillStyle = colorScale(keys[index]); layer.forEach(d => { const x = xScale(d.data.year); const barWidth = xScale.bandwidth(); const y = yScale(d[1]); const barHeight = yScale(d[0]) - yScale(d[1]); ctx.fillRect(x, y, barWidth, barHeight); // 可选:添加柱形边框 ctx.strokeStyle = "#fff"; ctx.strokeRect(x, y, barWidth, barHeight); }); }); // 6. 绘制坐标轴(利用临时SVG生成轴元素,再转绘到Canvas) // X轴 const xAxis = d3.axisBottom(xScale); const tempSvg = d3.create("svg"); const xAxisGroup = tempSvg.append("g").call(xAxis); // 绘制X轴路径 const xPath = xAxisGroup.select("path").attr("d"); ctx.beginPath(); new Path2D(xPath).forEach(([cmd, ...args]) => { if (cmd === "M") ctx.moveTo(...args); else if (cmd === "L") ctx.lineTo(...args); }); ctx.strokeStyle = "#333"; ctx.stroke(); // 绘制X轴刻度与文本 xAxisGroup.selectAll("g").forEach(g => { const tick = g.select("line"); const text = g.select("text"); const x = parseFloat(tick.attr("x1")); const y1 = parseFloat(tick.attr("y1")); const y2 = parseFloat(tick.attr("y2")); // 刻度线 ctx.beginPath(); ctx.moveTo(x, y1 + height - margin.bottom); ctx.lineTo(x, y2 + height - margin.bottom); ctx.strokeStyle = "#333"; ctx.stroke(); // 刻度文本 ctx.fillStyle = "#333"; ctx.font = "12px sans-serif"; ctx.textAlign = "center"; ctx.fillText(text.text(), x, y2 + height - margin.bottom + 15); }); // Y轴 const yAxis = d3.axisLeft(yScale).ticks(5); const yAxisGroup = tempSvg.append("g").call(yAxis); const yPath = yAxisGroup.select("path").attr("d"); ctx.beginPath(); new Path2D(yPath).forEach(([cmd, ...args]) => { if (cmd === "M") ctx.moveTo(args[0] + margin.left, args[1]); else if (cmd === "L") ctx.lineTo(args[0] + margin.left, args[1]); }); ctx.strokeStyle = "#333"; ctx.stroke(); // 绘制Y轴刻度与文本 yAxisGroup.selectAll("g").forEach(g => { const tick = g.select("line"); const text = g.select("text"); const x1 = parseFloat(tick.attr("x1")); const x2 = parseFloat(tick.attr("x2")); const y = parseFloat(tick.attr("y1")); // 刻度线 ctx.beginPath(); ctx.moveTo(x1 + margin.left, y); ctx.lineTo(x2 + margin.left, y); ctx.strokeStyle = "#333"; ctx.stroke(); // 刻度文本 ctx.fillStyle = "#333"; ctx.font = "12px sans-serif"; ctx.textAlign = "right"; ctx.fillText(text.text(), x1 + margin.left - 5, y + 4); }); // 7. 添加图例 const legendX = width - margin.right - 80; const legendY = margin.top; keys.forEach((key, index) => { ctx.fillStyle = colorScale(key); ctx.fillRect(legendX, legendY + index * 20, 15, 15); ctx.fillStyle = "#333"; ctx.font = "12px sans-serif"; ctx.textAlign = "left"; ctx.fillText(key, legendX + 20, legendY + index * 20 + 12); });
使用说明
- 在HTML中添加Canvas元素:
<canvas id="stacked-canvas"></canvas> - 引入D3.js库:
<script src="https://d3js.org/d3.v7.min.js"></script>(本地环境可下载后引入) - 将上述代码放在DOM加载完成的回调中,或HTML页面底部
内容的提问来源于stack exchange,提问作者Aris381
相关产品推荐
相关产品推荐

