使用 G6 讓兩個節點之間連多條邊

有以下的一份數據,如何使用 G6 讓兩個節點之間連多條邊?javascript

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'
  	}
  ]
};

接到上面這個問題後,咱們立刻就開始動手,二話不說先擼出了下面這段代碼。java

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();

看起來心情還不錯,給節點和邊都設置了樣式。node

運行上面的代碼之後,卻發現現實是如此的殘酷,說好的兩條邊哪去了,怎麼還只有一條呢?G6.pnggit

難道是線太直了,果斷換成貝塞爾曲線。github

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

// ...

難道是彎過頭,怎麼仍是這個鬼樣子?G6.pngide

對了,G6 還支持 quadratic 類型的邊,也拿來試試吧。this

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

// ...

神奇,竟然能夠了。G6.pngcode

這個時候,忽然想起,G6 3.1 版本新增了一個 polyline 類型的邊。blog

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

// ...

G6.png

拖動下節點看看。G6.pngip

這什麼鬼東西,看來仍是 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

相關文章
相關標籤/搜索