【问题标题】:Can d3.js draw two scatterplots on the same graph using data from the same source?d3.js 可以使用来自同一来源的数据在同一图表上绘制两个散点图吗?
【发布时间】:2012-07-26 15:11:44
【问题描述】:

我发现的所有 d3 教程都使用排列在对象数组中的数据,他们从中为数组中的每个对象绘制一个点。给定以下结构的数据:

data = [
     {id: 1, x: 4, y: 10, type: 1},
     {id: 2, x: 5, y: 20, type: 2}
     ...
]

x 和 y 值用于制作散点图。 type 参数用于改变每个点的颜色。请参阅此 jsfiddle 示例:http://jsfiddle.net/uxbHv/

不幸的是,我有不同的数据结构,我不知道如何通过为每个对象绘制两个数据点来创建相同的图表。以下是一些示例数据:

dataSet = [
     {xVar: 5, yVar1: 90, yVar2: 22},
     {xVar: 25, yVar1: 30, yVar2: 25},
     {xVar: 45, yVar1: 50, yVar2: 80},
     {xVar: 65, yVar1: 55, yVar2: 9},
     {xVar: 85, yVar1: 25, yVar2: 95}
]

我可以针对 yVar1 或 yVar2 分别绘制 xVar 图表,但我不知道如何在同一张图表上获得两者:http://jsfiddle.net/634QG/

【问题讨论】:

    标签: javascript d3.js


    【解决方案1】:

    使用data-join 时的一般规则是您需要从数据到元素的一对一映射。因此,如果散点图中有两个系列,则需要两个容器元素(例如 G elements)来表示系列。由于您目前只有一个 data 数组,因此您还需要使用 array.map 将数据表示转换为具有相同表示的两个并行数组。这样,您就不必为每个系列重复代码。

    假设您的数据以 CSV 文件的形式表示,其中一列用于 x 值,而其他多列用于每个系列的 y 值:

    x,y1,y2
    5,90,22
    25,30,25
    45,50,80
    65,55,9
    85,25,95
    

    如果您希望代码完全通用,您首先需要计算系列的名称,例如["y1", "y2"]。 (如果您在 CSV 文件中添加了第三列,它可能是 ["y1", "y2", "y3"]。)您可以使用 d3.keys 计算名称,它会从对象中提取命名属性。例如,d3.keys({foo: 1, bar: 2}) 返回["foo", "bar"]

    // Compute the series names ("y1", "y2", etc.) from the loaded CSV.
    var seriesNames = d3.keys(data[0])
        .filter(function(d) { return d !== "x"; })
        .sort();
    

    现在您有了系列名称,您可以创建一个由点组成的数组。外部数组表示系列(其中有两个),内部数组存储数据点。您可以同时将点转换为一致的表示(具有xy 属性的对象),从而允许您跨系列重用代码。

    // Map the data to an array of arrays of {x, y} tuples.
    var series = seriesNames.map(function(series) {
      return data.map(function(d) {
        return {x: +d.x, y: +d[series]};
      });
    });
    

    请注意,此代码使用 + 运算符将 CSV 值强制转换为数字。 (CSV 文件是无类型的,所以它们最初是字符串。)

    现在您已将数据映射为常规格式,您可以为每个系列创建 G 元素,然后为每个点圈出其中的元素。生成的 SVG 结构将如下所示:

    <g class="series">
      <circle class="point" r="4.5" cx="1" cy="2"/>
      <circle class="point" r="4.5" cx="3" cy="2"/>
      …
    </g>
    <g class="series">
      <circle class="point" r="4.5" cx="5" cy="4"/>
      <circle class="point" r="4.5" cx="7" cy="6"/>
      …
    </g>
    

    以及对应的D3代码:

    // Add the points!
    svg.selectAll(".series")
        .data(series)
      .enter().append("g")
        .attr("class", "series")
        .style("fill", function(d, i) { return z(i); })
      .selectAll(".point")
        .data(function(d) { return d; })
      .enter().append("circle")
        .attr("class", "point")
        .attr("r", 4.5)
        .attr("cx", function(d) { return x(d.x); })
        .attr("cy", function(d) { return y(d.y); });
    

    我还添加了一些代码,通过向包含的 G 元素添加填充样式来为每个系列分配唯一的颜色。当然,有很多不同的方法可以做到这一点。 (例如,您可能希望更具体地了解每个系列的颜色。)我还省略了计算 xy 域的代码缩放(以及渲染轴),但如果您想查看整个工作示例:

    【讨论】:

    • 几乎是一个教程...这个问题和其他由 Mike Bostock 通过如此长的回答回答的类似问题应该放在常见问题解答中
    【解决方案2】:

    将每个数据点的两个圆圈放入一个 svg:g 元素中。这会生成数据到元素的一对一映射,但仍允许您显示两个不同的点。

    var nodeEnter = vis1.selectAll("circle")
          .data(dataSet)
          .enter()
          .insert("svg:g");
    
    nodeEnter.insert("svg:circle")
               .attr("cx", function (d) { return 100 - d.xVar})
               .attr("cy", function (d) { return 100 - d.yVar1})
               .attr("r", 2)
               .style("fill", "green");
    
    nodeEnter.insert("svg:circle")
               .attr("cx", function (d) { return 100 - d.xVar})
               .attr("cy", function (d) { return 100 - d.yVar2})
               .attr("r", 2)
               .style("fill", "blue");
    

    工作JSFiddle

    【讨论】:

    • 感谢您的解决方案!我看到这如何处理静态数据,但现在我想知道添加转换和退出是否会很困难......回到 jsfiddle......
    • 这可能(现在)可行,但我强烈反对这种模式——API 不是为这种方式设计的。使用数据连接时,您应该始终在数据和元素之间进行一对一的映射。两次插入或附加到输入选择是对 API 的滥用。这种情况下正确的解决方案是创建两个vis元素,并将数据映射到两个表示一致的并行数组中。
    • @AndrewStaroscik 不,这仍然是针对同一选择输入两次。 (您创建了两个不同的选择对象,但从概念上讲,它们代表相同的元素,因此它们会发生碰撞。)请参阅我刚刚添加的答案。
    • @mbostock 已更新。我的更新是否有意义,还是我仍然只是在破解 D3 api?
    • 我也很好奇 Mike 对更新的看法。我的理解是,以这种方式添加额外的子元素(由表示与输入选择的一对一映射的父元素分组)是可以的。 Mike 的解决方案可能更干净,因为数据确实代表 2 个系列。但是在某些情况下,您可能希望从完全相同绑定数据创建多个子元素,我相信您在更新代码中显示的分组技术是正确的方法。但也许迈克可以证实或否认这一点。 :)
    猜你喜欢
    • 2019-01-14
    • 1970-01-01
    • 2020-11-12
    • 1970-01-01
    • 2020-05-05
    • 1970-01-01
    • 2021-08-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多