mirror of
https://github.com/weaveworks/scope.git
synced 2026-03-03 18:20:27 +00:00
Merge pull request #1261 from weaveworks/1238-cache-panning
Cache pan/zoom per topology
This commit is contained in:
@@ -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]);
|
||||
@@ -43,7 +45,8 @@ export default class NodesChart extends React.Component {
|
||||
panTranslateY: 0,
|
||||
scale: 1,
|
||||
selectedNodeScale: d3.scale.linear(),
|
||||
hasZoomed: false
|
||||
hasZoomed: false,
|
||||
zoomCache: {}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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
|
||||
@@ -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]) {
|
||||
|
||||
@@ -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']);
|
||||
});
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user