mirror of
https://github.com/weaveworks/scope.git
synced 2026-04-05 18:26:59 +00:00
94 lines
3.0 KiB
JavaScript
94 lines
3.0 KiB
JavaScript
import debug from 'debug';
|
|
import { createSelector, createStructuredSelector } from 'reselect';
|
|
import { Map as makeMap } from 'immutable';
|
|
import timely from 'timely';
|
|
|
|
import { EDGE_ID_SEPARATOR } from '../constants/naming';
|
|
import { doLayout } from '../charts/nodes-layout';
|
|
|
|
const log = debug('scope:nodes-chart');
|
|
|
|
|
|
function initEdgesFromNodes(nodes) {
|
|
let edges = makeMap();
|
|
|
|
nodes.forEach((node, nodeId) => {
|
|
const adjacency = node.get('adjacency');
|
|
if (adjacency) {
|
|
adjacency.forEach((adjacent) => {
|
|
const edge = nodeId < adjacent ? [nodeId, adjacent] : [adjacent, nodeId];
|
|
const edgeId = edge.join(EDGE_ID_SEPARATOR);
|
|
|
|
if (!edges.has(edgeId)) {
|
|
const source = edge[0];
|
|
const target = edge[1];
|
|
if (nodes.has(source) && nodes.has(target)) {
|
|
edges = edges.set(edgeId, makeMap({
|
|
id: edgeId,
|
|
value: 1,
|
|
source,
|
|
target
|
|
}));
|
|
}
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
return edges;
|
|
}
|
|
|
|
// TODO: Make all the selectors below pure (so that they only depend on the global state).
|
|
|
|
const layoutOptionsSelector = createStructuredSelector({
|
|
width: state => state.width,
|
|
height: state => state.height,
|
|
margins: (_, props) => props.margins,
|
|
forceRelayout: (_, props) => props.forceRelayout,
|
|
topologyId: (_, props) => props.topologyId,
|
|
topologyOptions: (_, props) => props.topologyOptions,
|
|
});
|
|
|
|
export const graphLayout = createSelector(
|
|
[
|
|
// TODO: Instead of sending the nodes with all the information (metrics, metadata, etc...)
|
|
// to the layout engine, it would suffice to forward it just the nodes adjacencies map, which
|
|
// we could get with another selector like:
|
|
//
|
|
// const nodesAdjacenciesSelector = createMapSelector(
|
|
// [ (_, props) => props.nodes ],
|
|
// node => node.get('adjacency') || makeList()
|
|
// );
|
|
//
|
|
// That would enable us to use smarter caching, so that the layout doesn't get recalculated
|
|
// if adjacencies don't change but e.g. metrics gets updated. We also don't need to init
|
|
// edges here as the adjacencies data is enough to reconstruct them in the layout engine (this
|
|
// might enable us to simplify the caching system there since we really only need to cache
|
|
// the adjacencies map in that case and not nodes and edges).
|
|
(_, props) => props.nodes,
|
|
layoutOptionsSelector,
|
|
],
|
|
(nodes, options) => {
|
|
// If the graph is empty, skip computing the layout.
|
|
if (nodes.size === 0) {
|
|
return {
|
|
layoutNodes: makeMap(),
|
|
layoutEdges: makeMap(),
|
|
};
|
|
}
|
|
|
|
const edges = initEdgesFromNodes(nodes);
|
|
const timedLayouter = timely(doLayout);
|
|
const graph = timedLayouter(nodes, edges, options);
|
|
|
|
// NOTE: We probably shouldn't log anything in a
|
|
// computed property, but this is still useful.
|
|
log(`graph layout calculation took ${timedLayouter.time}ms`);
|
|
|
|
return {
|
|
layoutNodes: graph.nodes,
|
|
layoutEdges: graph.edges,
|
|
};
|
|
}
|
|
);
|