本文作者: @明多牧(mingduomu)

作为图中的重要组成部分之一,节点一直是 G6 中的一等公民,可是从 G6 1.0 一直到 G6 3.x,我们自定义节点都是基于代码去定义的,需要去接触底层 G的 API,在 group 上 addShape,这样方式自由度比较高,但是上手难度也比较高。通过使用类似于标签的方式去自定义节点,达到只要会写html就能够使用。

基础语法

  1. <[group|shape] [key]="value" style={{ [key]: value }}>
  2. <[more tag] /> ...
  3. <text>value</text>
  4. </[group|shape]>

基础语法和大家熟悉的html标记语言基本相同,通过标签名来使用shape或者group,同时定义shape需要填写shape的各个attributes,而定义形状样式的attrs则由style属性来进行表达。style里面的结构是一个Object,可以value可以是字符串,数字等JSON支持的数据类型(注意,这里不能够是函数,函数只会导致解析错误)。

黑科技,不推荐使用 其中,text节点的内容可以直接作为内部内容使用,可以使用{{}}语法来取node上下文中的变量,比如{{label}}取的就是node.label的值,如果没有取到,会直接变为{{label}}字符串。

自定义节点的类型和style参考:https://g6.antv.vision/zh/docs/api/nodeEdge/shapeProperties
其中,为了相对定位,我们新加入了marginTop和marginLeft来定义左边和上面的间隔。

推荐用法

  • 在最外层包裹group标签,保证节点里面图形树结构完整
  • 字符串最好使用单引号包裹,以免遇到解析错误
  • style中随node变化的变量推荐使用${}的模板语法加入
  • 图形内的相对定位推荐使用marginTop和marginLeft进行,x,y会破坏层级关系定位

节点示例

以一个简单的图形为例子,来为大家示范怎么使用:

  1. // node的数据结构
  2. const node = {
  3. "description": "ant_type_name_...",
  4. "label": "Type / ReferType",
  5. "color": "#2196f3",
  6. "meta": {
  7. "creatorName": "a_creator"
  8. },
  9. "id": "test",
  10. type
  11. }
  12. // 注册xml node
  13. G6.registerNode('rect-xml', (cfg) => `
  14. <group>
  15. <rect style={{
  16. width: 200,
  17. height: 75,
  18. }}>
  19. <rect style={{
  20. width: 150,
  21. height: 20,
  22. fill: ${cfg.color},
  23. radius: [6, 6, 0, 0],
  24. cursor: 'move'
  25. stroke: ${cfg.color}
  26. }} draggable="true">
  27. <text style={{
  28. marginTop: 2,
  29. marginLeft: 75,
  30. textAlign: 'center',
  31. fontWeight: 'bold',
  32. fill: '#fff' }}>${cfg.label}</text>
  33. </rect>
  34. <rect style={{
  35. width: 150,
  36. height: 55,
  37. stroke: ${cfg.color},
  38. fill: #ffffff,
  39. radius: [0, 0, 6, 6],
  40. }}>
  41. <text style={{ marginTop: 5, marginLeft: 3, fill: '#333', marginLeft: 4 }}>描述: ${cfg.description}</text>
  42. <text style={{ marginTop: 10, marginLeft: 3, fill: '#333', marginLeft: 4 }}>创建者: ${cfg.meta.creatorName}</text>
  43. </rect>
  44. </rect>
  45. <circle style={{
  46. stroke: ${cfg.color},
  47. r: 10,
  48. fill: '#fff',
  49. marginLeft: 75,
  50. cursor: 'pointer'
  51. }} name="circle">
  52. <image style={{ img: 'https://gw.alipayobjects.com/zos/antfincdn/FLrTNDvlna/antv.png', width: 12, height: 12, marginLeft: 70, marginTop: -5 }} />
  53. </circle>
  54. </group>
  55. `)

下载 (3).png

