D3.js is a JavaScript library for manipulating documents based on data.html
就像我以前文章提到的,D3js 给本身的定位并非图表,如官网所言,他是数据驱动dom。能理解这一点,就能将之灵活运用到各自场景。好比,给普通table的<td/>
加上数据背景色变成数据透视表;给文本font-size绑定数据,变成简易词云;或者你就是要画一些数据指标,等等。在这些操做中,首先要用到的就是将dom与数据关联起来,并对dom进行增删改。那么enter 与 exit 两个函数就是起到这个做用。(若是在react或者vue中,你能够理解为dom的diff,只是在d3中咱们是显式地直接操做dom)vue
code depend d3 version: v5 github.com/d3/d3-selec…react
首先咱们先理解一下概念:假设集合 collectionA,集合 collectionB,判断两者之间是否是有交集 equalBy 。git
这张图,初学d3的同窗都见过。可能解释的比较少的是中间的equalBy部分。collectionA 即指上一次绘制所棒定的数据,若是上次未绑定数据即[undefine, undefine ....],若是没有图形就是空数组[]。collectionB 是咱们要刷新视图的新数据集合。
如今咱们来看代码,这是官网的demo以下:程序员
const circle = svg.selectAll("circle").data(data) // UPDATE
.style("fill", "blue");
circle.exit().remove(); // EXIT
circle = circle.enter().append("circle") // ENTER
.style("fill", "green")
.merge(circle) // ENTER + UPDATE
.style("stroke", "black");
复制代码
其实这里有个默认选项 即上图提到的equalBy。 上面的代码咱们把默认的equalBy补齐以下:github
const equalBy = (d, i) => i; // 根据索引判断元素是否为同一个元素
const circle = svg.selectAll("circle").data(data, equalBy) // UPDATE
.style("fill", "blue");
circle.exit().remove(); // EXIT
circle = circle.enter().append("circle") // ENTER
.style("fill", "green")
.merge(circle) // ENTER + UPDATE
.style("stroke", "black");
复制代码
因此,在没有指定equalBy的时候,是根据索引判断元素是否为同一个元素。数组
const collectionA = [{ id: 1, text: 1 }, { id: 2, text: 2 }, { id: 3, text: 3 }];
const collectionB = [{ id: 1, text: 1 }, { id: 2, text: 2 }, { id: 4, text: 4 }, { id: 5, text: 5 }];
复制代码
_但咱们正常更新数据时候,equal(collectionA[2],collectionB[2]) == false ,但你未设置equalBy的时候,即默认index为标记,这里就认为是同一个元素 ,equal(collectionA[2],collectionB[2]) == true(就好像一个程序员,10岁的和30岁的他,他的身份证号没有变,只是头发可能由于写代码剩的不太多,他的特征属性发生了变化),因此它属于update部分。
机智的你确定能够想到,那么若是我给数据集每一个对象一个身份证。bash
const equalBy = obj => obj.id;
const circle = svg.selectAll("circle").data(data, equalBy) // UPDATE
.style("fill", "blue");
复制代码
这时候对比属于enter exit 仍是update 则是根据 obj id是否是仍是旧的那个。(也就是,判断昨天的你和今天的你是不是一我的,是跟据你的身份证id来判断)。
咱们来个实践:app
// 伪代码
const equalBy = (d, i) => i; // 为设置,即d3默认规则 selection.data(data)
const collectionA = [{ id: 1, text: 1 }, { id: 2, text: 2 }, { id: 3, text: 3 }];
const collectionB = [{ id: 1, text: 1 }, { id: 2, text: 2 }, { id: 4, text: 4 }, { id: 5, text: 5 }];
// enter() = [{ id: 5, text: 5 }]
// exit() = []
// update = [{ id: 1, text: 1 }, { id: 2, text: 2 }, { id: 4, text: 4 }]
const equalBy = obj => obj.id; // 自定义设置规则 selection.data(data, equalBy)
const collectionA = [{ id: 1, text: 1 }, { id: 2, text: 2 }, { id: 3, text: 3 }];
const collectionB = [{ id: 1, text: 1 }, { id: 2, text: 2 }, { id: 4, text: 4 }, { id: 5, text: 5 }];
// enter() = [{ id: 4, text: 4 }, { id: 5, text: 5 }]
// exit() divsext: 3 }]
// update = [{ id: 1, text: 1 }, { id: 2, text: 2 x}]
复制代码
enter exit update 其实就是对dom元素的增删该,这很容易让咱们联想到react或者vue。这里我以react为例来讲说。
首先,equalBy 至关于react组件key (reactjs.org/docs/lists-… 即标识这个组件的身份id。一般对于exit咱们会作删除操做,enter与update 作render component操做(假设咱们的需求仅管理dom,复杂操做本文暂不提,后续独立篇幅)。dom
那么 exit 在react中咱们并不须要作什么,数据不存在,那么天然就被销毁。上诉的两种写法至关于以下代码:
class Demo extens React.Component {
render() {
const { data } =this.props;
return (<div> {data.map((obj,i) => <div ref={c => (c.__data__ = obj)} key={i}>obj.text</div>)} </div>)
}
}
class Demo extens React.Component {
render() {
const { data } =this.props;
return (<div> {data.map((obj,i) => <div ref={c => (c.__data__ = obj)} key={obj.id}>obj.text</div>)} </div>)
}
}
复制代码
细心的你可能发现了一行代码ref={c => (c.__data__ = obj)}
咱们用react组件建立的元素,若是后续须要使用d3-selection 继续作一系列操做,好比能够在componensDidUpdate后执行d3-transition动画等等,咱们要作的就是将数据关联到dom中去。 实际上,d3js 就是经过dom的propties __data__
来关联数据的(注:dom的propties与attribute 的区别)。因此咱们能够在ref 中获取dom实例,并赋值挂载。
d3js: d3js.org/
demo工具:beta.observablehq.com/
demo地址: beta.observablehq.com/@leannechn/…
react keys: reactjs.org/docs/lists-…