I'm currently working on creating a grouped bar chart to display performance test results using D3 for the first time.
The X axis should represent parallelism
, indicating the number of threads used, while the Y axis will show the duration in milliseconds. If there are multiple records with the same parallelism value, they should be displayed together, ideally sorted by duration although sorting is not critical.
I found inspiration in this code snippet:
After spending over an hour tweaking the code, I am still struggling to get the grouping right. The issue seems to lie in how I define the fx
and x
functions, and in how I assign
.attr("x", d => marginLeft + fx(d.duration))
. Despite trying numerous variations, I haven't been successful yet.
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);
// Sorting by duration but grouping by parallelism attempt
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 bars
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", ([parallelism]) => `translate(${x(parallelism)},0)`)
.selectAll()
.data(([, d]) => d)
.join("rect")
.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));
svg.append("g")
.attr("transform", `translate(${marginLeft},${height - marginBottom})`)
.call(d3.axisBottom(x));
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>