React項目中使用dagre-d3

React 基本上已經成爲公司全部項目的前端必選框架了, JavaScript社區很是活躍,各類知名的庫幾乎都已經有了React的版本。可是一些庫尚未,好比dagre-d3dagre-d3是一個來繪製關係圖的Javascript庫。javascript

參考了一下react-highcharts,其實在React中實現dagre-d3也還好,並無那麼困難。html

  • 開始以前
npm i dagre-d3 -S
複製代碼
  • 準備一下數據nodeedge,也就是點和邊
const nodes = {
  0: { label: 'ReactComponent', style: 'fill: rgb(80, 194, 138);' },
  1: { label: 'props', style: 'fill: rgb(204, 230, 255);' },
  2: { label: 'context', style: 'fill: rgb(204, 230, 255);' },
  3: { label: 'refs', style: 'fill: rgb(204, 230, 255);' }
};

const edges = [
  [0, 1, { style: 'stroke: rgb(214, 214, 214); fill: none', curve: d3.curveBasis }],
  [0, 2, { style: 'stroke: rgb(214, 214, 214); fill: none', curve: d3.curveBasis }],
  [0, 3, { style: 'stroke: rgb(214, 214, 214); fill: none', curve: d3.curveBasis }]
];
複製代碼
  • 獲取DOM node

這一步很重要,咱們圖固然是要繪製在DOM上了前端

ref 簡介java

Refs and the DOMnode

React提供的ref屬性,表示爲對組件真正實例的引用,其實就是ReactDOM.render()返回的組件實例。 有一點要注意一下,ReactDOM.render()渲染組件時返回的是組件實例;而渲染dom元素時,返回是具體的dom節點。react

getDOMRef(ref) {
  console.log(ref);
}

getComponentRef(ref) {
  console.log(ref);
}

<svg ref={this.getDOMRef} />

<DagreD3 ref={this.getComponentRef} /> 複製代碼

這裏咱們用nodeTree和nodeTreeInner來保存具體的dom節點。git

setNodeTree = (nodeTree) => {
  this.nodeTree = nodeTree;
}

setNodeTreeInner = (nodeTreeInner) => {
  this.nodeTreeInner = nodeTreeInner;
}

render() {
  return (
    <svg ref={this.setNodeTree}> <g ref={this.setNodeTreeInner} /> </svg> ) } 複製代碼
  • 渲染到DOM上(開始畫圖)

因爲要獲取真正的DOM節點,因此須要將上面代碼的執行放到componentDidMount裏。在propsgithub

咱們定義一個renderDag方法,放到componentDidMountcomponentDidUpdate裏面npm

renderDag() {
  const { nodes, edges } = this.props;
  const g = new dagreD3.graphlib.Graph()
    .setGraph({}) // Set an object for the graph label
    .setDefaultNodeLabel(() => ({}))
    .setDefaultEdgeLabel(() => ({})); // Default to assigning a new object as a label for each new edge.
  Object.keys(nodes).forEach((id) => { // 畫點
    g.setNode(id, nodes[id]);
  });
  edges.forEach((edge) => { // 畫線
    edge[2] ? g.setEdge(edge[0], edge[1], edge[2]) : g.setEdge(edge[0], edge[1]);
  });
  
  // 渲染dag圖
  const svg = d3.select(this.nodeTree);
  const inner = d3.select(this.nodeTreeInner);
  // 鼠標滾動縮放
  const zoom = d3.zoom().on('zoom', () => inner.attr('transform', d3.event.transform));
  svg.call(zoom);

  const render = new dagreD3.render(); // eslint-disable-line

  render(inner, g);
}

複製代碼
  • 使用
import DagreD3, { d3 } from './react-dagre-d3';

const nodes = {
  0: { label: 'ReactComponent', style: 'fill: rgb(80, 194, 138);' },
  1: { label: 'props', style: 'fill: rgb(204, 230, 255);' },
  2: { label: 'context', style: 'fill: rgb(204, 230, 255);' },
  3: { label: 'refs', style: 'fill: rgb(204, 230, 255);' }
};

