Topo2 Uses multiple ForceG layers for region animations
Topo2 corrected injected deps naming inconsistencies
Topo2 Region navigation comment fixes
Refactored Topo2Layout
Changed SVG 'g' elements to use class names
Center Layout on Region Navigation
Upgraded D3 to patch the force layout end event
Fix - No enhance on link hover if port highlight is disabled
Fix - Link selection labels for A/B Label and A/B Port properties
Refactored Topo2Layout link selection to be part of Topo2SelectService
Linted Topo2 Javascript
Refactored Topo2RegionService

Change-Id: I0e3a22fbc85df99af94fabd3e45191a95ee502b6
diff --git a/web/gui/src/main/webapp/app/fw/nav/nav.js b/web/gui/src/main/webapp/app/fw/nav/nav.js
index 6dbd3d3..07a5603 100644
--- a/web/gui/src/main/webapp/app/fw/nav/nav.js
+++ b/web/gui/src/main/webapp/app/fw/nav/nav.js
@@ -31,7 +31,6 @@
         d3.select('#nav').style('visibility', vis);
     }
 
-
     function showNav() {
         navShown = true;
         updatePane();
diff --git a/web/gui/src/main/webapp/app/fw/widget/listBuilder.js b/web/gui/src/main/webapp/app/fw/widget/listBuilder.js
index 113cb00..dbccf97 100644
--- a/web/gui/src/main/webapp/app/fw/widget/listBuilder.js
+++ b/web/gui/src/main/webapp/app/fw/widget/listBuilder.js
@@ -55,6 +55,8 @@
     angular.module('onosWidget')
     .factory('ListService', [
         function () {
-            return listProps;
+            return {
+                listProps: listProps
+            };
         }]);
 }());
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2.js b/web/gui/src/main/webapp/app/view/topo2/topo2.js
index 6845aa0..e37872f 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2.js
@@ -79,9 +79,8 @@
     // === Controller Definition -----------------------------------------
 
     angular.module('ovTopo2', ['onosUtil', 'onosSvg', 'onosRemote'])
-    .controller('OvTopo2Ctrl',
-        ['$scope', '$log', '$location',
-        'FnService', 'MastService', 'KeyService',
+    .controller('OvTopo2Ctrl', [
+        '$scope', '$log', '$location', 'FnService', 'MastService', 'KeyService',
         'GlyphService', 'MapService', 'SvgUtilService', 'FlashService',
         'WebSocketService', 'PrefsService', 'ThemeService',
         'Topo2EventService', 'Topo2ForceService', 'Topo2InstanceService',
@@ -193,9 +192,6 @@
                 }
             );
 
-            // initialize the force layout, ready to render the topology
-            forceG = zoomLayer.append('g').attr('id', 'topo-force');
-
             t2fs.init(svg, forceG, uplink, dim, zoomer);
             t2bcs.init();
             t2kcs.init(t2fs);
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Breadcrumb.js b/web/gui/src/main/webapp/app/view/topo2/topo2Breadcrumb.js
index 78e486c..84421c9 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Breadcrumb.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Breadcrumb.js
@@ -27,7 +27,8 @@
 
     // Internal
     var breadcrumbContainer,
-        breadcrumbs;
+        breadcrumbs,
+        layout;
 
     function init() {
         $log.debug("Topo2BreadcrumbService Initiated");
@@ -61,6 +62,9 @@
             rid: data.id
         });
 
+        layout.createForceElements();
+        layout.transitionDownRegion();
+
         render();
     }
 
@@ -101,13 +105,15 @@
             .styleTween('transform', function (d) {
                 return translateInterpolator;
             });
+    }
 
+    function addLayout(_layout_) {
+        layout = _layout_;
     }
 
     angular.module('ovTopo2')
-    .factory('Topo2BreadcrumbService',
-        ['$log', 'WebSocketService',
-
+    .factory('Topo2BreadcrumbService', [
+        '$log', 'WebSocketService',
         function (_$log_, _wss_) {
 
             $log = _$log_;
@@ -116,8 +122,9 @@
             return {
                 init: init,
                 addBreadcrumb: addBreadcrumb,
+                addLayout: addLayout,
                 hide: hide
             };
-        }]);
-
+        }
+    ]);
 })();
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Collection.js b/web/gui/src/main/webapp/app/view/topo2/topo2Collection.js
index ab684eb..a0dd430 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Collection.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Collection.js
@@ -22,8 +22,7 @@
 (function () {
     'use strict';
 
-    var Model,
-        extend;
+    var Model;
 
     function Collection(models, options) {
 
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2D3.js b/web/gui/src/main/webapp/app/view/topo2/topo2D3.js
index 9123ac7..932101c 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2D3.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2D3.js
@@ -39,14 +39,16 @@
     }
 
     angular.module('ovTopo2')
-    .factory('Topo2D3Service',
-    [function (_is_) {
-        return {
-            nodeEnter: nodeEnter,
-            nodeExit: nodeExit,
-            hostEnter: hostEnter,
-            linkEntering: linkEntering
-        };
-    }]
+    .factory('Topo2D3Service', [
+
+        function (_is_) {
+            return {
+                nodeEnter: nodeEnter,
+                nodeExit: nodeExit,
+                hostEnter: hostEnter,
+                linkEntering: linkEntering
+            };
+        }
+    ]
 );
 })();
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2DetailsPanel.js b/web/gui/src/main/webapp/app/view/topo2/topo2DetailsPanel.js
index 3bea0bc..16ab45b8 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2DetailsPanel.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2DetailsPanel.js
@@ -51,8 +51,8 @@
     }
 
     angular.module('ovTopo2')
