我正在尝试为性能测试结果创建分组条形图。这是我第一次使用D3。
X 轴应显示
parallelism
,这是使用的线程数和 Y 图表持续时间(以毫秒为单位)。如果同一 X 值(相同的并行性)存在多个记录,它们应该彼此相邻出现,理想情况下按持续时间排序,但这并不重要。
我在这段代码中获得了灵感:https://observablehq.com/@d3/grouped-bar-chart/2?intent=fork
我花了一个多小时摆弄它,但我仍然没有得到正确的分组。这个问题肯定是由我如何定义
fx
和 x
函数以及如何分配 .attr("x", d => marginLeft+fx(d.duration))
引起的。我尝试了很多变体,但没有成功。
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());
<div id="ca">
</div>
<script src="https://d3js.org/d3.v6.min.js"></script>
对我来说,您所遵循的示例情节不必要地令人困惑。它不遵循通常的
d3
图形构造,有太多的边距摆弄,并且数据操作可以使用清理。
也就是说,我大量重构了你的尝试。我添加了评论来解释更改:
<!DOCTYPE html>
<html>
<head>
<script src="https://d3js.org/d3.v6.min.js"></script>
</head>
<body>
<div id="ca"></div>
<script>
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 },
];
// this is usual d3 plot set
var margin = {top: 30, right: 30, bottom: 70, left: 60},
width = 460 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
// create the container with margins for axix
// plotting happens in the offset child g
var svg = d3.select("#ca")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
const groupedData = d3.group(runData, d => d.parallelism); //<-- group the data upfront
const durationDomain = d3.extent(runData, d => d.duration); //<-- min and max of duration
const innerDomain = d3.range(0, d3.max(groupedData, d => d[1].length)); //<-- domain inside the group
const outerDomain = Array.from(groupedData.keys()).sort(); //<-- domain of the groups
// scale of groups
const fx = d3
.scaleBand()
.domain(outerDomain)
.rangeRound([0, width])
.paddingInner(0.1);
// scale inside group
const x = d3
.scaleBand()
.domain(innerDomain)
.rangeRound([0, fx.bandwidth()])
.padding(0.05);
// color of each bar ordered by index
const color = d3
.scaleLinear()
.domain(innerDomain)
.range(['orange', 'steelblue', 'brown']);
// duration y-scale
const y = d3
.scaleLinear()
.domain([0, durationDomain[1]])
.nice()
.rangeRound([height, 0]);
svg
.append('g')
.selectAll()
.data(groupedData)
.join('g')
.attr('transform', (d) => `translate(${fx(d[0])},0)`) //<-- move group to position
.selectAll()
.data(([, d]) => d)
.join('rect')
.attr('x', (d,i) => x(i)) //<-- set position inside group
.attr('y', (d) => y(d.duration))
.attr('width', x.bandwidth())
.attr('height', (d) => y(0) - y(d.duration))
.attr('fill', (d,i) => color(i));
svg
.append('g')
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(fx));
svg
.append('g')
.call(d3.axisLeft(y));
</script>
</body>
</html>