做力导图使用到的数据为json数据,一般采用json数据来表达图结构。本次实验选用的json数据数据结构如下(图片中为经过了力模拟后的):
target
, source
组成
group
和id
构成
simulation.on("tick",tick)
,tick函数编写这步比较基础,直接贴上代码
const svg = d3.select('svg')
const height = +(svg.attr('height'))
const width = +(svg.attr('width'))
const margin = {
top:150,
left:50,
right:50,
bottom:0
}
const innerHeight = height-margin.top-margin.bottom;
const innerWidth = width - margin.left - margin.right;
var color = d3.scaleOrdinal(d3.schemeCategory10);
let link;
let nodes;
let simulation;
const render_init = function(){
svg.append("text")
.attr('class', 'title')
.attr('font-size','2em')
.attr('x',margin.left)
.attr('y', margin.top/2)
.attr('transform',`translate(0,-40)` )
.attr('font-weight', 'bold')
.attr('fill','blue' )
.html("Force Simulation")
svg.append("g")
.attr('id', 'maingroup')
.attr('x', margin.left)
.attr('y', margin.top)
.attr('transform', `translate(${margin.left},${margin.top})`)
.attr('width',innerWidth )
.attr('height',innerHeight )
}
通常读取进来的数据要进行预处理,但是图数据有些不同。通常我们拿到的数据都是不符合d3力导图数据结构要求的。一般用python等语言写的脚本文件进行数据预处理整理成符合要求的数据结构。
d3.json('./data/miserables.json').then(data => {
render_init();//画布初始化
//force simulation 力模拟
//data join 数据绑定
//drag 交互事件
})
有两点需要注意的地方:
node.id
。不然会按照node的索引来进行,这样设置tick的时候会非常不方便simulation.on(tick)
才会把力模拟的结果反映到图元上simulation = d3.forceSimulation()
.nodes(data.nodes)
.force("link",d3.forceLink(data.links).id(d => d.id))
.force("manyBody",d3.forceManyBody())
.force("center",d3.forceCenter(innerWidth/2,innerHeight/2))
.on("tick",tick)
这里我们需要绑定的三个元素为: 结点,结点名称,链接
一个传统的方法是circle
和text
分开绑定,但是这里采用一个更加高效的办法:就是创建一个 , 每个g标签代表一个结点,其中包含 circle
和 text
两个图元
nodes = group.append('g').attr("class", "nodegroup")
//先创建一个group,其中包含所有结点(这步可有可无)
.selectAll('.node')
.data(data.nodes)
.join('g')
.attr('class','node' )
结点绑定(circle)
var circle = nodes.append('circle')
.attr('r', 5)
.attr('fill', d => color(d.group))
结点名称绑定
var label = nodes.append('text')
.attr('x', 6)
.attr('y', 3 )
.html(d => d.id )
.attr('font-size', '12px')
这里有个坑,line标签是不能设置fill
的,应该通过设置stroke
属性来设置line的颜色。
//注意设置stroke
link = group.append('g').attr('class','linkgroup').selectAll('line')
.data(data.links)
.enter().append('line')
.attr('stroke-width', d => Math.sqrt(d.value))
.attr('stroke', 'green')
.attr('stroke-opacity',0.6 )
在tick函数中,我们要更新node的位置和link的起点和终点
注意:node指包含了circle和text的那个group,这里只需要更新那个group的位置, 和 就会一起更新。这就是上面提出为什么要把 和 放在一个 中。
const tick = function(){
link
.attr('x1', d => d.source.x)
.attr('y1', d => d.source.y)
.attr('x2', d => d.target.x)
.attr('y2', d => d.target.y)
nodes
.attr('transform', d => `translate(${d.x},${d.y})`)
}
现在给图加一些交互效果:让结点能够被鼠标选中并拖拽
d3.drag()
函数drag函数有三个需要配置的,分别是拖曳开始,拖曳过程和拖曳结束
const dragFunc = d3.drag()
.on('start',dragstarted)
.on('drag',dragged)
.on('end',dragended)
alphaTarget
:衰减系数,对节点位置移动过程的模拟,数值越高移动越快,数值范围[0,1]restart()
: 重新启动仿真的内部定时器并且返回仿真。与 simulation*.alphaTarget
或 simulation*.alpha
结合使用,这个方法可以在交互期间再次激活仿真,比如拖拽节点或者在使用 simulation.stop
临时暂停仿真后使用。function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
fx,fy
:结点的最终位置
const dragged = function(d){
d.fx = d3.event.x;
d.fy = d3.event.y;
}
最后将fx和fy设置成null,表示拖曳结束后让结点回到力模拟的位置,而不是停留在拖曳的位置
const dragended = function(d){
if (!d3.event.active) {
simulation.alphaTarget(0);
}
//让它回到原来的位置
d.fx = null;
d.fy = null;
}
dragFunc(nodes)//param:拖曳对象
DOCTYPE html>
<html>
<head>
<title>forcetitle>
<script src="./js/d3.min.js">script>
head>
<body>
<svg width="1200" height = "650">svg>
<script >
const svg = d3.select('svg')
const height = +(svg.attr('height'))
const width = +(svg.attr('width'))
const margin = {
top:150,
left:50,
right:50,
bottom:0
}
const innerHeight = height-margin.top-margin.bottom;
const innerWidth = width - margin.left - margin.right;
var color = d3.scaleOrdinal(d3.schemeCategory10);
let link;
let nodes;
let simulation;
const render_init = function(){
svg.append("text")
.attr('class', 'title')
.attr('font-size','2em')
.attr('x',margin.left)
.attr('y', margin.top/2)
.attr('transform',`translate(0,-40)` )
.attr('font-weight', 'bold')
.attr('fill','blue' )
.html("Force Simulation")
svg.append("g")
.attr('id', 'maingroup')
.attr('x', margin.left)
.attr('y', margin.top)
.attr('transform', `translate(${margin.left},${margin.top})`)
.attr('width',innerWidth )
.attr('height',innerHeight )
}
const tick = function(){
link
.attr('x1', d => d.source.x)
.attr('y1', d => d.source.y)
.attr('x2', d => d.target.x)
.attr('y2', d => d.target.y)
nodes
.attr('transform', d => `translate(${d.x},${d.y})`)
}
const dragstarted = function(d){
if(!d3.event.active){
设置衰减系数,对节点位置移动过程的模拟,数值越高移动越快,数值范围[0,1]
simulation.alphaTarget(0.3)
.restart();
d.fx = d.x;
d.fy = d.y;
}
}
const dragged = function(d){
d.fx = d3.event.x;
d.fy = d3.event.y;
}
const dragended = function(d){
if (!d3.event.active) {
simulation.alphaTarget(0);
}
//让它回到原来的位置
d.fx = null;
d.fy = null;
}
d3.json('./data/miserables.json').then(data => {
console.log(data);
render_init();
const group = d3.select('#maingroup')
simulation = d3.forceSimulation()
.nodes(data.nodes)
.force("link",d3.forceLink(data.links).id(d => d.id))
.force("manyBody",d3.forceManyBody())
.force("center",d3.forceCenter(innerWidth/2,innerHeight/2))
.on("tick",tick)
//注意设置stroke
link = group.append('g').attr('class','linkgroup').selectAll('line')
.data(data.links)
.enter().append('line')
.attr('stroke-width', d => Math.sqrt(d.value))
.attr('stroke', 'green')
.attr('stroke-opacity',0.6 )
nodes = group.append('g').attr("class", "nodegroup")
.selectAll('.node')
.data(data.nodes)
.join('g')
.attr('class','node' )
var circle = nodes.append('circle')
.attr('r', 5)
.attr('fill', d => color(d.group))
var label = nodes.append('text')
.attr('x', 6)
.attr('y', 3 )
.html(d => d.id )
.attr('font-size', '12px')
const dragFunc = d3.drag()
.on('start',dragstarted)
.on('drag',dragged)
.on('end',dragended)
dragFunc(nodes)//拖曳对象
})
script>
body>
html>