原文地址:https://juejin.im/post/6865574039398645774

AntV G6是蚂蚁金服出品的图可视化引擎。
官网地址:g6.antv.vision/zh

本文将基于React和AntV G6实现一个demo:鼠标经过节点时,可高亮其上下游的关系图。

使用 AntV G6 制作可‘追寻’上下游的关系图 - 图1

准备工作

1、使用create-react-app脚手架创建React项目

  1. $ create-react-app antv-demo

2、安装@antv/g6,本demo使用@antv/g6@3.4.8

  1. $ npm i @antv/g6@3.4.8

3、使用

  1. import G6 from '@antv/g6';

Start!GOGOGO

1、数据应由请求返回,简单起见,我们造一个recordLists

  1. /** * 节点的唯一标识为ID,即数据中的upID(上游ID)和_selfID(本节点ID) */
  2. this.recordLists = [
  3. {
  4. "upID": "111",
  5. "superior": "董事长",
  6. "superiorName": "Bob",
  7. "_selfID": "222",
  8. "_self": "高级顾问",
  9. "_selfName": "Alice"
  10. },
  11. {
  12. "upID": "111",
  13. "superior": "董事长",
  14. "superiorName": "Bob",
  15. "_selfID": "333",
  16. "_self": "董事长助理",
  17. "_selfName": "Mary"
  18. },
  19. {
  20. "upID": "111",
  21. "superior": "董事长",
  22. "superiorName": "Bob",
  23. "_selfID": "444",
  24. "_self": "总经理",
  25. "_selfName": "Henry"
  26. },
  27. {
  28. "upID": "222",
  29. "superior": "高级顾问",
  30. "superiorName": "upTenant222",
  31. "_selfID": "555",
  32. "_self": "高级顾问助理",
  33. "_selfName": "William"
  34. }
  35. ]

G6需要的数据data包括nodesedges:

  1. const data = {
  2. nodes: [],
  3. edges: []
  4. };

orderLists的内容解析到data中:
使用 AntV G6 制作可‘追寻’上下游的关系图 - 图2

2、自定义节点

节点中需要显示职位及姓名,所以我们进行节点的自定义,继承基类single-shape
fittingString 方法处理当文字长度超过节点宽度时,超过文本框的内容使用‘...’

  1. G6.registerNode('relationNode',
  2. {
  3. drawShape: function drawShape(cfg, group) {
  4. const strokeColor = '#CDCDCD';
  5. const calcStrLen = str => {
  6. let len = 0;
  7. for (let i = 0; i < str.length; i++) {
  8. if (str.charCodeAt(i) > 0 && str.charCodeAt(i) < 128) {
  9. len++;
  10. } else {
  11. len += 2;
  12. }
  13. }
  14. return len;
  15. };
  16. const fittingString = (str, maxWidth, fontSize) => {
  17. const fontWidth = fontSize * 1.3; // 字号+边距
  18. maxWidth = maxWidth * 2; // 需要根据自己项目调整
  19. const width = calcStrLen(str) * fontWidth;
  20. const ellipsis = '…';
  21. if (width > maxWidth) {
  22. const actualLen = Math.floor((maxWidth - 10) / fontWidth);
  23. const result = str.substring(0, actualLen) + ellipsis;
  24. return result;
  25. }
  26. return str;
  27. };
  28. const rect = group.addShape('rect', {
  29. attrs: {
  30. x: -100 + 5,
  31. y: -25,
  32. width: 200,
  33. height: 60,
  34. radius: 3,
  35. stroke: strokeColor,
  36. fill: `l (0) 0:${strokeColor} ` + 0.015 + `:${strokeColor} ` + 0.015 + ':#fff',
  37. fillOpacity: 1,
  38. lineWidth: 1
  39. }
  40. });
  41. group.addShape('text', {
  42. attrs: {
  43. x: -95 + 10,
  44. y: 3,
  45. fill: '#333',
  46. text: fittingString(cfg.superiorName, 185, 14),
  47. fontSize: 14,
  48. fontWeight: 510,
  49. isName: true
  50. }
  51. })
  52. group.addShape('text', {
  53. attrs: {
  54. x: -95 + 10,
  55. y: 25,
  56. fill: '#999',
  57. text: fittingString(cfg.superior, 185, 12),
  58. fontSize: 12,
  59. fontWeight: 510,
  60. isPosition: true
  61. }
  62. })
  63. return rect;
  64. }
  65. }, 'single-shape');

