blob: 51e4f6b9d985c0ecf8996a9680b2b2fefa086ce9 [file] [log] [blame]
Sean Condon83fc39f2018-04-19 18:56:13 +01001/*
Sean Condon5ca00262018-09-06 17:55:25 +01002 * Copyright 2018-present Open Networking Foundation
Sean Condon83fc39f2018-04-19 18:56:13 +01003 *
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 */
16import { Injectable } from '@angular/core';
17import { GlyphService } from './glyph.service';
Sean Condon5ca00262018-09-06 17:55:25 +010018import { LogService } from '../log.service';
Sean Condon83fc39f2018-04-19 18:56:13 +010019import { SvgUtilService } from './svgutil.service';
20import * as d3 from 'd3';
21
22const vboxSize = 50;
23const cornerSize = vboxSize / 10;
24const viewBox = '0 0 ' + vboxSize + ' ' + vboxSize;
25
26export const glyphMapping = new Map<string, string>([
27 // Maps icon ID to the glyph ID it uses.
28 // NOTE: icon ID maps to a CSS class for styling that icon
29 ['active', 'checkMark'],
Sean Condon49e15be2018-05-16 16:58:29 +010030 ['inactive', 'xMark'],
Sean Condon83fc39f2018-04-19 18:56:13 +010031
Sean Condon49e15be2018-05-16 16:58:29 +010032 ['plus', 'plus'],
33 ['minus', 'minus'],
34 ['play', 'play'],
35 ['stop', 'stop'],
Sean Condon83fc39f2018-04-19 18:56:13 +010036
Sean Condon49e15be2018-05-16 16:58:29 +010037 ['upload', 'upload'],
38 ['download', 'download'],
39 ['delta', 'delta'],
40 ['nonzero', 'nonzero'],
41 ['close', 'xClose'],
Sean Condon83fc39f2018-04-19 18:56:13 +010042
Sean Condon91481822019-01-01 13:56:14 +000043 ['m_cloud', 'm_cloud'],
44 ['m_map', 'm_map'],
45 ['m_selectMap', 'm_selectMap'],
46 ['thatsNoMoon', 'thatsNoMoon'],
Bhavesh72ead492018-07-19 16:29:18 +053047 ['m_ports', 'm_ports'],
Sean Condon0c577f62018-11-18 22:40:05 +000048 ['m_switch', 'm_switch'],
Sean Condon021f0fa2018-12-06 23:31:11 -080049 ['m_roadm', 'm_roadm'],
50 ['m_router', 'm_router'],
Sean Condon91481822019-01-01 13:56:14 +000051 ['m_uiAttached', 'm_uiAttached'],
Sean Condon021f0fa2018-12-06 23:31:11 -080052 ['m_endstation', 'm_endstation'],
Sean Condon91481822019-01-01 13:56:14 +000053 ['m_summary', 'm_summary'],
54 ['m_details', 'm_details'],
55 ['m_oblique', 'm_oblique'],
56 ['m_filters', 'm_filters'],
57 ['m_cycleLabels', 'm_cycleLabels'],
Sean Condon71910542019-02-16 18:16:42 +000058 ['m_cycleGridDisplay', 'm_cycleGridDisplay'],
Sean Condon91481822019-01-01 13:56:14 +000059 ['m_prev', 'm_prev'],
60 ['m_next', 'm_next'],
61 ['m_flows', 'm_flows'],
62 ['m_allTraffic', 'm_allTraffic'],
63 ['m_xMark', 'm_xMark'],
64 ['m_resetZoom', 'm_resetZoom'],
65 ['m_eqMaster', 'm_eqMaster'],
66 ['m_unknown', 'm_unknown'],
67 ['m_controller', 'm_controller'],
68 ['m_eqMaster', 'm_eqMaster'],
69 ['m_virtual', 'm_virtual'],
70 ['m_other', 'm_other'],
71 ['m_bgpSpeaker', 'm_bgpSpeaker'],
72 ['m_otn', 'm_otn'],
73 ['m_roadm_otn', 'm_roadm_otn'],
74 ['m_fiberSwitch', 'm_fiberSwitch'],
75 ['m_microwave', 'm_microwave'],
76 ['m_relatedIntents', 'm_relatedIntents'],
77 ['m_intentTraffic', 'm_intentTraffic'],
78 ['m_firewall', 'm_firewall'],
79 ['m_balancer', 'm_balancer'],
80 ['m_ips', 'm_ips'],
81 ['m_ids', 'm_ids'],
82 ['m_olt', 'm_olt'],
83 ['m_onu', 'm_onu'],
84 ['m_swap', 'm_swap'],
85 ['m_shortestGeoPath', 'm_shortestGeoPath'],
86 ['m_source', 'm_source'],
87 ['m_destination', 'm_destination'],
88 ['m_topo', 'm_topo'],
89 ['m_shortestPath', 'm_shortestPath'],
90 ['m_disjointPaths', 'm_disjointPaths'],
91 ['m_region', 'm_region'],
Bhavesh72ead492018-07-19 16:29:18 +053092
Sean Condon49e15be2018-05-16 16:58:29 +010093 ['topo', 'topo'],
Sean Condonf4f54a12018-10-10 23:25:46 +010094 ['bird', 'bird'],
Sean Condon83fc39f2018-04-19 18:56:13 +010095
Sean Condon49e15be2018-05-16 16:58:29 +010096 ['refresh', 'refresh'],
97 ['query', 'query'],
98 ['garbage', 'garbage'],
Sean Condon83fc39f2018-04-19 18:56:13 +010099
100
Sean Condon49e15be2018-05-16 16:58:29 +0100101 ['upArrow', 'triangleUp'],
102 ['downArrow', 'triangleDown'],
Sean Condon91481822019-01-01 13:56:14 +0000103 ['triangleLeft', 'triangleLeft'],
104 ['triangleRight', 'triangleRight'],
Sean Condon83fc39f2018-04-19 18:56:13 +0100105
Sean Condon49e15be2018-05-16 16:58:29 +0100106 ['appInactive', 'unknown'],
Sean Condonaa4366d2018-11-02 14:29:01 +0000107 ['uiAttached', 'uiAttached'],
Sean Condon83fc39f2018-04-19 18:56:13 +0100108
Sean Condon49e15be2018-05-16 16:58:29 +0100109 ['node', 'node'],
110 ['devIcon_SWITCH', 'switch'],
111 ['devIcon_ROADM', 'roadm'],
112 ['devIcon_OTN', 'otn'],
Sean Condon83fc39f2018-04-19 18:56:13 +0100113
Sean Condon49e15be2018-05-16 16:58:29 +0100114 ['portIcon_DEFAULT', 'm_ports'],
Sean Condon83fc39f2018-04-19 18:56:13 +0100115
Sean Condon49e15be2018-05-16 16:58:29 +0100116 ['meter', 'meterTable'], // TODO: m_meter icon?
Sean Condon83fc39f2018-04-19 18:56:13 +0100117
Sean Condon49e15be2018-05-16 16:58:29 +0100118 ['deviceTable', 'switch'],
119 ['flowTable', 'flowTable'],
120 ['portTable', 'portTable'],
121 ['groupTable', 'groupTable'],
122 ['meterTable', 'meterTable'],
123 ['pipeconfTable', 'pipeconfTable'],
Sean Condon83fc39f2018-04-19 18:56:13 +0100124
Sean Condon49e15be2018-05-16 16:58:29 +0100125 ['hostIcon_endstation', 'endstation'],
126 ['hostIcon_router', 'router'],
127 ['hostIcon_bgpSpeaker', 'bgpSpeaker'],
Sean Condon83fc39f2018-04-19 18:56:13 +0100128
129 // navigation menu icons...
Sean Condon49e15be2018-05-16 16:58:29 +0100130 ['nav_apps', 'bird'],
131 ['nav_settings', 'cog'],
132 ['nav_cluster', 'node'],
133 ['nav_processors', 'allTraffic'],
Bhavesh Kumard0b8bae2018-07-31 16:56:43 +0530134 ['nav_partitions', 'unknown'],
Sean Condon83fc39f2018-04-19 18:56:13 +0100135
Sean Condon49e15be2018-05-16 16:58:29 +0100136 ['nav_topo', 'topo'],
137 ['nav_topo2', 'm_cloud'],
138 ['nav_devs', 'switch'],
139 ['nav_links', 'ports'],
140 ['nav_hosts', 'endstation'],
141 ['nav_intents', 'relatedIntents'],
142 ['nav_tunnels', 'ports'], // TODO: use tunnel glyph, when available
143 ['nav_yang', 'yang'],
Sean Condon83fc39f2018-04-19 18:56:13 +0100144]);
145
146/**
147 * ONOS GUI -- SVG -- Icon Service
148 */
Sean Condon5ca00262018-09-06 17:55:25 +0100149@Injectable({
150 providedIn: 'root',
151})
Sean Condon83fc39f2018-04-19 18:56:13 +0100152export class IconService {
153
154 constructor(
155 private gs: GlyphService,
156 private log: LogService,
157 private sus: SvgUtilService
158 ) {
159
160 this.log.debug('IconService constructed');
161 }
162
163 ensureIconLibDefs() {
Sean Condon49e15be2018-05-16 16:58:29 +0100164 const body = d3.select('body');
Sean Condon83fc39f2018-04-19 18:56:13 +0100165 let svg = body.select('svg#IconLibDefs');
166 if (svg.empty()) {
167 svg = body.append('svg').attr('id', 'IconLibDefs');
168 svg.append('defs');
169 }
170 return svg.select('defs');
171 }
172
173 /**
174 * Load an icon
175 *
176 * @param div A D3 selection of the '&lt;div&gt;' element into which icon should load
177 * @param glyphId Identifies the glyph to use
178 * @param size The dimension of icon in pixels. Defaults to 20.
179 * @param installGlyph If truthy, will cause the glyph to be added to
180 * well-known defs element. Defaults to false.
181 * @param svgClass The CSS class used to identify the SVG layer.
182 * Defaults to 'embeddedIcon'.
183 */
184 loadIcon(div, glyphId: string = 'unknown', size: number = 20, installGlyph: boolean = true, svgClass: string = 'embeddedIcon') {
Sean Condon49e15be2018-05-16 16:58:29 +0100185 const dim = size || 20;
186 const svgCls = svgClass || 'embeddedIcon';
187 const gid = glyphId || 'unknown';
Sean Condon83fc39f2018-04-19 18:56:13 +0100188 let g;
189 let svgIcon: any;
190
191 if (installGlyph) {
192 this.gs.loadDefs(this.ensureIconLibDefs(), [gid], true);
193 }
194 this.log.warn('loadEmbeddedIcon. install done');
195
196 svgIcon = div
197 .append('svg')
198 .attr('class', svgCls)
199 .attr('width', dim)
200 .attr('height', dim)
201 .attr('viewBox', viewBox);
202
203 g = svgIcon.append('g')
204 .attr('class', 'icon');
205
206 g.append('rect')
207 .attr('width', vboxSize)
208 .attr('height', vboxSize)
209 .attr('rx', cornerSize);
210
211 g.append('use')
212 .attr('width', vboxSize)
213 .attr('height', vboxSize)
214 .attr('class', 'glyph')
215 .attr('xlink:href', '#' + gid);
216 }
217
218 /**
219 * Load an icon by class.
220 * @param div A D3 selection of the <DIV> element into which icon should load
221 * @param iconCls The CSS class used to identify the icon
Sean Condon5ca00262018-09-06 17:55:25 +0100222 * @param size The dimension of icon in pixels. Defaults to 20.
223 * @param installGlyph If truthy, will cause the glyph to be added to
Sean Condon83fc39f2018-04-19 18:56:13 +0100224 * well-known defs element. Defaults to false.
225 * @param svgClass The CSS class used to identify the SVG layer.
226 * Defaults to 'embeddedIcon'.
227 */
Sean Condon49e15be2018-05-16 16:58:29 +0100228 loadIconByClass(div, iconCls: string, size: number, installGlyph: boolean, svgClass= 'embeddedIcon') {
Sean Condon83fc39f2018-04-19 18:56:13 +0100229 this.loadIcon(div, glyphMapping.get(iconCls), size, installGlyph, svgClass);
230 div.select('svg g').classed(iconCls, true);
231 }
232
233 /**
234 * Load an embedded icon.
235 */
236 loadEmbeddedIcon(div, iconCls: string, size: number) {
237 this.loadIconByClass(div, iconCls, size, true);
238 }
239
240 /**
241 * Load an icon only to the svg defs collection
242 *
243 * Note: This is added for use with IconComponent, where the icon's
244 * svg element is defined in the component template (and not built
245 * inline using d3 manipulation
246 *
247 * @param iconCls The icon class as a string
248 */
249 loadIconDef(iconCls: string): void {
Sean Condon59d31372019-02-02 20:07:00 +0000250 let glyphName: string = glyphMapping.get(iconCls);
251 if (!glyphName) {
252 glyphName = iconCls;
253 }
254 this.gs.loadDefs(this.ensureIconLibDefs(), [glyphName], true);
Sean Condon28ecc5f2018-06-25 12:50:16 +0100255 this.log.debug('icon definition', iconCls, 'added to defs');
Sean Condon83fc39f2018-04-19 18:56:13 +0100256 }
257
258
259 /**
260 * Add a device icon
261 *
262 * Adds a device glyph to the specified element.
263 * Returns the D3 selection of the glyph (use) element.
264 */
265 addDeviceIcon(elem, glyphId, iconDim) {
Sean Condon49e15be2018-05-16 16:58:29 +0100266 const gid = this.gs.glyphDefined(glyphId) ? glyphId : 'query';
Sean Condon83fc39f2018-04-19 18:56:13 +0100267 return elem.append('use').attr({
268 'xlink:href': '#' + gid,
269 width: iconDim,
270 height: iconDim,
271 });
272 }
273
274 addHostIcon(elem, radius, glyphId) {
Sean Condon49e15be2018-05-16 16:58:29 +0100275 const dim = radius * 1.5;
276 const xlate = -dim / 2;
277 const g = elem.append('g')
Sean Condon83fc39f2018-04-19 18:56:13 +0100278 .attr('class', 'svgIcon hostIcon');
279
280 g.append('circle').attr('r', radius);
281
282 g.append('use').attr({
283 'xlink:href': '#' + glyphId,
284 width: dim,
285 height: dim,
Sean Condonfd6d11b2018-06-02 20:29:49 +0100286 transform: this.sus.translate([xlate], xlate),
Sean Condon83fc39f2018-04-19 18:56:13 +0100287 });
288 return g;
289 }
290
291 registerIconMapping(iconId, glyphId) {
292 if (glyphMapping[iconId]) {
293 this.log.warn('Icon with id', iconId, 'already mapped. Ignoring.');
294 } else {
295 // map icon-->glyph
296 glyphMapping[iconId] = glyphId;
297 // make sure definition is installed
298 this.gs.loadDefs(this.ensureIconLibDefs(), [glyphId], true);
299 }
300 }
301}