-    .factory('Topo2DetailsPanelService',
-    ['Topo2PanelService',
+    .factory('Topo2DetailsPanelService', [
+        'Topo2PanelService',
         function (_ps_) {
 
             Panel = _ps_;
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2DeviceDetailsPanel.js b/web/gui/src/main/webapp/app/view/topo2/topo2DeviceDetailsPanel.js
index 34645f8..e8778ed 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2DeviceDetailsPanel.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2DeviceDetailsPanel.js
@@ -23,7 +23,7 @@
     'use strict';
 
     // Injected Services
-    var Panel, gs, wss, flash, bs, fs, ns, listProps;
+    var panel, gs, wss, flash, bs, fs, ns, ls;
 
     // Internal State
     var detailsPanel;
@@ -65,7 +65,7 @@
     function init() {
 
         bindHandlers();
-        detailsPanel = Panel();
+        detailsPanel = panel();
     }
 
     function addBtnFooter() {
@@ -114,7 +114,7 @@
         gs.addGlyph(svg, (data.type || 'unknown'), 26);
         title.text(data.title);
 
-        listProps(tbody, data);
+        ls.listProps(tbody, data);
         addBtnFooter();
     }
 
@@ -187,19 +187,20 @@
     }
 
     angular.module('ovTopo2')
-    .factory('Topo2DeviceDetailsPanel',
-    ['Topo2DetailsPanelService', 'GlyphService', 'WebSocketService', 'FlashService',
-    'ButtonService', 'FnService', 'NavService', 'ListService', 
-        function (_ps_, _gs_, _wss_, _flash_, _bs_, _fs_, _ns_, _listService_) {
+    .factory('Topo2DeviceDetailsPanel', [
+        'Topo2DetailsPanelService', 'GlyphService', 'WebSocketService', 'FlashService',
+        'ButtonService', 'FnService', 'NavService', 'ListService',
 
-            Panel = _ps_;
+        function (_ps_, _gs_, _wss_, _flash_, _bs_, _fs_, _ns_, _ls_) {
+
+            panel = _ps_;
             gs = _gs_;
             wss = _wss_;
             flash = _flash_;
             bs = _bs_;
             fs = _fs_;
             ns = _ns_;
-            listProps = _listService_;
+            ls = _ls_;
 
             return {
                 init: init,
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Dialog.js b/web/gui/src/main/webapp/app/view/topo2/topo2Dialog.js
index 13da1a8..c7661fc 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Dialog.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Dialog.js
@@ -31,8 +31,8 @@
     // ==========================
 
     angular.module('ovTopo2')
-    .factory('Topo2DialogService',
-        ['DialogService',
+    .factory('Topo2DialogService', [
+        'DialogService',
 
         function (ds) {
             return {
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Event.js b/web/gui/src/main/webapp/app/view/topo2/topo2Event.js
index 138d866..76a573e 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Event.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Event.js
@@ -84,8 +84,8 @@
     // ========================== Main Service Definition
 
     angular.module('ovTopo2')
-    .factory('Topo2EventService',
-        ['$log', 'WebSocketService', 'Topo2ForceService',
+    .factory('Topo2EventService', [
+        '$log', 'WebSocketService', 'Topo2ForceService',
 
         function (_$log_, _wss_, _t2fs_) {
             $log = _$log_;
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Force.js b/web/gui/src/main/webapp/app/view/topo2/topo2Force.js
index 57a5f89..0c1ffb7 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Force.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Force.js
@@ -26,7 +26,7 @@
     var $log,
         wss;
 
-    var t2is, t2rs, t2ls, t2vs, t2bcs;
+    var t2is, t2rs, t2ls, t2vs, t2bcs, t2ss;
     var svg, forceG, uplink, dim, opts, zoomer;
 
     // D3 Selections
@@ -34,15 +34,17 @@
 
     // ========================== Helper Functions
 
-    function init(_svg_, _forceG_, _uplink_, _dim_, _zoomer_, _opts_) {
+    function init(_svg_, _forceG_, _uplink_, _dim_, zoomer, _opts_) {
         svg = _svg_;
         forceG = _forceG_;
         uplink = _uplink_;
         dim = _dim_;
         opts = _opts_;
-        zoomer = _zoomer_;
 
-        t2ls.init(svg, forceG, uplink, dim, zoomer, opts);
+        t2ls = t2ls(svg, forceG, uplink, dim, zoomer, opts);
+        t2bcs.addLayout(t2ls);
+        t2rs.layout = t2ls;
+        t2ss.init(svg, zoomer);
     }
 
     function destroy() {
@@ -233,12 +235,12 @@
     }
 
     angular.module('ovTopo2')
-    .factory('Topo2ForceService',
-        ['$log', 'WebSocketService', 'Topo2InstanceService',
+    .factory('Topo2ForceService', [
+        '$log', 'WebSocketService', 'Topo2InstanceService',
         'Topo2RegionService', 'Topo2LayoutService', 'Topo2ViewService',
-        'Topo2BreadcrumbService', 'Topo2ZoomService',
+        'Topo2BreadcrumbService', 'Topo2ZoomService', 'Topo2SelectService',
         function (_$log_, _wss_, _t2is_, _t2rs_, _t2ls_,
-            _t2vs_, _t2bcs_, zoomService) {
+            _t2vs_, _t2bcs_, zoomService, _t2ss_) {
 
             $log = _$log_;
             wss = _wss_;
@@ -247,6 +249,7 @@
             t2ls = _t2ls_;
             t2vs = _t2vs_;
             t2bcs = _t2bcs_;
+            t2ss = _t2ss_;
 
             var onZoom = function () {
                 var nodes = [].concat(
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Host.js b/web/gui/src/main/webapp/app/view/topo2/topo2Host.js
index ee13581..40755fe 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Host.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Host.js
@@ -48,12 +48,12 @@
     angular.module('ovTopo2')
     .factory('Topo2HostService', [
         'Topo2Collection', 'Topo2NodeModel', 'Topo2ViewService',
-        'IconService', 'Topo2ZoomService', 'Topo2HostsPanelService', 
-        function (_Collection_, _NodeModel_, _t2vs_, is, zs, t2hds) {
+        'IconService', 'Topo2ZoomService', 'Topo2HostsPanelService',
+        function (_c_, NodeModel, _t2vs_, is, zs, t2hds) {
 
-            Collection = _Collection_;
+            Collection = _c_;
 
-            Model = _NodeModel_.extend({
+            Model = NodeModel.extend({
                 initialize: function () {
                     this.super = this.constructor.__super__;
                     this.super.initialize.apply(this, arguments);
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2HostsPanel.js b/web/gui/src/main/webapp/app/view/topo2/topo2HostsPanel.js
index b131729..d6238a2 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2HostsPanel.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2HostsPanel.js
@@ -23,13 +23,13 @@
     'use strict';
 
     // Injected Services
-    var Panel, gs, wss, flash, listProps;
+    var panel, gs, flash, ls;
 
     // Internal State
     var hostPanel, hostData;
 
     function init() {
-        hostPanel = Panel();
+        hostPanel = panel();
     }
 
     function formatHostData(data) {
@@ -40,12 +40,12 @@
                 '-': '',
                 'MAC': data.get('id'),
                 'IP': data.get('ips')[0],
-                'VLAN': 'None', // TODO
+                'VLAN': 'None', // TODO: VLAN is not currently in the data received from backend
                 'Latitude': data.get('location').lat,
-                'Longitude': data.get('location').lng,
+                'Longitude': data.get('location').lng
             }
-        }
-    };
+        };
+    }
 
     function displayPanel(data) {
         init();
@@ -67,7 +67,7 @@
 
         title.text(hostData.title);
         gs.addGlyph(svg, 'bird', 24, 0, [1, 1]);
-        listProps(tbody, hostData);
+        ls.listProps(tbody, hostData);
     }
 
     function show() {
@@ -89,15 +89,14 @@
     }
 
     angular.module('ovTopo2')
-    .factory('Topo2HostsPanelService',
-    ['Topo2DetailsPanelService', 'GlyphService', 'WebSocketService', 'FlashService', 'ListService',
-        function (_ps_, _gs_, _wss_, _flash_, _listService_) {
+    .factory('Topo2HostsPanelService', [
+        'Topo2DetailsPanelService', 'GlyphService', 'FlashService', 'ListService',
+        function (_ps_, _gs_, _wss_, _flash_, _ls_) {
 
-            Panel = _ps_;
+            panel = _ps_;
             gs = _gs_;
-            wss = _wss_;
             flash = _flash_;
-            listProps = _listService_;
+            ls = _ls_;
 
             return {
                 displayPanel: displayPanel,
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Instance.js b/web/gui/src/main/webapp/app/view/topo2/topo2Instance.js
index abf5d11..307c2db 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Instance.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Instance.js
@@ -269,25 +269,26 @@
     }
 
     angular.module('ovTopo2')
-        .factory('Topo2InstanceService',
-        ['$log', 'PanelService', 'SvgUtilService', 'GlyphService', 'FlashService',
-        'ThemeService',
+        .factory('Topo2InstanceService', [
+            '$log', 'PanelService', 'SvgUtilService', 'GlyphService',
+            'FlashService', 'ThemeService',
 
-        function (_$log_, _ps_, _sus_, _gs_, _flash_, _ts_) {
-            $log = _$log_;
-            ps = _ps_;
-            sus = _sus_;
-            gs = _gs_;
-            flash = _flash_;
-            ts = _ts_;
+            function (_$log_, _ps_, _sus_, _gs_, _flash_, _ts_) {
+                $log = _$log_;
+                ps = _ps_;
+                sus = _sus_;
+                gs = _gs_;
+                flash = _flash_;
+                ts = _ts_;
 
-            return {
-                initInst: initInst,
-                allInstances: allInstances,
-                destroy: destroy,
-                toggle: toggle,
-                isVisible: function () { return oiBox.isVisible(); }
-            };
-        }]);
+                return {
+                    initInst: initInst,
+                    allInstances: allInstances,
+                    destroy: destroy,
+                    toggle: toggle,
+                    isVisible: function () { return oiBox.isVisible(); }
+                };
+            }
+        ]);
 
 })();
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2KeyCommands.js b/web/gui/src/main/webapp/app/view/topo2/topo2KeyCommands.js
index 02aa984..4f65679 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2KeyCommands.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2KeyCommands.js
@@ -91,7 +91,7 @@
 
     function cycleDeviceLabels() {
         var deviceLabelIndex = t2ps.get('dlbls') + 1,
-            newDeviceLabelIndex =  deviceLabelIndex % 3;
+            newDeviceLabelIndex = deviceLabelIndex % 3;
 
         t2ps.set('dlbls', newDeviceLabelIndex);
         t2fs.updateNodes();
@@ -140,13 +140,12 @@
     }
 
     angular.module('ovTopo2')
-    .factory('Topo2KeyCommandService',
-    ['KeyService', 'FlashService', 'WebSocketService', 'Topo2PrefsService',
-    'Topo2MapService', 'PrefsService', 'Topo2InstanceService',
-    'Topo2SummaryPanelService', 'Topo2DeviceDetailsPanel', 'Topo2ViewService',
-    'Topo2RegionService',
+    .factory('Topo2KeyCommandService', [
+        'KeyService', 'FlashService', 'WebSocketService', 'Topo2PrefsService',
+        'Topo2MapService', 'PrefsService', 'Topo2InstanceService',
+        'Topo2SummaryPanelService', 'Topo2ViewService', 'Topo2RegionService',
         function (_ks_, _flash_, _wss_, _t2ps_, _t2ms_, _ps_, _t2is_, _t2sp_,
-                  _t2ddp_, _t2vs_, _t2rs_) {
+                  _t2vs_, _t2rs_) {
 
             ks = _ks_;
             flash = _flash_;
@@ -156,7 +155,6 @@
             t2is = _t2is_;
             ps = _ps_;
             t2sp = _t2sp_;
-            t2ddp = _t2ddp_;
             t2vs = _t2vs_;
             t2rs = _t2rs_;
 
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Layout.js b/web/gui/src/main/webapp/app/view/topo2/topo2Layout.js
index 6e5df6c..9849393 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Layout.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Layout.js
@@ -22,10 +22,7 @@
 (function () {
     'use strict';
 
-    var $log, wss, sus, t2rs, t2d3, t2vs, t2ss;
-
-    var linkG, linkLabelG, nodeG;
-    var link, node, zoomer;
+    var instance;
 
     // default settings for force layout
     var defaultSettings = {
@@ -73,246 +70,14 @@
     };
 
     // internal state
-    var settings,               // merged default settings and options
-        force,                  // force layout object
-        drag,                   // drag behavior handler
-        previousNearestLink,    // previous link to mouse position
-        nodeLock = false;       // whether nodes can be dragged or not (locked)
-
-
-    function init(_svg_, forceG, _uplink_, _dim_, _zoomer_, opts) {
-
-        $log.debug("Initialising Topology Layout");
-        settings = angular.extend({}, defaultSettings, opts);
-
-        linkG = forceG.append('g').attr('id', 'topo-links');
-        linkLabelG = forceG.append('g').attr('id', 'topo-linkLabels');
-        forceG.append('g').attr('id', 'topo-numLinkLabels');
-        nodeG = forceG.append('g').attr('id', 'topo-nodes');
-        forceG.append('g').attr('id', 'topo-portLabels');
-
-        link = linkG.selectAll('.link');
-        linkLabelG.selectAll('.linkLabel');
-        node = nodeG.selectAll('.node');
-
-        zoomer = _zoomer_;
-        _svg_.on('mousemove', mouseMoveHandler);
-        _svg_.on('click', mouseClickHandler);
-    }
-
-    function getDeviceChargeForType(node) {
-
-        var nodeType = node.get('nodeType');
-
-        return settings.charge[nodeType] ||
-            settings.charge._def_;
-    }
-
-    function getLinkDistanceForLinkType(node) {
-        var nodeType = node.get('type');
-
-        return settings.linkDistance[nodeType] ||
-            settings.linkDistance._def_;
-    }
-
-    function getLinkStrenghForLinkType(node) {
-        var nodeType = node.get('type');
-
-        return settings.linkStrength[nodeType] ||
-            settings.linkStrength._def_;
-    }
-
-    function createForceLayout() {
-
-        var regionLinks = t2rs.regionLinks(),
-            regionNodes = t2rs.regionNodes();
-
-        force = d3.layout.force()
-            .size(t2vs.getDimensions())
-            .gravity(settings.gravity)
-            .friction(settings.friction)
-            .charge(getDeviceChargeForType)
-            .linkDistance(getLinkDistanceForLinkType)
-            .linkStrength(getLinkStrenghForLinkType)
-            .on("tick", tick);
-
-        force
-            .nodes(t2rs.regionNodes())
-            .links(regionLinks)
-            .start();
-
-        link = linkG.selectAll('.link')
-            .data(regionLinks, function (d) { return d.get('key'); });
-
-        node = nodeG.selectAll('.node')
-            .data(regionNodes, function (d) { return d.get('id'); });
-
-        drag = sus.createDragBehavior(force,
-          t2ss.selectObject, atDragEnd, dragEnabled, clickEnabled);
-
-        update();
-    }
+    var nodeLock = false;       // whether nodes can be dragged or not (locked)
 
     // predicate that indicates when clicking is active
     function clickEnabled() {
         return true;
     }
 
-    function zoomingOrPanning(ev) {
-        return ev.metaKey || ev.altKey;
-    }
-
-    function atDragEnd(d) {
-        // once we've finished moving, pin the node in position
-        d.fixed = true;
-        d3.select(this).classed('fixed', true);
-        sendUpdateMeta(d);
-        $log.debug(d);
-        t2ss.clickConsumed(true);
-    }
-
-    // predicate that indicates when dragging is active
-    function dragEnabled() {
-        var ev = d3.event.sourceEvent;
-        // nodeLock means we aren't allowing nodes to be dragged...
-        return !nodeLock && !zoomingOrPanning(ev);
-    }
-
-    function sendUpdateMeta(d, clearPos) {
-        var metaUi = {},
-            ll;
-
-        // if we are not clearing the position data (unpinning),
-        // attach the x, y, (and equivalent longitude, latitude)...
-        if (!clearPos) {
-            ll = d.lngLatFromCoord([d.x, d.y]);
-            metaUi = {
-                x: d.x,
-                y: d.y,
-                equivLoc: {
-                    lng: ll[0],
-                    lat: ll[1]
-                }
-            };
-        }
-        d.metaUi = metaUi;
-        wss.sendEvent('updateMeta2', {
-            id: d.get('id'),
-            class: d.get('class'),
-            memento: metaUi
-        });
-    }
-
-    function tick() {
-        link
-            .attr("x1", function (d) { return d.source.x; })
-            .attr("y1", function (d) { return d.source.y; })
-            .attr("x2", function (d) { return d.target.x; })
-            .attr("y2", function (d) { return d.target.y; });
-
-        node
-            .attr({
-                transform: function (d) {
-                    var dx = isNaN(d.x) ? 0 : d.x,
-                        dy = isNaN(d.y) ? 0 : d.y;
-                    return sus.translate(dx, dy);
-                }
-            });
-    }
-
-    function update() {
-        _updateNodes();
-        _updateLinks();
-    }
-
-    function _updateNodes() {
-
-        var regionNodes = t2rs.regionNodes();
-
-        // select all the nodes in the layout:
-        node = nodeG.selectAll('.node')
-            .data(regionNodes, function (d) { return d.get('id'); });
-
-        var entering = node.enter()
-            .append('g')
-            .attr({
-                id: function (d) { return sus.safeId(d.get('id')); },
-                class: function (d) { return d.svgClassName(); },
-                transform: function (d) {
-                    // Need to guard against NaN here ??
-                    return sus.translate(d.node.x, d.node.y);
-                },
-                opacity: 0
-            })
-            .call(drag)
-            .transition()
-            .attr('opacity', 1);
-
-        entering.filter('.device').each(t2d3.nodeEnter);
-        entering.filter('.sub-region').each(t2d3.nodeEnter);
-        entering.filter('.host').each(t2d3.hostEnter);
-
-        // operate on exiting nodes:
-        // Note that the node is removed after 2 seconds.
-        // Sub element animations should be shorter than 2 seconds.
-        var exiting = node.exit()
-            .transition()
-            .duration(300)
-            .style('opacity', 0)
-            .remove();
-
-        // exiting node specifics:
-        // exiting.filter('.host').each(t2d3.hostExit);
-        exiting.filter('.device').each(t2d3.nodeExit);
-    }
-
-    function _updateLinks() {
-
-        // var th = ts.theme();
-        var regionLinks = t2rs.regionLinks();
-
-        link = linkG.selectAll('.link')
-            .data(regionLinks, function (d) { return d.get('key'); });
-
-        // operate on entering links:
-        var entering = link.enter()
-            .append('line')
-            .call(calcPosition)
-            .attr({
-                x1: function (d) { return d.get('position').x1; },
-                y1: function (d) { return d.get('position').y1; },
-                x2: function (d) { return d.get('position').x2; },
-                y2: function (d) { return d.get('position').y2; },
-                stroke: linkConfig.light.inColor,
-                'stroke-width': linkConfig.inWidth
-            });
-
-        entering.each(t2d3.linkEntering);
-
-        // operate on exiting links:
-        link.exit()
-            .style('opacity', 1)
-            .transition()
-            .duration(300)
-            .style('opacity', 0.0)
-            .remove();
-    }
-
-    function calcPosition() {
-        var lines = this;
-
-        lines.each(function (d) {
-            if (d.get('type') === 'hostLink') {
-                d.set('position', getDefaultPos(d));
-            }
-        });
-
-        lines.each(function (d) {
-            d.set('position', getDefaultPos(d));
-        });
-    }
-
-    function getDefaultPos(link) {
+    function getDefaultPosition(link) {
         return {
             x1: link.get('source').x,
             y1: link.get('source').y,
@@ -321,156 +86,293 @@
         };
     }
 
-    function setDimensions() {
-        if (force) {
-            force.size(t2vs.getDimensions());
-        }
-    }
-
-    function start() {
-        force.start();
-    }
-
-    function mouseClickHandler() {
-
-        if (!d3.event.shiftKey) {
-            t2rs.deselectLink();
-        }
-
-        if (!t2ss.clickConsumed()) {
-            if (previousNearestLink) {
-                previousNearestLink.select();
-            }
-        }
-
-    }
-
-    // Select Links
-    function mouseMoveHandler() {
-        var mp = getLogicalMousePosition(this),
-            link = computeNearestLink(mp);
-
-        // link.enhance();
-        if (link) {
-            if (previousNearestLink && previousNearestLink != link) {
-                previousNearestLink.unenhance();
-            }
-            link.enhance();
-        } else {
-            if (previousNearestLink) {
-                previousNearestLink.unenhance();
-            }
-        }
-
-        previousNearestLink = link;
-    }
-
-
-    function getLogicalMousePosition(container) {
-        var m = d3.mouse(container),
-            sc = zoomer.scale(),
-            tr = zoomer.translate(),
-            mx = (m[0] - tr[0]) / sc,
-            my = (m[1] - tr[1]) / sc;
-        return {x: mx, y: my};
-    }
-
-    function sq(x) { return x * x; }
-
-    function mdist(p, m) {
-        return Math.sqrt(sq(p.x - m.x) + sq(p.y - m.y));
-    }
-
-    function prox(dist) {
-        return dist / zoomer.scale();
-    }
-
-    function computeNearestLink(mouse) {
-        var proximity = prox(30),
-            nearest = null,
-            minDist;
-
-        function pdrop(line, mouse) {
-            var x1 = line.x1,
-                y1 = line.y1,
-                x2 = line.x2,
-                y2 = line.y2,
-                x3 = mouse.x,
-                y3 = mouse.y,
-                k = ((y2-y1) * (x3-x1) - (x2-x1) * (y3-y1)) /
-                    (sq(y2-y1) + sq(x2-x1)),
-                x4 = x3 - k * (y2-y1),
-                y4 = y3 + k * (x2-x1);
-            return {x:x4, y:y4};
-        }
-
-        function lineHit(line, p, m) {
-            if (p.x < line.x1 && p.x < line.x2) return false;
-            if (p.x > line.x1 && p.x > line.x2) return false;
-            if (p.y < line.y1 && p.y < line.y2) return false;
-            if (p.y > line.y1 && p.y > line.y2) return false;
-            // line intersects, but are we close enough?
-            return mdist(p, m) <= proximity;
-        }
-
-        var links = t2rs.regionLinks();
-
-        if (links.length) {
-            minDist = proximity * 2;
-
-            links.forEach(function (d) {
-                var line = d.get('position'),
-                    point,
-                    hit,
-                    dist;
-
-                // TODO: Reinstate when showHost() is implemented
-                // if (!api.showHosts() && d.type() === 'hostLink') {
-                //     return; // skip hidden host links
-                // }
-
-                if (line) {
-                    point = pdrop(line, mouse);
-                    hit = lineHit(line, point, mouse);
-                    if (hit) {
-                        dist = mdist(point, mouse);
-                        if (dist < minDist) {
-                            minDist = dist;
-                            nearest = d;
-                        }
-                    }
-                }
-            });
-        }
-
-        return nearest;
-    }
-
     angular.module('ovTopo2')
     .factory('Topo2LayoutService',
         [
             '$log', 'WebSocketService', 'SvgUtilService', 'Topo2RegionService',
-            'Topo2D3Service', 'Topo2ViewService', 'Topo2SelectService',
+            'Topo2D3Service', 'Topo2ViewService', 'Topo2SelectService', 'Topo2ZoomService',
+            'Topo2ViewController',
+            function ($log, wss, sus, t2rs, t2d3, t2vs, t2ss, t2zs,
+                      ViewController) {
 
-            function (_$log_, _wss_, _sus_, _t2rs_, _t2d3_, _t2vs_, _t2ss_) {
+                var Layout = ViewController.extend({
+                    initialize: function (svg, forceG, uplink, dim, zoomer, opts) {
 
-                $log = _$log_;
-                wss = _wss_;
-                t2rs = _t2rs_;
-                t2d3 = _t2d3_;
-                t2vs = _t2vs_;
-                t2ss = _t2ss_;
-                sus = _sus_;
+                        $log.debug('initialize Layout');
+                        instance = this;
 
-                return {
-                    init: init,
-                    createForceLayout: createForceLayout,
-                    update: update,
-                    tick: tick,
-                    start: start,
+                        this.svg = svg;
 
-                    setDimensions: setDimensions
-                };
+                        // Append all the SVG Group elements to the forceG object
+                        this.createForceElements();
+
+                        this.uplink = uplink;
+                        this.dim = dim;
+                        this.zoomer = zoomer;
+
+                        this.settings = angular.extend({}, defaultSettings, opts);
+
+                        this.link = this.elements.linkG.selectAll('.link');
+                        this.elements.linkLabelG.selectAll('.linkLabel');
+                        this.node = this.elements.nodeG.selectAll('.node');
+                    },
+                    createForceElements: function () {
+
+                        this.prevForce = this.forceG;
+
+                        this.forceG = d3.select('#topo-zoomlayer')
+                            .append('g').attr('class', 'topo-force');
+
+                        this.elements = {
+                            linkG: this.addElement(this.forceG, 'topo-links'),
+                            linkLabelG: this.addElement(this.forceG, 'topo-linkLabels'),
+                            numLinksLabels: this.addElement(this.forceG, 'topo-numLinkLabels'),
+                            nodeG: this.addElement(this.forceG, 'topo-nodes'),
+                            portLabels: this.addElement(this.forceG, 'topo-portLabels')
+                        };
+                    },
+                    addElement: function (parent, className) {
+                        return parent.append('g').attr('class', className);
+                    },
+                    settingOrDefault: function (settingName, node) {
+                        var nodeType = node.get('nodeType');
+                        return this.settings[settingName][nodeType] || this.settings[settingName]._def_;
+                    },
+                    createForceLayout: function () {
+                        var _this = this,
+                            regionLinks = t2rs.regionLinks(),
+                            regionNodes = t2rs.regionNodes();
+
+                        this.force = d3.layout.force()
+                            .size(t2vs.getDimensions())
+                            .gravity(this.settings.gravity)
+                            .friction(this.settings.friction)
+                            .charge(this.settingOrDefault.bind(this, 'charge'))
+                            .linkDistance(this.settingOrDefault.bind(this, 'linkDistance'))
+                            .linkStrength(this.settingOrDefault.bind(this, 'linkStrength'))
+                            .nodes(regionNodes)
+                            .links(regionLinks)
+                            .on("tick", this.tick.bind(this))
+                            .on("start", function () {
+
+                                // TODO: Find a better way to do this
+                                setTimeout(function () {
+                                    _this.centerLayout();
+                                }, 500);
+                            })
+                            .start();
+
+                        this.link = this.elements.linkG.selectAll('.link')
+                            .data(regionLinks, function (d) { return d.get('key'); });
+
+                        this.node = this.elements.nodeG.selectAll('.node')
+                            .data(regionNodes, function (d) { return d.get('id'); });
+
+                        this.drag = sus.createDragBehavior(this.force,
+                            t2ss.selectObject,
+                            this.atDragEnd,
+                            this.dragEnabled.bind(this),
+                            clickEnabled
+                        );
+
+                        this.update();
+                    },
+                    centerLayout: function () {
+                        d3.select('#topo-zoomlayer').attr('data-layout', t2rs.model.get('id'));
+
+                        var zoomer = d3.select('#topo-zoomlayer').node().getBBox(),
+                            layoutBBox = this.forceG.node().getBBox(),
+                            scale = (zoomer.height - 150) / layoutBBox.height,
+                            x = (zoomer.width / 2) - ((layoutBBox.x + layoutBBox.width / 2) * scale),
+                            y = (zoomer.height / 2) - ((layoutBBox.y + layoutBBox.height / 2) * scale);
+
+                        t2zs.panAndZoom([x, y], scale, 1000);
+                    },
+                    tick: function () {
+                        this.link
+                            .attr("x1", function (d) { return d.source.x; })
+                            .attr("y1", function (d) { return d.source.y; })
+                            .attr("x2", function (d) { return d.target.x; })
+                            .attr("y2", function (d) { return d.target.y; });
+
+                        this.node
+                            .attr({
+                                transform: function (d) {
+                                    var dx = isNaN(d.x) ? 0 : d.x,
+                                        dy = isNaN(d.y) ? 0 : d.y;
+                                    return sus.translate(dx, dy);
+                                }
+                            });
+                    },
+
+                    start: function () {
+                        this.force.start();
+                    },
+                    update: function () {
+                        this.updateNodes();
+                        this.updateLinks();
+                    },
+                    updateNodes: function () {
+                        var regionNodes = t2rs.regionNodes();
+
+                        // select all the nodes in the layout:
+                        this.node = this.elements.nodeG.selectAll('.node')
+                            .data(regionNodes, function (d) { return d.get('id'); });
+
+                        var entering = this.node.enter()
+                            .append('g')
+                            .attr({
+                                id: function (d) { return sus.safeId(d.get('id')); },
+                                class: function (d) { return d.svgClassName(); },
+                                transform: function (d) {
+                                    // Need to guard against NaN here ??
+                                    return sus.translate(d.node.x, d.node.y);
+                                },
+                                opacity: 0
+                            })
+                            .call(this.drag)
+                            .transition()
+                            .attr('opacity', 1);
+
+                        entering.filter('.device').each(t2d3.nodeEnter);
+                        entering.filter('.sub-region').each(t2d3.nodeEnter);
+                        entering.filter('.host').each(t2d3.hostEnter);
+
+                        // operate on exiting nodes:
+                        // Note that the node is removed after 2 seconds.
+                        // Sub element animations should be shorter than 2 seconds.
+                        // var exiting = this.node.exit()
+                        //     .transition()
+                        //     .duration(300)
+                        //     .style('opacity', 0)
+                        //     .remove();
+
+                        // exiting node specifics:
+                        // exiting.filter('.host').each(t2d3.hostExit);
+                        // exiting.filter('.device').each(t2d3.nodeExit);
+                    },
+                    updateLinks: function () {
+
+                        var regionLinks = t2rs.regionLinks();
+
+                        this.link = this.elements.linkG.selectAll('.link')
+                            .data(regionLinks, function (d) { return d.get('key'); });
+
+                        // operate on entering links:
+                        var entering = this.link.enter()
+                            .append('line')
+                            .call(this.calcPosition)
+                            .attr({
+                                x1: function (d) { return d.get('position').x1; },
+                                y1: function (d) { return d.get('position').y1; },
+                                x2: function (d) { return d.get('position').x2; },
+                                y2: function (d) { return d.get('position').y2; },
+                                stroke: linkConfig.light.inColor,
+                                'stroke-width': linkConfig.inWidth
+                            });
+
+                        entering.each(t2d3.linkEntering);
+
+                        // operate on exiting links:
+                        this.link.exit()
+                            .style('opacity', 1)
+                            .transition()
+                            .duration(300)
+                            .style('opacity', 0.0)
+                            .remove();
+                    },
+                    calcPosition: function () {
+                        var lines = this;
+
+                        lines.each(function (d) {
+                            if (d.get('type') === 'hostLink') {
+                                d.set('position', getDefaultPosition(d));
+                            }
+                        });
+
+                        lines.each(function (d) {
+                            d.set('position', getDefaultPosition(d));
+                        });
+                    },
+                    sendUpdateMeta: function (d, clearPos) {
+                        var metaUi = {},
+                            ll;
+
+                        // if we are not clearing the position data (unpinning),
+                        // attach the x, y, (and equivalent longitude, latitude)...
+                        if (!clearPos) {
+                            ll = d.lngLatFromCoord([d.x, d.y]);
+                            metaUi = {
+                                x: d.x,
+                                y: d.y,
+                                equivLoc: {
+                                    lng: ll[0],
+                                    lat: ll[1]
+                                }
+                            };
+                        }
+                        d.metaUi = metaUi;
+                        wss.sendEvent('updateMeta2', {
+                            id: d.get('id'),
+                            class: d.get('class'),
+                            memento: metaUi
+                        });
+                    },
+                    setDimensions: function () {
+                        if (this.force) {
+                            this.force.size(t2vs.getDimensions());
+                        }
+                    },
+                    dragEnabled: function () {
+                        var ev = d3.event.sourceEvent;
+                        // nodeLock means we aren't allowing nodes to be dragged...
+                        return !nodeLock && !this.zoomingOrPanning(ev);
+                    },
+                    zoomingOrPanning: function (ev) {
+                        return ev.metaKey || ev.altKey;
+                    },
+                    atDragEnd: function (d) {
+                        // once we've finished moving, pin the node in position
+                        d.fixed = true;
+                        d3.select(this).classed('fixed', true);
+                        instance.sendUpdateMeta(d);
+                        $log.debug(d);
+                        t2ss.clickConsumed(true);
+                    },
+                    transitionDownRegion: function () {
+
+                        this.prevForce.transition()
+                            .duration(1500)
+                            .style('opacity', 0)
+                            .remove();
+
+                        this.forceG
+                            .style('opacity', 0)
+                            .transition()
+                            .delay(500)
+                            .duration(500)
+                            .style('opacity', 1);
+                    },
+                    transitionUpRegion: function () {
+                        this.prevForce.transition()
+                            .duration(1000)
+                            .style('opacity', 0)
+                            .remove();
+
+                        this.forceG
+                            .style('opacity', 0)
+                            .transition()
+                            .delay(500)
+                            .duration(500)
+                            .style('opacity', 1);
+                    }
+                });
+
+                function getInstance(svg, forceG, uplink, dim, zoomer, opts) {
+                    return instance || new Layout(svg, forceG, uplink, dim, zoomer, opts);
+                }
+
+                return getInstance;
             }
         ]
     );
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Link.js b/web/gui/src/main/webapp/app/view/topo2/topo2Link.js
index d769631..bcd315d 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Link.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Link.js
@@ -94,9 +94,10 @@
 
     function linkEndPoints(srcId, dstId) {
 
-        var allNodes = this.region.nodes();
-        var sourceNode = this.region.findNodeById(this, srcId);
-        var targetNode = this.region.findNodeById(this, dstId);
+        var findNodeById = this.region.model.findNodeById.bind(this.region),
+            allNodes = this.region.model.nodes(),
+            sourceNode = findNodeById(this, srcId),
+            targetNode = findNodeById(this, dstId);
 
         if (!sourceNode || !targetNode) {
             $log.error('Node(s) not on map for link:' + srcId + '~' + dstId);
@@ -157,13 +158,8 @@
                 var data = [],
                     point;
 
-                // angular.forEach(this.collection.models, function (link) {
-                //     link.unenhance();
-                // });
-
-                this.set('enhanced', true);
-
                 if (showPort()) {
+                    this.set('enhanced', true);
                     point = this.locatePortLabel();
                     angular.extend(point, {
                         id: 'topo-port-tgt',
@@ -185,7 +181,7 @@
                         .data(data)
                         .enter().append('g')
                         .classed('portLabel', true)
-                        .attr('id', function (d) { return d.id; })
+                        .attr('id', function (d) { return d.id; });
 
                     entering.each(function (d) {
                         var el = d3.select(this),
@@ -219,8 +215,6 @@
             },
             select: function () {
 
-                var ev = d3.event;
-
                 // TODO: if single selection clear selected devices, hosts, sub-regions
                 var s = Boolean(this.get('selected'));
                 // Clear all selected Items
@@ -340,27 +334,26 @@
     }
 
     angular.module('ovTopo2')
-    .factory('Topo2LinkService',
-        ['$log', 'Topo2Collection', 'Topo2Model',
+    .factory('Topo2LinkService', [
+        '$log', 'Topo2Collection', 'Topo2Model',
         'ThemeService', 'SvgUtilService', 'Topo2ZoomService',
         'Topo2ViewService', 'Topo2LinkPanelService', 'FnService',
-            function (_$log_, _Collection_, _Model_, _ts_, _sus_,
-                _t2zs_, _t2vs_, _t2lps_, _fn_) {
+        function (_$log_, _c_, _Model_, _ts_, _sus_,
+            _t2zs_, _t2vs_, _t2lps_, _fn_) {
 
-                $log = _$log_;
-                ts = _ts_;
-                sus = _sus_;
-                t2zs = _t2zs_;
-                t2vs = _t2vs_;
-                Collection = _Collection_;
-                Model = _Model_;
-                t2lps = _t2lps_;
-                fn = _fn_;
+            $log = _$log_;
+            ts = _ts_;
+            sus = _sus_;
+            t2zs = _t2zs_;
+            t2vs = _t2vs_;
+            Collection = _c_;
+            Model = _Model_;
+            t2lps = _t2lps_;
+            fn = _fn_;
 
-                return {
-                    createLinkCollection: createLinkCollection
-                };
-            }
-        ]);
-
+            return {
+                createLinkCollection: createLinkCollection
+            };
+        }
+    ]);
 })();
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2LinkPanel.js b/web/gui/src/main/webapp/app/view/topo2/topo2LinkPanel.js
index cb65ced..ea096b7 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2LinkPanel.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2LinkPanel.js
@@ -23,13 +23,13 @@
     'use strict';
 
     // Injected Services
-    var Panel, gs, wss, flash, listProps;
+    var panel, gs, flash, ls;
 
     // Internal State
     var linkPanel, linkData;
 
     function init() {
-        linkPanel = Panel();
+        linkPanel = panel();
     }
 
     function formatLinkData(data) {
@@ -49,15 +49,15 @@
                 'Type': data.get('type'),
                 'A Type': source.get('nodeType'),
                 'A Id': source.get('id'),
-                'A Label': 'Label',
-                'A Port': data.get('portA') || '',
+                'A Label': source.get('props').name,
+                'A Port': data.get('portA') || 'N/A',
                 'B Type': target.get('nodeType'),
                 'B Id': target.get('id'),
-                'B Label': 'Label',
-                'B Port': data.get('portB') || '',
+                'B Label': target.get('props').name,
+                'B Port': data.get('portB') || 'N/A'
             }
-        }
-    };
+        };
+    }
 
     function displayLink(data) {
         init();
@@ -79,7 +79,7 @@
 
         title.text(linkData.title);
         gs.addGlyph(svg, 'bird', 24, 0, [1, 1]);
-        listProps(tbody, linkData);
+        ls.listProps(tbody, linkData);
     }
 
     function show() {
@@ -97,20 +97,18 @@
     }
 
     function destroy() {
-        wss.unbindHandlers(handlerMap);
         linkPanel.destroy();
     }
 
     angular.module('ovTopo2')
-    .factory('Topo2LinkPanelService',
-    ['Topo2DetailsPanelService', 'GlyphService', 'WebSocketService', 'FlashService', 'ListService',
-        function (_ps_, _gs_, _wss_, _flash_, _listService_) {
+    .factory('Topo2LinkPanelService', [
+        'Topo2DetailsPanelService', 'GlyphService', 'FlashService', 'ListService',
+        function (_ps_, _gs_, _flash_, _ls_) {
 
-            Panel = _ps_;
+            panel = _ps_;
             gs = _gs_;
-            wss = _wss_;
             flash = _flash_;
-            listProps = _listService_;
+            ls = _ls_;
 
             return {
                 displayLink: displayLink,
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Model.js b/web/gui/src/main/webapp/app/view/topo2/topo2Model.js
index f76456e..c8430fb 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Model.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Model.js
@@ -22,8 +22,6 @@
 (function () {
     'use strict';
 
-    var extend;
-
     function Model(attributes) {
 
         var attrs = attributes || {};
@@ -125,7 +123,7 @@
         'FnService',
         function (fn) {
             Model.extend = fn.extend;
-            
+
             return Model;
         }
     ]);
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2NodeModel.js b/web/gui/src/main/webapp/app/view/topo2/topo2NodeModel.js
index cf5bb15..7171477 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2NodeModel.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2NodeModel.js
@@ -53,8 +53,8 @@
     }
 
     angular.module('ovTopo2')
-    .factory('Topo2NodeModel',
-        ['Topo2Model', 'FnService', 'Topo2PrefsService',
+    .factory('Topo2NodeModel', [
+        'Topo2Model', 'FnService', 'Topo2PrefsService',
         'SvgUtilService', 'IconService', 'ThemeService',
         'Topo2MapConfigService', 'Topo2ZoomService', 'Topo2NodePositionService',
         function (Model, _fn_, _ps_, _sus_, _is_, _ts_,
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Panel.js b/web/gui/src/main/webapp/app/view/topo2/topo2Panel.js
index 686ec96..88ba678 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Panel.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Panel.js
@@ -23,7 +23,7 @@
     'use strict';
 
     // Injected Services
-    var flash, ps;
+    var ps;
 
     var panel = {
         initialize: function (id, options) {
@@ -61,14 +61,13 @@
         isVisible: function () {
             return this.el.isVisible();
         }
-    }
+    };
 
     angular.module('ovTopo2')
-    .factory('Topo2PanelService',
-    ['Topo2UIView', 'FlashService', 'PanelService',
-        function (View, _flash_, _ps_) {
+    .factory('Topo2PanelService', [
+        'Topo2UIView', 'PanelService',
+        function (View, _ps_) {
 
-            flash = _flash_;
             ps = _ps_;
 
             return View.extend(panel);
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Prefs.js b/web/gui/src/main/webapp/app/view/topo2/topo2Prefs.js
index 9eaef00..690ac59 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Prefs.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Prefs.js
@@ -50,9 +50,8 @@
     }
 
     angular.module('ovTopo2')
-    .factory('Topo2PrefsService',
-    ['PrefsService',
-
+    .factory('Topo2PrefsService', [
+        'PrefsService',
         function (_ps_) {
 
             ps = _ps_;
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Region.js b/web/gui/src/main/webapp/app/view/topo2/topo2Region.js
index b40eff7..09b8cd8 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Region.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Region.js
@@ -23,189 +23,129 @@
     'use strict';
 
     // Injected Services
-    var $log, t2sr, t2ds, t2hs, t2ls, t2zs, t2dps, t2bcs;
     var Model;
 
     // Internal
-    var region
+    var instance
 
     // 'static' vars
     var ROOT = '(root)';
 
-    function init() {}
-
-    function addRegion(data) {
-
-        var RegionModel = Model.extend({
-            findNodeById: findNodeById,
-            nodes: regionNodes
-        });
-
-        region = new RegionModel({
-            id: data.id,
-            layerOrder: data.layerOrder
-        });
-
-        region.set({
-            subregions: t2sr.createSubRegionCollection(data.subregions, region),
-            devices: t2ds.createDeviceCollection(data.devices, region),
-            hosts: t2hs.createHostCollection(data.hosts, region),
-            links: t2ls.createLinkCollection(data.links, region)
-        });
-
-        angular.forEach(region.get('links').models, function (link) {
-            link.createLink();
-        });
-
-        // TODO: replace with an algorithm that computes appropriate transition
-        //        based on the location of the "region node" on the parent map
-
-        // TEMP Map Zoom
-        var regionPanZooms = {
-            "(root)": {
-                scale: 4.21,
-                translate: [-2066.3049871603093, -2130.190726668792]
-            },
-            c01: {
-                scale: 19.8855,
-                translate: [-10375.91165337411, -10862.217941271818]
-            },
-            c02: {
-                scale: 24.25,
-                translate: [-14169.70851936781, -15649.174761455488]
-            },
-            c03: {
-                scale: 22.72,
-                translate: [-14950.92246589002, -15390.955326616648]
-            },
-            c04: {
-                scale: 26.24,
-                translate: [-16664.006814209282, -16217.021478816077]
-            }
-        };
-
-
-        // Hide Breadcrumbs if there are no subregions configured in the root region
-        if (isRootRegion() && !region.get('subregions').models.length) {
-            t2bcs.hide();
-        }
-
-        setTimeout(function () {
-            var regionPZ = regionPanZooms[region.get('id')];
-            t2zs.panAndZoom(regionPZ.translate, regionPZ.scale);
-        }, 10);
-
-        $log.debug('Region: ', region);
-    }
-
-    function findNodeById(link, id) {
-
-
-        if (link.get('type') !== 'UiEdgeLink') {
-            // Remove /{port} from id if needed
-            var regex = new RegExp('^[^/]*');
-            id = regex.exec(id)[0];
-        }
-
-        return region.get('devices').get(id) ||
-            region.get('hosts').get(id) ||
-            region.get('subregions').get(id);
-    }
-
-    function regionNodes() {
-
-        if (region) {
-            return [].concat(
-                region.get('devices').models,
-                region.get('hosts').models,
-                region.get('subregions').models
-            );
-        }
-
-        return [];
-    }
-
-    function filterRegionNodes(predicate) {
-        var nodes = regionNodes();
-        return _.filter(nodes, predicate);
-    }
-
-    function regionLinks() {
-        return (region) ? region.get('links').models : [];
-    }
-
-    function deselectAllNodes() {
-
-        var selected = filterRegionNodes(function (node) {
-            return node.get('selected', true);
-        });
-
-        if (selected.length) {
-
-            selected.forEach(function (node) {
-                node.deselect();
-            });
-
-            t2dps().el.hide();
-            return true;
-        }
-
-        return false;
-    }
-
-    function deselectLink() {
-
-        var selected = _.filter(regionLinks(), function (link) {
-            return link.get('selected', true);
-        });
-
-        if (selected.length) {
-
-            selected.forEach(function (link) {
-                link.deselect();
-            });
-
-            t2dps().el.hide();
-            return true;
-        }
-
-        return false;
-    }
-
-    function isRootRegion() {
-        return region.get('id') === ROOT;
-    }
-
     angular.module('ovTopo2')
-    .factory('Topo2RegionService',
-        ['$log', 'Topo2Model',
-        'Topo2SubRegionService', 'Topo2DeviceService',
+    .factory('Topo2RegionService', [
+        '$log', 'Topo2Model', 'Topo2SubRegionService', 'Topo2DeviceService',
         'Topo2HostService', 'Topo2LinkService', 'Topo2ZoomService', 'Topo2DetailsPanelService',
-        'Topo2BreadcrumbService',
+        'Topo2BreadcrumbService', 'Topo2ViewController',
+        function ($log, _Model_, t2sr, t2ds, t2hs, t2ls, t2zs, t2dps, t2bcs, ViewController) {
 
-        function (_$log_, _Model_, _t2sr_, _t2ds_, _t2hs_, _t2ls_, _t2zs_, _t2dps_, _t2bcs_) {
-
-            $log = _$log_;
             Model = _Model_;
-            t2sr = _t2sr_;
-            t2ds = _t2ds_;
-            t2hs = _t2hs_;
-            t2ls = _t2ls_;
-            t2zs = _t2zs_;
-            t2dps = _t2dps_;
-            t2bcs = _t2bcs_;
 
-            return {
-                init: init,
+            var Region = ViewController.extend({
+                initialize: function () {
+                    instance = this;
+                    this.model = null;
+                },
+                addRegion: function (data) {
 
-                addRegion: addRegion,
-                regionNodes: regionNodes,
-                regionLinks: regionLinks,
-                filterRegionNodes: filterRegionNodes,
+                    var RegionModel = Model.extend({
+                        findNodeById: this.findNodeById,
+                        nodes: this.regionNodes.bind(this)
+                    });
 
-                deselectAllNodes: deselectAllNodes,
-                deselectLink: deselectLink,
-            };
+                    this.model = new RegionModel({
+                        id: data.id,
+                        layerOrder: data.layerOrder
+                    });
+
+                    this.model.set({
+                        subregions: t2sr.createSubRegionCollection(data.subregions, this),
+                        devices: t2ds.createDeviceCollection(data.devices, this),
+                        hosts: t2hs.createHostCollection(data.hosts, this),
+                        links: t2ls.createLinkCollection(data.links, this)
+                    });
+
+                    angular.forEach(this.model.get('links').models, function (link) {
+                        link.createLink();
+                    });
+
+                    // Hide Breadcrumbs if there are no subregions configured in the root region
+                    if (this.isRootRegion() && !this.model.get('subregions').models.length) {
+                        t2bcs.hide();
+                    }
+                },
+                isRootRegion: function () {
+                    return this.model.get('id') === ROOT;
+                },
+                findNodeById: function (link, id) {
+                    if (link.get('type') !== 'UiEdgeLink') {
+                        // Remove /{port} from id if needed
+                        var regex = new RegExp('^[^/]*');
+                        id = regex.exec(id)[0];
+                    }
+                    return this.model.get('devices').get(id) ||
+                        this.model.get('hosts').get(id) ||
+                        this.model.get('subregions').get(id);
+                },
+                regionNodes: function () {
+
+                    if (this.model) {
+                        return [].concat(
+                            this.model.get('devices').models,
+                            this.model.get('hosts').models,
+                            this.model.get('subregions').models
+                        );
+                    }
+
+                    return [];
+                },
+                regionLinks: function () {
+                    return (this.model) ? this.model.get('links').models : [];
+                },
+                filterRegionNodes: function (predicate) {
+                    var nodes = this.regionNodes();
+                    return _.filter(nodes, predicate);
+                },
+                deselectAllNodes: function () {
+                    var selected = this.filterRegionNodes(function (node) {
+                        return node.get('selected', true);
+                    });
+
+                    if (selected.length) {
+
+                        selected.forEach(function (node) {
+                            node.deselect();
+                        });
+
+                        t2dps().el.hide();
+                        return true;
+                    }
+
+                    return false;
+                },
+                deselectLink: function () {
+                    var selected = _.filter(this.regionLinks(), function (link) {
+                        return link.get('selected', true);
+                    });
+
+                    if (selected.length) {
+
+                        selected.forEach(function (link) {
+                            link.deselect();
+                        });
+
+                        t2dps().el.hide();
+                        return true;
+                    }
+
+                    return false;
+                }
+            });
+
+            function getInstance() {
+                return instance || new Region();
+            }
+
+            return getInstance();
         }]);
 
 })();
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Select.js b/web/gui/src/main/webapp/app/view/topo2/topo2Select.js
index 073147d..c9972a9 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Select.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Select.js
@@ -21,8 +21,18 @@
 (function () {
     'use strict';
 
+    var t2rs;
+
     // internal state
-    var consumeClick;
+    var consumeClick,
+        zoomer,
+        previousNearestLink;    // previous link to mouse position
+
+    function init(svg, _zoomer_) {
+        zoomer = _zoomer_;
+        svg.on('mousemove', mouseMoveHandler);
+        svg.on('click', mouseClickHandler);
+    }
 
     function selectObject(obj) {}
 
@@ -32,11 +42,129 @@
         return cc;
     }
 
+    function mouseClickHandler() {
+
+        if (!d3.event.shiftKey) {
+            t2rs.deselectLink();
+        }
+
+        if (!clickConsumed()) {
+            if (previousNearestLink) {
+                previousNearestLink.select();
+            }
+        }
+
+    }
+
+    // Select Links
+    function mouseMoveHandler() {
+        var mp = getLogicalMousePosition(this),
+            link = computeNearestLink(mp);
+
+        // link.enhance();
+        if (link) {
+            if (previousNearestLink && previousNearestLink !== link) {
+                previousNearestLink.unenhance();
+            }
+            link.enhance();
+        } else if (previousNearestLink) {
+            previousNearestLink.unenhance();
+        }
+
+        previousNearestLink = link;
+    }
+
+    function getLogicalMousePosition(container) {
+        var m = d3.mouse(container),
+            sc = zoomer.scale(),
+            tr = zoomer.translate(),
+            mx = (m[0] - tr[0]) / sc,
+            my = (m[1] - tr[1]) / sc;
+        return { x: mx, y: my };
+    }
+
+    function sq(x) {
+        return x * x;
+    }
+
+    function mdist(p, m) {
+        return Math.sqrt(sq(p.x - m.x) + sq(p.y - m.y));
+    }
+
+    function prox(dist) {
+        return dist / zoomer.scale();
+    }
+
+    function computeNearestLink(mouse) {
+        var proximity = prox(30),
+            nearest = null,
+            minDist;
+
+        function pdrop(line, mouse) {
+            var x1 = line.x1,
+                y1 = line.y1,
+                x2 = line.x2,
+                y2 = line.y2,
+                x3 = mouse.x,
+                y3 = mouse.y,
+                k = ((y2 - y1) * (x3 - x1) - (x2 - x1) * (y3 - y1)) /
+                    (sq(y2 - y1) + sq(x2 - x1)),
+                x4 = x3 - k * (y2 - y1),
+                y4 = y3 + k * (x2 - x1);
+            return { x: x4, y: y4 };
+        }
+
+        function lineHit(line, p, m) {
+            if (p.x < line.x1 && p.x < line.x2) return false;
+            if (p.x > line.x1 && p.x > line.x2) return false;
+            if (p.y < line.y1 && p.y < line.y2) return false;
+            if (p.y > line.y1 && p.y > line.y2) return false;
+            // line intersects, but are we close enough?
+            return mdist(p, m) <= proximity;
+        }
+
+        var links = t2rs.regionLinks();
+
+        if (links.length) {
+            minDist = proximity * 2;
+
+            links.forEach(function (d) {
+                var line = d.get('position'),
+                    point,
+                    hit,
+                    dist;
+
+                // TODO: Reinstate when showHost() is implemented
+                // if (!api.showHosts() && d.type() === 'hostLink') {
+                //     return; // skip hidden host links
+                // }
+
+                if (line) {
+                    point = pdrop(line, mouse);
+                    hit = lineHit(line, point, mouse);
+                    if (hit) {
+                        dist = mdist(point, mouse);
+                        if (dist < minDist) {
+                            minDist = dist;
+                            nearest = d;
+                        }
+                    }
+                }
+            });
+        }
+
+        return nearest;
+    }
+
     angular.module('ovTopo2')
     .factory('Topo2SelectService', [
-        function () {
+        'Topo2RegionService',
+        function (_t2rs_) {
+
+            t2rs = _t2rs_;
 
             return {
+                init: init,
                 selectObject: selectObject,
                 clickConsumed: clickConsumed
             };
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2SubRegion.js b/web/gui/src/main/webapp/app/view/topo2/topo2SubRegion.js
index 75a2ef4..0852788 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2SubRegion.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2SubRegion.js
@@ -22,7 +22,6 @@
 (function () {
     'use strict';
 
-    var wss;
     var Collection, Model;
 
     var remappedDeviceTypes = {
@@ -32,68 +31,72 @@
     function createSubRegionCollection(data, region) {
 
         var SubRegionCollection = Collection.extend({
-            model: Model
+            model: Model,
+            region: region
         });
 
         return new SubRegionCollection(data);
     }
 
     angular.module('ovTopo2')
-    .factory('Topo2SubRegionService',
-        ['WebSocketService', 'Topo2Collection', 'Topo2NodeModel',
-        'ThemeService', 'Topo2ViewService', 'Topo2SubRegionPanelService',
+    .factory('Topo2SubRegionService', [
+        'WebSocketService', 'Topo2Collection', 'Topo2NodeModel',
+        'Topo2SubRegionPanelService',
 
-            function (_wss_, _c_, _NodeModel_, _ts_, _t2vs_m, _t2srp_) {
+        function (wss, _c_, NodeModel, t2srp) {
 
-                wss = _wss_;
-                Collection = _c_;
+            Collection = _c_;
 
-                Model = _NodeModel_.extend({
-                    initialize: function () {
-                        this.super = this.constructor.__super__;
-                        this.super.initialize.apply(this, arguments);
-                    },
-                    events: {
-                        'dblclick': 'navigateToRegion',
-                        'click': 'onClick'
-                    },
-                    onChange: function () {
-                        // Update class names when the model changes
-                        if (this.el) {
-                            this.el.attr('class', this.svgClassName());
-                        }
-                    },
-                    nodeType: 'sub-region',
-                    icon: function () {
-                        var type = this.get('type');
-                        return remappedDeviceTypes[type] || type || 'm_cloud';
-                    },
-                    onClick: function () {
-                        var selected = this.select(d3.event);
-
-                        if (selected.length > 0) {
-                            _t2srp_.displayPanel(this);
-                        } else {
-                            _t2srp_.hide();
-                        }
-                    },
-                    navigateToRegion: function () {
-
-                        if (d3.event.defaultPrevented) return;
-
-                        wss.sendEvent('topo2navRegion', {
-                            dir: 'down',
-                            rid: this.get('id')
-                        });
-
-                        _t2srp_.hide();
+            Model = NodeModel.extend({
+                initialize: function () {
+                    this.super = this.constructor.__super__;
+                    this.super.initialize.apply(this, arguments);
+                },
+                events: {
+                    'dblclick': 'navigateToRegion',
+                    'click': 'onClick'
+                },
+                onChange: function () {
+                    // Update class names when the model changes
+                    if (this.el) {
+                        this.el.attr('class', this.svgClassName());
                     }
-                });
+                },
+                nodeType: 'sub-region',
+                icon: function () {
+                    var type = this.get('type');
+                    return remappedDeviceTypes[type] || type || 'm_cloud';
+                },
+                onClick: function () {
+                    var selected = this.select(d3.event);
 
-                return {
-                    createSubRegionCollection: createSubRegionCollection
-                };
-            }
-        ]);
+                    if (selected.length > 0) {
+                        t2srp.displayPanel(this);
+                    } else {
+                        t2srp.hide();
+                    }
+                },
+                navigateToRegion: function () {
+
+                    if (d3.event.defaultPrevented) return;
+
+                    wss.sendEvent('topo2navRegion', {
+                        dir: 'down',
+                        rid: this.get('id')
+                    });
+
+                    var layout = this.collection.region.layout;
+                    layout.createForceElements();
+                    layout.transitionDownRegion();
+
+                    t2srp.hide();
+                }
+            });
+
+            return {
+                createSubRegionCollection: createSubRegionCollection
+            };
+        }
+    ]);
 
 })();
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2SubRegionPanel.js b/web/gui/src/main/webapp/app/view/topo2/topo2SubRegionPanel.js
index b5e49b7..0185e89 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2SubRegionPanel.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2SubRegionPanel.js
@@ -23,13 +23,13 @@
     'use strict';
 
     // Injected Services
-    var Panel, gs, wss, flash, listProps;
+    var panel, gs, flash, ls;
 
     // Internal State
     var subRegionPanel, subRegionData;
 
     function init() {
-        subRegionPanel = Panel();
+        subRegionPanel = panel();
     }
 
     function formatSubRegionData(data) {
@@ -43,8 +43,8 @@
                 'Number of Devices': data.get('nDevs'),
                 'Number of Hosts': data.get('nHosts')
             }
-        }
-    };
+        };
+    }
 
     function displayPanel(data) {
         init();
@@ -65,7 +65,7 @@
 
         title.text(subRegionData.title);
         gs.addGlyph(svg, 'bird', 24, 0, [1, 1]);
-        listProps(tbody, subRegionData);
+        ls.listProps(tbody, subRegionData);
     }
 
     function show() {
@@ -87,15 +87,14 @@
     }
 
     angular.module('ovTopo2')
-    .factory('Topo2SubRegionPanelService',
-    ['Topo2DetailsPanelService', 'GlyphService', 'WebSocketService', 'FlashService', 'ListService',
-        function (_ps_, _gs_, _wss_, _flash_, _listService_) {
+    .factory('Topo2SubRegionPanelService', [
+        'Topo2DetailsPanelService', 'GlyphService', 'FlashService', 'ListService',
+        function (_ps_, _gs_, _flash_, _ls_) {
 
-            Panel = _ps_;
+            panel = _ps_;
             gs = _gs_;
-            wss = _wss_;
             flash = _flash_;
-            listProps = _listService_;
+            ls = _ls_;
 
             return {
                 displayPanel: displayPanel,
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2SummaryPanel.js b/web/gui/src/main/webapp/app/view/topo2/topo2SummaryPanel.js
index 682daac..6017504 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2SummaryPanel.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2SummaryPanel.js
@@ -23,7 +23,7 @@
     'use strict';
 
     // Injected Services
-    var Panel, gs, wss, flash, listProps;
+    var Panel, gs, wss, flash, ls;
 
     // Internal State
     var summaryPanel, summaryData;
@@ -64,7 +64,7 @@
 
         title.text(summaryData.title);
         gs.addGlyph(svg, 'bird', 24, 0, [1, 1]);
-        listProps(tbody, summaryData);
+        ls.listProps(tbody, summaryData);
     }
 
     function handleSummaryData(data) {
@@ -91,15 +91,15 @@
     }
 
     angular.module('ovTopo2')
-    .factory('Topo2SummaryPanelService',
-    ['Topo2PanelService', 'GlyphService', 'WebSocketService', 'FlashService', 'ListService',
-        function (_ps_, _gs_, _wss_, _flash_, _listService_) {
+    .factory('Topo2SummaryPanelService', [
+        'Topo2PanelService', 'GlyphService', 'WebSocketService', 'FlashService', 'ListService',
+        function (_ps_, _gs_, _wss_, _flash_, _ls_) {
 
             Panel = _ps_;
             gs = _gs_;
             wss = _wss_;
             flash = _flash_;
-            listProps = _listService_;
+            ls = _ls_;
 
             return {
                 init: init,
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2ViewController.js b/web/gui/src/main/webapp/app/view/topo2/topo2ViewController.js
new file mode 100644
index 0000000..9d9f399
--- /dev/null
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2ViewController.js
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2016-present 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 -- View Controller.
+ A base class for view controllers to extend from
+ */
+
+(function () {
+    'use strict';
+
+    function ViewController(options) {
+        this.initialize.apply(this, arguments);
+    }
+
+    ViewController.prototype = {
+        initialize: function () {
+
+        }
+    };
+
+    angular.module('ovTopo2')
+        .factory('Topo2ViewController', [
+            'FnService',
+            function (fn) {
+                ViewController.extend = fn.extend;
+                return ViewController;
+            }
+        ]);
+})();
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Zoom.js b/web/gui/src/main/webapp/app/view/topo2/topo2Zoom.js
index 89e7428..7dcf325 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Zoom.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Zoom.js
@@ -28,6 +28,10 @@
     var zoomer,
         zoomEventListeners = [];
 
+    function getZoomer() {
+        return zoomer;
+    }
+
     function createZoomer(options) {
         var settings = angular.extend({}, options, {
             zoomCallback: zoomCallback
@@ -78,14 +82,15 @@
     }
 
     angular.module('ovTopo2')
-    .factory('Topo2ZoomService',
-        ['ZoomService', 'PrefsService',
+    .factory('Topo2ZoomService', [
+        'ZoomService', 'PrefsService',
         function (_zs_, _ps_) {
 
             zs = _zs_;
             ps = _ps_;
 
             return {
+                getZoomer: getZoomer,
                 createZoomer: createZoomer,
                 addZoomEventListener: addZoomEventListener,
                 removeZoomEventListener: removeZoomEventListener,
diff --git a/web/gui/src/main/webapp/app/view/topo2/uiView.js b/web/gui/src/main/webapp/app/view/topo2/uiView.js
index 8bc01cc..77419a5 100644
--- a/web/gui/src/main/webapp/app/view/topo2/uiView.js
+++ b/web/gui/src/main/webapp/app/view/topo2/uiView.js
@@ -32,8 +32,8 @@
     }
 
     angular.module('ovTopo2')
-    .factory('Topo2UIView',
-    ['FnService',
+    .factory('Topo2UIView', [
+        'FnService',
         function (fn) {
 
             _.extend(View.prototype, {
diff --git a/web/gui/src/main/webapp/index.html b/web/gui/src/main/webapp/index.html
index c33e5cf..40edebe 100644
--- a/web/gui/src/main/webapp/index.html
+++ b/web/gui/src/main/webapp/index.html
@@ -162,6 +162,7 @@
     <script src="app/view/topo2/topo2SubRegionPanel.js"></script>
     <script src="app/view/topo2/topo2Theme.js"></script>
     <script src="app/view/topo2/topo2View.js"></script>
+    <script src="app/view/topo2/topo2ViewController.js"></script>
     <script src="app/view/topo2/topo2Zoom.js"></script>
     <script src="app/view/topo2/uiView.js"></script>
     <link rel="stylesheet" href="app/view/topo2/topo2.css">
diff --git a/web/gui/src/main/webapp/tp/d3.js b/web/gui/src/main/webapp/tp/d3.js
index fa4d7fa..2e70a83 100644
--- a/web/gui/src/main/webapp/tp/d3.js
+++ b/web/gui/src/main/webapp/tp/d3.js
@@ -1,36 +1,46 @@
 !function() {
   var d3 = {
-    version: "3.4.12"
-  };
-  if (!Date.now) Date.now = function() {
-    return +new Date();
+    version: "3.5.16"
   };
   var d3_arraySlice = [].slice, d3_array = function(list) {
     return d3_arraySlice.call(list);
   };
-  var d3_document = document, d3_documentElement = d3_document.documentElement, d3_window = window;
-  try {
-    d3_array(d3_documentElement.childNodes)[0].nodeType;
-  } catch (e) {
-    d3_array = function(list) {
-      var i = list.length, array = new Array(i);
-      while (i--) array[i] = list[i];
-      return array;
-    };
+  var d3_document = this.document;
+  function d3_documentElement(node) {
+    return node && (node.ownerDocument || node.document || node).documentElement;
   }
-  try {
-    d3_document.createElement("div").style.setProperty("opacity", 0, "");
-  } catch (error) {
-    var d3_element_prototype = d3_window.Element.prototype, d3_element_setAttribute = d3_element_prototype.setAttribute, d3_element_setAttributeNS = d3_element_prototype.setAttributeNS, d3_style_prototype = d3_window.CSSStyleDeclaration.prototype, d3_style_setProperty = d3_style_prototype.setProperty;
-    d3_element_prototype.setAttribute = function(name, value) {
-      d3_element_setAttribute.call(this, name, value + "");
-    };
-    d3_element_prototype.setAttributeNS = function(space, local, value) {
-      d3_element_setAttributeNS.call(this, space, local, value + "");
-    };
-    d3_style_prototype.setProperty = function(name, value, priority) {
-      d3_style_setProperty.call(this, name, value + "", priority);
-    };
+  function d3_window(node) {
+    return node && (node.ownerDocument && node.ownerDocument.defaultView || node.document && node || node.defaultView);
+  }
+  if (d3_document) {
+    try {
+      d3_array(d3_document.documentElement.childNodes)[0].nodeType;
+    } catch (e) {
+      d3_array = function(list) {
+        var i = list.length, array = new Array(i);
+        while (i--) array[i] = list[i];
+        return array;
+      };
+    }
+  }
+  if (!Date.now) Date.now = function() {
+    return +new Date();
+  };
+  if (d3_document) {
+    try {
+      d3_document.createElement("DIV").style.setProperty("opacity", 0, "");
+    } catch (error) {
+      var d3_element_prototype = this.Element.prototype, d3_element_setAttribute = d3_element_prototype.setAttribute, d3_element_setAttributeNS = d3_element_prototype.setAttributeNS, d3_style_prototype = this.CSSStyleDeclaration.prototype, d3_style_setProperty = d3_style_prototype.setProperty;
+      d3_element_prototype.setAttribute = function(name, value) {
+        d3_element_setAttribute.call(this, name, value + "");
+      };
+      d3_element_prototype.setAttributeNS = function(space, local, value) {
+        d3_element_setAttributeNS.call(this, space, local, value + "");
+      };
+      d3_style_prototype.setProperty = function(name, value, priority) {
+        d3_style_setProperty.call(this, name, value + "", priority);
+      };
+    }
   }
   d3.ascending = d3_ascending;
   function d3_ascending(a, b) {
@@ -42,10 +52,16 @@
   d3.min = function(array, f) {
     var i = -1, n = array.length, a, b;
     if (arguments.length === 1) {
-      while (++i < n && !((a = array[i]) != null && a <= a)) a = undefined;
+      while (++i < n) if ((b = array[i]) != null && b >= b) {
+        a = b;
+        break;
+      }
       while (++i < n) if ((b = array[i]) != null && a > b) a = b;
     } else {
-      while (++i < n && !((a = f.call(array, array[i], i)) != null && a <= a)) a = undefined;
+      while (++i < n) if ((b = f.call(array, array[i], i)) != null && b >= b) {
+        a = b;
+        break;
+      }
       while (++i < n) if ((b = f.call(array, array[i], i)) != null && a > b) a = b;
     }
     return a;
@@ -53,10 +69,16 @@
   d3.max = function(array, f) {
     var i = -1, n = array.length, a, b;
     if (arguments.length === 1) {
-      while (++i < n && !((a = array[i]) != null && a <= a)) a = undefined;
+      while (++i < n) if ((b = array[i]) != null && b >= b) {
+        a = b;
+        break;
+      }
       while (++i < n) if ((b = array[i]) != null && b > a) a = b;
     } else {
-      while (++i < n && !((a = f.call(array, array[i], i)) != null && a <= a)) a = undefined;
+      while (++i < n) if ((b = f.call(array, array[i], i)) != null && b >= b) {
+        a = b;
+        break;
+      }
       while (++i < n) if ((b = f.call(array, array[i], i)) != null && b > a) a = b;
     }
     return a;
@@ -64,13 +86,19 @@
   d3.extent = function(array, f) {
     var i = -1, n = array.length, a, b, c;
     if (arguments.length === 1) {
-      while (++i < n && !((a = c = array[i]) != null && a <= a)) a = c = undefined;
+      while (++i < n) if ((b = array[i]) != null && b >= b) {
+        a = c = b;
+        break;
+      }
       while (++i < n) if ((b = array[i]) != null) {
         if (a > b) a = b;
         if (c < b) c = b;
       }
     } else {
-      while (++i < n && !((a = c = f.call(array, array[i], i)) != null && a <= a)) a = undefined;
+      while (++i < n) if ((b = f.call(array, array[i], i)) != null && b >= b) {
+        a = c = b;
+        break;
+      }
       while (++i < n) if ((b = f.call(array, array[i], i)) != null) {
         if (a > b) a = b;
         if (c < b) c = b;
@@ -78,35 +106,67 @@
     }
     return [ a, c ];
   };
+  function d3_number(x) {
+    return x === null ? NaN : +x;
+  }
+  function d3_numeric(x) {
+    return !isNaN(x);
+  }
   d3.sum = function(array, f) {
     var s = 0, n = array.length, a, i = -1;
     if (arguments.length === 1) {
-      while (++i < n) if (!isNaN(a = +array[i])) s += a;
+      while (++i < n) if (d3_numeric(a = +array[i])) s += a;
     } else {
-      while (++i < n) if (!isNaN(a = +f.call(array, array[i], i))) s += a;
+      while (++i < n) if (d3_numeric(a = +f.call(array, array[i], i))) s += a;
     }
     return s;
   };
-  function d3_number(x) {
-    return x != null && !isNaN(x);
-  }
   d3.mean = function(array, f) {
     var s = 0, n = array.length, a, i = -1, j = n;
     if (arguments.length === 1) {
-      while (++i < n) if (d3_number(a = array[i])) s += a; else --j;
+      while (++i < n) if (d3_numeric(a = d3_number(array[i]))) s += a; else --j;
     } else {
-      while (++i < n) if (d3_number(a = f.call(array, array[i], i))) s += a; else --j;
+      while (++i < n) if (d3_numeric(a = d3_number(f.call(array, array[i], i)))) s += a; else --j;
     }
-    return j ? s / j : undefined;
+    if (j) return s / j;
   };
   d3.quantile = function(values, p) {
     var H = (values.length - 1) * p + 1, h = Math.floor(H), v = +values[h - 1], e = H - h;
     return e ? v + e * (values[h] - v) : v;
   };
   d3.median = function(array, f) {
-    if (arguments.length > 1) array = array.map(f);
-    array = array.filter(d3_number);
-    return array.length ? d3.quantile(array.sort(d3_ascending), .5) : undefined;
+    var numbers = [], n = array.length, a, i = -1;
+    if (arguments.length === 1) {
+      while (++i < n) if (d3_numeric(a = d3_number(array[i]))) numbers.push(a);
+    } else {
+      while (++i < n) if (d3_numeric(a = d3_number(f.call(array, array[i], i)))) numbers.push(a);
+    }
+    if (numbers.length) return d3.quantile(numbers.sort(d3_ascending), .5);
+  };
+  d3.variance = function(array, f) {
+    var n = array.length, m = 0, a, d, s = 0, i = -1, j = 0;
+    if (arguments.length === 1) {
+      while (++i < n) {
+        if (d3_numeric(a = d3_number(array[i]))) {
+          d = a - m;
+          m += d / ++j;
+          s += d * (a - m);
+        }
+      }
+    } else {
+      while (++i < n) {
+        if (d3_numeric(a = d3_number(f.call(array, array[i], i)))) {
+          d = a - m;
+          m += d / ++j;
+          s += d * (a - m);
+        }
+      }
+    }
+    if (j > 1) return s / (j - 1);
+  };
+  d3.deviation = function() {
+    var v = d3.variance.apply(this, arguments);
+    return v ? Math.sqrt(v) : v;
   };
   function d3_bisector(compare) {
     return {
@@ -138,11 +198,15 @@
       return d3_ascending(f(d), x);
     } : f);
   };
-  d3.shuffle = function(array) {
-    var m = array.length, t, i;
+  d3.shuffle = function(array, i0, i1) {
+    if ((m = arguments.length) < 3) {
+      i1 = array.length;
+      if (m < 2) i0 = 0;
+    }
+    var m = i1 - i0, t, i;
     while (m) {
       i = Math.random() * m-- | 0;
-      t = array[m], array[m] = array[i], array[i] = t;
+      t = array[m + i0], array[m + i0] = array[i + i0], array[i + i0] = t;
     }
     return array;
   };
@@ -156,20 +220,20 @@
     while (i < n) pairs[i] = [ p0 = p1, p1 = array[++i] ];
     return pairs;
   };
-  d3.zip = function() {
-    if (!(n = arguments.length)) return [];
-    for (var i = -1, m = d3.min(arguments, d3_zipLength), zips = new Array(m); ++i < m; ) {
-      for (var j = -1, n, zip = zips[i] = new Array(n); ++j < n; ) {
-        zip[j] = arguments[j][i];
+  d3.transpose = function(matrix) {
+    if (!(n = matrix.length)) return [];
+    for (var i = -1, m = d3.min(matrix, d3_transposeLength), transpose = new Array(m); ++i < m; ) {
+      for (var j = -1, n, row = transpose[i] = new Array(n); ++j < n; ) {
+        row[j] = matrix[j][i];
       }
     }
-    return zips;
+    return transpose;
   };
-  function d3_zipLength(d) {
+  function d3_transposeLength(d) {
     return d.length;
   }
-  d3.transpose = function(matrix) {
-    return d3.zip.apply(d3, matrix);
+  d3.zip = function() {
+    return d3.transpose(arguments);
   };
   d3.keys = function(map) {
     var keys = [];
@@ -223,80 +287,84 @@
     return k;
   }
   function d3_class(ctor, properties) {
-    try {
-      for (var key in properties) {
-        Object.defineProperty(ctor.prototype, key, {
-          value: properties[key],
-          enumerable: false
-        });
-      }
-    } catch (e) {
-      ctor.prototype = properties;
+    for (var key in properties) {
+      Object.defineProperty(ctor.prototype, key, {
+        value: properties[key],
+        enumerable: false
+      });
     }
   }
-  d3.map = function(object) {
+  d3.map = function(object, f) {
     var map = new d3_Map();
-    if (object instanceof d3_Map) object.forEach(function(key, value) {
-      map.set(key, value);
-    }); else for (var key in object) map.set(key, object[key]);
+    if (object instanceof d3_Map) {
+      object.forEach(function(key, value) {
+        map.set(key, value);
+      });
+    } else if (Array.isArray(object)) {
+      var i = -1, n = object.length, o;
+      if (arguments.length === 1) while (++i < n) map.set(i, object[i]); else while (++i < n) map.set(f.call(object, o = object[i], i), o);
+    } else {
+      for (var key in object) map.set(key, object[key]);
+    }
     return map;
   };
-  function d3_Map() {}
+  function d3_Map() {
+    this._ = Object.create(null);
+  }
+  var d3_map_proto = "__proto__", d3_map_zero = "\x00";
   d3_class(d3_Map, {
     has: d3_map_has,
     get: function(key) {
-      return this[d3_map_prefix + key];
+      return this._[d3_map_escape(key)];
     },
     set: function(key, value) {
-      return this[d3_map_prefix + key] = value;
+      return this._[d3_map_escape(key)] = value;
     },
     remove: d3_map_remove,
     keys: d3_map_keys,
     values: function() {
       var values = [];
-      this.forEach(function(key, value) {
-        values.push(value);
-      });
+      for (var key in this._) values.push(this._[key]);
       return values;
     },
     entries: function() {
       var entries = [];
-      this.forEach(function(key, value) {
-        entries.push({
-          key: key,
-          value: value
-        });
+      for (var key in this._) entries.push({
+        key: d3_map_unescape(key),
+        value: this._[key]
       });
       return entries;
     },
     size: d3_map_size,
     empty: d3_map_empty,
     forEach: function(f) {
-      for (var key in this) if (key.charCodeAt(0) === d3_map_prefixCode) f.call(this, key.slice(1), this[key]);
+      for (var key in this._) f.call(this, d3_map_unescape(key), this._[key]);
     }
   });
-  var d3_map_prefix = "\x00", d3_map_prefixCode = d3_map_prefix.charCodeAt(0);
+  function d3_map_escape(key) {
+    return (key += "") === d3_map_proto || key[0] === d3_map_zero ? d3_map_zero + key : key;
+  }
+  function d3_map_unescape(key) {
+    return (key += "")[0] === d3_map_zero ? key.slice(1) : key;
+  }
   function d3_map_has(key) {
-    return d3_map_prefix + key in this;
+    return d3_map_escape(key) in this._;
   }
   function d3_map_remove(key) {
-    key = d3_map_prefix + key;
-    return key in this && delete this[key];
+    return (key = d3_map_escape(key)) in this._ && delete this._[key];
   }
   function d3_map_keys() {
     var keys = [];
-    this.forEach(function(key) {
-      keys.push(key);
-    });
+    for (var key in this._) keys.push(d3_map_unescape(key));
     return keys;
   }
   function d3_map_size() {
     var size = 0;
-    for (var key in this) if (key.charCodeAt(0) === d3_map_prefixCode) ++size;
+    for (var key in this._) ++size;
     return size;
   }
   function d3_map_empty() {
-    for (var key in this) if (key.charCodeAt(0) === d3_map_prefixCode) return false;
+    for (var key in this._) return false;
     return true;
   }
   d3.nest = function() {
@@ -367,25 +435,27 @@
     if (array) for (var i = 0, n = array.length; i < n; ++i) set.add(array[i]);
     return set;
   };
-  function d3_Set() {}
+  function d3_Set() {
+    this._ = Object.create(null);
+  }
   d3_class(d3_Set, {
     has: d3_map_has,
-    add: function(value) {
-      this[d3_map_prefix + value] = true;
-      return value;
+    add: function(key) {
+      this._[d3_map_escape(key += "")] = true;
+      return key;
     },
-    remove: function(value) {
-      value = d3_map_prefix + value;
-      return value in this && delete this[value];
-    },
+    remove: d3_map_remove,
     values: d3_map_keys,
     size: d3_map_size,
     empty: d3_map_empty,
     forEach: function(f) {
-      for (var value in this) if (value.charCodeAt(0) === d3_map_prefixCode) f.call(this, value.slice(1));
+      for (var key in this._) f.call(this, d3_map_unescape(key));
     }
   });
   d3.behavior = {};
+  function d3_identity(d) {
+    return d;
+  }
   d3.rebind = function(target, source) {
     var i = 1, n = arguments.length, method;
     while (++i < n) target[method = arguments[i]] = d3_rebind(target, source, source[method]);
@@ -492,8 +562,12 @@
     return n.querySelector(s);
   }, d3_selectAll = function(s, n) {
     return n.querySelectorAll(s);
-  }, d3_selectMatcher = d3_documentElement.matches || d3_documentElement[d3_vendorSymbol(d3_documentElement, "matchesSelector")], d3_selectMatches = function(n, s) {
-    return d3_selectMatcher.call(n, s);
+  }, d3_selectMatches = function(n, s) {
+    var d3_selectMatcher = n.matches || n[d3_vendorSymbol(n, "matchesSelector")];
+    d3_selectMatches = function(n, s) {
+      return d3_selectMatcher.call(n, s);
+    };
+    return d3_selectMatches(n, s);
   };
   if (typeof Sizzle === "function") {
     d3_select = function(s, n) {
@@ -503,7 +577,7 @@
     d3_selectMatches = Sizzle.matchesSelector;
   }
   d3.selection = function() {
-    return d3_selectionRoot;
+    return d3.select(d3_document.documentElement);
   };
   var d3_selectionPrototype = d3.selection.prototype = [];
   d3_selectionPrototype.select = function(selector) {
@@ -546,9 +620,10 @@
       return d3_selectAll(selector, this);
     };
   }
+  var d3_nsXhtml = "http://www.w3.org/1999/xhtml";
   var d3_nsPrefix = {
     svg: "http://www.w3.org/2000/svg",
-    xhtml: "http://www.w3.org/1999/xhtml",
+    xhtml: d3_nsXhtml,
     xlink: "http://www.w3.org/1999/xlink",
     xml: "http://www.w3.org/XML/1998/namespace",
     xmlns: "http://www.w3.org/2000/xmlns/"
@@ -557,10 +632,7 @@
     prefix: d3_nsPrefix,
     qualify: function(name) {
       var i = name.indexOf(":"), prefix = name;
-      if (i >= 0) {
-        prefix = name.slice(0, i);
-        name = name.slice(i + 1);
-      }
+      if (i >= 0 && (prefix = name.slice(0, i)) !== "xmlns") name = name.slice(i + 1);
       return d3_nsPrefix.hasOwnProperty(prefix) ? {
         space: d3_nsPrefix[prefix],
         local: name
@@ -663,7 +735,10 @@
         for (priority in name) this.each(d3_selection_style(priority, name[priority], value));
         return this;
       }
-      if (n < 2) return d3_window.getComputedStyle(this.node(), null).getPropertyValue(name);
+      if (n < 2) {
+        var node = this.node();
+        return d3_window(node).getComputedStyle(node, null).getPropertyValue(name);
+      }
       priority = "";
     }
     return this.each(d3_selection_style(name, value, priority));
@@ -729,11 +804,14 @@
     });
   };
   function d3_selection_creator(name) {
-    return typeof name === "function" ? name : (name = d3.ns.qualify(name)).local ? function() {
+    function create() {
+      var document = this.ownerDocument, namespace = this.namespaceURI;
+      return namespace === d3_nsXhtml && document.documentElement.namespaceURI === d3_nsXhtml ? document.createElement(name) : document.createElementNS(namespace, name);
+    }
+    function createNS() {
       return this.ownerDocument.createElementNS(name.space, name.local);
-    } : function() {
-      return this.ownerDocument.createElementNS(this.namespaceURI, name);
-    };
+    }
+    return typeof name === "function" ? name : (name = d3.ns.qualify(name)).local ? createNS : create;
   }
   d3_selectionPrototype.insert = function(name, before) {
     name = d3_selection_creator(name);
@@ -743,11 +821,12 @@
     });
   };
   d3_selectionPrototype.remove = function() {
-    return this.each(function() {
-      var parent = this.parentNode;
-      if (parent) parent.removeChild(this);
-    });
+    return this.each(d3_selectionRemove);
   };
+  function d3_selectionRemove() {
+    var parent = this.parentNode;
+    if (parent) parent.removeChild(this);
+  }
   d3_selectionPrototype.data = function(value, key) {
     var i = -1, n = this.length, group, node;
     if (!arguments.length) {
@@ -762,29 +841,28 @@
     function bind(group, groupData) {
       var i, n = group.length, m = groupData.length, n0 = Math.min(n, m), updateNodes = new Array(m), enterNodes = new Array(m), exitNodes = new Array(n), node, nodeData;
       if (key) {
-        var nodeByKeyValue = new d3_Map(), dataByKeyValue = new d3_Map(), keyValues = [], keyValue;
+        var nodeByKeyValue = new d3_Map(), keyValues = new Array(n), keyValue;
         for (i = -1; ++i < n; ) {
-          keyValue = key.call(node = group[i], node.__data__, i);
-          if (nodeByKeyValue.has(keyValue)) {
-            exitNodes[i] = node;
-          } else {
-            nodeByKeyValue.set(keyValue, node);
+          if (node = group[i]) {
+            if (nodeByKeyValue.has(keyValue = key.call(node, node.__data__, i))) {
+              exitNodes[i] = node;
+            } else {
+              nodeByKeyValue.set(keyValue, node);
+            }
+            keyValues[i] = keyValue;
           }
-          keyValues.push(keyValue);
         }
         for (i = -1; ++i < m; ) {
-          keyValue = key.call(groupData, nodeData = groupData[i], i);
-          if (node = nodeByKeyValue.get(keyValue)) {
+          if (!(node = nodeByKeyValue.get(keyValue = key.call(groupData, nodeData = groupData[i], i)))) {
+            enterNodes[i] = d3_selection_dataNode(nodeData);
+          } else if (node !== true) {
             updateNodes[i] = node;
             node.__data__ = nodeData;
-          } else if (!dataByKeyValue.has(keyValue)) {
-            enterNodes[i] = d3_selection_dataNode(nodeData);
           }
-          dataByKeyValue.set(keyValue, nodeData);
-          nodeByKeyValue.remove(keyValue);
+          nodeByKeyValue.set(keyValue, true);
         }
         for (i = -1; ++i < n; ) {
-          if (nodeByKeyValue.has(keyValues[i])) {
+          if (i in keyValues && nodeByKeyValue.get(keyValues[i]) !== true) {
             exitNodes[i] = group[i];
           }
         }
@@ -959,40 +1037,28 @@
       return node;
     };
   }
-  d3_selectionPrototype.transition = function() {
-    var id = d3_transitionInheritId || ++d3_transitionId, subgroups = [], subgroup, node, transition = d3_transitionInherit || {
-      time: Date.now(),
-      ease: d3_ease_cubicInOut,
-      delay: 0,
-      duration: 250
-    };
-    for (var j = -1, m = this.length; ++j < m; ) {
-      subgroups.push(subgroup = []);
-      for (var group = this[j], i = -1, n = group.length; ++i < n; ) {
-        if (node = group[i]) d3_transitionNode(node, i, id, transition);
-        subgroup.push(node);
-      }
-    }
-    return d3_transition(subgroups, id);
-  };
-  d3_selectionPrototype.interrupt = function() {
-    return this.each(d3_selection_interrupt);
-  };
-  function d3_selection_interrupt() {
-    var lock = this.__transition__;
-    if (lock) ++lock.active;
-  }
   d3.select = function(node) {
-    var group = [ typeof node === "string" ? d3_select(node, d3_document) : node ];
-    group.parentNode = d3_documentElement;
+    var group;
+    if (typeof node === "string") {
+      group = [ d3_select(node, d3_document) ];
+      group.parentNode = d3_document.documentElement;
+    } else {
+      group = [ node ];
+      group.parentNode = d3_documentElement(node);
+    }
     return d3_selection([ group ]);
   };
   d3.selectAll = function(nodes) {
-    var group = d3_array(typeof nodes === "string" ? d3_selectAll(nodes, d3_document) : nodes);
-    group.parentNode = d3_documentElement;
+    var group;
+    if (typeof nodes === "string") {
+      group = d3_array(d3_selectAll(nodes, d3_document));
+      group.parentNode = d3_document.documentElement;
+    } else {
+      group = d3_array(nodes);
+      group.parentNode = null;
+    }
     return d3_selection([ group ]);
   };
-  var d3_selectionRoot = d3.select(d3_documentElement);
   d3_selectionPrototype.on = function(type, listener, capture) {
     var n = arguments.length;
     if (n < 3) {
@@ -1040,9 +1106,11 @@
     mouseenter: "mouseover",
     mouseleave: "mouseout"
   });
-  d3_selection_onFilters.forEach(function(k) {
-    if ("on" + k in d3_document) d3_selection_onFilters.remove(k);
-  });
+  if (d3_document) {
+    d3_selection_onFilters.forEach(function(k) {
+      if ("on" + k in d3_document) d3_selection_onFilters.remove(k);
+    });
+  }
   function d3_selection_onListener(listener, argumentz) {
     return function(e) {
       var o = d3.event;
@@ -1064,20 +1132,23 @@
       }
     };
   }
-  var d3_event_dragSelect = "onselectstart" in d3_document ? null : d3_vendorSymbol(d3_documentElement.style, "userSelect"), d3_event_dragId = 0;
-  function d3_event_dragSuppress() {
-    var name = ".dragsuppress-" + ++d3_event_dragId, click = "click" + name, w = d3.select(d3_window).on("touchmove" + name, d3_eventPreventDefault).on("dragstart" + name, d3_eventPreventDefault).on("selectstart" + name, d3_eventPreventDefault);
+  var d3_event_dragSelect, d3_event_dragId = 0;
+  function d3_event_dragSuppress(node) {
+    var name = ".dragsuppress-" + ++d3_event_dragId, click = "click" + name, w = d3.select(d3_window(node)).on("touchmove" + name, d3_eventPreventDefault).on("dragstart" + name, d3_eventPreventDefault).on("selectstart" + name, d3_eventPreventDefault);
+    if (d3_event_dragSelect == null) {
+      d3_event_dragSelect = "onselectstart" in node ? false : d3_vendorSymbol(node.style, "userSelect");
+    }
     if (d3_event_dragSelect) {
-      var style = d3_documentElement.style, select = style[d3_event_dragSelect];
+      var style = d3_documentElement(node).style, select = style[d3_event_dragSelect];
       style[d3_event_dragSelect] = "none";
     }
     return function(suppressClick) {
       w.on(name, null);
       if (d3_event_dragSelect) style[d3_event_dragSelect] = select;
       if (suppressClick) {
-        function off() {
+        var off = function() {
           w.on(click, null);
-        }
+        };
         w.on(click, function() {
           d3_eventPreventDefault();
           off();
@@ -1089,24 +1160,27 @@
   d3.mouse = function(container) {
     return d3_mousePoint(container, d3_eventSource());
   };
-  var d3_mouse_bug44083 = /WebKit/.test(d3_window.navigator.userAgent) ? -1 : 0;
+  var d3_mouse_bug44083 = this.navigator && /WebKit/.test(this.navigator.userAgent) ? -1 : 0;
   function d3_mousePoint(container, e) {
     if (e.changedTouches) e = e.changedTouches[0];
     var svg = container.ownerSVGElement || container;
     if (svg.createSVGPoint) {
       var point = svg.createSVGPoint();
-      if (d3_mouse_bug44083 < 0 && (d3_window.scrollX || d3_window.scrollY)) {
-        svg = d3.select("body").append("svg").style({
-          position: "absolute",
-          top: 0,
-          left: 0,
-          margin: 0,
-          padding: 0,
-          border: "none"
-        }, "important");
-        var ctm = svg[0][0].getScreenCTM();
-        d3_mouse_bug44083 = !(ctm.f || ctm.e);
-        svg.remove();
+      if (d3_mouse_bug44083 < 0) {
+        var window = d3_window(container);
+        if (window.scrollX || window.scrollY) {
+          svg = d3.select("body").append("svg").style({
+            position: "absolute",
+            top: 0,
+            left: 0,
+            margin: 0,
+            padding: 0,
+            border: "none"
+          }, "important");
+          var ctm = svg[0][0].getScreenCTM();
+          d3_mouse_bug44083 = !(ctm.f || ctm.e);
+          svg.remove();
+        }
       }
       if (d3_mouse_bug44083) point.x = e.pageX, point.y = e.pageY; else point.x = e.clientX, 
       point.y = e.clientY;
@@ -1125,13 +1199,13 @@
     }
   };
   d3.behavior.drag = function() {
-    var event = d3_eventDispatch(drag, "drag", "dragstart", "dragend"), origin = null, mousedown = dragstart(d3_noop, d3.mouse, d3_behavior_dragMouseSubject, "mousemove", "mouseup"), touchstart = dragstart(d3_behavior_dragTouchId, d3.touch, d3_behavior_dragTouchSubject, "touchmove", "touchend");
+    var event = d3_eventDispatch(drag, "drag", "dragstart", "dragend"), origin = null, mousedown = dragstart(d3_noop, d3.mouse, d3_window, "mousemove", "mouseup"), touchstart = dragstart(d3_behavior_dragTouchId, d3.touch, d3_identity, "touchmove", "touchend");
     function drag() {
       this.on("mousedown.drag", mousedown).on("touchstart.drag", touchstart);
     }
     function dragstart(id, position, subject, move, end) {
       return function() {
-        var that = this, target = d3.event.target, parent = that.parentNode, dispatch = event.of(that, arguments), dragged = 0, dragId = id(), dragName = ".drag" + (dragId == null ? "" : "-" + dragId), dragOffset, dragSubject = d3.select(subject()).on(move + dragName, moved).on(end + dragName, ended), dragRestore = d3_event_dragSuppress(), position0 = position(parent, dragId);
+        var that = this, target = d3.event.target.correspondingElement || d3.event.target, parent = that.parentNode, dispatch = event.of(that, arguments), dragged = 0, dragId = id(), dragName = ".drag" + (dragId == null ? "" : "-" + dragId), dragOffset, dragSubject = d3.select(subject(target)).on(move + dragName, moved).on(end + dragName, ended), dragRestore = d3_event_dragSuppress(target), position0 = position(parent, dragId);
         if (origin) {
           dragOffset = origin.apply(that, arguments);
           dragOffset = [ dragOffset.x - position0[0], dragOffset.y - position0[1] ];
@@ -1159,7 +1233,7 @@
         function ended() {
           if (!position(parent, dragId)) return;
           dragSubject.on(move + dragName, null).on(end + dragName, null);
-          dragRestore(dragged && d3.event.target === target);
+          dragRestore(dragged);
           dispatch({
             type: "dragend"
           });
@@ -1176,12 +1250,6 @@
   function d3_behavior_dragTouchId() {
     return d3.event.changedTouches[0].identifier;
   }
-  function d3_behavior_dragTouchSubject() {
-    return d3.event.target;
-  }
-  function d3_behavior_dragMouseSubject() {
-    return d3_window;
-  }
   d3.touches = function(container, touches) {
     if (arguments.length < 2) touches = d3_eventSource().touches;
     return touches ? d3_array(touches).map(function(touch) {
@@ -1190,7 +1258,7 @@
       return point;
     }) : [];
   };
-  var π = Math.PI, τ = 2 * π, halfπ = π / 2, ε = 1e-6, ε2 = ε * ε, d3_radians = π / 180, d3_degrees = 180 / π;
+  var ε = 1e-6, ε2 = ε * ε, π = Math.PI, τ = 2 * π, τε = τ - ε, halfπ = π / 2, d3_radians = π / 180, d3_degrees = 180 / π;
   function d3_sgn(x) {
     return x > 0 ? 1 : x < 0 ? -1 : 0;
   }
@@ -1217,25 +1285,38 @@
   }
   var ρ = Math.SQRT2, ρ2 = 2, ρ4 = 4;
   d3.interpolateZoom = function(p0, p1) {
-    var ux0 = p0[0], uy0 = p0[1], w0 = p0[2], ux1 = p1[0], uy1 = p1[1], w1 = p1[2];
-    var dx = ux1 - ux0, dy = uy1 - uy0, d2 = dx * dx + dy * dy, d1 = Math.sqrt(d2), b0 = (w1 * w1 - w0 * w0 + ρ4 * d2) / (2 * w0 * ρ2 * d1), b1 = (w1 * w1 - w0 * w0 - ρ4 * d2) / (2 * w1 * ρ2 * d1), r0 = Math.log(Math.sqrt(b0 * b0 + 1) - b0), r1 = Math.log(Math.sqrt(b1 * b1 + 1) - b1), dr = r1 - r0, S = (dr || Math.log(w1 / w0)) / ρ;
-    function interpolate(t) {
-      var s = t * S;
-      if (dr) {
-        var coshr0 = d3_cosh(r0), u = w0 / (ρ2 * d1) * (coshr0 * d3_tanh(ρ * s + r0) - d3_sinh(r0));
+    var ux0 = p0[0], uy0 = p0[1], w0 = p0[2], ux1 = p1[0], uy1 = p1[1], w1 = p1[2], dx = ux1 - ux0, dy = uy1 - uy0, d2 = dx * dx + dy * dy, i, S;
+    if (d2 < ε2) {
+      S = Math.log(w1 / w0) / ρ;
+      i = function(t) {
+        return [ ux0 + t * dx, uy0 + t * dy, w0 * Math.exp(ρ * t * S) ];
+      };
+    } else {
+      var d1 = Math.sqrt(d2), b0 = (w1 * w1 - w0 * w0 + ρ4 * d2) / (2 * w0 * ρ2 * d1), b1 = (w1 * w1 - w0 * w0 - ρ4 * d2) / (2 * w1 * ρ2 * d1), r0 = Math.log(Math.sqrt(b0 * b0 + 1) - b0), r1 = Math.log(Math.sqrt(b1 * b1 + 1) - b1);
+      S = (r1 - r0) / ρ;
+      i = function(t) {
+        var s = t * S, coshr0 = d3_cosh(r0), u = w0 / (ρ2 * d1) * (coshr0 * d3_tanh(ρ * s + r0) - d3_sinh(r0));
         return [ ux0 + u * dx, uy0 + u * dy, w0 * coshr0 / d3_cosh(ρ * s + r0) ];
-      }
-      return [ ux0 + t * dx, uy0 + t * dy, w0 * Math.exp(ρ * s) ];
+      };
     }
-    interpolate.duration = S * 1e3;
-    return interpolate;
+    i.duration = S * 1e3;
+    return i;
   };
   d3.behavior.zoom = function() {
     var view = {
       x: 0,
       y: 0,
       k: 1
-    }, translate0, center0, center, size = [ 960, 500 ], scaleExtent = d3_behavior_zoomInfinity, mousedown = "mousedown.zoom", mousemove = "mousemove.zoom", mouseup = "mouseup.zoom", mousewheelTimer, touchstart = "touchstart.zoom", touchtime, event = d3_eventDispatch(zoom, "zoomstart", "zoom", "zoomend"), x0, x1, y0, y1;
+    }, translate0, center0, center, size = [ 960, 500 ], scaleExtent = d3_behavior_zoomInfinity, duration = 250, zooming = 0, mousedown = "mousedown.zoom", mousemove = "mousemove.zoom", mouseup = "mouseup.zoom", mousewheelTimer, touchstart = "touchstart.zoom", touchtime, event = d3_eventDispatch(zoom, "zoomstart", "zoom", "zoomend"), x0, x1, y0, y1;
+    if (!d3_behavior_zoomWheel) {
+      d3_behavior_zoomWheel = "onwheel" in d3_document ? (d3_behavior_zoomDelta = function() {
+        return -d3.event.deltaY * (d3.event.deltaMode ? 120 : 1);
+      }, "wheel") : "onmousewheel" in d3_document ? (d3_behavior_zoomDelta = function() {
+        return d3.event.wheelDelta;
+      }, "mousewheel") : (d3_behavior_zoomDelta = function() {
+        return -d3.event.detail;
+      }, "MozMousePixelScroll");
+    }
     function zoom(g) {
       g.on(mousedown, mousedowned).on(d3_behavior_zoomWheel + ".zoom", mousewheeled).on("dblclick.zoom", dblclicked).on(touchstart, touchstarted);
     }
@@ -1251,7 +1332,7 @@
             };
             zoomstarted(dispatch);
           }).tween("zoom:zoom", function() {
-            var dx = size[0], dy = size[1], cx = dx / 2, cy = dy / 2, i = d3.interpolateZoom([ (cx - view.x) / view.k, (cy - view.y) / view.k, dx / view.k ], [ (cx - view1.x) / view1.k, (cy - view1.y) / view1.k, dx / view1.k ]);
+            var dx = size[0], dy = size[1], cx = center0 ? center0[0] : dx / 2, cy = center0 ? center0[1] : dy / 2, i = d3.interpolateZoom([ (cx - view.x) / view.k, (cy - view.y) / view.k, dx / view.k ], [ (cx - view1.x) / view1.k, (cy - view1.y) / view1.k, dx / view1.k ]);
             return function(t) {
               var l = i(t), k = dx / l[2];
               this.__chart__ = view = {
@@ -1261,6 +1342,8 @@
               };
               zoomed(dispatch);
             };
+          }).each("interrupt.zoom", function() {
+            zoomended(dispatch);
           }).each("end.zoom", function() {
             zoomended(dispatch);
           });
@@ -1287,8 +1370,9 @@
       view = {
         x: view.x,
         y: view.y,
-        k: +_
+        k: null
       };
+      scaleTo(+_);
       rescale();
       return zoom;
     };
@@ -1307,6 +1391,11 @@
       size = _ && [ +_[0], +_[1] ];
       return zoom;
     };
+    zoom.duration = function(_) {
+      if (!arguments.length) return duration;
+      duration = +_;
+      return zoom;
+    };
     zoom.x = function(z) {
       if (!arguments.length) return x1;
       x1 = z;
@@ -1343,6 +1432,18 @@
       view.x += p[0] - l[0];
       view.y += p[1] - l[1];
     }
+    function zoomTo(that, p, l, k) {
+      that.__chart__ = {
+        x: view.x,
+        y: view.y,
+        k: view.k
+      };
+      scaleTo(Math.pow(2, k));
+      translateTo(center0 = p, l);
+      that = d3.select(that);
+      if (duration > 0) that = that.transition().duration(duration);
+      that.call(zoom.event);
+    }
     function rescale() {
       if (x1) x1.domain(x0.range().map(function(x) {
         return (x - view.x) / view.k;
@@ -1352,7 +1453,7 @@
       }).map(y0.invert));
     }
     function zoomstarted(dispatch) {
-      dispatch({
+      if (!zooming++) dispatch({
         type: "zoomstart"
       });
     }
@@ -1365,12 +1466,12 @@
       });
     }
     function zoomended(dispatch) {
-      dispatch({
+      if (!--zooming) dispatch({
         type: "zoomend"
-      });
+      }), center0 = null;
     }
     function mousedowned() {
-      var that = this, target = d3.event.target, dispatch = event.of(that, arguments), dragged = 0, subject = d3.select(d3_window).on(mousemove, moved).on(mouseup, ended), location0 = location(d3.mouse(that)), dragRestore = d3_event_dragSuppress();
+      var that = this, dispatch = event.of(that, arguments), dragged = 0, subject = d3.select(d3_window(that)).on(mousemove, moved).on(mouseup, ended), location0 = location(d3.mouse(that)), dragRestore = d3_event_dragSuppress(that);
       d3_selection_interrupt.call(that);
       zoomstarted(dispatch);
       function moved() {
@@ -1380,13 +1481,12 @@
       }
       function ended() {
         subject.on(mousemove, null).on(mouseup, null);
-        dragRestore(dragged && d3.event.target === target);
+        dragRestore(dragged);
         zoomended(dispatch);
       }
     }
     function touchstarted() {
-      var that = this, dispatch = event.of(that, arguments), locations0 = {}, distance0 = 0, scale0, zoomName = ".zoom-" + d3.event.changedTouches[0].identifier, touchmove = "touchmove" + zoomName, touchend = "touchend" + zoomName, targets = [], subject = d3.select(that), dragRestore = d3_event_dragSuppress();
-      d3_selection_interrupt.call(that);
+      var that = this, dispatch = event.of(that, arguments), locations0 = {}, distance0 = 0, scale0, zoomName = ".zoom-" + d3.event.changedTouches[0].identifier, touchmove = "touchmove" + zoomName, touchend = "touchend" + zoomName, targets = [], subject = d3.select(that), dragRestore = d3_event_dragSuppress(that);
       started();
       zoomstarted(dispatch);
       subject.on(mousedown, null).on(touchstart, started);
@@ -1409,11 +1509,9 @@
         var touches = relocate(), now = Date.now();
         if (touches.length === 1) {
           if (now - touchtime < 500) {
-            var p = touches[0], l = locations0[p.identifier];
-            scaleTo(view.k * 2);
-            translateTo(p, l);
+            var p = touches[0];
+            zoomTo(that, p, locations0[p.identifier], Math.floor(Math.log(view.k) / Math.LN2) + 1);
             d3_eventPreventDefault();
-            zoomed(dispatch);
           }
           touchtime = now;
         } else if (touches.length > 1) {
@@ -1423,6 +1521,7 @@
       }
       function moved() {
         var touches = d3.touches(that), p0, l0, p1, l1;
+        d3_selection_interrupt.call(that);
         for (var i = 0, n = touches.length; i < n; ++i, l1 = null) {
           p1 = touches[i];
           if (l1 = locations0[p1.identifier]) {
@@ -1458,8 +1557,8 @@
     }
     function mousewheeled() {
       var dispatch = event.of(this, arguments);
-      if (mousewheelTimer) clearTimeout(mousewheelTimer); else translate0 = location(center0 = center || d3.mouse(this)), 
-      d3_selection_interrupt.call(this), zoomstarted(dispatch);
+      if (mousewheelTimer) clearTimeout(mousewheelTimer); else d3_selection_interrupt.call(this), 
+      translate0 = location(center0 = center || d3.mouse(this)), zoomstarted(dispatch);
       mousewheelTimer = setTimeout(function() {
         mousewheelTimer = null;
         zoomended(dispatch);
@@ -1470,23 +1569,12 @@
       zoomed(dispatch);
     }
     function dblclicked() {
-      var dispatch = event.of(this, arguments), p = d3.mouse(this), l = location(p), k = Math.log(view.k) / Math.LN2;
-      zoomstarted(dispatch);
-      scaleTo(Math.pow(2, d3.event.shiftKey ? Math.ceil(k) - 1 : Math.floor(k) + 1));
-      translateTo(p, l);
-      zoomed(dispatch);
-      zoomended(dispatch);
+      var p = d3.mouse(this), k = Math.log(view.k) / Math.LN2;
+      zoomTo(this, p, location(p), d3.event.shiftKey ? Math.ceil(k) - 1 : Math.floor(k) + 1);
     }
     return d3.rebind(zoom, event, "on");
   };
-  var d3_behavior_zoomInfinity = [ 0, Infinity ];
-  var d3_behavior_zoomDelta, d3_behavior_zoomWheel = "onwheel" in d3_document ? (d3_behavior_zoomDelta = function() {
-    return -d3.event.deltaY * (d3.event.deltaMode ? 120 : 1);
-  }, "wheel") : "onmousewheel" in d3_document ? (d3_behavior_zoomDelta = function() {
-    return d3.event.wheelDelta;
-  }, "mousewheel") : (d3_behavior_zoomDelta = function() {
-    return -d3.event.detail;
-  }, "MozMousePixelScroll");
+  var d3_behavior_zoomInfinity = [ 0, Infinity ], d3_behavior_zoomDelta, d3_behavior_zoomWheel;
   d3.color = d3_color;
   function d3_color() {}
   d3_color.prototype.toString = function() {
@@ -1548,7 +1636,7 @@
   }
   d3.lab = d3_lab;
   function d3_lab(l, a, b) {
-    return this instanceof d3_lab ? void (this.l = +l, this.a = +a, this.b = +b) : arguments.length < 2 ? l instanceof d3_lab ? new d3_lab(l.l, l.a, l.b) : l instanceof d3_hcl ? d3_hcl_lab(l.l, l.c, l.h) : d3_rgb_lab((l = d3_rgb(l)).r, l.g, l.b) : new d3_lab(l, a, b);
+    return this instanceof d3_lab ? void (this.l = +l, this.a = +a, this.b = +b) : arguments.length < 2 ? l instanceof d3_lab ? new d3_lab(l.l, l.a, l.b) : l instanceof d3_hcl ? d3_hcl_lab(l.h, l.c, l.l) : d3_rgb_lab((l = d3_rgb(l)).r, l.g, l.b) : new d3_lab(l, a, b);
   }
   var d3_lab_K = 18;
   var d3_lab_X = .95047, d3_lab_Y = 1, d3_lab_Z = 1.08883;
@@ -1616,7 +1704,7 @@
   }
   function d3_rgb_parse(format, rgb, hsl) {
     var r = 0, g = 0, b = 0, m1, m2, color;
-    m1 = /([a-z]+)\((.*)\)/i.exec(format);
+    m1 = /([a-z]+)\((.*)\)/.exec(format = format.toLowerCase());
     if (m1) {
       m2 = m1[2].split(",");
       switch (m1[1]) {
@@ -1631,7 +1719,9 @@
         }
       }
     }
-    if (color = d3_rgb_names.get(format)) return rgb(color.r, color.g, color.b);
+    if (color = d3_rgb_names.get(format)) {
+      return rgb(color.r, color.g, color.b);
+    }
     if (format != null && format.charAt(0) === "#" && !isNaN(color = parseInt(format.slice(1), 16))) {
       if (format.length === 4) {
         r = (color & 3840) >> 4;
@@ -1794,6 +1884,7 @@
     plum: 14524637,
     powderblue: 11591910,
     purple: 8388736,
+    rebeccapurple: 6697881,
     red: 16711680,
     rosybrown: 12357519,
     royalblue: 4286945,
@@ -1832,9 +1923,6 @@
     };
   }
   d3.functor = d3_functor;
-  function d3_identity(d) {
-    return d;
-  }
   d3.xhr = d3_xhrType(d3_identity);
   function d3_xhrType(response) {
     return function(url, mimeType, callback) {
@@ -1845,7 +1933,7 @@
   }
   function d3_xhr(url, mimeType, response, callback) {
     var xhr = {}, dispatch = d3.dispatch("beforesend", "progress", "load", "error"), headers = {}, request = new XMLHttpRequest(), responseType = null;
-    if (d3_window.XDomainRequest && !("withCredentials" in request) && /^(http(s)?:)?\/\//.test(url)) request = new XDomainRequest();
+    if (this.XDomainRequest && !("withCredentials" in request) && /^(http(s)?:)?\/\//.test(url)) request = new XDomainRequest();
     "onload" in request ? request.onload = request.onerror = respond : request.onreadystatechange = function() {
       request.readyState > 3 && respond();
     };
@@ -1997,7 +2085,7 @@
           a.push(t);
           t = token();
         }
-        if (f && !(a = f(a, n++))) continue;
+        if (f && (a = f(a, n++)) == null) continue;
         rows.push(a);
       }
       return rows;
@@ -2031,17 +2119,19 @@
   };
   d3.csv = d3.dsv(",", "text/csv");
   d3.tsv = d3.dsv("	", "text/tab-separated-values");
-  var d3_timer_queueHead, d3_timer_queueTail, d3_timer_interval, d3_timer_timeout, d3_timer_active, d3_timer_frame = d3_window[d3_vendorSymbol(d3_window, "requestAnimationFrame")] || function(callback) {
+  var d3_timer_queueHead, d3_timer_queueTail, d3_timer_interval, d3_timer_timeout, d3_timer_frame = this[d3_vendorSymbol(this, "requestAnimationFrame")] || function(callback) {
     setTimeout(callback, 17);
   };
-  d3.timer = function(callback, delay, then) {
+  d3.timer = function() {
+    d3_timer.apply(this, arguments);
+  };
+  function d3_timer(callback, delay, then) {
     var n = arguments.length;
     if (n < 2) delay = 0;
     if (n < 3) then = Date.now();
     var time = then + delay, timer = {
       c: callback,
       t: time,
-      f: false,
       n: null
     };
     if (d3_timer_queueTail) d3_timer_queueTail.n = timer; else d3_timer_queueHead = timer;
@@ -2051,7 +2141,8 @@
       d3_timer_interval = 1;
       d3_timer_frame(d3_timer_step);
     }
-  };
+    return timer;
+  }
   function d3_timer_step() {
     var now = d3_timer_mark(), delay = d3_timer_sweep() - now;
     if (delay > 24) {
@@ -2070,22 +2161,21 @@
     d3_timer_sweep();
   };
   function d3_timer_mark() {
-    var now = Date.now();
-    d3_timer_active = d3_timer_queueHead;
-    while (d3_timer_active) {
-      if (now >= d3_timer_active.t) d3_timer_active.f = d3_timer_active.c(now - d3_timer_active.t);
-      d3_timer_active = d3_timer_active.n;
+    var now = Date.now(), timer = d3_timer_queueHead;
+    while (timer) {
+      if (now >= timer.t && timer.c(now - timer.t)) timer.c = null;
+      timer = timer.n;
     }
     return now;
   }
   function d3_timer_sweep() {
     var t0, t1 = d3_timer_queueHead, time = Infinity;
     while (t1) {
-      if (t1.f) {
-        t1 = t0 ? t0.n = t1.n : d3_timer_queueHead = t1.n;
-      } else {
+      if (t1.c) {
         if (t1.t < time) time = t1.t;
         t1 = (t0 = t1).n;
+      } else {
+        t1 = t0 ? t0.n = t1.n : d3_timer_queueHead = t1.n;
       }
     }
     d3_timer_queueTail = t0;
@@ -2100,7 +2190,7 @@
   var d3_formatPrefixes = [ "y", "z", "a", "f", "p", "n", "µ", "m", "", "k", "M", "G", "T", "P", "E", "Z", "Y" ].map(d3_formatPrefix);
   d3.formatPrefix = function(value, precision) {
     var i = 0;
-    if (value) {
+    if (value = +value) {
       if (value < 0) value *= -1;
       if (precision) value = d3.round(value, d3_format_precision(value, precision));
       i = 1 + Math.floor(1e-12 + Math.log(value) / Math.LN10);
@@ -2120,21 +2210,22 @@
     };
   }
   function d3_locale_numberFormat(locale) {
-    var locale_decimal = locale.decimal, locale_thousands = locale.thousands, locale_grouping = locale.grouping, locale_currency = locale.currency, formatGroup = locale_grouping ? function(value) {
-      var i = value.length, t = [], j = 0, g = locale_grouping[0];
-      while (g > 0 && i > 0) {
+    var locale_decimal = locale.decimal, locale_thousands = locale.thousands, locale_grouping = locale.grouping, locale_currency = locale.currency, formatGroup = locale_grouping && locale_thousands ? function(value, width) {
+      var i = value.length, t = [], j = 0, g = locale_grouping[0], length = 0;
+      while (i > 0 && g > 0) {
+        if (length + g + 1 > width) g = Math.max(1, width - length);
         t.push(value.substring(i -= g, i + g));
+        if ((length += g + 1) > width) break;
         g = locale_grouping[j = (j + 1) % locale_grouping.length];
       }
       return t.reverse().join(locale_thousands);
     } : d3_identity;
     return function(specifier) {
-      var match = d3_format_re.exec(specifier), fill = match[1] || " ", align = match[2] || ">", sign = match[3] || "", symbol = match[4] || "", zfill = match[5], width = +match[6], comma = match[7], precision = match[8], type = match[9], scale = 1, prefix = "", suffix = "", integer = false;
+      var match = d3_format_re.exec(specifier), fill = match[1] || " ", align = match[2] || ">", sign = match[3] || "-", symbol = match[4] || "", zfill = match[5], width = +match[6], comma = match[7], precision = match[8], type = match[9], scale = 1, prefix = "", suffix = "", integer = false, exponent = true;
       if (precision) precision = +precision.substring(1);
       if (zfill || fill === "0" && align === "=") {
         zfill = fill = "0";
         align = "=";
-        if (comma) width -= Math.floor((width - 1) / 4);
       }
       switch (type) {
        case "n":
@@ -2161,6 +2252,8 @@
         if (symbol === "#") prefix = "0" + type.toLowerCase();
 
        case "c":
+        exponent = false;
+
        case "d":
         integer = true;
         precision = 0;
@@ -2181,7 +2274,7 @@
       return function(value) {
         var fullSuffix = suffix;
         if (integer && value % 1) return "";
-        var negative = value < 0 || value === 0 && 1 / value < 0 ? (value = -value, "-") : sign;
+        var negative = value < 0 || value === 0 && 1 / value < 0 ? (value = -value, "-") : sign === "-" ? "" : sign;
         if (scale < 0) {
           var unit = d3.formatPrefix(value, precision);
           value = unit.scale(value);
@@ -2190,10 +2283,17 @@
           value *= scale;
         }
         value = type(value, precision);
-        var i = value.lastIndexOf("."), before = i < 0 ? value : value.substring(0, i), after = i < 0 ? "" : locale_decimal + value.substring(i + 1);
-        if (!zfill && comma) before = formatGroup(before);
+        var i = value.lastIndexOf("."), before, after;
+        if (i < 0) {
+          var j = exponent ? value.lastIndexOf("e") : -1;
+          if (j < 0) before = value, after = ""; else before = value.substring(0, j), after = value.substring(j);
+        } else {
+          before = value.substring(0, i);
+          after = locale_decimal + value.substring(i + 1);
+        }
+        if (!zfill && comma) before = formatGroup(before, Infinity);
         var length = prefix.length + before.length + after.length + (zcomma ? 0 : negative.length), padding = length < width ? new Array(length = width - length + 1).join(fill) : "";
-        if (zcomma) before = formatGroup(padding + before);
+        if (zcomma) before = formatGroup(padding + before, padding.length ? width - after.length : Infinity);
         negative += prefix;
         value = before + after;
         return (align === "<" ? negative + value + padding : align === ">" ? padding + negative + value : align === "^" ? padding.substring(0, length >>= 1) + negative + value + padding.substring(length) : negative + (zcomma ? value : padding + value)) + fullSuffix;
@@ -2440,7 +2540,8 @@
         if (i != string.length) return null;
         if ("p" in d) d.H = d.H % 12 + d.p * 12;
         var localZ = d.Z != null && d3_date !== d3_date_utc, date = new (localZ ? d3_date_utc : d3_date)();
-        if ("j" in d) date.setFullYear(d.y, 0, d.j); else if ("w" in d && ("W" in d || "U" in d)) {
+        if ("j" in d) date.setFullYear(d.y, 0, d.j); else if ("W" in d || "U" in d) {
+          if (!("w" in d)) d.w = "W" in d ? 1 : 0;
           date.setFullYear(d.y, 0, 1);
           date.setFullYear(d.y, 0, "W" in d ? (d.w + 6) % 7 + d.W * 7 - (date.getDay() + 5) % 7 : d.w + d.U * 7 - (date.getDay() + 6) % 7);
         } else date.setFullYear(d.y, d.m, d.d);
@@ -3121,6 +3222,15 @@
       d3_geo_centroidPointXYZ(x0, y0, z0);
     }
   }
+  function d3_geo_compose(a, b) {
+    function compose(x, y) {
+      return x = a(x, y), b(x[0], x[1]);
+    }
+    if (a.invert && b.invert) compose.invert = function(x, y) {
+      return x = b.invert(x, y), x && a.invert(x[0], x[1]);
+    };
+    return compose;
+  }
   function d3_true() {
     return true;
   }
@@ -3719,15 +3829,6 @@
       return ca !== cb ? ca - cb : ca === 0 ? b[1] - a[1] : ca === 1 ? a[0] - b[0] : ca === 2 ? a[1] - b[1] : b[0] - a[0];
     }
   }
-  function d3_geo_compose(a, b) {
-    function compose(x, y) {
-      return x = a(x, y), b(x[0], x[1]);
-    }
-    if (a.invert && b.invert) compose.invert = function(x, y) {
-      return x = b.invert(x, y), x && a.invert(x[0], x[1]);
-    };
-    return compose;
-  }
   function d3_geo_conic(projectAt) {
     var φ0 = 0, φ1 = π / 3, m = d3_geo_projectionMutator(projectAt), p = m(φ0, φ1);
     p.parallels = function(_) {
@@ -3999,7 +4100,7 @@
       result: d3_noop
     };
     function point(x, y) {
-      context.moveTo(x, y);
+      context.moveTo(x + pointRadius, y);
       context.arc(x, y, pointRadius, 0, τ);
     }
     function pointLineStart(x, y) {
@@ -5507,11 +5608,11 @@
         }
       }
       function insertChild(n, d, x, y, x1, y1, x2, y2) {
-        var sx = (x1 + x2) * .5, sy = (y1 + y2) * .5, right = x >= sx, bottom = y >= sy, i = (bottom << 1) + right;
+        var xm = (x1 + x2) * .5, ym = (y1 + y2) * .5, right = x >= xm, below = y >= ym, i = below << 1 | right;
         n.leaf = false;
         n = n.nodes[i] || (n.nodes[i] = d3_geom_quadtreeNode());
-        if (right) x1 = sx; else x2 = sx;
-        if (bottom) y1 = sy; else y2 = sy;
+        if (right) x1 = xm; else x2 = xm;
+        if (below) y1 = ym; else y2 = ym;
         insert(n, d, x, y, x1, y1, x2, y2);
       }
       var root = d3_geom_quadtreeNode();
@@ -5521,6 +5622,9 @@
       root.visit = function(f) {
         d3_geom_quadtreeVisit(f, root, x1_, y1_, x2_, y2_);
       };
+      root.find = function(point) {
+        return d3_geom_quadtreeFind(root, point[0], point[1], x1_, y1_, x2_, y2_);
+      };
       i = -1;
       if (x1 == null) {
         while (++i < n) {
@@ -5574,6 +5678,42 @@
       if (children[3]) d3_geom_quadtreeVisit(f, children[3], sx, sy, x2, y2);
     }
   }
+  function d3_geom_quadtreeFind(root, x, y, x0, y0, x3, y3) {
+    var minDistance2 = Infinity, closestPoint;
+    (function find(node, x1, y1, x2, y2) {
+      if (x1 > x3 || y1 > y3 || x2 < x0 || y2 < y0) return;
+      if (point = node.point) {
+        var point, dx = x - node.x, dy = y - node.y, distance2 = dx * dx + dy * dy;
+        if (distance2 < minDistance2) {
+          var distance = Math.sqrt(minDistance2 = distance2);
+          x0 = x - distance, y0 = y - distance;
+          x3 = x + distance, y3 = y + distance;
+          closestPoint = point;
+        }
+      }
+      var children = node.nodes, xm = (x1 + x2) * .5, ym = (y1 + y2) * .5, right = x >= xm, below = y >= ym;
+      for (var i = below << 1 | right, j = i + 4; i < j; ++i) {
+        if (node = children[i & 3]) switch (i & 3) {
+         case 0:
+          find(node, x1, y1, xm, ym);
+          break;
+
+         case 1:
+          find(node, xm, y1, x2, ym);
+          break;
+
+         case 2:
+          find(node, x1, ym, xm, y2);
+          break;
+
+         case 3:
+          find(node, xm, ym, x2, y2);
+          break;
+        }
+      }
+    })(root, x0, y0, x3, y3);
+    return closestPoint;
+  }
   d3.interpolateRgb = d3_interpolateRgb;
   function d3_interpolateRgb(a, b) {
     a = d3.rgb(a);
@@ -5605,9 +5745,9 @@
   }
   d3.interpolateNumber = d3_interpolateNumber;
   function d3_interpolateNumber(a, b) {
-    b -= a = +a;
+    a = +a, b = +b;
     return function(t) {
-      return a + b * t;
+      return a * (1 - t) + b * t;
     };
   }
   d3.interpolateString = d3_interpolateString;
@@ -5652,7 +5792,7 @@
   }
   d3.interpolators = [ function(a, b) {
     var t = typeof b;
-    return (t === "string" ? d3_rgb_names.has(b) || /^(#|rgb\(|hsl\()/.test(b) ? d3_interpolateRgb : d3_interpolateString : b instanceof d3_color ? d3_interpolateRgb : Array.isArray(b) ? d3_interpolateArray : t === "object" && isNaN(b) ? d3_interpolateObject : d3_interpolateNumber)(a, b);
+    return (t === "string" ? d3_rgb_names.has(b.toLowerCase()) || /^(#|rgb\(|hsl\()/i.test(b) ? d3_interpolateRgb : d3_interpolateString : b instanceof d3_color ? d3_interpolateRgb : Array.isArray(b) ? d3_interpolateArray : t === "object" && isNaN(b) ? d3_interpolateObject : d3_interpolateNumber)(a, b);
   } ];
   d3.interpolateArray = d3_interpolateArray;
   function d3_interpolateArray(a, b) {
@@ -5853,68 +5993,82 @@
     f: 0
   };
   d3.interpolateTransform = d3_interpolateTransform;
-  function d3_interpolateTransform(a, b) {
-    var s = [], q = [], n, A = d3.transform(a), B = d3.transform(b), ta = A.translate, tb = B.translate, ra = A.rotate, rb = B.rotate, wa = A.skew, wb = B.skew, ka = A.scale, kb = B.scale;
-    if (ta[0] != tb[0] || ta[1] != tb[1]) {
-      s.push("translate(", null, ",", null, ")");
+  function d3_interpolateTransformPop(s) {
+    return s.length ? s.pop() + "," : "";
+  }
+  function d3_interpolateTranslate(ta, tb, s, q) {
+    if (ta[0] !== tb[0] || ta[1] !== tb[1]) {
+      var i = s.push("translate(", null, ",", null, ")");
       q.push({
-        i: 1,
+        i: i - 4,
         x: d3_interpolateNumber(ta[0], tb[0])
       }, {
-        i: 3,
+        i: i - 2,
         x: d3_interpolateNumber(ta[1], tb[1])
       });
     } else if (tb[0] || tb[1]) {
       s.push("translate(" + tb + ")");
-    } else {
-      s.push("");
     }
-    if (ra != rb) {
+  }
+  function d3_interpolateRotate(ra, rb, s, q) {
+    if (ra !== rb) {
       if (ra - rb > 180) rb += 360; else if (rb - ra > 180) ra += 360;
       q.push({
-        i: s.push(s.pop() + "rotate(", null, ")") - 2,
+        i: s.push(d3_interpolateTransformPop(s) + "rotate(", null, ")") - 2,
         x: d3_interpolateNumber(ra, rb)
       });
     } else if (rb) {
-      s.push(s.pop() + "rotate(" + rb + ")");
+      s.push(d3_interpolateTransformPop(s) + "rotate(" + rb + ")");
     }
-    if (wa != wb) {
+  }
+  function d3_interpolateSkew(wa, wb, s, q) {
+    if (wa !== wb) {
       q.push({
-        i: s.push(s.pop() + "skewX(", null, ")") - 2,
+        i: s.push(d3_interpolateTransformPop(s) + "skewX(", null, ")") - 2,
         x: d3_interpolateNumber(wa, wb)
       });
     } else if (wb) {
-      s.push(s.pop() + "skewX(" + wb + ")");
+      s.push(d3_interpolateTransformPop(s) + "skewX(" + wb + ")");
     }
-    if (ka[0] != kb[0] || ka[1] != kb[1]) {
-      n = s.push(s.pop() + "scale(", null, ",", null, ")");
+  }
+  function d3_interpolateScale(ka, kb, s, q) {
+    if (ka[0] !== kb[0] || ka[1] !== kb[1]) {
+      var i = s.push(d3_interpolateTransformPop(s) + "scale(", null, ",", null, ")");
       q.push({
-        i: n - 4,
+        i: i - 4,
         x: d3_interpolateNumber(ka[0], kb[0])
       }, {
-        i: n - 2,
+        i: i - 2,
         x: d3_interpolateNumber(ka[1], kb[1])
       });
-    } else if (kb[0] != 1 || kb[1] != 1) {
-      s.push(s.pop() + "scale(" + kb + ")");
+    } else if (kb[0] !== 1 || kb[1] !== 1) {
+      s.push(d3_interpolateTransformPop(s) + "scale(" + kb + ")");
     }
-    n = q.length;
+  }
+  function d3_interpolateTransform(a, b) {
+    var s = [], q = [];
+    a = d3.transform(a), b = d3.transform(b);
+    d3_interpolateTranslate(a.translate, b.translate, s, q);
+    d3_interpolateRotate(a.rotate, b.rotate, s, q);
+    d3_interpolateSkew(a.skew, b.skew, s, q);
+    d3_interpolateScale(a.scale, b.scale, s, q);
+    a = b = null;
     return function(t) {
-      var i = -1, o;
+      var i = -1, n = q.length, o;
       while (++i < n) s[(o = q[i]).i] = o.x(t);
       return s.join("");
     };
   }
   function d3_uninterpolateNumber(a, b) {
-    b = b - (a = +a) ? 1 / (b - a) : 0;
+    b = (b -= a = +a) || 1 / b;
     return function(x) {
-      return (x - a) * b;
+      return (x - a) / b;
     };
   }
   function d3_uninterpolateClamp(a, b) {
-    b = b - (a = +a) ? 1 / (b - a) : 0;
+    b = (b -= a = +a) || 1 / b;
     return function(x) {
-      return Math.max(0, Math.min(1, (x - a) * b));
+      return Math.max(0, Math.min(1, (x - a) / b));
     };
   }
   d3.layout = {};
@@ -6004,7 +6158,7 @@
           index: di,
           startAngle: x0,
           endAngle: x,
-          value: (x - x0) / k
+          value: groupSums[di]
         };
         x += padding;
       }
@@ -6072,7 +6226,7 @@
     return chord;
   };
   d3.layout.force = function() {
-    var force = {}, event = d3.dispatch("start", "tick", "end"), size = [ 1, 1 ], drag, alpha, friction = .9, linkDistance = d3_layout_forceLinkDistance, linkStrength = d3_layout_forceLinkStrength, charge = -30, chargeDistance2 = d3_layout_forceChargeDistance2, gravity = .1, theta2 = .64, nodes = [], links = [], distances, strengths, charges;
+    var force = {}, event = d3.dispatch("start", "tick", "end"), timer, size = [ 1, 1 ], drag, alpha, friction = .9, linkDistance = d3_layout_forceLinkDistance, linkStrength = d3_layout_forceLinkStrength, charge = -30, chargeDistance2 = d3_layout_forceChargeDistance2, gravity = .1, theta2 = .64, nodes = [], links = [], distances, strengths, charges;
     function repulse(node) {
       return function(quad, x1, _, x2) {
         if (quad.point !== node) {
@@ -6096,6 +6250,7 @@
     }
     force.tick = function() {
       if ((alpha *= .99) < .005) {
+        timer = null;
         event.end({
           type: "end",
           alpha: alpha = 0
@@ -6113,7 +6268,7 @@
           l = alpha * strengths[i] * ((l = Math.sqrt(l)) - distances[i]) / l;
           x *= l;
           y *= l;
-          t.x -= x * (k = s.weight / (t.weight + s.weight));
+          t.x -= x * (k = s.weight + t.weight ? s.weight / (s.weight + t.weight) : .5);
           t.y -= y * k;
           s.x += x * (k = 1 - k);
           s.y += y * k;
@@ -6209,18 +6364,21 @@
       if (!arguments.length) return alpha;
       x = +x;
       if (alpha) {
-        if (x > 0) alpha = x; else alpha = 0;
+        if (x > 0) {
+          alpha = x;
+        } else {
+          timer.c = null, timer.t = NaN, timer = null;
+          event.end({
+            type: "end",
+            alpha: alpha = 0
+          });
+        }
       } else if (x > 0) {
         event.start({
           type: "start",
           alpha: alpha = x
         });
-        //d3.timer(force.tick);
-        setTimeout(function tick(){
-          force.tick();
-          if(alpha >= .005);
-          setTimeout(tick, 0);
-        }, 0);
+        timer = d3_timer(force.tick);
       }
       return force;
     };
@@ -6262,8 +6420,8 @@
             neighbors[o.target.index].push(o.source);
           }
         }
-        var candidates = neighbors[i], j = -1, m = candidates.length, x;
-        while (++j < m) if (!isNaN(x = candidates[j][dimension])) return x;
+        var candidates = neighbors[i], j = -1, l = candidates.length, x;
+        while (++j < l) if (!isNaN(x = candidates[j][dimension])) return x;
         return Math.random() * size;
       }
       return force.resume();
@@ -6470,49 +6628,50 @@
     return d3_layout_hierarchyRebind(partition, hierarchy);
   };
   d3.layout.pie = function() {
-    var value = Number, sort = d3_layout_pieSortByValue, startAngle = 0, endAngle = τ;
+    var value = Number, sort = d3_layout_pieSortByValue, startAngle = 0, endAngle = τ, padAngle = 0;
     function pie(data) {
-      var values = data.map(function(d, i) {
+      var n = data.length, values = data.map(function(d, i) {
         return +value.call(pie, d, i);
-      });
-      var a = +(typeof startAngle === "function" ? startAngle.apply(this, arguments) : startAngle);
-      var k = ((typeof endAngle === "function" ? endAngle.apply(this, arguments) : endAngle) - a) / d3.sum(values);
-      var index = d3.range(data.length);
+      }), a = +(typeof startAngle === "function" ? startAngle.apply(this, arguments) : startAngle), da = (typeof endAngle === "function" ? endAngle.apply(this, arguments) : endAngle) - a, p = Math.min(Math.abs(da) / n, +(typeof padAngle === "function" ? padAngle.apply(this, arguments) : padAngle)), pa = p * (da < 0 ? -1 : 1), sum = d3.sum(values), k = sum ? (da - n * pa) / sum : 0, index = d3.range(n), arcs = [], v;
       if (sort != null) index.sort(sort === d3_layout_pieSortByValue ? function(i, j) {
         return values[j] - values[i];
       } : function(i, j) {
         return sort(data[i], data[j]);
       });
-      var arcs = [];
       index.forEach(function(i) {
-        var d;
         arcs[i] = {
           data: data[i],
-          value: d = values[i],
+          value: v = values[i],
           startAngle: a,
-          endAngle: a += d * k
+          endAngle: a += v * k + pa,
+          padAngle: p
         };
       });
       return arcs;
     }
-    pie.value = function(x) {
+    pie.value = function(_) {
       if (!arguments.length) return value;
-      value = x;
+      value = _;
       return pie;
     };
-    pie.sort = function(x) {
+    pie.sort = function(_) {
       if (!arguments.length) return sort;
-      sort = x;
+      sort = _;
       return pie;
     };
-    pie.startAngle = function(x) {
+    pie.startAngle = function(_) {
       if (!arguments.length) return startAngle;
-      startAngle = x;
+      startAngle = _;
       return pie;
     };
-    pie.endAngle = function(x) {
+    pie.endAngle = function(_) {
       if (!arguments.length) return endAngle;
-      endAngle = x;
+      endAngle = _;
+      return pie;
+    };
+    pie.padAngle = function(_) {
+      if (!arguments.length) return padAngle;
+      padAngle = _;
       return pie;
     };
     return pie;
@@ -6521,6 +6680,7 @@
   d3.layout.stack = function() {
     var values = d3_identity, order = d3_layout_stackOrderDefault, offset = d3_layout_stackOffsetZero, out = d3_layout_stackOut, x = d3_layout_stackX, y = d3_layout_stackY;
     function stack(data, index) {
+      if (!(n = data.length)) return data;
       var series = data.map(function(d, i) {
         return values.call(stack, d, i);
       });
@@ -6533,7 +6693,7 @@
       series = d3.permute(series, orders);
       points = d3.permute(points, orders);
       var offsets = offset.call(stack, points, index);
-      var n = series.length, m = series[0].length, i, j, o;
+      var m = series[0].length, n, i, j, o;
       for (j = 0; j < m; ++j) {
         out.call(stack, series[0][j], o = offsets[j], points[0][j][1]);
         for (i = 1; i < n; ++i) {
@@ -7185,10 +7345,8 @@
     }
     function treemap(d) {
       var nodes = stickies || hierarchy(d), root = nodes[0];
-      root.x = 0;
-      root.y = 0;
-      root.dx = size[0];
-      root.dy = size[1];
+      root.x = root.y = 0;
+      if (root.value) root.dx = size[0], root.dy = size[1]; else root.dx = root.dy = 0;
       if (stickies) hierarchy.revalue(root);
       scale([ root ], root.dx * root.dy / root.value);
       (stickies ? stickify : squarify)(root);
@@ -7408,7 +7566,9 @@
     return d3.rebind(scale, linear, "range", "rangeRound", "interpolate", "clamp");
   }
   function d3_scale_linearNice(domain, m) {
-    return d3_scale_nice(domain, d3_scale_niceStep(d3_scale_linearTickRange(domain, m)[2]));
+    d3_scale_nice(domain, d3_scale_niceStep(d3_scale_linearTickRange(domain, m)[2]));
+    d3_scale_nice(domain, d3_scale_niceStep(d3_scale_linearTickRange(domain, m)[2]));
+    return domain;
   }
   function d3_scale_linearTickRange(domain, m) {
     if (m == null) m = 10;
@@ -7510,10 +7670,11 @@
     scale.tickFormat = function(n, format) {
       if (!arguments.length) return d3_scale_logFormat;
       if (arguments.length < 2) format = d3_scale_logFormat; else if (typeof format !== "function") format = d3.format(format);
-      var k = Math.max(.1, n / scale.ticks().length), f = positive ? (e = 1e-12, Math.ceil) : (e = -1e-12, 
-      Math.floor), e;
+      var k = Math.max(1, base * n / scale.ticks().length);
       return function(d) {
-        return d / pow(f(log(d) + e)) <= k ? format(d) : "";
+        var i = d / pow(Math.round(log(d)));
+        if (i * base < base - .5) i *= base;
+        return i <= k ? format(d) : "";
       };
     };
     scale.copy = function() {
@@ -7610,8 +7771,9 @@
     };
     scale.rangePoints = function(x, padding) {
       if (arguments.length < 2) padding = 0;
-      var start = x[0], stop = x[1], step = (stop - start) / (Math.max(1, domain.length - 1) + padding);
-      range = steps(domain.length < 2 ? (start + stop) / 2 : start + step * padding / 2, step);
+      var start = x[0], stop = x[1], step = domain.length < 2 ? (start = (start + stop) / 2, 
+      0) : (stop - start) / (domain.length - 1 + padding);
+      range = steps(start + step * padding / 2, step);
       rangeBand = 0;
       ranger = {
         t: "rangePoints",
@@ -7619,6 +7781,18 @@
       };
       return scale;
     };
+    scale.rangeRoundPoints = function(x, padding) {
+      if (arguments.length < 2) padding = 0;
+      var start = x[0], stop = x[1], step = domain.length < 2 ? (start = stop = Math.round((start + stop) / 2), 
+      0) : (stop - start) / (domain.length - 1 + padding) | 0;
+      range = steps(start + Math.round(step * padding / 2 + (stop - start - (domain.length - 1 + padding) * step) / 2), step);
+      rangeBand = 0;
+      ranger = {
+        t: "rangeRoundPoints",
+        a: arguments
+      };
+      return scale;
+    };
     scale.rangeBands = function(x, padding, outerPadding) {
       if (arguments.length < 2) padding = 0;
       if (arguments.length < 3) outerPadding = padding;
@@ -7635,8 +7809,8 @@
     scale.rangeRoundBands = function(x, padding, outerPadding) {
       if (arguments.length < 2) padding = 0;
       if (arguments.length < 3) outerPadding = padding;
-      var reverse = x[1] < x[0], start = x[reverse - 0], stop = x[1 - reverse], step = Math.floor((stop - start) / (domain.length - padding + 2 * outerPadding)), error = stop - start - (domain.length - padding) * step;
-      range = steps(start + Math.round(error / 2), step);
+      var reverse = x[1] < x[0], start = x[reverse - 0], stop = x[1 - reverse], step = Math.floor((stop - start) / (domain.length - padding + 2 * outerPadding));
+      range = steps(start + Math.round((stop - start - (domain.length - padding) * step) / 2), step);
       if (reverse) range.reverse();
       rangeBand = Math.round(step * (1 - padding));
       ranger = {
@@ -7688,7 +7862,7 @@
     }
     scale.domain = function(x) {
       if (!arguments.length) return domain;
-      domain = x.filter(d3_number).sort(d3_ascending);
+      domain = x.map(d3_number).filter(d3_numeric).sort(d3_ascending);
       return rescale();
     };
     scale.range = function(x) {
@@ -7793,12 +7967,91 @@
     return identity;
   }
   d3.svg = {};
+  function d3_zero() {
+    return 0;
+  }
   d3.svg.arc = function() {
-    var innerRadius = d3_svg_arcInnerRadius, outerRadius = d3_svg_arcOuterRadius, startAngle = d3_svg_arcStartAngle, endAngle = d3_svg_arcEndAngle;
+    var innerRadius = d3_svg_arcInnerRadius, outerRadius = d3_svg_arcOuterRadius, cornerRadius = d3_zero, padRadius = d3_svg_arcAuto, startAngle = d3_svg_arcStartAngle, endAngle = d3_svg_arcEndAngle, padAngle = d3_svg_arcPadAngle;
     function arc() {
-      var r0 = innerRadius.apply(this, arguments), r1 = outerRadius.apply(this, arguments), a0 = startAngle.apply(this, arguments) + d3_svg_arcOffset, a1 = endAngle.apply(this, arguments) + d3_svg_arcOffset, da = (a1 < a0 && (da = a0, 
-      a0 = a1, a1 = da), a1 - a0), df = da < π ? "0" : "1", c0 = Math.cos(a0), s0 = Math.sin(a0), c1 = Math.cos(a1), s1 = Math.sin(a1);
-      return da >= d3_svg_arcMax ? r0 ? "M0," + r1 + "A" + r1 + "," + r1 + " 0 1,1 0," + -r1 + "A" + r1 + "," + r1 + " 0 1,1 0," + r1 + "M0," + r0 + "A" + r0 + "," + r0 + " 0 1,0 0," + -r0 + "A" + r0 + "," + r0 + " 0 1,0 0," + r0 + "Z" : "M0," + r1 + "A" + r1 + "," + r1 + " 0 1,1 0," + -r1 + "A" + r1 + "," + r1 + " 0 1,1 0," + r1 + "Z" : r0 ? "M" + r1 * c0 + "," + r1 * s0 + "A" + r1 + "," + r1 + " 0 " + df + ",1 " + r1 * c1 + "," + r1 * s1 + "L" + r0 * c1 + "," + r0 * s1 + "A" + r0 + "," + r0 + " 0 " + df + ",0 " + r0 * c0 + "," + r0 * s0 + "Z" : "M" + r1 * c0 + "," + r1 * s0 + "A" + r1 + "," + r1 + " 0 " + df + ",1 " + r1 * c1 + "," + r1 * s1 + "L0,0" + "Z";
+      var r0 = Math.max(0, +innerRadius.apply(this, arguments)), r1 = Math.max(0, +outerRadius.apply(this, arguments)), a0 = startAngle.apply(this, arguments) - halfπ, a1 = endAngle.apply(this, arguments) - halfπ, da = Math.abs(a1 - a0), cw = a0 > a1 ? 0 : 1;
+      if (r1 < r0) rc = r1, r1 = r0, r0 = rc;
+      if (da >= τε) return circleSegment(r1, cw) + (r0 ? circleSegment(r0, 1 - cw) : "") + "Z";
+      var rc, cr, rp, ap, p0 = 0, p1 = 0, x0, y0, x1, y1, x2, y2, x3, y3, path = [];
+      if (ap = (+padAngle.apply(this, arguments) || 0) / 2) {
+        rp = padRadius === d3_svg_arcAuto ? Math.sqrt(r0 * r0 + r1 * r1) : +padRadius.apply(this, arguments);
+        if (!cw) p1 *= -1;
+        if (r1) p1 = d3_asin(rp / r1 * Math.sin(ap));
+        if (r0) p0 = d3_asin(rp / r0 * Math.sin(ap));
+      }
+      if (r1) {
+        x0 = r1 * Math.cos(a0 + p1);
+        y0 = r1 * Math.sin(a0 + p1);
+        x1 = r1 * Math.cos(a1 - p1);
+        y1 = r1 * Math.sin(a1 - p1);
+        var l1 = Math.abs(a1 - a0 - 2 * p1) <= π ? 0 : 1;
+        if (p1 && d3_svg_arcSweep(x0, y0, x1, y1) === cw ^ l1) {
+          var h1 = (a0 + a1) / 2;
+          x0 = r1 * Math.cos(h1);
+          y0 = r1 * Math.sin(h1);
+          x1 = y1 = null;
+        }
+      } else {
+        x0 = y0 = 0;
+      }
+      if (r0) {
+        x2 = r0 * Math.cos(a1 - p0);
+        y2 = r0 * Math.sin(a1 - p0);
+        x3 = r0 * Math.cos(a0 + p0);
+        y3 = r0 * Math.sin(a0 + p0);
+        var l0 = Math.abs(a0 - a1 + 2 * p0) <= π ? 0 : 1;
+        if (p0 && d3_svg_arcSweep(x2, y2, x3, y3) === 1 - cw ^ l0) {
+          var h0 = (a0 + a1) / 2;
+          x2 = r0 * Math.cos(h0);
+          y2 = r0 * Math.sin(h0);
+          x3 = y3 = null;
+        }
+      } else {
+        x2 = y2 = 0;
+      }
+      if (da > ε && (rc = Math.min(Math.abs(r1 - r0) / 2, +cornerRadius.apply(this, arguments))) > .001) {
+        cr = r0 < r1 ^ cw ? 0 : 1;
+        var rc1 = rc, rc0 = rc;
+        if (da < π) {
+          var oc = x3 == null ? [ x2, y2 ] : x1 == null ? [ x0, y0 ] : d3_geom_polygonIntersect([ x0, y0 ], [ x3, y3 ], [ x1, y1 ], [ x2, y2 ]), ax = x0 - oc[0], ay = y0 - oc[1], bx = x1 - oc[0], by = y1 - oc[1], kc = 1 / Math.sin(Math.acos((ax * bx + ay * by) / (Math.sqrt(ax * ax + ay * ay) * Math.sqrt(bx * bx + by * by))) / 2), lc = Math.sqrt(oc[0] * oc[0] + oc[1] * oc[1]);
+          rc0 = Math.min(rc, (r0 - lc) / (kc - 1));
+          rc1 = Math.min(rc, (r1 - lc) / (kc + 1));
+        }
+        if (x1 != null) {
+          var t30 = d3_svg_arcCornerTangents(x3 == null ? [ x2, y2 ] : [ x3, y3 ], [ x0, y0 ], r1, rc1, cw), t12 = d3_svg_arcCornerTangents([ x1, y1 ], [ x2, y2 ], r1, rc1, cw);
+          if (rc === rc1) {
+            path.push("M", t30[0], "A", rc1, ",", rc1, " 0 0,", cr, " ", t30[1], "A", r1, ",", r1, " 0 ", 1 - cw ^ d3_svg_arcSweep(t30[1][0], t30[1][1], t12[1][0], t12[1][1]), ",", cw, " ", t12[1], "A", rc1, ",", rc1, " 0 0,", cr, " ", t12[0]);
+          } else {
+            path.push("M", t30[0], "A", rc1, ",", rc1, " 0 1,", cr, " ", t12[0]);
+          }
+        } else {
+          path.push("M", x0, ",", y0);
+        }
+        if (x3 != null) {
+          var t03 = d3_svg_arcCornerTangents([ x0, y0 ], [ x3, y3 ], r0, -rc0, cw), t21 = d3_svg_arcCornerTangents([ x2, y2 ], x1 == null ? [ x0, y0 ] : [ x1, y1 ], r0, -rc0, cw);
+          if (rc === rc0) {
+            path.push("L", t21[0], "A", rc0, ",", rc0, " 0 0,", cr, " ", t21[1], "A", r0, ",", r0, " 0 ", cw ^ d3_svg_arcSweep(t21[1][0], t21[1][1], t03[1][0], t03[1][1]), ",", 1 - cw, " ", t03[1], "A", rc0, ",", rc0, " 0 0,", cr, " ", t03[0]);
+          } else {
+            path.push("L", t21[0], "A", rc0, ",", rc0, " 0 0,", cr, " ", t03[0]);
+          }
+        } else {
+          path.push("L", x2, ",", y2);
+        }
+      } else {
+        path.push("M", x0, ",", y0);
+        if (x1 != null) path.push("A", r1, ",", r1, " 0 ", l1, ",", cw, " ", x1, ",", y1);
+        path.push("L", x2, ",", y2);
+        if (x3 != null) path.push("A", r0, ",", r0, " 0 ", l0, ",", 1 - cw, " ", x3, ",", y3);
+      }
+      path.push("Z");
+      return path.join("");
+    }
+    function circleSegment(r1, cw) {
+      return "M0," + r1 + "A" + r1 + "," + r1 + " 0 1," + cw + " 0," + -r1 + "A" + r1 + "," + r1 + " 0 1," + cw + " 0," + r1;
     }
     arc.innerRadius = function(v) {
       if (!arguments.length) return innerRadius;
@@ -7810,6 +8063,16 @@
       outerRadius = d3_functor(v);
       return arc;
     };
+    arc.cornerRadius = function(v) {
+      if (!arguments.length) return cornerRadius;
+      cornerRadius = d3_functor(v);
+      return arc;
+    };
+    arc.padRadius = function(v) {
+      if (!arguments.length) return padRadius;
+      padRadius = v == d3_svg_arcAuto ? d3_svg_arcAuto : d3_functor(v);
+      return arc;
+    };
     arc.startAngle = function(v) {
       if (!arguments.length) return startAngle;
       startAngle = d3_functor(v);
@@ -7820,13 +8083,18 @@
       endAngle = d3_functor(v);
       return arc;
     };
+    arc.padAngle = function(v) {
+      if (!arguments.length) return padAngle;
+      padAngle = d3_functor(v);
+      return arc;
+    };
     arc.centroid = function() {
-      var r = (innerRadius.apply(this, arguments) + outerRadius.apply(this, arguments)) / 2, a = (startAngle.apply(this, arguments) + endAngle.apply(this, arguments)) / 2 + d3_svg_arcOffset;
+      var r = (+innerRadius.apply(this, arguments) + +outerRadius.apply(this, arguments)) / 2, a = (+startAngle.apply(this, arguments) + +endAngle.apply(this, arguments)) / 2 - halfπ;
       return [ Math.cos(a) * r, Math.sin(a) * r ];
     };
     return arc;
   };
-  var d3_svg_arcOffset = -halfπ, d3_svg_arcMax = τ - ε;
+  var d3_svg_arcAuto = "auto";
   function d3_svg_arcInnerRadius(d) {
     return d.innerRadius;
   }
@@ -7839,6 +8107,17 @@
   function d3_svg_arcEndAngle(d) {
     return d.endAngle;
   }
+  function d3_svg_arcPadAngle(d) {
+    return d && d.padAngle;
+  }
+  function d3_svg_arcSweep(x0, y0, x1, y1) {
+    return (x0 - x1) * y0 - (y0 - y1) * x0 > 0 ? 0 : 1;
+  }
+  function d3_svg_arcCornerTangents(p0, p1, r1, rc, cw) {
+    var x01 = p0[0] - p1[0], y01 = p0[1] - p1[1], lo = (cw ? rc : -rc) / Math.sqrt(x01 * x01 + y01 * y01), ox = lo * y01, oy = -lo * x01, x1 = p0[0] + ox, y1 = p0[1] + oy, x2 = p1[0] + ox, y2 = p1[1] + oy, x3 = (x1 + x2) / 2, y3 = (y1 + y2) / 2, dx = x2 - x1, dy = y2 - y1, d2 = dx * dx + dy * dy, r = r1 - rc, D = x1 * y2 - x2 * y1, d = (dy < 0 ? -1 : 1) * Math.sqrt(Math.max(0, r * r * d2 - D * D)), cx0 = (D * dy - dx * d) / d2, cy0 = (-D * dx - dy * d) / d2, cx1 = (D * dy + dx * d) / d2, cy1 = (-D * dx + dy * d) / d2, dx0 = cx0 - x3, dy0 = cy0 - y3, dx1 = cx1 - x3, dy1 = cy1 - y3;
+    if (dx0 * dx0 + dy0 * dy0 > dx1 * dx1 + dy1 * dy1) cx0 = cx1, cy0 = cy1;
+    return [ [ cx0 - ox, cy0 - oy ], [ cx0 * r1 / r, cy0 * r1 / r ] ];
+  }
   function d3_svg_line(projection) {
     var x = d3_geom_pointX, y = d3_geom_pointY, defined = d3_true, interpolate = d3_svg_lineLinear, interpolateKey = interpolate.key, tension = .7;
     function line(data) {
@@ -7907,10 +8186,10 @@
     value.closed = /-closed$/.test(key);
   });
   function d3_svg_lineLinear(points) {
-    return points.join("L");
+    return points.length > 1 ? points.join("L") : points + "Z";
   }
   function d3_svg_lineLinearClosed(points) {
-    return d3_svg_lineLinear(points) + "Z";
+    return points.join("L") + "Z";
   }
   function d3_svg_lineStep(points) {
     var i = 0, n = points.length, p = points[0], path = [ p[0], ",", p[1] ];
@@ -7929,10 +8208,10 @@
     return path.join("");
   }
   function d3_svg_lineCardinalOpen(points, tension) {
-    return points.length < 4 ? d3_svg_lineLinear(points) : points[1] + d3_svg_lineHermite(points.slice(1, points.length - 1), d3_svg_lineCardinalTangents(points, tension));
+    return points.length < 4 ? d3_svg_lineLinear(points) : points[1] + d3_svg_lineHermite(points.slice(1, -1), d3_svg_lineCardinalTangents(points, tension));
   }
   function d3_svg_lineCardinalClosed(points, tension) {
-    return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite((points.push(points[0]), 
+    return points.length < 3 ? d3_svg_lineLinearClosed(points) : points[0] + d3_svg_lineHermite((points.push(points[0]), 
     points), d3_svg_lineCardinalTangents([ points[points.length - 2] ].concat(points, [ points[1] ]), tension));
   }
   function d3_svg_lineCardinal(points, tension) {
@@ -8099,7 +8378,7 @@
     while (++i < n) {
       point = points[i];
       r = point[0];
-      a = point[1] + d3_svg_arcOffset;
+      a = point[1] - halfπ;
       point[0] = r * Math.cos(a);
       point[1] = r * Math.sin(a);
     }
@@ -8200,7 +8479,7 @@
       return "M" + s.p0 + arc(s.r, s.p1, s.a1 - s.a0) + (equals(s, t) ? curve(s.r, s.p1, s.r, s.p0) : curve(s.r, s.p1, t.r, t.p0) + arc(t.r, t.p1, t.a1 - t.a0) + curve(t.r, t.p1, s.r, s.p0)) + "Z";
     }
     function subgroup(self, f, d, i) {
-      var subgroup = f.call(self, d, i), r = radius.call(self, subgroup, i), a0 = startAngle.call(self, subgroup, i) + d3_svg_arcOffset, a1 = endAngle.call(self, subgroup, i) + d3_svg_arcOffset;
+      var subgroup = f.call(self, d, i), r = radius.call(self, subgroup, i), a0 = startAngle.call(self, subgroup, i) - halfπ, a1 = endAngle.call(self, subgroup, i) - halfπ;
       return {
         r: r,
         a0: a0,
@@ -8290,7 +8569,7 @@
   };
   function d3_svg_diagonalRadialProjection(projection) {
     return function() {
-      var d = projection.apply(this, arguments), r = d[0], a = d[1] + d3_svg_arcOffset;
+      var d = projection.apply(this, arguments), r = d[0], a = d[1] - halfπ;
       return [ r * Math.cos(a), r * Math.sin(a) ];
     };
   }
@@ -8346,8 +8625,41 @@
   });
   d3.svg.symbolTypes = d3_svg_symbols.keys();
   var d3_svg_symbolSqrt3 = Math.sqrt(3), d3_svg_symbolTan30 = Math.tan(30 * d3_radians);
-  function d3_transition(groups, id) {
+  d3_selectionPrototype.transition = function(name) {
+    var id = d3_transitionInheritId || ++d3_transitionId, ns = d3_transitionNamespace(name), subgroups = [], subgroup, node, transition = d3_transitionInherit || {
+      time: Date.now(),
+      ease: d3_ease_cubicInOut,
+      delay: 0,
+      duration: 250
+    };
+    for (var j = -1, m = this.length; ++j < m; ) {
+      subgroups.push(subgroup = []);
+      for (var group = this[j], i = -1, n = group.length; ++i < n; ) {
+        if (node = group[i]) d3_transitionNode(node, i, ns, id, transition);
+        subgroup.push(node);
+      }
+    }
+    return d3_transition(subgroups, ns, id);
+  };
+  d3_selectionPrototype.interrupt = function(name) {
+    return this.each(name == null ? d3_selection_interrupt : d3_selection_interruptNS(d3_transitionNamespace(name)));
+  };
+  var d3_selection_interrupt = d3_selection_interruptNS(d3_transitionNamespace());
+  function d3_selection_interruptNS(ns) {
+    return function() {
+      var lock, activeId, active;
+      if ((lock = this[ns]) && (active = lock[activeId = lock.active])) {
+        active.timer.c = null;
+        active.timer.t = NaN;
+        if (--lock.count) delete lock[activeId]; else delete this[ns];
+        lock.active += .5;
+        active.event && active.event.interrupt.call(this, this.__data__, active.index);
+      }
+    };
+  }
+  function d3_transition(groups, ns, id) {
     d3_subclass(groups, d3_transitionPrototype);
+    groups.namespace = ns;
     groups.id = id;
     return groups;
   }
@@ -8356,44 +8668,44 @@
   d3_transitionPrototype.empty = d3_selectionPrototype.empty;
   d3_transitionPrototype.node = d3_selectionPrototype.node;
   d3_transitionPrototype.size = d3_selectionPrototype.size;
-  d3.transition = function(selection) {
-    return arguments.length ? d3_transitionInheritId ? selection.transition() : selection : d3_selectionRoot.transition();
+  d3.transition = function(selection, name) {
+    return selection && selection.transition ? d3_transitionInheritId ? selection.transition(name) : selection : d3.selection().transition(selection);
   };
   d3.transition.prototype = d3_transitionPrototype;
   d3_transitionPrototype.select = function(selector) {
-    var id = this.id, subgroups = [], subgroup, subnode, node;
+    var id = this.id, ns = this.namespace, subgroups = [], subgroup, subnode, node;
     selector = d3_selection_selector(selector);
     for (var j = -1, m = this.length; ++j < m; ) {
       subgroups.push(subgroup = []);
       for (var group = this[j], i = -1, n = group.length; ++i < n; ) {
         if ((node = group[i]) && (subnode = selector.call(node, node.__data__, i, j))) {
           if ("__data__" in node) subnode.__data__ = node.__data__;
-          d3_transitionNode(subnode, i, id, node.__transition__[id]);
+          d3_transitionNode(subnode, i, ns, id, node[ns][id]);
           subgroup.push(subnode);
         } else {
           subgroup.push(null);
         }
       }
     }
-    return d3_transition(subgroups, id);
+    return d3_transition(subgroups, ns, id);
   };
   d3_transitionPrototype.selectAll = function(selector) {
-    var id = this.id, subgroups = [], subgroup, subnodes, node, subnode, transition;
+    var id = this.id, ns = this.namespace, subgroups = [], subgroup, subnodes, node, subnode, transition;
     selector = d3_selection_selectorAll(selector);
     for (var j = -1, m = this.length; ++j < m; ) {
       for (var group = this[j], i = -1, n = group.length; ++i < n; ) {
         if (node = group[i]) {
-          transition = node.__transition__[id];
+          transition = node[ns][id];
           subnodes = selector.call(node, node.__data__, i, j);
           subgroups.push(subgroup = []);
           for (var k = -1, o = subnodes.length; ++k < o; ) {
-            if (subnode = subnodes[k]) d3_transitionNode(subnode, k, id, transition);
+            if (subnode = subnodes[k]) d3_transitionNode(subnode, k, ns, id, transition);
             subgroup.push(subnode);
           }
         }
       }
     }
-    return d3_transition(subgroups, id);
+    return d3_transition(subgroups, ns, id);
   };
   d3_transitionPrototype.filter = function(filter) {
     var subgroups = [], subgroup, group, node;
@@ -8406,23 +8718,23 @@
         }
       }
     }
-    return d3_transition(subgroups, this.id);
+    return d3_transition(subgroups, this.namespace, this.id);
   };
   d3_transitionPrototype.tween = function(name, tween) {
-    var id = this.id;
-    if (arguments.length < 2) return this.node().__transition__[id].tween.get(name);
+    var id = this.id, ns = this.namespace;
+    if (arguments.length < 2) return this.node()[ns][id].tween.get(name);
     return d3_selection_each(this, tween == null ? function(node) {
-      node.__transition__[id].tween.remove(name);
+      node[ns][id].tween.remove(name);
     } : function(node) {
-      node.__transition__[id].tween.set(name, tween);
+      node[ns][id].tween.set(name, tween);
     });
   };
   function d3_transition_tween(groups, name, value, tween) {
-    var id = groups.id;
+    var id = groups.id, ns = groups.namespace;
     return d3_selection_each(groups, typeof value === "function" ? function(node, i, j) {
-      node.__transition__[id].tween.set(name, tween(value.call(node, node.__data__, i, j)));
+      node[ns][id].tween.set(name, tween(value.call(node, node.__data__, i, j)));
     } : (value = tween(value), function(node) {
-      node.__transition__[id].tween.set(name, value);
+      node[ns][id].tween.set(name, value);
     }));
   }
   d3_transitionPrototype.attr = function(nameNS, value) {
@@ -8486,7 +8798,7 @@
     }
     function styleString(b) {
       return b == null ? styleNull : (b += "", function() {
-        var a = d3_window.getComputedStyle(this, null).getPropertyValue(name), i;
+        var a = d3_window(this).getComputedStyle(this, null).getPropertyValue(name), i;
         return a !== b && (i = d3_interpolate(a, b), function(t) {
           this.style.setProperty(name, i(t), priority);
         });
@@ -8497,7 +8809,7 @@
   d3_transitionPrototype.styleTween = function(name, tween, priority) {
     if (arguments.length < 3) priority = "";
     function styleTween(d, i) {
-      var f = tween.call(this, d, i, d3_window.getComputedStyle(this, null).getPropertyValue(name));
+      var f = tween.call(this, d, i, d3_window(this).getComputedStyle(this, null).getPropertyValue(name));
       return f && function(t) {
         this.style.setProperty(name, f(t), priority);
       };
@@ -8514,121 +8826,155 @@
     };
   }
   d3_transitionPrototype.remove = function() {
+    var ns = this.namespace;
     return this.each("end.transition", function() {
       var p;
-      if (this.__transition__.count < 2 && (p = this.parentNode)) p.removeChild(this);
+      if (this[ns].count < 2 && (p = this.parentNode)) p.removeChild(this);
     });
   };
   d3_transitionPrototype.ease = function(value) {
-    var id = this.id;
-    if (arguments.length < 1) return this.node().__transition__[id].ease;
+    var id = this.id, ns = this.namespace;
+    if (arguments.length < 1) return this.node()[ns][id].ease;
     if (typeof value !== "function") value = d3.ease.apply(d3, arguments);
     return d3_selection_each(this, function(node) {
-      node.__transition__[id].ease = value;
+      node[ns][id].ease = value;
     });
   };
   d3_transitionPrototype.delay = function(value) {
-    var id = this.id;
-    if (arguments.length < 1) return this.node().__transition__[id].delay;
+    var id = this.id, ns = this.namespace;
+    if (arguments.length < 1) return this.node()[ns][id].delay;
     return d3_selection_each(this, typeof value === "function" ? function(node, i, j) {
-      node.__transition__[id].delay = +value.call(node, node.__data__, i, j);
+      node[ns][id].delay = +value.call(node, node.__data__, i, j);
     } : (value = +value, function(node) {
-      node.__transition__[id].delay = value;
+      node[ns][id].delay = value;
     }));
   };
   d3_transitionPrototype.duration = function(value) {
-    var id = this.id;
-    if (arguments.length < 1) return this.node().__transition__[id].duration;
+    var id = this.id, ns = this.namespace;
+    if (arguments.length < 1) return this.node()[ns][id].duration;
     return d3_selection_each(this, typeof value === "function" ? function(node, i, j) {
-      node.__transition__[id].duration = Math.max(1, value.call(node, node.__data__, i, j));
+      node[ns][id].duration = Math.max(1, value.call(node, node.__data__, i, j));
     } : (value = Math.max(1, value), function(node) {
-      node.__transition__[id].duration = value;
+      node[ns][id].duration = value;
     }));
   };
   d3_transitionPrototype.each = function(type, listener) {
-    var id = this.id;
+    var id = this.id, ns = this.namespace;
     if (arguments.length < 2) {
       var inherit = d3_transitionInherit, inheritId = d3_transitionInheritId;
-      d3_transitionInheritId = id;
-      d3_selection_each(this, function(node, i, j) {
-        d3_transitionInherit = node.__transition__[id];
-        type.call(node, node.__data__, i, j);
-      });
-      d3_transitionInherit = inherit;
-      d3_transitionInheritId = inheritId;
+      try {
+        d3_transitionInheritId = id;
+        d3_selection_each(this, function(node, i, j) {
+          d3_transitionInherit = node[ns][id];
+          type.call(node, node.__data__, i, j);
+        });
+      } finally {
+        d3_transitionInherit = inherit;
+        d3_transitionInheritId = inheritId;
+      }
     } else {
       d3_selection_each(this, function(node) {
-        var transition = node.__transition__[id];
-        (transition.event || (transition.event = d3.dispatch("start", "end"))).on(type, listener);
+        var transition = node[ns][id];
+        (transition.event || (transition.event = d3.dispatch("start", "end", "interrupt"))).on(type, listener);
       });
     }
     return this;
   };
   d3_transitionPrototype.transition = function() {
-    var id0 = this.id, id1 = ++d3_transitionId, subgroups = [], subgroup, group, node, transition;
+    var id0 = this.id, id1 = ++d3_transitionId, ns = this.namespace, subgroups = [], subgroup, group, node, transition;
     for (var j = 0, m = this.length; j < m; j++) {
       subgroups.push(subgroup = []);
       for (var group = this[j], i = 0, n = group.length; i < n; i++) {
         if (node = group[i]) {
-          transition = Object.create(node.__transition__[id0]);
-          transition.delay += transition.duration;
-          d3_transitionNode(node, i, id1, transition);
+          transition = node[ns][id0];
+          d3_transitionNode(node, i, ns, id1, {
+            time: transition.time,
+            ease: transition.ease,
+            delay: transition.delay + transition.duration,
+            duration: transition.duration
+          });
         }
         subgroup.push(node);
       }
     }
-    return d3_transition(subgroups, id1);
+    return d3_transition(subgroups, ns, id1);
   };
-  function d3_transitionNode(node, i, id, inherit) {
-    var lock = node.__transition__ || (node.__transition__ = {
+  function d3_transitionNamespace(name) {
+    return name == null ? "__transition__" : "__transition_" + name + "__";
+  }
+  function d3_transitionNode(node, i, ns, id, inherit) {
+    var lock = node[ns] || (node[ns] = {
       active: 0,
       count: 0
-    }), transition = lock[id];
+    }), transition = lock[id], time, timer, duration, ease, tweens;
+    function schedule(elapsed) {
+      var delay = transition.delay;
+      timer.t = delay + time;
+      if (delay <= elapsed) return start(elapsed - delay);
+      timer.c = start;
+    }
+    function start(elapsed) {
+      var activeId = lock.active, active = lock[activeId];
+      if (active) {
+        active.timer.c = null;
+        active.timer.t = NaN;
+        --lock.count;
+        delete lock[activeId];
+        active.event && active.event.interrupt.call(node, node.__data__, active.index);
+      }
+      for (var cancelId in lock) {
+        if (+cancelId < id) {
+          var cancel = lock[cancelId];
+          cancel.timer.c = null;
+          cancel.timer.t = NaN;
+          --lock.count;
+          delete lock[cancelId];
+        }
+      }
+      timer.c = tick;
+      d3_timer(function() {
+        if (timer.c && tick(elapsed || 1)) {
+          timer.c = null;
+          timer.t = NaN;
+        }
+        return 1;
+      }, 0, time);
+      lock.active = id;
+      transition.event && transition.event.start.call(node, node.__data__, i);
+      tweens = [];
+      transition.tween.forEach(function(key, value) {
+        if (value = value.call(node, node.__data__, i)) {
+          tweens.push(value);
+        }
+      });
+      ease = transition.ease;
+      duration = transition.duration;
+    }
+    function tick(elapsed) {
+      var t = elapsed / duration, e = ease(t), n = tweens.length;
+      while (n > 0) {
+        tweens[--n].call(node, e);
+      }
+      if (t >= 1) {
+        transition.event && transition.event.end.call(node, node.__data__, i);
+        if (--lock.count) delete lock[id]; else delete node[ns];
+        return 1;
+      }
+    }
     if (!transition) {
-      var time = inherit.time;
+      time = inherit.time;
+      timer = d3_timer(schedule, 0, time);
       transition = lock[id] = {
         tween: new d3_Map(),
         time: time,
-        ease: inherit.ease,
+        timer: timer,
         delay: inherit.delay,
-        duration: inherit.duration
+        duration: inherit.duration,
+        ease: inherit.ease,
+        index: i
       };
+      inherit = null;
       ++lock.count;
-      d3.timer(function(elapsed) {
-        var d = node.__data__, ease = transition.ease, delay = transition.delay, duration = transition.duration, timer = d3_timer_active, tweened = [];
-        timer.t = delay + time;
-        if (delay <= elapsed) return start(elapsed - delay);
-        timer.c = start;
-        function start(elapsed) {
-          if (lock.active > id) return stop();
-          lock.active = id;
-          transition.event && transition.event.start.call(node, d, i);
-          transition.tween.forEach(function(key, value) {
-            if (value = value.call(node, d, i)) {
-              tweened.push(value);
-            }
-          });
-          d3.timer(function() {
-            timer.c = tick(elapsed || 1) ? d3_true : tick;
-            return 1;
-          }, 0, time);
-        }
-        function tick(elapsed) {
-          if (lock.active !== id) return stop();
-          var t = elapsed / duration, e = ease(t), n = tweened.length;
-          while (n > 0) {
-            tweened[--n].call(node, e);
-          }
-          if (t >= 1) {
-            transition.event && transition.event.end.call(node, d, i);
-            return stop();
-          }
-        }
-        function stop() {
-          if (--lock.count) delete lock[id]; else delete node.__transition__;
-          return 1;
-        }
-      }, 0, time);
     }
   }
   d3.svg.axis = function() {
@@ -8637,61 +8983,25 @@
       g.each(function() {
         var g = d3.select(this);
         var scale0 = this.__chart__ || scale, scale1 = this.__chart__ = scale.copy();
-        var ticks = tickValues == null ? scale1.ticks ? scale1.ticks.apply(scale1, tickArguments_) : scale1.domain() : tickValues, tickFormat = tickFormat_ == null ? scale1.tickFormat ? scale1.tickFormat.apply(scale1, tickArguments_) : d3_identity : tickFormat_, tick = g.selectAll(".tick").data(ticks, scale1), tickEnter = tick.enter().insert("g", ".domain").attr("class", "tick").style("opacity", ε), tickExit = d3.transition(tick.exit()).style("opacity", ε).remove(), tickUpdate = d3.transition(tick.order()).style("opacity", 1), tickTransform;
+        var ticks = tickValues == null ? scale1.ticks ? scale1.ticks.apply(scale1, tickArguments_) : scale1.domain() : tickValues, tickFormat = tickFormat_ == null ? scale1.tickFormat ? scale1.tickFormat.apply(scale1, tickArguments_) : d3_identity : tickFormat_, tick = g.selectAll(".tick").data(ticks, scale1), tickEnter = tick.enter().insert("g", ".domain").attr("class", "tick").style("opacity", ε), tickExit = d3.transition(tick.exit()).style("opacity", ε).remove(), tickUpdate = d3.transition(tick.order()).style("opacity", 1), tickSpacing = Math.max(innerTickSize, 0) + tickPadding, tickTransform;
         var range = d3_scaleRange(scale1), path = g.selectAll(".domain").data([ 0 ]), pathUpdate = (path.enter().append("path").attr("class", "domain"), 
         d3.transition(path));
         tickEnter.append("line");
         tickEnter.append("text");
-        var lineEnter = tickEnter.select("line"), lineUpdate = tickUpdate.select("line"), text = tick.select("text").text(tickFormat), textEnter = tickEnter.select("text"), textUpdate = tickUpdate.select("text");
-        switch (orient) {
-         case "bottom":
-          {
-            tickTransform = d3_svg_axisX;
-            lineEnter.attr("y2", innerTickSize);
-            textEnter.attr("y", Math.max(innerTickSize, 0) + tickPadding);
-            lineUpdate.attr("x2", 0).attr("y2", innerTickSize);
-            textUpdate.attr("x", 0).attr("y", Math.max(innerTickSize, 0) + tickPadding);
-            text.attr("dy", ".71em").style("text-anchor", "middle");
-            pathUpdate.attr("d", "M" + range[0] + "," + outerTickSize + "V0H" + range[1] + "V" + outerTickSize);
-            break;
-          }
-
-         case "top":
-          {
-            tickTransform = d3_svg_axisX;
-            lineEnter.attr("y2", -innerTickSize);
-            textEnter.attr("y", -(Math.max(innerTickSize, 0) + tickPadding));
-            lineUpdate.attr("x2", 0).attr("y2", -innerTickSize);
-            textUpdate.attr("x", 0).attr("y", -(Math.max(innerTickSize, 0) + tickPadding));
-            text.attr("dy", "0em").style("text-anchor", "middle");
-            pathUpdate.attr("d", "M" + range[0] + "," + -outerTickSize + "V0H" + range[1] + "V" + -outerTickSize);
-            break;
-          }
-
-         case "left":
-          {
-            tickTransform = d3_svg_axisY;
-            lineEnter.attr("x2", -innerTickSize);
-            textEnter.attr("x", -(Math.max(innerTickSize, 0) + tickPadding));
-            lineUpdate.attr("x2", -innerTickSize).attr("y2", 0);
-            textUpdate.attr("x", -(Math.max(innerTickSize, 0) + tickPadding)).attr("y", 0);
-            text.attr("dy", ".32em").style("text-anchor", "end");
-            pathUpdate.attr("d", "M" + -outerTickSize + "," + range[0] + "H0V" + range[1] + "H" + -outerTickSize);
-            break;
-          }
-
-         case "right":
-          {
-            tickTransform = d3_svg_axisY;
-            lineEnter.attr("x2", innerTickSize);
-            textEnter.attr("x", Math.max(innerTickSize, 0) + tickPadding);
-            lineUpdate.attr("x2", innerTickSize).attr("y2", 0);
-            textUpdate.attr("x", Math.max(innerTickSize, 0) + tickPadding).attr("y", 0);
-            text.attr("dy", ".32em").style("text-anchor", "start");
-            pathUpdate.attr("d", "M" + outerTickSize + "," + range[0] + "H0V" + range[1] + "H" + outerTickSize);
-            break;
-          }
+        var lineEnter = tickEnter.select("line"), lineUpdate = tickUpdate.select("line"), text = tick.select("text").text(tickFormat), textEnter = tickEnter.select("text"), textUpdate = tickUpdate.select("text"), sign = orient === "top" || orient === "left" ? -1 : 1, x1, x2, y1, y2;
+        if (orient === "bottom" || orient === "top") {
+          tickTransform = d3_svg_axisX, x1 = "x", y1 = "y", x2 = "x2", y2 = "y2";
+          text.attr("dy", sign < 0 ? "0em" : ".71em").style("text-anchor", "middle");
+          pathUpdate.attr("d", "M" + range[0] + "," + sign * outerTickSize + "V0H" + range[1] + "V" + sign * outerTickSize);
+        } else {
+          tickTransform = d3_svg_axisY, x1 = "y", y1 = "x", x2 = "y2", y2 = "x2";
+          text.attr("dy", ".32em").style("text-anchor", sign < 0 ? "end" : "start");
+          pathUpdate.attr("d", "M" + sign * outerTickSize + "," + range[0] + "H0V" + range[1] + "H" + sign * outerTickSize);
         }
+        lineEnter.attr(y2, sign * innerTickSize);
+        textEnter.attr(y1, sign * tickSpacing);
+        lineUpdate.attr(x2, 0).attr(y2, sign * innerTickSize);
+        textUpdate.attr(x1, 0).attr(y1, sign * tickSpacing);
         if (scale1.rangeBand) {
           var x = scale1, dx = x.rangeBand() / 2;
           scale0 = scale1 = function(d) {
@@ -8700,10 +9010,10 @@
         } else if (scale0.rangeBand) {
           scale0 = scale1;
         } else {
-          tickExit.call(tickTransform, scale1);
+          tickExit.call(tickTransform, scale1, scale0);
         }
-        tickEnter.call(tickTransform, scale0);
-        tickUpdate.call(tickTransform, scale1);
+        tickEnter.call(tickTransform, scale0, scale1);
+        tickUpdate.call(tickTransform, scale1, scale1);
       });
     }
     axis.scale = function(x) {
@@ -8718,7 +9028,7 @@
     };
     axis.ticks = function() {
       if (!arguments.length) return tickArguments_;
-      tickArguments_ = arguments;
+      tickArguments_ = d3_array(arguments);
       return axis;
     };
     axis.tickValues = function(x) {
@@ -8764,14 +9074,16 @@
     bottom: 1,
     left: 1
   };
-  function d3_svg_axisX(selection, x) {
+  function d3_svg_axisX(selection, x0, x1) {
     selection.attr("transform", function(d) {
-      return "translate(" + x(d) + ",0)";
+      var v0 = x0(d);
+      return "translate(" + (isFinite(v0) ? v0 : x1(d)) + ",0)";
     });
   }
-  function d3_svg_axisY(selection, y) {
+  function d3_svg_axisY(selection, y0, y1) {
     selection.attr("transform", function(d) {
-      return "translate(0," + y(d) + ")";
+      var v0 = y0(d);
+      return "translate(0," + (isFinite(v0) ? v0 : y1(d)) + ")";
     });
   }
   d3.svg.brush = function() {
@@ -8876,8 +9188,8 @@
       g.selectAll(".extent,.e>rect,.w>rect").attr("height", yExtent[1] - yExtent[0]);
     }
     function brushstart() {
-      var target = this, eventTarget = d3.select(d3.event.target), event_ = event.of(target, arguments), g = d3.select(target), resizing = eventTarget.datum(), resizingX = !/^(n|s)$/.test(resizing) && x, resizingY = !/^(e|w)$/.test(resizing) && y, dragging = eventTarget.classed("extent"), dragRestore = d3_event_dragSuppress(), center, origin = d3.mouse(target), offset;
-      var w = d3.select(d3_window).on("keydown.brush", keydown).on("keyup.brush", keyup);
+      var target = this, eventTarget = d3.select(d3.event.target), event_ = event.of(target, arguments), g = d3.select(target), resizing = eventTarget.datum(), resizingX = !/^(n|s)$/.test(resizing) && x, resizingY = !/^(e|w)$/.test(resizing) && y, dragging = eventTarget.classed("extent"), dragRestore = d3_event_dragSuppress(target), center, origin = d3.mouse(target), offset;
+      var w = d3.select(d3_window(target)).on("keydown.brush", keydown).on("keyup.brush", keyup);
       if (d3.event.changedTouches) {
         w.on("touchmove.brush", brushmove).on("touchend.brush", brushend);
       } else {
@@ -9238,6 +9550,5 @@
   d3.xml = d3_xhrType(function(request) {
     return request.responseXML;
   });
-  if (typeof define === "function" && define.amd) define(d3); else if (typeof module === "object" && module.exports) module.exports = d3;
-  this.d3 = d3;
+  if (typeof define === "function" && define.amd) this.d3 = d3, define(d3); else if (typeof module === "object" && module.exports) module.exports = d3; else this.d3 = d3;
 }();
\ No newline at end of file