| /* |
| * Copyright 2015-present Open Networking Foundation |
| * |
| * 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. |
| */ |
| |
| /* |
| ONOS GUI -- Topology Link Module. |
| Functions for highlighting/selecting links |
| */ |
| |
| (function () { |
| 'use strict'; |
| |
| // injected refs |
| var $log, fs, flash, tss; |
| |
| // internal state |
| var api, |
| td3, |
| network, |
| showPorts = true, // enable port highlighting by default |
| enhancedLink = null, // the link over which the mouse is hovering |
| selectedLinks = {}; // the links which are already selected |
| |
| // SVG elements; |
| var svg; |
| |
| // function to be replaced by the localization bundle function |
| var topoLion = function (x) { |
| return '#tlink#' + x + '#'; |
| }; |
| |
| // ======== ALGORITHM TO FIND LINK CLOSEST TO MOUSE ======== |
| |
| function getLogicalMousePosition(container) { |
| var m = d3.mouse(container), |
| sc = api.zoomer.scale(), |
| tr = api.zoomer.translate(), |
| mx = (m[0] - tr[0]) / sc, |
| my = (m[1] - tr[1]) / sc; |
| return { x: mx, y: my }; |
| } |
| |
| |
| function sq(x) { return x * x; } |
| |
| function mdist(p, m) { |
| return Math.sqrt(sq(p.x - m.x) + sq(p.y - m.y)); |
| } |
| |
| function prox(dist) { |
| return dist / api.zoomer.scale(); |
| } |
| |
| function computeNearestNode(mouse) { |
| var proximity = prox(30), |
| nearest = null, |
| minDist; |
| |
| if (network.nodes.length) { |
| minDist = proximity * 2; |
| |
| network.nodes.forEach(function (d) { |
| var dist; |
| |
| if (!api.showHosts() && d.class === 'host') { |
| return; // skip hidden hosts |
| } |
| |
| dist = mdist({ x: d.x, y: d.y }, mouse); |
| if (dist < minDist && dist < proximity) { |
| minDist = dist; |
| nearest = d; |
| } |
| }); |
| } |
| return nearest; |
| } |
| |
| |
| function computeNearestLink(mouse) { |
| var proximity = prox(30), |
| nearest = null, |
| minDist; |
| |
| function pdrop(line, mouse) { |
| var x1 = line.x1, |
| y1 = line.y1, |
| x2 = line.x2, |
| y2 = line.y2, |
| x3 = mouse.x, |
| y3 = mouse.y, |
| k = ((y2-y1) * (x3-x1) - (x2-x1) * (y3-y1)) / |
| (sq(y2-y1) + sq(x2-x1)), |
| x4 = x3 - k * (y2-y1), |
| y4 = y3 + k * (x2-x1); |
| return { x: x4, y: y4 }; |
| } |
| |
| function lineHit(line, p, m) { |
| if (p.x < line.x1 && p.x < line.x2) return false; |
| if (p.x > line.x1 && p.x > line.x2) return false; |
| if (p.y < line.y1 && p.y < line.y2) return false; |
| if (p.y > line.y1 && p.y > line.y2) return false; |
| // line intersects, but are we close enough? |
| return mdist(p, m) <= proximity; |
| } |
| |
| if (network.links.length) { |
| minDist = proximity * 2; |
| |
| network.links.forEach(function (d) { |
| var line = d.position, |
| point, |
| hit, |
| dist; |
| |
| if (!api.showHosts() && d.type() === 'hostLink') { |
| return; // skip hidden host links |
| } |
| |
| if (line) { |
| point = pdrop(line, mouse); |
| hit = lineHit(line, point, mouse); |
| if (hit) { |
| dist = mdist(point, mouse); |
| if (dist < minDist) { |
| minDist = dist; |
| nearest = d; |
| } |
| } |
| } |
| }); |
| } |
| return nearest; |
| } |
| |
| function enhanceLink(ldata) { |
| // if the new link is same as old link, do nothing |
| if (enhancedLink && ldata && enhancedLink.key === ldata.key) return; |
| |
| // first, unenhance the currently enhanced link |
| if (enhancedLink) { |
| unenhance(enhancedLink); |
| } |
| enhancedLink = ldata; |
| if (enhancedLink) { |
| enhance(enhancedLink); |
| } |
| } |
| |
| function unenhance(d) { |
| // guard against link element not set |
| if (d.el) { |
| d.el.classed('enhanced', false); |
| } |
| api.portLabelG().selectAll('.portLabel').remove(); |
| } |
| |
| function enhance(d) { |
| var data = [], |
| point; |
| |
| // guard against link element not set |
| if (!d.el) return; |
| |
| d.el.classed('enhanced', true); |
| |
| // Define port label data objects. |
| // NOTE: src port is absent in the case of host-links. |
| |
| point = locatePortLabel(d); |
| angular.extend(point, { |
| id: 'topo-port-tgt', |
| num: d.tgtPort, |
| }); |
| data.push(point); |
| |
| if (d.srcPort) { |
| point = locatePortLabel(d, 1); |
| angular.extend(point, { |
| id: 'topo-port-src', |
| num: d.srcPort, |
| }); |
| data.push(point); |
| } |
| |
| td3.applyPortLabels(data, api.portLabelG()); |
| } |
| |
| function locatePortLabel(link, src) { |
| var offset = 32, |
| pos = link.position, |
| nearX = src ? pos.x1 : pos.x2, |
| nearY = src ? pos.y1 : pos.y2, |
| farX = src ? pos.x2 : pos.x1, |
| farY = src ? pos.y2 : pos.y1; |
| |
| function dist(x, y) { return Math.sqrt(x*x + y*y); } |
| |
| var dx = farX - nearX, |
| dy = farY - nearY, |
| k = offset / dist(dx, dy); |
| |
| return { x: k * dx + nearX, y: k * dy + nearY }; |
| } |
| |
| function selectLink(ldata) { |
| // if the new link is same as old link, do nothing |
| if (d3.event.shiftKey && ldata.el.classed('selected')) { |
| unselLink(ldata); |
| return; |
| } |
| |
| if (d3.event.shiftKey && !ldata.el.classed('selected')) { |
| selLink(ldata); |
| return; |
| } |
| |
| tss.deselectAll(); |
| |
| if (ldata) { |
| if (ldata.el.classed('selected')) { |
| unselLink(ldata); |
| } else { |
| selLink(ldata); |
| } |
| } |
| } |
| |
| function unselLink(d) { |
| // guard against link element not set |
| if (d.el) { |
| d.el.classed('selected', false); |
| delete selectedLinks[d.key]; |
| } |
| } |
| |
| function selLink(d) { |
| // guard against link element not set |
| if (!d.el) return; |
| |
| d.el.classed('selected', true); |
| selectedLinks[d.key] = { key: d }; |
| |
| // TODO: deprecate tov.hooks.modifyLinkData |
| // tps.displayLink(d, tov.hooks.modifyLinkData); |
| // tps.displaySomething(); |
| } |
| |
| // ====== MOUSE EVENT HANDLERS ====== |
| |
| function mouseMoveHandler() { |
| var mp = getLogicalMousePosition(this), |
| link = computeNearestLink(mp); |
| enhanceLink(link); |
| } |
| |
| function mouseClickHandler() { |
| var mp, link, node; |
| if (!d3.event.shiftKey) { |
| deselectAllLinks(); |
| } |
| |
| if (!tss.clickConsumed()) { |
| mp = getLogicalMousePosition(this); |
| node = computeNearestNode(mp); |
| if (node) { |
| $log.debug('found nearest node:', node.labels[1]); |
| tss.selectObject(node); |
| } else { |
| link = computeNearestLink(mp); |
| selectLink(link); |
| tss.selectObject(link); |
| } |
| } |
| } |
| |
| |
| // ====================== |
| |
| function togglePorts(x) { |
| var kev = (x === 'keyev'), |
| on = kev ? !showPorts : !!x, |
| what = on ? topoLion('enable') : topoLion('disable'), |
| philite = topoLion('fl_port_highlighting'), |
| handler = on ? mouseMoveHandler : null; |
| |
| showPorts = on; |
| |
| if (!on) { |
| enhanceLink(null); |
| } |
| svg.on('mousemove', handler); |
| flash.flash(what + ' ' + philite); |
| return on; |
| } |
| |
| function deselectAllLinks() { |
| |
| if (Object.keys(selectedLinks).length > 0) { |
| network.links.forEach(function (d) { |
| if (selectedLinks[d.key]) { |
| unselLink(d); |
| } |
| }); |
| } |
| } |
| |
| // invoked after the localization bundle has been received from the server |
| function setLionBundle(bundle) { |
| topoLion = bundle; |
| } |
| |
| // ========================== |
| // Module definition |
| |
| angular.module('ovTopo') |
| .factory('TopoLinkService', |
| ['$log', 'FnService', 'FlashService', 'TopoSelectService', |
| |
| function (_$log_, _fs_, _flash_, _tss_) { |
| $log = _$log_; |
| fs = _fs_; |
| flash = _flash_; |
| tss = _tss_; |
| |
| function initLink(_api_, _td3_) { |
| api = _api_; |
| td3 = _td3_; |
| svg = api.svg; |
| network = api.network; |
| if (showPorts && !fs.isMobile()) { |
| svg.on('mousemove', mouseMoveHandler); |
| } |
| svg.on('click', mouseClickHandler); |
| } |
| |
| function destroyLink() { |
| // unconditionally remove any event handlers |
| svg.on('mousemove', null); |
| svg.on('click', null); |
| } |
| |
| return { |
| initLink: initLink, |
| destroyLink: destroyLink, |
| togglePorts: togglePorts, |
| deselectAllLinks: deselectAllLinks, |
| setLionBundle: setLionBundle, |
| }; |
| }]); |
| }()); |