From 4b7471b1b01fd7f6fbef5c92c687c95d1dfd8f00 Mon Sep 17 00:00:00 2001 From: Simon Howe Date: Tue, 6 Sep 2016 12:31:01 +0200 Subject: [PATCH] things working again, on the way to reselect! --- .../scripts/charts/nodes-chart-elements.js | 19 +++- client/app/scripts/charts/nodes-chart.js | 98 +++++++------------ client/app/scripts/components/nodes.js | 5 +- .../app/scripts/selectors/chartSelectors.js | 61 ++++++++++++ client/package.json | 1 + 5 files changed, 117 insertions(+), 67 deletions(-) create mode 100644 client/app/scripts/selectors/chartSelectors.js diff --git a/client/app/scripts/charts/nodes-chart-elements.js b/client/app/scripts/charts/nodes-chart-elements.js index 7d921f0a8..19b1aa996 100644 --- a/client/app/scripts/charts/nodes-chart-elements.js +++ b/client/app/scripts/charts/nodes-chart-elements.js @@ -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 ( - - ); } } -export default connect()(NodesChartElements); +function mapStateToProps(state, props) { + return { + completeNodes: completeNodesSelector(state, props) + }; +} + +export default connect(mapStateToProps)(NodesChartElements); diff --git a/client/app/scripts/charts/nodes-chart.js b/client/app/scripts/charts/nodes-chart.js index fdce656aa..ca36943af 100644 --- a/client/app/scripts/charts/nodes-chart.js +++ b/client/app/scripts/charts/nodes-chart.js @@ -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 (
@@ -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 } diff --git a/client/app/scripts/components/nodes.js b/client/app/scripts/components/nodes.js index e9632af3c..c96a9f22e 100644 --- a/client/app/scripts/components/nodes.js +++ b/client/app/scripts/components/nodes.js @@ -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 (
@@ -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'), diff --git a/client/app/scripts/selectors/chartSelectors.js b/client/app/scripts/selectors/chartSelectors.js new file mode 100644 index 000000000..9ca929b53 --- /dev/null +++ b/client/app/scripts/selectors/chartSelectors.js @@ -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); + } +); diff --git a/client/package.json b/client/package.json index f2d2a888e..207f383e1 100644 --- a/client/package.json +++ b/client/package.json @@ -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" },