怎样在G6里面定义一个自定义节点一直都是一个头疼的问题,我们也在努力让大家能更简单的定义节点,去年我们推出了类jsx定义节点的方案,很感谢大家使用并给出的反馈,这一次我们决定暂时放下与框架无关的限制,通过许多开发者都熟悉的React来帮助大家更好的定义一个节点。向你介绍@antv/g6-react-node,让你开发自定义节点像是开发react组件一样简单。我们支持了类似于Flex布局的自动布局,并且对事件定义精细到了shape级别,只要你会使用react,就会写G6的自定义节点。并且支持图形元素的组件级复用,让你开发图元素更加便捷。

安装

  1. npm install --save @antv/g6-react-node
  2. # or
  3. yarn add @antv/g6-react-node

推荐配合G6 4+使用,但是本插件也兼容G6 3.x版本。

用React定义节点

一、 定义节点keyshape,添加阴影

image.png
所有的React定义节点,我们都推荐用一个Group作为根节点来包裹。首先我们定义来这个节点的主要图形(keyshape)。

  1. <Group>
  2. <Rect
  3. style={{
  4. radius: [8],
  5. fill: '#fff',
  6. shadowColor: '#ddd',
  7. shadowBlur: 8,
  8. shadowOffsetX: 2,
  9. shadowOffsetY: 2,
  10. }}
  11. keyshape
  12. draggable
  13. />
  14. </Group>

二、 利用G的渐变方法给节点加一个标题区

image.png
我们在上一步的基础上,再次定义一个带渐变的header。这里内容涉及到了flex布局的小知识,用direction来让元素改变排列的方式,使用flex属性的空元素来让两边可以更好左对齐和右对齐。

  1. <Group>
  2. <Rect
  3. style={{
  4. radius: [8],
  5. fill: '#fff',
  6. shadowColor: '#ddd',
  7. shadowBlur: 8,
  8. shadowOffsetX: 2,
  9. shadowOffsetY: 2,
  10. }}
  11. keyshape
  12. draggable
  13. >
  14. <Rect
  15. style={{
  16. minWidth: 200,
  17. fill: 'l(0) 0:#0049FF 1:#0EB7FF', //使用G的渐变定义fill
  18. radius: [8, 8, 0, 0],
  19. padding: 12,
  20. flexDirection: 'row',//内部元素水平排列
  21. cursor: 'pointer',
  22. alignContent: 'center',
  23. }}
  24. >
  25. <Text style={{ fill: '#fff' }}>数据输入节点 - 日志源</Text>
  26. <Group style={{ flex: 1 }} /> // 通过flex 1的空Group来实现两个元素两端对齐
  27. <Circle
  28. style={{
  29. r: 5,
  30. fill: '#00CF10',
  31. }}
  32. />
  33. </Rect>
  34. </Rect>
  35. </Group>

三、 在余下区域给节点取值来进行详细信息的补充

image.png
需要对不同节点的信息进行透出,那么你可以使用cfg,cfg是这个节点本身的model数据,你可以通过它来展示信息,甚至是控制渲染细节。

  1. const Card = ({ cfg }) => {
  2. return (
  3. <Group>
  4. <Rect
  5. style={{
  6. radius: [8],
  7. fill: '#fff',
  8. shadowColor: '#ddd',
  9. shadowBlur: 8,
  10. shadowOffsetX: 2,
  11. shadowOffsetY: 2,
  12. }}
  13. keyshape
  14. draggable
  15. >
  16. <Rect
  17. style={{
  18. minWidth: 200,
  19. fill: 'l(0) 0:#0049FF 1:#0EB7FF',
  20. radius: [8, 8, 0, 0],
  21. padding: 12,
  22. flexDirection: 'row',
  23. cursor: 'pointer',
  24. alignContent: 'center',
  25. }}
  26. >
  27. <Text style={{ fill: '#fff' }}>数据输入节点 - 日志源</Text>
  28. <Group style={{ flex: 1 }} />
  29. <Circle
  30. style={{
  31. r: 5,
  32. fill: '#00CF10',
  33. }}
  34. />
  35. </Rect>
  36. <Group style={{ flexDirection: 'row', margin: [6, 12] }}>
  37. <Text style={{ fill: '#222', fontWeight: 'bold' }}>节点ID</Text>
  38. <Text style={{ fill: '#000', margin: [0, 4] }}>{cfg?.id}</Text>
  39. </Group>
  40. <Group style={{ flexDirection: 'row', margin: [6, 12] }}>
  41. <Text style={{ fill: '#222', fontWeight: 'bold' }}>日志来源</Text>
  42. <Text style={{ fill: '#000', margin: [0, 4] }}>点击埋点数据源</Text>
  43. </Group>
  44. </Rect>
  45. </Group>
  46. );
  47. };

四、 定义组件让开发更加顺畅

