Simon Hunt
Committed by Gerrit Code Review

GUI -- Revamp of the Glyph Service to allow for custom viewboxes to be defined f…

…or registered glyphs/sprites.
- Also, initial sketch for externally injected sprite definition and placement.
- Added 'cloud' sprite data.

Change-Id: I1c38d50212a6d67e00e9b7c15427f6e0af40b539
......@@ -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,
......
......@@ -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);
......
{
"_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"
}
}
]
}
......@@ -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 @@ describe('factory: fw/svg/glyph.js', function() {
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 @@ describe('factory: fw/svg/glyph.js', function() {
badgeIds = [
'uiAttached', 'checkMark', 'xMark', 'triangleUp', 'triangleDown',
'plus', 'minus', 'play', 'stop'
],
spriteIds = [
'cloud'
];
beforeEach(module('onosUtil', 'onosSvg'));
......@@ -106,8 +111,9 @@ describe('factory: fw/svg/glyph.js', function() {
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 @@ describe('factory: fw/svg/glyph.js', function() {
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 @@ describe('factory: fw/svg/glyph.js', function() {
}
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 @@ describe('factory: fw/svg/glyph.js', function() {
});
});
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 @@ describe('factory: fw/svg/glyph.js', function() {
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 @@ describe('factory: fw/svg/glyph.js', function() {
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 @@ describe('factory: fw/svg/glyph.js', function() {
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);
......