GUI -- Created sample subnet sprite layout (clouds.json).
- Made paths, defn, load mandatory properties of the sprite definition file.
- (layout.json still a WIP)

Change-Id: I323a7ec7317f0837ff3319d67956cb4f836405eb
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 0caeec7..23ef0b5 100644
--- a/web/gui/src/main/webapp/app/view/topo/topo.css
+++ b/web/gui/src/main/webapp/app/view/topo/topo.css
@@ -555,7 +555,7 @@
 }
 
 .light #ov-topo svg #topo-sprites .gold1 use {
-    stroke: #da2;
+    stroke: #fda;
     fill: none;
 }
 .dark #ov-topo svg #topo-sprites .gold1 use {
diff --git a/web/gui/src/main/webapp/app/view/topo/topoSprite.js b/web/gui/src/main/webapp/app/view/topo/topoSprite.js
index 3b1115a..9b5dbab 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoSprite.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoSprite.js
@@ -32,6 +32,7 @@
     // internal state
     var spriteLayer, defsElement;
 
+
     function registerPathsAsGlyphs(paths) {
         var custom = {},
             ids = [];
@@ -40,21 +41,25 @@
             return fs.isA(d) ? d.join('') : d;
         }
 
-        if (paths) {
-            paths.forEach(function (path) {
-                var tag = 'spr_' + path.tag;
-                custom['_' + tag] = path.viewbox || '0 0 1000 1000';
-                custom[tag] = mkd(path.d);
-                ids.push(tag);
-            });
+        paths.forEach(function (path) {
+            var tag = 'spr_' + path.tag;
 
-            gs.registerGlyphs(custom);
-            gs.loadDefs(defsElement, ids, true);
-        }
+            if (path.glyph) {
+                // assumption is that we are using a built-in glyph
+                return;
+            }
+
+            custom['_' + tag] = path.viewbox || '0 0 1000 1000';
+            custom[tag] = mkd(path.d);
+            ids.push(tag);
+        });
+
+        gs.registerGlyphs(custom);
+        gs.loadDefs(defsElement, ids, true);
     }
 
 
