d3:从2d对象数组绘制多线折线图

时间:2019-06-15 00:27:12

标签: javascript reactjs d3.js

我正在尝试在d3中绘制多线折线图,然后在每个刻度上对其进行更新。我遇到的问题是我最终遇到一个错误,即数据为零的图形。我在这里找到了其他答案,但它们要么全部用于d3的较旧版本,要么用于比我更简单的数据。

我的数据看起来像这样(显然是简化的,但是结构是一样的):

let data = [
  [{name: "a", val: 1}, {name: "b", val: 2}],
  [{name: "a", val: 2}, {name: "b", val: 3}],
  [{name: "a", val: 3}, {name: "b", val: 4}],
  [{name: "a", val: 4}, {name: "b", val: 5}],
]

我想要两行:每个索引的A值之一和每个索引B的值之一。 Other posts on SO提出了相同的问题,但问题是d3的3.0。

根据这个问题,我推断出以下内容:

let svg = ...
let g = svg.append("g")...

let line = d3.line()
    .x((d:any, i:number) => x(i))
    .y((d:any) => y(d.value))
g.selectAll(".line")
    .data(data)
    .enter().append("path")
    .attr("class", "line")
    .attr("d", line);

这会在每个刻度上抛出一个错误:Error: <path> attribute d: Expected number, "MNaN,20Z",我也不太惊讶,因为TypeScript也抱怨:

TS2345: Argument of type 'Line<[number, number]>' is not assignable to parameter of type 'ValueFn<SVGPathElement, GraphablePacketCount[], string | number | boolean | null>'.
    Types of parameters 'data' and 'datum' are incompatible.

我尝试过的其他东西:

for (const datum of data) {
    g.append("svg:line").data(datum)...
}

还有d3's general update pattern的行化版本:

let lines = g.selectAll("line").data(data);
lines.enter().append(...);

只会导致一个空图。在这种情况下,console.log(lines.enter())给了我数据,只是没有渲染。

接下来我要尝试什么?

1 个答案:

答案 0 :(得分:0)

您的数据数组具有val个字段,但是在行的y访问器中,您使用value

但是即使如此,您的问题仍然是您按错误的维度对数据进行了切片。您将数据绑定到行,因此数据(外部)数组中的每个元素应该是单行的数据,然后内部数组应该是该行的点(因为行生成器接受点数组)。但是,您却遇到了其他问题,因为您首先要经过点检,然后才可以区分线路。

您必须调整数据,方法是在源头进行调整,或者在绑定之前进行转换。参见下面的示例

<!DOCTYPE html>

<head>
  <meta charset="utf-8">
  <script src="https://d3js.org/d3.v4.min.js"></script>
  <style>
    body {
      margin: 0;
      position: fixed;
      top: 0;
      right: 0;
      bottom: 0;
      left: 0;
    }
  </style>
</head>

<body>
  <script>
    // Feel free to change or delete any of the code you see in this editor!
    var svg = d3.select("body").append("svg")
      .attr("width", 300)
      .attr("height", 100);

    let data = [
      [{
        name: "a",
        val: 1
      }, {
        name: "b",
        val: 2
      }],
      [{
        name: "a",
        val: 2
      }, {
        name: "b",
        val: 3
      }],
      [{
        name: "a",
        val: 3
      }, {
        name: "b",
        val: 4
      }],
      [{
        name: "a",
        val: 4
      }, {
        name: "b",
        val: 5
      }],
    ];

    var byLineDataMap = data.reduce(function(lineData, point) {
      point.forEach(function(linePoint) {
        if (!lineData[linePoint.name])
          lineData[linePoint.name] = [];
        lineData[linePoint.name].push({
          val: linePoint.val
        });
      });
      return lineData;
    }, {});

    var byLineData = Object.entries(byLineDataMap);
    /*
            var byLineData = [
            	['a', [{val: 1}, ...]],
            	['b', [{val: 2}, ...]]
            ];
    */

    var x = d3.scaleLinear()
      .domain([0, 4])
      .range([50, 250]);

    var y = d3.scaleLinear()
      .domain([1, 5])
      .range([80, 20]);

    var color = d3.scaleOrdinal()
      .domain(['a', 'b'])
      .range(['red', 'blue']);

    var lineGen = d3.line()
      .x(function(d, i) {
        return x(i)
      })
      .y(function(d, i) {
        return y(d.val)
      });

    svg.selectAll('path')
      .data(byLineData)
      .enter()
      .append('path')
      .style('stroke', function(d) {
        return color(d[0]);
      })
      .attr('d', function(d) {
        return lineGen(d[1]);
      });
  </script>
</body>