const edges = [
  [0, 1, { style: 'stroke: rgb(214, 214, 214); fill: none', curve: d3.curveBasis }],
  [0, 2, { style: 'stroke: rgb(214, 214, 214); fill: none', curve: d3.curveBasis }],
  [0, 3, { style: 'stroke: rgb(214, 214, 214); fill: none', curve: d3.curveBasis }]
];

<DagreD3 fit interactive graph={{ rankdir: 'LR' }} nodes={nodes} edges={edges} onNodeClick={this.onNodeClick} /> 複製代碼
  • 效果圖

  • 完整代碼
import React from 'react';
import PropTypes from 'prop-types';
import * as dagreD3 from 'dagre-d3';
import * as d3 from 'd3';

class DagreD3 extends React.Component {
  static defaultProps = {
    width: '100%',
    height: '100%',
    nodes: {},
    edges: [],
    graph: {},
    interactive: false,
    onNodeClick: () => {}
  }

  static propTypes = {
    width: PropTypes.oneOfType([
      PropTypes.string,
      PropTypes.number
    ]),
    height: PropTypes.oneOfType([
      PropTypes.string,
      PropTypes.number
    ]),
    nodes: PropTypes.object,
    edges: PropTypes.array,
    graph: PropTypes.object,
    interactive: PropTypes.bool,
    onNodeClick: PropTypes.func,
    onNodeHover: PropTypes.func
  }

  componentDidMount() {
    this.renderDag();
  }

  shouldComponentUpdate(nextProps) {
    return !(this.props.nodes === nextProps.nodes)
      || !(this.props.edges === nextProps.edges);
  }

  componentDidUpdate() {
    this.renderDag();
  }

  setNodeTree = (nodeTree) => {
    this.nodeTree = nodeTree;
  }

  setNodeTreeInner = (nodeTreeInner) => {
    this.nodeTreeInner = nodeTreeInner;
  }

  renderDag() {
    const { nodes, edges, interactive, fit, onNodeClick, graph } = this.props;
    const g = new dagreD3.graphlib.Graph()
      .setGraph({ ...graph }) // Set an object for the graph label
      .setDefaultNodeLabel(() => ({}))
      .setDefaultEdgeLabel(() => ({})); // Default to assigning a new object as a label for each new edge.
    Object.keys(nodes).forEach((id) => {
      g.setNode(id, nodes[id]);
    });
    edges.forEach((edge) => {
      edge[2] ? g.setEdge(edge[0], edge[1], edge[2]) : g.setEdge(edge[0], edge[1]);
    });

    const svg = d3.select(this.nodeTree);
    const inner = d3.select(this.nodeTreeInner);
    if (interactive) { // 自適應縮放
      const zoom = d3.zoom().on('zoom', () => inner.attr('transform', d3.event.transform));
      svg.call(zoom);
    }
    const render = new dagreD3.render(); // eslint-disable-line

    render(inner, g);


    render(inner, g);

    // 自適應寬高
    if (fit) {
      const { height: gHeight, width: gWidth } = g.graph();
      const { height, width } = this.nodeTree.getBBox();
      const transX = width - gWidth;
      const transY = height - gHeight;
      svg.attr('viewBox', `0 0 ${width} ${height}`);
      inner.attr('transform', d3.zoomIdentity.translate(transX, transY));
    }

    if (onNodeClick) { // 點擊事件
      svg.selectAll('g.node').on('click',
        id => onNodeClick(id));
    }
  }

  render() {
    const { width, height } = this.props;
    return (
      <svg width={width} height={height} ref={this.setNodeTree}> <g ref={this.setNodeTreeInner} /> </svg> ); } } export { d3 }; export default DagreD3; 複製代碼
相關文章
相關標籤/搜索