d3 - 建立力引导图将知识图谱可视化 (一)

本文要实现的功能与这个网页比较类似: KGBuilder知识图谱可视化

使用到的插件为: d3
没有采用echarts等实现的原因是: echarts比较死板, 有些需求不能实现, 而d3可以灵活的制作出想要的图表.

d3的引用/安装

  1. 在线方式:
    可以在 <head></head>中插入 <script src="https://d3js.org/d3.v5.min.js"></script>:
<head>
	<script src="https://d3js.org/d3.v5.min.js"></script>
</head>
  1. 下载安装
npm install d3 --save-dev

或者

cnpm install d3 --save-dev

组件中引入:

import * as d3 from 'd3';

vue中直接使用下面的可能会遇到一些问题, 可以参考: d3 -力引导图(四) vue项目中的使用及可能遇到的问题

基础知识

在绘制d3图形之前, 有一些基础知识需要掌握, 后续绘制图表的时候才不会太吃力.
主要是 SVG图形d3的一些基础知识.

带 * 号的是推荐必看的, 对d3的基础语法讲解的比较细致清晰, 可以帮助很快上手.

实例

有了一些基础后, 我们来看一个实例, 本实例的来源于: D3.js的v5版本入门教程(第六章)——做一个简单的图表, 如果能理解下面的代码在做什么, 就已经掌握了d3基础的语法.
在这里插入图片描述

<!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>
    <script src="https://d3js.org/d3.v5.min.js"></script>
</head>
<body>
    <svg></svg>
    <script>
        var marge = {top:60,bottom:60,left:0,right:60}//设置边距
        var dataset = [ 250 , 210 , 170 , 130 , 90 ];  //数据(表示矩形的宽度)

        var svg = d3.select("svg");//得到svg画布
    	var g = svg.append("g")//定义一个用来装整个图表的一个分组,并设置他的位置
    		.attr("transform","translate("+marge.top+","+marge.left+")");

        var rectHeight = 30;//设置每一个矩形的高度
    	
    	g.selectAll("rect")
    		.data(dataset)
    		.enter()
    		.append("rect") // 添加足够的条形表
    		.attr("x",20)//设置左上点的x
    		.attr("y",function(d,i){//设置左上点的y, i表示的是索引号
    			return i*rectHeight;
    		})
    		.attr("width",function(d){//设置宽
    			return d;
    		})
    		.attr("height",rectHeight-5)//设置长
    		.attr("fill","blue");//颜色填充
    </script>
</body>
</html>

d3绘制力引导图

为了绘制力引导图, 我们还需要新学习几个知识点:

  1. 如何新建一个力导向图: d3.forceSimulation()

  2. 如何添加或者移除一个力: d3.forceSimulation().force()

  3. 如何绘制节点
    绘制节点采用的是SVG图中的 circle 标签, 阮一峰SVG教程中给出的介绍如下:
    在这里插入图片描述

  4. 如何绘制连线
    绘制连线采用的SVG图中的 line 标签, 阮一峰SVG教程中给出的介绍如下:
    阮一峰SVG教程

  5. 如何绑定数据: d3绑定数据

  6. 如何绘制两个节点之间的连线, 将连线与节点联系起来:

举个例子, 有下面的数据:

当我们绘制节点后, nodes中会新增一些数据:
在这里插入图片描述
在这里插入图片描述

那么, 我们在绘制连线时, 就可以将连线的起始位置分别设置为两个节点的位置, 来实现在两个节点之间连线的目的.

比如有下面的数据:
在这里插入图片描述
我们在绘制连线时, 将 line 标签的 x1,y1,x2,y2 分别设置为source和target节点的x和y, 就可以实现节点间的连线了 (注: 绿色方框这里的n是指的每条连线.)
在这里插入图片描述
(其他参考例子: 基础知识二:网页端利用d3.js将json数据进行可视化展示 )

