UNPKG

@antv/g2

Version:

the Grammar of Graphics in Javascript

166 lines 7.57 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SliderWheel = SliderWheel; const g_1 = require("@antv/g"); const coordinate_1 = require("../utils/coordinate"); const sliderFilter_1 = require("./sliderFilter"); const utils_1 = require("./utils"); /** * SliderWheel interaction for mouse wheel/touchpad gestures on charts. */ /** * Get the real DOM canvas element from G2 container. * This helper function provides better type safety than using 'as any' directly. */ function getCanvasDOM(container) { var _a; try { const canvas = (_a = container.ownerDocument) === null || _a === void 0 ? void 0 : _a.defaultView; if (!canvas || typeof canvas.getContextService !== 'function') { return null; } const dom = canvas.getContextService().getDomElement(); return dom instanceof HTMLElement ? dom : null; } catch (_b) { return null; } } function SliderWheel({ className = sliderFilter_1.SLIDER_CLASS_NAME, setValue = (component, values) => component.setValues(values), minRange = 0.01, wheelSensitivity = 0.05, x = true, y = true, } = {}) { return (context) => { const { container, view } = context; const { coordinate } = view; const transposed = (0, coordinate_1.isTranspose)(coordinate); // Get the real DOM canvas element to attach wheel listener const canvasDOM = getCanvasDOM(container); const safeMinRange = Math.max(0.000001, Math.min(1, minRange)); const sliders = container.getElementsByClassName(className); if (!sliders.length) return () => { }; const isModifierKeyActive = (config, event) => { if (config === true) return true; if (config === false) return false; if (config === 'shift') return event.shiftKey && !event.ctrlKey && !event.altKey; if (config === 'ctrl') return event.ctrlKey && !event.shiftKey && !event.altKey; if (config === 'alt') return event.altKey && !event.shiftKey && !event.ctrlKey; return false; }; const triggerSliderValueChange = (slider, values) => { setValue(slider, values); slider.dispatchEvent(new g_1.CustomEvent('valuechange', { detail: { value: values }, nativeEvent: true, })); }; /** * Calculate zoom center based on mouse position relative to slider track. * @param mousePos - Mouse position (X or Y) relative to canvas * @param sliderPos - Slider position (X or Y) * @param trackLength - Length of slider track * @param v0 - Current selection start value [0-1] * @param v1 - Current selection end value [0-1] * @returns Normalized center position [0-1] clamped to current selection */ const calculateZoomCenter = (mousePos, sliderPos, trackLength, v0, v1) => { const relativePos = mousePos - sliderPos; const normalizedPosition = relativePos / trackLength; // Clamp to [0, 1] range first, then to current selection range const clamped = Math.max(0, Math.min(1, normalizedPosition)); return Math.max(v0, Math.min(v1, clamped)); }; const handleWheel = (event) => { // Early return if canvas DOM is not available if (!canvasDOM) return; // Check if the event target is within our canvas container const target = event.target; if (!canvasDOM.contains(target)) { return; } // Get mouse position relative to canvas const canvasRect = canvasDOM.getBoundingClientRect(); const mouseX = event.clientX - canvasRect.left; const mouseY = event.clientY - canvasRect.top; // Find all sliders that should respond to this event const activeSliders = []; for (const slider of sliders) { const { values, orientation } = slider.attributes; if (!values) continue; const isHorizontal = orientation === 'horizontal'; const actualAxis = transposed ? isHorizontal ? 'y' : 'x' : isHorizontal ? 'x' : 'y'; const axisConfig = actualAxis === 'x' ? x : y; if (isModifierKeyActive(axisConfig, event)) { activeSliders.push(slider); } } // If no slider should handle this event, let it propagate if (activeSliders.length === 0) { return; } // Prevent page scroll since we have active sliders event.preventDefault(); event.stopPropagation(); // Process all active sliders for (const slider of activeSliders) { const { values, orientation, x: sliderX, y: sliderY, trackLength, } = slider.attributes; const [v0, v1] = values; const range = v1 - v0; const isHorizontal = orientation === 'horizontal'; // Calculate zoom center based on mouse position relative to slider const center = isHorizontal ? calculateZoomCenter(mouseX, sliderX, trackLength, v0, v1) : calculateZoomCenter(mouseY, sliderY, trackLength, v0, v1); const adaptiveSensitivity = wheelSensitivity * (0, utils_1.calculateSensitivityMultiplier)(range); const delta = event.deltaY * adaptiveSensitivity; const zoomFactor = 1 + delta; const newRange = Math.max(safeMinRange, Math.min(1, range * zoomFactor)); // Calculate new range boundaries based on mouse position // The zoom should maintain the ratio of distances from center to edges const leftRatio = (center - v0) / range; const rightRatio = (v1 - center) / range; let newV0 = center - newRange * leftRatio; let newV1 = center + newRange * rightRatio; // Handle boundary conditions while trying to maintain mouse position as center if (newV0 < 0) { newV0 = 0; newV1 = Math.min(1, newRange); } else if (newV1 > 1) { newV1 = 1; newV0 = Math.max(0, 1 - newRange); } triggerSliderValueChange(slider, [newV0, newV1]); } }; // Listen on the real DOM canvas element with passive: false AND capture: true // Capture phase ensures we intercept BEFORE @antv/g-canvas's passive listener // This is the only way to preventDefault() before the passive listener receives it if (canvasDOM) { canvasDOM.addEventListener('wheel', handleWheel, { passive: false, capture: true, }); } return () => { if (canvasDOM) { canvasDOM.removeEventListener('wheel', handleWheel, { capture: true }); } }; }; } SliderWheel.props = { reapplyWhenUpdate: true, }; //# sourceMappingURL=sliderWheel.js.map