当用d3js绘制数据量过大,reload过慢!!!重写d3js数据绑定算法-灵析社区

JOHO

前言

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>


阅读量:461

点赞量:0

收藏量:0