blob: 5e0d89504b360965b6df9b5954ed0235359955ff [file] [log] [blame]
/*
*
* Copyright 2016 RIFT.IO Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import React from 'react';
import ReactDOM from 'react-dom';
import d3 from 'd3';
import DashboardCard from '../dashboard_card/dashboard_card.jsx';
import _ from 'lodash';
import $ from 'jquery';
import './topologyTree.scss';
export default class TopologyTree extends React.Component {
constructor(props) {
super(props);
this.data = props.data;
this.selectedID = 0;
this.nodeCount = 0;
this.size = this.wrapperSize();
this.tree = d3.layout.tree()
.size([this.props.treeHeight, this.props.treeWidth]);
this.diagonal = d3.svg.diagonal()
.projection(function(d) { return [d.y, d.x]; });
this.svg = null;
}
componentWillReceiveProps(props) {
let self = this;
if(!this.svg) {
let zoom = d3.behavior.zoom()
.translate([this.props.maxLabel, 0])
.scaleExtent([this.props.minScale, this.props.maxScale])
.on("zoom", self.zoom);
let svg = this.selectParent().append("svg")
.attr("width", this.size.width)
.attr("height", this.size.height)
.append("g")
.call(zoom)
.append("g")
.attr("transform", "translate(" + this.props.maxLabel + ",0)");
svg.append("rect")
.attr("class", "overlay")
.attr("width", this.size.width)
.attr("height", this.size.height);
// this.svg = d3.select()
this.svg = svg;
this.props.selectNode(props.data);
}
if(props.data.hasOwnProperty('type') && !this.props.hasSelected) {
this.selectedID = props.data.id;
//Commenting out to prevent transmitter push error
//this.props.selectNode(props.data);
}
if(this.svg) {
this.update(_.cloneDeep(props.data));
// this.selectedID = props.data.id;
}
}
wrapperSize() {
if (this.props.useDynamicWrapperSize) {
try {
let wrapper = $(".topologyTreeGraph-body");
return {
width: wrapper.width(),
height: wrapper.height()
}
} catch (e) {
console.log("ERROR: cannot get width and/or height from element."+
" Using props for width and height. e=", e);
return {
width: this.props.width,
height: this.props.height
}
}
} else {
return {
width: this.props.width,
height: this.props.height
}
}
}
selectParent() {
return d3.select(document.querySelector('#topology'));
}
computeRadius(d) {
// if(d.parameters && d.parameters.vcpu) {
// return this.props.radius + d.parameters.vcpu.total;
// } else {
return this.props.radius;
// }
}
click = (d) => {
this.props.selectNode(d);
this.selectedID = d.id;
// if (d.children){
// d._children = d.children;
// d.children = null;
// }
// else{
// d.children = d._children;
// d._children = null;
// }
// this.update(d);
}
zoom = () => {
this.svg.attr("transform", "translate(" + d3.event.translate +
")scale(" + d3.event.scale + ")");
}
update = (source) => {
// Compute the new tree layout.
var svg = this.svg;
var nodes = this.tree.nodes(source).reverse();
var links = this.tree.links(nodes);
var self = this;
// Normalize for fixed-depth.
nodes.forEach(function(d) { d.y = d.depth * self.props.maxLabel; });
// Update the nodes…
var node = svg.selectAll("g.node")
.data(nodes, function(d){
return d.id || (d.id = ++self.nodeCount);
});
// Enter any new nodes at the parent's previous position.
var nodeEnter = node.enter()
.append("g")
.attr("class", "node")
.attr("transform", function(d){
return "translate(" + source.y0 + "," + source.x0 + ")"; })
.on("click", this.click);
nodeEnter.append("circle")
.attr("r", 0)
.style("fill", function(d){
return d._children ? "lightsteelblue" : "white";
});
nodeEnter.append("text")
.attr("x", function(d){
var spacing = self.computeRadius(d) + 5;
return d.children || d._children ? -spacing : spacing; })
.attr("transform", function(d, i) {
return "translate(0," + ((i%2) ? 15 : -15) + ")"; })
.attr("dy", "3")
.attr("text-anchor", function(d){
return d.children || d._children ? "end" : "start";
})
.text(function(d){ return d.name; })
.style("fill-opacity", 0);
// Transition nodes to their new position.
var nodeUpdate = node
.transition()
.duration(this.props.duration)
.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")"; });
nodeUpdate.select("circle")
.attr("r", function(d){ return self.computeRadius(d); })
.style("fill", function(d) {
return d.id == self.selectedID ? "green" : "lightsteelblue"; });
nodeUpdate.select("text")
.style("fill-opacity", 1)
.style("font-weight", function(d) {
return d.id == self.selectedID ? "900" : "inherit";
})
.attr("transform", function(d, i) {
return "translate(0," + ((i%2) ? 15 : -15) + ")" +
(d.id == self.selectedID ? "scale(1.125)" : "scale(1)");
});
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit()
.transition()
.duration(this.props.duration)
.attr("transform", function(d) {
return "translate(" + source.y + "," + source.x + ")"; })
.remove();
nodeExit.select("circle").attr("r", 0);
nodeExit.select("text").style("fill-opacity", 0);
// Update the links…
var link = svg.selectAll("path.link")
.data(links, function(d){ return d.target.id; });
// Enter any new links at the parent's previous position.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("d", function(d){
var o = {x: source.x0, y: source.y0};
return self.diagonal({source: o, target: o});
});
// Transition links to their new position.
link
.transition()
.duration(this.props.duration)
.attr("d", self.diagonal);
// Transition exiting nodes to the parent's new position.
link.exit()
.transition()
.duration(this.props.duration)
.attr("d", function(d){
var o = {x: source.x, y: source.y};
return self.diagonal({source: o, target: o});
})
.remove();
// Stash the old positions for transition.
nodes.forEach(function(d){
d.x0 = d.x;
d.y0 = d.y;
});
}
render() {
let html = (
<DashboardCard className="topologyTreeGraph" showHeader={true} title="Topology Tree"
headerExtras={this.props.headerExtras} >
<div id="topology"></div>
</DashboardCard>
);
return html;
}
}
TopologyTree.defaultProps = {
treeWidth: 800,
treeHeight: 500,
width: 800,
height: 800,
minScale: 0.5,
maxScale: 2,
maxLabel: 150,
duration: 500,
radius: 5,
useDynamicWrapperSize: false,
data: {}
}