Add collaboration feature
[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
7 function diagonal(d) {
8
9         const offset = 50;
10         return 'M' + d.y + ',' + d.x
11                 + 'C' + (d.parent.y + offset) + ',' + d.x
12                 + ' ' + (d.parent.y + offset) + ',' + d.parent.x
13                 + ' ' + d.parent.y + ',' + d.parent.x;
14 }
15
16 const nodeRadius = 8;
17 const verticalSpaceBetweenNodes = 70;
18 const NARROW_HORIZONTAL_SPACES = 47;
19 const WIDE_HORIZONTAL_SPACES = 65;
20
21 const stratifyFn = stratify().id(d => d.id).parentId(d => d.parent);
22
23 class Tree extends Component {
24
25         // state = {
26         //      startingCoordinates: null,
27         //      isDown: false
28         // }
29
30         static propTypes = {
31                 name: PropTypes.string,
32                 width: PropTypes.number,
33                 allowScaleWidth: PropTypes.bool,
34                 nodes: PropTypes.arrayOf(PropTypes.shape({
35                         id: PropTypes.string,
36                         name: PropTypes.string,
37                         parent: PropTypes.string
38                 })),
39                 selectedNodeId: PropTypes.string,
40                 onNodeClick: PropTypes.func,
41                 onRenderedBeyondWidth: PropTypes.func
42         };
43
44         static defaultProps = {
45                 width: 500,
46                 allowScaleWidth : true,
47                 name: 'default-name'
48         };
49
50         render() {
51                 let {width, name, scrollable = false} = this.props;
52                 return (
53                         <div
54                                 className={`tree-view ${name}-container ${scrollable ? 'scrollable' : ''}`}>
55                                 <svg width={width} className={name}></svg>
56                         </div>
57                 );
58         }
59
60         componentDidMount() {
61                 this.renderTree();
62         }
63
64         // handleMouseMove(e) {
65         //      if (!this.state.isDown) {
66         //              return;
67         //      }
68         //      const container = select(`.tree-view.${this.props.name}-container`);
69         //      let coordinates = this.getCoordinates(e);
70         //      container.property('scrollLeft' , container.property('scrollLeft') + coordinates.x - this.state.startingCoordinates.x);
71         //      container.property('scrollTop' , container.property('scrollTop') + coordinates.y - this.state.startingCoordinates.y);
72         // }
73
74         // handleMouseDown(e) {
75         //      let startingCoordinates = this.getCoordinates(e);
76         //      this.setState({
77         //              startingCoordinates,
78         //              isDown: true
79         //      });
80         // }
81
82         // handleMouseUp() {
83         //      this.setState({
84         //              startingCorrdinates: null,
85         //              isDown: false
86         //      });
87         // }
88
89         // getCoordinates(e) {
90         //      var bounds = e.target.getBoundingClientRect();
91         //      var x = e.clientX - bounds.left;
92         //      var y = e.clientY - bounds.top;
93         //      return {x, y};
94         // }
95
96         componentDidUpdate(prevProps) {
97                 if (this.props.nodes.length !== prevProps.nodes.length ||
98                         this.props.selectedNodeId !== prevProps.selectedNodeId) {
99                         console.log('update');
100                         this.renderTree();
101                 }
102         }
103
104         renderTree() {
105                 let {width, nodes, name, allowScaleWidth, selectedNodeId, onRenderedBeyondWidth, toWiden} = this.props;
106                 if (nodes.length > 0) {
107
108                         let horizontalSpaceBetweenLeaves = toWiden ? WIDE_HORIZONTAL_SPACES : NARROW_HORIZONTAL_SPACES;
109                         const treeFn = tree().nodeSize([horizontalSpaceBetweenLeaves, verticalSpaceBetweenNodes]);//.size([width - 50, height - 50])
110                         let root = stratifyFn(nodes).sort((a, b) => a.data.name.localeCompare(b.data.name));
111                         let svgHeight = verticalSpaceBetweenNodes * root.height + nodeRadius * 6;
112
113                         treeFn(root);
114
115                         let nodesXValue = root.descendants().map(node => node.x);
116                         let maxX = Math.max(...nodesXValue);
117                         let minX = Math.min(...nodesXValue);
118
119                         let svgTempWidth = (maxX - minX) / 30 * (horizontalSpaceBetweenLeaves);
120                         let svgWidth = svgTempWidth < width ? (width - 5) : svgTempWidth;
121                         const svgEL = select(`svg.${name}`);
122                         const container = select(`.tree-view.${name}-container`);
123                         svgEL.html('');
124                         svgEL.attr('height', svgHeight);
125                         let canvasWidth = width;
126                         if (svgTempWidth > width) {
127                                 if (allowScaleWidth) {
128                                         canvasWidth = svgTempWidth;
129                                 }
130                                 // we seems to have a margin of 25px that we can still see with text
131                                 if (((svgTempWidth - 25) > width) && onRenderedBeyondWidth !== undefined) {
132                                         onRenderedBeyondWidth();
133                                 }
134                         };
135                         svgEL.attr('width', canvasWidth);
136                         let rootGroup = svgEL.append('g').attr('transform', `translate(${svgWidth / 2 + nodeRadius},${nodeRadius * 4}) rotate(90)`);
137
138                         // handle link
139                         rootGroup.selectAll('.link')
140                                 .data(root.descendants().slice(1))
141                                 .enter().append('path')
142                                 .attr('class', 'link')
143                                 .attr('d', diagonal);
144
145                         let node = rootGroup.selectAll('.node')
146                                 .data(root.descendants())
147                                 .enter().append('g')
148                                 .attr('class', node => `node ${node.children ? ' has-children' : ' leaf'} ${node.id === selectedNodeId ? 'selectedNode' : ''} ${this.props.onNodeClick ? 'clickable' : ''}`)
149                                 .attr('transform', node => 'translate(' + node.y + ',' + node.x + ')')
150                                 .on('click', node => this.onNodeClick(node));
151
152                         node.append('circle').attr('r', nodeRadius).attr('class', 'outer-circle');
153                         node.append('circle').attr('r', nodeRadius - 3).attr('class', 'inner-circle');
154
155                         node.append('text')
156                                 .attr('y', nodeRadius / 4 + 1)
157                                 .attr('x', - nodeRadius * 1.8)
158                                 .text(node => node.data.name)
159                                 .attr('transform', 'rotate(-90)');
160
161                         let selectedNode = selectedNodeId ? root.descendants().find(node => node.id === selectedNodeId) : null;
162                         if (selectedNode) {
163
164                                 container.property('scrollLeft', (svgWidth / 4) + (svgWidth / 4 - 100) - (selectedNode.x / 30 * horizontalSpaceBetweenLeaves));
165                                 container.property('scrollTop', (selectedNode.y / 100 * verticalSpaceBetweenNodes));
166
167                         } else {
168                                 container.property('scrollLeft', (svgWidth / 4) + (svgWidth / 4 - 100));
169                         }
170                 }
171         }
172
173         onNodeClick(node) {
174                 if (this.props.onNodeClick) {
175                         this.props.onNodeClick(node.data);
176                 }
177         }
178
179 }
180
181 export default Tree;