Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cache pan/zoom per topology #1261

Merged
merged 1 commit into from Apr 11, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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