From c9b7316610ba72f1933fd11c98bb96f16aa7744c Mon Sep 17 00:00:00 2001 From: Simon Hunt Date: Thu, 29 Jan 2015 14:02:15 -0800 Subject: [PATCH] GUI -- Completed Show Summary panel. - added GlyphService.addGlyph(). - added SvgUtilService.translate(). Change-Id: I0bbc51a8f1d9c24b8b4f1377236570070da6f160 --- web/gui/src/main/webapp/app/fw/svg/glyph.css | 34 ++++ web/gui/src/main/webapp/app/fw/svg/glyph.js | 176 ++++++++++-------- web/gui/src/main/webapp/app/fw/svg/svgUtil.js | 10 +- web/gui/src/main/webapp/app/index.html | 1 + .../src/main/webapp/app/view/topo/topo.css | 11 -- .../main/webapp/app/view/topo/topoPanel.js | 18 +- .../webapp/tests/app/fw/svg/glyph-spec.js | 45 ++++- .../webapp/tests/app/fw/svg/svgUtil-spec.js | 12 +- 8 files changed, 207 insertions(+), 100 deletions(-) create mode 100644 web/gui/src/main/webapp/app/fw/svg/glyph.css diff --git a/web/gui/src/main/webapp/app/fw/svg/glyph.css b/web/gui/src/main/webapp/app/fw/svg/glyph.css new file mode 100644 index 0000000000..829106543c --- /dev/null +++ b/web/gui/src/main/webapp/app/fw/svg/glyph.css @@ -0,0 +1,34 @@ +/* + * Copyright 2014,2015 Open Networking Laboratory + * + * 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. + */ + +/* + ONOS GUI -- Glyph Service -- CSS file + */ + +svg .glyph { + stroke: none; + fill-rule: evenodd; +} + +.light svg .glyph, +.dark svg .glyph.overlay { + fill: black; +} + +.dark svg .glyph, +.light svg .glyph.overlay { + fill: white; +} diff --git a/web/gui/src/main/webapp/app/fw/svg/glyph.js b/web/gui/src/main/webapp/app/fw/svg/glyph.js index 07c7d0b2d0..f85b69703b 100644 --- a/web/gui/src/main/webapp/app/fw/svg/glyph.js +++ b/web/gui/src/main/webapp/app/fw/svg/glyph.js @@ -20,9 +20,11 @@ (function () { 'use strict'; - var $log, - fs, - glyphs = d3.map(), + // injected references + var $log, fs, sus; + + // internal state + var glyphs = d3.map(), msgGS = 'GlyphService.'; // ---------------------------------------------------------------------- @@ -133,78 +135,102 @@ // ---------------------------------------------------------------------- + function clear() { + // start with a fresh map + glyphs = d3.map(); + } + + function init() { + clear(); + register(birdViewBox, birdData); + register(glyphViewBox, glyphData); + register(badgeViewBox, badgeData); + } + + function register(viewBox, data, overwrite) { + var dmap = d3.map(data), + dups = [], + ok; + + dmap.forEach(function (key, value) { + if (!overwrite && glyphs.get(key)) { + dups.push(key); + } else { + glyphs.set(key, {id: key, vb: viewBox, d: value}); + } + }); + ok = (dups.length == 0); + if (!ok) { + dups.forEach(function (id) { + $log.warn(msgGS + 'register(): ID collision: "'+id+'"'); + }); + } + return ok; + } + + function ids() { + return glyphs.keys(); + } + + function glyph(id) { + return glyphs.get(id); + } + + // Note: defs should be a D3 selection of a single element + function loadDefs(defs, glyphIds, noClear) { + var list = fs.isA(glyphIds) || ids(), + clearCache = !noClear; + + if (clearCache) { + // remove all existing content + defs.html(null); + } + + // load up the requested glyphs + list.forEach(function (id) { + var g = glyph(id); + if (g) { + if (noClear) { + // quick exit if symbol is already present + if (defs.select('symbol#' + g.id).size() > 0) { + return; + } + } + defs.append('symbol') + .attr({ id: g.id, viewBox: g.vb }) + .append('path').attr('d', g.d); + } + }); + } + + function addGlyph(elem, glyphId, size, overlay, trans) { + var sz = size || 40, + ovr = !!overlay, + xns = fs.isA(trans), + atr = { + width: sz, + height: sz, + 'class': 'glyph', + 'xlink:href': '#' + glyphId + }; + + if (xns) { + atr.transform = sus.translate(trans); + } + elem.append('use').attr(atr).classed('overlay', ovr); + + } + + // ---------------------------------------------------------------------- + angular.module('onosSvg') - .factory('GlyphService', ['$log', 'FnService', function (_$log_, _fs_) { + .factory('GlyphService', + ['$log', 'FnService', 'SvgUtilService', + + function (_$log_, _fs_, _sus_) { $log = _$log_; fs = _fs_; - - function clear() { - // start with a fresh map - glyphs = d3.map(); - } - - function init() { - clear(); - register(birdViewBox, birdData); - register(glyphViewBox, glyphData); - register(badgeViewBox, badgeData); - } - - function register(viewBox, data, overwrite) { - var dmap = d3.map(data), - dups = [], - ok; - - dmap.forEach(function (key, value) { - if (!overwrite && glyphs.get(key)) { - dups.push(key); - } else { - glyphs.set(key, {id: key, vb: viewBox, d: value}); - } - }); - ok = (dups.length == 0); - if (!ok) { - dups.forEach(function (id) { - $log.warn(msgGS + 'register(): ID collision: "'+id+'"'); - }); - } - return ok; - } - - function ids() { - return glyphs.keys(); - } - - function glyph(id) { - return glyphs.get(id); - } - - // Note: defs should be a D3 selection of a single element - function loadDefs(defs, glyphIds, noClear) { - var list = fs.isA(glyphIds) || ids(), - clearCache = !noClear; - - if (clearCache) { - // remove all existing content - defs.html(null); - } - - // load up the requested glyphs - list.forEach(function (id) { - var g = glyph(id); - if (g) { - if (noClear) { - // quick exit if symbol is already present - if (defs.select('symbol#' + g.id).size() > 0) { - return; - } - } - defs.append('symbol') - .attr({ id: g.id, viewBox: g.vb }) - .append('path').attr('d', g.d); - } - }); - } + sus = _sus_; return { clear: clear, @@ -212,8 +238,10 @@ register: register, ids: ids, glyph: glyph, - loadDefs: loadDefs + loadDefs: loadDefs, + addGlyph: addGlyph }; - }]); + }] + ); }()); diff --git a/web/gui/src/main/webapp/app/fw/svg/svgUtil.js b/web/gui/src/main/webapp/app/fw/svg/svgUtil.js index a0e0fff716..1f5adc0778 100644 --- a/web/gui/src/main/webapp/app/fw/svg/svgUtil.js +++ b/web/gui/src/main/webapp/app/fw/svg/svgUtil.js @@ -136,10 +136,18 @@ $log.warn('SvgUtilService: cat7 -- To Be Implemented'); } + function translate(x, y) { + if (fs.isA(x) && x.length === 2 && !y) { + return 'translate(' + x[0] + ',' + x[1] + ')'; + } + return 'translate(' + x + ',' + y + ')'; + } + return { createDragBehavior: createDragBehavior, loadGlow: loadGlow, - cat7: cat7 + cat7: cat7, + translate: translate }; }]); }()); diff --git a/web/gui/src/main/webapp/app/index.html b/web/gui/src/main/webapp/app/index.html index 92b1712f3d..738c9679e5 100644 --- a/web/gui/src/main/webapp/app/index.html +++ b/web/gui/src/main/webapp/app/index.html @@ -66,6 +66,7 @@ + diff --git a/web/gui/src/main/webapp/app/view/topo/topo.css b/web/gui/src/main/webapp/app/view/topo/topo.css index 785df8c3db..d7e7ec3bae 100644 --- a/web/gui/src/main/webapp/app/view/topo/topo.css +++ b/web/gui/src/main/webapp/app/view/topo/topo.css @@ -56,17 +56,6 @@ height: 42px; } -#topo-p-summary svg .glyphIcon { - stroke: none; - fill-rule: evenodd; -} -.light #topo-p-summary svg .glyphIcon { - fill: black; -} -.dark #topo-p-summary svg .glyphIcon { - fill: #ddd; -} - #topo-p-summary h2 { position: absolute; margin: 0 4px; diff --git a/web/gui/src/main/webapp/app/view/topo/topoPanel.js b/web/gui/src/main/webapp/app/view/topo/topoPanel.js index d05dc3eb35..efc195ffb4 100644 --- a/web/gui/src/main/webapp/app/view/topo/topoPanel.js +++ b/web/gui/src/main/webapp/app/view/topo/topoPanel.js @@ -23,7 +23,7 @@ 'use strict'; // injected refs - var $log, ps; + var $log, ps, gs; // constants var idSum = 'topo-p-summary', @@ -70,18 +70,15 @@ function populateSummary(data) { summaryPanel.empty(); - var svg = summaryPanel.append('svg').attr({ - width: 40, - height: 40 - }).style('background-color', 'goldenrod'), - iid = '#' + (data.type || 'unknown'); + var svg = summaryPanel.append('svg'); //.style('background-color', 'goldenrod'), + //iid = '#' + (data.type || 'unknown'); var title = summaryPanel.append('h2'), table = summaryPanel.append('table'), tbody = table.append('tbody'); - // append glyph iid to SVG // black fill - // append glyph bird to SVG // white fill + gs.addGlyph(svg, 'node', 40); + gs.addGlyph(svg, 'bird', 24, true, [8,12]); title.text(data.id); @@ -103,11 +100,12 @@ angular.module('ovTopo') .factory('TopoPanelService', - ['$log', 'PanelService', + ['$log', 'PanelService', 'GlyphService', - function (_$log_, _ps_) { + function (_$log_, _ps_, _gs_) { $log = _$log_; ps = _ps_; + gs = _gs_; function initPanels() { summaryPanel = ps.createPanel(idSum, panelOpts); diff --git a/web/gui/src/main/webapp/tests/app/fw/svg/glyph-spec.js b/web/gui/src/main/webapp/tests/app/fw/svg/glyph-spec.js index 587d617b63..b72de5a066 100644 --- a/web/gui/src/main/webapp/tests/app/fw/svg/glyph-spec.js +++ b/web/gui/src/main/webapp/tests/app/fw/svg/glyph-spec.js @@ -18,7 +18,7 @@ ONOS GUI -- SVG -- Glyph Service - Unit Tests */ describe('factory: fw/svg/glyph.js', function() { - var $log, fs, gs, d3Elem; + var $log, fs, gs, d3Elem, svg; var numBaseGlyphs = 13, vbBird = '352 224 113 112', @@ -47,13 +47,16 @@ describe('factory: fw/svg/glyph.js', function() { beforeEach(module('onosUtil', 'onosSvg')); beforeEach(inject(function (_$log_, FnService, GlyphService) { + var body = d3.select('body'); $log = _$log_; fs = FnService; gs = GlyphService; - d3Elem = d3.select('body').append('defs').attr('id', 'myDefs'); + d3Elem = body.append('defs').attr('id', 'myDefs'); + svg = body.append('svg').attr('id', 'mySvg'); })); afterEach(function () { + d3.select('#mySvg').remove(); d3.select('#myDefs').remove(); gs.clear(); }); @@ -64,7 +67,7 @@ describe('factory: fw/svg/glyph.js', function() { it('should define api functions', function () { expect(fs.areFunctions(gs, [ - 'clear', 'init', 'register', 'ids', 'glyph', 'loadDefs' + 'clear', 'init', 'register', 'ids', 'glyph', 'loadDefs', 'addGlyph' ])).toBeTruthy(); }); @@ -246,4 +249,40 @@ describe('factory: fw/svg/glyph.js', function() { verifyLoadedInDom('chain', vbGlyph); verifyLoadedInDom('node', vbGlyph); }); + + it('should add a glyph with default size', function () { + gs.init(); + gs.addGlyph(svg, 'crown'); + var what = svg.selectAll('use'); + expect(what.size()).toEqual(1); + expect(what.attr('width')).toEqual('40'); + expect(what.attr('height')).toEqual('40'); + expect(what.attr('xlink:href')).toEqual('#crown'); + expect(what.classed('glyph')).toBeTruthy(); + expect(what.classed('overlay')).toBeFalsy(); + }); + + it('should add a glyph with given size', function () { + gs.init(); + gs.addGlyph(svg, 'crown', 37); + var what = svg.selectAll('use'); + expect(what.size()).toEqual(1); + expect(what.attr('width')).toEqual('37'); + expect(what.attr('height')).toEqual('37'); + expect(what.attr('xlink:href')).toEqual('#crown'); + expect(what.classed('glyph')).toBeTruthy(); + expect(what.classed('overlay')).toBeFalsy(); + }); + + it('should add a glyph marked as overlay', function () { + gs.init(); + gs.addGlyph(svg, 'crown', 20, true); + var what = svg.selectAll('use'); + expect(what.size()).toEqual(1); + expect(what.attr('width')).toEqual('20'); + expect(what.attr('height')).toEqual('20'); + expect(what.attr('xlink:href')).toEqual('#crown'); + expect(what.classed('glyph')).toBeTruthy(); + expect(what.classed('overlay')).toBeTruthy(); + }); }); diff --git a/web/gui/src/main/webapp/tests/app/fw/svg/svgUtil-spec.js b/web/gui/src/main/webapp/tests/app/fw/svg/svgUtil-spec.js index 7caa83e0d7..964320b611 100644 --- a/web/gui/src/main/webapp/tests/app/fw/svg/svgUtil-spec.js +++ b/web/gui/src/main/webapp/tests/app/fw/svg/svgUtil-spec.js @@ -39,8 +39,18 @@ describe('factory: fw/svg/svgUtil.js', function() { it('should define api functions', function () { expect(fs.areFunctions(sus, [ - 'createDragBehavior', 'loadGlow', 'cat7' + 'createDragBehavior', 'loadGlow', 'cat7', 'translate' ])).toBeTruthy(); }); + // TODO: add unit tests for drag behavior etc. + + it('should translate from two args', function () { + expect(sus.translate(1,2)).toEqual('translate(1,2)'); + }); + + it('should translate from an array', function () { + expect(sus.translate([3,4])).toEqual('translate(3,4)'); + }); + });