D3 二维图表的绘制系列(二十二)桑基图sankey

上一篇: 仪表盘图 https://blog.csdn.net/zjw_python/article/details/98596174

下一篇: 旭日图 https://blog.csdn.net/zjw_python/article/details/98613674

代码结构和初始化画布的Chart对象介绍,请先看 https://blog.csdn.net/zjw_python/article/details/98182540

本图完整的源码地址: https://github.com/zjw666/D3_demo/tree/master/src/sanKeyChart/basicSanKey

1 图表效果

在这里插入图片描述

2 数据

桑基图的数据需要符合一定的规则,即包含nodes和links两个部分

{
    "nodes": [
        {"id": "a"},
        {"id": "b"},
        {"id": "c"},
        {"id": "d"},
        {"id": "e"},
        {"id": "f"},
        {"id": "g"},
        {"id": "h"},
        {"id": "i"}
    ],
    "links": [
        {"source": "a", "target": "d", "value": 10},
        {"source": "a", "target": "i", "value": 2},
        {"source": "b", "target": "d", "value": 8},
        {"source": "b", "target": "e", "value": 6},
        {"source": "c", "target": "e", "value": 5},
        {"source": "b", "target": "e", "value": 2},
        {"source": "b", "target": "i", "value": 4},
        {"source": "d", "target": "f", "value": 3},
        {"source": "d", "target": "g", "value": 4},
        {"source": "d", "target": "h", "value": 5},
        {"source": "e", "target": "g", "value": 7},
        {"source": "e", "target": "f", "value": 3},
        {"source": "e", "target": "h", "value": 5}
    ]
}

3 关键代码

导入数据

d3.json('./data.json').then(function(data){
....

一些样式配置参数

const config = {
        margins: {top: 80, left: 50, bottom: 50, right: 50},
        textColor: 'black',
        title: '基础桑基图'
    }

桑基图主要由d3.sankey计算布局,但这个API没有原生包含在d3里,而是一个插件,需要额外引入,例如

<script src="https://unpkg.com/d3-sankey@0"></script>

引入插件后,方法自动挂载在d3对象上,就可以直接调用了

/* ----------------------------数据转换------------------------  */
    const sankey = d3.sankey()
                        .nodeWidth(50)
                        .nodePadding(30)
                        .size([chart.getBodyWidth(), chart.getBodyHeight()])
                        .nodeId((d) => d.id);

    const {nodes, links} = sankey({
                                nodes: data.nodes,
                                links: data.links
                            });

经布局函数计算返回后的数据就可以直接绘制节点了,桑基图的节点是矩形,这里用rect元素渲染

/* ----------------------------渲染节点------------------------  */
    chart.renderNodes = function(){
        const rects = chart.body().append('g')
                                  .attr('class', 'rects')
                                  .selectAll('.node')
                                  .data(nodes);

              rects.enter()
                     .append('g')
                     .attr('class', 'node')
                     .attr('index', (d)=> d.id)
                     .attr('linkNodes', (d)=> {
                         const nextNodes = d.sourceLinks.map((link) => link.target.id).join('');
                         const prevNodes = d.targetLinks.map((link) => link.source.id).join('');
                         return nextNodes + d.id + prevNodes;
                     })
                     .append('rect')
                   .merge(rects)
                     .attr('x', (d) => d.x0)
                     .attr('y', (d) => d.y0)
                     .attr('width', (d) => d.x1 - d.x0)
                     .attr('height', (d) => d.y1 - d.y0)
                     .attr('fill', (d) => chart._colors(d.index % 10));

              rects.exit()
                     .remove();

    }

渲染连线则直接调用d3.sankeyLinkHorizontal,非常方便

/* ----------------------------渲染连线------------------------  */
    chart.renderLines = function(){
        const lines = chart.body().append('g')
                                  .attr('class', 'lines')
                                  .selectAll('path')
                                  .data(links);

               lines.enter()
                      .append('path')
                    .merge(lines)
                      .attr('linkNodes', (d) => d.source.id + '-' + d.target.id)
                      .attr('d', d3.sankeyLinkHorizontal())
                      .attr('stroke', (d) => chart._colors(d.source.index % 10))
                      .attr('stroke-width', (d) => d.width)
                      .attr('stroke-opacity', '0.4')
                      .attr('fill', 'none');

               lines.exit()
                      .remove();
    }

渲染文本标签

/* ----------------------------渲染文本标签------------------------  */
    chart.renderTexts = function(){
        d3.selectAll('.text').remove();

        chart.body().selectAll('.node')
                        .append('text')
                        .attr('class', 'text')
                        .attr('x', (d) => (d.x0 + d.x1)/2)
                        .attr('y', (d) => (d.y0 + d.y1)/2)
                        .attr('stroke', config.textColor)
                        .attr('text-anchor', 'middle')
                        .attr('dy', 6)
                        .text((d) => d.id);
    }

绑定鼠标交互事件,当鼠标悬停在某个节点上时,突出显示与该节点相连的边和节点;当鼠标悬停在某个边上时,则突出显示在这条边上的两个节点

/* ----------------------------绑定鼠标交互事件------------------------  */
    chart.addMouseOn = function(){

        // 悬停在节点上
        d3.selectAll('.node')
            .on('mouseover', function(d){
                d3.selectAll('.node, path')
                    .attr('fill-opacity', '0.1')
                    .attr('stroke-opacity', '0.1');

                d3.selectAll('[linkNodes*=' + d.id + ']')
                    .attr('fill-opacity', '1')
                    .attr('stroke-opacity', '0.4');



            })
            .on('mouseleave', function(){
                d3.selectAll('.node, path')
                    .attr('fill-opacity', '1')
                    .attr('stroke-opacity', '0.4');
            })

        // 悬停在连线上
        d3.selectAll('path')
            .on('mouseover', function(){
                d3.selectAll('.node, path')
                    .attr('fill-opacity', '0.1')
                    .attr('stroke-opacity', '0.1');

                const e = d3.event;
                const hoverNodes = d3.select(e.target)
                                        .attr('stroke-opacity', '0.4')
                                        .attr('linkNodes').split('-');

                hoverNodes.forEach((id) => {
                    d3.selectAll('[index=' + id + ']')
                    .attr('fill-opacity', '1')
                });
            })
            .on('mouseleave', function(){
                d3.selectAll('.node, path')
                    .attr('fill-opacity', '1')
                    .attr('stroke-opacity', '0.4');
            })
    }

大功告成!!!


如果觉得这篇文章帮助了您,请打赏一个小红包鼓励作者继续创作哦!!!

在这里插入图片描述

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值