React 專案中使用 dagre-d3
React 已經成為了公司所有專案的前端必選框架了, JavaScript
社群非常活躍,而 React
也幾乎成為了前端最火熱的框架,所以各種知名的庫幾乎都已經有了 React
的版本。但是一些庫還沒有,比如 dagre-d3
。 dagre-d3
是一個來繪製關係圖的 Javascript
庫。
參考了一下 react-highcharts
,其實在 React
中實現 dagre-d3
其實也還好。
- 獲取DOM node
第一步很重要,我們圖當然是要繪製在DOM上了
ref
簡介
ofollow,noindex">Refs and the DOM
React提供的 ref
屬性,表示為對元件真正例項的引用,其實就是ReactDOM.render()返回的元件例項。 有一點要注意一下, ReactDOM.render()
渲染元件時返回的是元件例項;而渲染dom元素時,返回是具體的dom節點。
getDOMRef(ref) { console.log(ref); } getComponentRef(ref) { console.log(ref); } <svg ref={this.getDOMRef} /> <DagreD3 ref={this.getComponentRef} /> 複製程式碼

這裡我們用nodeTree和nodeTreeInner來儲存具體的dom節點。
setNodeTree = (nodeTree) => { this.nodeTree = nodeTree; } setNodeTreeInner = (nodeTreeInner) => { this.nodeTreeInner = nodeTreeInner; } render() { return ( <svg ref={this.setNodeTree}> <g ref={this.setNodeTreeInner} /> </svg> ) } 複製程式碼
- 渲染到DOM上(開始畫圖)
由於要獲取真正的 DOM
節點,所以需要將上面程式碼的執行放到 componentDidMount
我們定義一個 renderDag
方法,放到 componentDidMount
和 componentDidUpdate
裡面
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 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); // Run the renderer. This is what draws the final graph. 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; 複製程式碼
- 使用
<DagreD3 fit interactive graph={{ rankdir: 'LR' }} nodes={nodes} edges={edges} onNodeClick={this.onNodeClick} /> 複製程式碼