D3.js Force Directed Graph:实现整体拖拽功能的解决方案

d3.js force directed graph:实现整体拖拽功能的解决方案

本文旨在解决D3.js力导向图中无法拖拽整个图的问题。通过将拖拽功能替换为缩放功能,并禁用鼠标滚轮缩放,实现了对整个图的平移操作,同时保留了节点拖拽的功能。本文将提供详细的代码示例和实现步骤,帮助开发者在D3.js力导向图中实现类似效果。

问题分析

在使用D3.js构建力导向图时,经常需要实现缩放和平移功能。D3.js提供了d3.zoom()来实现缩放功能,但如果直接将拖拽功能应用于包含所有节点和连接线的元素,可能无法达到预期效果,即无法拖动整个图。

解决方案:利用缩放功能实现平移

D3.js的缩放功能实际上是通过改变元素的transform属性来实现的,这为我们提供了一个思路:可以使用缩放功能来实现平移效果。具体步骤如下:

创建新的缩放函数: 创建一个新的d3.zoom()函数,并将其绑定到SVG元素上。在这个缩放函数中,我们将修改包含所有节点和连接线的元素的transform属性。

const zoomSvg = d3.zoom().on('zoom', (event) => {  group.attr('transform', event.transform).on('wheel.zoom', null);});

