echarts RoamController 源码

  • 2022-10-20
  • 浏览 (593)

echarts RoamController 代码

文件路径:/src/component/helper/RoamController.ts

/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.  The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License.  You may obtain a copy of the License at
*
*   http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied.  See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import Eventful from 'zrender/src/core/Eventful';
import * as eventTool from 'zrender/src/core/event';
import * as interactionMutex from './interactionMutex';
import { ZRenderType } from 'zrender/src/zrender';
import { ZRElementEvent, RoamOptionMixin } from '../../util/types';
import { Bind3, isString, bind, defaults, clone } from 'zrender/src/core/util';
import Group from 'zrender/src/graphic/Group';

// Can be null/undefined or true/false
// or 'pan/move' or 'zoom'/'scale'
export type RoamType = RoamOptionMixin['roam'];

interface RoamOption {
    zoomOnMouseWheel?: boolean | 'ctrl' | 'shift' | 'alt'
    moveOnMouseMove?: boolean | 'ctrl' | 'shift' | 'alt'
    moveOnMouseWheel?: boolean | 'ctrl' | 'shift' | 'alt'
    /**
     * If fixed the page when pan
     */
    preventDefaultMouseMove?: boolean
}

type RoamEventType = keyof RoamEventParams;

type RoamBehavior = 'zoomOnMouseWheel' | 'moveOnMouseMove' | 'moveOnMouseWheel';

export interface RoamEventParams {
    'zoom': {
        scale: number
        originX: number
        originY: number

        isAvailableBehavior: Bind3<typeof isAvailableBehavior, null, RoamBehavior, ZRElementEvent>
    }
    'scrollMove': {
        scrollDelta: number
        originX: number
        originY: number

        isAvailableBehavior: Bind3<typeof isAvailableBehavior, null, RoamBehavior, ZRElementEvent>
    }
    'pan': {
        dx: number
        dy: number
        oldX: number
        oldY: number
        newX: number
        newY: number

        isAvailableBehavior: Bind3<typeof isAvailableBehavior, null, RoamBehavior, ZRElementEvent>
    }
};

export interface RoamControllerHost {
    target: Group
    zoom: number
    zoomLimit: {
        min?: number
        max?: number
    }
}

