blob: 109a6941867c1ace7b25a50a858addb632c9d0f0 [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 Burrows0616e802016-10-06 21:45:07 -050032 devIconDimMin = 20,
33 devIconDimMax = 40,
Steven Burrowsbbe3dda2016-09-26 14:41:59 -070034 labelPad = 5,
35 textPad = 5,
36 halfDevIcon = devIconDim / 2;
37
38 // note: these are the device icon colors without affinity (no master)
39 var dColTheme = {
40 light: {
41 online: '#444444',
42 offline: '#cccccc'
43 },
44 dark: {
45 // TODO: theme
46 online: '#444444',
47 offline: '#cccccc'
48 }
49 };
50
51 function devGlyphColor(d) {
52 var o = this.get('online'),
53 id = this.get('master'),
54 otag = o ? 'online' : 'offline';
55 return o ? sus.cat7().getColor(id, 0, ts.theme()) :
56 dColTheme[ts.theme()][otag];
57 }
Steven Burrows6deb4ce2016-08-26 16:06:23 +010058
Steven Burrowsec1f45c2016-08-08 16:14:41 +010059 function positionNode(node, forUpdate) {
Steven Burrowse7cc3082016-09-27 11:24:58 -070060 var meta = node.get('metaUi'),
Steven Burrowsec1f45c2016-08-08 16:14:41 +010061 x = meta && meta.x,
62 y = meta && meta.y,
63 dim = [800, 600],
64 xy;
65
Steven Burrowsdfa52b02016-09-02 13:50:43 +010066 // If the device contains explicit LONG/LAT data, use that to position
Steven Burrowsec1f45c2016-08-08 16:14:41 +010067 if (setLongLat(node)) {
Steven Burrows482d9502016-09-27 11:24:58 -070068 // Indicate we want to update cached meta data...
Steven Burrowsec1f45c2016-08-08 16:14:41 +010069 return true;
70 }
71
72 // else if we have [x,y] cached in meta data, use that...
73 if (x !== undefined && y !== undefined) {
74 node.fixed = true;
75 node.px = node.x = x;
76 node.py = node.y = y;
77 return;
78 }
79
80 // if this is a node update (not a node add).. skip randomizer
81 if (forUpdate) {
82 return;
83 }
84
85 // Note: Placing incoming unpinned nodes at exactly the same point
86 // (center of the view) causes them to explode outwards when
87 // the force layout kicks in. So, we spread them out a bit
88 // initially, to provide a more serene layout convergence.
89 // Additionally, if the node is a host, we place it near
90 // the device it is connected to.
91
92 function rand() {
93 return {
94 x: randomService.randDim(dim[0]),
95 y: randomService.randDim(dim[1])
96 };
97 }
98
99 function near(node) {
100 return {
101 x: node.x + nearDist + randomService.spread(nearDist),
102 y: node.y + nearDist + randomService.spread(nearDist)
103 };
104 }
105
106 function getDevice(cp) {
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100107 return rand();
108 }
109
110 xy = (node.class === 'host') ? near(getDevice(node.cp)) : rand();
Steven Burrowse7cc3082016-09-27 11:24:58 -0700111
112 if (node.class === 'sub-region') {
113 xy = rand();
114 node.x = node.px = xy.x;
115 node.y = node.py = xy.y;
116 }
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100117 angular.extend(node, xy);
118 }
119
Steven Burrows482d9502016-09-27 11:24:58 -0700120 function setLongLat(el) {
121 var loc = el.get('location'),
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100122 coord;
123
124 if (loc && loc.type === 'lnglat') {
Steven Burrows482d9502016-09-27 11:24:58 -0700125
126 if (loc.lat === 0 && loc.lng === 0) {
127 return false;
128 }
129
Steven Burrowse7cc3082016-09-27 11:24:58 -0700130 coord = coordFromLngLat(loc);
Steven Burrows482d9502016-09-27 11:24:58 -0700131 el.fixed = true;
132 el.x = el.px = coord[0];
133 el.y = el.py = coord[1];
134
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100135 return true;
136 }
137 }
138
Steven Burrowse7cc3082016-09-27 11:24:58 -0700139 function coordFromLngLat(loc) {
140 var p = t2mcs.projection();
Steven Burrows482d9502016-09-27 11:24:58 -0700141 return p ? p([loc.lng, loc.lat]) : [0, 0];
Steven Burrowse7cc3082016-09-27 11:24:58 -0700142 }
143
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100144 angular.module('ovTopo2')
145 .factory('Topo2NodeModel',
Steven Burrows37549ee2016-09-21 14:41:39 +0100146 ['Topo2Model', 'FnService', 'RandomService', 'Topo2PrefsService',
Steven Burrows482d9502016-09-27 11:24:58 -0700147 'SvgUtilService', 'IconService', 'ThemeService',
Steven Burrows0616e802016-10-06 21:45:07 -0500148 'Topo2MapConfigService', 'Topo2ZoomService',
Steven Burrows482d9502016-09-27 11:24:58 -0700149 function (Model, _fn_, _RandomService_, _ps_, _sus_, _is_, _ts_,
Steven Burrows0616e802016-10-06 21:45:07 -0500150 _t2mcs_, zoomService) {
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100151
152 randomService = _RandomService_;
Steven Burrowsbbe3dda2016-09-26 14:41:59 -0700153 ts = _ts_;
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100154 fn = _fn_;
Steven Burrows37549ee2016-09-21 14:41:39 +0100155 ps = _ps_;
Steven Burrowsbbe3dda2016-09-26 14:41:59 -0700156 sus = _sus_;
157 is = _is_;
Steven Burrowse7cc3082016-09-27 11:24:58 -0700158 t2mcs = _t2mcs_;
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100159
160 return Model.extend({
161 initialize: function () {
Steven Burrows482d9502016-09-27 11:24:58 -0700162 this.set('class', this.nodeType);
163 this.set('svgClass', this.svgClassName());
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100164 this.node = this.createNode();
165 },
Steven Burrowse7cc3082016-09-27 11:24:58 -0700166 createNode: function () {
167 this.set('class', this.nodeType);
168 this.set('svgClass', this.svgClassName());
169 positionNode(this);
170 return this;
171 },
Steven Burrowsbbe3dda2016-09-26 14:41:59 -0700172 setUpEvents: function () {
173 var _this = this;
174 angular.forEach(this.events, function (handler, key) {
175 _this.el.on(key, _this[handler].bind(_this));
176 });
177 },
178 icon: function () {
179 return 'unknown';
180 },
Steven Burrows6deb4ce2016-08-26 16:06:23 +0100181 label: function () {
Steven Burrows6deb4ce2016-08-26 16:06:23 +0100182 var props = this.get('props'),
183 id = this.get('id'),
184 friendlyName = props ? props.name : id,
185 labels = ['', friendlyName, id],
Steven Burrows37549ee2016-09-21 14:41:39 +0100186 nli = ps.get('dlbls'),
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100187 idx = (nli < labels.length) ? nli : 0;
Steven Burrows6deb4ce2016-08-26 16:06:23 +0100188
189 return labels[idx];
190 },
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100191 trimLabel: function (label) {
Steven Burrows6deb4ce2016-08-26 16:06:23 +0100192 return (label && label.trim()) || '';
193 },
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100194 computeLabelWidth: function (el) {
Steven Burrows6deb4ce2016-08-26 16:06:23 +0100195 var text = el.select('text'),
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100196 box = text.node().getBBox();
Steven Burrows6deb4ce2016-08-26 16:06:23 +0100197 return box.width + labelPad * 2;
198 },
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100199 addLabelElements: function (label) {
Steven Burrows6deb4ce2016-08-26 16:06:23 +0100200 var rect = this.el.append('rect');
Steven Burrowsbbe3dda2016-09-26 14:41:59 -0700201 var glythRect = this.el.append('rect')
202 .attr('y', -halfDevIcon)
203 .attr('x', -halfDevIcon)
204 .attr('width', devIconDim)
205 .attr('height', devIconDim)
206 .style('fill', devGlyphColor.bind(this));
207
Steven Burrows6deb4ce2016-08-26 16:06:23 +0100208 var text = this.el.append('text').text(label)
209 .attr('text-anchor', 'left')
210 .attr('y', '0.3em')
Steven Burrowsbbe3dda2016-09-26 14:41:59 -0700211 .attr('x', halfDevIcon + labelPad + textPad);
Steven Burrows6deb4ce2016-08-26 16:06:23 +0100212
213 return {
214 rect: rect,
Steven Burrowsbbe3dda2016-09-26 14:41:59 -0700215 glythRect: glythRect,
Steven Burrows6deb4ce2016-08-26 16:06:23 +0100216 text: text
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100217 };
Steven Burrows6deb4ce2016-08-26 16:06:23 +0100218 },
Steven Burrowsbbe3dda2016-09-26 14:41:59 -0700219 labelBox: function (dim, labelWidth) {
220 var _textPad = (textPad * 2) - labelPad;
221
222 if (labelWidth === 0) {
223 _textPad = 0;
224 }
225
226 return {
227 x: -dim / 2 - labelPad,
228 y: -dim / 2 - labelPad,
229 width: dim + labelWidth + (labelPad * 2) + _textPad,
230 height: dim + (labelPad * 2)
231 };
232 },
233 iconBox: function (dim, labelWidth) {
Steven Burrows37549ee2016-09-21 14:41:39 +0100234 return {
235 x: -dim / 2,
236 y: -dim / 2,
237 width: dim + labelWidth,
238 height: dim
239 };
240 },
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100241 svgClassName: function () {
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100242 return fn.classNames('node',
243 this.nodeType,
244 this.get('type'),
245 {
246 online: this.get('online')
247 }
248 );
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100249 },
Steven Burrowse7cc3082016-09-27 11:24:58 -0700250 lngLatFromCoord: function (coord) {
251 var p = t2mcs.projection();
252 return p ? p.invert(coord) : [0, 0];
253 },
Steven Burrows37549ee2016-09-21 14:41:39 +0100254 update: function () {
255 this.updateLabel();
256 },
257 updateLabel: function () {
258 var node = this.el,
259 label = this.trimLabel(this.label()),
260 labelWidth;
261
262 node.select('text').text(label);
263 labelWidth = label ? this.computeLabelWidth(node) : 0;
264
265 node.select('rect')
266 .transition()
Steven Burrowsbbe3dda2016-09-26 14:41:59 -0700267 .attr(this.labelBox(devIconDim, labelWidth));
Steven Burrows37549ee2016-09-21 14:41:39 +0100268 },
Steven Burrowsbbe3dda2016-09-26 14:41:59 -0700269 onEnter: function (el) {
270 this.el = d3.select(el);
271 this.render();
272 },
Steven Burrows0616e802016-10-06 21:45:07 -0500273 setScale: function () {
274
275 var dim = devIconDim,
276 multipler = 1;
277
278 if (dim * zoomService.scale() < devIconDimMin) {
279 multipler = devIconDimMin / (dim * zoomService.scale());
280 } else if (dim * zoomService.scale() > devIconDimMax) {
281 multipler = devIconDimMax / (dim * zoomService.scale());
282 }
283
284
285 this.el.selectAll('*').style('transform', 'scale(' + multipler + ')');
286 },
Steven Burrowsbbe3dda2016-09-26 14:41:59 -0700287 render: function () {
288 var node = this.el,
289 glyphId = this.icon(this.get('type')),
290 label = this.trimLabel(this.label()),
291 glyph, labelWidth;
292
293 // Label
294 var labelElements = this.addLabelElements(label);
295 labelWidth = label ? this.computeLabelWidth(node) : 0;
296 labelElements.rect.attr(this.labelBox(devIconDim, labelWidth));
297
298 // Icon
299 glyph = is.addDeviceIcon(node, glyphId, devIconDim);
300 glyph.attr(this.iconBox(devIconDim, 0));
301 glyph.style('fill', 'white');
302
303 node.attr('transform', sus.translate(-halfDevIcon, -halfDevIcon));
304
305 if (this.events) {
306 this.setUpEvents();
307 }
Steven Burrows0616e802016-10-06 21:45:07 -0500308
309 this.setScale();
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100310 }
311 });
312 }]
313 );
314})();