GUI -- Cleaned up sprite definition format in JSON.
- Implemented sprite layer in topology view.

Change-Id: I0861641684df12202d6ccd069d89375a8005d4a8
diff --git a/web/gui/src/main/resources/core/js.html b/web/gui/src/main/resources/core/js.html
index 8d6d87d..ac9247b 100644
--- a/web/gui/src/main/resources/core/js.html
+++ b/web/gui/src/main/resources/core/js.html
@@ -9,6 +9,7 @@
 <script src="app/view/topo/topoOblique.js"></script>
 <script src="app/view/topo/topoPanel.js"></script>
 <script src="app/view/topo/topoSelect.js"></script>
+<script src="app/view/topo/topoSprite.js"></script>
 <script src="app/view/topo/topoTraffic.js"></script>
 <script src="app/view/topo/topoToolbar.js"></script>
 <script src="app/view/device/device.js"></script>
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 b629559..887c2fc 100644
--- a/web/gui/src/main/webapp/app/view/topo/topo.css
+++ b/web/gui/src/main/webapp/app/view/topo/topo.css
@@ -542,3 +542,46 @@
     fill: #eee;
 }
 
+/* Sprite Layer */
+
+#ov-topo svg #topo-sprites text {
+    text-anchor: middle;
+    font-size: 10pt;
+    font-style: italic;
+}
+
+.light #ov-topo svg #topo-sprites .sprite1 use {
+    stroke-width: 1.0;
+    stroke: goldenrod;
+    fill: none;
+}
+.dark #ov-topo svg #topo-sprites .sprite1 use {
+    stroke-width: 1.0;
+    stroke: #541;
+    fill: none;
+}
+
+.light #ov-topo svg #topo-sprites .sprite1 text {
+    fill: #eda;
+}
+.dark #ov-topo svg #topo-sprites .sprite1 text {
+    fill: #543;
+}
+
+.light #ov-topo svg #topo-sprites .sprite2 use {
+    stroke: #bbd;
+    stroke-width: 1.0;
+    fill: none;
+}
+.dark #ov-topo svg #topo-sprites .sprite2 use {
+    stroke: #445;
+    stroke-width: 1.0;
+    fill: none;
+}
+
+.light #ov-topo svg #topo-sprites .sprite2 text {
+    fill: #cce;
+}
+.dark #ov-topo svg #topo-sprites .sprite2 text {
+    fill: #446;
+}
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 b16cb6d..68ecf2c 100644
--- a/web/gui/src/main/webapp/app/view/topo/topo.js
+++ b/web/gui/src/main/webapp/app/view/topo/topo.js
@@ -33,7 +33,7 @@
         tes, tfs, tps, tis, tss, tls, tts, tos, fltr, ttbs;
 
     // DOM elements
-    var ovtopo, svg, defs, zoomLayer, mapG, forceG, noDevsLayer;
+    var ovtopo, svg, defs, zoomLayer, mapG, spriteG, forceG, noDevsLayer;
 
     // Internal state
     var zoomer, actionMap;
@@ -53,6 +53,7 @@
             P: [tfs.togglePorts, 'Toggle Port Highlighting'],
             dash: [tfs.showBadLinks, 'Show bad links'],
             B: [toggleMap, 'Toggle background map'],
+            S: [toggleSprites, 'Toggle sprite layer'],
 
             //X: [toggleNodeLock, 'Lock / unlock node positions'],
             Z: [tos.toggleOblique, 'Toggle oblique view (Experimental)'],
@@ -116,6 +117,14 @@
         flash.flash(verb + ' background map');
     }
 
+    function toggleSprites(x) {
+        var on = (x === 'keyev') ? !sus.visible(spriteG) : !!x,
+            verb = on ? 'Show' : 'Hide';
+        sus.visible(spriteG, on);
+        updatePrefsState('sprites', on);
+        flash.flash(verb + ' sprite layer');
+    }
+
     function resetZoom() {
         zoomer.reset();
     }
@@ -247,24 +256,6 @@
             .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 ----------------------------------------------
 
