Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions src/component/dataZoom/InsideZoomModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import DataZoomModel, {DataZoomOption} from './DataZoomModel';
import { inheritDefaultOption } from '../../util/component';
import { WheelAxisType } from '../helper/RoamController';

export interface InsideDataZoomOption extends DataZoomOption {

Expand All @@ -40,6 +41,29 @@ export interface InsideDataZoomOption extends DataZoomOption {

preventDefaultMouseMove?: boolean

/**
* Restricts the pan (triggered by `moveOnMouseWheel`) to a single
* wheel axis. Has no effect on zoom — see `zoomOnMouseWheelAxis`.
*
* - Omitted (default): either wheel axis can drive the pan, preserving
* the pre-existing single-`scrollDelta` behavior.
* - `'horizontal'`: only `deltaX` drives the pan.
* - `'vertical'`: only `deltaY` drives the pan.
*/
moveOnMouseWheelAxis?: WheelAxisType

/**
* Restricts the zoom (triggered by `zoomOnMouseWheel`) to a single
* wheel axis. Has no effect on pan — see `moveOnMouseWheelAxis`.
*
* - Omitted (default): any wheel direction triggers zoom, matching the
* pre-existing behavior where zrender's collapsed `wheelDelta` drives
* the scale factor.
* - `'horizontal'`: only `deltaX` drives the zoom.
* - `'vertical'`: only `deltaY` drives the zoom.
*/
zoomOnMouseWheelAxis?: WheelAxisType

/**
* Inside dataZoom don't support textStyle
*/
Expand Down
58 changes: 51 additions & 7 deletions src/component/dataZoom/InsideZoomView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import InsideZoomModel from './InsideZoomModel';
import GlobalModel from '../../model/Global';
import ExtensionAPI from '../../core/ExtensionAPI';
import { bind } from 'zrender/src/core/util';
import RoamController, {RoamEventParams} from '../helper/RoamController';
import RoamController, {RoamEventParams, WheelAxisType} from '../helper/RoamController';
import { AxisBaseModel } from '../../coord/AxisBaseModel';
import Polar from '../../coord/polar/Polar';
import SingleAxis from '../../coord/single/SingleAxis';
Expand Down Expand Up @@ -104,6 +104,14 @@ const getRangeHandlers: {
return;
}

// `zoomOnMouseWheelAxis` restricts the zoom to one wheel axis;
// unset falls back to the combined `scale` for backward
// compatibility.
const effectiveScale = pickWheelAxisValue(
(this.dataZoomModel as InsideZoomModel).get('zoomOnMouseWheelAxis', true),
e.scaleX, e.scaleY, e.scale
);

const directionInfo = getDirectionInfo[coordSysMainType](
null, [e.originX, e.originY], axisModel, controller, coordSysInfo
);
Expand All @@ -113,7 +121,7 @@ const getRangeHandlers: {
: (directionInfo.pixel - directionInfo.pixelStart)
) / directionInfo.pixelLength * (range[1] - range[0]) + range[0];

const scale = Math.max(1 / e.scale, 0);
const scale = Math.max(1 / effectiveScale, 0);
range[0] = (range[0] - percentPoint) * scale + percentPoint;
range[1] = (range[1] - percentPoint) * scale + percentPoint;

Expand All @@ -140,19 +148,55 @@ const getRangeHandlers: {
}),

scrollMove: makeMover(
function (range, axisModel, coordSysInfo, coordSysMainType, controller, e: RoamEventParams['scrollMove']
function (
this: InsideZoomView,
range, axisModel, coordSysInfo, coordSysMainType, controller,
e: RoamEventParams['scrollMove']
) {
// `moveOnMouseWheelAxis` restricts the pan to one wheel axis;
// unset falls back to the combined `scrollDelta` for backward
// compatibility.
const effectiveDelta = pickWheelAxisValue(
(this.dataZoomModel as InsideZoomModel).get('moveOnMouseWheelAxis', true),
e.scrollDeltaX, e.scrollDeltaY, e.scrollDelta
);
const directionInfo = getDirectionInfo[coordSysMainType](
[0, 0], [e.scrollDelta, e.scrollDelta], axisModel, controller, coordSysInfo
[0, 0], [effectiveDelta, effectiveDelta],
axisModel, controller, coordSysInfo
);
return directionInfo.signal * (range[1] - range[0]) * e.scrollDelta;
// Only `directionInfo.signal` is used here — the scalar already
// encodes the magnitude. `directionInfo.pixel` would go through
// `pointToCoord` for polar and no longer match the scalar.
return directionInfo.signal * (range[1] - range[0]) * effectiveDelta;
})
};

/**
* Picks the wheel-derived value for this dataZoom given a
* `moveOnMouseWheelAxis` / `zoomOnMouseWheelAxis` setting. Unset falls
* back to the caller-supplied scalar so existing configurations keep
* their pre-existing behavior.
*/
function pickWheelAxisValue(
wheelAxis: WheelAxisType | undefined,
axisHorizontal: number,
axisVertical: number,
fallback: number
): number {
if (wheelAxis === 'horizontal') {
return axisHorizontal;
}
if (wheelAxis === 'vertical') {
return axisVertical;
}
return fallback;
}

export type DataZoomGetRangeHandlers = typeof getRangeHandlers;

function makeMover(
getPercentDelta: (
this: InsideZoomView,
range: [number, number],
axisModel: AxisBaseModel,
coordSysInfo: DataZoomReferCoordSysInfo,
Expand All @@ -177,8 +221,8 @@ function makeMover(
return;
}

const percentDelta = getPercentDelta(
range, axisModel, coordSysInfo, coordSysMainType, controller, e
const percentDelta = getPercentDelta.call(
this, range, axisModel, coordSysInfo, coordSysMainType, controller, e
);

sliderMove(percentDelta, range, [0, 100], 'all');
Expand Down
182 changes: 156 additions & 26 deletions src/component/helper/RoamController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,44 @@ type RoamBehavior = 'zoomOnMouseWheel' | 'moveOnMouseMove' | 'moveOnMouseWheel';

export interface RoamEventParams {
'zoom': {
/**
* Zoom factor derived from zrender's scalar `wheelDelta`
* (Y-priority via its polyfill). Axis-aware consumers can read
* `scaleX` / `scaleY` instead.
*/
scale: number
/**
* Zoom factor derived from the horizontal wheel component. `1`
* (identity) when the wheel event carries no horizontal delta.
*/
scaleX: number
/**
* Zoom factor derived from the vertical wheel component. `1`
* (identity) when the wheel event carries no vertical delta.
*/
scaleY: number
originX: number
originY: number

isAvailableBehavior: Bind3<typeof isAvailableBehavior, null, RoamBehavior, ZRElementEvent>
}
'scrollMove': {
/**
* Pan magnitude (fraction of range) derived from zrender's scalar
* `wheelDelta` (Y-priority via its polyfill). Axis-aware consumers
* can read `scrollDeltaX` / `scrollDeltaY` instead.
*/
scrollDelta: number
/**
* Pan magnitude derived from the horizontal wheel component. `0`
* when the wheel event carries no horizontal delta.
*/
scrollDeltaX: number
/**
* Pan magnitude derived from the vertical wheel component. `0`
* when the wheel event carries no vertical delta.
*/
scrollDeltaY: number
originX: number
originY: number

Expand Down Expand Up @@ -362,43 +392,43 @@ class RoamController extends Eventful<RoamEventDefinition> {
const shouldZoom = isAvailableBehavior('zoomOnMouseWheel', e, this._opt);
const shouldMove = isAvailableBehavior('moveOnMouseWheel', e, this._opt);
const wheelDelta = e.wheelDelta;
const absWheelDeltaDelta = Math.abs(wheelDelta);
const originX = e.offsetX;
const originY = e.offsetY;

// wheelDelta maybe -0 in chrome mac.
if (wheelDelta === 0 || (!shouldZoom && !shouldMove)) {
return;
}

// If both `shouldZoom` and `shouldMove` is true, trigger
// their event both, and the final behavior is determined
// by event listener themselves.
// Reach through to the native WheelEvent for per-axis deltas;
// zrender's `wheelDelta` collapses the two axes into a single scalar.
// Pre-2013 IE has no `deltaY`; the axis helpers fall back to the
// passed default in that case.
const nativeEvent = (e.event as unknown as WheelEvent) || null;
const originX = e.offsetX;
const originY = e.offsetY;

if (shouldZoom) {
// Convenience:
// Mac and VM Windows on Mac: scroll up: zoom out.
// Windows: scroll up: zoom in.

// FIXME: Should do more test in different environment.
// wheelDelta is too complicated in difference nvironment
// (https://developer.mozilla.org/en-US/docs/Web/Events/mousewheel),
// although it has been normallized by zrender.
// wheelDelta of mouse wheel is bigger than touch pad.
const factor = absWheelDeltaDelta > 3 ? 1.4 : absWheelDeltaDelta > 1 ? 1.2 : 1.1;
const scale = wheelDelta > 0 ? factor : 1 / factor;
// `scaleY` defaults to `scale` on the IE fallback so the single
// scalar still drives zoom; `scaleX` defaults to identity so
// `zoomOnMouseWheelAxis: 'horizontal'` becomes a safe no-op.
const scale = ladderZoomScale(wheelDelta);
this._checkTriggerMoveZoom(this, 'zoom', 'zoomOnMouseWheel', e, {
scale: scale, originX: originX, originY: originY, isAvailableBehavior: null
scale: scale,
scaleX: axisZoomScale(nativeEvent, 'horizontal', 1),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With moveOnMouseWheelAxis: 'horizontal' / zoomOnMouseWheelAxis: 'horizontal', a plain vertical wheel becomes a no-op in InsideZoomView (effectiveDelta === 0 / effectiveScale === 1). But the event is still consumed later in _checkTriggerMoveZoom(), so the page/container can no longer scroll. That turns an axis mismatch from "ignored" into "blocked". Could we move the axis check earlier so non-matching wheel directions fall through like the existing modifier-gated cases do?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Justin-ZS

Thanks! I hadn't considered that the event is consumed even when the axis doesn't match. Let me think through the right place to hoist the check and I'll follow up.

scaleY: axisZoomScale(nativeEvent, 'vertical', scale),
originX: originX, originY: originY, isAvailableBehavior: null
});
}

if (shouldMove) {
// FIXME: Should do more test in different environment.
const absDelta = Math.abs(wheelDelta);
// wheelDelta of mouse wheel is bigger than touch pad.
const scrollDelta = (wheelDelta > 0 ? 1 : -1) * (absDelta > 3 ? 0.4 : absDelta > 1 ? 0.15 : 0.05);
// `scrollDelta` stays derived from the scalar `wheelDelta` so
// pre-existing listeners see bit-identical values. Per-axis
// fields let a horizontal wheel (or a diagonal trackpad swipe)
// drive the matching axis independently.
const scrollDelta = ladderScrollDelta(wheelDelta);
this._checkTriggerMoveZoom(this, 'scrollMove', 'moveOnMouseWheel', e, {
scrollDelta: scrollDelta, originX: originX, originY: originY, isAvailableBehavior: null
scrollDelta: scrollDelta,
scrollDeltaX: axisScrollDelta(nativeEvent, 'horizontal', 0),
scrollDeltaY: axisScrollDelta(nativeEvent, 'vertical', scrollDelta),
originX: originX, originY: originY, isAvailableBehavior: null
});
}
}
Expand All @@ -410,8 +440,13 @@ class RoamController extends Eventful<RoamEventDefinition> {
return;
}
const scale = e.pinchScale > 1 ? 1.1 : 1 / 1.1;
// Touch pinch is a 2D gesture without a distinct horizontal/vertical
// component, so mirror the combined `scale` into both per-axis fields.
this._checkTriggerMoveZoom(this, 'zoom', null, e, {
scale: scale, originX: e.pinchX, originY: e.pinchY, isAvailableBehavior: null
scale: scale,
scaleX: scale,
scaleY: scale,
originX: e.pinchX, originY: e.pinchY, isAvailableBehavior: null
});
}

Expand Down Expand Up @@ -442,6 +477,101 @@ function eventConsumed(e: ZRElementEvent): boolean {
return (e as RoamControllerZREventExtend).__ecRoamConsumed;
}

/**
* Wheel input axis name. Shared by the user-facing
* `moveOnMouseWheelAxis` / `zoomOnMouseWheelAxis` options and by the
* internal wheel-delta helpers.
*/
export type WheelAxisType = 'horizontal' | 'vertical';

// Legacy WebKit exposes these per-axis siblings alongside `wheelDelta`; prefer
// them when present because they preserve the exact per-notch scaling the
// IE-style wheel APIs used.
interface WheelEventWithLegacyPerAxis extends WheelEvent {
wheelDeltaX?: number
wheelDeltaY?: number
}

/**
* Per-axis equivalent of zrender's `getWheelDeltaMayPolyfill` + `/120`
* normalization. Returns a value on the same scale as `wheelDelta`
* (≈ ±1 per mouse-wheel notch, much smaller on trackpads), or 0 when the
* axis carries no input.
*/
function axisWheelDelta(
nativeEvent: WheelEvent, axis: WheelAxisType
): number {
const legacy = axis === 'horizontal'
? (nativeEvent as WheelEventWithLegacyPerAxis).wheelDeltaX
: (nativeEvent as WheelEventWithLegacyPerAxis).wheelDeltaY;
if (typeof legacy === 'number' && legacy !== 0) {
return legacy / 120;
}
const raw = axis === 'horizontal' ? nativeEvent.deltaX : nativeEvent.deltaY;
if (raw == null || raw === 0) {
return 0;
}
// Flip sign so positive == scroll up / left, matching the
// `wheelDelta` convention the downstream code already expects.
return -raw * 3 / 120;
}

// FIXME: Should do more test in different environments.
// `wheelDelta` is too complicated across environments
// (https://developer.mozilla.org/en-US/docs/Web/Events/mousewheel),
// although it has been normalized by zrender. `wheelDelta` of mouse
// wheel is bigger than touch pad.

/** Maps a `wheelDelta`-scaled value to a pan magnitude (fraction of range). */
function ladderScrollDelta(wheelDelta: number): number {
if (wheelDelta === 0) {
return 0;
}
const absDelta = Math.abs(wheelDelta);
return (wheelDelta > 0 ? 1 : -1)
* (absDelta > 3 ? 0.4 : absDelta > 1 ? 0.15 : 0.05);
}

/** Maps a `wheelDelta`-scaled value to a zoom scale (1 == identity). */
function ladderZoomScale(wheelDelta: number): number {
if (wheelDelta === 0) {
return 1;
}
const absDelta = Math.abs(wheelDelta);
const factor = absDelta > 3 ? 1.4 : absDelta > 1 ? 1.2 : 1.1;
return wheelDelta > 0 ? factor : 1 / factor;
}

/**
* Per-axis pan magnitude; returns `fallback` when the event lacks
* per-axis info (effectively only pre-2013 IE).
*/
function axisScrollDelta(
nativeEvent: WheelEvent | null,
axis: WheelAxisType,
fallback: number
): number {
if (!nativeEvent || nativeEvent.deltaY == null) {
return fallback;
}
return ladderScrollDelta(axisWheelDelta(nativeEvent, axis));
}

/**
* Per-axis zoom scale; returns `fallback` when the event lacks per-axis
* info (effectively only pre-2013 IE).
*/
function axisZoomScale(
nativeEvent: WheelEvent | null,
axis: WheelAxisType,
fallback: number
): number {
if (!nativeEvent || nativeEvent.deltaY == null) {
return fallback;
}
return ladderZoomScale(axisWheelDelta(nativeEvent, axis));
}


type RoamControllerZREventListener = (e: ZRElementEvent) => void;
type RoamControllerZREventType = 'mousedown' | 'mousemove' | 'mouseup' | 'mousewheel' | 'pinch';
Expand Down Expand Up @@ -577,4 +707,4 @@ function isAvailableBehavior(
);
}

export default RoamController;
export default RoamController;
Loading