425 lines
16 KiB
JavaScript
425 lines
16 KiB
JavaScript
|
|
|
||
|
|
/*
|
||
|
|
* 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.
|
||
|
|
*/
|
||
|
|
|
||
|
|
|
||
|
|
/**
|
||
|
|
* AUTO-GENERATED FILE. DO NOT MODIFY.
|
||
|
|
*/
|
||
|
|
|
||
|
|
/*
|
||
|
|
* 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 { __extends } from "tslib";
|
||
|
|
import Eventful from 'zrender/lib/core/Eventful.js';
|
||
|
|
import * as eventTool from 'zrender/lib/core/event.js';
|
||
|
|
import * as interactionMutex from './interactionMutex.js';
|
||
|
|
import { isString, bind, defaults, extend, retrieve2 } from 'zrender/lib/core/util.js';
|
||
|
|
import { makeInner } from '../../util/model.js';
|
||
|
|
import { retrieveZInfo } from '../../util/graphic.js';
|
||
|
|
import { onIrrelevantElement } from './cursorHelper.js';
|
||
|
|
;
|
||
|
|
/**
|
||
|
|
* An manager of zoom and pan(darg) hehavior.
|
||
|
|
* But it is not responsible for updating the view, since view updates vary and can
|
||
|
|
* not be handled in a uniform way.
|
||
|
|
*
|
||
|
|
* Note: regarding view updates:
|
||
|
|
* - Transformabe views typically use `coord/View` (e.g., geo and series.graph roaming).
|
||
|
|
* Some commonly used view update logic has been organized into `roamHelper.ts`.
|
||
|
|
* - Non-transformable views handle updates themselves, possibly involving re-layout,
|
||
|
|
* (e.g., treemap).
|
||
|
|
* - Some scenarios do not require transformation (e.g., dataZoom roaming for cartesian,
|
||
|
|
* brush component).
|
||
|
|
*/
|
||
|
|
var RoamController = /** @class */function (_super) {
|
||
|
|
__extends(RoamController, _super);
|
||
|
|
function RoamController(zr) {
|
||
|
|
var _this = _super.call(this) || this;
|
||
|
|
_this._zr = zr;
|
||
|
|
// Avoid two roamController bind the same handler
|
||
|
|
var mousedownHandler = bind(_this._mousedownHandler, _this);
|
||
|
|
var mousemoveHandler = bind(_this._mousemoveHandler, _this);
|
||
|
|
var mouseupHandler = bind(_this._mouseupHandler, _this);
|
||
|
|
var mousewheelHandler = bind(_this._mousewheelHandler, _this);
|
||
|
|
var 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 method is idempotent.
|
||
|
|
*/
|
||
|
|
_this.enable = function (controlType, rawOpt) {
|
||
|
|
var zInfo = rawOpt.zInfo;
|
||
|
|
var _a = retrieveZInfo(zInfo.component),
|
||
|
|
z = _a.z,
|
||
|
|
zlevel = _a.zlevel;
|
||
|
|
var zInfoParsed = {
|
||
|
|
component: zInfo.component,
|
||
|
|
z: z,
|
||
|
|
zlevel: zlevel,
|
||
|
|
// By default roam controller is the lowest z2 comparing to other elememts in a component.
|
||
|
|
z2: retrieve2(zInfo.z2, -Infinity)
|
||
|
|
};
|
||
|
|
var triggerInfo = extend({}, rawOpt.triggerInfo);
|
||
|
|
this._opt = defaults(extend({}, rawOpt), {
|
||
|
|
zoomOnMouseWheel: true,
|
||
|
|
moveOnMouseMove: true,
|
||
|
|
// By default, wheel do not trigger move.
|
||
|
|
moveOnMouseWheel: false,
|
||
|
|
preventDefaultMouseMove: true,
|
||
|
|
zInfoParsed: zInfoParsed,
|
||
|
|
triggerInfo: triggerInfo
|
||
|
|
});
|
||
|
|
if (controlType == null) {
|
||
|
|
controlType = true;
|
||
|
|
}
|
||
|
|
// A handy optimization for repeatedly calling `enable` during roaming.
|
||
|
|
// Assert `disable` is only affected by `controlType`.
|
||
|
|
if (!this._enabled || this._controlType !== controlType) {
|
||
|
|
this._enabled = true;
|
||
|
|
// Disable previous first
|
||
|
|
this.disable();
|
||
|
|
if (controlType === true || controlType === 'move' || controlType === 'pan') {
|
||
|
|
addRoamZrListener(zr, 'mousedown', mousedownHandler, zInfoParsed);
|
||
|
|
addRoamZrListener(zr, 'mousemove', mousemoveHandler, zInfoParsed);
|
||
|
|
addRoamZrListener(zr, 'mouseup', mouseupHandler, zInfoParsed);
|
||
|
|
}
|
||
|
|
if (controlType === true || controlType === 'scale' || controlType === 'zoom') {
|
||
|
|
addRoamZrListener(zr, 'mousewheel', mousewheelHandler, zInfoParsed);
|
||
|
|
addRoamZrListener(zr, 'pinch', pinchHandler, zInfoParsed);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
};
|
||
|
|
_this.disable = function () {
|
||
|
|
this._enabled = false;
|
||
|
|
removeRoamZrListener(zr, 'mousedown', mousedownHandler);
|
||
|
|
removeRoamZrListener(zr, 'mousemove', mousemoveHandler);
|
||
|
|
removeRoamZrListener(zr, 'mouseup', mouseupHandler);
|
||
|
|
removeRoamZrListener(zr, 'mousewheel', mousewheelHandler);
|
||
|
|
removeRoamZrListener(zr, 'pinch', pinchHandler);
|
||
|
|
};
|
||
|
|
return _this;
|
||
|
|
}
|
||
|
|
RoamController.prototype.isDragging = function () {
|
||
|
|
return this._dragging;
|
||
|
|
};
|
||
|
|
RoamController.prototype.isPinching = function () {
|
||
|
|
return this._pinching;
|
||
|
|
};
|
||
|
|
RoamController.prototype._checkPointer = function (e, x, y) {
|
||
|
|
var opt = this._opt;
|
||
|
|
var zInfoParsed = opt.zInfoParsed;
|
||
|
|
if (onIrrelevantElement(e, opt.api, zInfoParsed.component)) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
;
|
||
|
|
var triggerInfo = opt.triggerInfo;
|
||
|
|
var roamTrigger = triggerInfo.roamTrigger;
|
||
|
|
var inArea = false;
|
||
|
|
if (roamTrigger === 'global') {
|
||
|
|
inArea = true;
|
||
|
|
}
|
||
|
|
if (!inArea) {
|
||
|
|
inArea = triggerInfo.isInSelf(e, x, y);
|
||
|
|
}
|
||
|
|
if (inArea && triggerInfo.isInClip && !triggerInfo.isInClip(e, x, y)) {
|
||
|
|
inArea = false;
|
||
|
|
}
|
||
|
|
return inArea;
|
||
|
|
};
|
||
|
|
RoamController.prototype._decideCursorStyle = function (e, x, y, forReverse) {
|
||
|
|
// If this cursor style decision is not strictly consistent with zrender,
|
||
|
|
// it's fine - zr will set the cursor on the next mousemove.
|
||
|
|
// This `grab` cursor style should take the lowest precedence. If the hovring element already
|
||
|
|
// have a cursor, zrender will set it to be non-'default' before entering this handler.
|
||
|
|
// (note, e.target is never silent, e.topTarget can be silent be irrelevant.)
|
||
|
|
var target = e.target;
|
||
|
|
if (!target && this._checkPointer(e, x, y)) {
|
||
|
|
// To indicate users that this area is draggable, otherwise users probably cannot kwown
|
||
|
|
// that when hovering out of the shape but still inside the bounding rect.
|
||
|
|
return 'grab';
|
||
|
|
}
|
||
|
|
if (forReverse) {
|
||
|
|
return target && target.cursor || 'default';
|
||
|
|
}
|
||
|
|
};
|
||
|
|
RoamController.prototype.dispose = function () {
|
||
|
|
this.disable();
|
||
|
|
};
|
||
|
|
RoamController.prototype._mousedownHandler = function (e) {
|
||
|
|
if (eventTool.isMiddleOrRightButtonOnMouseUpDown(e) || eventConsumed(e)) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
var el = e.target;
|
||
|
|
while (el) {
|
||
|
|
if (el.draggable) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
// check if host is draggable
|
||
|
|
el = el.__hostTarget || el.parent;
|
||
|
|
}
|
||
|
|
var x = e.offsetX;
|
||
|
|
var y = e.offsetY;
|
||
|
|
// To determine dragging start, only by checking on mosedown, but not mousemove.
|
||
|
|
// Mouse can be out of target when mouse moving.
|
||
|
|
if (this._checkPointer(e, x, y)) {
|
||
|
|
this._x = x;
|
||
|
|
this._y = y;
|
||
|
|
this._dragging = true;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
RoamController.prototype._mousemoveHandler = function (e) {
|
||
|
|
var zr = this._zr;
|
||
|
|
if (e.gestureEvent === 'pinch' || interactionMutex.isTaken(zr, 'globalPan') || eventConsumed(e)) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
var x = e.offsetX;
|
||
|
|
var y = e.offsetY;
|
||
|
|
if (!this._dragging || !isAvailableBehavior('moveOnMouseMove', e, this._opt)) {
|
||
|
|
var cursorStyle = this._decideCursorStyle(e, x, y, false);
|
||
|
|
if (cursorStyle) {
|
||
|
|
zr.setCursorStyle(cursorStyle);
|
||
|
|
}
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
zr.setCursorStyle('grabbing');
|
||
|
|
var oldX = this._x;
|
||
|
|
var oldY = this._y;
|
||
|
|
var dx = x - oldX;
|
||
|
|
var dy = y - oldY;
|
||
|
|
this._x = x;
|
||
|
|
this._y = y;
|
||
|
|
if (this._opt.preventDefaultMouseMove) {
|
||
|
|
eventTool.stop(e.event);
|
||
|
|
}
|
||
|
|
e.__ecRoamConsumed = true;
|
||
|
|
trigger(this, 'pan', 'moveOnMouseMove', e, {
|
||
|
|
dx: dx,
|
||
|
|
dy: dy,
|
||
|
|
oldX: oldX,
|
||
|
|
oldY: oldY,
|
||
|
|
newX: x,
|
||
|
|
newY: y,
|
||
|
|
isAvailableBehavior: null
|
||
|
|
});
|
||
|
|
};
|
||
|
|
RoamController.prototype._mouseupHandler = function (e) {
|
||
|
|
if (eventConsumed(e)) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
var zr = this._zr;
|
||
|
|
if (!eventTool.isMiddleOrRightButtonOnMouseUpDown(e)) {
|
||
|
|
this._dragging = false;
|
||
|
|
var cursorStyle = this._decideCursorStyle(e, e.offsetX, e.offsetY, true);
|
||
|
|
if (cursorStyle) {
|
||
|
|
zr.setCursorStyle(cursorStyle);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
};
|
||
|
|
RoamController.prototype._mousewheelHandler = function (e) {
|
||
|
|
if (eventConsumed(e)) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
var shouldZoom = isAvailableBehavior('zoomOnMouseWheel', e, this._opt);
|
||
|
|
var shouldMove = isAvailableBehavior('moveOnMouseWheel', e, this._opt);
|
||
|
|
var wheelDelta = e.wheelDelta;
|
||
|
|
var absWheelDeltaDelta = Math.abs(wheelDelta);
|
||
|
|
var originX = e.offsetX;
|
||
|
|
var 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.
|
||
|
|
var factor = absWheelDeltaDelta > 3 ? 1.4 : absWheelDeltaDelta > 1 ? 1.2 : 1.1;
|
||
|
|
var scale = wheelDelta > 0 ? factor : 1 / factor;
|
||
|
|
this._checkTriggerMoveZoom(this, 'zoom', 'zoomOnMouseWheel', e, {
|
||
|
|
scale: scale,
|
||
|
|
originX: originX,
|
||
|
|
originY: originY,
|
||
|
|
isAvailableBehavior: null
|
||
|
|
});
|
||
|
|
}
|
||
|
|
if (shouldMove) {
|
||
|
|
// FIXME: Should do more test in different environment.
|
||
|
|
var absDelta = Math.abs(wheelDelta);
|
||
|
|
// wheelDelta of mouse wheel is bigger than touch pad.
|
||
|
|
var scrollDelta = (wheelDelta > 0 ? 1 : -1) * (absDelta > 3 ? 0.4 : absDelta > 1 ? 0.15 : 0.05);
|
||
|
|
this._checkTriggerMoveZoom(this, 'scrollMove', 'moveOnMouseWheel', e, {
|
||
|
|
scrollDelta: scrollDelta,
|
||
|
|
originX: originX,
|
||
|
|
originY: originY,
|
||
|
|
isAvailableBehavior: null
|
||
|
|
});
|
||
|
|
}
|
||
|
|
};
|
||
|
|
RoamController.prototype._pinchHandler = function (e) {
|
||
|
|
if (interactionMutex.isTaken(this._zr, 'globalPan') || eventConsumed(e)) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
var scale = e.pinchScale > 1 ? 1.1 : 1 / 1.1;
|
||
|
|
this._checkTriggerMoveZoom(this, 'zoom', null, e, {
|
||
|
|
scale: scale,
|
||
|
|
originX: e.pinchX,
|
||
|
|
originY: e.pinchY,
|
||
|
|
isAvailableBehavior: null
|
||
|
|
});
|
||
|
|
};
|
||
|
|
RoamController.prototype._checkTriggerMoveZoom = function (controller, eventName, behaviorToCheck, e, contollerEvent) {
|
||
|
|
if (controller._checkPointer(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);
|
||
|
|
e.__ecRoamConsumed = true;
|
||
|
|
trigger(controller, eventName, behaviorToCheck, e, contollerEvent);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
return RoamController;
|
||
|
|
}(Eventful);
|
||
|
|
function eventConsumed(e) {
|
||
|
|
return e.__ecRoamConsumed;
|
||
|
|
}
|
||
|
|
var innerZrStore = makeInner();
|
||
|
|
function ensureZrStore(zr) {
|
||
|
|
var store = innerZrStore(zr);
|
||
|
|
store.roam = store.roam || {};
|
||
|
|
store.uniform = store.uniform || {};
|
||
|
|
return store;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Listeners are sorted by z2/z/zlevel in descending order.
|
||
|
|
* This decides the precedence between different roam controllers if they are overlapped.
|
||
|
|
*
|
||
|
|
* [MEMO]: It's not easy to perfectly reconcile the conflicts caused by overlap.
|
||
|
|
* - Consider cases:
|
||
|
|
* - Multiple roam controllers overlapped.
|
||
|
|
* - Usually only the topmost can trigger roam.
|
||
|
|
* - Roam controllers overlap with other zr elements:
|
||
|
|
* - zr elements are relevant or irrelevent to the host of the roam controller. e.g., axis split line
|
||
|
|
* or series elements is relevant to a cartesian and should trigger roam.
|
||
|
|
* - zr elements is above or below the roam controller host, which affects the precedence of interaction.
|
||
|
|
* - zr elements may not silent only for triggering tooltip by hovering, which is available to roam;
|
||
|
|
* or may not silent for click, where roam is not preferable.
|
||
|
|
* - Approach - `addRoamZrListener+pointerChecker+onIrrelevantElement` (currently used):
|
||
|
|
* - Resolve the precedence between different roam controllers
|
||
|
|
* - But cannot prevent the handling on other zr elements that under the roam controller in z-order.
|
||
|
|
* - Approach - "use an invisible zr elements to receive the zr events to trigger roam":
|
||
|
|
* - More complicated in impl.
|
||
|
|
* - May cause bad cases where zr event cannot be receive due to other non-silient zr elements covering it.
|
||
|
|
*/
|
||
|
|
function addRoamZrListener(zr, eventType, listener, zInfoParsed) {
|
||
|
|
var store = ensureZrStore(zr);
|
||
|
|
var roam = store.roam;
|
||
|
|
var listenerList = roam[eventType] = roam[eventType] || [];
|
||
|
|
var idx = 0;
|
||
|
|
for (; idx < listenerList.length; idx++) {
|
||
|
|
var currZInfo = listenerList[idx].zInfoParsed;
|
||
|
|
if ((currZInfo.zlevel - zInfoParsed.zlevel || currZInfo.z - zInfoParsed.z || currZInfo.z2 - zInfoParsed.z2
|
||
|
|
// If all equals, the latter added one has a higher precedence.
|
||
|
|
) <= 0) {
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
listenerList.splice(idx, 0, {
|
||
|
|
listener: listener,
|
||
|
|
zInfoParsed: zInfoParsed
|
||
|
|
});
|
||
|
|
ensureUniformListener(zr, eventType);
|
||
|
|
}
|
||
|
|
function removeRoamZrListener(zr, eventType, listener) {
|
||
|
|
var store = ensureZrStore(zr);
|
||
|
|
var listenerList = store.roam[eventType] || [];
|
||
|
|
for (var idx = 0; idx < listenerList.length; idx++) {
|
||
|
|
if (listenerList[idx].listener === listener) {
|
||
|
|
listenerList.splice(idx, 1);
|
||
|
|
if (!listenerList.length) {
|
||
|
|
removeUniformListener(zr, eventType);
|
||
|
|
}
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
function ensureUniformListener(zr, eventType) {
|
||
|
|
var store = ensureZrStore(zr);
|
||
|
|
if (!store.uniform[eventType]) {
|
||
|
|
zr.on(eventType, store.uniform[eventType] = function (event) {
|
||
|
|
var listenerList = store.roam[eventType];
|
||
|
|
if (listenerList) {
|
||
|
|
for (var i = 0; i < listenerList.length; i++) {
|
||
|
|
listenerList[i].listener(event);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
function removeUniformListener(zr, eventType) {
|
||
|
|
var store = ensureZrStore(zr);
|
||
|
|
var uniform = store.uniform;
|
||
|
|
if (uniform[eventType]) {
|
||
|
|
zr.off(eventType, uniform[eventType]);
|
||
|
|
uniform[eventType] = null;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
function trigger(controller, eventName, behaviorToCheck, e, contollerEvent) {
|
||
|
|
// 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.trigger(eventName, contollerEvent);
|
||
|
|
}
|
||
|
|
// settings: {
|
||
|
|
// zoomOnMouseWheel
|
||
|
|
// moveOnMouseMove
|
||
|
|
// moveOnMouseWheel
|
||
|
|
// }
|
||
|
|
// The value can be: true / false / 'shift' / 'ctrl' / 'alt'.
|
||
|
|
function isAvailableBehavior(behaviorToCheck, e, settings) {
|
||
|
|
var setting = settings[behaviorToCheck];
|
||
|
|
return !behaviorToCheck || setting && (!isString(setting) || e.event[setting + 'Key']);
|
||
|
|
}
|
||
|
|
export default RoamController;
|