注意: 力引导图要想实现节点之间的连线, 在连线的对象中必须有source和target (分别指向起始节点), 不然会报错; 默认是索引.

下面是两个例子:
(1) 默认是索引
(源代码来自: CSDN)

<!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>
    <script src="https://d3js.org/d3.v5.min.js"></script>
</head>
<body>
    <svg width="960" height="600"></svg>
    <script>
    	var marge = {top:60,bottom:60,left:60,right:60}
    	var svg = d3.select("svg")
    	var width = svg.attr("width")
    	var height = svg.attr("height")
    	var g = svg.append("g")
    		.attr("transform","translate("+marge.top+","+marge.left+")");
    		
    	//准备数据
    	var nodes = [
    		{name:"湖南邵阳",id: 001},
    		{name:"山东莱州",id: 002},
    		{name:"广东阳江",id: 003},
    		{name:"山东枣庄",id: 004},
    		{name:"泽",id: 005},
    		{name:"恒",id: 006},
    		{name:"鑫",id: 007},
    		{name:"明山",id: 011},
    		{name:"班长",id: 012}
    	];
    	
    	var edges = [
    		{source:1,target:4,relation:"籍贯",value:1.3},
    		{source:4,target:5,relation:"舍友",value:1},
    		{source:4,target:6,relation:"舍友",value:1},
    		{source:4,target:7,relation:"舍友",value:1},
    		{source:1,target:6,relation:"籍贯",value:2},
    		{source:2,target:5,relation:"籍贯",value:0.9},
    		{source:3,target:7,relation:"籍贯",value:1},
    		{source:5,target:6,relation:"同学",value:1.6},
    		{source:6,target:7,relation:"朋友",value:0.7},
    		{source:6,target:8,relation:"职责",value:2}
    	];
    	//设置一个color的颜色比例尺,为了让不同的扇形呈现不同的颜色
    	var colorScale = d3.scaleOrdinal()
    		.domain(d3.range(nodes.length))
    		.range(d3.schemeCategory10);
    	
    	//新建一个力导向图
    	var forceSimulation = d3.forceSimulation()
    		.force("link",d3.forceLink())
    		.force("charge",d3.forceManyBody())
            // .force('collide', d3.forceCollide().strength(-30))
    		.force("center",d3.forceCenter());;
    		
    	//初始化力导向图,也就是传入数据
    	//生成节点数据
    	forceSimulation.nodes(nodes)
    		.on("tick",ticked);//这个函数很重要,后面给出具体实现和说明
    	//生成边数据
    	forceSimulation.force("link")
    		.links(edges)
    		.distance(function(d){//每一边的长度
    			return d.value*100;
    		})    	
    	//设置图形的中心位置	
    	forceSimulation.force("center")
    		.x(width/2)
    		.y(height/2);
    	//在浏览器的控制台输出
    	console.log(nodes);
    	console.log(edges);
    	
    	//有了节点和边的数据后,我们开始绘制
    	//绘制边
    	var links = g.append("g")
    		.selectAll("line")
    		.data(edges)
    		.enter()
    		.append("line")
    		.attr("stroke",function(d,i){
    			return colorScale(i);
    		})
    		.attr("stroke-width",1);
    	var linksText = g.append("g")
    		.selectAll("text")
    		.data(edges)
    		.enter()
    		.append("text")
    		.text(function(d){
    			return d.relation;
    		})
    	
    	//绘制节点
    	//老规矩,先为节点和节点上的文字分组
    	var gs = g.selectAll(".circleText")
    		.data(nodes)
    		.enter()
    		.append("g")
    		.attr("transform",function(d,i){
    			var cirX = d.x;
    			var cirY = d.y;
    			return "translate("+cirX+","+cirY+")";
    		})
    		.call(d3.drag()
    			.on("start",started)
    			.on("drag",dragged)
    			.on("end",ended)
    		);
    		
    	//绘制节点
    	gs.append("circle")
    		.attr("r",10)
    		.attr("fill",function(d,i){
    			return colorScale(i);
    		})
    	//文字
    	gs.append("text")
    		.attr("x",-10)
    		.attr("y",-20)
    		.attr("dy",10)
    		.text(function(d){
    			return d.name;
    		})
    	
    	function ticked(){
    		links
    			.attr("x1",function(d){return d.source.x;})
    			.attr("y1",function(d){return d.source.y;})
    			.attr("x2",function(d){return d.target.x;})
    			.attr("y2",function(d){return d.target.y;});
    			
    		linksText
    			.attr("x",function(d){
    			return (d.source.x+d.target.x)/2;
    		})
    		.attr("y",function(d){
    			return (d.source.y+d.target.y)/2;
    		});
    			
    		gs
    			.attr("transform",function(d) { return "translate(" + d.x + "," + d.y + ")"; });
    	}
    	function started(d){
    		if(!d3.event.active){
    			forceSimulation.alphaTarget(0.8).restart();
    		}
    		d.fx = d.x;
    		d.fy = d.y;
    	}
    	function dragged(d){
    		d.fx = d3.event.x;
    		d.fy = d3.event.y;
    	}
    	function ended(d){
    		if(!d3.event.active){
    			forceSimulation.alphaTarget(0);
    		}
    		d.fx = null;
    		d.fy = null;
    	}
    </script>