class RoamController extends Eventful<{
    [key in keyof RoamEventParams]: (params: RoamEventParams[key]) => void | undefined
}> {

    pointerChecker: (e: ZRElementEvent, x: number, y: number) => boolean;

    private _zr: ZRenderType;

    private _opt: Required<RoamOption>;

    private _dragging: boolean;

    private _pinching: boolean;

    private _x: number;

    private _y: number;

    readonly enable: (this: this, controlType?: RoamType, opt?: RoamOption) => void;

    readonly disable: () => void;


    constructor(zr: ZRenderType) {
        super();

        this._zr = zr;

        // Avoid two roamController bind the same handler
        const mousedownHandler = bind(this._mousedownHandler, this);
        const mousemoveHandler = bind(this._mousemoveHandler, this);
        const mouseupHandler = bind(this._mouseupHandler, this);
        const mousewheelHandler = bind(this._mousewheelHandler, this);
        const pinchHandler = bind(this._pinchHandler, this);

        /**
         * Notice: only enable needed types. For example, if 'zoom'
         * is not needed, 'zoom' should not be enabled, otherwise
         * default mousewheel behaviour (scroll page) will be disabled.
         */
        this.enable = function (controlType, opt) {

            // Disable previous first
            this.disable();

            this._opt = defaults(clone(opt) || {}, {
                zoomOnMouseWheel: true,
                moveOnMouseMove: true,
                // By default, wheel do not trigger move.
                moveOnMouseWheel: false,
                preventDefaultMouseMove: true
            });

            if (controlType == null) {
                controlType = true;
            }

            if (controlType === true || (controlType === 'move' || controlType === 'pan')) {
                zr.on('mousedown', mousedownHandler);
                zr.on('mousemove', mousemoveHandler);
                zr.on('mouseup', mouseupHandler);
            }
            if (controlType === true || (controlType === 'scale' || controlType === 'zoom')) {
                zr.on('mousewheel', mousewheelHandler);
                zr.on('pinch', pinchHandler);
            }
        };

        this.disable = function () {
            zr.off('mousedown', mousedownHandler);
            zr.off('mousemove', mousemoveHandler);
            zr.off('mouseup', mouseupHandler);
            zr.off('mousewheel', mousewheelHandler);
            zr.off('pinch', pinchHandler);
        };
    }

    isDragging() {
        return this._dragging;
    }

    isPinching() {
        return this._pinching;
    }

    setPointerChecker(pointerChecker: RoamController['pointerChecker']) {
        this.pointerChecker = pointerChecker;
    }

    dispose() {
        this.disable();
    }

    private _mousedownHandler(e: ZRElementEvent) {
        if (eventTool.isMiddleOrRightButtonOnMouseUpDown(e)) {
            return;
        }

        let el = e.target;
        while (el) {
            if (el.draggable) {
                return;
            }
            // check if host is draggable
            el = el.__hostTarget || el.parent;
        }

        const x = e.offsetX;
        const y = e.offsetY;

        // Only check on mosedown, but not mousemove.
        // Mouse can be out of target when mouse moving.
        if (this.pointerChecker && this.pointerChecker(e, x, y)) {
            this._x = x;
            this._y = y;
            this._dragging = true;
        }
    }

    private _mousemoveHandler(e: ZRElementEvent) {
        if (!this._dragging
            || !isAvailableBehavior('moveOnMouseMove', e, this._opt)
            || e.gestureEvent === 'pinch'
            || interactionMutex.isTaken(this._zr, 'globalPan')
        ) {
            return;
        }

        const x = e.offsetX;
        const y = e.offsetY;

        const oldX = this._x;
        const oldY = this._y;

        const dx = x - oldX;
        const dy = y - oldY;

        this._x = x;
        this._y = y;

        this._opt.preventDefaultMouseMove && eventTool.stop(e.event);

        trigger(this, 'pan', 'moveOnMouseMove', e, {
            dx: dx, dy: dy, oldX: oldX, oldY: oldY, newX: x, newY: y, isAvailableBehavior: null
        });
    }

    private _mouseupHandler(e: ZRElementEvent) {
        if (!eventTool.isMiddleOrRightButtonOnMouseUpDown(e)) {
            this._dragging = false;
        }
    }

    private _mousewheelHandler(e: ZRElementEvent) {
        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.

        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;
            checkPointerAndTrigger(this, 'zoom', 'zoomOnMouseWheel', e, {
                scale: 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);
            checkPointerAndTrigger(this, 'scrollMove', 'moveOnMouseWheel', e, {
                scrollDelta: scrollDelta, originX: originX, originY: originY, isAvailableBehavior: null
            });
        }
    }

    private _pinchHandler(e: ZRElementEvent) {
        if (interactionMutex.isTaken(this._zr, 'globalPan')) {
            return;
        }
        const scale = e.pinchScale > 1 ? 1.1 : 1 / 1.1;
        checkPointerAndTrigger(this, 'zoom', null, e, {
            scale: scale, originX: e.pinchX, originY: e.pinchY, isAvailableBehavior: null
        });
    }
}


function checkPointerAndTrigger<T extends 'scrollMove' | 'zoom'>(
    controller: RoamController,
    eventName: T,
    behaviorToCheck: RoamBehavior,
    e: ZRElementEvent,
    contollerEvent: RoamEventParams[T]
) {
    if (controller.pointerChecker
        && controller.pointerChecker(e, contollerEvent.originX, contollerEvent.originY)
    ) {
        // When mouse is out of roamController rect,
        // default befavoius should not be be disabled, otherwise
        // page sliding is disabled, contrary to expectation.
        eventTool.stop(e.event);

        trigger(controller, eventName, behaviorToCheck, e, contollerEvent);
    }
}

function trigger<T extends RoamEventType>(
    controller: RoamController,
    eventName: T,
    behaviorToCheck: RoamBehavior,
    e: ZRElementEvent,
    contollerEvent: RoamEventParams[T]
) {
    // Also provide behavior checker for event listener, for some case that
    // multiple components share one listener.
    contollerEvent.isAvailableBehavior = bind(isAvailableBehavior, null, behaviorToCheck, e);
    // TODO should not have type issue.
    (controller as any).trigger(eventName, contollerEvent);
}

// settings: {
//     zoomOnMouseWheel
//     moveOnMouseMove
//     moveOnMouseWheel
// }
// The value can be: true / false / 'shift' / 'ctrl' / 'alt'.
function isAvailableBehavior(
    behaviorToCheck: RoamBehavior,
    e: ZRElementEvent,
    settings: Pick<RoamOption, RoamBehavior>
) {
    const setting = settings[behaviorToCheck];
    return !behaviorToCheck || (
        setting && (!isString(setting) || e.event[setting + 'Key' as 'shiftKey' | 'ctrlKey' | 'altKey'])
    );
}

export default RoamController;

相关信息

echarts 源码目录

相关文章

echarts BrushController 源码

echarts BrushTargetManager 源码

echarts MapDraw 源码

echarts brushHelper 源码

echarts cursorHelper 源码

echarts interactionMutex 源码

echarts listComponent 源码

echarts roamHelper 源码

echarts sliderMove 源码

0  赞