1 import React, { Component } from 'react';
2 import PropTypes from 'prop-types';
3 import { select } from 'd3-selection';
4 import { tree, stratify } from 'd3-hierarchy';
14 (d.parent.y + offset) +
18 (d.parent.y + offset) +
29 const verticalSpaceBetweenNodes = 70;
30 const NARROW_HORIZONTAL_SPACES = 47;
31 const WIDE_HORIZONTAL_SPACES = 65;
33 const stratifyFn = stratify()
35 .parentId(d => d.parent);
37 class Tree extends Component {
39 // startingCoordinates: null,
44 name: PropTypes.string,
45 width: PropTypes.number,
46 allowScaleWidth: PropTypes.bool,
47 nodes: PropTypes.arrayOf(
50 name: PropTypes.string,
51 parent: PropTypes.string
54 selectedNodeId: PropTypes.string,
55 onNodeClick: PropTypes.func,
56 onRenderedBeyondWidth: PropTypes.func
59 static defaultProps = {
61 allowScaleWidth: true,
66 let { width, name, scrollable = false } = this.props;
69 className={`tree-view ${name}-container ${
70 scrollable ? 'scrollable' : ''
72 <svg width={width} className={name} />
81 // handleMouseMove(e) {
82 // if (!this.state.isDown) {
85 // const container = select(`.tree-view.${this.props.name}-container`);
86 // let coordinates = this.getCoordinates(e);
87 // container.property('scrollLeft' , container.property('scrollLeft') + coordinates.x - this.state.startingCoordinates.x);
88 // container.property('scrollTop' , container.property('scrollTop') + coordinates.y - this.state.startingCoordinates.y);
91 // handleMouseDown(e) {
92 // let startingCoordinates = this.getCoordinates(e);
94 // startingCoordinates,
101 // startingCorrdinates: null,
106 // getCoordinates(e) {
107 // var bounds = e.target.getBoundingClientRect();
108 // var x = e.clientX - bounds.left;
109 // var y = e.clientY - bounds.top;
113 componentDidUpdate(prevProps) {
115 this.props.nodes.length !== prevProps.nodes.length ||
116 this.props.selectedNodeId !== prevProps.selectedNodeId
129 onRenderedBeyondWidth,
132 if (nodes.length > 0) {
133 let horizontalSpaceBetweenLeaves = toWiden
134 ? WIDE_HORIZONTAL_SPACES
135 : NARROW_HORIZONTAL_SPACES;
136 const treeFn = tree().nodeSize([
137 horizontalSpaceBetweenLeaves,
138 verticalSpaceBetweenNodes
139 ]); //.size([width - 50, height - 50])
140 let root = stratifyFn(nodes).sort((a, b) =>
141 a.data.name.localeCompare(b.data.name)
144 verticalSpaceBetweenNodes * root.height + nodeRadius * 6;
148 let nodesXValue = root.descendants().map(node => node.x);
149 let maxX = Math.max(...nodesXValue);
150 let minX = Math.min(...nodesXValue);
153 (maxX - minX) / 30 * horizontalSpaceBetweenLeaves;
154 let svgWidth = svgTempWidth < width ? width - 5 : svgTempWidth;
155 const svgEL = select(`svg.${name}`);
156 const container = select(`.tree-view.${name}-container`);
158 svgEL.attr('height', svgHeight);
159 let canvasWidth = width;
160 if (svgTempWidth > width) {
161 if (allowScaleWidth) {
162 canvasWidth = svgTempWidth;
164 // we seems to have a margin of 25px that we can still see with text
166 svgTempWidth - 25 > width &&
167 onRenderedBeyondWidth !== undefined
169 onRenderedBeyondWidth();
172 svgEL.attr('width', canvasWidth);
173 let rootGroup = svgEL
177 `translate(${svgWidth / 2 + nodeRadius},${nodeRadius *
184 .data(root.descendants().slice(1))
187 .attr('class', 'link')
188 .attr('d', diagonal);
192 .data(root.descendants())
198 `node ${node.children ? ' has-children' : ' leaf'} ${
199 node.id === selectedNodeId ? 'selectedNode' : ''
200 } ${this.props.onNodeClick ? 'clickable' : ''}`
204 node => 'translate(' + node.y + ',' + node.x + ')'
206 .on('click', node => this.onNodeClick(node));
210 .attr('r', nodeRadius)
211 .attr('class', 'outer-circle');
214 .attr('r', nodeRadius - 3)
215 .attr('class', 'inner-circle');
219 .attr('y', nodeRadius / 4 + 1)
220 .attr('x', -nodeRadius * 1.8)
221 .text(node => node.data.name)
222 .attr('transform', 'rotate(-90)');
224 let selectedNode = selectedNodeId
225 ? root.descendants().find(node => node.id === selectedNodeId)
231 (svgWidth / 4 - 100) -
232 selectedNode.x / 30 * horizontalSpaceBetweenLeaves
236 selectedNode.y / 100 * verticalSpaceBetweenNodes
241 svgWidth / 4 + (svgWidth / 4 - 100)
248 if (this.props.onNodeClick) {
249 this.props.onNodeClick(node.data);