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/view/topo/topo.js b/web/gui/src/main/webapp/app/view/topo/topo.js
index adf7d2a..603a5a1 100644
--- a/web/gui/src/main/webapp/app/view/topo/topo.js
+++ b/web/gui/src/main/webapp/app/view/topo/topo.js
@@ -29,19 +29,22 @@
     ];
 
     // references to injected services etc.
-    var $log, ks, gs;
+    var $log, ks, zs, gs;
 
     // DOM elements
-    var defs;
+    var svg, defs;
 
     // Internal state
-    // ...
+    var zoomer;
 
     // Note: "exported" state should be properties on 'self' variable
 
+    // --- Short Cut Keys ------------------------------------------------
+
     var keyBindings = {
-        W: [logWarning, 'log a warning'],
-        E: [logError, 'log an error']
+        W: [logWarning, '(temp) log a warning'],
+        E: [logError, '(temp) log an error'],
+        R: [resetZoom, 'Reset pan / zoom']
     };
 
     // -----------------
@@ -54,32 +57,72 @@
     }
     // -----------------
 
+    function resetZoom() {
+        zoomer.reset();
+    }
+
     function setUpKeys() {
         ks.keyBindings(keyBindings);
     }
 
+
+    // --- Glyphs, Icons, and the like -----------------------------------
+
     function setUpDefs() {
-        defs = d3.select('#ov-topo svg').append('defs');
+        defs = svg.append('defs');
         gs.loadDefs(defs);
     }
 
 
+    // --- Pan and Zoom --------------------------------------------------
+
+    // zoom enabled predicate. ev is a D3 source event.
+    function zoomEnabled(ev) {
+        return (ev.metaKey || ev.altKey);
+    }
+
+    function zoomCallback() {
+        var tr = zoomer.translate(),
+            sc = zoomer.scale();
+        $log.log('ZOOM: translate = ' + tr + ', scale = ' + sc);
+
+        // TODO: keep the map lines constant width while zooming
+        //bgImg.style('stroke-width', 2.0 / scale + 'px');
+    }
+
+    function setUpZoom() {
+        var zoomLayer = svg.append('g').attr('id', 'topo-zoomlayer');
+        zoomer = zs.createZoomer({
+            svg: svg,
+            zoomLayer: zoomLayer,
+            zoomEnabled: zoomEnabled,
+            zoomCallback: zoomCallback
+        });
+    }
+
+
+    // --- Controller Definition -----------------------------------------
+
     angular.module('ovTopo', moduleDependencies)
 
         .controller('OvTopoCtrl', [
-            '$log', 'KeyService', 'GlyphService',
+            '$log', 'KeyService', 'ZoomService', 'GlyphService',
 
-        function (_$log_, _ks_, _gs_) {
+        function (_$log_, _ks_, _zs_, _gs_) {
             var self = this;
-
             $log = _$log_;
             ks = _ks_;
+            zs = _zs_;
             gs = _gs_;
 
+            // exported state
             self.message = 'Topo View Rocks!';
 
+            // svg layer and initialization of components
+            svg = d3.select('#ov-topo svg');
             setUpKeys();
             setUpDefs();
+            setUpZoom();
 
             $log.log('OvTopoCtrl has been created');
         }]);