GUI -- [ONOS-89] Creating affinity colors for ONOS instances. (WIP)
- added d3utils.cat7() to provide color scales.
- added 'equals' and 'dash' ids for key binding.
- added theme() callback to let views know when the theme changed.
- re-bound test keys (0, dash, equals)

Change-Id: Ie6c6140451ddab567e26c2ea17d65395fa9cc829
diff --git a/web/gui/src/main/webapp/d3Utils.js b/web/gui/src/main/webapp/d3Utils.js
index 90b3032..c6d405c 100644
--- a/web/gui/src/main/webapp/d3Utils.js
+++ b/web/gui/src/main/webapp/d3Utils.js
@@ -119,10 +119,100 @@
             .attr('in', String);
     }
 
+    // --- Ordinal scales for 7 values.
+    // TODO: tune colors for light and dark themes
+
+    var lightNorm = ['#1f77b4', '#2ca02c', '#d62728', '#9467bd', '#e377c2', '#bcbd22', '#17becf'],
+        lightMute = ['#aec7e8', '#98df8a', '#ff9896', '#c5b0d5', '#f7b6d2', '#dbdb8d', '#9edae5'],
+        darkNorm = ['#1f77b4', '#2ca02c', '#d62728', '#9467bd', '#e377c2', '#bcbd22', '#17becf'],
+        darkMute = ['#aec7e8', '#98df8a', '#ff9896', '#c5b0d5', '#f7b6d2', '#dbdb8d', '#9edae5'];
+
+    function cat7() {
+        var colors = {
+                light: {
+                    norm: d3.scale.ordinal().range(lightNorm),
+                    mute: d3.scale.ordinal().range(lightMute)
+                },
+                dark: {
+                    norm: d3.scale.ordinal().range(darkNorm),
+                    mute: d3.scale.ordinal().range(darkMute)
+                }
+            },
+            tcid = 'd3utilTestCard';
+
+        function get(id, muted, theme) {
+            // NOTE: since we are lazily assigning domain ids, we need to
+            //       get the color from all 4 scales, to keep the domains
+            //       in sync.
+            var ln = colors.light.norm(id),
+                lm = colors.light.mute(id),
+                dn = colors.dark.norm(id),
+                dm = colors.dark.mute(id);
+            if (theme === 'dark') {
+                return muted ? dm : dn;
+            } else {
+                return muted ? lm : ln;
+            }
+        }
+
+        function testCard(svg) {
+            var g = svg.select('g#' + tcid),
+                dom = d3.range(7),
+                k, muted, theme, what;
+
+            if (!g.empty()) {
+                g.remove();
+
+            } else {
+                g = svg.append('g')
+                    .attr('id', tcid)
+                    .attr('transform', 'scale(4)translate(20,20)');
+
+                for (k=0; k<4; k++) {
+                    muted = k%2;
+                    what = muted ? ' muted' : ' normal';
+                    theme = k < 2 ? 'light' : 'dark';
+                    dom.forEach(function (id, i) {
+                        var x = i * 20,
+                            y = k * 20,
+                            f = get(id, muted, theme);
+                        g.append('circle').attr({
+                            cx: x,
+                            cy: y,
+                            r: 5,
+                            fill: f
+                        });
+                    });
+                    g.append('rect').attr({
+                        x: 140,
+                        y: k * 20 - 5,
+                        width: 32,
+                        height: 10,
+                        rx: 2,
+                        fill: '#888'
+                    });
+                    g.append('text').text(theme + what)
+                        .attr({
+                            x: 142,
+                            y: k * 20 + 2,
+                            fill: 'white'
+                        })
+                        .style('font-size', '4pt');
+                }
+            }
+        }
+
+        return {
+            testCard: testCard,
+            get: get
+        };
+    }
+
     // === register the functions as a library
     onos.ui.addLib('d3util', {
         createDragBehavior: createDragBehavior,
-        appendGlow: appendGlow
+        appendGlow: appendGlow,
+        cat7: cat7
     });
 
 }(ONOS));
diff --git a/web/gui/src/main/webapp/onos2.js b/web/gui/src/main/webapp/onos2.js
index 6db968b..69e2648 100644
--- a/web/gui/src/main/webapp/onos2.js
+++ b/web/gui/src/main/webapp/onos2.js
@@ -93,6 +93,8 @@
                 case 40: return 'downArrow';
                 case 91: return 'cmdLeft';
                 case 93: return 'cmdRight';
