Rift.IO OSM R1 Initial Submission
[osm/UI.git] / skyquake / framework / widgets / topology / topologyTree.jsx
1
2 /*
3  * 
4  *   Copyright 2016 RIFT.IO Inc
5  *
6  *   Licensed under the Apache License, Version 2.0 (the "License");
7  *   you may not use this file except in compliance with the License.
8  *   You may obtain a copy of the License at
9  *
10  *       http://www.apache.org/licenses/LICENSE-2.0
11  *
12  *   Unless required by applicable law or agreed to in writing, software
13  *   distributed under the License is distributed on an "AS IS" BASIS,
14  *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  *   See the License for the specific language governing permissions and
16  *   limitations under the License.
17  *
18  */
19 import React from 'react';
20 import ReactDOM from 'react-dom';
21 import d3 from 'd3';
22 import DashboardCard from '../dashboard_card/dashboard_card.jsx';
23 import _ from 'lodash';
24 import $ from 'jquery';
25 import './topologyTree.scss';
26
27 export default class TopologyTree extends React.Component {
28     constructor(props) {
29         super(props);
30         this.data = props.data;
31         this.selectedID = 0;
32         this.nodeCount = 0;
33         this.size = this.wrapperSize();
34         this.tree = d3.layout.tree()
35             .size([this.props.treeHeight, this.props.treeWidth]);
36         this.diagonal = d3.svg.diagonal()
37             .projection(function(d) { return [d.y, d.x]; });
38         this.svg = null;
39     }
40     componentWillReceiveProps(props) {
41         let self = this;
42         if(!this.svg) {
43             let zoom = d3.behavior.zoom()
44                 .translate([this.props.maxLabel, 0])
45                 .scaleExtent([this.props.minScale, this.props.maxScale])
46                 .on("zoom", self.zoom);
47             let svg = this.selectParent().append("svg")
48                     .attr("width", this.size.width)
49                     .attr("height", this.size.height)
50                 .append("g")
51                     .call(zoom)
52                 .append("g")
53                     .attr("transform", "translate(" + this.props.maxLabel + ",0)");
54
55             svg.append("rect")
56                 .attr("class", "overlay")
57                 .attr("width", this.size.width)
58                 .attr("height", this.size.height);
59             // this.svg = d3.select()
60             this.svg = svg;
61             this.props.selectNode(props.data);
62         }
63         if(props.data.hasOwnProperty('type') && !this.props.hasSelected) {
64             this.selectedID = props.data.id;
65             //Commenting out to prevent transmitter push error
66             //this.props.selectNode(props.data);
67         }
68         if(this.svg) {
69           this.update(_.cloneDeep(props.data));
70           // this.selectedID = props.data.id;
71         }
72     }
73
74     wrapperSize() {
75         if (this.props.useDynamicWrapperSize) {
76             try {
77                 let wrapper = $(".topologyTreeGraph-body");
78
79                 return {
80                     width: wrapper.width(),
81                     height: wrapper.height()
82                 }
83             } catch (e) {
84                 console.log("ERROR: cannot get width and/or height from element."+
85                     " Using props for width and height. e=", e);
86                 return {
87                     width: this.props.width,
88                     height: this.props.height
89                 }
90             }
91         } else {
92             return {
93                 width: this.props.width,
94                 height: this.props.height
95             }
96         }
97     }
98     selectParent() {
99         return d3.select(document.querySelector('#topology'));
100     }
101     computeRadius(d) {
102         // if(d.parameters && d.parameters.vcpu) {
103         //     return this.props.radius + d.parameters.vcpu.total;
104         // } else {
105             return this.props.radius;
106         // }
107     }
108     click = (d) => {
109         this.props.selectNode(d);
110         this.selectedID = d.id;
111         // if (d.children){
112         //     d._children = d.children;
113         //     d.children = null;
114         // }
115         // else{
116         //     d.children = d._children;
117         //     d._children = null;
118         // }
119         // this.update(d);
120     }
121     zoom = () => {
122         this.svg.attr("transform", "translate(" + d3.event.translate +
123             ")scale(" + d3.event.scale + ")");
124     }
125     update = (source) => {
126         // Compute the new tree layout.
127         var svg = this.svg;
128         var nodes = this.tree.nodes(source).reverse();
129         var links = this.tree.links(nodes);
130         var self = this;
131
132         // Normalize for fixed-depth.
133         nodes.forEach(function(d) { d.y = d.depth * self.props.maxLabel; });
134         // Update the nodes…
135         var node = svg.selectAll("g.node")
136             .data(nodes, function(d){
137                 return d.id || (d.id = ++self.nodeCount);
138             });
139         // Enter any new nodes at the parent's previous position.
140         var nodeEnter = node.enter()
141             .append("g")
142             .attr("class", "node")
143             .attr("transform", function(d){
144                 return "translate(" + source.y0 + "," + source.x0 + ")"; })
145             .on("click", this.click);
146
147         nodeEnter.append("circle")
148             .attr("r", 0)
149             .style("fill", function(d){
150                 return d._children ? "lightsteelblue" : "white";
151             });
152
153         nodeEnter.append("text")
154             .attr("x", function(d){
155                 var spacing = self.computeRadius(d) + 5;
156                     return d.children || d._children ? -spacing : spacing; })
157             .attr("transform", function(d, i) {
158                     return "translate(0," + ((i%2) ? 15 : -15) + ")"; })
159             .attr("dy", "3")
160             .attr("text-anchor", function(d){
161                     return d.children || d._children ? "end" : "start";
162                 })
163             .text(function(d){ return d.name; })
164             .style("fill-opacity", 0);
165
166         // Transition nodes to their new position.
167         var nodeUpdate = node
168             .transition()
169             .duration(this.props.duration)
170             .attr("transform", function(d) {
171                 return "translate(" + d.y + "," + d.x + ")"; });
172
173         nodeUpdate.select("circle")
174             .attr("r", function(d){ return self.computeRadius(d); })
175             .style("fill", function(d) {
176                 return d.id == self.selectedID ? "green" : "lightsteelblue"; });
177
178         nodeUpdate.select("text")
179             .style("fill-opacity", 1)
180             .style("font-weight", function(d) {
181                 return d.id == self.selectedID ? "900" : "inherit";
182             })
183             .attr("transform", function(d, i) {
184                 return "translate(0," + ((i%2) ? 15 : -15) + ")" +
185                     (d.id == self.selectedID ? "scale(1.125)" : "scale(1)");
186             });
187
188         // Transition exiting nodes to the parent's new position.
189         var nodeExit = node.exit()
190             .transition()
191             .duration(this.props.duration)
192             .attr("transform", function(d) {
193                 return "translate(" + source.y + "," + source.x + ")"; })
194             .remove();
195
196         nodeExit.select("circle").attr("r", 0);
197         nodeExit.select("text").style("fill-opacity", 0);
198
199         // Update the links…
200         var link = svg.selectAll("path.link")
201             .data(links, function(d){ return d.target.id; });
202
203         // Enter any new links at the parent's previous position.
204         link.enter().insert("path", "g")
205             .attr("class", "link")
206             .attr("d", function(d){
207                 var o = {x: source.x0, y: source.y0};
208                 return self.diagonal({source: o, target: o});
209             });
210
211         // Transition links to their new position.
212         link
213             .transition()
214             .duration(this.props.duration)
215             .attr("d", self.diagonal);
216
217         // Transition exiting nodes to the parent's new position.
218         link.exit()
219             .transition()
220             .duration(this.props.duration)
221             .attr("d", function(d){
222                 var o = {x: source.x, y: source.y};
223                 return self.diagonal({source: o, target: o});
224             })
225             .remove();
226
227         // Stash the old positions for transition.
228         nodes.forEach(function(d){
229             d.x0 = d.x;
230             d.y0 = d.y;
231         });
232     }
233     render() {
234         let html = (
235             <DashboardCard  className="topologyTreeGraph" showHeader={true} title="Topology Tree"
236                 headerExtras={this.props.headerExtras} >
237                 <div id="topology"></div>
238             </DashboardCard>
239         );
240         return html;
241     }
242 }
243
244 TopologyTree.defaultProps = {
245     treeWidth: 800,
246     treeHeight: 500,
247     width: 800,
248     height: 800,
249     minScale: 0.5,
250     maxScale: 2,
251     maxLabel: 150,
252     duration: 500,
253     radius: 5,
254     useDynamicWrapperSize: false,
255     data: {}
256 }