</body>
</html>

在这里插入图片描述

(2) 设置source和target
(源代码来自: GitHub)

<!DOCTYPE html>
<!--draw the graph completely-->
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <!--import d3 version 5-->
    <script type="text/javascript" src="https://d3js.org/d3.v5.min.js"></script>
    <!--import jquery3.3.1-->
    <!-- <script type="text/javascript" src="js/jquery-3.3.1.min.js"></script> -->
    <script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
</head>

<body>
    <svg></svg>
    <script>
        //get json file

        const data = {
            "nodes": [{
                "entity_name": "林黛玉", 
                "entity_id": "entity/2870013", 
                "ontargetlogy_name": "人员",
                "relation_num": 4
            }, {
                "entity_name": "贾宝玉", 
                "entity_id": "entity/2870127", 
                "ontargetlogy_name": "人员",
                "relation_num": 2
            }],
            "links": [{
                "from": "entity/2870013",
                "to": "entity/2870127", 
                "relation_id": "relation/815478", 
                "relation_name": "表哥"
            }]
        }
        //GroupExplorer constructing function
        //this is one way to create a javascript object
        function GroupExplorer(data) {
            console.log(data)
            //create an object-include some data
            //this is an another way to create a javascript object
            var defaultConfig = {
                windowWidth: window.innerWidth,
                windowHeight: window.innerHeight,
                defaultLinkDistance: 150,
                data: data
            }

            console.log(defaultConfig)

            var svg = d3.select("svg");
            svg.attr("width", defaultConfig.windowWidth);
            svg.attr("height", defaultConfig.windowHeight);

            defaultConfig.data.links.forEach(function (e) {
                var sourceNode = defaultConfig.data.nodes.filter(function (n) {
                    return n.entity_id === e.from;
                })[0];
                var targetNode = defaultConfig.data.nodes.filter(function (n) {
                    return n.entity_id === e.to;
                })[0];
                e.source = sourceNode;
                e.target = targetNode;

                console.log(e)
            });

            //create a force graph
            var forceSimulation = d3.forceSimulation()
                .force("link", d3.forceLink())
                .force("charge", d3.forceManyBody())
                .force("center", d3.forceCenter(defaultConfig.windowWidth / 2, defaultConfig.windowHeight / 2));

            //transform nodes data
            forceSimulation.nodes(defaultConfig.data.nodes)
                .on("tick", ticked);
            //tranform links data
            forceSimulation.force("link")
                .links(defaultConfig.data.links)
                .distance(defaultConfig.defaultLinkDistance);

            console.log(defaultConfig.data.nodes);
            console.log(defaultConfig.data.links);

            //define arrow
            svg.append("svg:defs")
                .append("svg:marker")
                .attr("id", "marker")
                .attr('viewBox', '0 -5 10 10')
                .attr("refX", 20)
                .attr("refY", 0)
                .attr('markerWidth', 10)
                .attr('markerHeight', 10)
                .attr('orient', 'auto')
                .append('svg:path')
                .attr('d', 'M0,-5L10,0L0,5')
                .attr("fill", "grey");
            //draw links
            var links = svg.append("g")
                .selectAll("line")
                .data(defaultConfig.data.links)
                .enter()
                .append("line")
                .attr("x1", function (n) { return n.source.x })
                .attr("y1", function (n) { return n.source.y })
                .attr("x2", function (n) { return n.target.x })
                .attr("y2", function (n) { return n.target.y })
                .attr("stroke", "grey")
                .attr("stroke-width", 1)
                .attr("marker-end", "url(#marker)");
            //draw links-text
            var links_text = svg.append("g")
                .selectAll("text")
                .data(defaultConfig.data.links)
                .enter()
                .append("text")
                .attr("x", function (e) {
                    return (e.source.x + e.target.x) / 2;
                })
                .attr("y", function (e) {
                    console.log(e.source.y + "+" + e.target.y)
                    return (e.source.y + e.target.y) / 2;
                })
                .attr("font-size", 10)
                .text(function (e) { return e.relation_name });
            //draw nodes group = node+node-text
            var nodes_g = svg.append("g")
                .selectAll("g")
                .data(defaultConfig.data.nodes)
                .enter()
                .append("g")
                .attr("transform", function (e) {
                    return "translate(" + e.x + "," + e.y + ")";
                })
                .call(d3.drag()
                    .on("start", started)
                    .on("drag", dragged)
                    .on("end", ended));
            //draw nodes
            nodes_g.append("circle")
                .attr("r", function (e) {
                    return e.relation_num * 5
                })
                .attr("fill", "yellow");
            //draw node-text
            nodes_g.append("text")
                .attr("x", -15)
                .attr("y", 20)
                .attr("font-size", 10)
                .text(function (e) { return e.entity_name });

            function started(d) {
                if (!d3.event.active) {
                    forceSimulation.alphaTarget(0.8).restart();
                }
                d.fx = d.x;
                d.fy = d.y;
            }
            function dragged(d) {
                d.fx = d3.event.x;
                d.fy = d3.event.y;
            }
            function ended(d) {
                if (!d3.event.active) {
                    forceSimulation.alphaTarget(0);
                }
                d.fx = null;
                d.fy = null;
            }

            function ticked() {
                links
                    .attr("x1", function (n) { return n.source.x })
                    .attr("y1", function (n) { return n.source.y })
                    .attr("x2", function (n) { return n.target.x })
                    .attr("y2", function (n) { return n.target.y })
                links_text
                    .attr("x", function (e) {
                        return (e.source.x + e.target.x) / 2;
                    })
                    .attr("y", function (e) {
                        return (e.source.y + e.target.y) / 2;
                    })
                nodes_g
                    .attr("transform", function (e) {
                        return "translate(" + e.x + "," + e.y + ")";
                    })
            }

        }
        //because in the way of creating a javascript object,
        //you need to use "new" to use it
        new GroupExplorer(data);

    </script>
</body>

</html>

在这里插入图片描述

通过上面两段代码 (特别是林黛玉和贾宝玉的例子), 引用接口数据已经可以简单的绘制一个力引导图了.

更多内容可以查看我的d3专栏: d3绘制力引导图
下一篇: d3 - 力引导图(二) 节点多种颜色方案

参考源码:
KGBuilder GitHub源码

  • 4
    点赞
  • 51
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Charonmomo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值