精华内容
下载资源
问答
  • d3.js实现关系,多条关系连线,关系说明居中。根据json参数自动生成关系图谱。
  • 我的d3.js力导向图如何解决数据在首次渲染时,所有节点剧烈抖动,为什么抖动,(特别剧烈!!!</strong>)  </p>
  • d3.js力导向图使用详解

    千次阅读 2019-09-12 23:41:11
    创建一个力导向图需要三个东西: 仿真模拟系统 节点 当然,一般我们也会创建links来连接两个节点 仿真模拟系统中存在多个节点和多种类型的,通过控制节点的运动,每个节点都在多个的作用下不断发生移动...

    在这里插入图片描述
    创建一个力导向图需要三个东西:

    • 仿真模拟系统
    • 节点

    当然,一般我们也会创建links(边)来连接两个节点,例如上图

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

    节点

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

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

    常见的几种力:

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

    同时可以访问这个demo,测试下不同力的作用。

    links

    定义了节点之间的关系,通过节点间的连线定义。同时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 )
    }
    

    最后,这里有完整的代码,和在线demo

    参考文章

    展开全文
  • 摘要: 在本文中,我们将借助 D3.js 的灵活性这一优势,去新增一些 D3.js 本身...在上篇文章中(D3.js 力导向图的显示优化),我们说过 D3.js 在自定义图形上相较于其他开源可视化库的优势,以及如何对文档对象模型(.

    摘要: 在本文中,我们将借助 D3.js 的灵活性这一优势,去新增一些 D3.js 本身并不支持但我们想要的一些常见的功能:Nebula Graph 图探索的删除节点和缩放功能。

    文章首发于 Nebula Graph 官博:https://nebula-graph.com.cn/posts/d3-js-examples-for-advaned-uses/

    D3显示优化

    前言

    在上篇文章中(D3.js 力导向图的显示优化),我们说过 D3.js 在自定义图形上相较于其他开源可视化库的优势,以及如何对文档对象模型(DOM)进行灵活操作。既然 D3.js 辣么灵活,那是不是实现很多我们想做的事情呢?在本文中,我们将借助 D3.js 的灵活性这一优势,去新增一些 D3.js 本身并不支持但我们想要的一些常见的功能。

    构建 D3.js 力导向图

    在这里我们就不再细说 d3-force 粒子物理运动模块原理,感兴趣同学可以看看我们的上篇的简单描述, 本次实践我们侧重于可视化操作的功能实现。

    好的,进入我们的实践时间,我们还是以 D3.js 力导向图对图数据库的数据关系进行分析为目的,增加一些我们想要功能。

    首先,我们用 d3-force 力导向图来构建一个简单的关联网

    this.force = d3
            .forceSimulation()
            // 为节点分配坐标
            .nodes(data.vertexes)
            // 连接线
            .force('link', linkForce)
            // 整个实例中心
            .force('center', d3.forceCenter(width / 2, height / 2))
            // 引力
            .force('charge', d3.forceManyBody().strength(-20))
            // 碰撞力 防止节点重叠
            .force('collide',d3.forceCollide().radius(60).iterations(2));
    

    通过上述代码,我们可以得到下面这样一个可视化的节点和关系图。

    Nebula Graph Studio

    这里我们简单介绍下上图,上图为图数据库 Nebula Graph 可视化工具 Studio 的图探索功能截图,在业务上,图探索支持用户任意选中某个点进行拓展,找寻、显示同它存在某种关系的点,例如上图点 100 和 点 200 存在单向 follow 关系。

    上图数据量并不大,如果我们在拓展时返回的数据量较大或多步拓展出来的数据逐步累加显示,则会导致当前视图页节点和边极多,页面需呈现的数据信息量大,且也不好找到想要的某个节点。好的,一个新场景上线了:用户只想分析图中的部分节点数据,不想看到全部的节点信息。删除任意选中这个新功能就可以很好地应对上面场景,删除不需要的节点信息,只留下想探索的部分节点数据。

    支持删除任意选中功能

    在实现这个功能之前,我先开始介绍下 D3.js 自带 API。没错,还是上篇提及的 D3.js 的 enter() 及没提到的 exit()

    摘自文档的描述:

    数据绑定的时候可能出现 DOM 元素与数据元素个数不匹配的问题, enterexit 就是用来处理这个问题的。enter 操作用来添加新的 DOM 元素,exit 操作用来移除多余的 DOM 元素。
    如果数据元素多于 DOM 个数时用 enter,如果数据元素少于 DOM元素,则用 exit
    在数据绑定时候存在三种情形:

    • 数据元素个数多于 DOM 元素个数
    • 数据元素与 DOM 元素个数一样
    • 数据元素个数少于 DOM 元素个数

    根据文档描述,想实现删除任意选中功能还是很简单的,乐观的笔者想当然地认为直接在数据层面进行操作就行。于是笔者直接在 nodes 数据里删除选中的节点数据 node,然后根据官方用法 d3.select(this.nodeRef).exit().remove() 移除多余的元素,好的,我们现在来看看这样做会带来了什么?

    D3移除元素

    不想选中的节点是删除了,但其他节点的显示也乱了,节点颜色和属性同当前 DOM 节点对不上,为什么会这样呢?笔者又仔仔细细地看了一遍上面的文档描述,灵光一闪,来,先打印下 exit().remove() 的节点,看看到底它 remove 哪些节点?

    果然是它,D3.js enter().exit() 的触发其实是在监听元素的个数的变化,也就是说,如果总个数缺少了两个,它确实会触发 exit() 方法,但是它处理的数据不是真正需删除的数据,而是当前 nodes 数据最后两个节点。说白了 enter()、exit() 的触发原理,是 D3.js 监听当前数据的长度变化来触发的。然而 D3.js 在获取数据长度变化之后,以 exit() 为例,对单个数据的处理方法是根据长度的减量 N 截取数据数组位置中最后 N 位到最后一位区间的所有元素,enter() 则相反,会在数组位置中最后一个元素后面增加 N 个数据。

    所以,如果选中删除的是之前拓展探索出来的节点(它不是当前数据数组位置的最后一个元素),进行删除操作时,虽然从我们的 nodes 数据里面删除了这个数据,但是在已经存在的视图中,d3.select(this.nodeRef).exit() 方法定位到的操作元素却是最后一个,这样显示就乱套了,那么,我们该如何处理这个问题呢?

    这里就直接分享下我的方法,简单粗暴但有效——显然这个 exit() 并不能满足删除选中节点的业务需求,那我们单独地处理需删除的节点。我们定位到真实删除的节点 DOM 进行操作,为此我们需要在渲染时给每个节点绑定一个 ID,然后再进行遍历,根据已删除的节点数据找到这些需要删除的节点对应的 DOM,以下为我们的处理代码:

      componentDidUpdate(prevProps) {
        const { nodes } = this.props;
        if (nodes.length < prevProps.nodes.length) {
          const removeNodes = _.differenceBy(
            prevProps.nodes,
            nodes,
            (v: any) => v.name,
          );
          removeNodes.forEach(removeNode => {
            d3.select('#name_' + removeNode.name).remove();
          });
        } else {
          this.labelRender(this.props.nodes);
        }
      }
    

    其实在这里需要处理的不仅仅定位到当前真实删除节点的 DOM,还需要将它所关联的边、显示文案一并删除。因为没有起点/终点的边,是没有任何意义的,边、文案的处理方法同点删除的逻辑类似,这里不做赘述,如果你有任何疑问,欢迎前往我们的项目地址:https://github.com/vesoft-inc/nebula-web-docker 进行交流。

    支持按钮缩放功能

    说完删除选中点,在可视化视图中缩放操作也是比较常见的功能,D3.js 中的 d3.zoom() 就是用来实现缩放功能的,且该方法经过其他厂的业务考验相对来说成熟稳定,那我们还有什么理由要自己做呢?(要啥自行车 😂)。

    其实缩放功能纯粹是交互改动层面上的一个功能。采用滚轮控制缩放的方案的话,不了解 Nebula Graph Studio 的用户很难发现这种隐藏操作,而且滚动控制缩放无法控制缩放的明确比例,举个例子,用户想缩放 30% / 50%,对于这种限定的比例,滚动控制缩放就无能为力了。除此之外,笔者在实施滚轮缩放的过程中发现滚动缩放会影响节点和边的位置偏移,这又是什么原因造成的呢?

    通过查看 d3.zoom() 代码,我们发现 D3.js 本质是获取事件中 d3.event 的缩放值再针对整个画布修改 transform 属性值,但这样处理 svg 中的节点和边元素 x、y 坐标不发生变化,所以导致 d3.zoom() 实现缩放功能时,放大画布,视图会往坐左上方偏移(因为对画布来说,相较视图中的边元素 x、y 坐标,自己变小了),缩小画布,视图会往右下方偏移。

    发现问题形成的原因是解决问题的第一步,下面来解决下问题,在进行缩放时添加一个节点和边相对画布大小偏移量的变化处理逻辑,好的,那开始操作吧。

    我们先弄一个滑动条控件提供给用户进行手动控制缩放画布的比例,直接用 antd 的滑动条,根据它滑动的的值来控制整个画布缩放比例,下面直接贴代码了

     <svg
      width={width}
      viewBox={`0 0 ${width * (1 + scale)}  ${height * (1 + scale)}`}
      height={height}
      >
     {/*****/}
    </svg>
    

    上面代码中的 scale 参数是我们根据控件滚动条中缩放值来生成的,我们需要记录这个值来放大画布(svg 元素),从来造成视图缩小的效果的。

    此外,我们处理下上面提到的节点和边偏移问题时也需要 scale 值,因为我们需要给节点和边设置一个反偏移量。简单的说,画布放大 scale 倍,节点和边的 x、y 位置也要相对画布偏移当前的 scale 倍,这样就能保持在缩放过程中,节点和边位置相对画布大小变化而保持不变。下面就是处理节点缩放过程中偏移的关键代码

     const { width, height } = this.props;
        const scale = (100 - zoomSize) / 100;
        const offsetX = width * (scale / 2);
        const offsetY = height * (scale / 2);
        // 操作节点边父元素 DOM <g/> 的偏移
        d3.select(this.circleRef).attr(
          'transform',
          `translate(${offsetX} ${offsetY})`,
        );
    

    结语

    好了,以上便是笔者使用 D3.js 力导向图实现关系网的在自定义功能过程中思路和方法。不得不说,D3.js** **的自由度真的高,我们可以尽情地开脑洞实现我们想要的功能。

    在这次分享中,笔者分享了图数据库可视化业务中 2 个实用且用户高频使用的功能:任意选中删除节点、自定义缩放并优化视图偏移功能。说到可视化展示一个复杂的关系网,需要考虑的问题还很多,需要优化的交互和显示的地方也很多,我们会持续优化,后续我们会更新 D3.js 优化系列文,欢迎订阅 Nebula Graph 博客

    喜欢这篇文章?来来来,给我们的 GitHub 点个 star 表鼓励啦~~ 🙇‍♂️🙇‍♀️ [手动跪谢]

    交流图数据库技术?交个朋友,Nebula Graph 官方小助手微信:NebulaGraphbot 拉你进交流群~~

    作者有话说:Hi,我是 Nico,是 Nebula Graph 的前端工程师,对数据可视化比较感兴趣,分享一些自己的实践心得,希望这次分享能给大家带来帮助,如有不当之处,欢迎帮忙纠正,谢谢~

    展开全文
  • 最近在看到d3.js实现力导向图中节点的拖拽功能时,用到了下面的代码: function dragStarted(d) { d.fx = d.x; // <-E d.fy = d.y; } function dragged(d) { d.fx = d3.event.x; // <-F d.fy = d3....

    最近在看到d3.js实现力导向图中节点的拖拽功能时,用到了下面的代码:

        function dragStarted(d) {
            d.fx = d.x; // <-E
            d.fy = d.y;
        }
    
        function dragged(d) {
            d.fx = d3.event.x; // <-F
            d.fy = d3.event.y;
        }
    
        function dragEnded(d) {
            d.fx = null; // <-G
            d.fy = null;
        }

    这几个函数使用时非常简单,如下:

            newNodes.forEach(function (node) {
                svg.append("circle")
                        .data([node])
                    .attr("class", "node")
                    .attr("cx", function (d) {return d.x;})
                    .attr("cy", function (d) {return d.y;})
                    .attr("r", 1e-6)
                        .call(d3.drag() // <-D
                                .on("start", dragStarted)
                                .on("drag", dragged)
                                .on("end", dragEnded))
                        .transition()
                    .attr("r", 7)
                        .transition()
                        .delay(duration)
                    .attr("r", 1e-6)
                    .on("end", function () {nodes.shift();})
                    .remove();
            });

    主要疑惑在于,上面自定义的几个函数中,d.fx, d.fy的意义是什么?在拖拽过程中是怎么起作用的呢?

    原来,力模拟时,每一个节点是一个object,有这样几个属性(原文如下:https://github.com/d3/d3-force#simulation_nodes

    Each node must be an object. The following properties are assigned by the simulation:

    • index - the node’s zero-based index into nodes
    • x - the node’s current x-position
    • y - the node’s current y-position
    • vx - the node’s current x-velocity
    • vy - the node’s current y-velocity

    To fix a node in a given position, you may specify two additional properties:

    • fx - the node’s fixed x-position
    • fy - the node’s fixed y-position

    At the end of each tick, after the application of any forces, a node with a defined node.fx has node.x reset to this value and node.vx set to zero; likewise, a node with a defined node.fy has node.y reset to this value and node.vy set to zero. To unfix a node that was previously fixed, set node.fx and node.fy to null, or delete these properties.

    简单地说就是,如果node的fx,fy值如果设置了,那么x,y就会设为fx,fy,而vx,vy相应设为0,表示节点静止。而如果再次将fx,fy设置为null,或删除这一属性,那么x,x, vx,vy又会再一次受力的作用确定其位置了。

    展开全文
  • D3.js 作为一个前端,说到可视化除了听过 D3.js 的大名...和 EChart、Chart.js 等相比,D3.js** 的相对来说自由度会高很多,得益于 D3.js **中的 SVG 画图对事件处理器的支持,D3.js 可将任意数据绑定到文档对象模型...

    image

    D3.js

    作为一个前端,说到可视化除了听过 D3.js 的大名,常见的可视化库还有 EChartsChart.js,这两个库功能也很强大,但是有一个共同特点是封装层次高,留给开发者可设计和控制的部分太少。和 EChart、Chart.js 等相比,D3.js** 的相对来说自由度会高很多,得益于 D3.js **中的 SVG 画图对事件处理器的支持,D3.js 可将任意数据绑定到文档对象模型(DOM)上,也可以直接操作对象模型(DOM)完成 W3C DOM API 相关操作,对于想要展示自己设计图形的开发者,D3.js 绝对是一个不错的选择。

    d3-force 力导向图

    以实现一个关系网来说,d3-force 力导向图是不二的选择。d3-force 是 D3.js 实现以模拟粒子物理运动的 velocity Verlet 数值积分器的模块,可用来控制粒子和边秩序。在力导向图中,d3-force 中的每个节点都可以看成是一个放电粒子,粒子间存在某种斥力(库仑斥力)。同时,这些粒子间被它们之间的“边”所牵连,从而产生牵引力。

    而 d3-force 中的粒子在斥力和牵引力的作用下,从随机无序的初态不断发生位移,逐渐趋于平衡有序。整个图只有点 / 边,图形实现样例较少且自定义样式居多。

    下图就是最简单的关系网图,想要实现自己想要的关系网图,还是动手自己实现一个 D3.js 力导向图最佳。

    image

    构建 D3.js 力导向图

    在这里实践过程中,我们用 D3.js 力导向图来对图数据库的数据关系进行分析,其节点和关系线直观地体现出图数据库的数据关系,并且还可以关联相对应的图数据库语句完成拓展查询。进阶来说,可通过对文档对象模型(DOM)的直接操作同步到数据库进而更新数据,当然操作这个比较复杂,😂 不在本文中详细讲述。

    下面,我们来实现一个简单的力导向图,初窥 D3.js 对数据分析的作用和显示优化的一些思路。首先我们创建一个力导向图:

    this.force = d3
            .forceSimulation()
            // 为节点分配坐标
            .nodes(data.vertexes)
            // 连接线
            .force('link', linkForce)
            // 整个实例中心
            .force('center', d3.forceCenter(width / 2, height / 2))
            // 引力
            .force('charge', d3.forceManyBody().strength(-20))
            // 碰撞力 防止节点重叠
            .force('collide',d3.forceCollide().radius(60).iterations(2));
    

    通过上述代码,我们可以得到下面这样一个可视化的节点和关系图。

    image

    实现拓展查询显示优化

    看到关系图(上图),我们会发现有一个新需求:选中节点继续往下拓展查询。为了实现拓展查询,在这里笔者要介绍下 D3.js 自带 API。

    D3.js 的 enter() API 可对新增的节点作单独的逻辑处理,所以当拓展查询到新的节点 push 进节点数组时,不会去改变之前存在的节点信息(包括 x,y 坐标),而是按照 d3-force 实例分配的坐标进行渲染。从 API 上理解来说确实是这样,但是新增的节点对于 d3-force 这个已经存在的实例来说是一个不是简单的 push 就能处理的。因为 d3.forceSimulation() 这个模型给当前节点分配的位置坐标(x,y)是随机,目前看来没什么问题对不对?

    但由于d3.forceSimulation().node() 的坐标随机分配导致了图形拓展出来位置的随机出现,加上之前 d3-force 实例中我们设定好的 collide(碰撞力)和 links (引力)参数,所以和新节点相关联的节点受到牵引力影响互相靠近。在靠近的过程中又会和其他节点发送碰撞力的作用,当力导图存在的节点的情况下,这些新增节点出现时会让整个力导向图在 collide 和 links 的作用下不停地碰撞,进行牵引,直到每个节点都找到自己合适的位置,即碰撞力和牵引力都满足要求时才停止移动,看看下图,像不像宇宙大爆炸 🌞。

    image

    上述无序到有序熵减的过程,站在用户角度,每新增一个节点导致整个力导图都在一直在动,除了有一种抽搐的感,停止图形变化又需要长时间的等待,这是不能接受的。可 D3.js API enter() 又是这样定义规定好的,难道新增的节点和之前的节点的呈现处理需要开发者分开单独处理吗?如果是分开单独处理,每次节点渲染都要遍历判断是不是新增,在节点较多时反而更影响性能?那么如何优化这个新增节点呈现的问题呢?

    网上解决新增节点呈现问题,大多采用减小 d3-force 实例 collide,增大 links 的 distance 参数值,这样会让节点很快地找到平衡点从而使整个力导图稳定下来,这确实是一个好办法。但是这样节点之间的连线长度相差明显,而且图形整体偏大,对于大数据量的 case 来说,这种显示方式并不太适合。

    基于上述的方法,笔者有了另一种解决思路——保证新增节点是在该选中拓展的节点周围,也就是说直接把新增节点的坐标设置为对应选择拓展节点一样的 x,y 坐标而不用 D3 .forceSimulation().node() 分配,这样利用 d3-force 这个实例的节点碰撞力,保证新增节点的出现都不会覆盖,最终会在选中拓展节点周围出现。 这样处理虽然还是对新增节点小的范围内的节点有影响,但相对来说,不会大幅度地影响整个关系图形走势。关键代码如下:

    # 给新增的坐标设置为拓展起点中心或整个图中心
    addVertexes.map(d => {
      d.x = _.meanBy(selectVertexes, 'x') || svg.style('width') / 2;
      d.y = _.meanBy(selectVertexes, 'y') || svg.style('heigth') / 2;
    });
    

    如果没有选中节点(既添加起点)则该起点坐标位置就在图中心位置,对已存在的节点来说,影响程度会小很多,这还是一个很不错的思路,这个解决办法可以推荐一下。

    除了新增节点的呈现问题,整个图形的呈现还有另外一个问题:两点之间多边优化显示处理。

    两点之间多边优化显示处理

    当两个节点之间存在多条边关系时,默认连接线是直线的情况下肯定会出现多线覆盖。因此曲线连接便成了我们的另外需要解决的问题。
    曲线如何定义弯曲度保证两点之间的多条线不会交互覆盖呢?在多条线弯曲下,如何平均半圆弧弯曲避免全跑到某半圆弧上?定义曲线弧方向?

    上述问题都是下一步需要解决的问题,其实问题的解决方法也不少。目前笔者采用了先统计下两点之间的线条数,再将这些连接线分配到一个 map 里,两个节点的 name 字段进行拼接做成 key,这样计算得到两点之间的连接线总数。

    然后在遍历时同 map 的线根据方向分成正向、反向两组,正向组遍历给每条线追加设置一个 linknum 编号,同理,反向组遍历追加一个 -linknum 编号值。这个正向、反向判断方法很多,笔者是根据节点 source.name、target.name 进行比较,btw,这里其实是比较 ASCII 码。定义连接线的正反方向办法太多了,用两点之间的任意固定字段比较即可,在这里不做赘述。而我们设定的 linknum 值就是来确定该条弧线的弯曲度和弯曲方向的,这里搭配下面代码讲解比较好理解:

      const linkGroup = {};
      // 两点之间的线根据两点的 name 属性设置为同一个 key,加入到 linkGroup 中,给两点之间的所有边分成一个组
      edges.forEach((link: any) => {
        const key =
          link.source.name < link.target.name
            ? link.source.name + ':' + link.target.name
            : link.target.name + ':' + link.source.name;
        if (!linkGroup.hasOwnProperty(key)) {
          linkGroup[key] = [];
        }
        linkGroup[key].push(link);
      });
      // 遍历给每组去调用 setLinkNumbers 来分配 linkum
      edges.forEach((link: any) => {
        const key = setLinkName(link);
        link.size = linkGroup[key].length;
        const group = linkGroup[key];
        const keyPair = key.split(':');
        let type = 'noself';
        if (keyPair[0] === keyPair[1]) {
          type = 'self';
        }
        setLinkNumbers(group, type);
      });
    
    #根据不同方向分为 linkA,linkB 两个数组,分别分配两种 linknum,从而控制上下椭圆弧
    export function setLinkNumbers(group) {
      const len = group.length;
      const linksA: any = [];
      const linksB: any = [];
      for (let i = 0; i < len; i++) {
        const link = group[i];
        if (link.source.name < link.target.name) {
          linksA.push(link);
        } else {
          linksB.push(link);
        }
      }
      let startLinkANumber = 1;
      linksA.forEach(linkA=> {
        linkA.linknum = startLinkANumber++;
      }
      let startLinkBNumber = -1;
      linksB.forEach(linkB=> {
        linkB.linknum = startLinkBNumber--;
      }
    }
    

    按照我们上面描述的思路,给每条连接线分配 linknum 值后,接着在实现监听连接线的的 tick 事件函数里面判断 linknum 正负数判断设置 path 路径的弯曲度和方向 就行了,最终效果如下图

    image

    结语

    好了,以上便是笔者使用 D3.js 力导向图实现关系网的优化思路和方法。其实要构建一个复杂的关系网,需要考虑的问题很多,需要优化的地方也很多,今天给大家分享两个最容易遇到的新节点呈现、多边处理问题,后续我们会继续产出 D3.js 优化系列文,欢迎订阅 Nebula Graph 博客

    最后,你可以通过访问图数据库 Nebula Graph Studio:Nebula-Graph-Studio,体验下 D3.js 是如何呈现关系的。本文中如有任何错误或疏漏欢迎去 GitHub:https://github.com/vesoft-inc/nebula issue 区向我们提 issue 或者前往官方论坛:https://discuss.nebula-graph.com.cn/建议反馈 分类下提建议 👏;加入 Nebula Graph 交流群,请联系 Nebula Graph 官方小助手微信号:NebulaGraphbot

    作者有话说:Hi,我是 Nico,是 Nebula Graph 的前端工程师,对数据可视化比较感兴趣,分享一些自己的实践心得,希望这次分享能给大家带来帮助,如有不当之处,欢迎帮忙纠正,谢谢~

    关注公众号

    展开全文
  • 最近用到d3.js中的force力导向图,想实现效果如下,所有城市节点都在可视范围内,如果超出有滚动条也可以。遇到的问题是,当节点一多,有的节点就会跑到外面去,这边是通过加大charge相互作用,从原本的-300改为-...
  • 各位大佬们,怎么用D3.js力导向图时用自己的图标啊?就是像把节点换成自己的图片T-T
  • 将会给大家介绍了另外两种可视化图表,利用D3.js实现散点和气泡,文章通过多个方面介绍的非常详细,下面来一起看看吧。
  • D3.js力导向图(适用于其他类型图)中后添加元素遮盖已有元素的问题解决 上一篇说了在D3.js中动态增加节点及连线的一种实现方式,但是有后添加元素遮盖原节点的现象,这一篇说一下出现这个现象的解决办法. 在D3.js中后...
  • d3.js 力导向图

    千次阅读 2018-11-06 18:17:33
    <script src="https://d3js.org/d3.v3.min.js"> var width = 1900, height = 900; var color = d3.scale.category10(); // 构造一个 10 种颜色的序数比例尺 // color()只需要传入a-z字母即可获取对应的颜色 ...
  • 修改 以下是原完整代码 import React , { Component } from 'react' import * as d3 from 'd3' ; const WIDTH = 1900 ; const HEIGHT = 580 ; const R = 30 ...
  • 原文链接 <html> <head> <meta charset="utf-8"> <title>力导向图</title> <style> .links line { stroke: #999; ...
  • ,生成节点数据时直接修改偏移值
  • [图片说明](https://img-ask.csdn.net/upload/201901/25/1548412130_907960.jpg) ![图片说明](https://img-ask.csdn.net/upload/201901/25/1548412169_659772.png) 当点击0xc778时就变成二,为什么原来61...
  • 本文基于d3.js中的力导向图对关系网络进行可视化。针对实体之间多关系亦即节点之间多条连接线的问题,采用弧形连接线,同时对节点间的多条连接线进行动态编号,并根据编号绘制不同半径的弧线,从而解决多条弧形连接...
  • D3.js 力导向图

    2016-10-09 20:09:00
    力导向图是之前就有画过很多次的东西,但是这次的代码看上去很陌生,然后发现是D3更新了4.0.... 先贴代码 var svg = d3.select("svg"), width = +svg.attr("width"), height = +svg.attr("height"); var ...
  • 你可以把它理解成是一个创建作用,设置作用阈值和计算作用大小的高阶函数 这是github上D3.js对于forceManyBody()的解释。 简单来说,就是此函数可以创建一个带参的多体力对象,此对象中有一些函数,咱们可以...
  • D3.js创建力导向图(V4)

    2021-11-28 17:56:22
    D3.js创建力导向图(V4)   本文借鉴:https://blog.csdn.net/juzipidemimi/article/details/100787059   创建一个力导向图需要三个东西: 仿真模拟系统 节点   仿真模拟系统中存在多个节点和多种类型的...
  • 摘要: 在本文中,我们将借助 D3.js 的灵活性这一优势,去新增一些 D3.js 本身并不支持但我们想要的一些常见的功能:Nebula Graph 图探索的删除节点和缩放功能前言在上篇文章中(D3.js 力导向图的显示优化),我们说...
  • D3.js作为一个前端,说到可视化除了听过 D3.js 的大名,常见的可视化库还有 ECharts、Chart.js,这两个库功能也很强大,但是有一个共同特点是封装层次高,留给开发者可设计和控制的部分太少。和 EChart、Chart.js 等...
  • 最近项目需要写一个d3力导向图,之前没接触过d3.js 所以吧这次开发的经历写一下 文章目录分配点与线创建dom线点绘制线 友情提示:不要让设计设计的华丽呼哨,点多了很卡,而且svg 有些标签是不支持css 控制 某些...
  • 利用d3.js插件实现动态拓扑,动态增加节点,删除节点,结合拖拽操作,实现一个简单的演示利用d3.js插件实现动态拓扑,动态增加节点,删除节点,结合拖拽操作,实现一个简单的演示
  • svg_coordinate.js 用于计算矩形与连线的交点位置var coordinate = {};/*** 判断直线与水平线夹角* @param x1 点1的x坐标* @param y1 点1的y坐标* @param x2 点2的x坐标* @param y2 点2的y坐标* @returns {number}...
  • HTML导入代码模板:力导向图var nodes = [{name: "桂林"}, {name: "广州"}, {name: "厦门"}, {name: "杭州"}, {name: "上海"}, {name: "青岛"}, {name: "天津"}];var edges = [{source: 0,target: 1}, {source: 0,...
  • 大家在使用D3.js中的力导向图时,基本都会遇到动态增加节点及连线的需求,这里记录一下我的实现方式。话不多说,先放代码:Titlevar nodes = [{ name: "姓名1"},{ name: "姓名2"},{ name: "姓名3"},{ name: "姓名4"}...
  • 碎碎念:最近课题需要基于图论和力导向图可视化每个通道之间的连接性,MATLAB画的效果差强人意,于是上网搜寻找到JavaScript 的其中一个函数库——D3.js想快速得到

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,057
精华内容 422
关键字:

d3.js力导向图