前言
d3js数据绑定是d3js的一大优点, 尼写好一个d3js的数据绑定函数后,再有数据更新(新增、修改、删除)后再调用该函数就会给你更新数据了。
面临问题
再大数据量1w+, 甚至更多数据绘制的时候。会发现经过咱们的数据绑定函数重新
reload会时间略久。我这边也查看了下d3js数据绑定的源码 data源码链接, 是用m*n的算法时间复杂度,也就是for 嵌套for 做diff分层(unpdate == data()、remove == exit() 、add == enter())。
简单回顾下数据绑定
data 数据绑定可以分为3层:d3js数据绑定
- 1、渲染层:
enter()(数据绑定会根据你绑定的值区分下次调用有咩有进入enter层) - 2、修改层:
data()(数据绑定会根据你绑定的数据绑判断你有没有修改数据) - 3、删除层:exit() (数据绑定会根据你绑定的数据检测到你删除了那些数据)
一个简易的数据绑定函数如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div>
<button onclick="remove()">删除一条数据</button>
<button onclick="add()">新增一条数据</button>
<button onclick="exit()">修改一条数据</button>
<button onclick="all()">新增一条数据,并修改一条数据,并删除一条数据</button>
</div>
</body>
</html>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script>
const svg = d3.select('body')
.append('svg')
.attr('width', 500)
.attr('height', 500);
const data = [{id: 1, fill: 'red', x: 20, y: 20}, {id: 2, fill: 'blue', x: 40, y: 40}, {id: 3,fill: 'yellow',x: 60, y: 60}, {id: 4, fill: 'black',x: 80, y: 80}];
draw()
function draw() {
const update = svg.selectAll('rect')
.data(data, d => d.id);
//修改层
update.attr('x', (d,idx) => d.x)
.attr('y', (d,idx) => d.y)
.attr('fill', (d) => d.fill)
//渲染层
const enter = update.enter();
//删除层
const exit = update.exit();
enter.append('rect')
.attr('width', 20)
.attr('height', 20)
.attr('x', (d,idx) => d.x)
.attr('y', (d,idx) => d.y)
.attr('fill', (d) => d.fill)
.attr('stroke', 'blue')
.attr('strokeWidth',1)
exit.remove()
}
function remove() {
data.pop();
draw();
}
function add() {
data.push({id: Math.random() * 200, fill: 'violet', x: Math.random() * 200, y: Math.random() * 200});
draw();
}
function exit() {
data[0].fill = 'orange';
draw();
}
function all() {
data.shift();
data.push({id: Math.random() * 200, fill: 'green', x: 150, y: 150});
data[0].fill = 'pink';
console.log(data,'data')
draw();
}
</script>
重写数据绑定算法
其实d3js数据绑定做的就是分层(update、remove、add),也就是用上一次给的数据跟这次给的数据做对比。
diff逻辑
-
1、
add层一定是新数据有,老数据没有的(newData = [{id: 1, name: 1}, {id: 2, name: 2},{id: 3, name: 3}], lastData = [{id: 1, name: 1}, {id: 2, name: 2}]其中数据{id: 3, name: 3}就是add层的了); -
2、
update层一定是新数据和老数据都存在有数据发生变化的(newData = [{id: 1, name: 1}], lastData = [{id: 1, name: 2}]name值不同也就是说这条数据修改了应该进入到update层) -
3、
remove层一定是老数据有新数据没有了(newData = []; lastData = [{id: 1, name}]其中{id: 1, name}就就是要进入remove层了)
diff code
import { isEqual } from 'lodash';
// 视图层数据处理 以id作为key 做diff
// add 新增绘制层
// remove 删除层
// update 修改层
export function diffLayeredBy(newData, lastData, key = 'id') {
const newDataMap = new Map();
const lastDataMap = new Map();
const update = [];
lastData.forEach((item) => lastDataMap.set(item[key], item));
const add = newData.filter((item) => { // add
newDataMap.set(item[key], item);
const last = lastDataMap.get(item[key]);
if (!last) {
return item;
}
// update
if (!isEqual(last, item)) { // 没有引入loadsh的 可以简单用JSON.stringify(last) !== JSON.stringify(item) 做对比
update.push(item);
}
return false;
});
const remove = lastData.filter((item) => !newDataMap.get(item[key])); //remove
return { add, remove, update };
}
总结:把d3js的m*n的时间算法复杂度改成m+n, 先准备好需要的数据转化成map,完事再做数据分层。
把上面代码HTML改成使用我的diff算法
效果图:
源码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div>
<button onclick="remove()">删除一条数据</button>
<button onclick="add()">新增一条数据</button>
<button onclick="exit()">修改一条数据</button>
<button onclick="all()">新增一条数据,并修改一条数据,并删除一条数据</button>
</div>
</body>
</html>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script>
function diffLayeredBy(newData, lastData, key = 'id') {
const newDataMap = new Map();
const lastDataMap = new Map();
const update = [];
lastData.forEach((item) => lastDataMap.set(item[key], item));
const add = newData.filter((item) => { // 新增 add
newDataMap.set(item[key], item);
const last = lastDataMap.get(item[key]);
if (!last) {
return item;
}
if (JSON.stringify(last) !== JSON.stringify) { // 没有引入loadsh的 可以简单用JSON.stringify(last) !== JSON.stringify(item) 做对比
update.push(item); // 修改 update
}
return false;
});
const remove = lastData.filter((item) => !newDataMap.get(item[key])); // remove 删除
return { add, remove, update };
}
</script>
<script>
const ids = {};
const getIds = () => {
let currentId = (Math.random() * 20000).toFixed(0);
if (!ids[currentId]) {
ids[currentId] = currentId;
} else {
currentId = getIds();
}
return currentId;
};
const svg = d3.select('body')
.append('svg')
.attr('width', 500)
.attr('height', 500);
let lastData = [];
const data = [{ id: 1, fill: 'red', x: 20, y: 20 }, { id: 2, fill: 'blue', x: 40, y: 40 }, { id: 3, fill: 'yellow', x: 60, y: 60 }, { id: 4, fill: 'black', x: 80, y: 80 }];
draw()
function draw() {
const { add, update, remove } = diffLayeredBy(data, lastData);
console.log(add, update, remove, 'add, update, remove')
add.forEach(item => { // 新增层
svg.append('rect')
.datum(item) // 单个数据绑定
.attr('width', 20)
.attr('id', (d) => `node-${d.id}`)
.attr('height', 20)
.attr('x', (d, idx) => d.x)
.attr('y', (d, idx) => d.y)
.attr('fill', (d) => d.fill)
.attr('stroke', 'blue')
.attr('strokeWidth', 1)
});
update.forEach((item) => { // 修改层(重新绑定新数据)把修改的值重新赋值
d3.select(`#node-${item.id}`)
.datum(item) // 重新绑定新值
.attr('x', (d, idx) => d.x)
.attr('y', (d, idx) => d.y)
.attr('fill', (d) => d.fill)
});
remove.forEach((item) => { // remove 层(直接获取到删除)
d3.select(`#node-${item.id}`).remove();
});
lastData = JSON.parse(JSON.stringify(data)); // 我就用简单方法实现cloneDeep了, 一定要深克隆
}
function remove() {
data.pop();
draw();
}
function add() {
data.push({ id: getIds(), fill: 'violet', x: Math.random() * 200, y: Math.random() * 200 });
draw();
}
function exit() {
data[0].fill = 'orange';
draw();
}
function all() {
data.shift();
data.push({ id: getIds(), fill: 'green', x: 150, y: 150 });
data[0].fill = 'pink';
console.log(data, 'data')
draw();
}
</script>
往期推荐
- 【d3js】用d3js带你实现一个简易柱形图🎈
- 【d3js】手把手教你绘制一个折线图
- 【d3js】d3js实现一个围绕圆运动的动画效果
- 【看了必懂D3JS(一)】d3js select、selectAll、attr、style、append
- 【看了必懂D3JS(二)】d3js data、datum 实现一个数据驱动绘制的方法
- 【d3js(五)】d3.transtion() 过渡动画篇
结束语
使用自定义的
diffLayeredBy方法确实会reload快很多呢!!!!!!如果您d3还有其他优化点,欢迎留言讨论呀!!!
- 大家好 我是三原,多谢您的观看,我会更加努力(๑•̀ㅂ•́)و✧多多总结。
- 每个方法都是敲出来验证过通过的,有需要可以放心复制。
- 如果您给帮点个赞👍就更好了 谢谢您~~~~~
- 期待您的关注