blob: 5e0d89504b360965b6df9b5954ed0235359955ff [file] [log] [blame]
Jeremy Mordkoffe29efc32016-09-07 18:59:17 -04001
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 */
19import React from 'react';
20import ReactDOM from 'react-dom';
21import d3 from 'd3';
22import DashboardCard from '../dashboard_card/dashboard_card.jsx';
23import _ from 'lodash';
24import $ from 'jquery';
25import './topologyTree.scss';
26
27export 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
244TopologyTree.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}