qpid-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From eal...@apache.org
Subject [2/3] qpid-dispatch git commit: DISPATCH-1217 Treat node.fixed as bitmap
Date Mon, 10 Dec 2018 16:59:24 GMT
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/81e58b46/console/stand-alone/plugin/js/topology/qdrTopology.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/topology/qdrTopology.js b/console/stand-alone/plugin/js/topology/qdrTopology.js
index 54168ec..4ada081 100644
--- a/console/stand-alone/plugin/js/topology/qdrTopology.js
+++ b/console/stand-alone/plugin/js/topology/qdrTopology.js
@@ -21,60 +21,95 @@ under the License.
 /**
  * @module QDR
  */
-import { QDRLogger, QDRRedirectWhenConnected, QDRTemplatePath } from '../qdrGlobals.js';
-import { Traffic } from './traffic.js';
-import { separateAddresses } from '../chord/filters.js';
-import { Nodes } from './nodes.js';
-import { Links } from './links.js';
-import { nextHop, connectionPopupHTML, addStyles } from './topoUtils.js';
-import { BackgroundMap } from './map.js';
+import { QDRLogger, QDRRedirectWhenConnected, QDRTemplatePath } from "../qdrGlobals.js";
+import { Traffic } from "./traffic.js";
+import { separateAddresses } from "../chord/filters.js";
+import { Nodes } from "./nodes.js";
+import { Links } from "./links.js";
+import { nextHop, connectionPopupHTML, getSizes } from "./topoUtils.js";
+import { BackgroundMap } from "./map.js";
+import { utils } from "../amqp/utilities.js";
+import { Legend } from "./legend.js";
+import { appendCircle, appendContent, addGradient, addDefs, updateState } from "./svgUtils.js";
 /**
  * @module QDR
  */
 export class TopologyController {
-  constructor(QDRService, $scope, $log, $rootScope, $location, $timeout, $uibModal, $sce) {
-    this.controllerName = 'QDR.TopologyController';
-
-    let QDRLog = new QDRLogger($log, 'TopologyController');
-    const TOPOOPTIONSKEY = 'topoOptions';
+  constructor(
+    QDRService,
+    $scope,
+    $log,
+    $rootScope,
+    $location,
+    $timeout,
+    $uibModal,
+    $sce
+  ) {
+    this.controllerName = "QDR.TopologyController";
+
+    let QDRLog = new QDRLogger($log, "TopologyController");
+    const TOPOOPTIONSKEY = "topoOptions";
 
     //  - nodes is an array of router/client info. these are the circles
     //  - links is an array of connections between the routers. these are the lines with arrows
-    let nodes = new Nodes(QDRLog);
-    let links = new Links(QDRLog);
-    let forceData = {nodes: nodes, links: links};
+    let forceData = {
+      nodes: new Nodes(QDRLog),
+      links: new Links(QDRLog)
+    };
 
-    $scope.legendOptions = angular.fromJson(localStorage[TOPOOPTIONSKEY]) || {showTraffic: false, trafficType: 'dots', mapOpen: false, legendOpen: true};
-    if (typeof $scope.legendOptions.mapOpen == 'undefined')
+    // restore the state of the legend sections
+    $scope.legendOptions = angular.fromJson(localStorage[TOPOOPTIONSKEY]) || {
+      showTraffic: false,
+      trafficType: "dots",
+      mapOpen: false,
+      legendOpen: true
+    };
+    if (typeof $scope.legendOptions.mapOpen == "undefined")
       $scope.legendOptions.mapOpen = false;
-    if (typeof $scope.legendOptions.legendOpen == 'undefined')
+    if (typeof $scope.legendOptions.legendOpen == "undefined")
       $scope.legendOptions.legendOpen = false;
-    let backgroundMap = new BackgroundMap($scope, 
+    let backgroundMap = new BackgroundMap(
+      $scope,
       // notify: called each time a pan/zoom is performed
       function () {
         if ($scope.legend.status.mapOpen) {
           // set all the nodes' x,y position based on their saved lon,lat
-          nodes.setXY(backgroundMap);
-          nodes.savePositions();
+          forceData.nodes.setXY(backgroundMap);
+          forceData.nodes.savePositions();
           // redraw the nodes in their x,y position and let non-fixed nodes bungie
           force.start();
           clearPopups();
         }
-      });
+      }
+    );
     // urlPrefix is used when referring to svg:defs
     let urlPrefix = $location.absUrl();
-    urlPrefix = urlPrefix.split('#')[0];
+    urlPrefix = urlPrefix.split("#")[0];
 
     if (!$scope.legendOptions.trafficType)
-      $scope.legendOptions.trafficType = 'dots';
-    $scope.legend = {status: {legendOpen: true, optionsOpen: true, mapOpen: false}};
+      $scope.legendOptions.trafficType = "dots";
+    $scope.legend = {
+      status: {
+        legendOpen: true,
+        optionsOpen: true,
+        mapOpen: false
+      }
+    };
     $scope.legend.status.optionsOpen = $scope.legendOptions.showTraffic;
     $scope.legend.status.mapOpen = $scope.legendOptions.mapOpen;
-    let traffic = new Traffic($scope, $timeout, QDRService, separateAddresses, 
-      Nodes.radius('inter-router'), forceData, $scope.legendOptions.trafficType, urlPrefix);
+    let traffic = new Traffic(
+      $scope,
+      $timeout,
+      QDRService,
+      separateAddresses,
+      Nodes.radius("inter-router"),
+      forceData,
+      $scope.legendOptions.trafficType,
+      urlPrefix
+    );
 
     // the showTraaffic checkbox was just toggled (or initialized)
-    $scope.$watch('legend.status.optionsOpen', function () {
+    $scope.$watch("legend.status.optionsOpen", function () {
       $scope.legendOptions.showTraffic = $scope.legend.status.optionsOpen;
       localStorage[TOPOOPTIONSKEY] = JSON.stringify($scope.legendOptions);
       if ($scope.legend.status.optionsOpen) {
@@ -85,15 +120,21 @@ export class TopologyController {
         restart();
       }
     });
-    $scope.$watch('legendOptions.trafficType', function () {
+    // the traffic type was just changed or initialized
+    $scope.$watch("legendOptions.trafficType", function () {
       localStorage[TOPOOPTIONSKEY] = JSON.stringify($scope.legendOptions);
       if ($scope.legendOptions.showTraffic) {
         restart();
-        traffic.setAnimationType($scope.legendOptions.trafficType, separateAddresses, Nodes.radius('inter-router'));
+        traffic.setAnimationType(
+          $scope.legendOptions.trafficType,
+          separateAddresses,
+          Nodes.radius("inter-router")
+        );
         traffic.start();
       }
     });
-    $scope.$watch('legend.status.mapOpen', function (newvalue, oldvalue) {
+    // the background map was shown or hidden
+    $scope.$watch("legend.status.mapOpen", function (newvalue, oldvalue) {
       $scope.legendOptions.mapOpen = $scope.legend.status.mapOpen;
       localStorage[TOPOOPTIONSKEY] = JSON.stringify($scope.legendOptions);
       // map was shown
@@ -102,16 +143,12 @@ export class TopologyController {
         backgroundMap.restartZoom();
         // set the main_container div's background color to the ocean color
         backgroundMap.updateOceanColor();
-        d3.select('g.geo')
-          .style('opacity', 1);
+        d3.select("g.geo").style("opacity", 1);
       } else {
-        if (newvalue !== oldvalue)
-          backgroundMap.cancelZoom();
+        if (newvalue !== oldvalue) backgroundMap.cancelZoom();
         // hide the map and reset the background color
-        d3.select('g.geo')
-          .style('opacity', 0);
-        d3.select('#main_container')
-          .style('background-color', '#FFF');
+        d3.select("g.geo").style("opacity", 0);
+        d3.select("#main_container").style("background-color", "#FFF");
       }
     });
 
@@ -121,37 +158,38 @@ export class TopologyController {
       mouseup_node = null,
       initial_mouse_down_position = null;
 
-    $scope.schema = 'Not connected';
-    $scope.current_node = null,
-    $scope.mousedown_node = null,
-
+    $scope.schema = "Not connected";
+    $scope.current_node = null;
+    $scope.mousedown_node = null;
     $scope.contextNode = null; // node that is associated with the current context menu
-    $scope.isRight = function(mode) {
+    $scope.isRight = function (mode) {
       return mode.right;
     };
 
+    // show the details dialog for a client or group of clients
     function doDialog(d) {
-      $uibModal.open({
-        backdrop: true,
-        keyboard: true,
-        backdropClick: true,
-        templateUrl: QDRTemplatePath + 'tmplClientDetail.html',
-        controller: 'QDR.DetailDialogController',
-        resolve: {
-          d: function() {
-            return d;
+      $uibModal
+        .open({
+          backdrop: true,
+          keyboard: true,
+          backdropClick: true,
+          templateUrl: QDRTemplatePath + "tmplClientDetail.html",
+          controller: "QDR.DetailDialogController",
+          resolve: {
+            d: function () {
+              return d;
+            }
           }
-        }
-      }).result.then(function() {
-      });
+        })
+        .result.then(function () { });
     }
 
     // called from the html page's popup menu
-    $scope.setFixed = function(b) {
+    $scope.setFixed = function (b) {
       if ($scope.contextNode) {
-        $scope.contextNode.setFixed(b);
-        nodes.savePositions();
-        nodes.saveLonLat(backgroundMap, $scope.contextNode);
+        forceData.nodes.setFixed($scope.contextNode, b);
+        forceData.nodes.savePositions();
+        forceData.nodes.saveLonLat(backgroundMap, $scope.contextNode);
       }
       // redraw the circles/links
       restart();
@@ -159,20 +197,25 @@ export class TopologyController {
       if (!b) {
         // let the nodes move to a new position
         animate = true;
-        force.start(); 
+        force.start();
       }
     };
-    $scope.isFixed = function() {
-      if (!$scope.contextNode)
-        return false;
-      return ($scope.contextNode.fixed);
+    $scope.isFixed = function () {
+      if (!$scope.contextNode) return false;
+      return $scope.contextNode.fixed;
+    };
+    $scope.addressStyle = function (address) {
+      return {
+        "background-color": $scope.addressColors[address]
+      };
     };
 
     let mouseX, mouseY;
     var relativeMouse = function () {
-      let offset = $('#main_container').offset();
-      return {left: (mouseX + $(document).scrollLeft()) - 1,
-        top: (mouseY  + $(document).scrollTop()) - 1,
+      let offset = $("#main_container").offset();
+      return {
+        left: mouseX + $(document).scrollLeft() - 1,
+        top: mouseY + $(document).scrollTop() - 1,
         offset: offset
       };
     };
@@ -182,66 +225,50 @@ export class TopologyController {
       mouseY = e.clientY;
     });
     $(document).mousemove();
-    $(document).click(function() {
+    $(document).click(function () {
       $scope.contextNode = null;
-      $('.contextMenu').fadeOut(200);
+      $(".contextMenu").fadeOut(200);
     });
 
-    let svg, lsvg;  // main svg and legend svg
+    let svg; // main svg
     let force;
     let animate = false; // should the force graph organize itself when it is displayed
-    let path, circle;
-    let savedKeys = {};
+    let path, circle;   // the d3 selections for links and nodes respectively
+    let savedKeys = {}; // so we can redraw the svg if the topology changes
     let width = 0;
     let height = 0;
 
-    var getSizes = function() {
-      const gap = 5;
-      let legendWidth = 194;
-      let topoWidth = $('#topology').width();
-      if (topoWidth < 768)
-        legendWidth = 0;
-      let width = $('#topology').width() - gap - legendWidth;
-      let top = $('#topology').offset().top;
-      let height = window.innerHeight - top - gap;
-      if (width < 10) {
-        QDRLog.info(`page width and height are abnormal w: ${width} h: ${height}`);
-        return [0, 0];
-      }
-      return [width, height];
-    };
-    var resize = function() {
-      if (!svg)
-        return;
-      let sizes = getSizes();
+    var resize = function () {
+      if (!svg) return;
+      let sizes = getSizes(QDRLog);
       width = sizes[0];
       height = sizes[1];
       if (width > 0) {
         // set attrs and 'resume' force
-        svg.attr('width', width);
-        svg.attr('height', height);
+        svg.attr("width", width);
+        svg.attr("height", height);
         force.size(sizes).resume();
       }
-      $timeout(createLegend);
+      $timeout(updateLegend);
     };
 
     // the window is narrow and the page menu icon was clicked.
     // Re-create the legend
-    $scope.$on('pageMenuClicked', function () {
-      $timeout(createLegend);
+    $scope.$on("pageMenuClicked", function () {
+      $timeout(updateLegend);
     });
 
-    window.addEventListener('resize', resize);
-    let sizes = getSizes();
+    window.addEventListener("resize", resize);
+    let sizes = getSizes(QDRLog);
     width = sizes[0];
     height = sizes[1];
-    if (width <= 0 || height <= 0)
-      return;
+    if (width <= 0 || height <= 0) return;
 
     // initialize the nodes and links array from the QDRService.topology._nodeInfo object
-    var initForceGraph = function() {
-      forceData.nodes = nodes = new Nodes(QDRLog);
-      forceData.links = links = new Links(QDRLog);
+    var initForceGraph = function () {
+      if (width < 768) {
+        $scope.legend.status.mapOpen = false;
+      }
       let nodeInfo = QDRService.management.topology.nodeInfo();
       let nodeCount = Object.keys(nodeInfo).length;
 
@@ -250,110 +277,78 @@ export class TopologyController {
       mouseover_node = null;
       selected_node = null;
 
-      d3.select('#SVG_ID').remove();
-      svg = d3.select('#topology')
-        .append('svg')
-        .attr('id', 'SVG_ID')
-        .attr('width', width)
-        .attr('height', height)
-        .on('click', function () {
-          clearPopups();
+      if (d3.select("#SVG_ID").empty()) {
+        svg = d3
+          .select("#topology")
+          .append("svg")
+          .attr("id", "SVG_ID")
+          .attr("width", width)
+          .attr("height", height)
+          .on("click", function () {
+            clearPopups();
+          });
+        // read the map data from the data file and build the map layer
+        backgroundMap.init($scope, svg, width, height).then(function () {
+          forceData.nodes.saveLonLat(backgroundMap);
+          backgroundMap.setMapOpacity($scope.legend.status.mapOpen);
         });
-
-      // the legend
-      //d3.select('#topo_svg_legend svg').remove();
-      if (d3.select('#svglegend').empty()) {
-        lsvg = d3.select('#topo_svg_legend')
-          .append('svg')
-          .attr('id', 'svglegend');
-        lsvg = lsvg.append('svg:g')
-          .attr('transform', `translate(${Nodes.maxRadius()}, ${Nodes.maxRadius()})`)
-          .selectAll('g');
+        addDefs(svg);
+        addGradient(svg);
+        // handles to link and node element groups
+        path = svg.append("svg:g").attr("class", "links").selectAll("g");
+        circle = svg.append("svg:g").attr("class", "nodes").selectAll("g");
       }
-
       // mouse event vars
       $scope.mousedown_node = null;
       mouseup_node = null;
 
       // initialize the list of nodes
-      animate = nodes.initialize(nodeInfo, localStorage, width, height);
-      nodes.savePositions();
-      // read the map data from the data file and build the map layer
-      backgroundMap.init($scope, svg, width, height)
-        .then( function () {
-          nodes.saveLonLat(backgroundMap);
-          backgroundMap.setMapOpacity($scope.legend.status.mapOpen);
-        });
+      animate = forceData.nodes.initialize(nodeInfo, width, height, localStorage);
+      forceData.nodes.savePositions();
 
       // initialize the list of links
       let unknowns = [];
-      if (links.initialize(nodeInfo, nodes, unknowns, localStorage, height)) {
+      if (forceData.links.initialize(nodeInfo,
+        forceData.nodes,
+        unknowns,
+        height,
+        localStorage))
         animate = true;
-      }
       $scope.schema = QDRService.management.schema();
       // init D3 force layout
-      force = d3.layout.force()
-        .nodes(nodes.nodes)
-        .links(links.links)
+      force = d3.layout
+        .force()
+        .nodes(forceData.nodes.nodes)
+        .links(forceData.links.links)
         .size([width, height])
-        .linkDistance(function(d) { return nodes.linkDistance(d, nodeCount); })
-        .charge(function(d) { return nodes.charge(d, nodeCount); })
-        .friction(.10)
-        .gravity(function(d) { return nodes.gravity(d, nodeCount); })
-        .on('tick', tick)
-        .on('end', function () {nodes.savePositions(); nodes.saveLonLat(backgroundMap);})
+        .linkDistance(function (d) {
+          return forceData.nodes.linkDistance(d, nodeCount);
+        })
+        .charge(function (d) {
+          return forceData.nodes.charge(d, nodeCount);
+        })
+        .friction(0.1)
+        .gravity(function (d) {
+          return forceData.nodes.gravity(d, nodeCount);
+        })
+        .on("tick", tick)
+        .on("end", function () {
+          forceData.nodes.savePositions();
+          forceData.nodes.saveLonLat(backgroundMap);
+        })
         .start();
 
-      // This section adds in the arrows
-      // Generate a marker for each combination of:
-      //  start|end, ''|selected highlighted, and each possible node radius
-      {
-        let sten = ['start', 'end'];
-        let states = ['', 'selected', 'highlighted', 'unknown'];
-        let radii = Nodes.discrete();
-        let defs = [];
-        for (let isten=0; isten<sten.length; isten++) {
-          for (let istate=0; istate<states.length; istate++) {
-            for (let iradii=0; iradii<radii.length; iradii++) {
-              defs.push({sten: sten[isten], state: states[istate], r: radii[iradii]});
-            }
-          }
-        }
-        svg.append('svg:defs').attr('class', 'marker-defs').selectAll('marker')
-          .data(defs)
-          .enter().append('svg:marker')
-          .attr('id', function (d) { return [d.sten, d.state, d.r].join('-'); })
-          .attr('viewBox', '0 -5 10 10')
-          .attr('refX', function (d) { return Nodes.refX(d.sten, d.r); })
-          .attr('markerWidth', 14)
-          .attr('markerHeight', 14)
-          .attr('markerUnits', 'userSpaceOnUse')
-          .attr('orient', 'auto')
-          .append('svg:path')
-          .attr('d', function (d) {
-            return d.sten === 'end' ? 'M 0 -5 L 10 0 L 0 5 z' : 'M 10 -5 L 0 0 L 10 5 z';
-          });
-        addStyles (sten, {selected: '#33F', highlighted: '#6F6', unknown: '#888'}, radii);
-      }
-      // gradient for sender/receiver client
-      let grad = svg.append('svg:defs').append('linearGradient')
-        .attr('id', 'half-circle')
-        .attr('x1', '0%')
-        .attr('x2', '0%')
-        .attr('y1', '100%')
-        .attr('y2', '0%');
-      grad.append('stop').attr('offset', '50%').style('stop-color', '#C0F0C0');
-      grad.append('stop').attr('offset', '50%').style('stop-color', '#F0F000');
-
-      // handles to link and node element groups
-      path = svg.append('svg:g').attr('class', 'links').selectAll('g'),
-      circle = svg.append('svg:g').attr('class', 'nodes').selectAll('g');
-
       // app starts here
-      //if (unknowns.length === 0)
-      restart();
+      if (unknowns.length === 0)
+        restart();
+      // the legend
+      // call updateLegend in timeout because:
+      // If we create the legend right away, then it will be destroyed when the accordian
+      // gets initialized as the page loads.
+      $timeout(updateLegend);
+
       if (oldSelectedNode) {
-        d3.selectAll('circle.inter-router').classed('selected', function (d) {
+        d3.selectAll("circle.inter-router").classed("selected", function (d) {
           if (d.key === oldSelectedNode.key) {
             selected_node = d;
             return true;
@@ -362,13 +357,19 @@ export class TopologyController {
         });
       }
       if (oldMouseoverNode && selected_node) {
-        d3.selectAll('circle.inter-router').each(function (d) {
+        d3.selectAll("circle.inter-router").each(function (d) {
           if (d.key === oldMouseoverNode.key) {
             mouseover_node = d;
-            QDRService.management.topology.ensureAllEntities([{entity: 'router.node', attrs: ['id','nextHop']}], function () {
-              nextHopHighlight(selected_node, d);
-              restart();
-            });
+            QDRService.management.topology.ensureAllEntities(
+              [{
+                entity: "router.node",
+                attrs: ["id", "nextHop"]
+              }],
+              function () {
+                nextHopHighlight(selected_node, d);
+                restart();
+              }
+            );
           }
         });
       }
@@ -384,38 +385,45 @@ export class TopologyController {
           setTimeout(continueForce, 100, extra);
         }
       };
-      continueForce(Nodes.forceScale(nodeCount, [0, 200]));  // give large graphs time to settle down
+      continueForce(Nodes.forceScale(nodeCount, [0, 200])); // give large graphs time to settle down
     };
 
     // To start up quickly, we only get the connection info for each router.
-    // That means we don't have the router.link info when links.initialize() is first called.
+    // That means we don't have the router.link info when links.initialize() is first called
+    // and the initial graph is drawn.
     // The router.link info is needed to determine which direction the arrows between routers
     // and client should point. (Direction between interior routers is determined by connection.dir)
-    // So, the first time through links.initialize() we keep track of the nodes for which we 
+    // So, the first time through links.initialize() we keep track of the nodes for which we
     // need router.link info and fill in that info here.
     var resolveUnknowns = function (nodeInfo, unknowns) {
       let unknownNodes = {};
       // collapse the unknown nodes using an object
-      for (let i=0; i<unknowns.length; ++i) {
+      for (let i = 0; i < unknowns.length; ++i) {
         unknownNodes[unknowns[i]] = 1;
       }
       unknownNodes = Object.keys(unknownNodes);
-      QDRService.management.topology.ensureEntities(unknownNodes, 
-        [{entity: 'router.link', 
-          attrs: ['linkType','connectionId','linkDir','owningAddr'], 
-          force: true}], 
+      QDRService.management.topology.ensureEntities(
+        unknownNodes,
+        [{
+          entity: "router.link",
+          attrs: ["linkType", "connectionId", "linkDir", "owningAddr"],
+          force: true
+        }],
         function () {
           let nodeInfo = QDRService.management.topology.nodeInfo();
-          forceData.nodes = nodes = new Nodes(QDRLog);
-          nodes.initialize(nodeInfo, localStorage, width, height);
-          forceData.links = links = new Links(QDRLog);
+          forceData.nodes.initialize(nodeInfo, width, height, localStorage);
           let edgeUnknowns = [];
-          links.initialize(nodeInfo, nodes, edgeUnknowns, localStorage, height);
+          forceData.links.initialize(nodeInfo, forceData.nodes, edgeUnknowns, height, localStorage);
           animate = true;
-          force.nodes(nodes.nodes).links(links.links).start();
-          nodes.saveLonLat(backgroundMap);
+          force
+            .nodes(forceData.nodes.nodes)
+            .links(forceData.links.links)
+            .start();
+          forceData.nodes.saveLonLat(backgroundMap);
           restart();
-        });
+          updateLegend();
+        }
+      );
     };
 
     function resetMouseVars() {
@@ -427,7 +435,7 @@ export class TopologyController {
     // update force layout (called automatically each iteration)
     function tick() {
       // move the circles
-      circle.attr('transform', function(d) {
+      circle.attr("transform", function (d) {
         // don't let the edges of the circle go beyond the edges of the svg
         let r = Nodes.radius(d.nodeType);
         d.x = Math.max(Math.min(d.x, width - r), r);
@@ -436,10 +444,9 @@ export class TopologyController {
       });
 
       // draw lines from node centers
-      path.selectAll('path')
-        .attr('d', function(d) {
-          return `M${d.source.x},${d.source.y}L${d.target.x},${d.target.y}`;
-        });
+      path.selectAll("path").attr("d", function (d) {
+        return `M${d.source.x},${d.source.y}L${d.target.x},${d.target.y}`;
+      });
 
       if (!animate) {
         animate = true;
@@ -448,218 +455,259 @@ export class TopologyController {
     }
 
     function nextHopHighlight(selected_node, d) {
-      nextHop(selected_node, d, nodes, links, QDRService, selected_node, function (hlLink, hnode) {
-        hlLink.highlighted = true;
-        hnode.highlighted = true;
-      });
-      let hnode = nodes.nodeFor(d.name);
+      nextHop(
+        selected_node,
+        d,
+        forceData.nodes,
+        forceData.links,
+        QDRService.management.topology.nodeInfo(),
+        selected_node,
+        function (hlLink, hnode) {
+          hlLink.highlighted = true;
+          hnode.highlighted = true;
+        }
+      );
+      let hnode = forceData.nodes.nodeFor(d.name);
       hnode.highlighted = true;
     }
 
     function clearPopups() {
-      d3.select('#crosssection').style('display', 'none');
-      $('.hastip').empty();
-      d3.select('#multiple_details').style('display', 'none');
-      d3.select('#link_details').style('display', 'none');
-      d3.select('#node_context_menu').style('display', 'none');
-      d3.select('#popover-div').style('display', 'none');
+      d3.select("#crosssection").style("display", "none");
+      $(".hastip").empty();
+      d3.select("#multiple_details").style("display", "none");
+      d3.select("#link_details").style("display", "none");
+      d3.select("#node_context_menu").style("display", "none");
+      d3.select("#popover-div").style("display", "none");
     }
 
     function clearAllHighlights() {
-      links.clearHighlighted();
-      nodes.clearHighlighted();
+      forceData.links.clearHighlighted();
+      forceData.nodes.clearHighlighted();
     }
     // Takes the forceData.nodes and forceData.links array and creates svg elements
     // Also updates any existing svg elements based on the updated values in forceData.nodes
     // and forceData.links
     function restart() {
-      if (!circle)
-        return;
+      if (!circle) return;
       circle.call(force.drag);
 
       // path is a selection of all g elements under the g.links svg:group
       // here we associate the links.links array with the {g.links g} selection
       // based on the link.uid
-      path = path.data(links.links, function(d) {return d.uid;});
+      path = path.data(forceData.links.links, function (d) {
+        return d.uid;
+      });
 
       // update each existing {g.links g.link} element
-      path.select('.link')
-        .classed('selected', function(d) {
+      path
+        .select(".link")
+        .classed("selected", function (d) {
           return d.selected;
         })
-        .classed('highlighted', function(d) {
+        .classed("highlighted", function (d) {
           return d.highlighted;
         })
-        .classed('unknown', function (d) {
+        .classed("unknown", function (d) {
           return !d.right && !d.left;
         });
 
       // reset the markers based on current highlighted/selected
-      if (!$scope.legend.status.optionsOpen || $scope.legendOptions.trafficType === 'dots') {
-        path.select('.link')
-          .attr('marker-end', function(d) {
-            return d.right ? `url(${urlPrefix}#end${d.markerId('end')})` : null;
+      if (
+        !$scope.legend.status.optionsOpen ||
+        $scope.legendOptions.trafficType === "dots"
+      ) {
+        path
+          .select(".link")
+          .attr("marker-end", function (d) {
+            return d.right ? `url(${urlPrefix}#end${d.markerId("end")})` : null;
           })
-          .attr('marker-start', function(d) {
-            return (d.left || (!d.left && !d.right)) ? `url(${urlPrefix}#start${d.markerId('start')})` : null;
+          .attr("marker-start", function (d) {
+            return d.left || (!d.left && !d.right) ?
+              `url(${urlPrefix}#start${d.markerId("start")})` :
+              null;
           });
       }
       // add new links. if a link with a new uid is found in the data, add a new path
-      let enterpath = path.enter().append('g')
-        .on('mouseover', function(d) { // mouse over a path
+      let enterpath = path
+        .enter()
+        .append("g")
+        .on("mouseover", function (d) {
+          // mouse over a path
           let event = d3.event;
           d.selected = true;
           let updateTooltip = function () {
             $timeout(function () {
               if (d.selected) {
-                $scope.trustedpopoverContent = $sce.trustAsHtml(connectionPopupHTML(d, QDRService));
+                $scope.trustedpopoverContent = $sce.trustAsHtml(
+                  connectionPopupHTML(
+                    d,
+                    QDRService.management.topology.nodeInfo()
+                  )
+                );
                 displayTooltip(event);
               }
             });
           };
           // update the contents of the popup tooltip each time the data is polled
-          QDRService.management.topology.addUpdatedAction('connectionPopupHTML', updateTooltip);
+          QDRService.management.topology.addUpdatedAction(
+            "connectionPopupHTML",
+            updateTooltip
+          );
           // request the data and update the tooltip as soon as it arrives
           QDRService.management.topology.ensureAllEntities(
-            [{ entity: 'router.link', force: true},{entity: 'connection'}], function () {
+            [{
+              entity: "router.link",
+              force: true
+            }, {
+              entity: "connection"
+            }],
+            function () {
               updateTooltip();
-            });
+            }
+          );
           // just show the tooltip with whatever data we have
           updateTooltip();
           restart();
-
         })
-        .on('mouseout', function(d) { // mouse out of a path
-          QDRService.management.topology.delUpdatedAction('connectionPopupHTML');
-          d3.select('#popover-div')
-            .style('display', 'none');
+        .on("mouseout", function (d) {
+          // mouse out of a path
+          QDRService.management.topology.delUpdatedAction(
+            "connectionPopupHTML"
+          );
+          d3.select("#popover-div").style("display", "none");
           d.selected = false;
           connectionPopupHTML();
           restart();
         })
         // left click a path
-        .on('click', function () {
+        .on("click", function () {
           d3.event.stopPropagation();
           clearPopups();
         });
 
-      enterpath.append('path')
-        .attr('class', 'link')
-        .attr('marker-end', function(d) {
-          return d.right ? `url(${urlPrefix}#end${d.markerId('end')})` : null;
+      enterpath
+        .append("path")
+        .attr("class", "link")
+        .attr("marker-end", function (d) {
+          return d.right ? `url(${urlPrefix}#end${d.markerId("end")})` : null;
         })
-        .attr('marker-start', function(d) {
-          return (d.left || (!d.left && !d.right)) ? `url(${urlPrefix}#start${d.markerId('start')})` : null;
+        .attr("marker-start", function (d) {
+          return d.left || (!d.left && !d.right) ?
+            `url(${urlPrefix}#start${d.markerId("start")})` :
+            null;
         })
-        .attr('id', function (d) {
-          const si = d.source.uid(QDRService);
-          const ti = d.target.uid(QDRService);
-          return ['path', si, ti].join('-');
+        .attr("id", function (d) {
+          const si = d.source.uid();
+          const ti = d.target.uid();
+          return ["path", si, ti].join("-");
         })
-        .classed('unknown', function (d) {
+        .classed("unknown", function (d) {
           return !d.right && !d.left;
         });
 
-      enterpath.append('path')
-        .attr('class', 'hittarget');
+      enterpath.append("path").attr("class", "hittarget");
 
       // remove old links
       path.exit().remove();
 
-
       // circle (node) group
-      // nodes are known by router id, or for groups, by the router id + 1st connectionId
-      circle = circle.data(nodes.nodes, function(d) {
-        return d.uid();
-      });
+      circle = d3.select("g.nodes").selectAll("g")
+        .data(forceData.nodes.nodes, function (d) {
+          return d.uid();
+        });
 
       // update existing nodes visual states
-      circle.selectAll('circle')
-        .classed('highlighted', function(d) {
-          return d.highlighted;
-        })
-        .classed('selected', function(d) {
-          return (d === selected_node);
-        })
-        .classed('fixed', function(d) {
-          return d.fixed;
-        });
-      circle
-        .classed('multiple', function (d) {
-          return (d.normals && d.normals.length > 1);
-        });
+      updateState(circle, selected_node);
 
       // add new circle nodes
-      let g = circle.enter().append('svg:g')
-        .classed('multiple', function(d) {
-          return (d.normals && d.normals.length > 1);
-        })
-        .attr('id', function (d) { return (d.nodeType !== 'normal' ? 'router' : 'client') + '-' + d.index; });
+      let enterCircle = circle
+        .enter()
+        .append("g")
+        .classed("multiple", function (d) {
+          return d.normals && d.normals.length > 1;
+        })
+        .attr("id", function (d) {
+          return (
+            (d.nodeType !== "normal" ? "router" : "client") + "-" + d.index
+          );
+        });
 
-      appendCircle(g)
-        .on('mouseover', function(d) {  // mouseover a circle
+      appendCircle(enterCircle)
+        .on("mouseover", function (d) {
+          // mouseover a circle
           $scope.current_node = d;
-          QDRService.management.topology.delUpdatedAction('connectionPopupHTML');
+          QDRService.management.topology.delUpdatedAction(
+            "connectionPopupHTML"
+          );
           let e = d3.event;
-          d.toolTip(QDRService.management.topology)
-            .then( function (toolTip) {
-              showToolTip(toolTip, e);
-            });
-          if (d === $scope.mousedown_node)
-            return;
+          d.toolTip(QDRService.management.topology).then(function (toolTip) {
+            showToolTip(toolTip, e);
+          });
+          if (d === $scope.mousedown_node) return;
           // enlarge target node
-          d3.select(this).attr('transform', 'scale(1.1)');
+          d3.select(this).attr("transform", "scale(1.1)");
           if (!selected_node) {
             return;
           }
           // highlight the next-hop route from the selected node to this node
           clearAllHighlights();
           // we need .router.node info to highlight hops
-          QDRService.management.topology.ensureAllEntities([{entity: 'router.node', attrs: ['id','nextHop']}], function () {
-            mouseover_node = d;  // save this node in case the topology changes so we can restore the highlights
-            nextHopHighlight(selected_node, d);
-            restart();
-          });
+          QDRService.management.topology.ensureAllEntities(
+            [{
+              entity: "router.node",
+              attrs: ["id", "nextHop"]
+            }],
+            function () {
+              mouseover_node = d; // save this node in case the topology changes so we can restore the highlights
+              nextHopHighlight(selected_node, d);
+              restart();
+            }
+          );
         })
-        .on('mouseout', function() { // mouse out for a circle
+        .on("mouseout", function () {
+          // mouse out for a circle
           $scope.current_node = null;
           // unenlarge target node
-          d3.select('#popover-div')
-            .style('display', 'none');
-          d3.select(this).attr('transform', '');
+          d3.select("#popover-div").style("display", "none");
+          d3.select(this).attr("transform", "");
           clearAllHighlights();
           mouseover_node = null;
           restart();
         })
-        .on('mousedown', function(d) { // mouse down for circle
+        .on("mousedown", function (d) {
+          // mouse down for circle
           backgroundMap.cancelZoom();
           $scope.current_node = d;
-          if (d3.event.button !== 0) { // ignore all but left button
+          if (d3.event.button !== 0) {
+            // ignore all but left button
             return;
           }
           $scope.mousedown_node = d;
           // mouse position relative to svg
-          initial_mouse_down_position = d3.mouse(this.parentNode.parentNode.parentNode).slice();
+          initial_mouse_down_position = d3
+            .mouse(this.parentNode.parentNode.parentNode)
+            .slice();
         })
-        .on('mouseup', function(d) {  // mouse up for circle
+        .on("mouseup", function (d) {
+          // mouse up for circle
           backgroundMap.restartZoom();
-          if (!$scope.mousedown_node)
-            return;
+          if (!$scope.mousedown_node) return;
 
           // unenlarge target node
-          d3.select(this).attr('transform', '');
+          d3.select(this).attr("transform", "");
 
           // check for drag
           mouseup_node = d;
 
-          let mySvg = d3.select('#SVG_ID').node();
           // if we dragged the node, make it fixed
-          let cur_mouse = d3.mouse(mySvg);
-          if (cur_mouse[0] != initial_mouse_down_position[0] ||
-            cur_mouse[1] != initial_mouse_down_position[1]) {
-            d.setFixed(true);
-            nodes.savePositions();
-            nodes.saveLonLat(backgroundMap);
+          let cur_mouse = d3.mouse(svg.node());
+          if (
+            cur_mouse[0] != initial_mouse_down_position[0] ||
+            cur_mouse[1] != initial_mouse_down_position[1]
+          ) {
+            forceData.nodes.setFixed(d, true);
+            forceData.nodes.savePositions();
+            forceData.nodes.saveLonLat(backgroundMap);
             resetMouseVars();
             restart();
             return;
@@ -669,10 +717,12 @@ export class TopologyController {
           if ($scope.mousedown_node === selected_node) {
             selected_node = null;
           } else {
-            if (d.nodeType !== 'normal' && 
-                d.nodeType !== 'on-demand' && 
-                d.nodeType !== 'edge' &&
-                d.nodeTYpe !== '_edge')
+            if (
+              d.nodeType !== "normal" &&
+              d.nodeType !== "on-demand" &&
+              d.nodeType !== "edge" &&
+              d.nodeTYpe !== "_edge"
+            )
               selected_node = $scope.mousedown_node;
           }
           clearAllHighlights();
@@ -684,314 +734,115 @@ export class TopologyController {
           }
           // apply any data changes to the interface
           restart();
-
         })
-        .on('dblclick', function(d) { // circle
+        .on("dblclick", function (d) {
+          // circle
           d3.event.preventDefault();
           if (d.fixed) {
-            d.setFixed(false);
+            forceData.nodes.setFixed(d, false);
             restart(); // redraw the node without a dashed line
             force.start(); // let the nodes move to a new position
           }
         })
-        .on('contextmenu', function(d) {  // circle
+        .on("contextmenu", function (d) {
+          // circle
           $(document).click();
           d3.event.preventDefault();
           let rm = relativeMouse();
-          d3.select('#node_context_menu')
-            .style({
-              display: 'block',
-              left: rm.left + 'px',
-              top: (rm.top - rm.offset.top) + 'px'
-            });
-          $timeout( function () {
+          d3.select("#node_context_menu").style({
+            display: "block",
+            left: rm.left + "px",
+            top: rm.top - rm.offset.top + "px"
+          });
+          $timeout(function () {
             $scope.contextNode = d;
           });
         })
-        .on('click', function(d) {  // circle
-          if (!mouseup_node)
-            return;
+        .on("click", function (d) {
+          // circle
+          if (!mouseup_node) return;
           // clicked on a circle
           clearPopups();
           if (!d.normals) {
             // circle was a router or a broker
-            if (QDRService.utilities.isArtemis(d)) {
-              const artemisPath = '/jmx/attributes?tab=artemis&con=Artemis';
-              window.location = $location.protocol() + '://localhost:8161/hawtio' + artemisPath;
+            if (utils.isArtemis(d)) {
+              const artemisPath = "/jmx/attributes?tab=artemis&con=Artemis";
+              window.location =
+                $location.protocol() + "://localhost:8161/hawtio" + artemisPath;
             }
             return;
           }
           d3.event.stopPropagation();
         });
 
-      appendContent(g);
-      //appendTitle(g);
+      appendContent(enterCircle);
 
       // remove old nodes
       circle.exit().remove();
 
       // add text to client circles if there are any that represent multiple clients
-      svg.selectAll('.subtext').remove();
-      let multiples = svg.selectAll('.multiple');
-      multiples.each(function(d) {
+      svg.selectAll(".subtext").remove();
+      let multiples = svg.selectAll(".multiple");
+      multiples.each(function (d) {
         let g = d3.select(this);
         let r = Nodes.radius(d.nodeType);
-        g.append('svg:text')
-          .attr('x', r + 4)
-          .attr('y', Math.floor((r / 2) - 4))
-          .attr('class', 'subtext')
-          .text('* ' + d.normals.length);
+        g.append("svg:text")
+          .attr("x", r + 4)
+          .attr("y", Math.floor(r / 2 - 4))
+          .attr("class", "subtext")
+          .text("* " + d.normals.length);
       });
-      // call createLegend in timeout because:
-      // If we create the legend right away, then it will be destroyed when the accordian
-      // gets initialized as the page loads.
-      $timeout(createLegend);
 
       if (!$scope.mousedown_node || !selected_node)
         return;
 
       // set the graph in motion
-      //QDRLog.debug("mousedown_node is " + mousedown_node);
       force.start();
-
     }
-    let createLegend = function () {
-      // dynamically create the legend based on which node types are present
-      d3.select('#topo_svg_legend svg').remove();
-      lsvg = d3.select('#topo_svg_legend')
-        .append('svg')
-        .attr('id', 'svglegend');
-      lsvg = lsvg.append('svg:g')
-        .attr('transform', `translate(${Nodes.maxRadius()}, ${Nodes.maxRadius()})`)
-        .selectAll('g');
-      let legendNodes = new Nodes(QDRLog);
-      legendNodes.addUsing('Router', '', 'inter-router', undefined, 0, 0, 0, 0, false, {});
-      if (!svg.selectAll('circle.edge').empty() || !svg.selectAll('circle._edge').empty()) {
-        legendNodes.addUsing('Router', 'Edge', 'edge', undefined, 0, 0, 1, 0, false, {});
-      }
-      if (!svg.selectAll('circle.console').empty()) {
-        legendNodes.addUsing('Console', 'Console', 'normal', undefined, 0, 0, 2, 0, false, {
-          console_identifier: 'Dispatch console'
-        });
-      }
-      if (!svg.selectAll('circle.client.in').empty()) {
-        legendNodes.addUsing('Sender', 'Sender', 'normal', undefined, 0, 0, 3, 0, false, {}).cdir = 'in';
-      }
-      if (!svg.selectAll('circle.client.out').empty()) {
-        legendNodes.addUsing('Receiver', 'Receiver', 'normal', undefined, 0, 0, 4, 0, false, {}).cdir = 'out';
-      }
-      if (!svg.selectAll('circle.client.inout').empty()) {
-        legendNodes.addUsing('Sender/Receiver', 'Sender/Receiver', 'normal', undefined, 0, 0, 5, 0, false, {}).cdir = 'both';
-      }
-      if (!svg.selectAll('circle.qpid-cpp').empty()) {
-        legendNodes.addUsing('Qpid broker', 'Qpid broker', 'route-container', undefined, 0, 0, 6, 0, false, {
-          product: 'qpid-cpp'
-        });
-      }
-      if (!svg.selectAll('circle.artemis').empty()) {
-        legendNodes.addUsing('Artemis broker', 'Artemis broker', 'route-container', undefined, 0, 0, 7, 0, false,
-          {product: 'apache-activemq-artemis'});
-      }
-      if (!svg.selectAll('circle.route-container').empty()) {
-        legendNodes.addUsing('Service', 'Service', 'route-container', undefined, 0, 0, 8, 0, false,
-          {product: ' External Service'});
-      }
-      lsvg = lsvg.data(legendNodes.nodes, function(d) {
-        return d.key + d.name;
-      });
-      let cury = 0;
-      let lg = lsvg.enter().append('svg:g')
-        .attr('transform', function(d) {
-          let t = `translate(0, ${cury})`;
-          cury += (Nodes.radius(d.nodeType) * 2 + 10);
-          return t;
-        });
 
-      appendCircle(lg);
-      appendContent(lg);
-      appendTitle(lg);
-      lg.append('svg:text')
-        .attr('x', 35)
-        .attr('y', 6)
-        .attr('class', 'label')
-        .text(function(d) {
-          return d.key;
-        });
-      lsvg.exit().remove();
-      let svgEl = document.getElementById('svglegend');
-      if (svgEl) {
-        let bb;
-        // firefox can throw an exception on getBBox on an svg element
-        try {
-          bb = svgEl.getBBox();
-        } catch (e) {
-          bb = {
-            y: 0,
-            height: 200,
-            x: 0,
-            width: 200
-          };
-        }
-        svgEl.style.height = (bb.y + bb.height) + 'px';
-        svgEl.style.width = (bb.x + bb.width) + 'px';
-      }
-    };
-    let appendCircle = function(g) {
-      // add new circles and set their attr/class/behavior
-      return g.append('svg:circle')
-        .attr('class', 'node')
-        .attr('r', function(d) {
-          return Nodes.radius(d.nodeType);
-        })
-        .attr('fill', function (d) {
-          if (d.cdir === 'both' && !QDRService.utilities.isConsole(d)) {
-            return 'url(' + urlPrefix + '#half-circle)';
-          }
-          return null;
-        })
-        .classed('fixed', function(d) {
-          return d.fixed;
-        })
-        .classed('normal', function(d) {
-          return d.nodeType == 'normal' || QDRService.utilities.isConsole(d);
-        })
-        .classed('in', function(d) {
-          return d.cdir == 'in';
-        })
-        .classed('out', function(d) {
-          return d.cdir == 'out';
-        })
-        .classed('inout', function(d) {
-          return d.cdir == 'both';
-        })
-        .classed('inter-router', function(d) {
-          return d.nodeType == 'inter-router' || d.nodeType === '_topo';
-        })
-        .classed('on-demand', function(d) {
-          return d.nodeType == 'on-demand';
-        })
-        .classed('edge', function(d) {
-          return d.nodeType === 'edge' || d.nodeType === '_edge';
-        })
-        .classed('console', function(d) {
-          return QDRService.utilities.isConsole(d);
-        })
-        .classed('artemis', function(d) {
-          return QDRService.utilities.isArtemis(d);
-        })
-        .classed('qpid-cpp', function(d) {
-          return QDRService.utilities.isQpid(d);
-        })
-        .classed('route-container', function (d) {
-          return (!QDRService.utilities.isArtemis(d) && !QDRService.utilities.isQpid(d) && d.nodeType === 'route-container');
-        })
-        .classed('client', function(d) {
-          return d.nodeType === 'normal' && !d.properties.console_identifier;
-        });
-    };
-    let appendContent = function(g) {
-      // show node IDs
-      g.append('svg:text')
-        .attr('x', 0)
-        .attr('y', function(d) {
-          let y = 7;
-          if (QDRService.utilities.isArtemis(d))
-            y = 8;
-          else if (QDRService.utilities.isQpid(d))
-            y = 9;
-          else if (d.nodeType === 'inter-router')
-            y = 4;
-          else if (d.nodeType === 'route-container')
-            y = 5;
-          else if (d.nodeType === 'edge' || d.nodeType === '_edge')
-            y = 4;
-          return y;
-        })
-        .attr('class', 'id')
-        .classed('console', function(d) {
-          return QDRService.utilities.isConsole(d);
-        })
-        .classed('normal', function(d) {
-          return d.nodeType === 'normal';
-        })
-        .classed('on-demand', function(d) {
-          return d.nodeType === 'on-demand';
-        })
-        .classed('edge', function(d) {
-          return d.nodeType === 'edge';
-        })
-        .classed('edge', function(d) {
-          return d.nodeType === '_edge';
-        })
-        .classed('artemis', function(d) {
-          return QDRService.utilities.isArtemis(d);
-        })
-        .classed('qpid-cpp', function(d) {
-          return QDRService.utilities.isQpid(d);
-        })
-        .text(function(d) {
-          if (QDRService.utilities.isConsole(d)) {
-            return '\uf108'; // icon-desktop for this console
-          } else if (QDRService.utilities.isArtemis(d)) {
-            return '\ue900';
-          } else if (QDRService.utilities.isQpid(d)) {
-            return '\ue901';
-          } else if (d.nodeType === 'route-container') {
-            return d.properties.product ? d.properties.product[0].toUpperCase() : 'S';
-          } else if (d.nodeType === 'normal') {
-            return '\uf109'; // icon-laptop for clients
-          } else if (d.nodeType === 'edge' || d.nodeType === '_edge') {
-            return 'Edge';
-          }
-          return d.name.length > 7 ?
-            d.name.substr(0, 3) + '...' + d.name.substr(d.name.length-3, 3) :
-            d.name;
-        });
-    };
-    let appendTitle = function(g) {
-      g.append('svg:title').text(function(d) {
-        return d.title();
-      });
-    };
+    function updateLegend() {
+      // dynamically create/update the legend based on which node types are present
+      let lsvg = new Legend(svg);
+      lsvg.update(svg, QDRLog, urlPrefix);
+    }
 
-    let showToolTip = function (title, event) {
+    function showToolTip(title, event) {
       // show the tooltip
-      $timeout ( function () {
+      $timeout(function () {
         $scope.trustedpopoverContent = $sce.trustAsHtml(title);
         displayTooltip(event);
       });
-    };
+    }
 
-    let displayTooltip = function (event) {
-      $timeout( function () {
-        let top = $('#topology').offset().top - 5;
-        let width = $('#topology').width();
-        d3.select('#popover-div')
-          .style('visibility', 'hidden')
-          .style('display', 'block')
-          .style('left', (event.pageX+5)+'px')
-          .style('top', (event.pageY-top)+'px');
-        let pwidth = $('#popover-div').width();
-        d3.select('#popover-div')
-          .style('visibility', 'visible')
-          .style('left',(Math.min(width-pwidth, event.pageX+5) + 'px'))
-          .on('mouseout', function () {
-            d3.select(this)
-              .style('display', 'none');
+    function displayTooltip(event) {
+      $timeout(function () {
+        let top = $("#topology").offset().top - 5;
+        let width = $("#topology").width();
+        d3.select("#popover-div")
+          .style("visibility", "hidden")
+          .style("display", "block")
+          .style("left", event.pageX + 5 + "px")
+          .style("top", event.pageY - top + "px");
+        let pwidth = $("#popover-div").width();
+        d3.select("#popover-div")
+          .style("visibility", "visible")
+          .style("left", Math.min(width - pwidth, event.pageX + 5) + "px")
+          .on("mouseout", function () {
+            d3.select(this).style("display", "none");
           });
       });
-    };
+    }
 
     function hasChanged() {
       // Don't update the underlying topology diagram if we are adding a new node.
       // Once adding is completed, the topology will update automatically if it has changed
       let nodeInfo = QDRService.management.topology.nodeInfo();
       // don't count the nodes without connection info
-      let cnodes = Object.keys(nodeInfo).filter ( function (node) {
-        return (nodeInfo[node]['connection']);
+      let cnodes = Object.keys(nodeInfo).filter(function (node) {
+        return nodeInfo[node]["connection"];
       });
-      let routers = nodes.nodes.filter( function (node) {
-        return node.nodeType === '_topo';
+      let routers = forceData.nodes.nodes.filter(function (node) {
+        return node.nodeType === "_topo";
       });
       if (routers.length > cnodes.length) {
         return -1;
@@ -1000,14 +851,13 @@ export class TopologyController {
         return cnodes.length > Object.keys(savedKeys).length ? 1 : -1;
       }
       // we may have dropped a node and added a different node in the same update cycle
-      for (let i=0; i<cnodes.length; i++) {
+      for (let i = 0; i < cnodes.length; i++) {
         let key = cnodes[i];
         // if this node isn't in the saved node list
-        if (!savedKeys.hasOwnProperty(key))
-          return 1;
+        if (!savedKeys.hasOwnProperty(key)) return 1;
         // if the number of connections for this node chaanged
-        if (nodeInfo[key]['connection'].results.length !== savedKeys[key])
-          return nodeInfo[key]['connection'].results.length - savedKeys[key];
+        if (nodeInfo[key]["connection"].results.length !== savedKeys[key])
+          return nodeInfo[key]["connection"].results.length - savedKeys[key];
       }
       return 0;
     }
@@ -1017,52 +867,53 @@ export class TopologyController {
       let nodeInfo = QDRService.management.topology.nodeInfo();
       // save the number of connections per node
       for (let key in nodeInfo) {
-        if (nodeInfo[key]['connection'])
-          savedKeys[key] = nodeInfo[key]['connection'].results.length;
+        if (nodeInfo[key]["connection"])
+          savedKeys[key] = nodeInfo[key]["connection"].results.length;
       }
     }
-    function destroy () {
-      nodes.savePositions();
+
+    function destroy() {
+      forceData.nodes.savePositions();
       QDRService.management.topology.setUpdateEntities([]);
       QDRService.management.topology.stopUpdating();
-      QDRService.management.topology.delUpdatedAction('normalsStats');
-      QDRService.management.topology.delUpdatedAction('topology');
-      QDRService.management.topology.delUpdatedAction('connectionPopupHTML');
+      QDRService.management.topology.delUpdatedAction("normalsStats");
+      QDRService.management.topology.delUpdatedAction("topology");
+      QDRService.management.topology.delUpdatedAction("connectionPopupHTML");
 
-      d3.select('#SVG_ID').remove();
-      window.removeEventListener('resize', resize);
+      d3.select("#SVG_ID").remove();
+      window.removeEventListener("resize", resize);
       traffic.stop();
-      d3.select('#main_container')
-        .style('background-color', 'white');
+      d3.select("#main_container").style("background-color", "white");
     }
     // When the DOM element is removed from the page,
     // AngularJS will trigger the $destroy event on
     // the scope
-    $scope.$on('$destroy', function() {
+    $scope.$on("$destroy", function () {
       destroy();
     });
     // we are about to leave the page, save the node positions
-    $rootScope.$on('$locationChangeStart', function() {
+    $rootScope.$on("$locationChangeStart", function () {
       destroy();
     });
 
     function handleInitialUpdate() {
       // we only need to update connections during steady-state
-      QDRService.management.topology.setUpdateEntities(['connection']);
+      QDRService.management.topology.setUpdateEntities(["connection"]);
       // we currently have all entities available on all routers
       initForceGraph();
       saveChanged();
       // after the graph is displayed fetch all .router.node info. This is done so highlighting between nodes
       // doesn't incur a delay
-      QDRService.management.topology.addUpdateEntities([
-        {entity: 'router.node', attrs: ['id','nextHop']}
-      ]);
+      QDRService.management.topology.addUpdateEntities([{
+        entity: "router.node",
+        attrs: ["id", "nextHop"]
+      }]);
       // call this function every time a background update is done
-      QDRService.management.topology.addUpdatedAction('topology', function() {
+      QDRService.management.topology.addUpdatedAction("topology", function () {
         let changed = hasChanged();
         // there is a new node, we need to get all of it's entities before drawing the graph
         if (changed > 0) {
-          QDRService.management.topology.delUpdatedAction('topology');
+          QDRService.management.topology.delUpdatedAction("topology");
           animate = true;
           setupInitialUpdate();
         } else if (changed === -1) {
@@ -1073,27 +924,36 @@ export class TopologyController {
         } else {
           //QDRLog.debug("topology didn't change")
         }
-
       });
     }
+
     function setupInitialUpdate() {
       // make sure all router nodes have .connection info. if not then fetch any missing info
       QDRService.management.topology.ensureAllEntities(
-        [{entity: 'connection'}],
-        handleInitialUpdate);
+        [{
+          entity: "connection"
+        }],
+        handleInitialUpdate
+      );
     }
     if (!QDRService.management.connection.is_connected()) {
       // we are not connected. we probably got here from a bookmark or manual page reload
-      QDRRedirectWhenConnected($location, 'topology');
+      QDRRedirectWhenConnected($location, "topology");
       return;
     }
 
-
-
     animate = true;
     setupInitialUpdate();
     QDRService.management.topology.startUpdating(true);
-
   }
 }
-TopologyController.$inject = ['QDRService', '$scope', '$log', '$rootScope', '$location', '$timeout', '$uibModal', '$sce'];
+TopologyController.$inject = [
+  "QDRService",
+  "$scope",
+  "$log",
+  "$rootScope",
+  "$location",
+  "$timeout",
+  "$uibModal",
+  "$sce"
+];
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/81e58b46/console/stand-alone/plugin/js/topology/svgUtils.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/topology/svgUtils.js b/console/stand-alone/plugin/js/topology/svgUtils.js
new file mode 100644
index 0000000..999bcf5
--- /dev/null
+++ b/console/stand-alone/plugin/js/topology/svgUtils.js
@@ -0,0 +1,249 @@
+/*
+ * Copyright 2018 Red Hat Inc.
+ *
+ * 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.
+ */
+
+import { Nodes } from "./nodes.js";
+import { utils } from "../amqp/utilities.js";
+
+export function updateState(circle, selected_node) {
+  circle
+    .selectAll("circle")
+    .classed("highlighted", function (d) {
+      return d.highlighted;
+    })
+    .classed("selected", function (d) {
+      return d === selected_node;
+    })
+    .classed("fixed", function (d) {
+      return d.fixed ? d.fixed & 1 : false;
+    })
+    .classed("multiple", function (d) {
+      return d.normals && d.normals.length > 1;
+    });
+}
+
+export function appendCircle(g, urlPrefix) {
+  // add new circles and set their attr/class/behavior
+  return g
+    .append("svg:circle")
+    .attr("class", "node")
+    .attr("r", function (d) {
+      return Nodes.radius(d.nodeType);
+    })
+    .attr("fill", function (d) {
+      if (d.cdir === "both" && !utils.isConsole(d)) {
+        return "url(" + urlPrefix + "#half-circle)";
+      }
+      return null;
+    })
+    .classed("fixed", function (d) {
+      return d.fixed ? d.fixed & 1 : false;
+    })
+    .classed("normal", function (d) {
+      return d.nodeType == "normal" || utils.isConsole(d);
+    })
+    .classed("in", function (d) {
+      return d.cdir == "in";
+    })
+    .classed("out", function (d) {
+      return d.cdir == "out";
+    })
+    .classed("inout", function (d) {
+      return d.cdir == "both";
+    })
+    .classed("inter-router", function (d) {
+      return d.nodeType == "inter-router" || d.nodeType === "_topo";
+    })
+    .classed("on-demand", function (d) {
+      return d.nodeType == "on-demand";
+    })
+    .classed("edge", function (d) {
+      return d.nodeType === "edge" || d.nodeType === "_edge";
+    })
+    .classed("console", function (d) {
+      return utils.isConsole(d);
+    })
+    .classed("artemis", function (d) {
+      return utils.isArtemis(d);
+    })
+    .classed("qpid-cpp", function (d) {
+      return utils.isQpid(d);
+    })
+    .classed("route-container", function (d) {
+      return (
+        !utils.isArtemis(d) &&
+        !utils.isQpid(d) &&
+        d.nodeType === "route-container"
+      );
+    })
+    .classed("client", function (d) {
+      return d.nodeType === "normal" && !d.properties.console_identifier;
+    });
+}
+
+export function appendContent(g) {
+  // show node IDs
+  g.append("svg:text")
+    .attr("x", 0)
+    .attr("y", function (d) {
+      let y = 7;
+      if (utils.isArtemis(d)) y = 8;
+      else if (utils.isQpid(d)) y = 9;
+      else if (d.nodeType === "inter-router") y = 4;
+      else if (d.nodeType === "route-container") y = 5;
+      else if (d.nodeType === "edge" || d.nodeType === "_edge") y = 4;
+      return y;
+    })
+    .attr("class", "id")
+    .classed("console", function (d) {
+      return utils.isConsole(d);
+    })
+    .classed("normal", function (d) {
+      return d.nodeType === "normal";
+    })
+    .classed("on-demand", function (d) {
+      return d.nodeType === "on-demand";
+    })
+    .classed("edge", function (d) {
+      return d.nodeType === "edge";
+    })
+    .classed("edge", function (d) {
+      return d.nodeType === "_edge";
+    })
+    .classed("artemis", function (d) {
+      return utils.isArtemis(d);
+    })
+    .classed("qpid-cpp", function (d) {
+      return utils.isQpid(d);
+    })
+    .text(function (d) {
+      if (utils.isConsole(d)) {
+        return "\uf108"; // icon-desktop for a console
+      } else if (utils.isArtemis(d)) {
+        return "\ue900";  // custom font character
+      } else if (utils.isQpid(d)) {
+        return "\ue901";  // custom font character
+      } else if (d.nodeType === "route-container") {
+        return d.properties.product ?
+          d.properties.product[0].toUpperCase() :
+          "S";
+      } else if (d.nodeType === "normal") {
+        return "\uf109"; // icon-laptop for clients
+      } else if (d.nodeType === "edge" || d.nodeType === "_edge") {
+        return "Edge";
+      }
+      return d.name.length > 7 ?
+        d.name.substr(0, 3) + "..." + d.name.substr(d.name.length - 3, 3) :
+        d.name;
+    });
+}
+
+export function appendTitle(g) {
+  g.append("svg:title").text(function (d) {
+    return d.title();
+  });
+}
+
+// Generate a marker for each combination of:
+//  start|end, ''|selected highlighted, and each possible node radius
+export function addDefs(svg) {
+  let sten = ["start", "end"];
+  let states = ["", "selected", "highlighted", "unknown"];
+  let radii = Nodes.discrete();
+  let defs = [];
+  for (let isten = 0; isten < sten.length; isten++) {
+    for (let istate = 0; istate < states.length; istate++) {
+      for (let iradii = 0; iradii < radii.length; iradii++) {
+        defs.push({
+          sten: sten[isten],
+          state: states[istate],
+          r: radii[iradii]
+        });
+      }
+    }
+  }
+  svg
+    .append("svg:defs")
+    .attr("class", "marker-defs")
+    .selectAll("marker")
+    .data(defs)
+    .enter()
+    .append("svg:marker")
+    .attr("id", function (d) {
+      return [d.sten, d.state, d.r].join("-");
+    })
+    .attr("viewBox", "0 -5 10 10")
+    .attr("refX", function (d) {
+      return Nodes.refX(d.sten, d.r);
+    })
+    .attr("markerWidth", 14)
+    .attr("markerHeight", 14)
+    .attr("markerUnits", "userSpaceOnUse")
+    .attr("orient", "auto")
+    .append("svg:path")
+    .attr("d", function (d) {
+      return d.sten === "end" ?
+        "M 0 -5 L 10 0 L 0 5 z" :
+        "M 10 -5 L 0 0 L 10 5 z";
+    });
+  addStyles(
+    sten, {
+      selected: "#33F",
+      highlighted: "#6F6",
+      unknown: "#888"
+    },
+    radii
+  );
+}
+export function addGradient(svg) {
+  // gradient for sender/receiver client
+  let grad = svg
+    .append("svg:defs")
+    .append("linearGradient")
+    .attr("id", "half-circle")
+    .attr("x1", "0%")
+    .attr("x2", "0%")
+    .attr("y1", "100%")
+    .attr("y2", "0%");
+  grad
+    .append("stop")
+    .attr("offset", "50%")
+    .style("stop-color", "#C0F0C0");
+  grad
+    .append("stop")
+    .attr("offset", "50%")
+    .style("stop-color", "#F0F000");
+}
+
+function addStyles(stend, stateColor, radii) {
+  // the <style>
+  let element = document.querySelector("style");
+  // Reference to the stylesheet
+  let sheet = element.sheet;
+
+  let states = Object.keys(stateColor);
+  // create styles for each combo of 'stend-state-radii'
+  for (let istend = 0; istend < stend.length; istend++) {
+    for (let istate = 0; istate < states.length; istate++) {
+      let selectors = [];
+      for (let iradii = 0; iradii < radii.length; iradii++) {
+        selectors.push(`#${stend[istend]}-${states[istate]}-${radii[iradii]}`);
+      }
+      let color = stateColor[states[istate]];
+      let sels = `${selectors.join(",")} {fill: ${color}; stroke: ${color};}`;
+      sheet.insertRule(sels, 0);
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/81e58b46/console/stand-alone/plugin/js/topology/topoUtils.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/topology/topoUtils.js b/console/stand-alone/plugin/js/topology/topoUtils.js
index 71e5e4c..99e2c9a 100644
--- a/console/stand-alone/plugin/js/topology/topoUtils.js
+++ b/console/stand-alone/plugin/js/topology/topoUtils.js
@@ -18,36 +18,44 @@ under the License.
 */
 
 /* global Set */
+import {
+  utils
+} from "../amqp/utilities.js";
 
 // highlight the paths between the selected node and the hovered node
-function findNextHopNode(from, d, QDRService, selected_node, nodes) {
+function findNextHopNode(from, d, nodeInfo, selected_node, nodes) {
   // d is the node that the mouse is over
   // from is the selected_node ....
-  if (!from)
-    return null;
+  if (!from) return null;
 
-  if (from == d)
-    return selected_node;
+  if (from == d) return selected_node;
 
-  let sInfo = QDRService.management.topology.nodeInfo()[from.key];
+  let sInfo = nodeInfo[from.key];
 
   // find the hovered name in the selected name's .router.node results
-  if (!sInfo['router.node'])
-    return null;
-  let aAr = sInfo['router.node'].attributeNames;
-  let vAr = sInfo['router.node'].results;
+  if (!sInfo["router.node"]) return null;
+  let aAr = sInfo["router.node"].attributeNames;
+  let vAr = sInfo["router.node"].results;
   for (let hIdx = 0; hIdx < vAr.length; ++hIdx) {
-    let addrT = QDRService.utilities.valFor(aAr, vAr[hIdx], 'id');
-    if (d.name && (addrT == d.name)) {
-      let next = QDRService.utilities.valFor(aAr, vAr[hIdx], 'nextHop');
-      return (next == null) ? nodes.nodeFor(addrT) : nodes.nodeFor(next);
+    let addrT = utils.valFor(aAr, vAr[hIdx], "id");
+    if (d.name && addrT == d.name) {
+      let next = utils.valFor(aAr, vAr[hIdx], "nextHop");
+      return next == null ? nodes.nodeFor(addrT) : nodes.nodeFor(next);
     }
   }
   return null;
 }
-export function nextHop(thisNode, d, nodes, links, QDRService, selected_node, cb) {
-  if ((thisNode) && (thisNode != d)) {
-    let target = findNextHopNode(thisNode, d, QDRService, selected_node, nodes);
+export function nextHop(
+  thisNode,
+  d,
+  nodes,
+  links,
+  nodeInfo,
+  selected_node,
+  cb
+) {
+  if (thisNode && thisNode != d) {
+    let target = findNextHopNode(thisNode, d, nodeInfo, selected_node, nodes);
     if (target) {
       let hnode = nodes.nodeFor(thisNode.name);
       let hlLink = links.linkFor(hnode, target);
@@ -55,164 +63,177 @@ export function nextHop(thisNode, d, nodes, links, QDRService, selected_node, cb
         if (cb) {
           cb(hlLink, hnode, target);
         }
-      }
-      else
-        target = null;
+      } else target = null;
     }
-    nextHop(target, d, nodes, links, QDRService, selected_node, cb);
+    nextHop(target, d, nodes, links, nodeInfo, selected_node, cb);
   }
 }
 
 let linkRateHistory = {};
-export function connectionPopupHTML (d, QDRService) {
+export function connectionPopupHTML(d, nodeInfo) {
   if (!d) {
     linkRateHistory = {};
     return;
   }
-  let utils = QDRService.utilities;
   // return all of onode's connections that connecto to right
   let getConnsArray = function (onode, key, right) {
     if (right.normals) {
       // if we want connections between a router and a client[s]
       let connIds = new Set();
-      let connIndex = onode.connection.attributeNames.indexOf('identity');
-      for (let n=0; n<right.normals.length; n++) {
+      let connIndex = onode.connection.attributeNames.indexOf("identity");
+      for (let n = 0; n < right.normals.length; n++) {
         let normal = right.normals[n];
         if (normal.key === key) {
           connIds.add(normal.connectionId);
         } else if (normal.alsoConnectsTo) {
-          normal.alsoConnectsTo.forEach( function (ac2) {
-            if (ac2.key === key)
-              connIds.add(ac2.connectionId);
+          normal.alsoConnectsTo.forEach(function (ac2) {
+            if (ac2.key === key) connIds.add(ac2.connectionId);
           });
         }
       }
-      return onode.connection.results.filter( function (result) {
-        return connIds.has(result[connIndex]);
-      }).map( function (c) {
-        return utils.flatten(onode.connection.attributeNames, c);
-      });
-    }
-    else {
-    // we want the connection between two routers
+      return onode.connection.results
+        .filter(function (result) {
+          return connIds.has(result[connIndex]);
+        })
+        .map(function (c) {
+          return utils.flatten(onode.connection.attributeNames, c);
+        });
+    } else {
+      // we want the connection between two routers
       let container = utils.nameFromId(right.key);
-      let containerIndex = onode.connection.attributeNames.indexOf('container');
-      let roleIndex = onode.connection.attributeNames.indexOf('role');
-      return onode.connection.results.filter( function (conn) {
-        return conn[containerIndex] === container && conn[roleIndex] === 'inter-router';
-      }).map( function (c) {
-        return utils.flatten(onode.connection.attributeNames, c);
-      });
+      let containerIndex = onode.connection.attributeNames.indexOf("container");
+      let roleIndex = onode.connection.attributeNames.indexOf("role");
+      return onode.connection.results
+        .filter(function (conn) {
+          return (
+            conn[containerIndex] === container &&
+            conn[roleIndex] === "inter-router"
+          );
+        })
+        .map(function (c) {
+          return utils.flatten(onode.connection.attributeNames, c);
+        });
     }
   };
   // construct HTML to be used in a popup when the mouse is moved over a link.
   // The HTML is sanitized elsewhere before it is displayed
   let linksHTML = function (onode, conns) {
     const max_links = 10;
-    const fields = ['deliveryCount', 'undeliveredCount', 'unsettledCount', 'rejectedCount', 'releasedCount', 'modifiedCount'];
+    const fields = [
+      "deliveryCount",
+      "undeliveredCount",
+      "unsettledCount",
+      "rejectedCount",
+      "releasedCount",
+      "modifiedCount"
+    ];
     // local function to determine if a link's connectionId is in any of the connections
     let isLinkFor = function (connectionId, conns) {
-      for (let c=0; c<conns.length; c++) {
-        if (conns[c].identity === connectionId)
-          return true;
+      for (let c = 0; c < conns.length; c++) {
+        if (conns[c].identity === connectionId) return true;
       }
       return false;
     };
     let fnJoin = function (ar, sepfn) {
-      let out = '';
+      let out = "";
       out = ar[0];
-      for (let i=1; i<ar.length; i++) {
-        let sep = sepfn(ar[i], i===ar.length-1);
-        out += (sep[0] + sep[1]);
+      for (let i = 1; i < ar.length; i++) {
+        let sep = sepfn(ar[i], i === ar.length - 1);
+        out += sep[0] + sep[1];
       }
       return out;
     };
     // if the data for the line is from a client (small circle), we may have multiple connections
     // loop through all links for this router and accumulate those belonging to the connection(s)
-    let nodeLinks = onode['router.link'];
-    if (!nodeLinks)
-      return '';
+    let nodeLinks = onode["router.link"];
+    if (!nodeLinks) return "";
     let links = [];
     let hasAddress = false;
-    for (let n=0; n<nodeLinks.results.length; n++) {
+    for (let n = 0; n < nodeLinks.results.length; n++) {
       let link = utils.flatten(nodeLinks.attributeNames, nodeLinks.results[n]);
       let allZero = true;
-      if (link.linkType !== 'router-control') {
+      if (link.linkType !== "router-control") {
         if (isLinkFor(link.connectionId, conns)) {
-          if (link.owningAddr)
-            hasAddress = true;
+          if (link.owningAddr) hasAddress = true;
           if (link.name) {
-            let rates = utils.rates(link, fields, linkRateHistory, link.name, 1);
+            let rates = utils.rates(
+              link,
+              fields,
+              linkRateHistory,
+              link.name,
+              1
+            );
             // replace the raw value with the rate
-            for (let i=0; i<fields.length; i++) {
-              if (rates[fields[i]] > 0)
-                allZero = false;
+            for (let i = 0; i < fields.length; i++) {
+              if (rates[fields[i]] > 0) allZero = false;
               link[fields[i]] = rates[fields[i]];
             }
           }
-          if (!allZero)
-            links.push(link);
+          if (!allZero) links.push(link);
         }
       }
     }
     // we may need to limit the number of links displayed, so sort descending by the sum of the field values
     let sum = function (a) {
       let s = 0;
-      for (let i=0; i<fields.length; i++) {
+      for (let i = 0; i < fields.length; i++) {
         s += a[fields[i]];
       }
       return s;
     };
-    links.sort( function (a, b) {
+    links.sort(function (a, b) {
       let asum = sum(a);
       let bsum = sum(b);
       return asum < bsum ? 1 : asum > bsum ? -1 : 0;
     });
 
-    let HTMLHeading = '<h5>Rates (per second) for links</h5>';
+    let HTMLHeading = "<h5>Rates (per second) for links</h5>";
     let HTML = '<table class="popupTable">';
     // copy of fields since we may be prepending an address
     let th = fields.slice();
     let td = fields;
-    th.unshift('dir');
-    td.unshift('linkDir');
-    th.push('priority');
-    td.push('priority');
+    th.unshift("dir");
+    td.unshift("linkDir");
+    th.push("priority");
+    td.push("priority");
     // add an address field if any of the links had an owningAddress
     if (hasAddress) {
-      th.unshift('address');
-      td.unshift('owningAddr');
+      th.unshift("address");
+      td.unshift("owningAddr");
     }
 
     let rate_th = function (th) {
-      let rth = th.map( function (t) {
-        if (t.endsWith('Count'))
-          t = t.replace('Count', 'Rate');
+      let rth = th.map(function (t) {
+        if (t.endsWith("Count")) t = t.replace("Count", "Rate");
         return utils.humanify(t);
       });
       return rth;
     };
-    HTML += ('<tr class="header"><td>' + rate_th(th).join('</td><td>') + '</td></tr>');
+    HTML +=
+      '<tr class="header"><td>' + rate_th(th).join("</td><td>") + "</td></tr>";
     // add rows to the table for each link
-    for (let l=0; l<links.length; l++) {
-      if (l>=max_links) {
+    for (let l = 0; l < links.length; l++) {
+      if (l >= max_links) {
         HTMLHeading = `<h5>Rates (per second) for top ${max_links} links</h5>`;
         break;
       }
       let link = links[l];
-      let vals = td.map( function (f) {
-        if (f === 'owningAddr') {
+      let vals = td.map(function (f) {
+        if (f === "owningAddr") {
           let identity = utils.identity_clean(link.owningAddr);
           return utils.addr_text(identity);
         }
         return link[f];
       });
       let joinedVals = fnJoin(vals, function (v1, last) {
-        return [`</td><td${(isNaN(+v1) ? '': ' align="right"')}>`, last ? v1 : utils.pretty(v1 || '0', ',.2f')];
+        return [
+          `</td><td${isNaN(+v1) ? "" : ' align="right"'}>`,
+          last ? v1 : utils.pretty(v1 || "0", ",.2f")
+        ];
       });
       HTML += `<tr><td> ${joinedVals} </td></tr>`;
     }
-    return links.length > 0 ? `${HTMLHeading}${HTML}</table>` : '';
+    return links.length > 0 ? `${HTMLHeading}${HTML}</table>` : "";
   };
 
   let left, right;
@@ -228,43 +249,40 @@ export function connectionPopupHTML (d, QDRService) {
     [left, right] = [right, left];
   }
   // left is a router. right is either a router or a client[s]
-  let onode = QDRService.management.topology.nodeInfo()[left.key];
+  let onode = nodeInfo[left.key];
   // find all the connections for left that go to right
   let conns = getConnsArray(onode, left.key, right);
 
-  let HTML = '';
-  HTML += '<h5>Connection'+(conns.length > 1 ? 's' : '')+'</h5>';
-  HTML += '<table class="popupTable"><tr class="header"><td>Security</td><td>Authentication</td><td>Tenant</td><td>Host</td>';
+  let HTML = "";
+  HTML += "<h5>Connection" + (conns.length > 1 ? "s" : "") + "</h5>";
+  HTML +=
+    '<table class="popupTable"><tr class="header"><td>Security</td><td>Authentication</td><td>Tenant</td><td>Host</td>';
 
-  for (let c=0; c<Math.min(conns.length, 10); c++) {
-    HTML += ('<tr><td>' + utils.connSecurity(conns[c]) + '</td>');
-    HTML += ('<td>' + utils.connAuth(conns[c]) + '</td>');
-    HTML += ('<td>' + (utils.connTenant(conns[c]) || '--') + '</td>');
-    HTML += ('<td>' + conns[c].host + '</td>');
-    HTML += '</tr>';
+  for (let c = 0; c < Math.min(conns.length, 10); c++) {
+    HTML += "<tr><td>" + utils.connSecurity(conns[c]) + "</td>";
+    HTML += "<td>" + utils.connAuth(conns[c]) + "</td>";
+    HTML += "<td>" + (utils.connTenant(conns[c]) || "--") + "</td>";
+    HTML += "<td>" + conns[c].host + "</td>";
+    HTML += "</tr>";
   }
-  HTML += '</table>';
+  HTML += "</table>";
   HTML += linksHTML(onode, conns);
   return HTML;
 }
 
-export function addStyles (stend, stateColor, radii) {
-  // the <style>
-  let element = document.querySelector('style');
-  // Reference to the stylesheet
-  let sheet = element.sheet;
-
-  let states = Object.keys(stateColor);
-  // create styles for each combo of 'stend-state-radii'
-  for (let istend=0; istend<stend.length; istend++) {
-    for (let istate=0; istate<states.length; istate++) {
-      let selectors = [];
-      for (let iradii=0; iradii<radii.length; iradii++) {
-        selectors.push(`#${stend[istend]}-${states[istate]}-${radii[iradii]}`);
-      }
-      let color = stateColor[states[istate]];
-      let sels = `${selectors.join(',')} {fill: ${color}; stroke: ${color};}`;
-      sheet.insertRule(sels, 0);
-    }
+export function getSizes(QDRLog) {
+  const gap = 5;
+  let legendWidth = 194;
+  let topoWidth = $("#topology").width();
+  if (topoWidth < 768) legendWidth = 0;
+  let width = $("#topology").width() - gap - legendWidth;
+  let top = $("#topology").offset().top;
+  let height = window.innerHeight - top - gap;
+  if (width < 10) {
+    QDRLog.info(
+      `page width and height are abnormal w: ${width} h: ${height}`
+    );
+    return [0, 0];
   }
+  return [width, height];
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org


Mime
View raw message