Skip to content

Commit

Permalink
feat(zoom): Add option to zoom by dragging
Browse files Browse the repository at this point in the history
- Adds an option to allow zooming by dragging on the chart instead of
using the scrollwheel.
- Add reset button when zoomed-in

Fix #416
Fix #508
Close #513

skip: add rest button
  • Loading branch information
Lennert Claeys authored and netil committed Aug 2, 2018
1 parent f3881bc commit da2ce10
Show file tree
Hide file tree
Showing 13 changed files with 474 additions and 134 deletions.
68 changes: 41 additions & 27 deletions demo/demo.js
Original file line number Diff line number Diff line change
Expand Up @@ -1584,6 +1584,20 @@ var demos = {
enabled: true
}
}
},
DragZoom: {
options: {
data: {
columns: [
["sample", 30, 200, 100, 400, 150, 250, 150, 200, 170, 240, 350, 150, 100, 400, 150, 250, 150, 200, 170, 240, 100, 150, 250, 150, 200, 170, 240, 30, 200, 100, 400, 150, 250, 150, 200, 170, 240, 350, 150, 100, 400, 350, 220, 250, 300, 270, 140, 150, 90, 150, 50, 120, 70, 40]
]
},
zoom: {
enabled: {
type: "drag"
}
}
}
}
},

Expand Down Expand Up @@ -2595,37 +2609,37 @@ d3.select(".chart_area")
done: function() {
chart.flow({
columns: [
["x", '2013-02-11', '2013-02-12', '2013-02-13', '2013-02-14'],
["data1", 200, 300, 100, 250],
["data2", 100, 90, 40, 120],
["data3", 100, 100, 300, 500]
["x", '2013-02-11', '2013-02-12', '2013-02-13', '2013-02-14'],
["data1", 200, 300, 100, 250],
["data2", 100, 90, 40, 120],
["data3", 100, 100, 300, 500]
],
length: 0,
duration: 1500,
done: function() {
chart.flow({
columns: [
["x", '2013-03-01', '2013-03-02'],
["data1", 200, 300],
["data2", 150, 250],
["data3", 100, 100]
],
length: 2,
duration: 1500,
done: function() {
chart.flow({
columns: [
["x", '2013-03-21', '2013-04-01'],
["data1", 500, 200],
["data2", 100, 150],
["data3", 200, 400]
],
to: '2013-03-01',
duration: 1500
});
}
});
}
chart.flow({
columns: [
["x", '2013-03-01', '2013-03-02'],
["data1", 200, 300],
["data2", 150, 250],
["data3", 100, 100]
],
length: 2,
duration: 1500,
done: function() {
chart.flow({
columns: [
["x", '2013-03-21', '2013-04-01'],
["data1", 500, 200],
["data2", 100, 150],
["data3", 200, 400]
],
to: '2013-03-01',
duration: 1500
});
}
});
}
});
},
});
Expand Down
100 changes: 98 additions & 2 deletions spec/interactions/zoom-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,9 @@ describe("ZOOM", function() {
]
},
zoom: {
enabled: true
enabled: {
type: "wheel"
}
}
};
});
Expand Down Expand Up @@ -129,5 +131,99 @@ describe("ZOOM", function() {
// check if chart react on resize
expect(+domain.attr("d").match(rx)[1]).to.be.above(pathValue);
});
});
});

describe("zoom type drag", () => {
before(() => {
args = {
size: {
width: 300,
height: 250
},
data: {
columns: [
["data1", 30, 200, 100, 400, 3150, 250],
["data2", 50, 20, 10, 40, 15, 6025]
]
},
zoom: {
enabled: {
type: "drag"
}
}
};
});

it("check for data zoom", () => {
const main = chart.$.main;
const xValue = +main.select(`.${CLASS.eventRect}-2`).attr("x");

// when
chart.zoom([0, 3]); // zoom in

expect(+main.select(`.${CLASS.eventRect}-2`).attr("x")).to.be.above(xValue);
});

it("check for x axis resize after zoom", () => {
const main = chart.$.main;
const rx = /H(\d+)/;

const domain = main.select(`.${CLASS.axisX} > .domain`);
const pathValue = +domain.attr("d").match(rx)[1];

chart.zoom([0, 4]);
chart.resize({ width: 400 });

expect(+domain.attr("d").match(rx)[1]).to.be.above(pathValue);
});

it("check for x axis resize after zoom in/out", () => {
const main = chart.$.main;
const rx = /H(\d+)/;

const domain = main.select(`.${CLASS.axisX} > .domain`);
const pathValue = +domain.attr("d").match(rx)[1];

chart.zoom([0, 4]); // zoom in
chart.zoom([0, 6]); // zoom out

expect(+domain.attr("d").match(rx)[1]).to.be.equal(pathValue);

// resize
chart.resize({ width: 400 });

// check if chart react on resize
expect(+domain.attr("d").match(rx)[1]).to.be.above(pathValue);
});

it("check for the reset zoom button", () => {
// when
chart.zoom([0, 4]);

const resetBtn = chart.$.chart.select(`.${CLASS.buttonZoomReset}`);

expect(resetBtn.empty()).to.be.false;

// when button is clicked
resetBtn.node().click();

expect(resetBtn.style("display")).to.be.equal("none");
});

it("set options zoom.resetButton.text='test", () => {
args.zoom.resetButton = {
text: "test"
};
});

it("check for the custom reset zoom button text", () => {
// when
chart.zoom([0, 4]);

const resetBtn = chart.$.chart.select(`.${CLASS.buttonZoomReset}`);

expect(resetBtn.empty()).to.be.false;
expect(resetBtn.text()).to.be.equal("test");
});
});
});
65 changes: 49 additions & 16 deletions src/api/api.zoom.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
} from "d3-array";
import {zoomIdentity as d3ZoomIdentity} from "d3-zoom";
import Chart from "../internals/Chart";
import {isDefined, isObject, isFunction, extend} from "../internals/util";
import {isDefined, isObject, isFunction, isString, extend} from "../internals/util";

