mirror of
https://github.com/weaveworks/scope.git
synced 2026-03-03 02:00:43 +00:00
things working again, on the way to reselect!
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { completeNodesSelector } from '../selectors/chartSelectors';
|
||||
import NodesChartEdges from './nodes-chart-edges';
|
||||
import NodesChartNodes from './nodes-chart-nodes';
|
||||
|
||||
@@ -9,14 +10,24 @@ class NodesChartElements extends React.Component {
|
||||
const props = this.props;
|
||||
return (
|
||||
<g className="nodes-chart-elements" transform={props.transform}>
|
||||
<NodesChartEdges layoutEdges={props.layoutEdges}
|
||||
<NodesChartEdges
|
||||
layoutEdges={props.layoutEdges}
|
||||
layoutPrecision={props.layoutPrecision} />
|
||||
<NodesChartNodes layoutNodes={props.layoutNodes} nodeScale={props.nodeScale}
|
||||
scale={props.scale} selectedNodeScale={props.selectedNodeScale}
|
||||
<NodesChartNodes
|
||||
layoutNodes={props.completeNodes}
|
||||
nodeScale={props.nodeScale}
|
||||
scale={props.scale}
|
||||
selectedNodeScale={props.selectedNodeScale}
|
||||
layoutPrecision={props.layoutPrecision} />
|
||||
</g>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect()(NodesChartElements);
|
||||
function mapStateToProps(state, props) {
|
||||
return {
|
||||
completeNodes: completeNodesSelector(state, props)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(NodesChartElements);
|
||||
|
||||
@@ -24,22 +24,6 @@ const radiusDensity = d3.scale.threshold()
|
||||
.range([2.5, 3.5, 3]);
|
||||
|
||||
|
||||
function toNodes(nodes) {
|
||||
return nodes.map((node, id) => makeMap({
|
||||
id,
|
||||
label: node.get('label'),
|
||||
pseudo: node.get('pseudo'),
|
||||
subLabel: node.get('label_minor'),
|
||||
nodeCount: node.get('node_count'),
|
||||
metrics: node.get('metrics'),
|
||||
rank: node.get('rank'),
|
||||
shape: node.get('shape'),
|
||||
stack: node.get('stack'),
|
||||
networks: node.get('networks'),
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
function identityPresevingMerge(a, b) {
|
||||
//
|
||||
// merge two maps, if the values are equal return the old value to preserve (a === a')
|
||||
@@ -51,14 +35,6 @@ function identityPresevingMerge(a, b) {
|
||||
}
|
||||
|
||||
|
||||
function mergeDeepIfExists(mapA, mapB) {
|
||||
//
|
||||
// Does a deep merge on any key that exists in the first map
|
||||
//
|
||||
return mapA.map((v, k) => v.mergeDeep(mapB.get(k)));
|
||||
}
|
||||
|
||||
|
||||
function getLayoutNodes(nodes) {
|
||||
return nodes.map(n => makeMap({
|
||||
id: n.get('id'),
|
||||
@@ -97,12 +73,13 @@ function initEdges(nodes) {
|
||||
}
|
||||
|
||||
|
||||
function getNodeScale(nodes, width, height) {
|
||||
function getNodeScale(nodesCount, width, height) {
|
||||
console.log(nodesCount, width, height);
|
||||
const expanse = Math.min(height, width);
|
||||
const nodeSize = expanse / 3; // single node should fill a third of the screen
|
||||
const maxNodeSize = Math.min(MAX_NODE_SIZE, expanse / 10);
|
||||
const normalizedNodeSize = Math.max(MIN_NODE_SIZE,
|
||||
Math.min(nodeSize / Math.sqrt(nodes.size), maxNodeSize));
|
||||
Math.min(nodeSize / Math.sqrt(nodesCount), maxNodeSize));
|
||||
|
||||
return d3.scale.linear().range([0, normalizedNodeSize]);
|
||||
}
|
||||
@@ -110,7 +87,7 @@ function getNodeScale(nodes, width, height) {
|
||||
|
||||
function updateLayout({width, height, margins, topologyId, topologyOptions, forceRelayout,
|
||||
nodes }) {
|
||||
const nodeScale = getNodeScale(nodes, width, height);
|
||||
const nodeScale = getNodeScale(nodes.size, width, height);
|
||||
const edges = initEdges(nodes);
|
||||
|
||||
const options = {
|
||||
@@ -119,7 +96,7 @@ function updateLayout({width, height, margins, topologyId, topologyOptions, forc
|
||||
margins: margins.toJS(),
|
||||
forceRelayout,
|
||||
topologyId,
|
||||
topologyOptions: topologyOptions.toJS(),
|
||||
topologyOptions: (topologyOptions && topologyOptions.toJS()),
|
||||
scale: nodeScale,
|
||||
};
|
||||
|
||||
@@ -128,20 +105,21 @@ function updateLayout({width, height, margins, topologyId, topologyOptions, forc
|
||||
|
||||
log(`graph layout took ${timedLayouter.time}ms`);
|
||||
|
||||
// extract coords and save for restore
|
||||
const layoutNodes = graph.nodes.map(node => makeMap({
|
||||
x: node.get('x'),
|
||||
px: node.get('x'),
|
||||
y: node.get('y'),
|
||||
// extract coords and save for restore
|
||||
px: node.get('x'),
|
||||
py: node.get('y')
|
||||
}));
|
||||
|
||||
const layoutEdges = graph.edges
|
||||
.map(edge => edge.set('ppoints', edge.get('points')));
|
||||
|
||||
return { layoutNodes, layoutEdges, graph, nodeScale };
|
||||
return { layoutNodes, layoutEdges, width: graph.width, height: graph.height };
|
||||
}
|
||||
|
||||
|
||||
class NodesChart extends React.Component {
|
||||
|
||||
constructor(props, context) {
|
||||
@@ -244,6 +222,7 @@ class NodesChart extends React.Component {
|
||||
const translate = [panTranslateX, panTranslateY];
|
||||
const transform = `translate(${translate}) scale(${scale})`;
|
||||
const svgClassNames = this.props.isEmpty ? 'hide' : '';
|
||||
console.log('nodes-chart.render');
|
||||
|
||||
return (
|
||||
<div className="nodes-chart">
|
||||
@@ -338,7 +317,7 @@ class NodesChart extends React.Component {
|
||||
});
|
||||
|
||||
// auto-scale node size for selected nodes
|
||||
const selectedNodeScale = getNodeScale(adjacentNodes, state.width, state.height);
|
||||
const selectedNodeScale = getNodeScale(adjacentNodes.size, state.width, state.height);
|
||||
|
||||
return {
|
||||
selectedNodeScale,
|
||||
@@ -375,11 +354,6 @@ class NodesChart extends React.Component {
|
||||
};
|
||||
}
|
||||
|
||||
const nextState = { nodes: toNodes(props.nodes) };
|
||||
|
||||
//
|
||||
// pull this out into redux.
|
||||
//
|
||||
const layoutInput = identityPresevingMerge(state.layoutInput, {
|
||||
width: state.width,
|
||||
height: state.height,
|
||||
@@ -391,34 +365,32 @@ class NodesChart extends React.Component {
|
||||
});
|
||||
|
||||
// layout input hasn't changed.
|
||||
// TODO: move this out into reselect
|
||||
if (state.layoutInput !== layoutInput) {
|
||||
nextState.layoutInput = layoutInput;
|
||||
|
||||
const { layoutNodes, layoutEdges, graph, nodeScale } = updateLayout(layoutInput.toObject());
|
||||
//
|
||||
// adjust layout based on viewport
|
||||
const xFactor = (state.width - props.margins.left - props.margins.right) / graph.width;
|
||||
const yFactor = state.height / graph.height;
|
||||
const zoomFactor = Math.min(xFactor, yFactor);
|
||||
let zoomScale = state.scale;
|
||||
|
||||
if (this.zoom && !state.hasZoomed && zoomFactor > 0 && zoomFactor < 1) {
|
||||
zoomScale = zoomFactor;
|
||||
// saving in d3's behavior cache
|
||||
this.zoom.scale(zoomFactor);
|
||||
}
|
||||
|
||||
nextState.scale = zoomScale;
|
||||
nextState.edges = layoutEdges;
|
||||
nextState.nodeScale = nodeScale;
|
||||
nextState.layoutNodes = layoutNodes;
|
||||
// TODO: move this out into reselect (relies on `state` a bit right now which makes it tricky)
|
||||
if (state.layoutInput === layoutInput) {
|
||||
return {};
|
||||
}
|
||||
|
||||
nextState.nodes = mergeDeepIfExists(nextState.nodes,
|
||||
(nextState.layoutNodes || state.layoutNodes));
|
||||
const { layoutNodes, layoutEdges, width, height } = updateLayout(layoutInput.toObject());
|
||||
//
|
||||
// adjust layout based on viewport
|
||||
const xFactor = (state.width - props.margins.left - props.margins.right) / width;
|
||||
const yFactor = state.height / height;
|
||||
const zoomFactor = Math.min(xFactor, yFactor);
|
||||
let zoomScale = state.scale;
|
||||
|
||||
return nextState;
|
||||
if (this.zoom && !state.hasZoomed && zoomFactor > 0 && zoomFactor < 1) {
|
||||
zoomScale = zoomFactor;
|
||||
// saving in d3's behavior cache
|
||||
this.zoom.scale(zoomFactor);
|
||||
}
|
||||
|
||||
return {
|
||||
layoutInput,
|
||||
scale: zoomScale,
|
||||
nodes: layoutNodes,
|
||||
edges: layoutEdges,
|
||||
nodeScale: getNodeScale(props.nodes.size, state.width, state.height),
|
||||
};
|
||||
}
|
||||
|
||||
zoomed() {
|
||||
@@ -436,6 +408,7 @@ class NodesChart extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
adjacentNodes: getAdjacentNodes(state),
|
||||
@@ -446,6 +419,7 @@ function mapStateToProps(state) {
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
{ clickBackground }
|
||||
|
||||
@@ -8,6 +8,7 @@ import { DelayedShow } from '../utils/delayed-show';
|
||||
import { Loading, getNodeType } from './loading';
|
||||
import { isTopologyEmpty } from '../utils/topology-utils';
|
||||
import { CANVAS_MARGINS } from '../constants/styles';
|
||||
import { nodesSelector } from '../selectors/chartSelectors';
|
||||
|
||||
const navbarHeight = 194;
|
||||
const marginTop = 0;
|
||||
@@ -71,6 +72,8 @@ class Nodes extends React.Component {
|
||||
currentTopology } = this.props;
|
||||
const layoutPrecision = getLayoutPrecision(nodes.size);
|
||||
|
||||
console.log('nodes.render');
|
||||
|
||||
return (
|
||||
<div className="nodes-wrapper">
|
||||
<DelayedShow delay={1000} show={!topologiesLoaded || (topologiesLoaded && !nodesLoaded)}>
|
||||
@@ -113,7 +116,7 @@ function mapStateToProps(state) {
|
||||
return {
|
||||
currentTopology: state.get('currentTopology'),
|
||||
gridMode: state.get('gridMode'),
|
||||
nodes: state.get('nodes').filter(node => !node.get('filtered')),
|
||||
nodes: nodesSelector(state),
|
||||
nodesLoaded: state.get('nodesLoaded'),
|
||||
topologies: state.get('topologies'),
|
||||
topologiesLoaded: state.get('topologiesLoaded'),
|
||||
|
||||
61
client/app/scripts/selectors/chartSelectors.js
Normal file
61
client/app/scripts/selectors/chartSelectors.js
Normal file
@@ -0,0 +1,61 @@
|
||||
import { createSelector } from 'reselect';
|
||||
import { Map as makeMap } from 'immutable';
|
||||
|
||||
|
||||
const allNodesSelector = state => state.get('nodes');
|
||||
|
||||
|
||||
export const nodesSelector = createSelector(
|
||||
allNodesSelector,
|
||||
(allNodes) => allNodes.filter(node => !node.get('filtered'))
|
||||
);
|
||||
|
||||
|
||||
export const nodeAdjacenciesSelector = createSelector(
|
||||
nodesSelector,
|
||||
(nodes) => nodes.map(n => makeMap({
|
||||
id: n.get('id'),
|
||||
adjacency: n.get('adjacency'),
|
||||
}))
|
||||
);
|
||||
|
||||
|
||||
export const layoutNodesSelector = (_, props) => props.layoutNodes;
|
||||
|
||||
|
||||
export const dataNodesSelector = createSelector(
|
||||
nodesSelector,
|
||||
(nodes) => nodes.map((node, id) => makeMap({
|
||||
id,
|
||||
label: node.get('label'),
|
||||
pseudo: node.get('pseudo'),
|
||||
subLabel: node.get('label_minor'),
|
||||
nodeCount: node.get('node_count'),
|
||||
metrics: node.get('metrics'),
|
||||
rank: node.get('rank'),
|
||||
shape: node.get('shape'),
|
||||
stack: node.get('stack'),
|
||||
networks: node.get('networks'),
|
||||
}))
|
||||
);
|
||||
|
||||
|
||||
function mergeDeepIfExists(mapA, mapB) {
|
||||
//
|
||||
// Does a deep merge on any key that exists in the first map
|
||||
//
|
||||
return mapA.map((v, k) => v.mergeDeep(mapB.get(k)));
|
||||
}
|
||||
|
||||
|
||||
export const completeNodesSelector = createSelector(
|
||||
layoutNodesSelector,
|
||||
dataNodesSelector,
|
||||
(layoutNodes, dataNodes) => {
|
||||
if (!layoutNodes || layoutNodes.size === 0) {
|
||||
return makeMap();
|
||||
}
|
||||
|
||||
return mergeDeepIfExists(dataNodes, layoutNodes);
|
||||
}
|
||||
);
|
||||
@@ -29,6 +29,7 @@
|
||||
"redux-logger": "2.6.1",
|
||||
"redux-thunk": "2.0.1",
|
||||
"reqwest": "~2.0.5",
|
||||
"reselect": "^2.5.3",
|
||||
"timely": "0.1.0",
|
||||
"whatwg-fetch": "0.11.0"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user