license fix
[sdc.git] / openecomp-ui / src / nfvo-components / tree / Tree.jsx
1 /*
2  * Copyright © 2016-2018 European Support Limited
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  * 
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  * 
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 import React, { Component } from 'react';
17 import PropTypes from 'prop-types';
18 import { select } from 'd3-selection';
19 import { tree, stratify } from 'd3-hierarchy';
20
21 function diagonal(d) {
22     const offset = 50;
23     return (
24         'M' +
25         d.y +
26         ',' +
27         d.x +
28         'C' +
29         (d.parent.y + offset) +
30         ',' +
31         d.x +
32         ' ' +
33         (d.parent.y + offset) +
34         ',' +
35         d.parent.x +
36         ' ' +
37         d.parent.y +
38         ',' +
39         d.parent.x
40     );
41 }
42
43 const nodeRadius = 8;
44 const verticalSpaceBetweenNodes = 70;
45 const NARROW_HORIZONTAL_SPACES = 47;
46 const WIDE_HORIZONTAL_SPACES = 65;
47
48 const stratifyFn = stratify()
49     .id(d => d.id)
50     .parentId(d => d.parent);
51
52 class Tree extends Component {
53     // state = {
54     //  startingCoordinates: null,
55     //  isDown: false
56     // }
57
58     static propTypes = {
59         name: PropTypes.string,
60         width: PropTypes.number,
61         allowScaleWidth: PropTypes.bool,
62         nodes: PropTypes.arrayOf(
63             PropTypes.shape({
64                 id: PropTypes.string,
65                 name: PropTypes.string,
66                 parent: PropTypes.string
67             })
68         ),
69         selectedNodeId: PropTypes.string,
70         onNodeClick: PropTypes.func,
71         onRenderedBeyondWidth: PropTypes.func
72     };
73
74     static defaultProps = {
75         width: 500,
76         allowScaleWidth: true,
77         name: 'default-name'
78     };
79
80     render() {
81         let { width, name, scrollable = false } = this.props;
82         return (
83             <div
84                 className={`tree-view ${name}-container ${
85                     scrollable ? 'scrollable' : ''
86                 }`}>
87                 <svg width={width} className={name} />
88             </div>
89         );
90     }
91
92     componentDidMount() {
93         this.renderTree();
94     }
95
96     // handleMouseMove(e) {
97     //  if (!this.state.isDown) {
98     //          return;
99     //  }
100     //  const container = select(`.tree-view.${this.props.name}-container`);
101     //  let coordinates = this.getCoordinates(e);
102     //  container.property('scrollLeft' , container.property('scrollLeft') + coordinates.x - this.state.startingCoordinates.x);
103     //  container.property('scrollTop' , container.property('scrollTop') + coordinates.y - this.state.startingCoordinates.y);
104     // }
105
106     // handleMouseDown(e) {
107     //  let startingCoordinates = this.getCoordinates(e);
108     //  this.setState({
109     //          startingCoordinates,
110     //          isDown: true
111     //  });
112     // }
113
114     // handleMouseUp() {
115     //  this.setState({
116     //          startingCorrdinates: null,
117     //          isDown: false
118     //  });
119     // }
120
121     // getCoordinates(e) {
122     //  var bounds = e.target.getBoundingClientRect();
123     //  var x = e.clientX - bounds.left;
124     //  var y = e.clientY - bounds.top;
125     //  return {x, y};
126     // }
127
128     componentDidUpdate(prevProps) {
129         if (
130             this.props.nodes.length !== prevProps.nodes.length ||
131             this.props.selectedNodeId !== prevProps.selectedNodeId
132         ) {
133             this.renderTree();
134         }
135     }
136
137     renderTree() {
138         let {
139             width,
140             nodes,
141             name,
142             allowScaleWidth,
143             selectedNodeId,
144             onRenderedBeyondWidth,
145             toWiden
146         } = this.props;
147         if (nodes.length > 0) {
148             let horizontalSpaceBetweenLeaves = toWiden
149                 ? WIDE_HORIZONTAL_SPACES
150                 : NARROW_HORIZONTAL_SPACES;
151             const treeFn = tree().nodeSize([
152                 horizontalSpaceBetweenLeaves,
153                 verticalSpaceBetweenNodes
154             ]); //.size([width - 50, height - 50])
155             let root = stratifyFn(nodes).sort((a, b) =>
156                 a.data.name.localeCompare(b.data.name)
157             );
158             let svgHeight =
159                 verticalSpaceBetweenNodes * root.height + nodeRadius * 6;
160
161             treeFn(root);
162
163             let nodesXValue = root.descendants().map(node => node.x);
164             let maxX = Math.max(...nodesXValue);
165             let minX = Math.min(...nodesXValue);
166
167             let svgTempWidth =
168                 (maxX - minX) / 30 * horizontalSpaceBetweenLeaves;
169             let svgWidth = svgTempWidth < width ? width - 5 : svgTempWidth;
170             const svgEL = select(`svg.${name}`);
171             const container = select(`.tree-view.${name}-container`);
172             svgEL.html('');
173             svgEL.attr('height', svgHeight);
174             let canvasWidth = width;
175             if (svgTempWidth > width) {
176                 if (allowScaleWidth) {
177                     canvasWidth = svgTempWidth;
178                 }
179                 // we seems to have a margin of 25px that we can still see with text
180                 if (
181                     svgTempWidth - 25 > width &&
182                     onRenderedBeyondWidth !== undefined
183                 ) {
184                     onRenderedBeyondWidth();
185                 }
186             }
187             svgEL.attr('width', canvasWidth);
188             let rootGroup = svgEL
189                 .append('g')
190                 .attr(
191                     'transform',
192                     `translate(${svgWidth / 2 + nodeRadius},${nodeRadius *
193                         4}) rotate(90)`
194                 );
195
196             // handle link
197             rootGroup
198                 .selectAll('.link')
199                 .data(root.descendants().slice(1))
200                 .enter()
201                 .append('path')
202                 .attr('class', 'link')
203                 .attr('d', diagonal);
204
205             let node = rootGroup
206                 .selectAll('.node')
207                 .data(root.descendants())
208                 .enter()
209                 .append('g')
210                 .attr(
211                     'class',
212                     node =>
213                         `node ${node.children ? ' has-children' : ' leaf'} ${
214                             node.id === selectedNodeId ? 'selectedNode' : ''
215                         } ${this.props.onNodeClick ? 'clickable' : ''}`
216                 )
217                 .attr(
218                     'transform',
219                     node => 'translate(' + node.y + ',' + node.x + ')'
220                 )
221                 .on('click', node => this.onNodeClick(node));
222
223             node
224                 .append('circle')
225                 .attr('r', nodeRadius)
226                 .attr('class', 'outer-circle');
227             node
228                 .append('circle')
229                 .attr('r', nodeRadius - 3)
230                 .attr('class', 'inner-circle');
231
232             node
233                 .append('text')
234                 .attr('y', nodeRadius / 4 + 1)
235                 .attr('x', -nodeRadius * 1.8)
236                 .text(node => node.data.name)
237                 .attr('transform', 'rotate(-90)');
238
239             let selectedNode = selectedNodeId
240                 ? root.descendants().find(node => node.id === selectedNodeId)
241                 : null;
242             if (selectedNode) {
243                 container.property(
244                     'scrollLeft',
245                     svgWidth / 4 +
246                         (svgWidth / 4 - 100) -
247                         selectedNode.x / 30 * horizontalSpaceBetweenLeaves
248                 );
249                 container.property(
250                     'scrollTop',
251                     selectedNode.y / 100 * verticalSpaceBetweenNodes
252                 );
253             } else {
254                 container.property(
255                     'scrollLeft',
256                     svgWidth / 4 + (svgWidth / 4 - 100)
257                 );
258             }
259         }
260     }
261
262     onNodeClick(node) {
263         if (this.props.onNodeClick) {
264             this.props.onNodeClick(node.data);
265         }
266     }
267 }
268
269 export default Tree;