diff --git a/client/app/scripts/charts/nodes-chart.js b/client/app/scripts/charts/nodes-chart.js
index 952eff3e5..2fe09476c 100644
--- a/client/app/scripts/charts/nodes-chart.js
+++ b/client/app/scripts/charts/nodes-chart.js
@@ -133,11 +133,10 @@ const NodesChart = React.createClass({
_.each(topology, function(node, id) {
nodes[id] = prevNodes[id] || {};
- // initialize position for new nodes
+ // use cached positions if available
_.defaults(nodes[id], {
x: centerX,
- y: centerY,
- textAnchor: 'start'
+ y: centerY
});
// copy relevant fields to state nodes
@@ -184,12 +183,17 @@ const NodesChart = React.createClass({
},
updateGraphState: function(props) {
+ const n = _.size(props.nodes);
+
+ if (n === 0) {
+ return;
+ }
+
const nodes = this.initNodes(props.nodes, this.state.nodes);
const edges = this.initEdges(props.nodes, nodes);
const expanse = Math.min(props.height, props.width);
const nodeSize = expanse / 2;
- const n = _.size(props.nodes);
const nodeScale = d3.scale.linear().range([0, nodeSize / Math.pow(n, 0.7)]);
const timedLayouter = timely(NodesLayout.doLayout);
@@ -199,7 +203,8 @@ const NodesChart = React.createClass({
props.width,
props.height,
nodeScale,
- MARGINS
+ MARGINS,
+ this.props.topologyId
);
debug('graph layout took ' + timedLayouter.time + 'ms');
diff --git a/client/app/scripts/charts/nodes-layout.js b/client/app/scripts/charts/nodes-layout.js
index 10ce9bfe8..456b87dbf 100644
--- a/client/app/scripts/charts/nodes-layout.js
+++ b/client/app/scripts/charts/nodes-layout.js
@@ -4,78 +4,89 @@ const Naming = require('../constants/naming');
const _ = require('lodash');
const MAX_NODES = 100;
-const g = new dagre.graphlib.Graph({});
+const topologyGraphs = {};
-const doLayout = function(nodes, edges, width, height, scale, margins) {
+const doLayout = function(nodes, edges, width, height, scale, margins, topologyId) {
let offsetX = 0 + margins.left;
let offsetY = 0 + margins.top;
+ let graph;
if (_.size(nodes) > MAX_NODES) {
debug('Too many nodes for graph layout engine. Limit: ' + MAX_NODES);
return null;
}
+ // one engine per topology, to keep renderings similar
+ if (!topologyGraphs[topologyId]) {
+ topologyGraphs[topologyId] = new dagre.graphlib.Graph({});
+ }
+ graph = topologyGraphs[topologyId];
+
// configure node margins
- g.setGraph({
+ graph.setGraph({
nodesep: scale(2.5),
ranksep: scale(2.5)
});
// add nodes to the graph if not already there
_.each(nodes, function(node) {
- if (!g.hasNode(node.id)) {
- g.setNode(node.id, {id: node.id, width: scale(0.75), height: scale(0.75)});
+ if (!graph.hasNode(node.id)) {
+ graph.setNode(node.id, {
+ id: node.id,
+ width: scale(0.75),
+ height: scale(0.75)
+ });
}
});
// remove nodes that are no longer there
- _.each(g.nodes(), function(nodeid) {
+ _.each(graph.nodes(), function(nodeid) {
if (!_.has(nodes, nodeid)) {
- g.removeNode(nodeid);
+ graph.removeNode(nodeid);
}
});
// add edges to the graph if not already there
_.each(edges, function(edge) {
- if (!g.hasEdge(edge.source.id, edge.target.id)) {
+ if (!graph.hasEdge(edge.source.id, edge.target.id)) {
const virtualNodes = edge.source.id === edge.target.id ? 1 : 0;
- g.setEdge(edge.source.id, edge.target.id, {id: edge.id, minlen: virtualNodes});
+ graph.setEdge(edge.source.id, edge.target.id, {id: edge.id, minlen: virtualNodes});
}
});
// remoed egdes that are no longer there
- _.each(g.edges(), function(edgeObj) {
+ _.each(graph.edges(), function(edgeObj) {
const edge = [edgeObj.v, edgeObj.w];
const edgeId = edge.join(Naming.EDGE_ID_SEPARATOR);
if (!_.has(edges, edgeId)) {
- g.removeEdge(edgeObj.v, edgeObj.w);
+ graph.removeEdge(edgeObj.v, edgeObj.w);
}
});
- dagre.layout(g);
+ dagre.layout(graph);
- const graph = g.graph();
+ const layout = graph.graph();
// shifting graph coordinates to center
- if (graph.width < width) {
- offsetX = (width - graph.width) / 2 + margins.left;
+ if (layout.width < width) {
+ offsetX = (width - layout.width) / 2 + margins.left;
}
- if (graph.height < height) {
- offsetY = (height - graph.height) / 2 + margins.top;
+ if (layout.height < height) {
+ offsetY = (height - layout.height) / 2 + margins.top;
}
// apply coordinates to nodes and edges
- g.nodes().forEach(function(id) {
+ graph.nodes().forEach(function(id) {
const node = nodes[id];
- const graphNode = g.node(id);
+ const graphNode = graph.node(id);
node.x = graphNode.x + offsetX;
node.y = graphNode.y + offsetY;
});
- g.edges().forEach(function(id) {
- const graphEdge = g.edge(id);
+ graph.edges().forEach(function(id) {
+ const graphEdge = graph.edge(id);
const edge = edges[graphEdge.id];
_.each(graphEdge.points, function(point) {
point.x += offsetX;
@@ -84,9 +95,9 @@ const doLayout = function(nodes, edges, width, height, scale, margins) {
edge.points = graphEdge.points;
});
- // return object with width and height of layout
+ // return object with the width and height of layout
- return graph;
+ return layout;
};
module.exports = {
diff --git a/client/app/scripts/components/app.js b/client/app/scripts/components/app.js
index a9b0899d2..2902e8a42 100644
--- a/client/app/scripts/components/app.js
+++ b/client/app/scripts/components/app.js
@@ -15,6 +15,7 @@ const ESC_KEY_CODE = 27;
function getStateFromStores() {
return {
currentTopology: AppStore.getCurrentTopology(),
+ currentTopologyId: AppStore.getCurrentTopologyId(),
errorUrl: AppStore.getErrorUrl(),
highlightedEdgeIds: AppStore.getHighlightedEdgeIds(),
highlightedNodeIds: AppStore.getHighlightedNodeIds(),
@@ -70,7 +71,8 @@ const App = React.createClass({