things working again, on the way to reselect!

This commit is contained in:
Simon Howe
2016-09-06 12:31:01 +02:00
parent 9ce399607a
commit 4b7471b1b0
5 changed files with 117 additions and 67 deletions

View File

@@ -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);

View File

@@ -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 }

View File

@@ -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'),

View 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);
}
);

View File

@@ -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"
},