Rift.IO OSM R1 Initial Submission
[osm/UI.git] / skyquake / framework / widgets / topology / topologyL2Graph.jsx
diff --git a/skyquake/framework/widgets/topology/topologyL2Graph.jsx b/skyquake/framework/widgets/topology/topologyL2Graph.jsx
new file mode 100644 (file)
index 0000000..fe4360b
--- /dev/null
@@ -0,0 +1,253 @@
+
+/*
+ * 
+ *   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 d3 from 'd3';
+import DashboardCard from '../dashboard_card/dashboard_card.jsx';
+
+export default class TopologyL2Graph extends React.Component {
+    constructor(props) {
+        super(props);
+        this.data = props.data;
+        this.selectedID = 0;
+        this.nodeCount = 0;
+        this.network_coding = {}
+        this.nodeEvent = props.nodeEvent || null;
+    }
+    componentDidMount(){
+        var weight = 400;
+        var handler = this;
+        this.force = d3.layout.force()
+            .size([this.props.width, this.props.height])
+            .charge(-weight)
+            .linkDistance(weight)
+            .on("tick", this.tick.bind(this));
+
+        this.drag = this.force.drag()
+            .on("dragstart", function(d) {
+                handler.dragstart(d, handler);
+            });
+
+    }
+    componentWillUnmount() {
+        d3.select('svg').remove();
+    }
+    componentWillReceiveProps(props) {
+        if(!this.svg) {
+            // NOTE: We may need to revisit how D3 accesses DOM elements
+             this.svg = d3.select(document.querySelector('#topologyL2')).append("svg")
+            .attr("width", this.props.width)
+            .attr("height", this.props.height)
+            .classed("topology", true);
+        }
+
+        if (props.data.links.length > 0) {
+            this.network_coding = this.create_group_coding(props.data.network_ids.sort());
+            this.update(props.data);
+        }
+    }
+
+    create_group_coding(group_ids) {
+        var group_coding = {};
+        group_ids.forEach(function(element, index, array) {
+            group_coding[element] = index+1;
+        });
+        return group_coding;
+    }
+    getNetworkCoding(network_id) {
+        var group = this.network_coding[network_id];
+        if (group != undefined) {
+            return group;
+        } else {
+            return 0;
+        }
+    }
+
+    drawLegend(graph) {
+        // Hack to prevent multiple legends being displayed
+        this.svg.selectAll(".legend").remove();
+
+        var showBox = false;
+        var svg = this.svg;
+        var item_count = (graph.network_ids) ? graph.network_ids.length : 0;
+        var pos = {
+            anchorX: 5,
+            anchorY: 5,
+            height: 40,
+            width: 200,
+            items_y: 35,
+            items_x: 7,
+            item_height: 25
+        };
+        pos.height += item_count * pos.item_height;
+        var legend_translate = "translate("+pos.anchorX+","+pos.anchorY+")";
+
+        var legend = svg.append("g")
+            .attr("class", "legend")
+            .attr("transform", legend_translate);
+
+        var legend_box = (showBox) ? legend.append("rect")
+                .attr("x", 0)
+                .attr("y", 0)
+                .attr("height", pos.height)
+                .attr("width", pos.width)
+                .style("stroke", "black")
+                .style("fill", "none") : null;
+
+        legend.append("text")
+            .attr("x", 5)
+            .attr("y", 15)
+            .text("Network color mapping:");
+
+        legend.selectAll("g").data(graph.network_ids)
+            .enter()
+            .append("g")
+            .each(function(d, i) {
+                var colors = ["green", "orange", "red" ];
+                var g = d3.select(this);
+                var group_number = i+1;
+                g.attr('class', "node-group-" + group_number);
+
+                g.append("circle")
+                    .attr("cx", pos.items_x + 3)
+                    .attr("cy", pos.items_y + i * pos.item_height)
+                    .attr("r", 6);
+
+                g.append("text")
+                    .attr("x", pos.items_x + 25)
+                    .attr("y", pos.items_y + (i * pos.item_height + 4))
+                    .attr("height", 20)
+                    .attr("width", 80)
+                    .text(d);
+            })
+    }
+
+    update(graph) {
+        var svg = this.svg;
+        var handler = this;
+        this.force
+            .nodes(graph.nodes)
+            .links(graph.links)
+            .start();
+
+        this.link = svg.selectAll(".link")
+            .data(graph.links)
+            .enter().append("line")
+                .attr("class", "link");
+
+        this.gnodes = svg.selectAll('g.gnode')
+            .data(graph.nodes)
+            .enter()
+            .append('g')
+            .classed('gnode', true)
+            .attr('data-network', function(d) { return d.network; })
+            .attr('class', function(d) {
+                return d3.select(this).attr('class') + ' node-group-'+ handler.getNetworkCoding(d.network);
+            });
+
+        this.node = this.gnodes.append("circle")
+            .attr("class", "node")
+            .attr("r", this.props.radius)
+            .on("dblclick", function(d) {
+                handler.dblclick(d, handler)
+            })
+            .call(this.drag)
+            .on('click', function(d) {
+                handler.click.call(this, d, handler)
+            });
+        var labels = this.gnodes.append("text")
+            .attr("text-anchor", "middle")
+            .attr("fill", "black")
+            .attr("font-size", "12")
+            .attr("y", "-10")
+            .text(function(d) { return d.name; });
+        this.drawLegend(graph);
+    }
+
+    tick = () => {
+        this.link.attr("x1", function(d) { return d.source.x; })
+               .attr("y1", function(d) { return d.source.y; })
+               .attr("x2", function(d) { return d.target.x; })
+               .attr("y2", function(d) { return d.target.y; });
+
+        this.gnodes.attr("transform", function(d) {
+            return 'translate(' + [d.x, d.y] + ')';
+        });
+
+    }
+
+    click(d, topo) {
+        console.log("TopologyL2Graph.click called");
+        // 'This' is the svg circle element
+        var gnode = d3.select(this.parentNode);
+
+        topo.svg.selectAll("text").transition()
+            .duration(topo.props.nodeText.transitionTime)
+            .attr("font-size", topo.props.nodeText.size)
+            .attr("fill", topo.props.nodeText.color)
+
+        // Set focus node text properties
+        d3.select(this.parentNode).selectAll('text').transition()
+            .duration(topo.props.nodeText.transitionTime)
+            .attr("font-size", topo.props.nodeText.focus.size)
+            .attr("fill", topo.props.nodeText.focus.color);
+
+        // Perform detail view
+        topo.selectedID = d.id;
+        if (topo.nodeEvent) {
+            topo.nodeEvent(d.id);
+        }
+        // set record view as listener
+    }
+
+    dblclick(d, topo) {
+        this.d3.select(this).classed("fixed", d.fixed = false);
+    }
+
+    dragstart(d) {
+        //d3.select(this).classed("fixed", d.fixed = true);
+    }
+
+    render() {
+        return ( <DashboardCard showHeader={true} title="Topology L2 Graph">
+                <div id="topologyL2"></div>
+                </DashboardCard>)
+    }
+}
+
+TopologyL2Graph.defaultProps = {
+    width: 700,
+    height: 500,
+    maxLabel: 150,
+    duration: 500,
+    radius: 6,
+    data: {
+        nodes: [],
+        links: [],
+        network_ids: []
+    },
+    nodeText: {
+        size: 12,
+        color: 'black',
+        focus: {
+            size: 14,
+            color: 'blue'
+        },
+        transitionTime: 250
+    }
+}