Skip to content
On this page

d3数据绑定

TIP

d3js数据绑定的实现是一大亮点,必须且掌握数据绑定。 等d3js数据绑定可以在用select获取到的元素找到__data__,这个就是绑定到的数据。d3js数据绑定又可以把数据分为新增层、修改层、删除层,往下看。

data篇

TIP

  • 1、data是绑定一个数组的形势 data(data)这个样默认是绑定的data的索引,也可以这个样data(data, (d,idx) => d)指定绑定某个字段,这个绑定很重要后面会说到
  • 2、绘制多个rect 然后给他们分享绑定data, 大家可以观察下最后的console.log() 会输出对应绑定的1、2、3、4、5、6、7
html
<!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>
    
</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 = [1,2,3,4,5,6,7];

    data.forEach((item => {
        svg.append('rect')
        .attr('width', 20)
        .attr('height', 20)
        .attr('x', 20 * item)
        .attr('y', 20 * item)
        .attr('fill', 'red')
    }))

    

    svg.selectAll('rect')
       .data(data)
       .attr('stroke',(d,idx) => {
           console.log(d,'d')
       })
</script>

效果呈现:

1629699807(1).png

datum

TIP

datum 跟data的作用是一样的都是绑定数据,他是绑定单个数据

  • 用法示例
javascript
svg.select('rect')
       .datum(1)
       .attr('stroke',(d,idx) => {
           console.log(d,'d')
       })

data、datum获取数据

TIP

上面讲到绑定数据,那绑定的数据一样可以获取了; datumdata 用法一致不过他获取到的是个单个数据代码最后一行console.log()输出:[1,2,3,4,5,6,7]

  • 用法示例:
html
<!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>
    
</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 = [1,2,3,4,5,6,7];

    data.forEach((item => {
        svg.append('rect')
        .attr('width', 20)
        .attr('height', 20)
        .attr('x', 20 * item)
        .attr('y', 20 * item)
        .attr('fill', 'red')
    }))



    svg.selectAll('rect')
       .data(data)
       .attr('stroke',(d,idx) => {
           console.log(d,'d')
       })

    console.log(svg.selectAll('rect').data(),'data')
</script>

enter

TIP

大家应该记得上面我是用forEach创建的rect 是不是挺费劲的,d3也有自己的方式结合data实现多个元素创建。enter是把不足的给补充了,例如下面代码是选中多个rect绑定然后调用enter,之后再append就是不足data的用append方法给添加上去。enter用法示例:

html
<!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>
    
</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 = [1,2,3,4,5,6,7];



    svg.selectAll('rect')
       .data(data)
       .enter()
       .append('rect')
       .attr('width', 20)
       .attr('height', 20)
       //d代表绑定的数据,idx代表索引,再d3中每个方法都可以用回调函数
       .attr('x', (d,idx) => 20 * d)
       .attr('y', (d,idx) => 20 * d)
       .attr('fill', 'red')
       .attr('stroke', 'blue')
       .attr('strokeWidth',1)
    
</script>

呈现效果:

1629699880(1).png

exit

TIP

exit 是当数据少,选中的多的时候不自动填充,可以搭配remove使用把多余的元素给去掉

用法示例:

html
<!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>
    
</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 = [1,2,3,4,5,6,7];

    [1,2,3,4,5,6,7,8,9,10].forEach((item => {
        svg.append('rect')
        .attr('width', 20)
        .attr('height', 20)
        .attr('x', 20 * item)
        .attr('y', 20 * item)
        .attr('fill', 'red')
    }))



    svg.selectAll('rect')
       .data(data)
       .exit()
       .remove()

    console.log(svg.selectAll('rect').data(),'data')
</script>

如何实现一个数据驱动的方法呢?

TIP

1、渲染层;2、修改层;3、删除层;

  • 1、渲染层: enter() (数据绑定会根据你绑定的值区分下次调用有咩有进入enter层)
  • 2、修改层: data() (数据绑定会根据你绑定的数据绑判断你有没有修改数据)
  • 3、删除层:exit() (数据绑定会根据你绑定的数据检测到你删除了那些数据)
html
<!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>
    
</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}];
    

   //修改层
   const update = svg.selectAll('rect')
       .data(data)

    //渲染层
    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()
</script>

呈现效果:

1629699966(1).png

封装成一个数据驱动的函数

TIP

把上面的那些层次放进一个函数里面,实现数据驱动。在每次数据更新,咱们重新render该方法即可帮咱们绘制出新的图形。

代码示例:

html
<!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);

    

    let 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)

    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: 120, y: 120});
    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>

效果呈现:可能你会发现点击最后一个按钮没反映尼(问题在于数据绑定的.data(data))要改成让他识别id.data(data, d => d.id)

1629700004(1).png

正确的数据驱动函数

html
<!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);

    

    let 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>

总结

TIP

其实大家可以对比下canvas,canvas的话你要是想改变、删除、新增、修改一个元素的话都是一直要重绘,d3+svg的数据绑定帮咱们解决了不用再去清除重绘的优点。写好绘制函数了后 数据再有更改调用绘制函数就好了,再有一个对数据监听的watch就更棒了,大家可以去参看我之前实现的一个自己的watch

Released under the MIT License.