blob: 09e6ef0e61faf8906aedd070a24e92bfdaa8d7e1 [file] [log] [blame]
Steven Burrowsec1f45c2016-08-08 16:14:41 +01001/*
2 * Copyright 2016-present 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 Layout Module.
19 Module that contains the d3.force.layout logic
20 */
21
22(function () {
23 'use strict';
24
Steven Burrowse7cc3082016-09-27 11:24:58 -070025 var randomService, ps, sus, is, ts, t2mcs;
Steven Burrowsec1f45c2016-08-08 16:14:41 +010026 var fn;
27
Steven Burrowsdfa52b02016-09-02 13:50:43 +010028 // Internal state;
29 var nearDist = 15;
Steven Burrowsec1f45c2016-08-08 16:14:41 +010030
Steven Burrows6deb4ce2016-08-26 16:06:23 +010031 var devIconDim = 36,
Steven Burrowsbbe3dda2016-09-26 14:41:59 -070032 labelPad = 5,
33 textPad = 5,
34 halfDevIcon = devIconDim / 2;
35
36 // note: these are the device icon colors without affinity (no master)
37 var dColTheme = {
38 light: {
39 online: '#444444',
40 offline: '#cccccc'
41 },
42 dark: {
43 // TODO: theme
44 online: '#444444',
45 offline: '#cccccc'
46 }
47 };
48
49 function devGlyphColor(d) {
50 var o = this.get('online'),
51 id = this.get('master'),
52 otag = o ? 'online' : 'offline';
53 return o ? sus.cat7().getColor(id, 0, ts.theme()) :
54 dColTheme[ts.theme()][otag];
55 }
Steven Burrows6deb4ce2016-08-26 16:06:23 +010056
Steven Burrowsec1f45c2016-08-08 16:14:41 +010057 function positionNode(node, forUpdate) {
Steven Burrowse7cc3082016-09-27 11:24:58 -070058 var meta = node.get('metaUi'),
Steven Burrowsec1f45c2016-08-08 16:14:41 +010059 x = meta && meta.x,
60 y = meta && meta.y,
61 dim = [800, 600],
62 xy;
63
Steven Burrowsdfa52b02016-09-02 13:50:43 +010064 // If the device contains explicit LONG/LAT data, use that to position
Steven Burrowsec1f45c2016-08-08 16:14:41 +010065 if (setLongLat(node)) {
Steven Burrows482d9502016-09-27 11:24:58 -070066 // Indicate we want to update cached meta data...
Steven Burrowsec1f45c2016-08-08 16:14:41 +010067 return true;
68 }
69
70 // else if we have [x,y] cached in meta data, use that...
71 if (x !== undefined && y !== undefined) {
72 node.fixed = true;
73 node.px = node.x = x;
74 node.py = node.y = y;
75 return;
76 }
77
78 // if this is a node update (not a node add).. skip randomizer
79 if (forUpdate) {
80 return;
81 }
82
83 // Note: Placing incoming unpinned nodes at exactly the same point
84 // (center of the view) causes them to explode outwards when
85 // the force layout kicks in. So, we spread them out a bit
86 // initially, to provide a more serene layout convergence.
87 // Additionally, if the node is a host, we place it near
88 // the device it is connected to.
89
90 function rand() {
91 return {
92 x: randomService.randDim(dim[0]),
93 y: randomService.randDim(dim[1])
94 };
95 }
96
97 function near(node) {
98 return {
99 x: node.x + nearDist + randomService.spread(nearDist),
100 y: node.y + nearDist + randomService.spread(nearDist)
101 };
102 }
103
104 function getDevice(cp) {
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100105 return rand();
106 }
107
108 xy = (node.class === 'host') ? near(getDevice(node.cp)) : rand();
Steven Burrowse7cc3082016-09-27 11:24:58 -0700109
110 if (node.class === 'sub-region') {
111 xy = rand();
112 node.x = node.px = xy.x;
113 node.y = node.py = xy.y;
114 }
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100115 angular.extend(node, xy);
116 }
117
Steven Burrows482d9502016-09-27 11:24:58 -0700118 function setLongLat(el) {
119 var loc = el.get('location'),
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100120 coord;
121
122 if (loc && loc.type === 'lnglat') {
Steven Burrows482d9502016-09-27 11:24:58 -0700123
124 if (loc.lat === 0 && loc.lng === 0) {
125 return false;
126 }
127
Steven Burrowse7cc3082016-09-27 11:24:58 -0700128 coord = coordFromLngLat(loc);
Steven Burrows482d9502016-09-27 11:24:58 -0700129 el.fixed = true;
130 el.x = el.px = coord[0];
131 el.y = el.py = coord[1];
132
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100133 return true;
134 }
135 }
136
Steven Burrowse7cc3082016-09-27 11:24:58 -0700137 function coordFromLngLat(loc) {
138 var p = t2mcs.projection();
Steven Burrows482d9502016-09-27 11:24:58 -0700139 return p ? p([loc.lng, loc.lat]) : [0, 0];
Steven Burrowse7cc3082016-09-27 11:24:58 -0700140 }
141
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100142 angular.module('ovTopo2')
143 .factory('Topo2NodeModel',
Steven Burrows37549ee2016-09-21 14:41:39 +0100144 ['Topo2Model', 'FnService', 'RandomService', 'Topo2PrefsService',
Steven Burrows482d9502016-09-27 11:24:58 -0700145 'SvgUtilService', 'IconService', 'ThemeService',
146 'Topo2MapConfigService',
147 function (Model, _fn_, _RandomService_, _ps_, _sus_, _is_, _ts_,
148 _t2mcs_) {
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100149
150 randomService = _RandomService_;
Steven Burrowsbbe3dda2016-09-26 14:41:59 -0700151 ts = _ts_;
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100152 fn = _fn_;
Steven Burrows37549ee2016-09-21 14:41:39 +0100153 ps = _ps_;
Steven Burrowsbbe3dda2016-09-26 14:41:59 -0700154 sus = _sus_;
155 is = _is_;
Steven Burrowse7cc3082016-09-27 11:24:58 -0700156 t2mcs = _t2mcs_;
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100157
158 return Model.extend({
159 initialize: function () {
Steven Burrows482d9502016-09-27 11:24:58 -0700160 this.set('class', this.nodeType);
161 this.set('svgClass', this.svgClassName());
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100162 this.node = this.createNode();
163 },
Steven Burrowse7cc3082016-09-27 11:24:58 -0700164 createNode: function () {
165 this.set('class', this.nodeType);
166 this.set('svgClass', this.svgClassName());
167 positionNode(this);
168 return this;
169 },
Steven Burrowsbbe3dda2016-09-26 14:41:59 -0700170 setUpEvents: function () {
171 var _this = this;
172 angular.forEach(this.events, function (handler, key) {
173 _this.el.on(key, _this[handler].bind(_this));
174 });
175 },
176 icon: function () {
177 return 'unknown';
178 },
Steven Burrows6deb4ce2016-08-26 16:06:23 +0100179 label: function () {
Steven Burrows6deb4ce2016-08-26 16:06:23 +0100180 var props = this.get('props'),
181 id = this.get('id'),
182 friendlyName = props ? props.name : id,
183 labels = ['', friendlyName, id],
Steven Burrows37549ee2016-09-21 14:41:39 +0100184 nli = ps.get('dlbls'),
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100185 idx = (nli < labels.length) ? nli : 0;
Steven Burrows6deb4ce2016-08-26 16:06:23 +0100186
187 return labels[idx];
188 },
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100189 trimLabel: function (label) {
Steven Burrows6deb4ce2016-08-26 16:06:23 +0100190 return (label && label.trim()) || '';
191 },
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100192 computeLabelWidth: function (el) {
Steven Burrows6deb4ce2016-08-26 16:06:23 +0100193 var text = el.select('text'),
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100194 box = text.node().getBBox();
Steven Burrows6deb4ce2016-08-26 16:06:23 +0100195 return box.width + labelPad * 2;
196 },
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100197 addLabelElements: function (label) {
Steven Burrows6deb4ce2016-08-26 16:06:23 +0100198 var rect = this.el.append('rect');
Steven Burrowsbbe3dda2016-09-26 14:41:59 -0700199 var glythRect = this.el.append('rect')
200 .attr('y', -halfDevIcon)
201 .attr('x', -halfDevIcon)
202 .attr('width', devIconDim)
203 .attr('height', devIconDim)
204 .style('fill', devGlyphColor.bind(this));
205
Steven Burrows6deb4ce2016-08-26 16:06:23 +0100206 var text = this.el.append('text').text(label)
207 .attr('text-anchor', 'left')
208 .attr('y', '0.3em')
Steven Burrowsbbe3dda2016-09-26 14:41:59 -0700209 .attr('x', halfDevIcon + labelPad + textPad);
Steven Burrows6deb4ce2016-08-26 16:06:23 +0100210
211 return {
212 rect: rect,
Steven Burrowsbbe3dda2016-09-26 14:41:59 -0700213 glythRect: glythRect,
Steven Burrows6deb4ce2016-08-26 16:06:23 +0100214 text: text
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100215 };
Steven Burrows6deb4ce2016-08-26 16:06:23 +0100216 },
Steven Burrowsbbe3dda2016-09-26 14:41:59 -0700217 labelBox: function (dim, labelWidth) {
218 var _textPad = (textPad * 2) - labelPad;
219
220 if (labelWidth === 0) {
221 _textPad = 0;
222 }
223
224 return {
225 x: -dim / 2 - labelPad,
226 y: -dim / 2 - labelPad,
227 width: dim + labelWidth + (labelPad * 2) + _textPad,
228 height: dim + (labelPad * 2)
229 };
230 },
231 iconBox: function (dim, labelWidth) {
Steven Burrows37549ee2016-09-21 14:41:39 +0100232 return {
233 x: -dim / 2,
234 y: -dim / 2,
235 width: dim + labelWidth,
236 height: dim
237 };
238 },
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100239 svgClassName: function () {
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100240 return fn.classNames('node',
241 this.nodeType,
242 this.get('type'),
243 {
244 online: this.get('online')
245 }
246 );
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100247 },
Steven Burrowse7cc3082016-09-27 11:24:58 -0700248 lngLatFromCoord: function (coord) {
249 var p = t2mcs.projection();
250 return p ? p.invert(coord) : [0, 0];
251 },
Steven Burrows37549ee2016-09-21 14:41:39 +0100252 update: function () {
253 this.updateLabel();
254 },
255 updateLabel: function () {
256 var node = this.el,
257 label = this.trimLabel(this.label()),
258 labelWidth;
259
260 node.select('text').text(label);
261 labelWidth = label ? this.computeLabelWidth(node) : 0;
262
263 node.select('rect')
264 .transition()
Steven Burrowsbbe3dda2016-09-26 14:41:59 -0700265 .attr(this.labelBox(devIconDim, labelWidth));
Steven Burrows37549ee2016-09-21 14:41:39 +0100266 },
Steven Burrowsbbe3dda2016-09-26 14:41:59 -0700267 onEnter: function (el) {
268 this.el = d3.select(el);
269 this.render();
270 },
271 render: function () {
272 var node = this.el,
273 glyphId = this.icon(this.get('type')),
274 label = this.trimLabel(this.label()),
275 glyph, labelWidth;
276
277 // Label
278 var labelElements = this.addLabelElements(label);
279 labelWidth = label ? this.computeLabelWidth(node) : 0;
280 labelElements.rect.attr(this.labelBox(devIconDim, labelWidth));
281
282 // Icon
283 glyph = is.addDeviceIcon(node, glyphId, devIconDim);
284 glyph.attr(this.iconBox(devIconDim, 0));
285 glyph.style('fill', 'white');
286
287 node.attr('transform', sus.translate(-halfDevIcon, -halfDevIcon));
288
289 if (this.events) {
290 this.setUpEvents();
291 }
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100292 }
293 });
294 }]
295 );
296})();