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; 复制代码
相关文章
相关标签/搜索