第 41 章 D3.js中的数据绑定与更新

卡洛斯·科罗内尔(Carlos Coronel),史蒂文·莫里斯(Steven Morris)和彼得·罗布(Peter Rob)在其名著《Database Systems: Design, Implementation, and Management》中认为:

数据是纯粹的事实。“纯粹”意味着这种事实没有经过任何处理,其意义也没有得到揭示。而信息是数据处理的结果,它揭示了数据的意义。

而数据的可视化更注重人对数据内在观察的展示。而D3能将数据和图形领域联系起来。

41.1 进入—更新—退出模式

D3使用它称为“进入—更新—退出”(enter-update-exit)模式将数据和图形建立联系。进入—更新—退出模式是所有基于D3的可视化技术的基石。

  • 进入模式 enter函数将返回一个新的D3对象集合,这个集合包含了所有没有可视化的数据,我们可以在这个集合上级联调用相关函数,创建新的图形来表示给定的数据。这个集合的状态称为进入模式(Enter Mode)。
  • 更新模式 data函数的返回值是一个(绑定了数据的)新D3对象集合,也就是数据与可视化图形的交集中的所有元素。现在,我们就可以针对新集合调用相关函数,并对其中的元素进行更新。这个新集合所在的状态通常称为更新状态(Update Mode)。
  • 退出模式 当我们在一个拥有数据绑定的D3对象集合上调用selection.data(data).exit函数的时候,会得到一个新的D3对象集合。这个新的集合包含原集合上没有关联任何有效数据的图形。当然,作为一个有效的D3选集对象,我们也可以在这个新的集合上级联调用相关函数以更新这些图形,或者当我们不再需要这些图形时删除它们。这个新的集合状态称为退出模式(Exit Mode)。

除了上述三种模式之外,还有一种合并模式(merge),merge()函数将传递给merge函数的给定选集与调用函数的选集进行合并,并返回二者的并集作为新的选集。在进入—更新—退出(enter-update-exit)模式中,合并函数通常用于构造一个同时涉及进入模式和更新模式的选集。

41.1.1 理解Update、Enter、Exit

updateenter:例如将数组[3,6,9,12,15]绑定到三个<p>上。可以想象到,数组的最后两个元素没有可以绑定的元素,这时D3会建立两个空的元素与数组最后的两个数据相对于,那么这部分就称为Enter。而有元素与数据对应的部分就称为Update。 exit:如果数组[3]绑定到三个<p>上,可以想象,最后两个<p>没有可绑定的数据,那么没有数据绑定的部分就称为Exit

image.png

d3.select(document.body).append('p')
d3.select(document.body).append('p')
d3.select(document.body).append('p')

var dataset = [3, 6, 9, 12, 15];
var p = d3.select("body")
    .selectAll("p");
var update = p.data(dataset) //绑定数据,并得到update部分
var enter = update.enter(); //得到enter部分
//下面检验是否真的得到对于update的处理
update.text(function(d, i) {
    return "update: " + d + ",index: " + i;
})
//对于enter的处理
//注意,这里需要先添加足够多的<p>,然后在添加文本
var pEnter = enter.append("p") //添加足够多的<p>
pEnter.text(function(d, i) {
            return "enter: " + d + ",index: " + i;
        })
$$.html(document.body.innerhtml)
var dataset = [ 3 ];

//选择body中的p元素
var p = d3.select("body").selectAll("p");

//获取update部分
var update = p.data(dataset);

//获取exit部分
var exit = update.exit();

//update部分的处理:更新属性值
update.text(function(d){
    return "update " + d;
});

//exit部分的处理:修改p元素的属性
exit.text(function(d){
        return "exit";
    });

//exit部分的处理通常是删除元素

exit.remove();

41.2 将数组绑定为数据

使用JavaScript中的数组作为D3可视化中的数据是最常见的做法。例如,你有一个数组,它里面存储了许多数据元素,你希望生成一系列图形元素,其中每一个图形代表数组中的一个记录,并且当数组中的数据进行更新的时候,图形元素也发生相应的改变。

var data = [10, 15, 30, 50, 80, 65, 55, 30, 20, 10, 8];

var bars = d3.select("body").selectAll("div") //宣布页面上应该有一系列div元素,已构成图形集合
    .data(data); //将数据绑定到这些将要创建的图形元素上 update

bars.enter() // Enter选出还没有可视化的数据
    .append("div") //         为选定的每一个数据创建一个div元素,并将其加入body元素中
    .style("background-color", "#ccc")
    .style('text-align', 'right')
    .style('margin', '2px')
    .style("width", function(d) {
        return (d * 3) + "px";
    })
    .text(function(d) {
        return d;
    });
10
15
30
50
80
65
55
30
20
10
8
data = data.map(function(i) {
    return i + 3;
});

bars = d3.select("body").selectAll("div")
    .data(data);

bars.enter()
    .append("div")
    .style("background-color", "#ccc")
    .style('text-align', 'right')
    .style('margin', '2px')
    .merge(bars) //对进入模式选中的元素(进入模式选集)和更新模式选中的元素(更新模式选集)进行合并,从而返回二者的并集
    .style("width", function(d) {
        return (d * 3) + "px";
    })
    .text(function(d) {
        return d;
    })

$$.html(document.body.innerHTML);
13
18
33
53
83
68
58
33
23
13
11

动态转换方法实际上有d和i两个形式参数。其中,第一个参数d代表与图形元素相关联的数据,这个我们之前已经提过了;第二个参数i是一个从0开始的当前图形元素的下标。

41.3 将对象绑定为数组

比数组更为常见的数组形式,是对象。

var dataObject = [ // <- A
    {
        width: 10,
        color: 23
    }, {
        width: 15,
        color: 33
    },
    {
        width: 30,
        color: 40
    }, {
        width: 50,
        color: 60
    },
    {
        width: 80,
        color: 22
    }, {
        width: 65,
        color: 10
    },
    {
        width: 55,
        color: 5
    }, {
        width: 30,
        color: 30
    },
    {
        width: 20,
        color: 60
    }, {
        width: 10,
        color: 90
    },
    {
        width: 8,
        color: 10
    }
];
var colorScale = d3.scaleLinear()
    .domain([0, 100])
    .range(["#add8e6", "blue"]); // <- B
function render(data) {
    var bars = d3.select("body").selectAll("div.h-bar")
        .data(dataObject); // Update
    // Enter
    bars.enter()
        .append("div")
        .attr("class", "h-bar")
        .merge(bars) // Enter + Update
        .style("width", function(d) { // <- C
                style("width", function(d) { // <- C
                        return (d.width * 5) + "px"; // <- D
                    })
                    .style("background-color", function(d) {
                        return colorScale(d.color); // <- E
                    })
                    .text(function(d) {
                        return d.width; // <- F
                    });
                // Exit
                bars.exit().remove();
            }

            function randomValue() {
                return Math.round(Math.random() * 100);
            }
            style("width", function(d) { // <- C
                return (d.width * 5) + "px"; // <- D
            })
            .style("background-color", function(d) {
                return colorScale(d.color); // <- E
            })
            .text(function(d) {
                return d.width; // <- F
            });
            // Exit
            bars.exit().remove();
        }

    function randomValue() {
        return Math.round(Math.random() * 100);
    }

    render(data);

41.4 打开外部数据文件

D3.js 也可以外部数据文件为数据来源,我们可以使用异步编程方式,打开外部数据。