这里,group是包含所有节点和连接线的元素。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('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    }  })

注意事项

禁用鼠标滚轮缩放: 为了避免鼠标滚轮缩放与平移操作冲突,建议禁用鼠标滚轮缩放功能。性能优化: 对于大型力导向图,频繁的transform属性更新可能会影响性能。可以考虑使用requestAnimationFrame来优化性能。兼容性: 该解决方案基于D3.js的缩放功能,请确保你的D3.js版本支持该功能。

总结

通过利用D3.js的缩放功能,我们可以轻松实现力导向图的整体拖拽功能,同时保留节点拖拽的功能。这种方法简单有效,可以满足大多数应用场景的需求。在实际应用中,可以根据具体需求进行调整和优化,例如添加过渡效果、调整缩放比例等。

以上就是D3.js Force Directed Graph:实现整体拖拽功能的解决方案的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1518957.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年12月20日 12:01:08
下一篇 2025年12月20日 12:01:14

相关推荐

  • Elementor中Swiper JS引用返回undefined的解决方案

    本文旨在解决Elementor中使用Swiper JS库时,swiper实例返回undefined的问题。通过分析代码和Elementor的Swiper集成方式,提供直接初始化Swiper实例的解决方案,并探讨动态加载Swiper库的可能性,帮助开发者成功访问和修改Swiper实例,从而实现对Ele…

    好文分享 2025年12月20日
    000
  • JavaScript对象与HTML表格的动态绑定实践

    本教程详细讲解如何利用javascript动态管理和显示数据,特别是在html表格中展示javascript对象。我们将通过一个图书管理系统示例,学习如何使用构造函数创建对象、将对象存储在数组中,并通过dom操作实时更新表格内容,实现数据与视图的有效同步。 在现代Web开发中,动态地在网页上展示数据…

    2025年12月20日
    000
  • 使用JavaScript实现实时日期计数器:setInterval方法详解

    本文将指导您如何利用javascript的`setinterval`函数,结合`date`对象,创建一个动态更新的实时日期计数器。通过示例代码,您将学习如何计算并显示自特定日期以来的时间流逝,实现类似网页上的时间累加效果,为您的网页增添动态时间展示功能。 在现代网页应用中,实时显示时间流逝,例如项目…

    2025年12月20日
    000
  • JSX 语法规范:正确处理元素闭合标签

    本文旨在解决 react 开发中常见的“jsx 元素缺少闭合标签”错误。文章将详细阐述 jsx 元素正确的闭合语法,区分普通元素与自闭合组件的写法,并通过代码示例演示如何避免和修正此类问题,确保组件能够正确渲染,提升代码的健壮性。 理解 JSX 元素闭合规则 在 React 应用中,JSX(Java…

    2025年12月20日
    000
  • 掌握HTML5汉堡菜单的平滑动画:从瞬间切换到流畅过渡

    本教程详细讲解如何为html5汉堡菜单实现平滑的展开与收起动画,而非生硬的瞬间切换。通过摒弃传统的display: none,转而利用css的transform和transition属性,结合javascript的类切换机制,我们将实现菜单图标和内容区域的同步流畅动画效果,并提供完整的代码示例和最佳…

    2025年12月20日
    000
  • 在Express.js中利用async/await高效处理Axios异步请求

    本文深入探讨在Express.js应用中,如何通过`async/await`语法正确处理Axios发起的异步HTTP请求,以避免获取到未解析的Promise对象。教程将详细演示如何改造异步工具函数和Express路由处理器,确保数据能够被正确地等待、捕获并返回,从而实现清晰、可维护的异步代码流。 理…

    2025年12月20日
    000
  • JavaScript中从API获取并解析CSV数据:变量填充与数据匹配指南

    本文详细介绍了如何使用javascript从远程api获取csv数据,并利用papaparse库进行解析和处理。重点阐述了在数据解析过程中,确保变量正确填充的关键步骤,特别是如何核对csv文件的实际列名与代码中的数据访问方式,以避免常见的“变量无法获取数据”问题。通过示例代码,演示了数据获取、解析、…

    2025年12月20日
    000
  • 优化jQuery侧边栏菜单:解决首次加载折叠后需双击才能展开的逻辑问题

    本文旨在解决使用jquery实现侧边栏菜单在页面加载时默认折叠,但首次点击需要两次才能展开的问题。核心在于纠正javascript状态变量与初始dom状态的不一致。通过将控制菜单状态的`toggle`变量初始化为`false`,确保其与页面加载时侧边栏的折叠状态保持同步,从而实现单次点击即可正确切换…

    2025年12月20日
    000
  • 解决Elementor中Swiper实例未定义的问题

    本文旨在解决在elementor网站上集成swiper.js时,swiper实例返回“undefined”的常见问题。我们将深入探讨为何传统的数据访问方法可能失效,并提供两种可靠的解决方案:直接通过swiper构造函数初始化实例,以及在特定场景下动态加载swiper库以确保其可用性。通过这些方法,开…

    2025年12月20日
    000
  • React下拉选择框:优雅处理多字段显示与隐藏ID存储

    本文详细探讨了在react应用中,如何使用material-ui的autocomplete组件实现一个用户友好的下拉选择框。该选择框能够同时显示多个字段(如名称和描述),而在用户选择后,能够无缝地存储关联的隐藏id,避免了在选项中直接暴露id,提升了用户体验和代码的整洁性。 在构建交互式Web应用时…

    2025年12月20日
    000
  • Node.js与PostgreSQL集成:解决路由处理函数参数传递错误

    本文旨在解决Node.js Express应用中集成PostgreSQL时常见的参数传递错误。当数据库操作函数期望接收`req`和`res`对象,但在Express路由中以不正确的方式调用时,会导致`TypeError: Cannot read properties of undefined (re…

    2025年12月20日
    000
  • 如何从CSV API中准确提取和处理数据:JavaScript实践指南

    本文旨在解决从csv格式的api获取数据时,变量填充失败的问题。我们将深入探讨如何正确识别csv数据源的列名,利用`fetch` api和`papaparse`库进行数据获取、解析、筛选和类型转换,最终实现数据的准确提取和在控制台的展示,并提供一套完整的javascript代码实践方案。 在现代We…

    2025年12月20日
    000
  • 实现平滑动画的HTML5汉堡菜单

    本教程将指导您如何利用html、css和javascript,创建一个具有流畅动画效果的汉堡菜单。我们将重点讲解如何通过css `transform`属性实现菜单的平滑滑动显示与隐藏,以及汉堡图标的动态变形,避免`display: none`带来的生硬切换,提升用户体验。 在现代网页设计中,汉堡菜单…

    2025年12月20日
    000
  • TypeScript中处理异构泛型回调的类型推断挑战与解决方案

    本文探讨了在typescript中为不同事件类型使用泛型回调时遇到的类型推断问题,特别是当数组包含多种泛型实例时,typescript默认的同构推断机制会导致类型错误。文章提供了两种主要解决方案:一是通过将泛型参数提升至整个数组元组层面,利用映射元组类型和可变参数元组类型来精确推断;二是通过将con…

    2025年12月20日
    000
  • TypeScript 泛型回调处理多事件类型时的类型推断与解决方案

    本文深入探讨了在 typescript 中使用泛型回调函数处理不同事件类型的集合时遇到的类型推断挑战。针对 typescript 默认的同构数组推断行为,文章提供了两种主要解决方案:一是通过调整泛型参数,利用映射元组类型和可变参数元组类型强制编译器进行异构元组推断;二是通过定义分布式对象类型,将泛型…

    2025年12月20日
    000
  • Elementor中Swiper实例未定义:解决方案与实践

    本文旨在解决在elementor网站中尝试自定义swiper滑块功能时,swiper实例返回`undefined`的问题。我们将深入探讨两种核心解决方案:一是通过`new swiper()`构造函数直接初始化swiper实例,以确保正确引用;二是在swiper库未加载完成时,通过动态脚本加载机制确保…

    2025年12月20日
    000
  • React useEffect中循环数组、解决闭包陷阱与状态管理实践

    本文深入探讨了在react `useeffect`中使用`setinterval`循环展示数组内容时常见的挑战。我们将解决数组负索引访问错误、`useeffect`闭包导致的陈旧状态问题,并提供两种解决方案:利用`useref`获取最新状态,以及通过优化索引管理逻辑实现无缝循环。旨在帮助开发者理解并…

    2025年12月20日
    000
  • JavaScript虚拟滚动实现

    虚拟滚动通过只渲染可视区和缓冲区元素来提升长列表性能,利用占位器维持滚动高度,滚动时动态更新元素位置与内容,核心是计算可视区域的起始索引并复用DOM,结合requestAnimationFrame优化渲染。 虚拟滚动的核心是只渲染可视区域内的元素,而不是一次性加载全部数据。这样可以极大提升长列表的性…

    2025年12月20日
    000
  • Flask应用中正确显示HTML模板图片教程

    本教程详细介绍了如何在flask应用中正确配置和显示html模板中的图片。核心在于理解flask的静态文件服务机制,即默认将图片、css、js等静态资源放置在应用根目录下的`static`文件夹中,并通过`url_for(‘static’, filename=’&…

    2025年12月20日 好文分享
    000
  • 解决 Vue 3 组件非元素根节点上的运行时指令警告

    本文旨在解决 vue 3 升级过程中常见的 “runtime directive used on component with non-element root node” 警告。该警告表明组件模板的根节点不是单一的 html 元素,导致运行时指令无法按预期工作。核心解决方案…

    2025年12月20日 好文分享
    000

发表回复

登录后才能评论
关注微信