d3js data joins深入理解

Data joins

给定一个数据数组和一个 D3 selection  我们就可以attach或者说是'join'数组中的每个数据到selection中的每个元素上。

这将使得我们的数据和可视化元素之间建立紧密的联系并实现可视化成为可能。

比如如果我们有以下SVG的circles:

<circle r="40" />
<circle r="40" cx="120" />
<circle r="40" cx="240" />
<circle r="40" cx="360" />
<circle r="40" cx="480" />

和下面的data数组:

var scores = [
  {
    "name": "Andy",
    "score": 25
  },
  {
    "name": "Beth",
    "score": 39
  },
  {
    "name": "Craig",
    "score": 42
  },
  {
    "name": "Diane",
    "score": 35
  },
  {
    "name": "Evelyn",
    "score": 48
  }
]

那么我们就可以选中这些circles并且join data到这个selection:

d3.selectAll('circle')
  .data(scores);
随后我们就可以对这个selection中的元素来设置相应属性为绑定的数据
d3.selectAll('circle')
  .attr('r', function(d) {
    return d.score;
  });

上面的代码就将每个圆的半径设置为每个人的score值的大小。(实际使用中,我们往往还会用到scaleSqrt比例尺,因为分数大小和可视化的半径大小往往需要一个映射!)

从这个形象化的结果,我们就实现了数据的可视化:score值越大,圆的半径就越大,因此只要看到圆大就知道其分数高!

Making a data join

var myData = [ 10, 40, 20, 30 ];

var s = d3.selectAll('circle');

s.data(myData);

数组可以包含任何类型的数据,比如对象数组:

var cities = [
  { name: 'London', population: 8674000},
  { name: 'New York', population: 8406000},
  { name: 'Sydney', population: 4293000}
];

var s = d3.selectAll('circle');

s.data(cities);

Data-driven modification of elements

d3.selectAll('circle')
  .attr('r', function(d) {
    return d;
  });

对每个selection中的element, d3都会调用.attr()函数, 传入该element的joined data d. 而上面匿名函数的返回值作为r的值设定到该元素中.

再比如以下elements:

<circle />
<circle />
<circle />
<circle />
<circle />

和如下数据

var myData = [ 10, 40, 20, 30, 50 ];

我们执行一下data join:

var s = d3.selectAll('circle');

// Do the join
s.data(myData);
s.attr('r', function(d) {
  return d;
});

传入 .attr() 的匿名函数将被调用 5 次 (selection中的每一个元素调用一次). 第一次调用时d 是10 因此第一个circle的r设置为10. 第二次调用时为40 因此第二个circle的r设置为40...

我们也可以对d做任何其他处理之后再返回,比如

s.attr('r', function(d) {
  return 2 * d;
});

再比如,如果元素绑定的数据大于40,我们则给该元素添加一个css类,以便更改这类元素的样式。

s.classed('high', function(d) {
  return d >= 40; // returns true or false
});

最后我们来使用 i 参数来设置其cx的位置:

s.attr('cx', function(d, i) {
  return i * 120;
});

总结如下:

var myData = [ 10, 40, 20, 30, 50 ];

var s = d3.selectAll('circle');

// Do the data join
s.data(myData);

// Modify the selected elements
s.attr('r', function(d) {
  return d;
  })
  .classed('high', function(d) {
    return d >= 40;
  })
  .attr('cx', function(d, i) {
    return i * 120;
  });

Arrays of objects

var cities = [
  { name: 'London', population: 8674000},
  { name: 'New York', population: 8406000},
  { name: 'Sydney', population: 4293000},
  { name: 'Paris', population: 2244000},
  { name: 'Beijing', population: 11510000}
];

var s = d3.selectAll('circle');

s.data(cities);

这时当我们根据joined data来修改元素属性时,d 将代表的是joined object. 这样对第一个元素,其 d 将是 { name: 'London', population: 8674000}.

我们再来根据每个城市的population设定circle的半径大小:

s.attr('r', function(d) {
    var scaleFactor = 0.000005;
    return d.population * scaleFactor;
  })
  .attr('cx', function(d, i) {
    return i * 120;
  });

当然,我们并不限于修改circle元素,加入我们还有rect和text元素,我们同样可以使用该data join并且设定这些元素的属性:

var cities = [
  { name: 'London', population: 8674000},
  { name: 'New York', population: 8406000},
  { name: 'Sydney', population: 4293000},
  { name: 'Paris', population: 2244000},
  { name: 'Beijing', population: 11510000}
];

// Join cities to rect elements and modify height, width and position
d3.selectAll('rect')
  .data(cities)
  .attr('height', 19)
  .attr('width', function(d) {
    var scaleFactor = 0.00004;
    return d.population * scaleFactor;
  })
  .attr('y', function(d, i) {
    return i * 20;
  })

// Join cities to text elements and modify content and position
d3.selectAll('text')
  .data(cities)
  .attr('y', function(d, i) {
    return i * 20 + 13;
  })
  .attr('x', -4)
  .text(function(d) {
    return d.name;
  });

Under the hood

当d3执行data join时,d3将对每个dom元素增加一个attribute: __data__,并且将对应的data赋值给该属性。实际上,如果我们需要设置其他额外的数据的话,也可以在.each函数中通过.attr('custom_attr','data')来添加并且在后面使用。

 

使用这种方式来检查是否有绑定好的数据,在需要调试代码行为是否符合预期时是非常有用的。

如果我们的数组长度大于或者小鱼对应的selection元素将会发生什么?

到目前为止,数据数组的长度和元素的个数是相同的。往往现实并非如此,这时我们就需要了解enter和exit的概念了,我们在另外一篇博客中描述。

What’s .datum for?

在有些情况下, (比如当处理地图相关的可视化时 geographic visualisations) ,我们可能需要给一个只含一个元素的selection绑定一个single data,比如:

var featureCollection = {type: 'FeatureCollection', features: features};
d3.select('path#my-map')
  .datum(featureCollection);

这将添加一个 __data__ attribute 到元素上并且 assigns the joined data (featureCollection )到这个元素上。可以查看 geographic visualisations 

大多数情况下.data用于data join, .datum用于特定场景。

posted @ 2017-07-16 17:16  世有因果知因求果  阅读(1311)  评论(0编辑  收藏  举报