在D3条形图中将具有相同X值的多条记录分组

问题描述 投票:0回答:1

我正在尝试为性能测试结果创建分组条形图。这是我第一次使用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>

javascript d3.js
1个回答
0
投票

对我来说,您所遵循的示例情节不必要地令人困惑。它不遵循通常的

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>

© www.soinside.com 2019 - 2024. All rights reserved.