1a1ebafa2ca61208130022cb43f4516d7d440ca6
[sdc.git] / openecomp-ui / src / nfvo-components / tree / Tree.jsx
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';
5
6 function diagonal(d) {
7     const offset = 50;
8     return (
9         'M' +
10         d.y +
11         ',' +
12         d.x +
13         'C' +
14         (d.parent.y + offset) +
15         ',' +
16         d.x +
17         ' ' +
18         (d.parent.y + offset) +
19         ',' +
20         d.parent.x +
21         ' ' +
22         d.parent.y +
23         ',' +
24         d.parent.x
25     );
26 }
27
28 const nodeRadius = 8;
29 const verticalSpaceBetweenNodes = 70;
30 const NARROW_HORIZONTAL_SPACES = 47;
31 const WIDE_HORIZONTAL_SPACES = 65;
32
33 const stratifyFn = stratify()
34     .id(d => d.id)
35     .parentId(d => d.parent);
36
37 class Tree extends Component {
38     // state = {
39     //  startingCoordinates: null,
40     //  isDown: false
41     // }
42
43     static propTypes = {
44         name: PropTypes.string,
45         width: PropTypes.number,
46         allowScaleWidth: PropTypes.bool,
47         nodes: PropTypes.arrayOf(
48             PropTypes.shape({
49                 id: PropTypes.string,
50                 name: PropTypes.string,
51                 parent: PropTypes.string
52             })
53         ),
54         selectedNodeId: PropTypes.string,
55         onNodeClick: PropTypes.func,
56         onRenderedBeyondWidth: PropTypes.func
57     };
58
59     static defaultProps = {
60         width: 500,
61         allowScaleWidth: true,
62         name: 'default-name'
63     };
64
65     render() {
66         let { width, name, scrollable = false } = this.props;
67         return (
68             <div
69                 className={`tree-view ${name}-container ${
70                     scrollable ? 'scrollable' : ''
71                 }`}>
72                 <svg width={width} className={name} />
73             </div>
74         );
75     }
76
77     componentDidMount() {
78         this.renderTree();
79     }
80
81     // handleMouseMove(e) {
82     //  if (!this.state.isDown) {
83     //          return;
84     //  }
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);
89     // }
90
91     // handleMouseDown(e) {
92     //  let startingCoordinates = this.getCoordinates(e);
93     //  this.setState({
94     //          startingCoordinates,
95     //          isDown: true
96     //  });
97     // }
98
99     // handleMouseUp() {
100     //  this.setState({
101     //          startingCorrdinates: null,
102     //          isDown: false
103     //  });
104     // }
105
106     // getCoordinates(e) {
107     //  var bounds = e.target.getBoundingClientRect();
108     //  var x = e.clientX - bounds.left;
109     //  var y = e.clientY - bounds.top;
110     //  return {x, y};
111     // }
112
113     componentDidUpdate(prevProps) {
114         if (
115             this.props.nodes.length !== prevProps.nodes.length ||
116             this.props.selectedNodeId !== prevProps.selectedNodeId
117         ) {
118             this.renderTree();
119         }
120     }
121
122     renderTree() {
123         let {
124             width,
125             nodes,
126             name,
127             allowScaleWidth,
128             selectedNodeId,
129             onRenderedBeyondWidth,
130             toWiden
131         } = this.props;
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)
142             );
143             let svgHeight =
144                 verticalSpaceBetweenNodes * root.height + nodeRadius * 6;
145
146             treeFn(root);
147
148             let nodesXValue = root.descendants().map(node => node.x);
149             let maxX = Math.max(...nodesXValue);
150             let minX = Math.min(...nodesXValue);
151
152             let svgTempWidth =
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`);
157             svgEL.html('');
158             svgEL.attr('height', svgHeight);
159             let canvasWidth = width;
160             if (svgTempWidth > width) {
161                 if (allowScaleWidth) {
162                     canvasWidth = svgTempWidth;
163                 }
164                 // we seems to have a margin of 25px that we can still see with text
165                 if (
166                     svgTempWidth - 25 > width &&
167                     onRenderedBeyondWidth !== undefined
168                 ) {
169                     onRenderedBeyondWidth();
170                 }
171             }
172             svgEL.attr('width', canvasWidth);
173             let rootGroup = svgEL
174                 .append('g')
175                 .attr(
176                     'transform',
177                     `translate(${svgWidth / 2 + nodeRadius},${nodeRadius *
178                         4}) rotate(90)`
179                 );
180
181             // handle link
182             rootGroup
183                 .selectAll('.link')
184                 .data(root.descendants().slice(1))
185                 .enter()
186                 .append('path')
187                 .attr('class', 'link')
188                 .attr('d', diagonal);
189
190             let node = rootGroup
191                 .selectAll('.node')
192                 .data(root.descendants())
193                 .enter()
194                 .append('g')
195                 .attr(
196                     'class',
197                     node =>
198                         `node ${node.children ? ' has-children' : ' leaf'} ${
199                             node.id === selectedNodeId ? 'selectedNode' : ''
200                         } ${this.props.onNodeClick ? 'clickable' : ''}`
201                 )
202                 .attr(
203                     'transform',
204                     node => 'translate(' + node.y + ',' + node.x + ')'
205                 )
206                 .on('click', node => this.onNodeClick(node));
207
208             node
209                 .append('circle')
210                 .attr('r', nodeRadius)
211                 .attr('class', 'outer-circle');
212             node
213                 .append('circle')
214                 .attr('r', nodeRadius - 3)
215                 .attr('class', 'inner-circle');
216
217             node
218                 .append('text')
219                 .attr('y', nodeRadius / 4 + 1)
220                 .attr('x', -nodeRadius * 1.8)
221                 .text(node => node.data.name)
222                 .attr('transform', 'rotate(-90)');
223
224             let selectedNode = selectedNodeId
225                 ? root.descendants().find(node => node.id === selectedNodeId)
226                 : null;
227             if (selectedNode) {
228                 container.property(
229                     'scrollLeft',
230                     svgWidth / 4 +
231                         (svgWidth / 4 - 100) -
232                         selectedNode.x / 30 * horizontalSpaceBetweenLeaves
233                 );
234                 container.property(
235                     'scrollTop',
236                     selectedNode.y / 100 * verticalSpaceBetweenNodes
237                 );
238             } else {
239                 container.property(
240                     'scrollLeft',
241                     svgWidth / 4 + (svgWidth / 4 - 100)
242                 );
243             }
244         }
245     }
246
247     onNodeClick(node) {
248         if (this.props.onNodeClick) {
249             this.props.onNodeClick(node.data);
250         }
251     }
252 }
253
254 export default Tree;