Rift.IO OSM R1 Initial Submission
[osm/UI.git] / skyquake / framework / widgets / topology / topologyTree.jsx
diff --git a/skyquake/framework/widgets/topology/topologyTree.jsx b/skyquake/framework/widgets/topology/topologyTree.jsx
new file mode 100644 (file)
index 0000000..5e0d895
--- /dev/null
@@ -0,0 +1,256 @@
+
+/*
+ * 
+ *   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: {}
+}