blob: 834f024ec9f4080ace76d809fc0b31951efd07eb [file] [log] [blame]
Simon Huntfb8ea1f2015-02-24 21:38:09 -08001/*
2 * Copyright 2015 Open Networking Laboratory
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17/*
18 ONOS GUI -- Topology Link Module.
19 Functions for highlighting/selecting links
20 */
21
22(function () {
23 'use strict';
24
25 // injected refs
Simon Hunt0c6b2d32015-03-26 17:46:29 -070026 var $log, fs, sus, ts, flash, tss, tps;
Simon Huntfb8ea1f2015-02-24 21:38:09 -080027
Simon Hunt0c6b2d32015-03-26 17:46:29 -070028 // internal state
Simon Huntfb8ea1f2015-02-24 21:38:09 -080029 var api,
Simon Hunt1a5301e2015-02-25 15:31:25 -080030 td3,
Simon Huntfb8ea1f2015-02-24 21:38:09 -080031 network,
Simon Hunt0c6b2d32015-03-26 17:46:29 -070032 showPorts = true, // enable port highlighting by default
33 enhancedLink = null, // the link over which the mouse is hovering
34 selectedLink = null; // the link which is currently selected
Simon Huntfb8ea1f2015-02-24 21:38:09 -080035
36 // SVG elements;
Simon Hunt9e2104c2015-02-26 10:48:59 -080037 var svg;
38
Simon Huntfb8ea1f2015-02-24 21:38:09 -080039
40 // ======== ALGORITHM TO FIND LINK CLOSEST TO MOUSE ========
41
Simon Hunt0c6b2d32015-03-26 17:46:29 -070042 function getLogicalMousePosition(container) {
43 var m = d3.mouse(container),
Simon Hunt9e2104c2015-02-26 10:48:59 -080044 sc = api.zoomer.scale(),
45 tr = api.zoomer.translate(),
46 mx = (m[0] - tr[0]) / sc,
47 my = (m[1] - tr[1]) / sc;
Simon Hunt0c6b2d32015-03-26 17:46:29 -070048 return {x: mx, y: my};
Simon Huntfb8ea1f2015-02-24 21:38:09 -080049 }
50
Simon Hunt5aac2fc2015-06-09 12:34:07 -070051
52 function sq(x) { return x * x; }
53
54 function mdist(p, m) {
55 return Math.sqrt(sq(p.x - m.x) + sq(p.y - m.y));
56 }
57
58 function prox(dist) {
59 return dist / api.zoomer.scale();
60 }
61
62 function computeNearestNode(mouse) {
63 var proximity = prox(30),
Simon Hunt0c6b2d32015-03-26 17:46:29 -070064 nearest = null,
65 minDist;
Simon Huntfb8ea1f2015-02-24 21:38:09 -080066
Simon Hunt5aac2fc2015-06-09 12:34:07 -070067 if (network.nodes.length) {
68 minDist = proximity * 2;
Simon Huntfb8ea1f2015-02-24 21:38:09 -080069
Simon Hunt5aac2fc2015-06-09 12:34:07 -070070 network.nodes.forEach(function (d) {
71 var dist;
72
73 if (!api.showHosts() && d.class === 'host') {
74 return; // skip hidden hosts
75 }
76
77 dist = mdist({x: d.x, y: d.y}, mouse);
78 if (dist < minDist && dist < proximity) {
79 minDist = dist;
80 nearest = d;
81 }
82 });
Simon Hunt9e2104c2015-02-26 10:48:59 -080083 }
Simon Hunt5aac2fc2015-06-09 12:34:07 -070084 return nearest;
85 }
86
87
88 function computeNearestLink(mouse) {
89 var proximity = prox(30),
90 nearest = null,
91 minDist;
Simon Hunt9e2104c2015-02-26 10:48:59 -080092
Simon Huntfb8ea1f2015-02-24 21:38:09 -080093 function pdrop(line, mouse) {
94 var x1 = line.x1,
95 y1 = line.y1,
96 x2 = line.x2,
97 y2 = line.y2,
98 x3 = mouse.x,
99 y3 = mouse.y,
100 k = ((y2-y1) * (x3-x1) - (x2-x1) * (y3-y1)) /
101 (sq(y2-y1) + sq(x2-x1)),
102 x4 = x3 - k * (y2-y1),
103 y4 = y3 + k * (x2-x1);
104 return {x:x4, y:y4};
105 }
106
Bri Prebilic Coleaeeb33e2015-07-09 15:15:54 -0700107 // FIXME: x and y position calculated here, use link.position
Simon Huntfb8ea1f2015-02-24 21:38:09 -0800108 function lineSeg(d) {
109 return {
110 x1: d.source.x,
111 y1: d.source.y,
112 x2: d.target.x,
113 y2: d.target.y
114 };
115 }
116
117 function lineHit(line, p, m) {
118 if (p.x < line.x1 && p.x < line.x2) return false;
119 if (p.x > line.x1 && p.x > line.x2) return false;
120 if (p.y < line.y1 && p.y < line.y2) return false;
121 if (p.y > line.y1 && p.y > line.y2) return false;
Simon Hunt5f361082015-02-25 11:36:38 -0800122 // line intersects, but are we close enough?
Simon Huntfb8ea1f2015-02-24 21:38:09 -0800123 return mdist(p, m) <= proximity;
124 }
125
126 if (network.links.length) {
Simon Huntfb8ea1f2015-02-24 21:38:09 -0800127 minDist = proximity * 2;
128
129 network.links.forEach(function (d) {
130 if (!api.showHosts() && d.type() === 'hostLink') {
131 return; // skip hidden host links
132 }
133
134 var line = lineSeg(d),
135 point = pdrop(line, mouse),
136 hit = lineHit(line, point, mouse),
137 dist;
138
139 if (hit) {
140 dist = mdist(point, mouse);
141 if (dist < minDist) {
142 minDist = dist;
143 nearest = d;
144 }
Simon Huntfb8ea1f2015-02-24 21:38:09 -0800145 }
146 });
Simon Huntfb8ea1f2015-02-24 21:38:09 -0800147 }
Simon Hunt0c6b2d32015-03-26 17:46:29 -0700148 return nearest;
Simon Huntfb8ea1f2015-02-24 21:38:09 -0800149 }
150
Simon Hunt0c6b2d32015-03-26 17:46:29 -0700151 function enhanceLink(ldata) {
Simon Huntfb8ea1f2015-02-24 21:38:09 -0800152 // if the new link is same as old link, do nothing
153 if (enhancedLink && ldata && enhancedLink.key === ldata.key) return;
154
155 // first, unenhance the currently enhanced link
156 if (enhancedLink) {
157 unenhance(enhancedLink);
158 }
159 enhancedLink = ldata;
160 if (enhancedLink) {
161 enhance(enhancedLink);
162 }
163 }
164
165 function unenhance(d) {
Simon Hunt969b3c92015-02-25 18:11:31 -0800166 // guard against link element not set
167 if (d.el) {
168 d.el.classed('enhanced', false);
169 }
Simon Hunt1a5301e2015-02-25 15:31:25 -0800170 api.portLabelG().selectAll('.portLabel').remove();
Simon Huntfb8ea1f2015-02-24 21:38:09 -0800171 }
172
173 function enhance(d) {
Simon Hunt969b3c92015-02-25 18:11:31 -0800174 var data = [],
175 point;
176
177 // guard against link element not set
178 if (!d.el) return;
179
Simon Huntd5264122015-02-25 10:17:43 -0800180 d.el.classed('enhanced', true);
Simon Hunt1a5301e2015-02-25 15:31:25 -0800181
Simon Hunt8ae474b2015-02-25 15:39:14 -0800182 // Define port label data objects.
183 // NOTE: src port is absent in the case of host-links.
184
Simon Hunt969b3c92015-02-25 18:11:31 -0800185 point = locatePortLabel(d);
186 angular.extend(point, {
Simon Hunt8ae474b2015-02-25 15:39:14 -0800187 id: 'topo-port-tgt',
Simon Hunt969b3c92015-02-25 18:11:31 -0800188 num: d.tgtPort
189 });
190 data.push(point);
Simon Hunt8ae474b2015-02-25 15:39:14 -0800191
192 if (d.srcPort) {
Simon Hunt969b3c92015-02-25 18:11:31 -0800193 point = locatePortLabel(d, 1);
194 angular.extend(point, {
Simon Hunt1a5301e2015-02-25 15:31:25 -0800195 id: 'topo-port-src',
Simon Hunt969b3c92015-02-25 18:11:31 -0800196 num: d.srcPort
Simon Hunt8ae474b2015-02-25 15:39:14 -0800197 });
Simon Hunt969b3c92015-02-25 18:11:31 -0800198 data.push(point);
Simon Hunt8ae474b2015-02-25 15:39:14 -0800199 }
Simon Hunt1a5301e2015-02-25 15:31:25 -0800200
201 td3.applyPortLabels(data, api.portLabelG());
Simon Huntfb8ea1f2015-02-24 21:38:09 -0800202 }
203
Bri Prebilic Coleaeeb33e2015-07-09 15:15:54 -0700204 // FIXME: x and y position calculated here somewhere
Simon Hunt969b3c92015-02-25 18:11:31 -0800205 function locatePortLabel(link, src) {
206 var near = src ? 'source' : 'target',
207 far = src ? 'target' : 'source',
208 ln = link[near],
209 lf = link[far],
210 offset = 32;
211
212 function dist(x, y) { return Math.sqrt(x*x + y*y); }
213
214 var dx = lf.x - ln.x,
215 dy = lf.y - ln.y,
216 k = offset / dist(dx, dy);
217
218 return {x: k * dx + ln.x, y: k * dy + ln.y};
219 }
Simon Huntfb8ea1f2015-02-24 21:38:09 -0800220
Simon Hunt0c6b2d32015-03-26 17:46:29 -0700221
222 function selectLink(ldata) {
223 // if the new link is same as old link, do nothing
224 if (selectedLink && ldata && selectedLink.key === ldata.key) return;
225
226 // make sure no nodes are selected
227 tss.deselectAll();
228
229 // first, unenhance the currently enhanced link
230 if (selectedLink) {
231 unselLink(selectedLink);
232 }
233 selectedLink = ldata;
234 if (selectedLink) {
235 selLink(selectedLink);
236 }
237 }
238
239 function unselLink(d) {
240 // guard against link element not set
241 if (d.el) {
242 d.el.classed('selected', false);
243 }
244 }
245
246 function selLink(d) {
247 // guard against link element not set
248 if (!d.el) return;
249
250 d.el.classed('selected', true);
251
252 tps.displayLink(d);
253 tps.displaySomething();
254 }
255
256 // ====== MOUSE EVENT HANDLERS ======
257
258 function mouseMoveHandler() {
259 var mp = getLogicalMousePosition(this),
260 link = computeNearestLink(mp);
261 enhanceLink(link);
262 }
263
264 function mouseClickHandler() {
Simon Hunt5aac2fc2015-06-09 12:34:07 -0700265 var mp, link, node;
Simon Hunt0c6b2d32015-03-26 17:46:29 -0700266
267 if (!tss.clickConsumed()) {
268 mp = getLogicalMousePosition(this);
Simon Hunt5aac2fc2015-06-09 12:34:07 -0700269 node = computeNearestNode(mp);
270 if (node) {
271 $log.debug('found nearest node:', node.labels[1]);
272 tss.selectObject(node);
273 } else {
274 link = computeNearestLink(mp);
275 selectLink(link);
276 }
Simon Hunt0c6b2d32015-03-26 17:46:29 -0700277 }
278 }
279
280
281 // ======================
282
Simon Huntfcbde892015-04-16 12:05:28 -0700283 function togglePorts(x) {
284 var kev = (x === 'keyev'),
285 on = kev ? !showPorts : !!x,
286 what = on ? 'Enable' : 'Disable',
287 handler = on ? mouseMoveHandler : null;
Simon Hunt9e2104c2015-02-26 10:48:59 -0800288
Simon Huntfcbde892015-04-16 12:05:28 -0700289 showPorts = on;
Simon Hunt9e2104c2015-02-26 10:48:59 -0800290
Simon Huntfcbde892015-04-16 12:05:28 -0700291 if (!on) {
Simon Hunt0c6b2d32015-03-26 17:46:29 -0700292 enhanceLink(null);
Simon Hunt9e2104c2015-02-26 10:48:59 -0800293 }
294 svg.on('mousemove', handler);
295 flash.flash(what + ' port highlighting');
Simon Huntfcbde892015-04-16 12:05:28 -0700296 return on;
Simon Hunt9e2104c2015-02-26 10:48:59 -0800297 }
298
Simon Hunt0c6b2d32015-03-26 17:46:29 -0700299 function deselectLink() {
300 if (selectedLink) {
301 unselLink(selectedLink);
302 selectedLink = null;
303 return true;
304 }
305 return false;
306 }
307
Simon Huntfb8ea1f2015-02-24 21:38:09 -0800308 // ==========================
309 // Module definition
310
311 angular.module('ovTopo')
312 .factory('TopoLinkService',
Simon Hunt9e2104c2015-02-26 10:48:59 -0800313 ['$log', 'FnService', 'SvgUtilService', 'ThemeService', 'FlashService',
Simon Hunt0c6b2d32015-03-26 17:46:29 -0700314 'TopoSelectService', 'TopoPanelService',
Simon Huntfb8ea1f2015-02-24 21:38:09 -0800315
Simon Hunt0c6b2d32015-03-26 17:46:29 -0700316 function (_$log_, _fs_, _sus_, _ts_, _flash_, _tss_, _tps_) {
Simon Hunt9e2104c2015-02-26 10:48:59 -0800317 $log = _$log_;
318 fs = _fs_;
319 sus = _sus_;
320 ts = _ts_;
321 flash = _flash_;
Simon Hunt0c6b2d32015-03-26 17:46:29 -0700322 tss = _tss_;
323 tps = _tps_;
Simon Huntfb8ea1f2015-02-24 21:38:09 -0800324
Simon Hunt9e2104c2015-02-26 10:48:59 -0800325 function initLink(_api_, _td3_) {
326 api = _api_;
327 td3 = _td3_;
328 svg = api.svg;
329 network = api.network;
Bri Prebilic Coled8745462015-06-01 16:08:57 -0700330 if (showPorts && !fs.isMobile()) {
Simon Hunt9e2104c2015-02-26 10:48:59 -0800331 svg.on('mousemove', mouseMoveHandler);
Simon Huntfb8ea1f2015-02-24 21:38:09 -0800332 }
Simon Hunt0c6b2d32015-03-26 17:46:29 -0700333 svg.on('click', mouseClickHandler);
Simon Hunt9e2104c2015-02-26 10:48:59 -0800334 }
Simon Huntfb8ea1f2015-02-24 21:38:09 -0800335
Simon Hunt9e2104c2015-02-26 10:48:59 -0800336 function destroyLink() {
Simon Hunt0c6b2d32015-03-26 17:46:29 -0700337 // unconditionally remove any event handlers
Simon Hunt9e2104c2015-02-26 10:48:59 -0800338 svg.on('mousemove', null);
Simon Hunt0c6b2d32015-03-26 17:46:29 -0700339 svg.on('click', null);
Simon Hunt9e2104c2015-02-26 10:48:59 -0800340 }
Simon Huntfb8ea1f2015-02-24 21:38:09 -0800341
Simon Hunt9e2104c2015-02-26 10:48:59 -0800342 return {
343 initLink: initLink,
344 destroyLink: destroyLink,
Simon Hunt0c6b2d32015-03-26 17:46:29 -0700345 togglePorts: togglePorts,
346 deselectLink: deselectLink
Simon Hunt9e2104c2015-02-26 10:48:59 -0800347 };
348 }]);
Simon Huntfb8ea1f2015-02-24 21:38:09 -0800349}());