-    function doSprite(spr, def, pstrk) {
+    function doSprite(spr, def, pmeta) {
         var c = spr.class || 'gray1',
             p = spr.pos || [0,0],
             lab = spr.label,
@@ -64,7 +69,6 @@
             dy = def.labelyoff || 1,
             sc = def.scale,
             xfm = sus.translate(p),
-            useId = def.glyph || 'spr_' + def.path,
             g, attr, use, style;
 
         if (sc) {
@@ -78,14 +82,14 @@
         attr = {
             width: w,
             height: h,
-            'xlink:href': '#' + useId
+            'xlink:href': '#' + pmeta.u
         };
 
         use = g.append('use').attr(attr);
 
-        if (pstrk) {
+        if (pmeta.s) {
             style = {};
-            angular.forEach(pstrk, function (value, key) {
+            angular.forEach(pmeta.s, function (value, key) {
                 style['stroke-' + key] = value;
             });
             use.style(style);
@@ -127,47 +131,66 @@
     //  data for the requested sprite definition.
     function inData(payload) {
         var data = payload.data,
-            name, desc, sprites, labels, alpha,
-            pathstrokes = {},
-            defs = {};
+            name, desc, pfx, sprites, labels, alpha,
+            paths, defn, load,
+            pathmeta = {},
+            defs = {},
+            warn = [];
 
         if (!data) {
-            $log.warn(tssid + 'No sprite data loaded.')
+            $log.warn(tssid + 'No sprite data loaded.');
             return;
         }
         name = data.defn_name;
         desc = data.defn_desc;
+        paths = data.paths;
+        defn = data.defn;
+        load = data.load;
+        pfx = tssid + '[' + name + ']: ';
 
         $log.debug("Loading sprites...[" + name + "]", desc);
 
-        if (data.paths) {
-            registerPathsAsGlyphs(data.paths);
-            data.paths.forEach(function (p) {
-                pathstrokes[p.tag] = p.stroke;
-            });
+        function no(what) {
+            warn.push(pfx + 'No ' + what + ' property defined');
         }
 
-        if (data.defn) {
-            data.defn.forEach(function (d) {
-                defs[d.id] = d;
-            });
+        if (!paths) no('paths');
+        if (!defn) no('defn');
+        if (!load) no('load');
+
+        if (warn.length) {
+            $log.error(warn.join('\n'));
+            return;
         }
 
-        // pull out the sprite and label items
-        if (data.load) {
-            sprites = data.load.sprites;
-            labels = data.load.labels;
-            alpha = data.load.alpha;
-            if (alpha) {
-                spriteLayer.style('opacity', alpha);
-            }
+        // any custom paths need to be added to the glyph DB, and imported
+        registerPathsAsGlyphs(paths);
+
+        paths.forEach(function (p) {
+            pathmeta[p.tag] = {
+                s: p.stroke,
+                u: p.glyph || 'spr_' + p.tag
+            };
+        });
+
+        defn.forEach(function (d) {
+            defs[d.id] = d;
+        });
+
+        // sprites, labels and alpha are each optional components of the load
+        sprites = load.sprites;
+        labels = load.labels;
+        alpha = load.alpha;
+
+        if (alpha) {
+            spriteLayer.style('opacity', alpha);
         }
 
         if (sprites) {
             sprites.forEach(function (spr) {
                 var def = defs[spr.id],
-                    pstrk = def.path && pathstrokes[def.path];
-                doSprite(spr, def, pstrk);
+                    pmeta = pathmeta[def.path];
+                doSprite(spr, def, pmeta);
             });
         }
 
diff --git a/web/gui/src/main/webapp/data/sprites/clouds.json b/web/gui/src/main/webapp/data/sprites/clouds.json
new file mode 100644
index 0000000..e0c5603
--- /dev/null
+++ b/web/gui/src/main/webapp/data/sprites/clouds.json
@@ -0,0 +1,59 @@
+{
+  "defn_name": "clouds",
+  "defn_desc": "Sample Subnet Cloud layout",
+
+  "_comment": [
+    "Sample cloud sprite layout",
+    "(1) Register on the server with ...",
+    "    onos-upload-sprites localhost clouds.json",
+    "(2) Load into topology view with ...",
+    "    http://localhost:8181/onos/ui/index.html#/topo?sprites=clouds"
+  ],
+
+  "paths": [
+    {
+      "tag": "cloud-dashed",
+      "stroke": {
+        "width": 0.8,
+        "dasharray": [4,2]
+      },
+      "glyph": "cloud"
+    },
+    {
+      "tag": "cloud-solid",
+      "stroke": {
+        "width": 1
+      },
+      "glyph": "cloud"
+    }
+  ],
+
+  "defn": [
+    {
+      "id": "subnetA",
+      "path": "cloud-dashed",
+      "dim": [400,400],
+      "labelyoff": 0.35
+    },
+    {
+      "id": "subnetB",
+      "path": "cloud-solid",
+      "dim": [600,600],
+      "labelyoff": 0.35
+    }
+  ],
+
+  "load": {
+    "sprites": [
+      { "id": "subnetA", "pos":[-200,40], "label":"apples", "class":"blue1" },
+      { "id": "subnetA", "pos":[250,40], "label":"bananas", "class":"blue1" },
+      { "id": "subnetA", "pos":[700,40], "label":"cherries", "class":"blue1" },
+      { "id": "subnetB", "pos":[-200,400], "label":"Menlo Park", "class":"gold1" },
+      { "id": "subnetB", "pos":[500,400], "label":"Stanford", "class":"gold1" }
+    ],
+    "labels": [
+      { "pos":[0,50], "text":"Sample Subnets", "size":1.4 }
+    ]
+  }
+
+}
diff --git a/web/gui/src/main/webapp/data/sprites/layout.json b/web/gui/src/main/webapp/data/sprites/layout.json
index 3e41939..243681a 100644
--- a/web/gui/src/main/webapp/data/sprites/layout.json
+++ b/web/gui/src/main/webapp/data/sprites/layout.json
@@ -18,16 +18,18 @@
   "paths": [
     {
       "tag": "border",
-      "d": "M0,0h1000v1000h-1000z",
-      "_comment": "bounds of viewbox 0 0 1000 1000"
+      "viewbox": "0 0 1 1",
+      "d": "M0,0h1v1h-1z",
+      "_comment": "path defined in single string"
     },
     {
-      "tag": "multi",
+      "tag": "banner",
+      "viewbox": "0 0 4 8",
       "d": [
-        "M500,500l-50,50v-200h100v200z",
-        "M600,400h200v50h-200z"
+        "M0,0h4v6l-2,-2l-2,2z",
+        "M1,6h2v2h-2z"
       ],
-      "_comment": "shows path constructed from multiple strings"
+      "_comment": "path defined in multiple strings"
     },
     {
       "tag": "triangle",
@@ -49,18 +51,20 @@
     "",
     "The 'glyph' property refers to glyphs registered with the UI.",
     "Alternatively, the 'path' property refers to a custom path defined in",
-    "the path array above. The 'dim' property provides the [width,height]",
-    "bounds within which the glyph/path is drawn. The 'labelyoff' property",
-    "defines the Y-offset of the label as a percentage from the top of the",
-    "sprite; for example, 0.4 = 40%. The label is centered horizontally.",
-    "",
-    "Note that dimension (dim) defaults to [40,40] which is the",
-    "approximate size of a device icon."
+    " the path array above.",
+    "The 'dim' property provides the [width,height] bounds within which the",
+    " glyph/path is drawn. (default is [40,40])",
+    "The 'labelyoff' property defines the Y-offset of the label as a",
+    " percentage from the top of the sprite; for example, 0.4 = 40%. The",
+    " label is centered horizontally.",
+    "The 'scale' property (default is 1) defines the scaling factor, which",
+    " is applied after the sprite has been translated to its position."
   ],
   "defn": [
     {
       "id": "border",
-      "path": "border"
+      "path": "border",
+      "dim":[1000,1000]
     },
     {
       "id": "multi",
@@ -103,6 +107,7 @@
   ],
   "load": {
     "sprites": [
+      { "id": "border", "class": "gold1" },
       { "id": "subnet", "pos":[-40,20], "label":"apples", "class": "blue1" },
       { "id": "subnet", "pos":[400,40], "label":"bananas", "class": "blue1" },
       { "id": "subnet", "pos":[840,60], "label":"cherries", "class": "blue1" },