D3.js v7力导向图如何新增节点与链接并实现平滑重渲染
D3.js v7 力导向图动态新增节点与边实现方案
直接向nodes和links数组追加数据的核心思路是正确的,功能不生效是因为缺少了数据更新后同步DOM、更新力模拟配置、重启模拟三个关键步骤。
具体修改逻辑
- 追加完新节点和边数据后,将新数据同步给力模拟实例
- 重新执行D3的data-join逻辑,渲染新增的节点和边DOM元素
- 给新增的节点绑定拖拽事件、补充圆形、文本等子元素
- 重置力模拟的热度系数,重启模拟实现平滑过渡
完整可运行代码
<script src="https://d3js.org/d3.v7.min.js"></script> <div class="container"></div>
.my-button { position: absolute; top: 0; left: 0; }
const graph = { "nodes": [ { "id": "A", "group": 1 }, { "id": "B", "group": 2 }, { "id": "C", "group": 2 }, { "id": "D", "group": 3 }, { "id": "E", "group": 3 } ], "links": [ { "source": "A", "target": "B", "value": 1, type: 'default' }, { "source": "A", "target": "C", "value": 2, type: 'default' }, { "source": "B", "target": "D", "value": 2, type: 'default' }, { "source": "A", "target": "E", "value": 4, type: 'default' }, { "source": "A", "target": "D", "value": 2, type: 'default' } ] } const links = graph.links.map(d => Object.create(d)); const nodes = graph.nodes.map(d => Object.create(d)); const simulation = d3 .forceSimulation(nodes) .force( 'link', d3.forceLink(links).id(d => d.id), ) .force('charge', d3.forceManyBody().strength(-300)) .force('x', d3.forceX()) .force('y', d3.forceY()) .force( 'collide', d3.forceCollide(d => 65), ) .force('center', d3.forceCenter(500, 500)); const drag = (simulation) => { function dragstarted(event, d) { if (!event.active) simulation.alphaTarget(0.3).restart(); d.fx = d.x; d.fy = d.y; } function dragged(event, d) { d.fx = event.x; d.fy = event.y; } function dragended(event, d) { if (!event.active) simulation.alphaTarget(0); d.fx = null; d.fy = null; } return d3.drag().on('start', dragstarted).on('drag', dragged).on('end', dragended); }; const svg = d3 .select('.container') .append('svg') .attr('preserveAspectRatio', 'xMinYMin meet') .attr('viewBox', '0 0 1000 1000') .classed('svg-content', true) .call(d3.zoom().on('zoom', e => svg.attr('transform', e.transform))) .append('g'); // 生成通用箭头 svg .append('defs') .append('marker') .attr('id', 'arrow-default') .attr('viewBox', '0 -5 10 10') .attr('refX', 38) .attr('refY', 0) .attr('markerWidth', 6) .attr('markerHeight', 6) .attr('orient', 'auto') .append('path') .attr('fill', '#262626') .attr('d', 'M0,-5L10,0L0,5'); let link = svg .append('g') .attr('fill', 'none') .attr('stroke-width', 1.5) .selectAll('path') .data(links) .join('path') .attr('stroke', 'green') .attr('marker-end', d => `url(#arrow-default)`); let node = svg .append('g') .attr('fill', 'currentColor') .attr('stroke-linecap', 'round') .attr('stroke-linejoin', 'round') .selectAll('g') .data(nodes) .join('g') .call(drag(simulation)); node .append('circle') .attr('stroke', 'white') .attr('stroke-width', 1.5) .attr('r', 25) .attr('fill', d => '#6baed6'); node .append('text') .attr('x', 34) .attr('y', '0.31em') .text(d => d.id) .clone(true) .lower() .attr('fill', 'none') .attr('stroke', 'white') .attr('stroke-width', 3); node.on('dblclick', (e, d) => console.log(nodes[d.index])); const clickHandler = a => { // 避免重复添加节点 if (nodes.find(item => item.id === 'F')) return nodes.push({ id: 'F', group: 1 }); nodes.push({ id: 'G', group: 1 }); links.push({ source: 'A', target: 'F', value: 2, type: 'default' }); links.push({ source: 'F', target: 'G', value: 2, type: 'default' }); // 同步新数据到力模拟 simulation.nodes(nodes) simulation.force('link').links(links) // 更新边DOM link = link.data(links).join('path') .attr('stroke', 'green') .attr('marker-end', d => `url(#arrow-default)`); // 更新节点DOM node = node.data(nodes).join( enter => { const newNode = enter.append('g').call(drag(simulation)) newNode.append('circle') .attr('stroke', 'white') .attr('stroke-width', 1.5) .attr('r', 25) .attr('fill', '#6baed6'); newNode.append('text') .attr('x', 34) .attr('y', '0.31em') .text(d => d.id) .clone(true) .lower() .attr('fill', 'none') .attr('stroke', 'white') .attr('stroke-width', 3); newNode.on('dblclick', (e, d) => console.log(nodes[d.index])); return newNode } ) // 重启力模拟实现平滑动画 simulation.alpha(0.3).restart() }; simulation.on('tick', () => { link.attr('d', d => `M${d.source.x},${d.source.y}A0,0 0 0,1 ${d.target.x},${d.target.y}`); node.attr('transform', d => `translate(${d.x},${d.y})`); }); d3.select('.container').append('button').attr('class', 'my-button').text('add data').on('click', clickHandler); const color = (types) => d3.scaleOrdinal(types, d3.schemeCategory10);
内容的提问来源于stack exchange,提问作者luhu
相关产品推荐
相关产品推荐

