使用 G6 让两个节点之间连多条边

有如下的一份数据,如何使用 G6 让两个节点之间连多条边?

const data = {
  nodes: [{
    id: 'node1',
    x: 100,
    y: 150,
    label: 'node1',
  }, {
    id: 'node2',
    x: 300,
    y: 150,
    label: 'node2'
  }],
  edges: [{
        source: 'node1',
        target: 'node2'
    },
   {
     source: 'node2',
     target: 'node1'
    }
  ]
};

接到上面这个问题后,我们马上就开始动手,二话不说先撸出了下面这段代码。

const graph = new G6.Graph({
  container: GRAPH_CONTAINER,
  width: 500,
  height: 500,
  defaultNode: {
    style: {
      fill: '#DEE9FF',
      stroke: '#5B8FF9'
    },
    labelCfg: {
      style: {
        fontSize: 12
      }
    }
  },
  defaultEdge: {
    style: {
      stroke: '#e2e2e2'
    }
  }
});

graph.data(data);
graph.render();

看起来心情还不错,给节点和边都设置了样式。

运行上面的代码以后,却发现现实是如此的残酷,说好的两条边哪去了,怎么还只有一条呢?
G6.png

难道是线太直了,果断换成贝塞尔曲线。

const graph = new G6.Graph({
  // ...
  defaultEdge: {
    shape: 'cubic',
    style: {
      stroke: '#e2e2e2'
    }
  }
});

// ...

难道是弯过头,怎么还是这个鬼样子?

G6.png

对了,G6 还支持 quadratic 类型的边,也拿来试试吧。

const graph = new G6.Graph({
  // ...
  defaultEdge: {
    shape: 'quadratic',
    style: {
      stroke: '#e2e2e2'
    }
  }
});

// ...

神奇,居然可以了。
G6.png

这个时候,突然想起,G6 3.1 版本新增了一个 polyline 类型的边。

const graph = new G6.Graph({
  // ...
  defaultEdge: {
    shape: 'polyline',
    style: {
      stroke: '#e2e2e2'
    }
  }
});

// ...
G6.png

拖动下节点看看。

G6.png

这什么鬼东西,看来还是 quadratic 香啊。

这个时候,PD 突然说到,两个节点之间需要显示3条边,what,这不 so easy 吗?

年轻的你很开心地把数据改成了这样:

const data = {
  nodes: [{
    id: 'node1',
    x: 100,
    y: 150,
    label: 'node1',
  }, {
    id: 'node2',
    x: 300,
    y: 150,
    label: 'node2'
  }],
  edges: [{
        source: 'node1',
        target: 'node2'
    },
   {
     source: 'node2',
        target: 'node1'
    },
    {
     source: 'node2',
        target: 'node1'
    }
  ]
};

然而,执行结果却很骨感。
G6.png

新加的边哪去了?

就正在你 百思不得骑姐其解 的时候,突然灵光一闪,想到了原来 G6 还是强大的黑科技「自定义边」。

有了这个黑科技,什么样的需求,那还不是分分钟的事。

当然了,在使用「自定义边」的之前,有两件事还是需要明确下的:

  • 两个节点之间同方向超过一条边的,总需要有个标识来区分;
  • 需要有一个值控制边的弯曲度,以防边重叠

我们就在边数据中添加一个 edgeType 用于区分不同的边。有了这个约定以后,就可以开始动手撸码了。

自定义边后的效果如下:
G6.jpg

完善的自定义边的代码如下:

const edgeTypeColorMap = {
  type1: ['#531dab', '#391085', '#391085'],
  type2: ['#d9d9d9', '#bfbfbf', '#8c8c8c'],
  type3: ['#d3adf7', '#b37feb', '#9254de']
}

