Skip to content

Commit

Permalink
Merge pull request #1261 from weaveworks/1238-cache-panning
Browse files Browse the repository at this point in the history
Cache pan/zoom per topology
  • Loading branch information
davkal committed Apr 11, 2016
2 parents ed09f5b + cd12d86 commit ff417c9
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 22 deletions.
23 changes: 19 additions & 4 deletions client/app/scripts/charts/nodes-chart.js
Expand Up @@ -23,6 +23,8 @@ const MARGINS = {
bottom: 0
};

const ZOOM_CACHE_FIELDS = ['scale', 'panTranslateX', 'panTranslateY'];

// make sure circular layouts a bit denser with 3-6 nodes
const radiusDensity = d3.scale.threshold()
.domain([3, 6]).range([2.5, 3.5, 3]);
Expand All @@ -43,7 +45,8 @@ export default class NodesChart extends React.Component {
panTranslateY: 0,
scale: 1,
selectedNodeScale: d3.scale.linear(),
hasZoomed: false
hasZoomed: false,
zoomCache: {}
};
}

Expand All @@ -58,13 +61,25 @@ export default class NodesChart extends React.Component {

// wipe node states when showing different topology
if (nextProps.topologyId !== this.props.topologyId) {
_.assign(state, {
// re-apply cached canvas zoom/pan to d3 behavior
const nextZoom = this.state.zoomCache[nextProps.topologyId];
if (nextZoom) {
this.zoom.scale(nextZoom.scale);
this.zoom.translate([nextZoom.panTranslateX, nextZoom.panTranslateY]);
}

// saving previous zoom state
const prevZoom = _.pick(this.state, ZOOM_CACHE_FIELDS);
const zoomCache = _.assign({}, this.state.zoomCache);
zoomCache[this.props.topologyId] = prevZoom;

// clear canvas and apply zoom state
_.assign(state, nextZoom, { zoomCache }, {
nodes: makeMap(),
edges: makeMap()
});
}
//
// FIXME add PureRenderMixin, Immutables, and move the following functions to render()

// _.assign(state, this.updateGraphState(nextProps, state));
if (nextProps.forceRelayout || nextProps.nodes !== this.props.nodes) {
_.assign(state, this.updateGraphState(nextProps, state));
Expand Down
15 changes: 2 additions & 13 deletions client/app/scripts/charts/nodes-layout.js
Expand Up @@ -3,7 +3,7 @@ import debug from 'debug';
import { fromJS, Map as makeMap, Set as ImmSet } from 'immutable';

import { EDGE_ID_SEPARATOR } from '../constants/naming';
import { updateNodeDegrees } from '../utils/topology-utils';
import { buildTopologyCacheId, updateNodeDegrees } from '../utils/topology-utils';

const log = debug('scope:nodes-layout');

Expand All @@ -25,17 +25,6 @@ function fromGraphNodeId(encodedId) {
return encodedId.replace('<DOT>', '.');
}

function buildCacheIdFromOptions(options) {
if (options) {
let id = options.topologyId;
if (options.topologyOptions) {
id += JSON.stringify(options.topologyOptions);
}
return id;
}
return '';
}

/**
* Layout engine runner
* After the layout engine run nodes and edges have x-y-coordinates. Engine is
Expand Down Expand Up @@ -348,7 +337,7 @@ function copyLayoutProperties(layout, nodeCache, edgeCache) {
*/
export function doLayout(immNodes, immEdges, opts) {
const options = opts || {};
const cacheId = buildCacheIdFromOptions(options);
const cacheId = buildTopologyCacheId(options.topologyId, options.topologyOptions);

// one engine and node and edge caches per topology, to keep renderings similar
if (!topologyCaches[cacheId]) {
Expand Down
20 changes: 15 additions & 5 deletions client/app/scripts/utils/__tests__/topology-utils-test.js
Expand Up @@ -107,17 +107,27 @@ describe('TopologyUtils', () => {
expect(nodes.n3.degree).toEqual(0);
});

describe('buildTopologyCacheId', () => {
it('should generate a cache ID', () => {
const fun = TopologyUtils.buildTopologyCacheId;
expect(fun()).toEqual('');
expect(fun('test')).toEqual('test');
expect(fun(undefined, 'test')).toEqual('');
expect(fun('test', {a: 1})).toEqual('test{"a":1}');
});
});

describe('filterHiddenTopologies', () => {
it('should filter out empty topos that set hide_if_empty=true', () => {
const topos = [
{id: 'a', hide_if_empty: true, stats: {node_count: 0, filtered_nodes:0}},
{id: 'b', hide_if_empty: true, stats: {node_count: 1, filtered_nodes:0}},
{id: 'c', hide_if_empty: true, stats: {node_count: 0, filtered_nodes:1}},
{id: 'd', hide_if_empty: false, stats: {node_count: 0, filtered_nodes:0}}
{id: 'a', hide_if_empty: true, stats: {node_count: 0, filtered_nodes: 0}},
{id: 'b', hide_if_empty: true, stats: {node_count: 1, filtered_nodes: 0}},
{id: 'c', hide_if_empty: true, stats: {node_count: 0, filtered_nodes: 1}},
{id: 'd', hide_if_empty: false, stats: {node_count: 0, filtered_nodes: 0}}
];

const res = TopologyUtils.filterHiddenTopologies(topos);
expect(res.map(t => t.id)).toEqual(['b', 'c', 'd']);
});
})
});
});
23 changes: 23 additions & 0 deletions client/app/scripts/utils/topology-utils.js
@@ -1,5 +1,28 @@
import _ from 'lodash';

/**
* Returns a cache ID based on the topologyId and optionsQuery
* @param {String} topologyId
* @param {object} topologyOptions (optional)
* @return {String}
*/
export function buildTopologyCacheId(topologyId, topologyOptions) {
let id = '';
if (topologyId) {
id = topologyId;
if (topologyOptions) {
id += JSON.stringify(topologyOptions);
}
}
return id;
}

/**
* Returns a topology object from the topology tree
* @param {List} subTree
* @param {String} topologyId
* @return {Map} topology if found
*/
export function findTopologyById(subTree, topologyId) {
let foundTopology;

Expand Down

0 comments on commit ff417c9

Please sign in to comment.