3、图初始化

  1. const graph = new G6.Graph({
  2. //挂载节点
  3. container: 'mountNode',
  4. width: this.props.width || window.innerWidth,
  5. height: this.props.height || window.innerHeight,
  6. layout: {
  7. type: 'dagre',
  8. ranksep: 40,
  9. nodesep: 80,
  10. controlPoints: true
  11. },
  12. modes: {
  13. default: [
  14. 'drag-canvas',//可拖拽
  15. 'zoom-canvas'//可缩放
  16. ]
  17. },
  18. defaultNode: {
  19. //使用自定义节点
  20. type: 'relationNode',
  21. labelCfg: {
  22. style: {
  23. fill: '#666',
  24. fontSize: 14,
  25. fontWeight: 'bold'
  26. }
  27. }
  28. },
  29. defaultEdge: {
  30. type: 'polyline',
  31. style: {
  32. radius: 20,
  33. endArrow: {
  34. path: 'M 0,0 L 8,4 A 5,5,0,0,1,8,-4 Z',
  35. fill: '#ddd'
  36. },
  37. },
  38. },
  39. });

4、节点事件绑定

本demo使用的事件:
mouseenter:鼠标移入元素范围内触发,追寻上游和下游节点,并highlight,其余节点置灰;
mousemove:鼠标在元素内部移到时不断触发,若经过的区域为‘职级’或‘姓名’,则弹出tooltip;
mouseout:鼠标移出目标元素后触发,移除tooltip;
mouseleave:鼠标移出元素范围时触发,回到所有节点原始状态。

  1. graph.on('node:mouseenter', ev => {
  2. const item = ev.item;
  3. const edgeItems = ev.item.getInEdges() || [];
  4. const sonEdgeItems = ev.item.getOutEdges() || [];
  5. findParents(edgeItems, item, item);//追寻上游节点
  6. findSons(sonEdgeItems, item, item);//追寻下游节点
  7. graph.setItemState(item, 'highlight', true);
  8. graph.update(item, {
  9. style: {
  10. stroke: '#FD9839',
  11. fill: 'l (0) 0:#FD9839 ' + 0.015 + ':#FD9839 ' + 0.015 + ':#fff',
  12. cursor: 'pointer',
  13. }
  14. })
  15. changeOthers();//其余节点置灰
  16. });
  17. graph.on('node:mousemove', (evt) => {
  18. const { item, target, x, y } = evt;
  19. const {
  20. attrs: { isName, isPosition },
  21. } = target;
  22. const model = item.getModel();
  23. const { superiorName, _selfName, superior, _self, id } = model;
  24. if (isName || isPosition) {
  25. const postion = graph.getClientByPoint(x, y);
  26. createTooltip(postion, isName ? superiorName || _selfName : superior || _self, id);
  27. } else {
  28. removeTooltip(id);
  29. }
  30. });
  31. graph.on('node:mouseout', (evt) => {
  32. const { item, target } = evt;
  33. const {
  34. attrs: { isName, isPosition },
  35. } = target;
  36. const model = item.getModel();
  37. const { id } = model;
  38. if (isName || isPosition) {
  39. removeTooltip(id);
  40. }
  41. });
  42. graph.on('node:mouseleave', ev => {
  43. const item = ev.item;
  44. clearStates();
  45. graph.setItemState(item, 'highlight', false);
  46. graph.update(item, {
  47. style: {
  48. stroke: '#CDCDCD',
  49. fill: 'l (0) 0:#CDCDCD ' + 0.015 + ':#CDCDCD ' + 0.015 + ':#fff',
  50. cursor: 'default',
  51. fillOpacity: 1
  52. }
  53. })
  54. })

**

完整 DEMO

demo完整示例 https://github.com/Toxic12138/antv-demo