const defaultConf = {
  style: {
    lineAppendWidth: 5,
    lineDash: [0, 0],
    lineDashOffset: 0,
    opacity: 1,
    labelCfg: {
      style: {
        fillOpacity: 1
      }
    }
  },
  /**
   * 绘制边
   * @override
   * @param  {Object} cfg   边的配置项
   * @param  {G.Group} group 边的容器
   * @return {G.Shape} 图形
   */
  drawShape(cfg, group) {
    const item = group.get('item')
    const shapeStyle = this.getShapeStyle(cfg, item);
    const shape = group.addShape('path', {
      className: 'edge-path',
      attrs: shapeStyle
    });
    return shape;
  },
  drawLabel(cfg, group) {
    const labelCfg = cfg.labelCfg || {}
    const labelStyle = this.getLabelStyle(cfg, labelCfg, group)
    const text = group.addShape('text', {
      attrs: {
        ...labelStyle,
        text: cfg.label,
        fontSize: 12,
        fill: '#404040',
        cursor: 'pointer'
      },
      className: 'edge-label'
    })

    return text 
  },

  /**
   * 获取图形的配置项
   * @internal 仅在定义这一类节点使用,用户创建和更新节点
   * @param  {Object} cfg 节点的配置项
   * @return {Object} 图形的配置项
   */
  getShapeStyle(cfg, item) {
    const { startPoint, endPoint } = cfg
    const type = item.get('type')

    const defaultStyle =  this.getStateStyle('default', true, item)

    
    if(type === 'node') {
      return Object.assign({}, cfg.style, defaultStyle);
    }

    const controlPoints = this.getControlPoints(cfg);
    let points = [ startPoint ]; // 添加起始点
    // 添加控制点
    if (controlPoints) {
      points = points.concat(controlPoints);
    }
    // 添加结束点
    points.push(endPoint);
    const path = this.getPath(points);

    const style = Object.assign({}, { path }, cfg.style, defaultStyle);
    return style;
  },
  getControlPoints(cfg) {
    let controlPoints = cfg.controlPoints; // 指定controlPoints

    if (!controlPoints || !controlPoints.length) {
      const { startPoint, endPoint } = cfg;
      const innerPoint = G6.Util.getControlPoint(startPoint, endPoint, 0.5, cfg.edgeOffset || 30);
      controlPoints = [ innerPoint ];
    }
    return controlPoints;
  },
  /**
   * 获取三次贝塞尔曲线的path
   *
   * @param {array} points 起始点和两个控制点
   * @returns
   */
  getPath(points) {
    const path = [];
    path.push([ 'M', points[0].x, points[0].y ]);
    path.push([ 'Q', points[1].x, points[1].y, points[2].x, points[2].y ]);
    return path;
  },
  /**
   * 根据不同状态,获取不同状态下的样式值
   * @param {string} name 
   * @param {string} value 
   * @param {Item} item 
   */
  getStateStyle(name, value, item) {
    const model = item.getModel()
    const { style = {} } = model

    const defaultStyle = Object.assign({}, this.style)

    // 更新颜色
    return {
      ...defaultStyle,
      lineWidth: 1,
      stroke: edgeTypeColorMap[model.edgeType] && edgeTypeColorMap[model.edgeType][0],
      ...style
    }
  },
  /**
   * 拖动时更新path及边的label
   *
   * @param {object} cfg 边的model
   * @param {Edge} item 边的实例
   */
  update(cfg, item) {
    const { data, style, 
      startPoint, endPoint, labelCfg = {} } = cfg
    const group = item.getContainer()
    const model = data || cfg

    const defaultStyle = Object.assign({}, this.style, {
      lineWidth: 1,
      stroke: edgeTypeColorMap[model.edgeType] && edgeTypeColorMap[model.edgeType][0]
    }, style)

    const { opacity, onlyHideText } = defaultStyle

    // 更新 path
    const keyShape = item.getKeyShape();

    const controlPoints = this.getControlPoints(cfg);

    keyShape.attr({
      path: [
        ['M', startPoint.x, startPoint.y],
        ['Q', controlPoints[0].x, controlPoints[0].y, endPoint.x, endPoint.y]
      ],
      ...defaultStyle
    });

    const labelStyle = this.getLabelStyle(cfg, labelCfg, group);
    const text = group.findByClassName('edge-label');
    const attrs = {
      ...labelStyle,
      fillOpacity: onlyHideText ? 0 : opacity === 0 ? opacity : 1,
      fill: '#404040'
    }
    if(text) {
      text.resetMatrix();
      text.attr(attrs);
    }
  }
};

G6.registerEdge('quadratic-label-edge', defaultConf, 'quadratic');

const GRAPH_CONTAINER = 'container';

const data = {
  nodes: [{
    id: 'node1',
    x: 100,
    y: 150,
    label: 'node1',
  }, {
    id: 'node2',
    x: 300,
    y: 150,
    label: 'node2'
  }],
  edges: [{
        source: 'node1',
        target: 'node2',
    edgeType: 'type1'
    },
   {
     source: 'node2',
        target: 'node1',
     edgeType: 'type2'
    },
    {
     source: 'node2',
        target: 'node1',
      edgeType: 'type3',
      edgeOffset: -20
    }
  ]
};

const graph = new G6.Graph({
  container: GRAPH_CONTAINER,
  width: 500,
  height: 500,
  modes: {
    default: [{
      type: 'drag-node',
      delegate: false
    }]
  },
  defaultNode: {
    style: {
      fill: '#DEE9FF',
      stroke: '#5B8FF9'
    },
    labelCfg: {
      style: {
        fontSize: 12
      }
    }
  },
  defaultEdge: {
    shape: 'quadratic-label-edge',
  }
});

graph.data(data);
graph.render();

到这里为止,我们也就实现了让两个节点之间展示多条边的功能。

G6 是 AntV 团队的图可视化引擎,更多动态请关注我们的 GitHub,求关注,求 Star

你可能感兴趣的:(使用 G6 让两个节点之间连多条边)