基于D3.js绘制的桑基图(带有边隐现功能)

3 篇文章 0 订阅

基于D3.js绘制的桑基图

桑基图(Sankey Diagram)是一种特殊类型的流图,它通过宽度不等的箭头直观地表示不同元素之间的流量大小,通常用于展示能量、材料、成本或其他资源的转移。桑基图的特点如下:

  1. 流量可视化:桑基图通过箭头的宽度来表示流量的大小,宽度越大,流量越大。这使得比较不同路径的流量变得直观。
  2. 流向清晰:它能够清晰地展示从一个节点到另一个节点的流向,非常适合展示转换过程或分配情况。
  3. 层次关系:桑基图可以很好地表示层次结构,例如在能源转换、数据流或组织结构中,上层的输出可以作为下层的输入。
  4. 交叉比较:由于流量的大小直接体现在视觉上,用户可以轻松地比较不同路径或节点之间的流量差异。

本文使用D3.js(版本5)绘制桑基图,提供变隐现,节点拖动,节点相关性高亮功能。

老规矩,先看结果:

在这里插入图片描述

一、桑基图json格式

在绘制桑基图之前,先了解桑基图的json结构。包含两个节点,nodes是节点名字通过id作为节点唯一标识,level是节点等级;links是用来记录连边的。source为始边,target为终边,value为权重。

[{
	"topic1": {
		"nodes": [{
				"id": "node1",
				"name": "topic1",
				"level": "1"
			}
             ......
		],
		"links": [{
				"source": "topic1",
				"target": "李信",
				"value": 8
			}
             ......
		]
	}
}]

在这里插入图片描述

二、 桑基图样式

<style>
    html,
    body {
        width: 100%;
        height: 100%;
    }
    /* svg */
    .svgsankey {
        background-color: black;
    }
    /* svg容器 */
    .sankey {
        width: 100%;
        height: 100%;
    }
    /* 节点矩形 */
    .node rect {
        cursor: move;
        fill-opacity: .9;
        shape-rendering: crispEdges;
    }
    /* 节点文本 */
    .node text {
        pointer-events: none;
        fill: rgb(131, 19, 19);
        /* text-shadow: 0 1px 0 #fff;*/
    }
    /* 链接 */
    .selected .link {
        stroke-opacity: .5;
    }
</style>

三、基础配置

主要设置画布长宽,颜色比例尺,透明度变化前后的值。

const width = 800;
const height = 600;
//边距
let margin = { top: 10,right: 10,bottom: 10,left: 10};
let opacityLow = 1, //新透明度
    opacityDefault = 0.3; //默认透明度
let maxLevel = 1;
// 定义颜色比例尺
let color = d3.scaleOrdinal(["rgb(255, 138, 128)", "rgb(255, 255, 0)", "rgb(234, 128, 252)",
    "rgb(29, 233, 182)", "rgb(130, 177, 255)", "rgb(132, 255, 255)",
    "rgb(167, 255, 235)"
])

添加画布

let svg = d3.select(".sankey")
    .append("svg")
    .attr("class", "svgsankey")
    .attr("viewBox", "0 0 820 620")
    .attr("width", "100%")
    .attr("height", "100%")
let g = svg.append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

桑基图配置。

// 设置sankey图表属性
let sankey = d3.sankey()
    .nodeWidth(20)
    .nodePadding(10)
    .size([width - 10, height - 10]);
let path = sankey.link();

读取数据后,进行数据处理,nodeMap包含的内容格式为 {节点名:节点名对应的数据}

d3.json("json/data.json").then(function(alldata) {
    let graph = alldata[0]["topic1"]
    let nodeMap = {};
    // 遍历节点并找到最大级别
    graph.nodes.forEach(function(node) {
        node.hidden = false;// 假设在节点数据中添加了一个hidden属性,默认为false
        nodeMap[node.name] = node;
        if (node.level > maxLevel) {
            maxLevel = node.level;
        }
    });
    /*处理成桑基图需要的链接格式*/
    graph.links = graph.links.map(function(x) {
        return {
            source: nodeMap[x.source],
            target: nodeMap[x.target],
            value: x.value
        };
    });
})

把节点数据和链接数据应用到桑基图上。

sankey.nodes(graph.nodes)
    .links(graph.links)
    .layout(35);

此时打开页面会发现什么都没有,这是因为桑基图只有数据,并没有添加对应的svg元素。

四、 绘制桑基图链接边

边样式配置。

//添加链接
let link = g.append("g")
    .selectAll(".link")
    .data(graph.links)
    .enter()
    .append("path")
    .attr("class", "link")
    .attr('linkNodes', (d) => d.source.id + '-' + d.target.id)
    .style("fill", function(d) {
        d.source.color = color(d.source.name); 
        return "none"
    })
    .style("stroke", function(d, i) {
        return d3.rgb(d.source.color); //颜色比例尺
    })
    .attr("d", path)
    .style("stroke-width", function(d) {
        return Math.max(1, d.dy);
    })
    .style("opacity", opacityDefault)	

绘制结果:

在这里插入图片描述

五、绘制桑基图节点

这里使得节点组下,每组g元素下面都包含一个rect标签,同时添加rect样式配置。

