本文要实现的功能与这个网页比较类似: KGBuilder知识图谱可视化
使用到的插件为: d3
没有采用echarts等实现的原因是: echarts比较死板, 有些需求不能实现, 而d3可以灵活的制作出想要的图表.
中插入
:<head>
<script src="https://d3js.org/d3.v5.min.js"></script>
</head>
npm install d3 --save-dev
或者
cnpm install d3 --save-dev
组件中引入:
import * as d3 from 'd3';
vue中直接使用下面的可能会遇到一些问题, 可以参考: d3 -力引导图(四) vue项目中的使用及可能遇到的问题
在绘制d3图形之前, 有一些基础知识需要掌握, 后续绘制图表的时候才不会太吃力.
主要是 SVG图形 和 d3的一些基础知识.
阮一峰SVG基础教程 *
d3基础教程 *
d3数据驱动文档 *
d3如何绑定数据 *
d3官网
d3官方API
带 * 号的是推荐必看的, 对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.forceSimulation()
如何添加或者移除一个力: d3.forceSimulation().force()
如何绑定数据: d3绑定数据
如何绘制两个节点之间的连线, 将连线与节点联系起来:
举个例子, 有下面的数据:
那么, 我们在绘制连线时, 就可以将连线的起始位置分别设置为两个节点的位置, 来实现在两个节点之间连线的目的.
比如有下面的数据:
我们在绘制连线时, 将 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>Documenttitle>
<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>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
<script type="text/javascript" src="https://d3js.org/d3.v5.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源码