上一篇: 仪表盘图 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');
})
}