
本文旨在解决D3.js力导向图中无法拖拽整个图的问题。通过将拖拽功能替换为缩放功能,并禁用鼠标滚轮缩放,实现了对整个图的平移操作,同时保留了节点拖拽的功能。本文将提供详细的代码示例和实现步骤,帮助开发者在D3.js力导向图中实现类似效果。
在使用D3.js构建力导向图时,经常需要实现缩放和平移功能。D3.js提供了d3.zoom()来实现缩放功能,但如果直接将拖拽功能应用于包含所有节点和连接线的<g>元素,可能无法达到预期效果,即无法拖动整个图。
D3.js的缩放功能实际上是通过改变元素的transform属性来实现的,这为我们提供了一个思路:可以使用缩放功能来实现平移效果。具体步骤如下:
创建新的缩放函数: 创建一个新的d3.zoom()函数,并将其绑定到SVG元素上。在这个缩放函数中,我们将修改包含所有节点和连接线的<g>元素的transform属性。
const zoomSvg = d3.zoom().on('zoom', (event) => {
group.attr('transform', event.transform).on('wheel.zoom', null);
});这里,group是包含所有节点和连接线的<g>元素。event.transform包含了缩放和平移的信息。.on('wheel.zoom', null)用于禁用鼠标滚轮缩放,防止与平移操作冲突。
将缩放函数绑定到SVG元素: 将新创建的缩放函数绑定到SVG元素上。
const svg = d3
.select(container)
.append('svg')
.attr('viewBox', [-width / 2, -height / 2, width, height])
.call(zoomSvg as any);container是包含SVG元素的容器。.call(zoomSvg as any)将缩放函数绑定到SVG元素上。
下面是完整的代码示例,展示了如何使用缩放功能实现力导向图的整体拖拽功能:
const data = jsonFyStory(selectedVariable, stories)
const links = data.links.map((d) => d)
const nodes = data.nodes.map((d: any) => d)
const containerRect = container.getBoundingClientRect()
const height = containerRect.height
const width = containerRect.width
function dragstarted() {
// @ts-ignore
d3.select(this).classed('fixing', true)
setDisplayCta(false)
setDisplayNodeDescription(false)
setNodeData({})
}
function dragged(event: DragEvent, d: any) {
d.fx = event.x
d.fy = event.y
simulation.alpha(1).restart()
setDisplayNodeDescription(true)
d.class === 'story-node' && setDisplayCta(true)
setNodeData({
name: d.name as string,
class: d.class as string,
definition: d.definition as string,
summary: d.summary as string,
})
}
// dragended function in case we move away from sticky dragging!
function dragended(event: DragEvent, d: DNode) {
// @ts-ignore
d3.select(this).classed('fixed', true)
console.log(d)
}
function click(event: TouchEvent, d: DNode) {
delete d.fx
delete d.fy
console.log(d)
// @ts-ignore
d3.select(this).classed('fixed', false)
// @ts-ignore
d3.select(this).classed('fixing', false)
simulation.alpha(1).restart()
}
const simulation = d3
.forceSimulation(nodes as any[])
.force(
'link',
d3.forceLink(links).id((d: any) => d.id)
)
.force('charge', d3.forceManyBody().strength(isMobile ? -600 : -1300))
.force('collision', d3.forceCollide().radius(isMobile ? 5 : 20))
.force('x', d3.forceX())
.force('y', d3.forceY())
if (container.children) {
d3.select(container).selectAll('*').remove()
}
const zoom = d3
.zoom()
.on('zoom', (event) => {
group.attr('transform', event.transform)
})
.scaleExtent([0.2, 100])
const zoomSvg = d3.zoom().on('zoom', (event) => {
group.attr('transform', event.transform).on('wheel.zoom', null)
})
const svg = d3
.select(container)
.append('svg')
.attr('viewBox', [-width / 2, -height / 2, width, height])
.call(zoomSvg as any)
const group = svg
.append('g')
.attr('width', '100%')
.attr('height', '100%')
.call(
d3
.drag()
.on('start', dragstarted)
.on('drag', dragged as any)
.on('end', dragended as any) as any
)
// .call(zoom as any)
const link = group
.append('g')
.attr('stroke', '#1e1e1e')
.attr('stroke-opacity', 0.2)
.selectAll('line')
.data(links)
.join('line')
const node = group
.append('g')
.selectAll<SVGCircleElement, { x: number; y: number }>('g')
.data(nodes)
.join('g')
.classed('node', true)
.classed('fixed', (d: any) => d.fx !== undefined)
.attr('class', (d: any) => d.class as string)
.call(
d3
.drag()
.on('start', dragstarted)
.on('drag', dragged as any)
.on('end', dragended as any) as any
)
.on('click', click as any)
d3.selectAll('.category-node')
.append('circle')
.attr('fill', '#0083C5')
.attr('r', isMobile ? 4 : 7)
d3.selectAll('.tag-node')
.append('circle')
.attr('fill', '#FFC434')
.attr('r', isMobile ? 4 : 7)
d3.selectAll('.story-node')
.append('foreignObject')
.attr('height', isMobile ? 18 : 35)
.attr('width', isMobile ? 18 : 35)
.attr('x', isMobile ? -9 : -17)
.attr('y', isMobile ? -18 : -30)
.attr('r', isMobile ? 16 : 30)
.append('xhtml:div')
.attr('class', 'node-image')
.append('xhtml:img')
.attr('src', (d: any) => d.image)
.attr('transform-origin', 'center')
.attr('height', isMobile ? 18 : 35)
.attr('width', isMobile ? 18 : 35)
d3.selectAll('.main-story-node')
.append('foreignObject')
.attr('height', isMobile ? 50 : 100)
.attr('width', isMobile ? 50 : 100)
.attr('x', isMobile ? -25 : -50)
.attr('y', isMobile ? -25 : -50)
.attr('r', isMobile ? 50 : 100)
.append('xhtml:div')
.attr('class', 'node-image')
.append('xhtml:img')
.attr('src', (d: any) => d.image)
.attr('transform-origin', 'center')
.attr('height', isMobile ? 50 : 100)
.attr('width', isMobile ? 50 : 100)
node
.append('foreignObject')
.attr('height', (d: any) => (d.class === 'main-story-node' ? 65 : 55))
.attr('width', (d: any) =>
isMobile
? d.class === 'main-story-node'
? 80
: 50
: d.class === 'main-story-node'
? 120
: 70
)
.attr('x', (d: any) =>
isMobile
? d.class === 'main-story-node'
? -40
: -25
: d.class === 'main-story-node'
? -60
: -35
)
.attr('y', (d: any) =>
isMobile
? d.class === 'main-story-node'
? 32
: 7
: d.class === 'main-story-node'
? 60
: 12
)
.append('xhtml:p')
.attr('class', (d: any) => d.class)
.text((d: any) => d.name)
simulation.on('tick', () => {
link
.attr('x1', (d: any) => d.source.x)
.attr('y1', (d: any) => d.source.y)
.attr('x2', (d: any) => d.target.x)
.attr('y2', (d: any) => d.target.y)
node
.attr('cx', (d: any) => d.x as number)
.attr('cy', (d: any) => d.y as number)
.attr('transform', (d: any) => {
return `translate(${d.x},${d.y})`
})
})
function transition(zoomLevel: number) {
group
.transition()
.delay(100)
.duration(500)
.call(zoom.scaleBy as any, zoomLevel)
}
transition(0.7)
d3.selectAll('.zoom-button').on('click', function () {
// @ts-ignore
if (this && this.id === 'zoom-in') {
transition(1.2) // increase on 0.2 each time
}
// @ts-ignore
if (this.id === 'zoom-out') {
transition(0.8) // deacrease on 0.2 each time
}
// @ts-ignore
if (this.id === 'zoom-init') {
group
.transition()
.delay(100)
.duration(500)
.call(zoom.scaleTo as any, 0.7) // return to initial state
}
})通过利用D3.js的缩放功能,我们可以轻松实现力导向图的整体拖拽功能,同时保留节点拖拽的功能。这种方法简单有效,可以满足大多数应用场景的需求。在实际应用中,可以根据具体需求进行调整和优化,例如添加过渡效果、调整缩放比例等。
以上就是D3.js Force Directed Graph:实现整体拖拽功能的解决方案的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号