@@ -285,6 +276,7 @@
         toggleInstances(prefsState.insts);
         toggleSummary(prefsState.summary);
         toggleDetails(prefsState.detail);
+        toggleSprites(prefsState.sprites);
     }
 
 
@@ -298,11 +290,11 @@
             'TopoEventService', 'TopoForceService', 'TopoPanelService',
             'TopoInstService', 'TopoSelectService', 'TopoLinkService',
             'TopoTrafficService', 'TopoObliqueService', 'TopoFilterService',
-            'TopoToolbarService',
+            'TopoToolbarService', 'TopoSpriteService',
 
         function ($scope, _$log_, $loc, $timeout, _$cookies_, _fs_, mast, _ks_,
                   _zs_, _gs_, _ms_, _sus_, _flash_, _wss_, _ps_, _tes_, _tfs_,
-                  _tps_, _tis_, _tss_, _tls_, _tts_, _tos_, _fltr_, _ttbs_) {
+                  _tps_, _tis_, _tss_, _tls_, _tts_, _tos_, _fltr_, _ttbs_, tspr) {
             var self = this,
                 projection,
                 dim,
@@ -373,7 +365,8 @@
                     toggleMap(prefsState.bg);
                 }
             );
-     //       addSprites();
+            spriteG = zoomLayer.append ('g').attr('id', 'topo-sprites');
+            tspr.loadSprites(spriteG);
 
             forceG = zoomLayer.append('g').attr('id', 'topo-force');
             tfs.initForce(svg, forceG, uplink, dim);
diff --git a/web/gui/src/main/webapp/app/view/topo/topoSprite.js b/web/gui/src/main/webapp/app/view/topo/topoSprite.js
new file mode 100644
index 0000000..15e5182
--- /dev/null
+++ b/web/gui/src/main/webapp/app/view/topo/topoSprite.js
@@ -0,0 +1,142 @@
+/*
+ * Copyright 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 -- Topology Sprite Module.
+ Defines behavior for loading sprites.
+ */
+
+(function () {
+    'use strict';
+
+    // injected refs
+    var $log, $http, fs, sus;
+
+    // internal state
+    var spriteLayer,
+        cache = d3.map();
+
+    // constants
+    var urlPrefix = 'data/ext/';
+
+    function getUrl(id) {
+        return urlPrefix + id + '.json';
+    }
+
+    // =========================
+
+    function clearCache() {
+        cache = d3.map();
+    }
+
+
+    function loadSpriteData(id, cb) {
+        var url = getUrl(id),
+            promise = cache.get(id);
+
+        if (!promise) {
+            // need to fetch data and cache it
+            promise = $http.get(url);
+
+            promise.meta = {
+                id: id,
+                url: url,
+                wasCached: false
+            };
+
+            promise.then(function (response) {
+                // success
+                promise.spriteData = response.data;
+                cb(promise.spriteData);
+            }, function (response) {
+                // error
+                $log.warn('Failed to retrieve sprite data: ' + url,
+                    response.status, response.data);
+            });
+
+        } else {
+            promise.meta.wasCached = true;
+            cb(promise.spriteData);
+        }
+    }
+
+    function doSprite(def, item) {
+        var g;
+
+        function xfm(x, y, s) {
+            return sus.translate([x,y]) + sus.scale(s, s);
+        }
+
+        g = spriteLayer.append('g')
+            .classed(def['class'], true)
+            .attr('transform', xfm(item.x, item.y, def.scale));
+
+        if (item.label) {
+            g.append('text')
+                .text(item.label)
+                .attr({
+                    x: def.width / 2,
+                    y: def.height * def.textyoff
+                });
+        }
+
+        g.append('use').attr({
+            width: def.width,
+            height: def.height,
+            'xlink:href': '#' + def.use
+        });
+    }
+
+    function loadSprites(layer) {
+        spriteLayer = layer;
+
+        loadSpriteData('sprites', function (data) {
+            var defs = {};
+
+            $log.debug("Loading sprites...", data.file_desc);
+
+            data.defn.forEach(function (d) {
+                defs[d.id] = d;
+            });
+
+            data.load.forEach(function (item) {
+                doSprite(defs[item.id], item);
+            });
+        });
+
+    }
+
+
+    // === -----------------------------------------------------
+    // === MODULE DEFINITION ===
+
+    angular.module('ovTopo')
+    .factory('TopoSpriteService',
+        ['$log', '$http', 'FnService', 'SvgUtilService',
+
+        function (_$log_, _$http_, _fs_, _sus_) {
+            $log = _$log_;
+            $http = _$http_;
+            fs = _fs_;
+            sus = _sus_;
+
+            return {
+                clearCache: clearCache,
+                loadSprites: loadSprites
+            };
+        }]);
+
+}());
diff --git a/web/gui/src/main/webapp/app/view/topo/topoToolbar.js b/web/gui/src/main/webapp/app/view/topo/topoToolbar.js
index 8a1895b..47e2a00 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoToolbar.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoToolbar.js
@@ -42,6 +42,7 @@
         M: { id: 'offline-tog', gid: 'switch', isel: true },
         P: { id: 'ports-tog', gid: 'ports', isel: true },
         B: { id: 'bkgrnd-tog', gid: 'map', isel: true },
