blob: d4917a38dca78c576dea624ba9afa026b045a88a [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 Burrowsec1f45c2016-08-08 16:14:41 +010066 return true;
67 }
68
69 // else if we have [x,y] cached in meta data, use that...
70 if (x !== undefined && y !== undefined) {
71 node.fixed = true;
72 node.px = node.x = x;
73 node.py = node.y = y;
74 return;
75 }
76
77 // if this is a node update (not a node add).. skip randomizer
78 if (forUpdate) {
79 return;
80 }
81
82 // Note: Placing incoming unpinned nodes at exactly the same point
83 // (center of the view) causes them to explode outwards when
84 // the force layout kicks in. So, we spread them out a bit
85 // initially, to provide a more serene layout convergence.
86 // Additionally, if the node is a host, we place it near
87 // the device it is connected to.
88
89 function rand() {
90 return {
91 x: randomService.randDim(dim[0]),
92 y: randomService.randDim(dim[1])
93 };
94 }
95
96 function near(node) {
97 return {
98 x: node.x + nearDist + randomService.spread(nearDist),
99 y: node.y + nearDist + randomService.spread(nearDist)
100 };
101 }
102
103 function getDevice(cp) {
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100104 return rand();
105 }
106
107 xy = (node.class === 'host') ? near(getDevice(node.cp)) : rand();
Steven Burrowse7cc3082016-09-27 11:24:58 -0700108
109 if (node.class === 'sub-region') {
110 xy = rand();
111 node.x = node.px = xy.x;
112 node.y = node.py = xy.y;
113 }
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100114 angular.extend(node, xy);
115 }
116
117 function setLongLat(node) {
118 var loc = node.location,
119 coord;
120
121 if (loc && loc.type === 'lnglat') {
Steven Burrowse7cc3082016-09-27 11:24:58 -0700122 coord = coordFromLngLat(loc);
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100123 node.fixed = true;
124 node.px = node.x = coord[0];
125 node.py = node.y = coord[1];
126 return true;
127 }
128 }
129
Steven Burrowse7cc3082016-09-27 11:24:58 -0700130 function coordFromLngLat(loc) {
131 var p = t2mcs.projection();
132 return p ? p.invert([loc.lng, loc.lat]) : [0, 0];
133 }
134
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100135 angular.module('ovTopo2')
136 .factory('Topo2NodeModel',
Steven Burrows37549ee2016-09-21 14:41:39 +0100137 ['Topo2Model', 'FnService', 'RandomService', 'Topo2PrefsService',
Steven Burrowse7cc3082016-09-27 11:24:58 -0700138 'SvgUtilService', 'IconService', 'ThemeService', 'Topo2MapConfigService',
139 function (Model, _fn_, _RandomService_, _ps_, _sus_, _is_, _ts_, _t2mcs_) {
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100140
141 randomService = _RandomService_;
Steven Burrowsbbe3dda2016-09-26 14:41:59 -0700142 ts = _ts_;
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100143 fn = _fn_;
Steven Burrows37549ee2016-09-21 14:41:39 +0100144 ps = _ps_;
Steven Burrowsbbe3dda2016-09-26 14:41:59 -0700145 sus = _sus_;
146 is = _is_;
Steven Burrowse7cc3082016-09-27 11:24:58 -0700147 t2mcs = _t2mcs_;
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100148
149 return Model.extend({
150 initialize: function () {
151 this.node = this.createNode();
152 },
Steven Burrowse7cc3082016-09-27 11:24:58 -0700153 createNode: function () {
154 this.set('class', this.nodeType);
155 this.set('svgClass', this.svgClassName());
156 positionNode(this);
157 return this;
158 },
Steven Burrowsbbe3dda2016-09-26 14:41:59 -0700159 setUpEvents: function () {
160 var _this = this;
161 angular.forEach(this.events, function (handler, key) {
162 _this.el.on(key, _this[handler].bind(_this));
163 });
164 },
165 icon: function () {
166 return 'unknown';
167 },
Steven Burrows6deb4ce2016-08-26 16:06:23 +0100168 label: function () {
Steven Burrows6deb4ce2016-08-26 16:06:23 +0100169 var props = this.get('props'),
170 id = this.get('id'),
171 friendlyName = props ? props.name : id,
172 labels = ['', friendlyName, id],
Steven Burrows37549ee2016-09-21 14:41:39 +0100173 nli = ps.get('dlbls'),
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100174 idx = (nli < labels.length) ? nli : 0;
Steven Burrows6deb4ce2016-08-26 16:06:23 +0100175
176 return labels[idx];
177 },
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100178 trimLabel: function (label) {
Steven Burrows6deb4ce2016-08-26 16:06:23 +0100179 return (label && label.trim()) || '';
180 },
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100181 computeLabelWidth: function (el) {
Steven Burrows6deb4ce2016-08-26 16:06:23 +0100182 var text = el.select('text'),
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100183 box = text.node().getBBox();
Steven Burrows6deb4ce2016-08-26 16:06:23 +0100184 return box.width + labelPad * 2;
185 },
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100186 addLabelElements: function (label) {
Steven Burrows6deb4ce2016-08-26 16:06:23 +0100187 var rect = this.el.append('rect');
Steven Burrowsbbe3dda2016-09-26 14:41:59 -0700188 var glythRect = this.el.append('rect')
189 .attr('y', -halfDevIcon)
190 .attr('x', -halfDevIcon)
191 .attr('width', devIconDim)
192 .attr('height', devIconDim)
193 .style('fill', devGlyphColor.bind(this));
194
Steven Burrows6deb4ce2016-08-26 16:06:23 +0100195 var text = this.el.append('text').text(label)
196 .attr('text-anchor', 'left')
197 .attr('y', '0.3em')
Steven Burrowsbbe3dda2016-09-26 14:41:59 -0700198 .attr('x', halfDevIcon + labelPad + textPad);
Steven Burrows6deb4ce2016-08-26 16:06:23 +0100199
200 return {
201 rect: rect,
Steven Burrowsbbe3dda2016-09-26 14:41:59 -0700202 glythRect: glythRect,
Steven Burrows6deb4ce2016-08-26 16:06:23 +0100203 text: text
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100204 };
Steven Burrows6deb4ce2016-08-26 16:06:23 +0100205 },
Steven Burrowsbbe3dda2016-09-26 14:41:59 -0700206 labelBox: function (dim, labelWidth) {
207 var _textPad = (textPad * 2) - labelPad;
208
209 if (labelWidth === 0) {
210 _textPad = 0;
211 }
212
213 return {
214 x: -dim / 2 - labelPad,
215 y: -dim / 2 - labelPad,
216 width: dim + labelWidth + (labelPad * 2) + _textPad,
217 height: dim + (labelPad * 2)
218 };
219 },
220 iconBox: function (dim, labelWidth) {
Steven Burrows37549ee2016-09-21 14:41:39 +0100221 return {
222 x: -dim / 2,
223 y: -dim / 2,
224 width: dim + labelWidth,
225 height: dim
226 };
227 },
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100228 svgClassName: function () {
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100229 return fn.classNames('node',
230 this.nodeType,
231 this.get('type'),
232 {
233 online: this.get('online')
234 }
235 );
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100236 },
Steven Burrowse7cc3082016-09-27 11:24:58 -0700237 lngLatFromCoord: function (coord) {
238 var p = t2mcs.projection();
239 return p ? p.invert(coord) : [0, 0];
240 },
Steven Burrows37549ee2016-09-21 14:41:39 +0100241 update: function () {
242 this.updateLabel();
243 },
244 updateLabel: function () {
245 var node = this.el,
246 label = this.trimLabel(this.label()),
247 labelWidth;
248
249 node.select('text').text(label);
250 labelWidth = label ? this.computeLabelWidth(node) : 0;
251
252 node.select('rect')
253 .transition()
Steven Burrowsbbe3dda2016-09-26 14:41:59 -0700254 .attr(this.labelBox(devIconDim, labelWidth));
Steven Burrows37549ee2016-09-21 14:41:39 +0100255 },
Steven Burrowsbbe3dda2016-09-26 14:41:59 -0700256 onEnter: function (el) {
257 this.el = d3.select(el);
258 this.render();
259 },
260 render: function () {
261 var node = this.el,
262 glyphId = this.icon(this.get('type')),
263 label = this.trimLabel(this.label()),
264 glyph, labelWidth;
265
266 // Label
267 var labelElements = this.addLabelElements(label);
268 labelWidth = label ? this.computeLabelWidth(node) : 0;
269 labelElements.rect.attr(this.labelBox(devIconDim, labelWidth));
270
271 // Icon
272 glyph = is.addDeviceIcon(node, glyphId, devIconDim);
273 glyph.attr(this.iconBox(devIconDim, 0));
274 glyph.style('fill', 'white');
275
276 node.attr('transform', sus.translate(-halfDevIcon, -halfDevIcon));
277
278 if (this.events) {
279 this.setUpEvents();
280 }
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100281 }
282 });
283 }]
284 );
285})();