image.png
我们支持来大部分组件的嵌套逻辑,这样就可以将一些相同的视觉元素绑定到一起,可以大幅度减少重复开发。在这里我们定义了一个Button组件来定义按钮组件。

  1. const Button = ({ color = '#2D5AF6', children, onClick }) => (
  2. <Rect
  3. onClick={onClick}
  4. style={{
  5. padding: [4, 8],
  6. fill: color,
  7. margin: [0, 4],
  8. radius: 4,
  9. cursor: 'pointer',
  10. }}
  11. >
  12. <Text style={{ fill: '#fff', cursor: 'pointer' }} onClick={onClick}>
  13. {children}
  14. </Text>
  15. </Rect>
  16. );
  17. const Card = ({ cfg }) => {
  18. return (
  19. <Group>
  20. <Rect
  21. style={{
  22. radius: [8],
  23. fill: '#fff',
  24. shadowColor: '#ddd',
  25. shadowBlur: 8,
  26. shadowOffsetX: 2,
  27. shadowOffsetY: 2,
  28. }}
  29. keyshape
  30. draggable
  31. >
  32. <Rect
  33. style={{
  34. minWidth: 200,
  35. fill: 'l(0) 0:#0049FF 1:#0EB7FF',
  36. radius: [8, 8, 0, 0],
  37. padding: 12,
  38. flexDirection: 'row',
  39. cursor: 'pointer',
  40. alignContent: 'center',
  41. }}
  42. >
  43. <Text style={{ fill: '#fff' }}>数据输入节点 - 日志源</Text>
  44. <Group style={{ flex: 1 }} />
  45. <Circle
  46. style={{
  47. r: 5,
  48. fill: '#00CF10',
  49. }}
  50. />
  51. </Rect>
  52. <Group style={{ flexDirection: 'row', margin: [6, 12] }}>
  53. <Text style={{ fill: '#222', fontWeight: 'bold' }}>节点ID</Text>
  54. <Text style={{ fill: '#000', margin: [0, 4] }}>{cfg?.id}</Text>
  55. </Group>
  56. <Group style={{ flexDirection: 'row', margin: [6, 12] }}>
  57. <Text style={{ fill: '#222', fontWeight: 'bold' }}>日志来源</Text>
  58. <Text style={{ fill: '#000', margin: [0, 4] }}>点击埋点数据源</Text>
  59. </Group>
  60. <Group style={{ flexDirection: 'row', margin: [6, 12] }}>
  61. <Button>详情</Button>
  62. <Button color="#FF1313">停止</Button>
  63. </Group>
  64. </Rect>
  65. </Group>
  66. );
  67. };

五、组件上的事件绑定

image.png
我们将G6大部分事件进行了独立封装,可以基于shape来定义事件的触发,方便更直观的管理事件。

  1. <Group style={{ flexDirection: 'row', margin: [6, 12] }}>
  2. <Button
  3. onClick={() => {
  4. alert(`你点击了${cfg.id}的详情`);
  5. }}
  6. >
  7. 详情
  8. </Button>
  9. <Button
  10. onClick={() => {
  11. alert(`你点击了${cfg.id}的停止`);
  12. }}
  13. color="#FF1313"
  14. >
  15. 停止
  16. </Button>
  17. </Group>

在G6中使用React定义的节点

当你已经使用React定义了一个节点后,我们就可以使用G6来注册这个自定义节点了

  1. import React from 'react';
  2. import G6 from '@antv/g6';
  3. import {
  4. Rect,
  5. Text,
  6. Circle,
  7. Image,
  8. Group,
  9. createNodeFromReact,
  10. appenAutoShapeListener,
  11. } from '@antv/g6-react-node';
  12. const Button = ({ color = '#2D5AF6', children, onClick }) => (
  13. <Rect
  14. onClick={onClick}
  15. style={{
  16. padding: [4, 8],
  17. fill: color,
  18. margin: [0, 4],
  19. radius: 4,
  20. cursor: 'pointer',
  21. }}
  22. >
  23. <Text style={{ fill: '#fff', cursor: 'pointer' }} onClick={onClick}>
  24. {children}
  25. </Text>
  26. </Rect>
  27. );
  28. const Card = ({ cfg }) => {
  29. return (
  30. <Group>
  31. <Rect
  32. style={{
  33. radius: [8],
  34. fill: '#fff',
  35. shadowColor: '#ddd',
  36. shadowBlur: 8,
  37. shadowOffsetX: 2,
  38. shadowOffsetY: 2,
  39. }}
  40. keyshape
  41. draggable
  42. >
  43. <Rect
  44. style={{
  45. minWidth: 200,
  46. fill: 'l(0) 0:#0049FF 1:#0EB7FF',
  47. radius: [8, 8, 0, 0],
  48. padding: 12,
  49. flexDirection: 'row',
  50. cursor: 'pointer',
  51. alignContent: 'center',
  52. }}
  53. >
  54. <Text style={{ fill: '#fff' }}>数据输入节点 - 日志源</Text>
  55. <Group style={{ flex: 1 }} />
  56. <Circle
  57. style={{
  58. r: 5,
  59. fill: '#00CF10',
  60. }}
  61. />
  62. </Rect>
  63. <Group style={{ flexDirection: 'row', margin: [6, 12] }}>
  64. <Text style={{ fill: '#222', fontWeight: 'bold' }}>节点ID</Text>
  65. <Text style={{ fill: '#000', margin: [0, 4] }}>{cfg.id}</Text>
  66. </Group>
  67. <Group style={{ flexDirection: 'row', margin: [6, 12] }}>
  68. <Text style={{ fill: '#222', fontWeight: 'bold' }}>日志来源</Text>
  69. <Text style={{ fill: '#000', margin: [0, 4] }}>点击埋点数据源</Text>
  70. </Group>
  71. <Group style={{ flexDirection: 'row', margin: [6, 12] }}>
  72. <Button
  73. onClick={() => {
  74. alert(`你点击了${cfg.id}的详情`);
  75. }}
  76. >
  77. 详情
  78. </Button>
  79. <Button
  80. onClick={() => {
  81. alert(`你点击了${cfg.id}的停止`);
  82. }}
  83. color="#FF1313"
  84. >
  85. 停止
  86. </Button>
  87. </Group>
  88. </Rect>
  89. </Group>
  90. );
  91. };
  92. G6.registerNode('test', createNodeFromReact(Card));
  93. // 当你创建好Graph后,如果需要使用在节点内定义的事件
  94. appenAutoShapeListener(graph)