GUI -- Completed Show Summary panel.
- added GlyphService.addGlyph().
- added SvgUtilService.translate().

Change-Id: I0bbc51a8f1d9c24b8b4f1377236570070da6f160
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 0000000..8291065
--- /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 07c7d0b..f85b697 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 <defs> 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 <defs> 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 a0e0fff..1f5adc0 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 92b1712..738c967 100644
--- a/web/gui/src/main/webapp/app/index.html
+++ b/web/gui/src/main/webapp/app/index.html
@@ -66,6 +66,7 @@
     <link rel="stylesheet" href="onos.css">
     <link rel="stylesheet" href="common.css">
     <link rel="stylesheet" href="fw/mast/mast.css">
+    <link rel="stylesheet" href="fw/svg/glyph.css">
     <link rel="stylesheet" href="fw/svg/icon.css">
     <link rel="stylesheet" href="fw/layer/panel.css">
     <link rel="stylesheet" href="fw/nav/nav.css">
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 785df8c..d7e7ec3 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 d05dc3e..efc195f 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 587d617..b72de5a 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 @@
     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 @@
 
     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 @@
         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 7caa83e..964320b 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 @@
 
     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)');
+    });
+
 });