找回密码
 会员注册
查看: 25|回复: 0

G6自定义节点的不完全指北

[复制链接]

2万

主题

0

回帖

6万

积分

超级版主

积分
64454
发表于 2024-9-19 16:22:16 | 显示全部楼层 |阅读模式
前言前段时间有个好玩的需求:把人、货、场的整个流程通过一个个节点有序排列出来。一开始觉得这很简单呀,用G6一条线搞定。接着,需要把不同领域拆分开来,那也简单,按照规则拆出几份出来就好了。但在做的过程中,发现不太对,每个领域在同一个时间节点的数据没有上下对齐,每个节点下罗列的几行内容,横向看起来也有错落,看起来不直观。接着,对整个数据结构进行一个算法调整,完美实现上下对齐,图文节点清晰。过程中踩了些许的坑,很上头。以下是我们最终的产出效果:(部分信息做模糊处理)本次先不聊算法调整,接下来一起聊聊如何正确姿势使用G6自定义节点让图文排列整齐,希望能够帮助大家绕开一些坑点。首先,对于G6的一些基础概念,前面有同学已经总结过了,可以回顾一下~本文涉及:自定义节点方法节点间动画自定义节点目前G6官网教程里介绍了三种自定义节点:原生、类JSX语法、React。前文已讲过使用原生方式,本文则会介绍后面两种方式。在介绍这两种方式前,需要先说明两个核心概念,Shape和keyShape。图形Shape与keyShape图形ShapeShape指G6中的图形、形状,它可以是圆形、矩形、路径等。它一般与G6中的节点、边、Combo相关。G6中的每一种节点/边/Combo由一个或多个Shape组成KeyShape每个节点/边/Combo都必须有一个唯一的关键图形keyShape。keyShape是在节点/边/Combo的draw()方法或drawShape()方法中返回的图形对象。包围盒确定确定节点/Combo的包围盒(BoundingBox)——bbox(x,y,width,height),从而计算相关边的连入点(与相关边的交点),如下示例:从例子来看,自定义节点的边连接点受唯一的keyShape影响。对图形Shape和关键图形keyShape有个印象后,接下来看下第一种自定义节点方式。类JSX语法定义节点在G63.7.0及以后的版本中,使用类似JSX的语法来定义节点,只需要在使用G6.registerNode自定义节点时,将第二个参数设置为字符串或一个Function,其返回值为string类型看一个官网的例子:G6.registerNode(  'custom-node',  (cfg) => `          ${cfg.label || cfg.id}                 `,);其基础语法和大家熟悉的HTML标记语言基本相同:通过标签名来使用shape或者groupstyle属性用来描述形状样式,Object结构,其值可以是字符串,数字等JSON支持的数据类型这里需要注意几点:keyshape是全小写这种写法,默认所有布局都会按照正常的文档流,自上而下布局为了相对定位,形状样式这里新增了marginTop和marginLeft来定义左边和上边的间隔如果涉及到需要横向排列的元素,在上一个元素使用next:inline来实现下一个元素跟随在上个元素后方虽然类JSX语法相比原始的api方法简便多了,但在调整纵向横向节点的形状样式时,还是有一些复杂,总觉得形状怎么不听使唤呢,抓狂。接着,尝试了另一种自定义节点的方式:React使用React定义节点首先在安装完G6后,需要额外安装@antv/g6-react-nodenpm install @antv/g6-react-node// yarn add @antv/g6-react-node根据类jsx写法的例子,修改后:importReactfrom'react';importG6from'@antv/g6';import{Rect,Text,Image,Polygon,createNodeFromReact}from'@antv/g6-react-node';constCustomNode=({cfg})=>{const{label,id}=cfgreturn({label||id} )}G6.registerNode('custom-node',createNodeFromReact(CustomNode))从代码来看,不难发现,定义了一个函数组件,返回了自定义节点。这看起来,对于熟悉react开发的同学来说,更容易上手了。相比起来,还有两个优点:支持flex布局组件支持事件属性但在做的过程中,依旧是踩坑了:虽然文档说明支持marginTop、marginLeft,但是实际使用margin才能生效keyshape关键图形,必须是驼峰形式keyShape如果发现某个形状的事件响应有问题,大概率是子形状的影响,可以在子形状设置capture=false这三种方式都做了尝试,虽然文档不全,但相对推荐最后一种react自定义节点。除了节点设置形状样式外,还涉及到动画制作。节点间动画当我们使用上面的方式完成一幅图时,其基本功能已经实现,动画更多的作用是锦上添花。接下来主要说一下在本次需求中使用的边动画。动画函数先来看一下动画的核心——动画函数原理是通过该函数的返回值,定义每一帧的样式。shape.animate(  (ratio) => {    // 每一帧的操作,入参ratio:这一帧的比例值(Number)。返回值:这一帧需要变化的参数集(Object)。    return {    // 返回需要变化的参数集    };  },  {    repeat: true, // 动画重复    duration: 3000, // 一次动画的时间长度  },); 大致链接这个函数的原理,剩下的就是发动小脑筋去想一下如何通过这个函数去实现我们想要的效果,以下面这个效果为例:可以分为简单的三步:自定义一条边额外创建一个小的圆形节点在animate方法中,根据入参radio,不断修改小圆点的x,y坐标G6.registerEdge(  'circle-running',  {    afterDraw(cfg, group) {      // 获得当前边的第一个图形,这里是边本身的 path      const shape = group.get('children')[0];      // 边 path 的起点位置      const startPoint = shape.getPoint(0);      // 添加 circle 图形,并使其初始位置在边的起点上      const circle = group.addShape('circle', {        attrs: {          x: startPoint.x,          y: startPoint.y,          r: 3, // 小圆的半径        },      });      // 对小圆点添加动画      circle.animate(        (ratio) => {          // 根据比例值,获得在边path上对应比例的位置。          const tmpPoint = shape.getPoint(ratio);          // 返回需要变化的参数集,这里返回了位置 x 和 y          return {            x: tmpPoint.x,            y: tmpPoint.y,          };        },        {          repeat: true,           duration: 3000,        },      );     },  },  'cubic',); // 该自定义边继承内置三阶贝塞尔曲线 cubic此例子中,动画是写在afterDraw里,也就是在节点绘制完成后就开始动画了。那一整张图里,多条线,可能会同时运动,眼睛可能不会注意图里的重要信息,只顾着看动效了,这就没达到效果。在某些时候,我们应该能够根据不同的场景,通过鼠标来控制动画的启动和停止。动画的启动&停止比如需求需要:在鼠标悬浮在节点边的时候才触发当前节点到下一节点的动画效果,在鼠标离开节点边时停止动画。这里我们用到了另一组方法:setState:定义状态setItemState:设置状态1.在自定义节点边时,给边一个setState属性:// lineDash array 虚线运动效果,通过修改该数组可以自定义虚线的效果const lineDash = [4, 2, 1, 2];G6.registerEdge(  'to-others',  {    // name:状态名称,可以给该元素定义多个状态名,根据传入的状态名不同做出不同的回应    // value:状态是否可用,为 true 时可用,否则不可用    // item:元素实例    setState(name, value, item) {      // 获取关键图形      const shape = item.get('keyShape')      // 只有当设置的状态名为'running-edge'时执行      if (name === 'running-edge') {        if (value) {          // 状态值为true,表示开始执行动画          let index = 0;          shape.animate(            () => {              index++;              if (index > 9) index = 0;              const res = {                lineDash,                lineDashOffset: -index,              };              return res;            },            {              repeat: true,              duration: 3000,            },          );        } else {          // 状态值为false,表示停止动画          // 停止动画          shape.stopAnimate();           // 动画停止后并不会自动将元素状态复原,需要手动将元素的属性值修改回初始值          shape.attr('lineDash', null);         }      }    },  },  'polyline')2.给元素绑定事件graph.on('edge:mouseenter', (e) => {  const edges = e.item   // 给当前触发mouseenter事件的边元素,设置状态:running-edge为true  graph.setItemState(edges, 'running-edge', true)})graph.on('edge:mouseleave', (e) => {  const edges = e.item  // 给当前触发mouseleave事件的边元素,设置状态:running-edge为false  graph.setItemState(edges, 'running-edge', false)})通过这两步,就可以实现动画自由启动和停止了。当然,也可以尝试使用节点动画,让图在某些操作时看起来更生动,其实现原理类似。以上,大致是react自定义节点和动画的基本内容,不完全踩坑。总结以前在可视化编辑图和图分析方向的尝试比较少,这次或多或少遇到了一些难点和坑点。虽然做图很累很上头,但看到成果能够大幅度提升后续的协作效率,还是挺值得的。社区中G6/X6等方案也比较成熟,后续会有类似的需求产出,会做更多的尝试,并在此基础上做迭代沉淀。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 会员注册

本版积分规则

QQ|手机版|心飞设计-版权所有:微度网络信息技术服务中心 ( 鲁ICP备17032091号-12 )|网站地图

GMT+8, 2024-12-27 01:01 , Processed in 0.502134 second(s), 25 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表