赞
踩
创建一个力导向图需要三个东西:
当然,一般我们也会创建links(边)来连接两个节点,例如上图
仿真模拟系统中存在多个节点和多种类型的力,通过力控制节点的运动,每个节点都在多个力的作用下不断发生移动,直到系统趋于平衡。中间会发生多次tick
事件,每次tick,仿真系统都会更新节点的位置,且系统的能量(alpha)也会逐渐降低,直到达到某个数值(alphaMin),整个图表就停止运动了。
节点是一个对象数组,对象的属性没有限制,你可以添加多种信息来控制图表的渲染(例如颜色大小等),每次tick都会更新节点的位置(x,y
)和速度velocity(vx,vy
)。
力驱动着整个系统运动,你可以给系统添加力,控制节点的运动。
常见的几种力:
[0,0]
,施加力时,所有节点的相对位置保持不变同时可以访问这个demo,测试下不同力的作用。
定义了节点之间的关系,通过节点间的连线定义。同时links也是创建连接力(forceLink
)必不可少的东西。
example:
var nodes = [
{"id": "Alice"},
{"id": "Bob"},
{"id": "Carol"}
];
var links = [
{"source": 0, "target": 1}, // Alice → Bob
{"source": 1, "target": 2} // Bob → Carol
];
每个对象必须包含source
target
属性,表示边的起点和终点,属性的值是节点的id
,默认是节点在数组中的索引,同时也可以自定义id getter
:
d3.forceLink().id(d => d.id)
通过渲染一个树结构的数据来展示力导向图的使用
数据:json
1、根据这份数据分析出我们需要的所有节点和边:
const root = d3.hierarchy(data)
const nodes = root.descendants()
const links = root.links()
2、创建svg容器
const svg = d3.select('body')
.append('svg')
.attr('width', width)
.attr('height', height)
.attr('class', 'chart')
3、创建仿真系统
const simulation = d3.forceSimulation(nodes)
.force('charge', d3.forceManyBody())
.force('link', d3.forceLink(links))
.force('x', d3.forceX(width / 2))
.force('y', d3.forceY(height / 2))
4、设置排斥力和连接力的部分属性
simulation.alphaDecay(0.05) // 衰减系数,值越大,图表稳定越快
simulation.force('charge')
.strength(-50) // 排斥力强度,正值相互吸引,负值相互排斥
simulation.force('link')
.id(d => d.id) // set id getter
.distance(0) // 连接距离
.strength(1) // 连接力强度 0 ~ 1
.iterations(1) // 迭代次数
5、绘制边
边的绘制需要先进行,因为svg中没有类似z-index
这样的属性来设置层级,后绘制的会覆盖先绘制的
const simulationLinks = svg.append('g')
.selectAll('line')
.data(links)
.enter()
.append('line')
.attr('stroke', d => '#c2c2c2')
6、绘制节点并设置拖动事件
每次拖动开始,设置alphaTarget
并重启仿真系统,alpha的值会从alphaTarget
递减到alphaMin
,所以如果你将alphaTarget
的值设置的比alphaMin
小,就会卡住,不会继续更新。
const simulationNodes = svg.append('g') .attr('fill', '#fff') .attr('stroke', '#000') .attr('stroke-width', 1.5) .selectAll('circle') .data(nodes) .enter() .append('circle') .attr('r', 3.5) .attr('fill', d => d.children ? null : '#000') // 叶子节点黑底白边,父节点白底黑边 .attr('stroke', d => d.children ? null : '#fff') .call(d3.drag() .on('start', started) .on('drag', dragged) .on('end', ended) ) function started(d) { if (!d3.event.active) { simulation.alphaTarget(.2).restart() } d.fx = d.x // fx fy 表示下次节点被固定的位置 // 每次tick结束node.x都会被设置为node.fx,node.vx设置为0 d.fy = d.y } function dragged(d) { d.fx = d3.event.x d.fy = d3.event.y } function ended(d) { if (!d3.event.active) { // 设置为0直接停止,如果大于alphaMin则会逐渐停止 simulation.alphaTarget(0) } d.fx = null d.fy = null }
7、最后设置tick事件
虽然仿真系统会更新节点的位置(只是设置了nodes对象的x y属性
),但是它不会转为svg内部元素的坐标表示,这需要我们自己来操作
simulation.on('tick', ticked)
function ticked() {
simulationLinks.attr('x1', d => d.source.x )
.attr('y1', d => d.source.y )
.attr('x2', d => d.target.x )
.attr('y2', d => d.target.y )
simulationNodes.attr('cx', d => d.x )
.attr('cy', d => d.y )
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。