blob: 6a900fbc591f1a7febfbe1f1a00218510859dd56 [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
Bhavesh72ead492018-07-19 16:29:18 +053043 ['m_ports', 'm_ports'],
Sean Condon0c577f62018-11-18 22:40:05 +000044 ['m_switch', 'm_switch'],
Bhavesh72ead492018-07-19 16:29:18 +053045
Sean Condon49e15be2018-05-16 16:58:29 +010046 ['topo', 'topo'],
Sean Condonf4f54a12018-10-10 23:25:46 +010047 ['bird', 'bird'],
Sean Condon83fc39f2018-04-19 18:56:13 +010048
Sean Condon49e15be2018-05-16 16:58:29 +010049 ['refresh', 'refresh'],
50 ['query', 'query'],
51 ['garbage', 'garbage'],
Sean Condon83fc39f2018-04-19 18:56:13 +010052
53
Sean Condon49e15be2018-05-16 16:58:29 +010054 ['upArrow', 'triangleUp'],
55 ['downArrow', 'triangleDown'],
Sean Condon83fc39f2018-04-19 18:56:13 +010056
Sean Condon49e15be2018-05-16 16:58:29 +010057 ['appInactive', 'unknown'],
Sean Condonaa4366d2018-11-02 14:29:01 +000058 ['uiAttached', 'uiAttached'],
Sean Condon83fc39f2018-04-19 18:56:13 +010059
Sean Condon49e15be2018-05-16 16:58:29 +010060 ['node', 'node'],
61 ['devIcon_SWITCH', 'switch'],
62 ['devIcon_ROADM', 'roadm'],
63 ['devIcon_OTN', 'otn'],
Sean Condon83fc39f2018-04-19 18:56:13 +010064
Sean Condon49e15be2018-05-16 16:58:29 +010065 ['portIcon_DEFAULT', 'm_ports'],
Sean Condon83fc39f2018-04-19 18:56:13 +010066
Sean Condon49e15be2018-05-16 16:58:29 +010067 ['meter', 'meterTable'], // TODO: m_meter icon?
Sean Condon83fc39f2018-04-19 18:56:13 +010068
Sean Condon49e15be2018-05-16 16:58:29 +010069 ['deviceTable', 'switch'],
70 ['flowTable', 'flowTable'],
71 ['portTable', 'portTable'],
72 ['groupTable', 'groupTable'],
73 ['meterTable', 'meterTable'],
74 ['pipeconfTable', 'pipeconfTable'],
Sean Condon83fc39f2018-04-19 18:56:13 +010075
Sean Condon49e15be2018-05-16 16:58:29 +010076 ['hostIcon_endstation', 'endstation'],
77 ['hostIcon_router', 'router'],
78 ['hostIcon_bgpSpeaker', 'bgpSpeaker'],
Sean Condon83fc39f2018-04-19 18:56:13 +010079
80 // navigation menu icons...
Sean Condon49e15be2018-05-16 16:58:29 +010081 ['nav_apps', 'bird'],
82 ['nav_settings', 'cog'],
83 ['nav_cluster', 'node'],
84 ['nav_processors', 'allTraffic'],
Bhavesh Kumard0b8bae2018-07-31 16:56:43 +053085 ['nav_partitions', 'unknown'],
Sean Condon83fc39f2018-04-19 18:56:13 +010086
Sean Condon49e15be2018-05-16 16:58:29 +010087 ['nav_topo', 'topo'],
88 ['nav_topo2', 'm_cloud'],
89 ['nav_devs', 'switch'],
90 ['nav_links', 'ports'],
91 ['nav_hosts', 'endstation'],
92 ['nav_intents', 'relatedIntents'],
93 ['nav_tunnels', 'ports'], // TODO: use tunnel glyph, when available
94 ['nav_yang', 'yang'],
Sean Condon83fc39f2018-04-19 18:56:13 +010095]);
96
97/**
98 * ONOS GUI -- SVG -- Icon Service
99 */
Sean Condon5ca00262018-09-06 17:55:25 +0100100@Injectable({
101 providedIn: 'root',
102})
Sean Condon83fc39f2018-04-19 18:56:13 +0100103export class IconService {
104
105 constructor(
106 private gs: GlyphService,
107 private log: LogService,
108 private sus: SvgUtilService
109 ) {
110
111 this.log.debug('IconService constructed');
112 }
113
114 ensureIconLibDefs() {
Sean Condon49e15be2018-05-16 16:58:29 +0100115 const body = d3.select('body');
Sean Condon83fc39f2018-04-19 18:56:13 +0100116 let svg = body.select('svg#IconLibDefs');
117 if (svg.empty()) {
118 svg = body.append('svg').attr('id', 'IconLibDefs');
119 svg.append('defs');
120 }
121 return svg.select('defs');
122 }
123
124 /**
125 * Load an icon
126 *
127 * @param div A D3 selection of the '&lt;div&gt;' element into which icon should load
128 * @param glyphId Identifies the glyph to use
129 * @param size The dimension of icon in pixels. Defaults to 20.
130 * @param installGlyph If truthy, will cause the glyph to be added to
131 * well-known defs element. Defaults to false.
132 * @param svgClass The CSS class used to identify the SVG layer.
133 * Defaults to 'embeddedIcon'.
134 */
135 loadIcon(div, glyphId: string = 'unknown', size: number = 20, installGlyph: boolean = true, svgClass: string = 'embeddedIcon') {
Sean Condon49e15be2018-05-16 16:58:29 +0100136 const dim = size || 20;
137 const svgCls = svgClass || 'embeddedIcon';
138 const gid = glyphId || 'unknown';
Sean Condon83fc39f2018-04-19 18:56:13 +0100139 let g;
140 let svgIcon: any;
141
142 if (installGlyph) {
143 this.gs.loadDefs(this.ensureIconLibDefs(), [gid], true);
144 }
145 this.log.warn('loadEmbeddedIcon. install done');
146
147 svgIcon = div
148 .append('svg')
149 .attr('class', svgCls)
150 .attr('width', dim)
151 .attr('height', dim)
152 .attr('viewBox', viewBox);
153
154 g = svgIcon.append('g')
155 .attr('class', 'icon');
156
157 g.append('rect')
158 .attr('width', vboxSize)
159 .attr('height', vboxSize)
160 .attr('rx', cornerSize);
161
162 g.append('use')
163 .attr('width', vboxSize)
164 .attr('height', vboxSize)
165 .attr('class', 'glyph')
166 .attr('xlink:href', '#' + gid);
167 }
168
169 /**
170 * Load an icon by class.
171 * @param div A D3 selection of the <DIV> element into which icon should load
172 * @param iconCls The CSS class used to identify the icon
Sean Condon5ca00262018-09-06 17:55:25 +0100173 * @param size The dimension of icon in pixels. Defaults to 20.
174 * @param installGlyph If truthy, will cause the glyph to be added to
Sean Condon83fc39f2018-04-19 18:56:13 +0100175 * well-known defs element. Defaults to false.
176 * @param svgClass The CSS class used to identify the SVG layer.
177 * Defaults to 'embeddedIcon'.
178 */
Sean Condon49e15be2018-05-16 16:58:29 +0100179 loadIconByClass(div, iconCls: string, size: number, installGlyph: boolean, svgClass= 'embeddedIcon') {
Sean Condon83fc39f2018-04-19 18:56:13 +0100180 this.loadIcon(div, glyphMapping.get(iconCls), size, installGlyph, svgClass);
181 div.select('svg g').classed(iconCls, true);
182 }
183
184 /**
185 * Load an embedded icon.
186 */
187 loadEmbeddedIcon(div, iconCls: string, size: number) {
188 this.loadIconByClass(div, iconCls, size, true);
189 }
190
191 /**
192 * Load an icon only to the svg defs collection
193 *
194 * Note: This is added for use with IconComponent, where the icon's
195 * svg element is defined in the component template (and not built
196 * inline using d3 manipulation
197 *
198 * @param iconCls The icon class as a string
199 */
200 loadIconDef(iconCls: string): void {
201 this.gs.loadDefs(this.ensureIconLibDefs(), [glyphMapping.get(iconCls)], true);
Sean Condon28ecc5f2018-06-25 12:50:16 +0100202 this.log.debug('icon definition', iconCls, 'added to defs');
Sean Condon83fc39f2018-04-19 18:56:13 +0100203 }
204
205
206 /**
207 * Add a device icon
208 *
209 * Adds a device glyph to the specified element.
210 * Returns the D3 selection of the glyph (use) element.
211 */
212 addDeviceIcon(elem, glyphId, iconDim) {
Sean Condon49e15be2018-05-16 16:58:29 +0100213 const gid = this.gs.glyphDefined(glyphId) ? glyphId : 'query';
Sean Condon83fc39f2018-04-19 18:56:13 +0100214 return elem.append('use').attr({
215 'xlink:href': '#' + gid,
216 width: iconDim,
217 height: iconDim,
218 });
219 }
220
221 addHostIcon(elem, radius, glyphId) {
Sean Condon49e15be2018-05-16 16:58:29 +0100222 const dim = radius * 1.5;
223 const xlate = -dim / 2;
224 const g = elem.append('g')
Sean Condon83fc39f2018-04-19 18:56:13 +0100225 .attr('class', 'svgIcon hostIcon');
226
227 g.append('circle').attr('r', radius);
228
229 g.append('use').attr({
230 'xlink:href': '#' + glyphId,
231 width: dim,
232 height: dim,
Sean Condonfd6d11b2018-06-02 20:29:49 +0100233 transform: this.sus.translate([xlate], xlate),
Sean Condon83fc39f2018-04-19 18:56:13 +0100234 });
235 return g;
236 }
237
238 registerIconMapping(iconId, glyphId) {
239 if (glyphMapping[iconId]) {
240 this.log.warn('Icon with id', iconId, 'already mapped. Ignoring.');
241 } else {
242 // map icon-->glyph
243 glyphMapping[iconId] = glyphId;
244 // make sure definition is installed
245 this.gs.loadDefs(this.ensureIconLibDefs(), [glyphId], true);
246 }
247 }
248}