| Jeremy Mordkoff | e29efc3 | 2016-09-07 18:59:17 -0400 | [diff] [blame] | 1 | |
| 2 | /* |
| 3 | * |
| 4 | * Copyright 2016 RIFT.IO Inc |
| 5 | * |
| 6 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 7 | * you may not use this file except in compliance with the License. |
| 8 | * You may obtain a copy of the License at |
| 9 | * |
| 10 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 11 | * |
| 12 | * Unless required by applicable law or agreed to in writing, software |
| 13 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 15 | * See the License for the specific language governing permissions and |
| 16 | * limitations under the License. |
| 17 | * |
| 18 | */ |
| 19 | import React from 'react'; |
| 20 | import d3 from 'd3'; |
| 21 | import DashboardCard from '../dashboard_card/dashboard_card.jsx'; |
| 22 | |
| 23 | export default class TopologyL2Graph extends React.Component { |
| 24 | constructor(props) { |
| 25 | super(props); |
| 26 | this.data = props.data; |
| 27 | this.selectedID = 0; |
| 28 | this.nodeCount = 0; |
| 29 | this.network_coding = {} |
| 30 | this.nodeEvent = props.nodeEvent || null; |
| 31 | } |
| 32 | componentDidMount(){ |
| 33 | var weight = 400; |
| 34 | var handler = this; |
| 35 | this.force = d3.layout.force() |
| 36 | .size([this.props.width, this.props.height]) |
| 37 | .charge(-weight) |
| 38 | .linkDistance(weight) |
| 39 | .on("tick", this.tick.bind(this)); |
| 40 | |
| 41 | this.drag = this.force.drag() |
| 42 | .on("dragstart", function(d) { |
| 43 | handler.dragstart(d, handler); |
| 44 | }); |
| 45 | |
| 46 | } |
| 47 | componentWillUnmount() { |
| 48 | d3.select('svg').remove(); |
| 49 | } |
| 50 | componentWillReceiveProps(props) { |
| 51 | if(!this.svg) { |
| 52 | // NOTE: We may need to revisit how D3 accesses DOM elements |
| 53 | this.svg = d3.select(document.querySelector('#topologyL2')).append("svg") |
| 54 | .attr("width", this.props.width) |
| 55 | .attr("height", this.props.height) |
| 56 | .classed("topology", true); |
| 57 | } |
| 58 | |
| 59 | if (props.data.links.length > 0) { |
| 60 | this.network_coding = this.create_group_coding(props.data.network_ids.sort()); |
| 61 | this.update(props.data); |
| 62 | } |
| 63 | } |
| 64 | |
| 65 | create_group_coding(group_ids) { |
| 66 | var group_coding = {}; |
| 67 | group_ids.forEach(function(element, index, array) { |
| 68 | group_coding[element] = index+1; |
| 69 | }); |
| 70 | return group_coding; |
| 71 | } |
| 72 | getNetworkCoding(network_id) { |
| 73 | var group = this.network_coding[network_id]; |
| 74 | if (group != undefined) { |
| 75 | return group; |
| 76 | } else { |
| 77 | return 0; |
| 78 | } |
| 79 | } |
| 80 | |
| 81 | drawLegend(graph) { |
| 82 | // Hack to prevent multiple legends being displayed |
| 83 | this.svg.selectAll(".legend").remove(); |
| 84 | |
| 85 | var showBox = false; |
| 86 | var svg = this.svg; |
| 87 | var item_count = (graph.network_ids) ? graph.network_ids.length : 0; |
| 88 | var pos = { |
| 89 | anchorX: 5, |
| 90 | anchorY: 5, |
| 91 | height: 40, |
| 92 | width: 200, |
| 93 | items_y: 35, |
| 94 | items_x: 7, |
| 95 | item_height: 25 |
| 96 | }; |
| 97 | pos.height += item_count * pos.item_height; |
| 98 | var legend_translate = "translate("+pos.anchorX+","+pos.anchorY+")"; |
| 99 | |
| 100 | var legend = svg.append("g") |
| 101 | .attr("class", "legend") |
| 102 | .attr("transform", legend_translate); |
| 103 | |
| 104 | var legend_box = (showBox) ? legend.append("rect") |
| 105 | .attr("x", 0) |
| 106 | .attr("y", 0) |
| 107 | .attr("height", pos.height) |
| 108 | .attr("width", pos.width) |
| 109 | .style("stroke", "black") |
| 110 | .style("fill", "none") : null; |
| 111 | |
| 112 | legend.append("text") |
| 113 | .attr("x", 5) |
| 114 | .attr("y", 15) |
| 115 | .text("Network color mapping:"); |
| 116 | |
| 117 | legend.selectAll("g").data(graph.network_ids) |
| 118 | .enter() |
| 119 | .append("g") |
| 120 | .each(function(d, i) { |
| 121 | var colors = ["green", "orange", "red" ]; |
| 122 | var g = d3.select(this); |
| 123 | var group_number = i+1; |
| 124 | g.attr('class', "node-group-" + group_number); |
| 125 | |
| 126 | g.append("circle") |
| 127 | .attr("cx", pos.items_x + 3) |
| 128 | .attr("cy", pos.items_y + i * pos.item_height) |
| 129 | .attr("r", 6); |
| 130 | |
| 131 | g.append("text") |
| 132 | .attr("x", pos.items_x + 25) |
| 133 | .attr("y", pos.items_y + (i * pos.item_height + 4)) |
| 134 | .attr("height", 20) |
| 135 | .attr("width", 80) |
| 136 | .text(d); |
| 137 | }) |
| 138 | } |
| 139 | |
| 140 | update(graph) { |
| 141 | var svg = this.svg; |
| 142 | var handler = this; |
| 143 | this.force |
| 144 | .nodes(graph.nodes) |
| 145 | .links(graph.links) |
| 146 | .start(); |
| 147 | |
| 148 | this.link = svg.selectAll(".link") |
| 149 | .data(graph.links) |
| 150 | .enter().append("line") |
| 151 | .attr("class", "link"); |
| 152 | |
| 153 | this.gnodes = svg.selectAll('g.gnode') |
| 154 | .data(graph.nodes) |
| 155 | .enter() |
| 156 | .append('g') |
| 157 | .classed('gnode', true) |
| 158 | .attr('data-network', function(d) { return d.network; }) |
| 159 | .attr('class', function(d) { |
| 160 | return d3.select(this).attr('class') + ' node-group-'+ handler.getNetworkCoding(d.network); |
| 161 | }); |
| 162 | |
| 163 | this.node = this.gnodes.append("circle") |
| 164 | .attr("class", "node") |
| 165 | .attr("r", this.props.radius) |
| 166 | .on("dblclick", function(d) { |
| 167 | handler.dblclick(d, handler) |
| 168 | }) |
| 169 | .call(this.drag) |
| 170 | .on('click', function(d) { |
| 171 | handler.click.call(this, d, handler) |
| 172 | }); |
| 173 | var labels = this.gnodes.append("text") |
| 174 | .attr("text-anchor", "middle") |
| 175 | .attr("fill", "black") |
| 176 | .attr("font-size", "12") |
| 177 | .attr("y", "-10") |
| 178 | .text(function(d) { return d.name; }); |
| 179 | this.drawLegend(graph); |
| 180 | } |
| 181 | |
| 182 | tick = () => { |
| 183 | this.link.attr("x1", function(d) { return d.source.x; }) |
| 184 | .attr("y1", function(d) { return d.source.y; }) |
| 185 | .attr("x2", function(d) { return d.target.x; }) |
| 186 | .attr("y2", function(d) { return d.target.y; }); |
| 187 | |
| 188 | this.gnodes.attr("transform", function(d) { |
| 189 | return 'translate(' + [d.x, d.y] + ')'; |
| 190 | }); |
| 191 | |
| 192 | } |
| 193 | |
| 194 | click(d, topo) { |
| 195 | console.log("TopologyL2Graph.click called"); |
| 196 | // 'This' is the svg circle element |
| 197 | var gnode = d3.select(this.parentNode); |
| 198 | |
| 199 | topo.svg.selectAll("text").transition() |
| 200 | .duration(topo.props.nodeText.transitionTime) |
| 201 | .attr("font-size", topo.props.nodeText.size) |
| 202 | .attr("fill", topo.props.nodeText.color) |
| 203 | |
| 204 | // Set focus node text properties |
| 205 | d3.select(this.parentNode).selectAll('text').transition() |
| 206 | .duration(topo.props.nodeText.transitionTime) |
| 207 | .attr("font-size", topo.props.nodeText.focus.size) |
| 208 | .attr("fill", topo.props.nodeText.focus.color); |
| 209 | |
| 210 | // Perform detail view |
| 211 | topo.selectedID = d.id; |
| 212 | if (topo.nodeEvent) { |
| 213 | topo.nodeEvent(d.id); |
| 214 | } |
| 215 | // set record view as listener |
| 216 | } |
| 217 | |
| 218 | dblclick(d, topo) { |
| 219 | this.d3.select(this).classed("fixed", d.fixed = false); |
| 220 | } |
| 221 | |
| 222 | dragstart(d) { |
| 223 | //d3.select(this).classed("fixed", d.fixed = true); |
| 224 | } |
| 225 | |
| 226 | render() { |
| 227 | return ( <DashboardCard showHeader={true} title="Topology L2 Graph"> |
| 228 | <div id="topologyL2"></div> |
| 229 | </DashboardCard>) |
| 230 | } |
| 231 | } |
| 232 | |
| 233 | TopologyL2Graph.defaultProps = { |
| 234 | width: 700, |
| 235 | height: 500, |
| 236 | maxLabel: 150, |
| 237 | duration: 500, |
| 238 | radius: 6, |
| 239 | data: { |
| 240 | nodes: [], |
| 241 | links: [], |
| 242 | network_ids: [] |
| 243 | }, |
| 244 | nodeText: { |
| 245 | size: 12, |
| 246 | color: 'black', |
| 247 | focus: { |
| 248 | size: 14, |
| 249 | color: 'blue' |
| 250 | }, |
| 251 | transitionTime: 250 |
| 252 | } |
| 253 | } |