Rift.IO OSM R1 Initial Submission
[osm/UI.git] / skyquake / framework / widgets / topology / topologyL2Graph.jsx
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 }