GUI -- Revamp of the Glyph Service to allow for custom viewboxes to be defined for registered glyphs/sprites.
- Also, initial sketch for externally injected sprite definition and placement.
- Added 'cloud' sprite data.
Change-Id: I1c38d50212a6d67e00e9b7c15427f6e0af40b539
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 ba5c08a..0612427 100644
--- a/web/gui/src/main/webapp/app/fw/svg/glyph.js
+++ b/web/gui/src/main/webapp/app/fw/svg/glyph.js
@@ -24,15 +24,13 @@
var $log, fs, sus;
// internal state
- var glyphs = d3.map(),
- msgGS = 'GlyphService.';
+ var glyphs = d3.map();
// ----------------------------------------------------------------------
// Base set of Glyphs...
- var birdViewBox = '352 224 113 112',
-
- birdData = {
+ var birdData = {
+ _bird: "352 224 113 112",
bird: "M427.7,300.4 c-6.9,0.6-13.1,5-19.2,7.1c-18.1,6.2-33.9," +
"9.1-56.5,4.7c24.6,17.2,36.6,13,63.7,0.1c-0.5,0.6-0.7,1.3-1.3," +
"1.9c1.4-0.4,2.4-1.7,3.4-2.2c-0.4,0.7-0.9,1.5-1.4,1.9c2.2-0.6," +
@@ -47,9 +45,9 @@
"C429.9,285.5,426.7,293.2,427.7,300.4z"
},
- glyphViewBox = '0 0 110 110',
+ glyphDataSet = {
+ _viewbox: "0 0 110 110",
- glyphData = {
unknown: "M35,40a5,5,0,0,1,5-5h30a5,5,0,0,1,5,5v30a5,5,0,0,1-5,5" +
"h-30a5,5,0,0,1-5-5z",
@@ -288,9 +286,9 @@
"L22,23.7z M97.9,46.5H77.2L88,23.7L97.9,46.5z"
},
- badgeViewBox = '0 0 10 10',
+ badgeDataSet = {
+ _viewbox: "0 0 10 10",
- badgeData = {
uiAttached: "M2,2.5a.5,.5,0,0,1,.5-.5h5a.5,.5,0,0,1,.5,.5v3" +
"a.5,.5,0,0,1-.5,.5h-5a.5,.5,0,0,1-.5-.5zM2.5,2.8a.3,.3,0,0,1," +
".3-.3h4.4a.3,.3,0,0,1,.3,.3v2.4a.3,.3,0,0,1-.3,.3h-4.4" +
@@ -324,9 +322,70 @@
play: "M2.5,2l5.5,3l-5.5,3z",
stop: "M2.5,2.5h5v5h-5z"
+ },
+
+ spriteData = {
+ _cloud: '0 0 110 110',
+ cloud: "M37.6,79.5c-6.9,8.7-20.4,8.6-22.2-2.7" +
+ "M16.3,41.2c-0.8-13.9,19.4-19.2,23.5-7.8" +
+ "M38.9,30.9c5.1-9.4,15.1-8.5,16.9-1.3" +
+ "M54.4,32.9c4-12.9,14.8-9.6,18.6-3.8" +
+ "M95.8,58.5c10-4.1,11.7-17.8-0.9-19.8" +
+ "M18.1,76.4C5.6,80.3,3.8,66,13.8,61.5" +
+ "M16.2,62.4C2.1,58.4,3.5,36,16.8,36.6" +
+ "M93.6,74.7c10.2-2,10.7-14,5.8-18.3" +
+ "M71.1,79.3c11.2,7.6,24.6,6.4,22.1-11.7" +
+ "M36.4,76.8c3.4,13.3,35.4,11.6,36.1-1.4" +
+ "M70.4,31c11.8-10.4,26.2-5.2,24.7,10.1"
};
// ----------------------------------------------------------------------
+ // === Constants
+
+ var msgGS = 'GlyphService.',
+ rg = "registerGlyphs(): ",
+ rgs = "registerGlyphSet(): ";
+
+ // ----------------------------------------------------------------------
+
+ function warn(msg) {
+ $log.warn(msgGS + msg);
+ }
+
+ function addToMap(key, value, vbox, overwrite, dups) {
+ if (!overwrite && glyphs.get(key)) {
+ dups.push(key);
+ } else {
+ glyphs.set(key, {id: key, vb: vbox, d: value});
+ }
+ }
+
+ function reportDups(dups, which) {
+ var ok = (dups.length == 0),
+ msg = 'ID collision: ';
+
+ if (!ok) {
+ dups.forEach(function (id) {
+ warn(which + msg + '"' + id + '"');
+ });
+ }
+ return ok;
+ }
+
+ function reportMissVb(missing, which) {
+ var ok = (missing.length == 0),
+ msg = 'Missing viewbox property: ';
+
+ if (!ok) {
+ missing.forEach(function (vbk) {
+ warn(which + msg + '"' + vbk + '"');
+ });
+ }
+ return ok;
+ }
+
+ // ----------------------------------------------------------------------
+ // === API functions ===
function clear() {
// start with a fresh map
@@ -335,30 +394,46 @@
function init() {
clear();
- register(birdViewBox, birdData);
- register(glyphViewBox, glyphData);
- register(badgeViewBox, badgeData);
+ registerGlyphs(birdData);
+ registerGlyphSet(glyphDataSet);
+ registerGlyphSet(badgeDataSet);
+ registerGlyphs(spriteData);
}
- function register(viewBox, data, overwrite) {
- var dmap = d3.map(data),
- dups = [],
- ok;
+ function registerGlyphs(data, overwrite) {
+ var dups = [],
+ missvb = [];
- dmap.forEach(function (key, value) {
- if (!overwrite && glyphs.get(key)) {
- dups.push(key);
- } else {
- glyphs.set(key, {id: key, vb: viewBox, d: value});
+ angular.forEach(data, function (value, key) {
+ var vbk = '_' + key,
+ vb = data[vbk];
+
+ if (key[0] !== '_') {
+ if (!vb) {
+ missvb.push(vbk);
+ return;
+ }
+ addToMap(key, value, vb, overwrite, dups);
}
});
- ok = (dups.length == 0);
- if (!ok) {
- dups.forEach(function (id) {
- $log.warn(msgGS + 'register(): ID collision: "'+id+'"');
- });
+ return reportDups(dups, rg) && reportMissVb(missvb, rg);
+ }
+
+ function registerGlyphSet(data, overwrite) {
+ var dups = [],
+ vb = data._viewbox;
+
+ if (!vb) {
+ warn(rgs + 'no "_viewbox" property found');
+ return false;
}
- return ok;
+
+ angular.forEach(data, function (value, key) {
+ if (key[0] !== '_') {
+ addToMap(key, value, vb, overwrite, dups);
+ }
+ });
+ return reportDups(dups, rgs);
}
function ids() {
@@ -428,7 +503,8 @@
return {
clear: clear,
init: init,
- register: register,
+ registerGlyphs: registerGlyphs,
+ registerGlyphSet: registerGlyphSet,
ids: ids,
glyph: glyph,
loadDefs: loadDefs,
diff --git a/web/gui/src/main/webapp/app/view/topo/topo.js b/web/gui/src/main/webapp/app/view/topo/topo.js
index f40beb1..b16cb6d 100644
--- a/web/gui/src/main/webapp/app/view/topo/topo.js
+++ b/web/gui/src/main/webapp/app/view/topo/topo.js
@@ -247,6 +247,25 @@
.attr('opacity', b ? 1 : 0);
}
+ function addSprites() {
+ var g = zoomLayer.append ('g').attr('id', 'topo-sprites');
+
+ function cloud(g, x, y) {
+ g.append('use').attr({
+ width: 100,
+ height: 100,
+ 'xlink:href': '#cloud',
+ transform: sus.translate([x, y]) + sus.scale(4,4)
+ }).style('stroke', 'goldenrod')
+ .style('fill', 'none')
+ .style('stroke-width', 1.0);
+ }
+
+ cloud(g, 0, 50);
+ cloud(g, 800, 40);
+ cloud(g, 400, 450);
+ }
+
// --- User Preferemces ----------------------------------------------
var prefsState = {};
@@ -354,6 +373,7 @@
toggleMap(prefsState.bg);
}
);
+ // addSprites();
forceG = zoomLayer.append('g').attr('id', 'topo-force');
tfs.initForce(svg, forceG, uplink, dim);
diff --git a/web/gui/src/main/webapp/data/ext/sprites.json b/web/gui/src/main/webapp/data/ext/sprites.json
new file mode 100644
index 0000000..5cf4109
--- /dev/null
+++ b/web/gui/src/main/webapp/data/ext/sprites.json
@@ -0,0 +1,44 @@
+{
+ "_comment": [
+ "configuration file for loading canned and/or custom sprites (and labels)",
+ "into the topology view. These appear above the map layer, but below",
+ "the nodes/links layer."
+ ],
+
+ "_comment_defn": "'defn' array contains custom sprite definitions",
+ "defn": [
+
+ ],
+
+ "_comment_defstyle": "'defstyle' defines default styles to apply",
+ "defstyle": {
+ "sprite": {
+ "stroke": "goldenrod",
+ "stroke-width": 1.0,
+ "fill": "none"
+ },
+ "text": {
+ "text-style": "italic",
+ "test-size": "20pt"
+ }
+ },
+
+ "_comment_load": [
+ "'load' array contains list of sprites/labels to load",
+ " note that 'copies' array defines [x,y] coords to position copies"
+ ],
+ "load": [
+ {
+ "id": "cloud",
+ "width": 100,
+ "height": 100,
+ "scale": 4.0,
+ "copies": [
+ [0, 50], [800, 40], [400, 450]
+ ],
+ "style": {
+ "stroke": "green"
+ }
+ }
+ ]
+}
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 9b24e47..2f168ae 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
@@ -20,7 +20,7 @@
describe('factory: fw/svg/glyph.js', function() {
var $log, fs, gs, d3Elem, svg;
- var numBaseGlyphs = 35,
+ var numBaseGlyphs = 36,
vbBird = '352 224 113 112',
vbGlyph = '0 0 110 110',
vbBadge = '0 0 10 10',
@@ -67,6 +67,8 @@
play: 'M2.5,2l5.5,3',
stop: 'M2.5,2.5h5',
+ cloud: 'M37.6,79.5c-6.9,8.7-20.4,8.6',
+
// our test ones..
triangle: 'M.5,.2',
diamond: 'M.2,.5'
@@ -81,6 +83,9 @@
badgeIds = [
'uiAttached', 'checkMark', 'xMark', 'triangleUp', 'triangleDown',
'plus', 'minus', 'play', 'stop'
+ ],
+ spriteIds = [
+ 'cloud'
];
beforeEach(module('onosUtil', 'onosSvg'));
@@ -106,8 +111,9 @@
it('should define api functions', function () {
expect(fs.areFunctions(gs, [
- 'clear', 'init', 'register', 'ids', 'glyph', 'loadDefs', 'addGlyph'
- ])).toBeTruthy();
+ 'clear', 'init', 'registerGlyphs', 'registerGlyphSet',
+ 'ids', 'glyph', 'loadDefs', 'addGlyph'
+ ])).toBe(true);
});
it('should start with no glyphs loaded', function () {
@@ -131,7 +137,7 @@
glyph = gs.glyph(id),
prefix = prefixLookup[pfxId],
plen = prefix.length;
- expect(fs.contains(gs.ids(), id)).toBeTruthy();
+ expect(fs.contains(gs.ids(), id)).toBe(true);
expect(glyph).toBeDefined();
expect(glyph.id).toEqual(id);
expect(glyph.vb).toEqual(vbox);
@@ -139,7 +145,8 @@
}
it('should be configured with the correct number of glyphs', function () {
- expect(1 + glyphIds.length + badgeIds.length).toEqual(numBaseGlyphs);
+ var nGlyphs = 1 + glyphIds.length + badgeIds.length + spriteIds.length;
+ expect(nGlyphs).toEqual(numBaseGlyphs);
});
it('should load the bird glyph', function() {
@@ -161,29 +168,64 @@
});
});
+ it('should load the sprites', function () {
+ gs.init();
+ spriteIds.forEach(function (id) {
+ verifyGlyphLoadedInCache(id, vbGlyph);
+ });
+ });
+
// define some glyphs that we want to install
var testVbox = '0 0 1 1',
+ triVbox = '0 0 12 12',
+ diaVbox = '0 0 15 15',
dTriangle = 'M.5,.2l.3,.6,h-.6z',
dDiamond = 'M.2,.5l.3,-.3l.3,.3l-.3,.3z',
newGlyphs = {
+ _viewbox: testVbox,
triangle: dTriangle,
diamond: dDiamond
},
dupGlyphs = {
+ _viewbox: testVbox,
router: dTriangle,
switch: dDiamond
},
- idCollision = 'GlyphService.register(): ID collision: ';
+ altNewGlyphs = {
+ _triangle: triVbox,
+ triangle: dTriangle,
+ _diamond: diaVbox,
+ diamond: dDiamond
+ },
+ altDupGlyphs = {
+ _router: triVbox,
+ router: dTriangle,
+ _switch: diaVbox,
+ switch: dDiamond
+ },
+ badGlyphSet = {
+ triangle: dTriangle,
+ diamond: dDiamond
+ },
+ warnMsg = 'GlyphService.registerGlyphs(): ',
+ warnMsgSet = 'GlyphService.registerGlyphSet(): ',
+ idCollision = warnMsg + 'ID collision: ',
+ idCollisionSet = warnMsgSet + 'ID collision: ',
+ missVbSet = warnMsgSet + 'no "_viewbox" property found',
+ missVbCustom = warnMsg + 'Missing viewbox property: ',
+ missVbTri = missVbCustom + '"_triangle"',
+ missVbDia = missVbCustom + '"_diamond"';
- it('should install new glyphs', function () {
+
+ it('should install new glyphs as a glyph-set', function () {
gs.init();
expect(gs.ids().length).toEqual(numBaseGlyphs);
spyOn($log, 'warn');
- var ok = gs.register(testVbox, newGlyphs);
- expect(ok).toBeTruthy();
+ var ok = gs.registerGlyphSet(newGlyphs);
+ expect(ok).toBe(true);
expect($log.warn).not.toHaveBeenCalled();
expect(gs.ids().length).toEqual(numBaseGlyphs + 2);
@@ -191,13 +233,69 @@
verifyGlyphLoadedInCache('diamond', testVbox);
});
+ it('should not overwrite glyphs (via glyph-set) with dup IDs', function () {
+ gs.init();
+ expect(gs.ids().length).toEqual(numBaseGlyphs);
+ spyOn($log, 'warn');
+
+ var ok = gs.registerGlyphSet(dupGlyphs);
+ expect(ok).toBe(false);
+ expect($log.warn).toHaveBeenCalledWith(idCollisionSet + '"switch"');
+ expect($log.warn).toHaveBeenCalledWith(idCollisionSet + '"router"');
+
+ expect(gs.ids().length).toEqual(numBaseGlyphs);
+ // verify original glyphs still exist...
+ verifyGlyphLoadedInCache('router', vbGlyph);
+ verifyGlyphLoadedInCache('switch', vbGlyph);
+ });
+
+ it('should replace glyphs (via glyph-set) if asked nicely', function () {
+ gs.init();
+ expect(gs.ids().length).toEqual(numBaseGlyphs);
+ spyOn($log, 'warn');
+
+ var ok = gs.registerGlyphSet(dupGlyphs, true);
+ expect(ok).toBe(true);
+ expect($log.warn).not.toHaveBeenCalled();
+
+ expect(gs.ids().length).toEqual(numBaseGlyphs);
+ // verify glyphs have been overwritten...
+ verifyGlyphLoadedInCache('router', testVbox, 'triangle');
+ verifyGlyphLoadedInCache('switch', testVbox, 'diamond');
+ });
+
+ it ('should complain if missing _viewbox in a glyph-set', function () {
+ gs.init();
+ expect(gs.ids().length).toEqual(numBaseGlyphs);
+ spyOn($log, 'warn');
+
+ var ok = gs.registerGlyphSet(badGlyphSet);
+ expect(ok).toBe(false);
+ expect($log.warn).toHaveBeenCalledWith(missVbSet);
+ expect(gs.ids().length).toEqual(numBaseGlyphs);
+ });
+
+ it('should install new glyphs', function () {
+ gs.init();
+ expect(gs.ids().length).toEqual(numBaseGlyphs);
+ spyOn($log, 'warn');
+
+ var ok = gs.registerGlyphs(altNewGlyphs);
+ expect(ok).toBe(true);
+ expect($log.warn).not.toHaveBeenCalled();
+
+ expect(gs.ids().length).toEqual(numBaseGlyphs + 2);
+ verifyGlyphLoadedInCache('triangle', triVbox);
+ verifyGlyphLoadedInCache('diamond', diaVbox);
+ });
+
it('should not overwrite glyphs with dup IDs', function () {
gs.init();
expect(gs.ids().length).toEqual(numBaseGlyphs);
spyOn($log, 'warn');
- var ok = gs.register(testVbox, dupGlyphs);
- expect(ok).toBeFalsy();
+ var ok = gs.registerGlyphs(altDupGlyphs);
+ expect(ok).toBe(false);
expect($log.warn).toHaveBeenCalledWith(idCollision + '"switch"');
expect($log.warn).toHaveBeenCalledWith(idCollision + '"router"');
@@ -212,14 +310,26 @@
expect(gs.ids().length).toEqual(numBaseGlyphs);
spyOn($log, 'warn');
- var ok = gs.register(testVbox, dupGlyphs, true);
- expect(ok).toBeTruthy();
+ var ok = gs.registerGlyphs(altDupGlyphs, true);
+ expect(ok).toBe(true);
expect($log.warn).not.toHaveBeenCalled();
expect(gs.ids().length).toEqual(numBaseGlyphs);
// verify glyphs have been overwritten...
- verifyGlyphLoadedInCache('router', testVbox, 'triangle');
- verifyGlyphLoadedInCache('switch', testVbox, 'diamond');
+ verifyGlyphLoadedInCache('router', triVbox, 'triangle');
+ verifyGlyphLoadedInCache('switch', diaVbox, 'diamond');
+ });
+
+ it ('should complain if missing custom viewbox', function () {
+ gs.init();
+ expect(gs.ids().length).toEqual(numBaseGlyphs);
+ spyOn($log, 'warn');
+
+ var ok = gs.registerGlyphs(badGlyphSet);
+ expect(ok).toBe(false);
+ expect($log.warn).toHaveBeenCalledWith(missVbTri);
+ expect($log.warn).toHaveBeenCalledWith(missVbDia);
+ expect(gs.ids().length).toEqual(numBaseGlyphs);
});
function verifyPathPrefix(elem, prefix) {
@@ -245,7 +355,7 @@
it('should load custom glyphs into the DOM', function () {
gs.init();
- gs.register(testVbox, newGlyphs);
+ gs.registerGlyphSet(newGlyphs);
gs.loadDefs(d3Elem);
expect(d3Elem.selectAll('symbol').size()).toEqual(numBaseGlyphs + 2);
verifyLoadedInDom('diamond', testVbox);