D3.js创建力导向图(V4)

D3.js创建力导向图(V4)

  本文借鉴https://blog.csdn.net/juzipidemimi/article/details/100787059

  创建一个力导向图需要三个东西:

  1. 仿真模拟系统
  2. 节点

  仿真模拟系统中存在多个节点和多种类型的力,通过力控制节点的运动,每个节点都在多个力的作用下不断发生移动,直到系统趋于平衡。中间会发生多次tick事件,每次tick,仿真系统都会更新节点的位置,且系统的能量(alpha)也会逐渐降低,直到达到某个数值(alphaMin),整个图表就停止运动了。

1.节点

  节点是一个对象数组,对象的属性没有限制,你可以添加多种信息来控制图表的渲染(例如颜色大小等),每次tick都会更新节点的位置(x,y)和速度velocity(vx,vy)。

2.力

  力驱动着整个系统运动,你可以给系统添加力,控制节点的运动。

  常见的几种力:

  • d3.forceCenter([x,
    y]):中心力,将所有的节点都推向图表的中心(给定的一个点),默认坐标是[0,0],施加力时,所有节点的相对位置保持不变
  • d3.forceCollide([radius]):collision,碰撞力,使两个节点接触时像弹簧球一样弹开
  • d3.forceLink([links]): 连接力,拉动节点相互连接,好像节点之间有一个弹簧
  • d3.forceManyBody():排斥力,类似带电电子的排斥方式,推动所有节点彼此远离
  • d3.forceX,d3.forceY:定位力,将节点推向期望的点(x,y),不同于forceCenter,它们会改变节点的相对位置
3.links

  定义了节点之间的关系,通过节点间的连线定义。同时links也是创建连接力(forceLink)必不可少的东西。

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)

4.创建一个力导向图
4.1 数据
		var nodes = [ { name: "0"},
					  { name: "1"},
					  { name: "2"},
					  { name: "3"},
					  { name: "4"},
					  { name: "5"},
					  { name: "6"} ];
    // source 对应nodes数组元素的下标 表示连线的开始节点
    // target  对应nodes数组元素的下标 表示连线的结束节点
		var edges = [  { source : 0  , target: 1 } ,
					   { source : 0  , target: 2 } ,
					   { source : 0  , target: 3 } ,
					   { source : 1  , target: 4 } ,
					   { source : 1  , target: 5 } ,
					   { source : 1  , target: 6 }];
4.2 创建svg容器
const svg = d3.select('body')
    .append('svg')
    .attr('width', width)
    .attr('height', height)
    .attr('class', 'chart')
4.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.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) // 迭代次数
4.5 绘制边

  边的绘制需要先进行,因为svg中没有类似z-index这样的属性来设置层级,后绘制的会覆盖先绘制的

const simulationLinks = svg.append('g')
    .selectAll('line')
    .data(links)
    .enter()
    .append('line')
    .attr('stroke', d => '#c2c2c2')
4.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
}

4.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 )
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值