元素及其配置

图的元素特指图上的节点NodeEdge。在上一章节中,我们已经将Tutorial案例的图绘制了出来,但是各个元素及其 label 在视觉上很简陋。本文通过将上一章节中简陋的元素美化成如下效果,介绍元素的属性、配置方法。 元素及其配置 - 图1

图 1 元素属性配置后的 Tutorial案例

基本概念

图的元素

图的元素特指图上的节点NodeEdge。G6 内置了一系列 内置的节点内置的边,供用户自由选择。G6 不同的内置节点或不同的内置边主要区别在于元素的 图形Shape,例如,节点可以是圆形、矩形、图片等。

元素的属性

不论是节点还是边,它们的属性分为两种:

  • 样式属性 style:对应 Canvas 中的各种样式,在元素状态State 发生变化时,可以被改变;
  • 其他属性:例如图形( shape)、id(id )一类在元素状态State发生变化时不能被改变的属性。

例如,G6 设定 hover 或 click 节点,造成节点状态的改变,只能自动改变节点的样式属性(如 fillstroke其他属性(如 shape 等)不能被改变。如果需要改变其他属性,要通过 graph.updateItem 手动配置。样式属性是一个名为 style 的对象, style 字段与其他属性并行。

数据结构

以节点元素为例,其属性的数据结构如下:

  1. {
  2. id: 'node0', // 元素的 id
  3. shape: 'circle', // 元素的图形
  4. size: 40, // 元素的大小
  5. label: 'node0' // 标签文字
  6. labelCfg: { // 标签配置属性
  7. positions: 'center',// 标签的属性,标签在元素中的位置
  8. style: { // 包裹标签样式属性的字段 style 与标签其他属性在数据结构上并行
  9. fontSize: 12 // 标签的样式属性,文字字体大小
  10. }
  11. }
  12. // ..., // 其他属性
  13. style: { // 包裹样式属性的字段 style 与其他属性在数据结构上并行
  14. fill: '#000', // 样式属性,元素的填充色
  15. stroke: '#888', // 样式属性,元素的描边色
  16. // ... // 其他样式属性
  17. }
  18. }

边元素的属性数据结构与节点元素相似,只是其他属性中多了 sourcetarget 字段,代表起始和终止节点的 id。细化在图 1 中 Tutorial案例 的视觉需求,我们需要完成:

  • 视觉效果:

    • R1: 节点的描边和填充色,对应节点样式属性:fillstroke
    • R2: 节点上标签文本的颜色,对应节点其他属性:labelCfg
    • R3: 边的透明度和颜色,对应边样式属性:opacitystroke
    • R4: 边上标签文本的方向和颜色,对应边其他属性:labelCfg
  • 数据与视觉映射:

    • R5: 根据数据中节点的 class 属性映射节点的形状,对应节点其他属性:shape
    • R6: 根据数据中边的 weight 属性映射边的粗细,对应边样式属性:lineWidth

配置属性

在 G6 中,根据不同的场景需求,有 7 种配置元素属性的方式。这里,我们简单介绍其中的两种:

  • 实例化图时配置元素的全局属性;
  • 在数据中配置。

1. 实例化图时全局配置

适用场景:所有节点统一的属性配置,所有边统一的属性配置。使用方式:使用图的两个配置项:

  • defaultNode:节点在默认状态下的样式属性style)和其他属性
  • defaultEdge:边在默认状态下的样式属性style)和其他属性

    注意 :由于是统一的配置,不能根据数据中的属性(如 classweight)等值的不同进行个性化设置,因此只能满足 R1、R2、R3、R4 需求。达到如下效果: 元素及其配置 - 图2

图 2 全局配置元素属性后的 Tutorial案例

通过如下方式在实例化图时 defaultNodedefaultEdge ,可以完成上图效果:

  1. const graph = new G6.Graph({
  2. // ... // 图的其他配置
  3. // 节点在默认状态下的样式配置(style)和其他配置
  4. defaultNode: {
  5. size: 30, // 节点大小
  6. // ... // 节点的其他配置
  7. // 节点样式配置
  8. style: {
  9. fill: 'steelblue', // 节点填充色
  10. stroke: '#666', // 节点描边色
  11. lineWidth: 1, // 节点描边粗细
  12. },
  13. // 节点上的标签文本配置
  14. labelCfg: {
  15. // 节点上的标签文本样式配置
  16. style: {
  17. fill: '#fff', // 节点标签文字颜色
  18. },
  19. },
  20. },
  21. // 边在默认状态下的样式配置(style)和其他配置
  22. defaultEdge: {
  23. // ... // 边的其他配置
  24. // 边样式配置
  25. style: {
  26. opacity: 0.6, // 边透明度
  27. stroke: 'grey', // 边描边颜色
  28. },
  29. // 边上的标签文本配置
  30. labelCfg: {
  31. autoRotate: true, // 边上的标签文本根据边的方向旋转
  32. },
  33. },
  34. });

2. 在数据中配置

