From 11ce5638c52ac87c905c7c993c5074f84f752212 Mon Sep 17 00:00:00 2001 From: Filip Barl Date: Thu, 23 Feb 2017 15:47:01 +0100 Subject: [PATCH 1/2] Moved nodes-chart-layout. Moved nodes-chart-zoom. Moved zoomCache to global state. Moved nodes-chart-focus. Fixed some bugs and polished the code. Keeping track of topology options in zoomCache. Fixed forceRelayout and circular layout. Unified graph complexity heuristic criterion. --- client/app/scripts/actions/app-actions.js | 16 +- .../scripts/charts/nodes-chart-elements.js | 27 ++- client/app/scripts/charts/nodes-chart.js | 138 +++++------- client/app/scripts/charts/nodes-grid.js | 12 +- client/app/scripts/components/nodes.js | 28 +-- client/app/scripts/constants/action-types.js | 2 + client/app/scripts/constants/styles.js | 2 +- client/app/scripts/constants/timer.js | 3 + client/app/scripts/reducers/root.js | 31 ++- .../app/scripts/selectors/canvas-viewport.js | 48 ++++ .../scripts/selectors/nodes-chart-focus.js | 157 ------------- .../scripts/selectors/nodes-chart-graph.js | 76 +++++++ .../scripts/selectors/nodes-chart-layout.js | 213 +++++++++++++----- .../app/scripts/selectors/nodes-chart-zoom.js | 56 ++--- client/app/scripts/selectors/topology.js | 13 ++ client/app/scripts/utils/topology-utils.js | 11 +- 16 files changed, 466 insertions(+), 367 deletions(-) create mode 100644 client/app/scripts/selectors/canvas-viewport.js delete mode 100644 client/app/scripts/selectors/nodes-chart-focus.js create mode 100644 client/app/scripts/selectors/nodes-chart-graph.js create mode 100644 client/app/scripts/selectors/topology.js diff --git a/client/app/scripts/actions/app-actions.js b/client/app/scripts/actions/app-actions.js index d4b88f702..a5a9024ee 100644 --- a/client/app/scripts/actions/app-actions.js +++ b/client/app/scripts/actions/app-actions.js @@ -237,6 +237,12 @@ export function clickForceRelayout() { }; } +export function setViewportDimensions(width, height) { + return (dispatch) => { + dispatch({ type: ActionTypes.SET_VIEWPORT_DIMENSIONS, width, height }); + }; +} + export function toggleGridMode(enabledArgument) { return (dispatch, getState) => { const enabled = (enabledArgument === undefined) ? @@ -247,9 +253,6 @@ export function toggleGridMode(enabledArgument) { enabled }); updateRoute(getState); - if (!enabled) { - dispatch(clickForceRelayout()); - } }; } @@ -346,6 +349,13 @@ export function clickTopology(topologyId) { }; } +export function cacheZoomState(zoomState) { + return { + type: ActionTypes.CACHE_ZOOM_STATE, + zoomState + }; +} + export function openWebsocket() { return { type: ActionTypes.OPEN_WEBSOCKET diff --git a/client/app/scripts/charts/nodes-chart-elements.js b/client/app/scripts/charts/nodes-chart-elements.js index fdd65823a..44f71e422 100644 --- a/client/app/scripts/charts/nodes-chart-elements.js +++ b/client/app/scripts/charts/nodes-chart-elements.js @@ -1,11 +1,20 @@ import React from 'react'; +import { connect } from 'react-redux'; import NodesChartEdges from './nodes-chart-edges'; import NodesChartNodes from './nodes-chart-nodes'; +import { graphExceedsComplexityThreshSelector } from '../selectors/topology'; +import { + selectedScaleSelector, + layoutNodesSelector, + layoutEdgesSelector +} from '../selectors/nodes-chart-layout'; -export default class NodesChartElements extends React.PureComponent { + +class NodesChartElements extends React.Component { render() { - const { transform, layoutEdges, layoutNodes, selectedScale, isAnimated } = this.props; + const { layoutNodes, layoutEdges, selectedScale, transform, isAnimated } = this.props; + return ( @@ -115,17 +93,33 @@ class NodesChart extends React.Component { - + ); } + cacheZoom() { + const zoomState = pick(this.state, ZOOM_CACHE_FIELDS); + this.props.cacheZoomState(fromJS(zoomState)); + } + + restoreCachedZoom(props) { + if (!props.layoutZoom.isEmpty()) { + const zoomState = props.layoutZoom.toJS(); + + // Restore the zooming settings + this.zoom = this.zoom.scaleExtent([zoomState.minZoomScale, zoomState.maxZoomScale]); + this.svg.call(this.zoom.transform, zoomIdentity + .translate(zoomState.panTranslateX, zoomState.panTranslateY) + .scale(zoomState.zoomScale)); + + // Update the state variables + this.setState(zoomState); + this.zoomRestored = true; + } + } + handleMouseClick() { if (!this.isZooming || this.props.selectedNodeId) { this.props.clickBackground(); @@ -134,37 +128,16 @@ class NodesChart extends React.Component { } } - isTopologyGraphComplex() { - return this.state.layoutNodes.size > GRAPH_COMPLEXITY_NODES_TRESHOLD; - } - - cacheZoomState(state) { - const zoomState = pick(state, ZOOM_CACHE_FIELDS); - const zoomCache = assign({}, state.zoomCache); - zoomCache[zoomCacheKey(this.props)] = zoomState; - return { zoomCache }; - } - - applyZoomState({ zoomScale, minZoomScale, maxZoomScale, panTranslateX, panTranslateY }) { - this.zoom = this.zoom.scaleExtent([minZoomScale, maxZoomScale]); - this.svg.call(this.zoom.transform, zoomIdentity - .translate(panTranslateX, panTranslateY) - .scale(zoomScale)); - } - zoomed() { this.isZooming = true; // don't pan while node is selected if (!this.props.selectedNodeId) { - let state = assign({}, this.state, { + this.setState({ panTranslateX: d3Event.transform.x, panTranslateY: d3Event.transform.y, zoomScale: d3Event.transform.k }); - // Cache the zoom state as soon as it changes as it is cheap, and makes us - // be able to skip difficult conditions on when this caching should happen. - state = assign(state, this.cacheZoomState(state)); - this.setState(state); + this.debouncedCacheZoom(); } } } @@ -172,16 +145,15 @@ class NodesChart extends React.Component { function mapStateToProps(state) { return { - nodes: shownNodesSelector(state), - forceRelayout: state.get('forceRelayout'), + layoutZoom: activeLayoutZoomSelector(state), + layoutId: JSON.stringify(activeTopologyZoomCacheKeyPath(state)), selectedNodeId: state.get('selectedNodeId'), - topologyId: state.get('currentTopologyId'), - topologyOptions: getActiveTopologyOptions(state), + forceRelayout: state.get('forceRelayout'), }; } export default connect( mapStateToProps, - { clickBackground } + { clickBackground, cacheZoomState } )(NodesChart); diff --git a/client/app/scripts/charts/nodes-grid.js b/client/app/scripts/charts/nodes-grid.js index cfd9cd037..1512d142d 100644 --- a/client/app/scripts/charts/nodes-grid.js +++ b/client/app/scripts/charts/nodes-grid.js @@ -7,6 +7,7 @@ import NodeDetailsTable from '../components/node-details/node-details-table'; import { clickNode, sortOrderChanged } from '../actions/app-actions'; import { shownNodesSelector } from '../selectors/node-filters'; +import { CANVAS_MARGINS } from '../constants/styles'; import { searchNodeMatchesSelector } from '../selectors/search'; import { getNodeColor } from '../utils/color-utils'; @@ -96,13 +97,13 @@ class NodesGrid extends React.Component { } render() { - const { margins, nodes, height, gridSortedBy, gridSortedDesc, + const { nodes, height, gridSortedBy, gridSortedDesc, searchNodeMatches, searchQuery } = this.props; const cmpStyle = { height, - marginTop: margins.top, - paddingLeft: margins.left, - paddingRight: margins.right, + marginTop: CANVAS_MARGINS.top, + paddingLeft: CANVAS_MARGINS.left, + paddingRight: CANVAS_MARGINS.right, }; const tbodyHeight = height - 24 - 18; const className = 'scroll-body'; @@ -151,7 +152,8 @@ function mapStateToProps(state) { currentTopologyId: state.get('currentTopologyId'), searchNodeMatches: searchNodeMatchesSelector(state), searchQuery: state.get('searchQuery'), - selectedNodeId: state.get('selectedNodeId') + selectedNodeId: state.get('selectedNodeId'), + height: state.getIn(['viewport', 'height']), }; } diff --git a/client/app/scripts/components/nodes.js b/client/app/scripts/components/nodes.js index 380d168cc..ecca7733a 100644 --- a/client/app/scripts/components/nodes.js +++ b/client/app/scripts/components/nodes.js @@ -1,5 +1,6 @@ import React from 'react'; import { connect } from 'react-redux'; +import { debounce } from 'lodash'; import NodesChart from '../charts/nodes-chart'; import NodesGrid from '../charts/nodes-grid'; @@ -7,12 +8,13 @@ import NodesError from '../charts/nodes-error'; import DelayedShow from '../utils/delayed-show'; import { Loading, getNodeType } from './loading'; import { isTopologyEmpty } from '../utils/topology-utils'; -import { CANVAS_MARGINS } from '../constants/styles'; +import { setViewportDimensions } from '../actions/app-actions'; +import { VIEWPORT_RESIZE_DEBOUNCE_INTERVAL } from '../constants/timer'; + const navbarHeight = 194; const marginTop = 0; - const EmptyTopologyError = show => (