我们来仔细分析一下这个模板字符串的内容,是如何定义的:

  1. <group>
  2. <!-- 最外层的正方形 -->
  3. <rect style={{
  4. width: 200,
  5. height: 75,
  6. }}>
  7. <!-- 顶部的蓝色标题方形 -->
  8. <rect style={{
  9. width: 150,
  10. height: 20,
  11. fill: ${cfg.color},
  12. radius: [6, 6, 0, 0],
  13. cursor: 'move',
  14. stroke: ${cfg.color} <!-- 通过获取context也就是node中的属性color来填充fill -->
  15. }} draggable="true">
  16. <!-- 顶部的label -->
  17. <text style={{
  18. marginTop: 2,
  19. marginLeft: 75,
  20. textAlign: 'center',
  21. fontWeight: 'bold',
  22. fill: '#fff' }}>${label}</text>
  23. </rect>
  24. <!-- 下面的主体框 -->
  25. <rect style={{
  26. width: 150,
  27. height: 55,
  28. stroke: ${cfg.color},
  29. fill: #ffffff, <!-- 如果在不加''的情况下,直接被判断为字符串'#ffffff' -->
  30. radius: [0, 0, 6, 6],
  31. }}>
  32. <!-- 用marginTop和marginLeft进行相对定位 -->
  33. <text style={{ marginTop: 5, marginLeft: 3, fill: '#333', marginLeft: 4 }}>描述: ${cfg.description}}</text>
  34. <text style={{ marginTop: 10, marginLeft: 3, fill: '#333', marginLeft: 4 }}>创建者: ${cfg.meta.creatorName}</text>
  35. </rect>
  36. </rect>
  37. <!-- 最下面的圆形 -->
  38. <circle style={{
  39. stroke: ${cfg.color},
  40. r: 10,
  41. fill: '#fff',
  42. marginLeft: 75,
  43. cursor: 'pointer'
  44. }} name="circle">
  45. <image style={{ img: 'https://gw.alipayobjects.com/zos/antfincdn/FLrTNDvlna/antv.png', width: 12, height: 12, marginLeft: 70, marginTop: -5 }} />
  46. </circle>
  47. </group>

优点与不足

优点

  • 只要xml格式正确,可以实现组件级别的复用:在下面这个例子中,percentageBar就是一个可复用的结构 ``javascript const percentageBar = ({ width, used, height = 12 }) => `

const textXML = cfg => `

${cfg.id} ${cfg.cpuUsage > 60 ? ‘FULL‘ : ‘’} ${cfg.metric}: ${cfg.cpuUsage}% ${percentageBar({ width: 80, used: cfg.cpuUsage })} `;

  1. - 定义节点无需考虑update方法,基于xml语法树的diff进行最小改动
  2. - 可以轻松实现跟随数据变化的节点结构变化。
  3. ```javascript
  4. const percentageBar = ({ width, used, height = 12 }) => `
  5. <rect style={{
  6. marginLeft: 10,
  7. marginTop: 3,
  8. width: ${width},
  9. height: ${height},
  10. fill: '#fff',
  11. stroke: '#1890ff'
  12. }} name="body" >
  13. ${
  14. width > 0 ? `<rect style={{
  15. marginLeft: 10,
  16. width: ${width / 100 * used},
  17. height: ${height},
  18. fill: '#1890ff',
  19. stroke: '#1890ff'
  20. }}/>` : ''
  21. }
  22. </rect>

不足

  • 不能传递引用/function等一些不能被字符串代替的结构
  • tag的attribute需要a-bc转换为驼峰命名aBc
  • 不能自动选定keyshape,需要手动keyshape=”true”来进行指定

实现原理

文本节点解析

  1. <shape style={{ fill: '#ffffff' }} keyshape="true">
  2. title
  3. </shape>

把文本解析为xml节点树,shape作为type,style作为attr,剩余的attributes作为shape的其他属性,title会作为text图形的attr.text输入,解析完成得到一颗节点树,进入下一步的布局计算。

节点布局流程

节点diff原理

最终根据节点对shape item进行逐一的修改。