From 5642216e91bd1f16efd587a562443e34cc89b469 Mon Sep 17 00:00:00 2001 From: netil Date: Mon, 25 Nov 2024 19:27:02 +0900 Subject: [PATCH] fix(axis): Fix x axis extent to work Make axis.x.extent option to limit dragging area for zoom drag Ref #3768 --- src/ChartInternal/Axis/Axis.ts | 23 +++++ src/ChartInternal/interactions/subchart.ts | 27 +----- src/ChartInternal/interactions/zoom.ts | 21 +++++ src/config/Options/axis/x.ts | 3 +- test/interactions/drag-spec.ts | 98 ++++++++++++++++++++++ types/axis.d.ts | 4 +- 6 files changed, 148 insertions(+), 28 deletions(-) diff --git a/src/ChartInternal/Axis/Axis.ts b/src/ChartInternal/Axis/Axis.ts index 65abd9fe8..5a550844e 100644 --- a/src/ChartInternal/Axis/Axis.ts +++ b/src/ChartInternal/Axis/Axis.ts @@ -106,6 +106,29 @@ class Axis { return type; } + /** + * Get extent value + * @returns {Array} default extent + * @private + */ + public getExtent(): number[] { + const $$ = this.owner; + const {config, scale} = $$; + let extent = config.axis_x_extent; + + if (extent) { + if (isFunction(extent)) { + extent = extent.bind($$.api)($$.getXDomain($$.data.targets), scale.subX); + } else if (this.isTimeSeries() && extent.every(isNaN)) { + const fn = parseDate.bind($$); + + extent = extent.map(v => scale.subX(fn(v))); + } + } + + return extent; + } + init() { const $$ = this.owner; const {config, $el: {main, axis}, state: {clip}} = $$; diff --git a/src/ChartInternal/interactions/subchart.ts b/src/ChartInternal/interactions/subchart.ts index 2e1a88760..5d2dfb83f 100644 --- a/src/ChartInternal/interactions/subchart.ts +++ b/src/ChartInternal/interactions/subchart.ts @@ -5,7 +5,7 @@ import {brushSelection as d3BrushSelection, brushX as d3BrushX, brushY as d3BrushY} from "d3-brush"; import {select as d3Select} from "d3-selection"; import CLASS from "../../config/classes"; -import {brushEmpty, capitalize, isArray, isFunction, parseDate} from "../../module/util"; +import {brushEmpty, capitalize, isArray} from "../../module/util"; export default { /** @@ -92,7 +92,7 @@ export default { // set the brush extent $$.brush.scale = function(scale) { const h = config.subchart_size_height; - let extent = $$.getExtent(); + let extent = $$.axis.getExtent(); if (!extent && scale.range) { extent = [[0, 0], [scale.range()[1], h]]; @@ -373,28 +373,5 @@ export default { subchart.main.attr("transform", $$.getTranslate("context")); subXAxis.attr("transform", $$.getTranslate("subX")); - }, - - /** - * Get extent value - * @returns {Array} default extent - * @private - */ - getExtent(): number[] { - const $$ = this; - const {config, scale} = $$; - let extent = config.axis_x_extent; - - if (extent) { - if (isFunction(extent)) { - extent = extent.bind($$.api)($$.getXDomain($$.data.targets), scale.subX); - } else if ($$.axis.isTimeSeries() && extent.every(isNaN)) { - const fn = parseDate.bind($$); - - extent = extent.map(v => scale.subX(fn(v))); - } - } - - return extent; } }; diff --git a/src/ChartInternal/interactions/zoom.ts b/src/ChartInternal/interactions/zoom.ts index 06b5992c7..6e4b52b54 100644 --- a/src/ChartInternal/interactions/zoom.ts +++ b/src/ChartInternal/interactions/zoom.ts @@ -367,6 +367,7 @@ export default { let start = 0; let end = 0; let zoomRect; + let extent; const prop = { axis: isRotated ? "y" : "x", @@ -377,6 +378,9 @@ export default { $$.zoomBehaviour = d3Drag() .clickDistance(4) .on("start", function(event) { + // get extent at first zooming, when is zoomed do not consider + extent = $$.scale.zoom ? null : $$.axis.getExtent(); + state.event = event; $$.setDragStatus(true); $$.unselectRect(); @@ -390,6 +394,15 @@ export default { } start = getPointer(event, this as SVGAElement)[prop.index]; + + if (extent) { + if (start < extent[0]) { + start = extent[0]; + } else if (start > extent[1]) { + start = extent[1]; + } + } + end = start; zoomRect @@ -401,6 +414,14 @@ export default { .on("drag", function(event) { end = getPointer(event, this as SVGAElement)[prop.index]; + if (extent) { + if (end > extent[1]) { + end = extent[1]; + } else if (end < extent[0]) { + end = extent[0]; + } + } + zoomRect .attr(prop.axis, Math.min(start, end)) .attr(prop.attr, Math.abs(end - start)); diff --git a/src/config/Options/axis/x.ts b/src/config/Options/axis/x.ts index 302235f97..b1e6fba30 100644 --- a/src/config/Options/axis/x.ts +++ b/src/config/Options/axis/x.ts @@ -635,7 +635,8 @@ export default { axis_x_height: undefined, /** - * Set default extent for subchart and zoom. This can be an array or function that returns an array. + * Set extent for subchart and zoom(drag only). This can be an array or function that returns an array. + * - **NOTE:** Specifying value, will limit the zoom scope selection within. * @name axis․x․extent * @memberof Options * @type {Array|Function} diff --git a/test/interactions/drag-spec.ts b/test/interactions/drag-spec.ts index 9157493cb..8cc21eb0c 100644 --- a/test/interactions/drag-spec.ts +++ b/test/interactions/drag-spec.ts @@ -7,6 +7,7 @@ import {beforeEach, beforeAll, afterEach, describe, expect, it} from "vitest"; import {$DRAG, $SELECT} from "../../src/config/classes"; import util from "../assets/util"; +import { transition } from "d3-transition"; describe("DRAG", function() { let chart; @@ -170,4 +171,101 @@ describe("DRAG", function() { }, 300); })); }); + + describe("axis.x.extent", () => { + beforeAll(() => { + args = { + data: { + x: "x", + json: { + Temperature: [ + "23.43", + "23.16", + "27.48", + "26.78", + "26.62", + "26.64", + "26.29", + "26.01", + "25.84", + "25.07", + "24.85", + "24.01", + ], + x: [ + "2018-01-01", + "2018-02-01", + "2018-03-01", + "2018-04-01", + "2018-05-01", + "2018-06-01", + "2018-07-01", + "2018-08-01", + "2018-09-01", + "2018-10-01", + "2018-11-01", + "2018-12-01", + ], + }, + type: "area", + }, + axis: { + x: { + tick: { + fit: false, + count: 5 + }, + type: "timeseries", + extent: ["2018-04-01", "2018-07-01"], + } + }, + zoom: { + enabled: true, + type: "drag" + }, + transition: { + duration: 0 + } + }; + }); + + it("drag zoom can't surpass the extent", () => new Promise(done => { + const {internal: {$el, scale}} = chart; + + util.doDrag($el.eventRect.node(), { + clientX: 50, + clientY: 50 + }, { + clientX: 550, + clientY: 50, + }); + + setTimeout(() => { + const zoomed = chart.zoom().map(v => Number(v).toString()); + + expect(/^15225.*/.test(zoomed[0])).to.be.true; + expect(/^1530.*/.test(zoomed[1])).to.be.true; + + done(1); + }, 300); + })); + + it("set options: subchart.show=true", () => { + args.subchart = { + show: true + }; + + args.zoom.enabled = false; + }); + + it("subchart selection width should match with extent", () => { + const {internal: {brush, scale}} = chart; + const overlay = brush.getSelection().select(".overlay"); + const start = scale.x(new Date(`${args.axis.x.extent[0]} 00:00:00`)); + const end = scale.x(new Date(`${args.axis.x.extent[1]} 00:00:00`)); + + expect(+overlay.attr("x")).to.be.equal(start); + expect(+overlay.attr("x") + +overlay.attr("width")).to.be.equal(end); + }); + }); }); diff --git a/types/axis.d.ts b/types/axis.d.ts index 89602b4fb..9bf7ea944 100644 --- a/types/axis.d.ts +++ b/types/axis.d.ts @@ -121,8 +121,8 @@ export interface xAxisConfiguration extends AxisConfigurationBase { height?: number; /** - * Set default extent for subchart and zoom. - * This can be an array or function that returns an array. + * Set extent for subchart and zoom(drag only). This can be an array or function that returns an array. + * - **NOTE:** Specifying value, will limit the zoom scope selection within. */ extent?: Array | ( (