GUI -- Implemented ZoomService, with unit tests.
- Added zoomer to topo.js; we are at least generating the events.
- Added GlyphService.clear()

Change-Id: I5400e52b58ee584866d8ffbb20d5bde70b336985
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 8129c16..621436c 100644
--- a/web/gui/src/main/webapp/app/fw/svg/glyph.js
+++ b/web/gui/src/main/webapp/app/fw/svg/glyph.js
@@ -124,9 +124,13 @@
         .factory('GlyphService', ['$log', function (_$log_) {
             $log = _$log_;
 
-            function init() {
+            function clear() {
                 // start with a fresh map
                 glyphs = d3.map();
+            }
+
+            function init() {
+                clear();
                 register(birdViewBox, birdData);
                 register(glyphViewBox, glyphData);
                 register(badgeViewBox, badgeData);
@@ -175,6 +179,7 @@
             }
 
             return {
+                clear: clear,
                 init: init,
                 register: register,
                 ids: ids,
diff --git a/web/gui/src/main/webapp/app/fw/svg/zoom.js b/web/gui/src/main/webapp/app/fw/svg/zoom.js
index 2dc5236..09c8455 100644
--- a/web/gui/src/main/webapp/app/fw/svg/zoom.js
+++ b/web/gui/src/main/webapp/app/fw/svg/zoom.js
@@ -22,14 +22,112 @@
 (function () {
     'use strict';
 
+    // configuration
+    var defaultSettings = {
+        zoomMin: 0.25,
+        zoomMax: 10,
+        zoomEnabled: function (ev) { return true; },
+        zoomCallback: function () {}
+    };
+
+    // injected references to services
     var $log;
 
     angular.module('onosSvg')
-        .factory('ZoomService', ['$log', function (_$log_) {
+        .factory('ZoomService', ['$log',
+
+        function (_$log_) {
             $log = _$log_;
 
+/*
+            NOTE: opts is an object:
+            {
+                svg: svg,                       D3 selection of <svg> element
+                zoomLayer: zoomLayer,           D3 selection of <g> element
+                zoomEnabled: function (ev) { ... },
+                zoomCallback: function () { ... }
+            }
+
+            where:
+                * svg and zoomLayer should be D3 selections of DOM elements.
+                    * zoomLayer <g> is a child of <svg> element.
+                * zoomEnabled is an optional predicate based on D3 event.
+                    * default is always enabled.
+                * zoomCallback is an optional callback invoked each time we pan/zoom.
+                    * default is do nothing.
+
+            Optionally, zoomMin and zoomMax also can be defined.
+            These default to 0.25 and 10 respectively.
+*/
+            function createZoomer(opts) {
+                var cz = 'ZoomService.createZoomer(): ',
+                    d3s = ' (D3 selection) property defined',
+                    settings = $.extend({}, defaultSettings, opts),
+                    zoom = d3.behavior.zoom()
+                        .translate([0, 0])
+                        .scale(1)
+                        .scaleExtent([settings.zoomMin, settings.zoomMax])
+                        .on('zoom', zoomed),
+                    fail = false,
+                    zoomer;
+
+                if (!settings.svg) {
+                    $log.error(cz + 'No "svg" (svg tag)' + d3s);
+                    fail = true;
+                }
+                if (!settings.zoomLayer) {
+                    $log.error(cz + 'No "zoomLayer" (g tag)' + d3s);
+                    fail = true;
+                }
+
+                if (fail) {
+                    return null;
+                }
+
+                // zoom events from mouse gestures...
+                function zoomed() {
+                    var ev = d3.event.sourceEvent;
+                    if (settings.zoomEnabled(ev)) {
+                        adjustZoomLayer(d3.event.translate, d3.event.scale);
+                    }
+                }
+
+                function adjustZoomLayer(translate, scale) {
+                    settings.zoomLayer.attr('transform',
+                        'translate(' + translate + ')scale(' + scale + ')');
+                    settings.zoomCallback();
+                }
+
+                zoomer = {
+                    panZoom: function (translate, scale) {
+                        zoom.translate(translate).scale(scale);
+                        adjustZoomLayer(translate, scale);
+                    },
+
+                    reset: function () {
+                        zoomer.panZoom([0,0], 1);
+                    },
+
+                    translate: function () {
+                        return zoom.translate();
+                    },
+
+                    scale: function () {
+                        return zoom.scale();
+                    },
+
+                    scaleExtent: function () {
+                        return zoom.scaleExtent();
+                    }
+                };
+
+                // apply the zoom behavior to the SVG element
+                settings.svg && settings.svg.call(zoom);
+                return zoomer;
+            }
+
             return {
-                tbd: function () {}
+                createZoomer: createZoomer
             };
         }]);