// 添加节点
let node = g.append("g")
    .selectAll(".node")
    .data(graph.nodes)
    .enter()
    .append("g")
    .attr("class", "node")
    .attr("transform", function(d) {
        // return  d.level!=="3"? "translate(" + d.x + "," + d.y + ")":"translate(" + (d.x-20)+ "," + d.y + ")"
        return "translate(" + d.x + "," + d.y + ")";
    })
let rect = node
    .append("rect")
    .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;
    })
    .attr("height", function(d) {
        return d.dy;
    })
    .attr("width", sankey.nodeWidth())
    .style("fill", function(d) {
        return d.color = color(d.name);
    })
    .style("stroke", function(d) {
        return d3.rgb(d.color).darker(10);
    })
    .style('opacity', opacityDefault)

绘制结果:

在这里插入图片描述

下图中,鼠标移动或点击将不会有交互,因为还没有添加鼠标事件。

在这里插入图片描述

5.1 给节点添加标题

给每个添加节点的标题,保证第三级节点的名字在节点左边,这是为了防止文字超出svg显示范围。

node.append("text")
    .attr("x", -6)
    .attr("y", d => d.dy / 2)
    .attr("dy", ".35em")
    .attr("text-anchor", "end")
    .attr("transform", null)
    .style("fill", "white")
    .text(d => d.name)
    .filter(d => d.x < width / 2)
    .style("font-size", function(d) {
        return d.level == 3 ? "9px" : "12px"
    })

添加标题后:

在这里插入图片描述

5.2 给节点添加拖拽功能

给节点添加拖拽功能。

node.call(d3.drag()
    .on("drag", dragmove)
);
// 移动节点的功能
function dragmove(d) {
    d3.select(this)
        .attr("transform",
            "translate(" + (
                d.x = Math.max(0, Math.min(width - d.dx, d3.event.x))
            ) + "," + (
                d.y = Math.max(0, Math.min(height - d.dy, d3.event.y))
            ) + ")");
    sankey.relayout();
    link.attr("d", path);
}

移动节点:

在这里插入图片描述

5.3 给链接添加隐现功能

给节点添加隐现功能,隐藏被点击节点的后续链接。

node.on('click', function(d) {
    // 切换当前节点的hidden状态
    d.hidden = !d.hidden;
    let clickcolor = d3.select(this)
        .select("rect")
        .style("fill")
    //判断当前节点的颜色
    if (clickcolor === "red") {
        d3.select(this).select("rect")
            .style("fill", function(d) {
                return d3.rgb(d.color);
            });
    } else {
        d3.select(this)
            .select("rect")
            .style("fill", function(d) {
                return "red"
                // return d3.rgb(d.color);
            });
    }

    // 更新边的样式
    g.selectAll('.link')
        .style('stroke-opacity', function(link) {
            // 如果边是从当前节点出发的,根据hidden状态设置透明度
            if (link.source.id === d.id) {
                return d.hidden ? 0 : 1;
            }
            // 如果边不是从当前节点出发的,检查边的源节点是否隐藏,如果隐藏则不透明度为0,否则为1
            return link.source.hidden ? 0 : 1;
        });
});

结果展示:

在这里插入图片描述

六、总结

本次绘制的桑基图,对于不追求高交互的用户,基本满足使用。提供了全网首发的链接隐藏功能

不包括相关边和节点高亮功能。其它功能会在付费文章中更新。

很高兴为您提供答案。 实现桑基图,需要使用 d3.js 中的 d3.sankey() 函数。该函数可以将数据转换成桑基图的节点和边,然后使用 d3.js 绘图库绘制。 以下是一个简单的实现代码示例: ``` // 定义数据 var data = { nodes: [ {name: "A"}, {name: "B"}, {name: "C"}, {name: "D"} ], links: [ {source: 0, target: 1, value: 10}, {source: 1, target: 2, value: 5}, {source: 1, target: 3, value: 5}, {source: 2, target: 3, value: 2} ] }; // 定义 SVG 容器 var svg = d3.select("body").append("svg") .attr("width", 400) .attr("height", 300); // 定义桑基图布局 var sankey = d3.sankey() .size([400, 300]) .nodeWidth(20) .nodePadding(10) .nodes(data.nodes) .links(data.links) .layout(32); // 绘制桑基图 var path = sankey.link(); svg.selectAll(".link") .data(data.links) .enter().append("path") .attr("class", "link") .attr("d", path) .style("stroke-width", function(d) { return Math.max(1, d.dy); }) .sort(function(a, b) { return b.dy - a.dy; }); svg.selectAll(".node") .data(data.nodes) .enter().append("rect") .attr("class", "node") .attr("x", function(d) { return d.x; }) .attr("y", function(d) { return d.y; }) .attr("height", function(d) { return d.dy; }) .attr("width", sankey.nodeWidth()); svg.selectAll(".node text") .data(data.nodes) .enter().append("text") .attr("x", function(d) { return d.x + sankey.nodeWidth() / 2; }) .attr("y", function(d) { return d.y + d.dy / 2; }) .attr("dy", ".35em") .text(function(d) { return d.name; }); ``` 以上代码用数据模拟了一个简单的桑基图。请根据您的实际需求修改数据和配置参数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值