diff --git a/client/app/scripts/charts/nodes-chart.js b/client/app/scripts/charts/nodes-chart.js index c48cb2c4b2..e200efe1fa 100644 --- a/client/app/scripts/charts/nodes-chart.js +++ b/client/app/scripts/charts/nodes-chart.js @@ -64,8 +64,6 @@ class NodesChart extends React.Component { function mapStateToProps(state) { return { selectedNodeId: state.get('selectedNodeId'), - layoutZoomState: graphZoomStateSelector(state), - layoutZoomLimits: graphZoomLimitsSelector(state), }; } diff --git a/client/app/scripts/components/zoom-control.js b/client/app/scripts/components/zoom-control.js new file mode 100644 index 0000000000..f653f4d241 --- /dev/null +++ b/client/app/scripts/components/zoom-control.js @@ -0,0 +1,65 @@ +import React from 'react'; +import Slider from 'rc-slider'; +import { scaleLog } from 'd3-scale'; + + +const SLIDER_STEP = 0.001; +const CLICK_STEP = 0.05; + +// Returns a log-scale that maps zoom factors to slider values. +const getSliderScale = ({ minScale, maxScale }) => ( + scaleLog() + // Zoom limits may vary between different views. + .domain([minScale, maxScale]) + // Taking the unit range for the slider ensures consistency + // of the zoom button steps across different zoom domains. + .range([0, 1]) + // This makes sure the input values are always clamped into the valid domain/range. + .clamp(true) +); + +export default class ZoomControl extends React.Component { + constructor(props, context) { + super(props, context); + + this.handleChange = this.handleChange.bind(this); + this.handleZoomOut = this.handleZoomOut.bind(this); + this.handleZoomIn = this.handleZoomIn.bind(this); + this.getSliderValue = this.getSliderValue.bind(this); + this.toZoomScale = this.toZoomScale.bind(this); + } + + handleChange(sliderValue) { + this.props.zoomAction(this.toZoomScale(sliderValue)); + } + + handleZoomOut() { + this.props.zoomAction(this.toZoomScale(this.getSliderValue() - CLICK_STEP)); + } + + handleZoomIn() { + this.props.zoomAction(this.toZoomScale(this.getSliderValue() + CLICK_STEP)); + } + + getSliderValue() { + const toSliderValue = getSliderScale(this.props); + return toSliderValue(this.props.scale); + } + + toZoomScale(sliderValue) { + const toSliderValue = getSliderScale(this.props); + return toSliderValue.invert(sliderValue); + } + + render() { + const value = this.getSliderValue(); + + return ( +
+ + + +
+ ); + } +} diff --git a/client/app/scripts/components/zoom-indicator.js b/client/app/scripts/components/zoom-indicator.js deleted file mode 100644 index a99ed2a8e3..0000000000 --- a/client/app/scripts/components/zoom-indicator.js +++ /dev/null @@ -1,45 +0,0 @@ -import React from 'react'; -import Slider from 'rc-slider'; - - -export default class ZoomIndicator extends React.Component { - constructor(props, context) { - super(props, context); - - this.handleChange = this.handleChange.bind(this); - this.handleZoomOut = this.handleZoomOut.bind(this); - this.handleZoomIn = this.handleZoomIn.bind(this); - } - - handleChange(value) { - this.props.slideAction(Math.exp(value) * this.props.minScale); - } - - handleZoomOut() { - const newValue = Math.max(0, this.value - (0.05 * this.max)); - this.props.slideAction(Math.exp(newValue) * this.props.minScale); - } - - handleZoomIn() { - const newValue = Math.min(this.max, this.value + (0.05 * this.max)); - this.props.slideAction(Math.exp(newValue) * this.props.minScale); - } - - render() { - const { minScale, maxScale, scale } = this.props; - const max = Math.log(maxScale / minScale); - const value = Math.log(scale / minScale); - this.value = value; - this.max = max; - - return ( -
- - - - - -
- ); - } -} diff --git a/client/app/scripts/components/zoomable-canvas.js b/client/app/scripts/components/zoomable-canvas.js index b39e2f3375..7eff294afc 100644 --- a/client/app/scripts/components/zoomable-canvas.js +++ b/client/app/scripts/components/zoomable-canvas.js @@ -7,7 +7,7 @@ import { event as d3Event, select } from 'd3-selection'; import { zoom, zoomIdentity } from 'd3-zoom'; import Logo from '../components/logo'; -import ZoomIndicator from '../components/zoom-indicator'; +import ZoomControl from '../components/zoom-control'; import { cacheZoomState } from '../actions/app-actions'; import { transformToString } from '../utils/transform-utils'; import { activeTopologyZoomCacheKeyPathSelector } from '../selectors/zooming'; @@ -38,8 +38,8 @@ class ZoomableCanvas extends React.Component { }; this.debouncedCacheZoom = debounce(this.cacheZoom.bind(this), ZOOM_CACHE_DEBOUNCE_INTERVAL); + this.handleZoomControlAction = this.handleZoomControlAction.bind(this); this.canChangeZoom = this.canChangeZoom.bind(this); - this.handleSlide = this.handleSlide.bind(this); this.zoomed = this.zoomed.bind(this); } @@ -81,15 +81,15 @@ class ZoomableCanvas extends React.Component { } } - handleSlide(scale) { - const updatedState = this.cachableState({ - scaleX: scale, - scaleY: scale, - }); - + handleZoomControlAction(scale) { + // Update the canvas scale (not touching the translation). this.svg.call(this.zoom.scaleTo, scale); - this.setState(updatedState); + // Update the scale state and propagate to the global cache. + this.setState(this.cachableState({ + scaleX: scale, + scaleY: scale, + })); this.debouncedCacheZoom(); } @@ -109,8 +109,8 @@ class ZoomableCanvas extends React.Component { {forwardTransform ? children(this.state) : children} - {this.canChangeZoom() &&