+        S: { id: 'sprite-tog', gid: 'cloud', isel: false },
 
         //X: { id: 'nodelock-tog', gid: 'lock', isel: false },
         Z: { id: 'oblique-tog', gid: 'oblique', isel: false },
@@ -62,6 +63,7 @@
     // initial toggle state: default settings and tag to key mapping
     var defaultPrefsState = {
             bg: 1,
+            sprites: 0,
             insts: 1,
             summary: 1,
             detail: 1,
@@ -69,6 +71,7 @@
         },
         prefsMap = {
             bg: 'B',
+            sprites: 'S',
             insts: 'I',
             summary: 'O',
             details: 'D',
@@ -129,6 +132,7 @@
         addToggle('M');
         addToggle('P');
         addToggle('B');
+        addToggle('S');
     }
     function addSecondRow() {
         //addToggle('X');
diff --git a/web/gui/src/main/webapp/data/ext/sprites.json b/web/gui/src/main/webapp/data/ext/sprites.json
index 5cf4109..74b9129 100644
--- a/web/gui/src/main/webapp/data/ext/sprites.json
+++ b/web/gui/src/main/webapp/data/ext/sprites.json
@@ -1,44 +1,44 @@
 {
+  "file_desc": "Cloud Sprite Data",
+
   "_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",
+  "_comment_custom": "'custom' contains custom path data",
+  "custom": [
+
+  ],
+
+  "_comment_defn": "'defn' array contains 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"
-      }
+      "id": "subnet",
+      "class": "sprite1",
+      "use": "cloud",
+      "width": 120,
+      "height": 120,
+      "scale": 3.0,
+      "textyoff": 0.4
+    },
+    {
+      "id": "subnet2",
+      "class": "sprite2",
+      "use": "cloud",
+      "width": 200,
+      "height": 200,
+      "scale": 3.0,
+      "textyoff": 0.4
     }
+  ],
+
+  "_comment_load": "'load' array contains list of sprites to load",
+  "load": [
+    { "id": "subnet", "x": -40, "y":20, "label":"apples" },
+    { "id": "subnet", "x":400, "y":40, "label":"bananas" },
+    { "id": "subnet", "x":840, "y":60, "label":"cherries" },
+    { "id": "subnet2", "x":300, "y":400 }
   ]
 }
diff --git a/web/gui/src/main/webapp/index.html b/web/gui/src/main/webapp/index.html
index 5d78257..e408846 100644
--- a/web/gui/src/main/webapp/index.html
+++ b/web/gui/src/main/webapp/index.html
@@ -107,6 +107,7 @@
     <script src="app/view/topo/topoOblique.js"></script>
     <script src="app/view/topo/topoPanel.js"></script>
     <script src="app/view/topo/topoSelect.js"></script>
+    <script src="app/view/topo/topoSprite.js"></script>
     <script src="app/view/topo/topoTraffic.js"></script>
     <script src="app/view/topo/topoToolbar.js"></script>
     <script src="app/view/device/device.js"></script>