blob: 3f781c4e0654acc7b01cc8eb8a5a1535e71dfce7 [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 Burrowsbbe3dda2016-09-26 14:41:59 -070025 var randomService, ps, sus, is, ts;
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) {
58
59 var meta = node.metaUi,
60 x = meta && meta.x,
61 y = meta && meta.y,
62 dim = [800, 600],
63 xy;
64
Steven Burrowsdfa52b02016-09-02 13:50:43 +010065 // If the device contains explicit LONG/LAT data, use that to position
Steven Burrowsec1f45c2016-08-08 16:14:41 +010066 if (setLongLat(node)) {
Steven Burrowsdfa52b02016-09-02 13:50:43 +010067 // Indicate we want to update cached meta data...
Steven Burrowsec1f45c2016-08-08 16:14:41 +010068 return true;
69 }
70
71 // else if we have [x,y] cached in meta data, use that...
72 if (x !== undefined && y !== undefined) {
73 node.fixed = true;
74 node.px = node.x = x;
75 node.py = node.y = y;
76 return;
77 }
78
79 // if this is a node update (not a node add).. skip randomizer
80 if (forUpdate) {
81 return;
82 }
83
84 // Note: Placing incoming unpinned nodes at exactly the same point
85 // (center of the view) causes them to explode outwards when
86 // the force layout kicks in. So, we spread them out a bit
87 // initially, to provide a more serene layout convergence.
88 // Additionally, if the node is a host, we place it near
89 // the device it is connected to.
90
91 function rand() {
92 return {
93 x: randomService.randDim(dim[0]),
94 y: randomService.randDim(dim[1])
95 };
96 }
97
98 function near(node) {
99 return {
100 x: node.x + nearDist + randomService.spread(nearDist),
101 y: node.y + nearDist + randomService.spread(nearDist)
102 };
103 }
104
105 function getDevice(cp) {
106 // console.log(cp);
107 // var d = lu[cp.device];
108 // return d || rand();
109 return rand();
110 }
111
112 xy = (node.class === 'host') ? near(getDevice(node.cp)) : rand();
113 angular.extend(node, xy);
114 }
115
116 function setLongLat(node) {
117 var loc = node.location,
118 coord;
119
120 if (loc && loc.type === 'lnglat') {
121 coord = [0, 0];
122 node.fixed = true;
123 node.px = node.x = coord[0];
124 node.py = node.y = coord[1];
125 return true;
126 }
127 }
128
129 angular.module('ovTopo2')
130 .factory('Topo2NodeModel',
Steven Burrows37549ee2016-09-21 14:41:39 +0100131 ['Topo2Model', 'FnService', 'RandomService', 'Topo2PrefsService',
Steven Burrowsbbe3dda2016-09-26 14:41:59 -0700132 'SvgUtilService', 'IconService', 'ThemeService',
133 function (Model, _fn_, _RandomService_, _ps_, _sus_, _is_, _ts_) {
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100134
135 randomService = _RandomService_;
Steven Burrowsbbe3dda2016-09-26 14:41:59 -0700136 ts = _ts_;
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100137 fn = _fn_;
Steven Burrows37549ee2016-09-21 14:41:39 +0100138 ps = _ps_;
Steven Burrowsbbe3dda2016-09-26 14:41:59 -0700139 sus = _sus_;
140 is = _is_;
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100141
142 return Model.extend({
143 initialize: function () {
144 this.node = this.createNode();
145 },
Steven Burrowsbbe3dda2016-09-26 14:41:59 -0700146 setUpEvents: function () {
147 var _this = this;
148 angular.forEach(this.events, function (handler, key) {
149 _this.el.on(key, _this[handler].bind(_this));
150 });
151 },
152 icon: function () {
153 return 'unknown';
154 },
Steven Burrows6deb4ce2016-08-26 16:06:23 +0100155 label: function () {
Steven Burrows6deb4ce2016-08-26 16:06:23 +0100156 var props = this.get('props'),
157 id = this.get('id'),
158 friendlyName = props ? props.name : id,
159 labels = ['', friendlyName, id],
Steven Burrows37549ee2016-09-21 14:41:39 +0100160 nli = ps.get('dlbls'),
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100161 idx = (nli < labels.length) ? nli : 0;
Steven Burrows6deb4ce2016-08-26 16:06:23 +0100162
163 return labels[idx];
164 },
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100165 trimLabel: function (label) {
Steven Burrows6deb4ce2016-08-26 16:06:23 +0100166 return (label && label.trim()) || '';
167 },
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100168 computeLabelWidth: function (el) {
Steven Burrows6deb4ce2016-08-26 16:06:23 +0100169 var text = el.select('text'),
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100170 box = text.node().getBBox();
Steven Burrows6deb4ce2016-08-26 16:06:23 +0100171 return box.width + labelPad * 2;
172 },
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100173 addLabelElements: function (label) {
Steven Burrows6deb4ce2016-08-26 16:06:23 +0100174 var rect = this.el.append('rect');
Steven Burrowsbbe3dda2016-09-26 14:41:59 -0700175 var glythRect = this.el.append('rect')
176 .attr('y', -halfDevIcon)
177 .attr('x', -halfDevIcon)
178 .attr('width', devIconDim)
179 .attr('height', devIconDim)
180 .style('fill', devGlyphColor.bind(this));
181
Steven Burrows6deb4ce2016-08-26 16:06:23 +0100182 var text = this.el.append('text').text(label)
183 .attr('text-anchor', 'left')
184 .attr('y', '0.3em')
Steven Burrowsbbe3dda2016-09-26 14:41:59 -0700185 .attr('x', halfDevIcon + labelPad + textPad);
Steven Burrows6deb4ce2016-08-26 16:06:23 +0100186
187 return {
188 rect: rect,
Steven Burrowsbbe3dda2016-09-26 14:41:59 -0700189 glythRect: glythRect,
Steven Burrows6deb4ce2016-08-26 16:06:23 +0100190 text: text
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100191 };
Steven Burrows6deb4ce2016-08-26 16:06:23 +0100192 },
Steven Burrowsbbe3dda2016-09-26 14:41:59 -0700193 labelBox: function (dim, labelWidth) {
194 var _textPad = (textPad * 2) - labelPad;
195
196 if (labelWidth === 0) {
197 _textPad = 0;
198 }
199
200 return {
201 x: -dim / 2 - labelPad,
202 y: -dim / 2 - labelPad,
203 width: dim + labelWidth + (labelPad * 2) + _textPad,
204 height: dim + (labelPad * 2)
205 };
206 },
207 iconBox: function (dim, labelWidth) {
Steven Burrows37549ee2016-09-21 14:41:39 +0100208 return {
209 x: -dim / 2,
210 y: -dim / 2,
211 width: dim + labelWidth,
212 height: dim
213 };
214 },
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100215 svgClassName: function () {
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100216 return fn.classNames('node',
217 this.nodeType,
218 this.get('type'),
219 {
220 online: this.get('online')
221 }
222 );
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100223 },
Steven Burrows37549ee2016-09-21 14:41:39 +0100224 update: function () {
225 this.updateLabel();
226 },
227 updateLabel: function () {
228 var node = this.el,
229 label = this.trimLabel(this.label()),
230 labelWidth;
231
232 node.select('text').text(label);
233 labelWidth = label ? this.computeLabelWidth(node) : 0;
234
235 node.select('rect')
236 .transition()
Steven Burrowsbbe3dda2016-09-26 14:41:59 -0700237 .attr(this.labelBox(devIconDim, labelWidth));
Steven Burrows37549ee2016-09-21 14:41:39 +0100238 },
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100239 createNode: function () {
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100240 var node = angular.extend({}, this.attributes);
241
242 // Augment as needed...
243 node.class = this.nodeType;
244 node.svgClass = this.svgClassName();
245 positionNode(node);
246 return node;
Steven Burrowsbbe3dda2016-09-26 14:41:59 -0700247 },
248 onEnter: function (el) {
249 this.el = d3.select(el);
250 this.render();
251 },
252 render: function () {
253 var node = this.el,
254 glyphId = this.icon(this.get('type')),
255 label = this.trimLabel(this.label()),
256 glyph, labelWidth;
257
258 // Label
259 var labelElements = this.addLabelElements(label);
260 labelWidth = label ? this.computeLabelWidth(node) : 0;
261 labelElements.rect.attr(this.labelBox(devIconDim, labelWidth));
262
263 // Icon
264 glyph = is.addDeviceIcon(node, glyphId, devIconDim);
265 glyph.attr(this.iconBox(devIconDim, 0));
266 glyph.style('fill', 'white');
267
268 node.attr('transform', sus.translate(-halfDevIcon, -halfDevIcon));
269
270 if (this.events) {
271 this.setUpEvents();
272 }
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100273 }
274 });
275 }]
276 );
277})();