适用场景:不同节点/边可以有不同的个性化配置。因此,这种配置方式可以满足 R5、R6 需求。使用方式:可以直接将配置写入数据文件;也可以在读入数据后,通过遍历的方式写入配置。这里展示读入数据后,通过遍历的方式写入配置。下面代码展示了如何遍历数据进行属性的配置:

  1. const nodes = remoteData.nodes;
  2. nodes.forEach(node => {
  3. if (!node.style) {
  4. node.style = {};
  5. }
  6. switch (
  7. node.class // 根据节点数据中的 class 属性配置图形
  8. ) {
  9. case 'c0': {
  10. node.shape = 'circle'; // class = 'c0' 时节点图形为 circle
  11. break;
  12. }
  13. case 'c1': {
  14. node.shape = 'rect'; // class = 'c1' 时节点图形为 rect
  15. node.size = [35, 20]; // class = 'c1' 时节点大小
  16. break;
  17. }
  18. case 'c2': {
  19. node.shape = 'ellipse'; // class = 'c1' 时节点图形为 ellipse
  20. node.size = [35, 20]; // class = 'c2' 时节点大小
  21. break;
  22. }
  23. }
  24. });
  25. graph.data(remoteData);

运行结果如下: 元素及其配置 - 图3

图 3

可以看到,图中有一些节点被渲染成了矩形,还有一些被渲染成了椭圆形。除了设置 shape 属性之外,我们还覆盖了上文全局配置的节点的 size 属性,在矩形和椭圆的情况下,size 是一个数组;而在默认圆形的情况下,G6 仍然会去读全局配置的 size 属性为数字 30。也就是说,动态配置属性让我们既可以根据数据的不同配置不同的属性值,也可以有能力覆盖全局静态的属性值。

进一步地,我们尝试根据数据的比重不同,配置不一样边的粗细:

  1. // const nodes = ...
  2. // 遍历边数据
  3. const edges = remoteData.edges;
  4. edges.forEach(edge => {
  5. if (!edge.style) {
  6. edge.style = {};
  7. }
  8. edge.style.lineWidth = edge.weight; // 边的粗细映射边数据中的 weight 属性数值
  9. });
  10. graph.data(remoteData);

结果如下: 元素及其配置 - 图4 如图所示,边的粗细已经按照数据的比重成功渲染了出来,但是边原有的样式(透明度、颜色)却丢失了。这是因为我们提到过动态配置属性会覆盖全局配置属性,这里配置了 style.lineWidth,导致覆盖了全局的 style 对象。解决办法是将被覆盖的边的样式都移到动态配置里面来:

  1. const graph = new G6.Graph({
  2. // ...
  3. defaultEdge: {
  4. // 去掉全局配置的 style
  5. labelCfg: {
  6. // 边上的标签文本配置
  7. autoRotate: true, // 边上的标签文本根据边的方向旋转
  8. },
  9. },
  10. });
  11. // 遍历点数据
  12. // const nodes = ...
  13. // nodes.forEach ...
  14. // 遍历边数据
  15. const edges = remoteData.edges;
  16. edges.forEach(edge => {
  17. if (!edge.style) {
  18. edge.style = {};
  19. }
  20. edge.style.lineWidth = edge.weight; // 边的粗细映射边数据中的 weight 属性数值
  21. // 移到此处
  22. opt.style.opacity = 0.6;
  23. opt.style.stroke = 'grey';
  24. });
  25. graph.data(remoteData);
  26. graph.render();

完整代码

至此,完整代码如下:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Tutorial Demo</title>
  6. </head>
  7. <body>
  8. <div id="mountNode"></div>
  9. <script src="https://gw.alipayobjects.com/os/antv/pkg/_antv.g6-3.1.0/build/g6.js"></script>
  10. <script>
  11. const graph = new G6.Graph({
  12. container: 'mountNode',
  13. width: 800,
  14. height: 600,
  15. fitView: true,
  16. fitViewPadding: [ 20, 40, 50, 20 ],
  17. defaultNode: {
  18. size: 30,
  19. labelCfg: {
  20. style: {
  21. fill: '#fff'
  22. }
  23. }
  24. },
  25. defaultEdge: {
  26. labelCfg: {
  27. autoRotate: true
  28. }
  29. },
  30. });
  31. const main = async () => {
  32. const response = await fetch(
  33. 'https://gw.alipayobjects.com/os/basement_prod/6cae02ab-4c29-44b2-b1fd-4005688febcb.json'
  34. );
  35. const remoteData = await response.json();
  36. const nodes = remoteData.nodes;
  37. const edges = remoteData.edges;
  38. nodes.forEach(node => {
  39. if (!node.style) {
  40. node.style = {};
  41. }
  42. node.style.lineWidth = 1;
  43. node.style.stroke = '#666';
  44. node.style.fill = 'steelblue';
  45. switch (node.class) {
  46. case 'c0': {
  47. node.shape = 'circle';
  48. break;
  49. }
  50. case 'c1': {
  51. node.shape = 'rect';
  52. node.size = [ 35, 20 ];
  53. break;
  54. }
  55. case 'c2': {
  56. node.shape = 'ellipse';
  57. node.size = [ 35, 20 ];
  58. break;
  59. }
  60. }
  61. });
  62. edges.forEach(edge => {
  63. if (!edge.style) {
  64. edge.style = {};
  65. }
  66. edge.style.lineWidth = edge.weight;
  67. edge.style.opacity = 0.6;
  68. edge.style.stroke = 'grey';
  69. });
  70. graph.data(remoteData);
  71. graph.render();
  72. };
  73. main();
  74. </script>
  75. </body>
  76. </html>

⚠️注意 若需更换数据,请替换 'https://gw.alipayobjects.com/os/basement_prod/6cae02ab-4c29-44b2-b1fd-4005688febcb.json&#39; 为新的数据文件地址。