/* * * 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 = (
); 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: {} }