使用selection.filter获取多个选择的最佳方法

时间:2019-04-15 17:16:29

标签: javascript d3.js

我只是在玩d3.js,我想知道什么是使用selection.filter()或以其他方式在单个循环中获得多个选择的最佳方法。

从数组的角度考虑,我将使用Array.prototype.filter()来获取所需的数据集。每当我需要基于不同条件的多组数据时,我都会使用Array.prototype.reduce()并将数据作为对象推送到累加器中的各个键。

因此,对于D3选择,我将如何在单个循环中过滤并获得针对不同条件的选择(类似于d3.reduce())。这样我就可以对过滤的选择使用选择方法。我阅读了文档,并且知道从v4开始,选择不再是数组。

2 个答案:

答案 0 :(得分:2)

您的问题很有趣:如何在单个循环中过滤选择并填充多个选择。但是,我必须说,它就像它一样有趣,很可能是无用的:惯用的方法很简单,在于只需执行几个过滤器:

const bigSelection = d3.selectAll(foo);

const smallSelection1 = bigSelection.filter(function with condition 1);
const smallSelection2 = bigSelection.filter(function with condition 2);
const smallSelection3 = bigSelection.filter(function with condition 3);
//etc...

但是,只是出于好奇:这可能吗?是的。但是使用selection.each,而不是selection.filter

我的第一个想法是使用selection.merge,但是我不得不迅速放弃它,因为正如Bostock(D3创建者)所说,

  

selection.merge的当前实现仅处理两个选择具有相同结构(即,相同的父代和索引)并返回具有相同结构的选择的情况。

因此,我决定只连接节点,您可以使用Array.prototype.concat进行连接。这就是想法:首先,我们声明一些空选择...

let foo = d3.selectAll(null);
let bar = d3.selectAll(null);
let baz = d3.selectAll(null);

然后,在更大的选择中使用each,我们检查一个属性(在这里名为label)并相应地连接节点:

bigSelection.each(function(d) {
  if (d.label === "foo") {
    foo = d3.selectAll(foo.nodes().concat(this))
  } else if (d.label === "bar") {
    bar = d3.selectAll(bar.nodes().concat(this))
  } else {
    baz = d3.selectAll(baz.nodes().concat(this))
  }
});

这里是一个演示。大选择包含10个圆圈,所有圆圈均为黑色。然后,在each中,我们填充三个选择(circlesFoocirclesBarcirclesBaz),分别用绿色,红色和蓝色绘制:

const data = [{
    x: 20,
    label: "foo"
  },
  {
    x: 50,
    label: "bar"
  }, {
    x: 80,
    label: "foo"
  }, {
    x: 110,
    label: "baz"
  }, {
    x: 140,
    label: "bar"
  }, {
    x: 170,
    label: "baz"
  }, {
    x: 200,
    label: "baz"
  }, {
    x: 230,
    label: "foo"
  }, {
    x: 260,
    label: "foo"
  }, {
    x: 290,
    label: "bar"
  },
];

const svg = d3.select("svg");
const circles = svg.selectAll(null)
  .data(data)
  .enter()
  .append("circle")
  .attr("cy", 75)
  .attr("r", 10)
  .attr("cx", d => d.x);

let circlesFoo = d3.selectAll(null);
let circlesBar = d3.selectAll(null);
let circlesBaz = d3.selectAll(null);

circles.each(function(d) {
  if (d.label === "foo") {
    circlesFoo = d3.selectAll(circlesFoo.nodes().concat(this))
  } else if (d.label === "bar") {
    circlesBar = d3.selectAll(circlesBar.nodes().concat(this))
  } else {
    circlesBaz = d3.selectAll(circlesBaz.nodes().concat(this))
  }
});

circlesFoo.style("fill", "green");
circlesBar.style("fill", "red");
circlesBaz.style("fill", "blue");
<svg></svg>
<script src="https://d3js.org/d3.v5.min.js"></script>

答案 1 :(得分:0)

D3js的选择具有过滤功能。

  

来自API

     

selection.filter(filter)<>

     

过滤选择,返回一个新选择,该选择仅包含指定过滤器为true的元素。过滤器可以指定为选择器字符串或函数。如果过滤器是一个函数,则会为每个选择的元素按顺序评估,依次传递当前数据(d),当前索引(i)和当前组(节点),并将其作为当前DOM元素(节点[i])。

这意味着如果您要根据数据附加一堆形状(例如圆形),则可以使用filter函数指定一个函数,如果该函数返回true,则将为那一点。

例如,

svg.selectAll("dot")
    .data(data)
    .enter()
    .append("circle")
    .filter(function(d) { return d.value < 100 }) //filters those data points below value of 100
    .style("fill", "red")
    .attr("r", 3.5)
    .attr("cx", function(d) { return x(d.x); })
    .attr("cy", function(d) { return y(d.y); });

如果您想编辑已经存在的对象,只需在选择后使用filter函数,即selectAll

d3.selectAll("circle")
  .filter(function(d) { return d.value < 20 }) //filters those data points below value of 20
  .attr("fill", "blue");

很遗憾,您没有提及任何代码或特定示例来更好地指导您。