api/api.zoom.js

/**
 * Copyright (c) 2017 ~ present NAVER Corp.
 * billboard.js project is licensed under the MIT license
 */
import {
	min as d3Min,
	max as d3Max
} from "d3-array";
import {zoomIdentity as d3ZoomIdentity, zoomTransform as d3ZoomTransform} from "d3-zoom";
import Chart from "../internals/Chart";
import CLASS from "../config/classes";
import {callFn, isDefined, isObject, isString, extend} from "../internals/util";

/**
 * Zoom by giving x domain.
 * - **NOTE:** For `wheel` type zoom, the minimum zoom range will be set as the given domain.<br>
 * To get the initial state, [.unzoom()](#unzoom) should be called.
 * @method zoom
 * @instance
 * @memberof Chart
 * @param {Array} domainValue If domain is given, the chart will be zoomed to the given domain. If no argument is given, the current zoomed domain will be returned.
 * @return {Array} domain value in array
 * @example
 *  // Zoom to specified domain
 *  chart.zoom([10, 20]);
 *
 *  // Get the current zoomed domain
 *  chart.zoom();
 */
const zoom = function(domainValue) {
	const $$ = this.internal;
	let domain = domainValue;
	let resultDomain;

	if ($$.config.zoom_enabled && domain) {
		const isTimeSeries = $$.isTimeSeries();

		if (isTimeSeries) {
			domain = domain.map(x => $$.parseDate(x));
		}

		if ($$.config.subchart_show) {
			const xScale = $$.zoomScale || $$.x;

			$$.brush.getSelection().call($$.brush.move, [xScale(domain[0]), xScale(domain[1])]);
			resultDomain = domain;
		} else {
			$$.x.domain(domain);
			$$.zoomScale = $$.x;
			$$.xAxis.scale($$.zoomScale);

			resultDomain = $$.zoomScale.orgDomain();
		}

		$$.redraw({
			withTransition: true,
			withY: $$.config.zoom_rescale,
			withDimension: false
		});

		$$.setZoomResetButton();
		callFn($$.config.zoom_onzoom, this, $$.x.orgDomain());
	} else {
		resultDomain = $$.zoomScale ?
			$$.zoomScale.domain() : $$.x.orgDomain();
	}

	return resultDomain;
};

extend(zoom, {
	/**
	 * Enable and disable zooming.
	 * @method zoom․enable
	 * @instance
	 * @memberof Chart
	 * @param {String|Boolean} enabled Possible string values are "wheel" or "drag". If enabled is true, "wheel" will be used. If false is given, zooming will be disabled.<br>When set to false, the current zooming status will be reset.
	 * @example
	 *  // Enable zooming using the mouse wheel
	 *  chart.zoom.enable(true);
	 *  // Or
	 *  chart.zoom.enable("wheel");
	 *
	 *  // Enable zooming by dragging
	 *  chart.zoom.enable("drag");
	 *
	 *  // Disable zooming
	 *  chart.zoom.enable(false);
	 */
	enable: function(enabled = "wheel") {
		const $$ = this.internal;
		const config = $$.config;
		let enableType = enabled;

		if (enabled) {
			enableType = isString(enabled) && /^(drag|wheel)$/.test(enabled) ?
				{type: enabled} : enabled;
		}

		config.zoom_enabled = enableType;

		if (!$$.zoom) {
			$$.initZoom();
			$$.initZoomBehaviour();
			$$.bindZoomEvent();
		} else if (enabled === false) {
			$$.bindZoomEvent(false);
		}

		$$.updateAndRedraw();
	},

	/**
	 * Set or get x Axis maximum zoom range value
	 * @method zoom․max
	 * @instance
	 * @memberof Chart
	 * @param {Number} [max] maximum value to set for zoom
	 * @return {Number} zoom max value
	 * @example
	 *  // Set maximum range value
	 *  chart.zoom.max(20);
	 */
	max: function(max) {
		const $$ = this.internal;
		const config = $$.config;

		if (max === 0 || max) {
			config.zoom_x_max = d3Max([$$.orgXDomain[1], max]);
		}

		return config.zoom_x_max;
	},

	/**
	 * Set or get x Axis minimum zoom range value
	 * @method zoom․min
	 * @instance
	 * @memberof Chart
	 * @param {Number} [min] minimum value tp set for zoom
	 * @return {Number} zoom min value
	 * @example
	 *  // Set minimum range value
	 *  chart.zoom.min(-1);
	 */
	min: function(min) {
		const $$ = this.internal;
		const config = $$.config;

		if (min === 0 || min) {
			config.zoom_x_min = d3Min([$$.orgXDomain[0], min]);
		}

		return config.zoom_x_min;
	},

	/**
	 * Set zoom range
	 * @method zoom․range
	 * @instance
	 * @memberof Chart
	 * @param {Object} [range]
	 * @return {Object} zoom range value
	 * {
	 *   min: 0,
	 *   max: 100
	 * }
	 * @example
	 *  chart.zoom.range({
	 *      min: 10,
	 *      max: 100
	 *  });
	 */
	range: function(range) {
		const zoom = this.zoom;

		if (isObject(range)) {
			const {min, max} = range;

			isDefined(min) && zoom.min(min);
			isDefined(max) && zoom.max(max);
		}

		return {
			min: zoom.min(),
			max: zoom.max()
		};
	}
});

extend(Chart.prototype, {
	zoom,

	/**
	 * Unzoom zoomed area
	 * @method unzoom
	 * @instance
	 * @memberof Chart
	 * @example
	 *  chart.unzoom();
	 */
	unzoom() {
		const $$ = this.internal;
		const config = $$.config;

		if ($$.zoomScale) {
			config.subchart_show ?
				$$.brush.getSelection().call($$.brush.move, null) :
				$$.zoom.updateTransformScale(d3ZoomIdentity);

			$$.updateZoom(true);
			$$.zoom.resetBtn && $$.zoom.resetBtn.style("display", "none");

			// reset transform
			const eventRects = $$.main.select(`.${CLASS.eventRects}`);

			if (d3ZoomTransform(eventRects.node()) !== d3ZoomIdentity) {
				$$.zoom.transform(eventRects, d3ZoomIdentity);
			}

			$$.redraw({
				withTransition: true,
				withY: config.zoom_rescale
			});
		}
	}
});