使用D3.js实现相同parallelism值的分组柱状图问题
D3.js分组柱状图实现问题
我首次使用D3.js,想要为性能测试结果创建分组柱状图,需求如下:
- X轴展示parallelism(线程数),Y轴展示duration(耗时,单位毫秒)
- 相同parallelism值的多条记录需相邻显示,理想状态下按duration排序
- 参考分组柱状图示例调试了一个多小时,始终无法正确实现分组,问题集中在
fx、x比例尺的定义以及.attr("x", d => marginLeft+fx(d.duration))的赋值上,尝试多种变体均无效。
原代码
JavaScript
const runData = [ {"duration":74950.52171194553,"parallelism":1}, {"duration":88687.86499893665,"parallelism":0,"exitCode":0}, {"duration":60000,"parallelism":1,"exitCode":0}, {"duration":90000,"parallelism":0,"exitCode":0}, {"duration":90000,"parallelism":0,"exitCode":0} ]; const width = 700; const height = 400; const marginTop = 10; const marginRight = 10; const marginBottom = 20; const marginLeft = 40; // Create the SVG container. const svg = d3.select("#ca") .append("svg") .attr("width", width) .attr("height", height); const paralelismSet = new Set(runData.map(x=>x.parallelism)); const paralelismList = runData.map(x=>x.parallelism); paralelismList.sort(); const durationSet = new Set(runData.map(x=>x.duration)); const minDuration = Math.min(...durationSet); const maxDuration = Math.max(...durationSet); // I am trying here to sort by duration but group by parallelism const fx = d3.scaleBand() .domain(durationSet) .rangeRound([marginLeft, width - marginRight]) .paddingInner(0.1); const x = d3.scaleBand() .domain(paralelismList) .rangeRound([0, fx.bandwidth()]) .padding(0.05); const color = d3.scaleLinear([minDuration, maxDuration], ["green", "red"]); // Y encodes the height of the bar. const y = d3.scaleLinear() .domain([0, maxDuration]).nice() .rangeRound([height - marginBottom, marginTop]); svg.append("g") .selectAll() .data(d3.group(runData, d => d.parallelism)) .join("g") .attr("transform", ([paralelism]) => `translate(${x(paralelism)},0)`) .selectAll() .data(([, d]) => d) .join("rect") // I tried various combinations of subtracting fx from x and vice versa .attr("x", d => marginLeft+fx(d.duration)) .attr("y", d => y(d.duration)) .attr("width", x.bandwidth()) .attr("height", d => y(0) - y(d.duration)) .attr("fill", d => color(d.duration)); // The horizontal axis does not work well either, it is too short svg.append("g") .attr("transform", `translate(${marginLeft},${height - marginBottom})`) .call(d3.axisBottom(x)); // This code works fine svg.append("g") .attr("transform", `translate(${marginLeft},0)`) .call(d3.axisLeft(y).ticks(null, "s")) .call(g => g.selectAll(".domain").remove());
HTML
<div id="ca"> </div> <script src="https://d3js.org/d3.v6.min.js"></script>
问题分析与修正方案
核心问题
- 比例尺定义逻辑颠倒:分组柱状图需外层比例尺对应X轴分组(parallelism),内层比例尺对应分组内子项位置
- 分组数据未按duration排序,不符合需求
- X轴定位错误,导致显示异常
修正后的完整代码
JavaScript
const runData = [ {"duration":74950.52171194553,"parallelism":1}, {"duration":88687.86499893665,"parallelism":0,"exitCode":0}, {"duration":60000,"parallelism":1,"exitCode":0}, {"duration":90000,"parallelism":0,"exitCode":0}, {"duration":90000,"parallelism":0,"exitCode":0} ]; const width = 700; const height = 400; const marginTop = 10; const marginRight = 10; const marginBottom = 20; const marginLeft = 40; // 创建SVG容器 const svg = d3.select("#ca") .append("svg") .attr("width", width) .attr("height", height); // 提取并去重排序parallelism值,作为X轴分组 const parallelismValues = [...new Set(runData.map(d => d.parallelism))].sort(); // 获取分组内最大子项数量,用于内层比例尺适配 const maxGroupSize = d3.max([...d3.group(runData, d => d.parallelism)].map(([_, group]) => group.length)); const minDuration = d3.min(runData, d => d.duration); const maxDuration = d3.max(runData, d => d.duration); // 外层比例尺:控制X轴分组位置 const xScale = d3.scaleBand() .domain(parallelismValues) .rangeRound([marginLeft, width - marginRight]) .paddingInner(0.1); // 内层比例尺:控制分组内子项横向排列 const xSubScale = d3.scaleBand() .domain(d3.range(maxGroupSize)) .rangeRound([0, xScale.bandwidth()]) .padding(0.05); // 颜色比例尺:耗时越短越绿,越长越红 const colorScale = d3.scaleLinear() .domain([minDuration, maxDuration]) .range(["green", "red"]); // Y轴比例尺:映射耗时到高度 const yScale = d3.scaleLinear() .domain([0, maxDuration]).nice() .rangeRound([height - marginBottom, marginTop]); // 绘制分组柱状图 svg.append("g") .selectAll("g") .data(d3.group(runData, d => d.parallelism)) .join("g") // 外层分组平移到对应parallelism的X轴位置 .attr("transform", ([parallelism]) => `translate(${xScale(parallelism)}, 0)`) .selectAll("rect") // 分组内数据按duration升序排序 .data(([, group]) => group.sort((a, b) => a.duration - b.duration)) .join("rect") // 通过子项索引获取内层X位置 .attr("x", (d, i) => xSubScale(i)) .attr("y", d => yScale(d.duration)) .attr("width", xSubScale.bandwidth()) .attr("height", d => yScale(0) - yScale(d.duration)) .attr("fill", d => colorScale(d.duration)); // 绘制X轴 svg.append("g") .attr("transform", `translate(0, ${height - marginBottom})`) .call(d3.axisBottom(xScale)) .call(g => g.selectAll(".domain").remove()); // 绘制Y轴 svg.append("g") .attr("transform", `translate(${marginLeft}, 0)`) .call(d3.axisLeft(yScale).ticks(null, "s")) .call(g => g.selectAll(".domain").remove());
HTML
<div id="ca"></div> <script src="https://d3js.org/d3.v6.min.js"></script>
关键修改说明
- 比例尺逻辑调整:外层
xScale对应parallelism分组,内层xSubScale用索引控制分组内子项位置 - 数据排序:绑定分组数据时添加排序逻辑,实现同分组内按耗时升序排列
- 坐标轴修正:X轴移除多余的
marginLeft偏移,与分组位置对齐 - 子项定位:通过子项索引计算内层X位置,确保同分组内子项相邻显示
内容的提问来源于stack exchange,提问作者Tomáš Zato
相关产品推荐
相关产品推荐

