| |
| /* |
| * |
| * 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 |
| } |
| } |