+                case 187: return 'equals';
+                case 189: return 'dash';
                 case 191: return 'slash';
                 default:
                     if ((code >= 48 && code <= 57) ||
@@ -446,6 +448,7 @@
             current.theme = (current.theme === 'light') ? 'dark' : 'light';
             body.classed('light dark', false);
             body.classed(current.theme, true);
+            theme(view);
             return true;
         }
 
@@ -546,6 +549,13 @@
             }
         }
 
+        function theme() {
+            // allow current view to react to theme event...
+            if (current.view) {
+                current.view.theme(current.ctx, current.flags);
+            }
+        }
+
         // ..........................................................
         // View class
         //   Captures state information about a view.
@@ -607,7 +617,7 @@
                     dataLoadError: this.dataLoadError,
                     alert: this.alert,
                     flash: this.flash,
-                    theme: this.theme
+                    getTheme: this.getTheme
                 }
             },
 
@@ -672,6 +682,16 @@
                 }
             },
 
+            theme: function (ctx, flags) {
+                var c = ctx | '',
+                    fn = isF(this.cb.theme);
+                traceFn('View.theme', this.vid);
+                if (fn) {
+                    trace('THEME cb for ' + this.vid);
+                    fn(this.token(), c, flags);
+                }
+            },
+
             error: function (ctx, flags) {
                 var c = ctx || '',
                     fn = isF(this.cb.error);
@@ -699,7 +719,7 @@
                 setKeyBindings(keyArg);
             },
 
-            theme: function () {
+            getTheme: function () {
                 return current.theme;
             },
 
diff --git a/web/gui/src/main/webapp/topo2.js b/web/gui/src/main/webapp/topo2.js
index aee4cc0..6407438 100644
--- a/web/gui/src/main/webapp/topo2.js
+++ b/web/gui/src/main/webapp/topo2.js
@@ -136,9 +136,9 @@
     // key bindings
     var keyDispatch = {
         // TODO: remove these "development only" bindings
-        M: testMe,
-        S: injectStartupEvents,
-        space: injectTestEvent,
+        0: testMe,
+        equals: injectStartupEvents,
+        dash: injectTestEvent,
 
         O: [toggleSummary, 'Toggle ONOS summary pane'],
         I: [toggleInstances, 'Toggle ONOS instances pane'],
@@ -189,7 +189,8 @@
         onosOrder = [],
         oiBox,
         oiShowMaster = false,
-        portLabelsOn = false;
+        portLabelsOn = false,
+        cat7 = d3u.cat7();
 
     var hoverModeAll = 1,
         hoverModeFlows = 2,
@@ -240,8 +241,9 @@
     // Key Callbacks
 
     function testMe(view) {
-        //view.alert('Theme is ' + view.theme());
+        //view.alert('Theme is ' + view.getTheme());
         //view.flash('This is some text');
+        cat7.testCard(svg);
     }
 
     function abortIfLive() {
@@ -862,12 +864,17 @@
         return true;
     }
 
+    function colorAffinity(on) {
+        // FIXME: need to code this portion up.
+    }
 
     function toggleInstances() {
         if (!oiBox.isVisible()) {
             oiBox.show();
+            colorAffinity(true);
         } else {
             oiBox.hide();
+            colorAffinity(false);
         }
     }
 
@@ -1123,6 +1130,13 @@
         });
 
         // operate on existing + new onoses here
+        // set the affinity colors...
+        onoses.each(function (d) {
+            var el = d3.select(this),
+                rect = el.select('svg').select('rect'),
+                col = instColor(d.id, d.online);
+            rect.style('fill', col);
+        });
 
         // adjust the panel size appropriately...
         oiBox.width(instDim.w * onosOrder.length);
@@ -1132,6 +1146,10 @@
         onoses.exit().remove();
     }
 
+    function instColor(id, online) {
+        return cat7.get(id, !online, network.view.getTheme());
+    }
+
     function clickInst(d) {
         var el = d3.select(this),
             aff = el.classed('affinity');
@@ -2571,6 +2589,12 @@
             .select('g').attr('transform', birdTranslate(w, h));
     }
 
+    function theme(view, ctx, flags) {
+        updateInstances();
+        // TODO: update other theme-affected elements
+
+    }
+
     function birdTranslate(w, h) {
         var bdim = config.birdDim;
         return 'translate('+((w-bdim)*.4)+','+((h-bdim)*.1)+')';
@@ -2583,7 +2607,8 @@
         preload: preload,
         load: load,
         unload: unload,
-        resize: resize
+        resize: resize,
+        theme: theme
     });
 
     summaryPane = onos.ui.addFloatingPanel('topo-summary');