Merge "Unit test for ovsdb.southbound.ovsdb.transact"
[netvirt.git] / ovsdb-ui / module / src / main / resources / ovsdb / Graph.js
1 define(['app/ovsdb/lib/d3.min', 'app/ovsdb/OvsCore', 'app/ovsdb/matrix', 'underscore'], function(d3, OvsCore, Geom, _) {
2   'use strict';
3
4   var root = null,
5     forceLayout = null,
6     baseBB = null,
7     nodes = null,
8     links = null,
9     nodeData = [],
10     linkData = [],
11     drag = null,
12     nodePosCache = null;
13
14   function Graph(id, width, height) {
15     var x = d3.scale.linear()
16       .domain([0, width])
17       .range([0, width]);
18
19     var y = d3.scale.linear()
20       .domain([0, height])
21       .range([height, 0]);
22
23     var tmp = d3.select(id).append("svg")
24     .attr('width', width)
25     .attr('height', height)
26     .append("svg:g")
27     .attr('class', 'layer_0');
28
29     tmp.append('svg:rect')
30     .attr('width', width)
31     .attr('height', height)
32     .attr('fill', 'white');
33
34     root = tmp.call(d3.behavior.zoom().x(x).y(y).scaleExtent([1, 6]).on("zoom", zoom))
35       .append("g");
36
37     this.matrix = new Geom.Matrix();
38
39   //  this._bg = tmp.insert('svg:polygon', ':first-child');
40   //  this._bg.attr('id', 'ttt').attr('fill', 'rgb(250, 220, 220)').attr('stroke', 'rgb(250,0,0)');
41
42     drag = d3.behavior.drag()
43       .origin(function(d) {
44         return d;
45       })
46       .on("dragstart", dragstarted.bind(this))
47       .on("drag", dragmove.bind(this))
48       .on("dragend", dragend.bind(this));
49
50     // a layout for the bridge and his link
51     forceLayout = new d3.layout.force()
52       .gravity(0.05)
53       .charge(-400)
54       .linkDistance(80)
55       .size([width, height])
56       .on('tick', this.update.bind(this));
57
58     addDefs();
59   }
60
61   function zoom() {
62     root.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
63   }
64
65   function dragstarted(d) {
66     d3.event.sourceEvent.stopPropagation();
67     forceLayout.stop();
68   }
69
70   function dragmove(d) {
71     d.x += d3.event.dx;
72     d.y += d3.event.dy;
73     d.px += d3.event.dx;
74     d.py += d3.event.dy;
75     this.update();
76   }
77
78   function dragend(d) {
79     d.fixed = true;
80     this.update();
81     forceLayout.resume();
82     baseBB = null;
83   }
84
85   // Define reusable svg item like box-shadow
86   function addDefs() {
87     // box-shadow
88     var defs = d3.select('svg').insert('svg:defs', ':first-child');
89     var filter = defs.append('svg:filter').attr('id', 'selectNode').attr('x', '-20%').attr('y', '-20%').attr('width', '140%').attr('height', '140%');
90     filter.append('feGaussianBlur').attr('stdDeviation', '2').attr('result', 'coloredBlur');
91     var femerge = filter.append('feMerge');
92     femerge.append('feMergeNode').attr('in', 'coloredBlur');
93     femerge.append('feMergeNode').attr('in', 'SourceGraphic');
94   }
95
96   function sortLink(links) {
97     links.sort(function(a,b) {
98       if (a.source > b.source) {
99         return 1;
100       } else if (a.source < b.source) {
101         return -1;
102       }
103       else {
104         if (a.target > b.target) {
105           return 1;
106         }
107         if (a.target < b.target) {
108           return -1;
109         } else {
110           return 0;
111         }
112       }
113     });
114   }
115
116   function setLinkIndexAndNum(links) {
117     _.each(links, function(link, i) {
118       if (i != 0 && links[i].source == links[i-1].source &&
119         links[i].target == links[i-1].target) {
120           links[i].linkindex = links[i-1].linkindex + 1;
121         } else {
122           links[i].linkindex = 1;
123         }
124     });
125   }
126
127   // Properties to quick access and set nodes and links
128   Object.defineProperties(Graph.prototype, {
129     links: {
130       get: function() {
131         return links;
132       },
133       set: function(value) {
134         sortLink(value);
135         setLinkIndexAndNum(value);
136
137         forceLayout.links(value);
138         links = root.selectAll('links')
139           .data(value)
140           .enter().append('svg:path')
141           .attr('class', function(d) {
142             return d.linkType;
143           })
144           .attr('fill', 'none')
145           .attr('stroke-dasharray', function(d) {
146             return d.dashArray;
147           })
148           .attr('stroke-width', function(d) {
149             return d.width;
150           })
151           .attr('stroke', function(d) {
152             return d.color;
153           });
154       }
155     },
156     nodes: {
157       get: function() {
158         return nodes;
159       },
160       set: function(value) {
161         var _this = this; // context change in callback function and we need both
162
163         forceLayout.nodes(value);
164         nodes = root.selectAll('.nodes')
165           .data(value)
166           .enter().append('svg:g')
167           .call(drag)
168           .attr('class', function(d) {
169             return (d.node instanceof OvsCore.BridgeNode) ? 'bridge' : 'switch';
170           })
171           .on('click', function(d) {
172             if (d3.event.defaultPrevented) return;
173             _this.onNodeClick(d, nodes, links, this);
174           })
175           .on("mouseover", function(d) {
176             _this.onNodeOver(d, nodes, links, this);
177           })
178           .on("mouseout", function(d) {
179             _this.onNodeOut(d, nodes, links, this);
180           });
181
182         nodes.append('text')
183           .attr('x', 0)
184           .attr('y', 30)
185           .attr('fill', 'black')
186           .attr('text-anchor', 'middle')
187           .text(function(d) {
188             if (d.node instanceof OvsCore.BridgeNode)
189               return d.node.flowInfo.ip;
190             else
191               return d.node.otherLocalIp;
192           });
193
194         //svg node
195         var layer = d3.selectAll('g.switch').append('svg:g').attr('class', 'switch').attr('transform', 'translate(-16 -16)');
196         layer.append('svg:rect').style('fill-rule', 'evenodd').attr('ry', '3.6808').attr('height', '28.901')
197           .attr('width', '28.784').style('stroke', '#002b69').attr('y', '0').attr('x', '0').style('stroke-width', "3px").style('fill', '#2a7fff');
198         layer.append('svg:path').attr('d', 'm27.043 6.2-5.0754 3.3764-.01018-2.1082-5.9209-.022.01773-2.6018 5.9031-.037.08118-2.1164z').style('fill', "#002b69");
199         layer.append('svg:path').attr('d', "m26.866 19.4-5.0754 3.3764-.01018-2.1082-5.9209-.022.01773-2.6018 5.9031-.037.08118-2.1164z").style('fill', "#002b69");
200         layer.append('svg:path').attr('d', "m3.0872 11.6 5.0754 3.3764.01018-2.1082 5.9209-.022-.01773-2.6018-5.9031-.037-.08118-2.1164z").style('fill', "#002b69");
201         layer.append('svg:path').attr('d', "m3.2639 24.8 5.0754 3.3764.01018-2.1082 5.9209-.022-.01773-2.6018-5.9031-.037-.08118-2.1164z").style('fill', "#002b69");
202
203         //svg bridge
204         var layer = d3.selectAll('g.bridge').append('svg:g').attr('transform', 'translate(-16 -16)');
205         layer.append('svg:path').style('fill', '#d40000').style('stroke', '#ff0000').style('stroke-width', '0.10413094')
206           .style('stroke-linecap', 'round') //stroke-linejoin:round
207           .attr('d', 'm 2.9656662,3.4868 c 4.8978761,7.5117 16.2156478,6.1742 21.9929178,2.0807 l 2.154019,-1.5814 -0.08265,18.1941 -2.055088,2.0313 -24.17161713,0.055 -0.0255688,-18.6893 z');
208         layer.append('svg:path').style('fill', '#ff5555').style('stroke', '#ff0000').style('stroke-width', '0.10413094')
209           .style('stroke-linecap', 'round') //stroke-linejoin:round
210           .attr('d', 'm 0.83642587,5.5637 c 4.89787603,7.5117 18.41115613,4.1109 24.18842613,0.018 l -0.06627,18.6546 -24.12215693,0 z');
211       }
212     }
213   });
214
215   // Update the node and link position on the canvas
216   Graph.prototype.update = function(e) {
217     var k = 1 , //.1 * e.alpha
218       isDragging = (e !== null)
219 /*
220     if (!isDragging) {
221       k = .1 * e.alpha;
222     }
223
224     if (!isDragging) {
225       forceLayout.nodes().forEach(function(o) {
226         var i = (o.node instanceof OvsCore.OvsNode) ? 1 : 0;
227         o.y += (layerAnchor[i].y - o.y) * k;
228         o.x += (layerAnchor[i].x - o.x) * k;
229       });
230     }*/
231
232     if (links) {
233       links.attr('d', (function(d) {
234         var srcT = this.matrix.transformPoint(d.source.x, d.source.y),
235           targetT = this.matrix.transformPoint(d.target.x, d.target.y),
236           src = {x:srcT.elements[0],y:srcT.elements[1]},
237           tgt = {x:targetT.elements[0],y:targetT.elements[1]},
238           anchor1 = {}, anchor2 = {};
239
240         if (d.linkType === 'tunnel') {
241           // curve the line and arc it on a direction of a 90 degree vector
242           var perp = { x : -tgt.y, y: tgt.x};
243           var srcNorm = Math.sqrt(src.x * src.x + src.y * src.y);
244           var perpNorm = Math.sqrt(perp.x * perp.x + perp.y * perp.y);
245           var dy = (perp.y/perpNorm - src.y/srcNorm);
246           var dx = (perp.x/perpNorm - src.x/srcNorm);
247
248           anchor1 = {x: src.x - dx * 30, y: src.y - dy * 30 };
249           anchor2 = {x: tgt.x - dx * 30, y: tgt.y - dy * 30 };
250         } else {
251           // default strait line
252           anchor1 = src;
253           anchor2 = tgt;
254         }
255
256         return OvsCore.Util.String.Format('M{0},{1} C{2},{3} {4},{5} {6},{7}',
257           srcT.elements[0], srcT.elements[1],
258           anchor1.x, anchor1.y,
259           anchor2.x, anchor2.y,
260           targetT.elements[0], targetT.elements[1]
261         );
262       }).bind(this));
263     }
264
265     nodes.attr("transform", (function(d) {
266       var a = new Geom.Matrix.fromString(root.attr('transform'));
267       var tmp = Geom.Matrix.combine(a, this.matrix);
268
269       var transV = this.matrix.transformPoint(d.x, d.y);
270       var v = tmp.transformPoint(d.x, d.y);
271       d.pos = {
272         x: v.elements[0],
273         y: v.elements[1]
274       };
275       nodePosCache[d.node.nodeId] =  { pos: d.pos, fixed: d.fixed};
276       return "translate(" + transV.elements[0] + ',' + transV.elements[1] + ')';
277     }).bind(this));
278   };
279
280   Graph.prototype.setPosCache = function(cache) {
281     nodePosCache = cache;
282   };
283
284   // Apply few matrix transform to fake a perspective effet
285   Graph.prototype.applyPerspective = function(value) {
286     if (!baseBB)
287       baseBB = root.node().getBBox();
288
289     var padding = 50;
290     var persMatrix = new Geom.Matrix()
291       .translate(-(baseBB.x + baseBB.width / 2), -(baseBB.y + baseBB.height / 2))
292       .skew(-25, 0)
293       .scale(1, 0.6)
294       .translate(baseBB.x + baseBB.width / 2, baseBB.y + baseBB.height / 2);
295
296     this.matrix.transform = value ? this.matrix.transform.x(persMatrix.transform) : this.matrix.transform.x(persMatrix.transform.inverse());
297
298     var p1 = this.matrix.transformPoint(baseBB.x - padding, baseBB.y - padding);
299     var p2 = this.matrix.transformPoint(baseBB.x - padding, baseBB.y + baseBB.height + padding);
300     var p3 = this.matrix.transformPoint(baseBB.x + baseBB.width + padding, baseBB.y + baseBB.height + padding);
301     var p4 = this.matrix.transformPoint(baseBB.x + baseBB.width + padding, baseBB.y - padding);
302
303     this._bg.attr('points', OvsCore.Util.String.Format("{0},{1} {2},{3} {4},{5} {6},{7}",
304       p1.elements[0],
305       p1.elements[1],
306       //--
307       p2.elements[0],
308       p2.elements[1],
309       //--
310       p3.elements[0],
311       p3.elements[1],
312       //--
313       p4.elements[0],
314       p4.elements[1])).style('display', (value) ? 'block' : 'none');
315
316     this.update();
317   };
318
319   function positionateBaseOnCache() {
320     forceLayout.stop();
321     forceLayout.nodes().forEach(function(d) {
322       var nodeCached = nodePosCache[d.node.nodeId];
323       d.px = d.x = nodeCached.pos.x;
324       d.py = d.y = nodeCached.pos.y;
325       d.fixed = nodeCached.fixed;
326     });
327     forceLayout.resume();
328   }
329
330   // start the force layout
331   Graph.prototype.start = function() {
332     forceLayout.linkStrength(function(link) {
333       if (link.linkType === 'bridgeOvsLink') {
334         return 0;
335       }
336       return 1;
337     });
338     forceLayout.start();
339     if (_.size(nodePosCache) > 0) {
340       positionateBaseOnCache();
341     }
342   };
343
344   Graph.prototype.freeDOM = function() {
345     nodes.remove();
346     links.remove();
347     forceLayout.nodes([]);
348     forceLayout.links([]);
349   };
350
351   // Enable to manipulate attributes of the graph group node
352   Graph.prototype.attr = function(name, value) {
353     return root.attr(name, value);
354   };
355
356   // Enable the monipulate the css of the graph group node
357   Graph.prototype.style = function(name, value) {
358     return root.style(name, value);
359   };
360
361   Graph.prototype.selectAll = function(a) {
362     return root.selectAll(a);
363   };
364
365   // callback function
366   Graph.prototype.onNodeOver = _.noop;
367   Graph.prototype.onNodeOut = _.noop;
368   Graph.prototype.onNodeClick = _.noop;
369
370   return Graph;
371 });