| /* |
| * Copyright 2015-present Open Networking Foundation |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| import { Injectable } from '@angular/core'; |
| import { GlyphService } from './glyph.service'; |
| import { LogService } from '../../log.service'; |
| import { SvgUtilService } from './svgutil.service'; |
| import * as d3 from 'd3'; |
| |
| const vboxSize = 50; |
| const cornerSize = vboxSize / 10; |
| const viewBox = '0 0 ' + vboxSize + ' ' + vboxSize; |
| |
| export const glyphMapping = new Map<string, string>([ |
| // Maps icon ID to the glyph ID it uses. |
| // NOTE: icon ID maps to a CSS class for styling that icon |
| ['active', 'checkMark'], |
| ['inactive', 'xMark'], |
| |
| ['plus', 'plus'], |
| ['minus', 'minus'], |
| ['play', 'play'], |
| ['stop', 'stop'], |
| |
| ['upload', 'upload'], |
| ['download', 'download'], |
| ['delta', 'delta'], |
| ['nonzero', 'nonzero'], |
| ['close', 'xClose'], |
| |
| ['m_ports', 'm_ports'], |
| |
| ['topo', 'topo'], |
| |
| ['refresh', 'refresh'], |
| ['query', 'query'], |
| ['garbage', 'garbage'], |
| |
| |
| ['upArrow', 'triangleUp'], |
| ['downArrow', 'triangleDown'], |
| |
| ['appInactive', 'unknown'], |
| |
| ['node', 'node'], |
| ['devIcon_SWITCH', 'switch'], |
| ['devIcon_ROADM', 'roadm'], |
| ['devIcon_OTN', 'otn'], |
| |
| ['portIcon_DEFAULT', 'm_ports'], |
| |
| ['meter', 'meterTable'], // TODO: m_meter icon? |
| |
| ['deviceTable', 'switch'], |
| ['flowTable', 'flowTable'], |
| ['portTable', 'portTable'], |
| ['groupTable', 'groupTable'], |
| ['meterTable', 'meterTable'], |
| ['pipeconfTable', 'pipeconfTable'], |
| |
| ['hostIcon_endstation', 'endstation'], |
| ['hostIcon_router', 'router'], |
| ['hostIcon_bgpSpeaker', 'bgpSpeaker'], |
| |
| // navigation menu icons... |
| ['nav_apps', 'bird'], |
| ['nav_settings', 'cog'], |
| ['nav_cluster', 'node'], |
| ['nav_processors', 'allTraffic'], |
| |
| ['nav_topo', 'topo'], |
| ['nav_topo2', 'm_cloud'], |
| ['nav_devs', 'switch'], |
| ['nav_links', 'ports'], |
| ['nav_hosts', 'endstation'], |
| ['nav_intents', 'relatedIntents'], |
| ['nav_tunnels', 'ports'], // TODO: use tunnel glyph, when available |
| ['nav_yang', 'yang'], |
| ]); |
| |
| /** |
| * ONOS GUI -- SVG -- Icon Service |
| */ |
| @Injectable() |
| export class IconService { |
| |
| constructor( |
| private gs: GlyphService, |
| private log: LogService, |
| private sus: SvgUtilService |
| ) { |
| |
| this.log.debug('IconService constructed'); |
| } |
| |
| ensureIconLibDefs() { |
| const body = d3.select('body'); |
| let svg = body.select('svg#IconLibDefs'); |
| if (svg.empty()) { |
| svg = body.append('svg').attr('id', 'IconLibDefs'); |
| svg.append('defs'); |
| } |
| return svg.select('defs'); |
| } |
| |
| /** |
| * Load an icon |
| * |
| * @param div A D3 selection of the '<div>' element into which icon should load |
| * @param glyphId Identifies the glyph to use |
| * @param size The dimension of icon in pixels. Defaults to 20. |
| * @param installGlyph If truthy, will cause the glyph to be added to |
| * well-known defs element. Defaults to false. |
| * @param svgClass The CSS class used to identify the SVG layer. |
| * Defaults to 'embeddedIcon'. |
| */ |
| loadIcon(div, glyphId: string = 'unknown', size: number = 20, installGlyph: boolean = true, svgClass: string = 'embeddedIcon') { |
| const dim = size || 20; |
| const svgCls = svgClass || 'embeddedIcon'; |
| const gid = glyphId || 'unknown'; |
| let g; |
| let svgIcon: any; |
| |
| if (installGlyph) { |
| this.gs.loadDefs(this.ensureIconLibDefs(), [gid], true); |
| } |
| this.log.warn('loadEmbeddedIcon. install done'); |
| |
| svgIcon = div |
| .append('svg') |
| .attr('class', svgCls) |
| .attr('width', dim) |
| .attr('height', dim) |
| .attr('viewBox', viewBox); |
| |
| g = svgIcon.append('g') |
| .attr('class', 'icon'); |
| |
| g.append('rect') |
| .attr('width', vboxSize) |
| .attr('height', vboxSize) |
| .attr('rx', cornerSize); |
| |
| g.append('use') |
| .attr('width', vboxSize) |
| .attr('height', vboxSize) |
| .attr('class', 'glyph') |
| .attr('xlink:href', '#' + gid); |
| } |
| |
| /** |
| * Load an icon by class. |
| * @param div A D3 selection of the <DIV> element into which icon should load |
| * @param iconCls The CSS class used to identify the icon |
| * @param {number} size The dimension of icon in pixels. Defaults to 20. |
| * @param {boolean} installGlyph If truthy, will cause the glyph to be added to |
| * well-known defs element. Defaults to false. |
| * @param svgClass The CSS class used to identify the SVG layer. |
| * Defaults to 'embeddedIcon'. |
| */ |
| loadIconByClass(div, iconCls: string, size: number, installGlyph: boolean, svgClass= 'embeddedIcon') { |
| this.loadIcon(div, glyphMapping.get(iconCls), size, installGlyph, svgClass); |
| div.select('svg g').classed(iconCls, true); |
| } |
| |
| /** |
| * Load an embedded icon. |
| */ |
| loadEmbeddedIcon(div, iconCls: string, size: number) { |
| this.loadIconByClass(div, iconCls, size, true); |
| } |
| |
| /** |
| * Load an icon only to the svg defs collection |
| * |
| * Note: This is added for use with IconComponent, where the icon's |
| * svg element is defined in the component template (and not built |
| * inline using d3 manipulation |
| * |
| * @param iconCls The icon class as a string |
| */ |
| loadIconDef(iconCls: string): void { |
| this.gs.loadDefs(this.ensureIconLibDefs(), [glyphMapping.get(iconCls)], true); |
| this.log.debug('icon definition', iconCls, 'added to defs'); |
| } |
| |
| |
| /** |
| * Add a device icon |
| * |
| * Adds a device glyph to the specified element. |
| * Returns the D3 selection of the glyph (use) element. |
| */ |
| addDeviceIcon(elem, glyphId, iconDim) { |
| const gid = this.gs.glyphDefined(glyphId) ? glyphId : 'query'; |
| return elem.append('use').attr({ |
| 'xlink:href': '#' + gid, |
| width: iconDim, |
| height: iconDim, |
| }); |
| } |
| |
| addHostIcon(elem, radius, glyphId) { |
| const dim = radius * 1.5; |
| const xlate = -dim / 2; |
| const g = elem.append('g') |
| .attr('class', 'svgIcon hostIcon'); |
| |
| g.append('circle').attr('r', radius); |
| |
| g.append('use').attr({ |
| 'xlink:href': '#' + glyphId, |
| width: dim, |
| height: dim, |
| transform: this.sus.translate([xlate], xlate), |
| }); |
| return g; |
| } |
| |
| registerIconMapping(iconId, glyphId) { |
| if (glyphMapping[iconId]) { |
| this.log.warn('Icon with id', iconId, 'already mapped. Ignoring.'); |
| } else { |
| // map icon-->glyph |
| glyphMapping[iconId] = glyphId; |
| // make sure definition is installed |
| this.gs.loadDefs(this.ensureIconLibDefs(), [glyphId], true); |
| } |
| } |
| } |