diff --git a/client/app/scripts/actions/app-actions.js b/client/app/scripts/actions/app-actions.js index 7fba88d65..713ff60a6 100644 --- a/client/app/scripts/actions/app-actions.js +++ b/client/app/scripts/actions/app-actions.js @@ -57,6 +57,12 @@ export function clickCloseTerminal(pipeId, closePipe) { updateRoute(); } +export function clickForceRelayout() { + AppDispatcher.dispatch({ + type: ActionTypes.CLICK_FORCE_RELAYOUT + }); +} + export function clickNode(nodeId, label, origin) { AppDispatcher.dispatch({ type: ActionTypes.CLICK_NODE, diff --git a/client/app/scripts/charts/nodes-chart.js b/client/app/scripts/charts/nodes-chart.js index 91030f316..4f16b1879 100644 --- a/client/app/scripts/charts/nodes-chart.js +++ b/client/app/scripts/charts/nodes-chart.js @@ -74,7 +74,7 @@ export default class NodesChart extends React.Component { }); } // FIXME add PureRenderMixin, Immutables, and move the following functions to render() - if (nextProps.nodes !== this.props.nodes) { + if (nextProps.forceRelayout || nextProps.nodes !== this.props.nodes) { _.assign(state, this.updateGraphState(nextProps, state)); } if (this.props.selectedNodeId !== nextProps.selectedNodeId) { @@ -411,6 +411,7 @@ export default class NodesChart extends React.Component { height: props.height, scale: nodeScale, margins: MARGINS, + forceRelayout: props.forceRelayout, topologyId: this.props.topologyId }; diff --git a/client/app/scripts/charts/nodes-layout.js b/client/app/scripts/charts/nodes-layout.js index ccad2042a..05daf643c 100644 --- a/client/app/scripts/charts/nodes-layout.js +++ b/client/app/scripts/charts/nodes-layout.js @@ -361,7 +361,7 @@ export function doLayout(immNodes, immEdges, opts) { let layout; ++layoutRuns; - if (cachedLayout && nodeCache && edgeCache && !hasUnseenNodes(immNodes, nodeCache)) { + if (!options.forceRelayout && cachedLayout && nodeCache && edgeCache && !hasUnseenNodes(immNodes, nodeCache)) { log('skip layout, trivial adjustment', ++layoutRunsTrivial, layoutRuns); layout = cloneLayout(cachedLayout, immNodes, immEdges); // copy old properties, works also if nodes get re-added diff --git a/client/app/scripts/components/app.js b/client/app/scripts/components/app.js index 7b57180e6..9a796b5a1 100644 --- a/client/app/scripts/components/app.js +++ b/client/app/scripts/components/app.js @@ -7,7 +7,7 @@ import Status from './status.js'; import Topologies from './topologies.js'; import TopologyOptions from './topology-options.js'; import { getApiDetails, getTopologies, basePathSlash } from '../utils/web-api-utils'; -import { hitEsc } from '../actions/app-actions'; +import { clickForceRelayout, hitEsc } from '../actions/app-actions'; import Details from './details'; import Nodes from './nodes'; import EmbeddedTerminal from './embedded-terminal'; @@ -25,6 +25,7 @@ function getStateFromStores() { currentTopologyId: AppStore.getCurrentTopologyId(), currentTopologyOptions: AppStore.getCurrentTopologyOptions(), errorUrl: AppStore.getErrorUrl(), + forceRelayout: AppStore.isForceRelayout(), highlightedEdgeIds: AppStore.getHighlightedEdgeIds(), highlightedNodeIds: AppStore.getHighlightedNodeIds(), hostname: AppStore.getHostname(), @@ -79,6 +80,8 @@ export default class App extends React.Component { // link url to switch contrast with current UI state const otherContrastModeUrl = contrastMode ? basePathSlash(window.location.pathname) : 'contrast.html'; const otherContrastModeTitle = contrastMode ? 'Switch to normal contrast' : 'Switch to high contrast'; + const forceRelayoutClassName = 'footer-label footer-label-icon'; + const forceRelayoutTitle = 'Force re-layout (might reduce edge crossings, but may shift nodes around)'; return (