/**
* Zoom by giving x domain.
Expand All @@ -26,11 +26,12 @@ import {isDefined, isObject, isFunction, extend} from "../internals/util";
*/
const zoom = function(domainValue) {
const $$ = this.internal;
const isTimeSeries = $$.isTimeSeries();
let domain = domainValue;
let resultDomain;

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

if (isTimeSeries) {
domain = domain.map(x => $$.parseDate(x));
}
Expand All @@ -43,8 +44,9 @@ const zoom = function(domainValue) {
} else {
const orgDomain = $$.x.orgDomain();
const k = (orgDomain[1] - orgDomain[0]) / (domain[1] - domain[0]);
const gap = $$.isCategorized() ? $$.xAxis.tickOffset() : 0;
const tx = isTimeSeries ?
(0 - k * $$.x(domain[0].getTime())) : domain[0] - k * ($$.x(domain[0]) - $$.xAxis.tickOffset());
(0 - k * $$.x(domain[0].getTime())) : domain[0] - k * ($$.x(domain[0]) - gap);

$$.zoom.updateTransformScale(
d3ZoomIdentity.translate(tx, 0).scale(k)
Expand All @@ -59,9 +61,13 @@ const zoom = function(domainValue) {
withDimension: false
});

isFunction($$.config.zoom_onzoom) && $$.config.zoom_onzoom.call(this, $$.x.orgDomain());
$$.setZoomResetButton();

isFunction($$.config.zoom_onzoom) &&
$$.config.zoom_onzoom.call(this, $$.x.orgDomain());
} else {
resultDomain = ($$.zoomScale || $$.x).domain();
resultDomain = $$.zoomScale ?
$$.zoomScale.domain() : $$.x.orgDomain();
}

return resultDomain;
Expand All @@ -73,18 +79,39 @@ extend(zoom, {
* @method zoom․enable
* @instance
* @memberOf Chart
* @param {Boolean} enabled If enabled is true, the feature of zooming will be enabled. If false is given, it will be disabled.<br>When set to false, the current zooming status will be reset.
* @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
* // 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 = 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);
}

$$.config.zoom_enabled = enabled;
$$.updateAndRedraw();
},

Expand Down Expand Up @@ -177,14 +204,20 @@ extend(Chart.prototype, {
*/
unzoom() {
const $$ = this.internal;
const config = $$.config;

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

$$.redraw({
withTransition: true,
withY: $$.config.zoom_rescale
});
$$.updateZoom();
$$.zoom.resetBtn && $$.zoom.resetBtn.style("display", "none");

$$.redraw({
withTransition: true,
withY: config.zoom_rescale
});
}
}
});
20 changes: 17 additions & 3 deletions src/config/Options.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ export default class Options {
* @memberOf Options
* @type {Object}
* @property {Boolean} [zoom.enabled=false] Enable zooming.
* @property {String} [zoom.enabled.type='wheel'] Set zoom interaction type.
* @property {Boolean} [zoom.rescale=false] Enable to rescale after zooming.<br>
* If true set, y domain will be updated according to the zoomed region.
* @property {Array} [zoom.extent=[1, 10]] Change zoom extent.
Expand All @@ -140,9 +141,13 @@ export default class Options {
* Specified function receives the zoomed domain.
* @property {Function} [zoom.onzoomend=undefined] Set callback that is called when zooming ends.<br>
* Specified function receives the zoomed domain.
* @property {Boolean|Object} [zoom.resetButton=true] Set to display zoom reset button for 'drag' type zoom
* @property {String} [zoom.resetButton.text='Reset Zoom'] Text value for zoom reset button.
* @example
* zoom: {
* enabled: true,
* enabled: {
* type: "drag"
* },
* rescale: true,
* extent: [1, 100] // enable more zooming
* x: {
Expand All @@ -151,16 +156,25 @@ export default class Options {
* },
* onzoomstart: function(event) { ... },
* onzoom: function(domain) { ... },
* onzoomend: function(domain) { ... }
* onzoomend: function(domain) { ... },
*
* // show reset button when is zoomed-in
* resetButton: true,
*
* // customized text value for reset zoom button
* resetButton: {
* text: "Unzoom"
* }
* }
*/
zoom_enabled: false,
zoom_enabled: undefined,
zoom_extent: undefined,
zoom_privileged: false,
zoom_rescale: false,
zoom_onzoom: undefined,
zoom_onzoomstart: undefined,
zoom_onzoomend: undefined,
zoom_resetButton: true,
zoom_x_min: undefined,
zoom_x_max: undefined,

Expand Down
Loading

0 comments on commit da2ce10

Please sign in to comment.