| /* |
| * 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. |
| */ |
| |
| // Only create one roam controller for each coordinate system. |
| // one roam controller might be refered by two inside data zoom |
| // components (for example, one for x and one for y). When user |
| // pan or zoom, only dispatch one action for those data zoom |
| // components. |
| |
| import RoamController, { RoamType } from '../../component/helper/RoamController'; |
| import * as throttleUtil from '../../util/throttle'; |
| import { makeInner } from '../../util/model'; |
| import { Dictionary, ZRElementEvent } from '../../util/types'; |
| import ExtensionAPI from '../../core/ExtensionAPI'; |
| import InsideZoomModel from './InsideZoomModel'; |
| import { each, curry, Curry1, HashMap, createHashMap } from 'zrender/src/core/util'; |
| import { |
| DataZoomPayloadBatchItem, collectReferCoordSysModelInfo, |
| DataZoomCoordSysMainType, DataZoomReferCoordSysInfo |
| } from './helper'; |
| import GlobalModel from '../../model/Global'; |
| import { CoordinateSystemHostModel } from '../../coord/CoordinateSystem'; |
| import { DataZoomGetRangeHandlers } from './InsideZoomView'; |
| import { EChartsExtensionInstallRegisters } from '../../extension'; |
| |
| |
| interface DataZoomInfo { |
| getRange: DataZoomGetRangeHandlers; |
| model: InsideZoomModel; |
| dzReferCoordSysInfo: DataZoomReferCoordSysInfo |
| } |
| |
| interface CoordSysRecord { |
| // key: dataZoom.uid |
| dataZoomInfoMap: HashMap<DataZoomInfo, string>; |
| model: CoordinateSystemHostModel, |
| // count: number |
| // coordId: string |
| controller: RoamController; |
| containsPoint: (e: ZRElementEvent, x: number, y: number) => boolean; |
| dispatchAction: Curry1<typeof dispatchAction, ExtensionAPI>; |
| } |
| |
| |
| const inner = makeInner<{ |
| // key: coordSysModel.uid |
| coordSysRecordMap: HashMap<CoordSysRecord, string>; |
| }, ExtensionAPI>(); |
| |
| export function setViewInfoToCoordSysRecord( |
| api: ExtensionAPI, |
| dataZoomModel: InsideZoomModel, |
| getRange: DataZoomGetRangeHandlers |
| ): void { |
| inner(api).coordSysRecordMap.each(function (coordSysRecord) { |
| const dzInfo = coordSysRecord.dataZoomInfoMap.get(dataZoomModel.uid); |
| if (dzInfo) { |
| dzInfo.getRange = getRange; |
| } |
| }); |
| } |
| |
| export function disposeCoordSysRecordIfNeeded(api: ExtensionAPI, dataZoomModel: InsideZoomModel) { |
| const coordSysRecordMap = inner(api).coordSysRecordMap; |
| const coordSysKeyArr = coordSysRecordMap.keys(); |
| for (let i = 0; i < coordSysKeyArr.length; i++) { |
| const coordSysKey = coordSysKeyArr[i]; |
| const coordSysRecord = coordSysRecordMap.get(coordSysKey); |
| const dataZoomInfoMap = coordSysRecord.dataZoomInfoMap; |
| if (dataZoomInfoMap) { |
| const dzUid = dataZoomModel.uid; |
| const dzInfo = dataZoomInfoMap.get(dzUid); |
| if (dzInfo) { |
| dataZoomInfoMap.removeKey(dzUid); |
| if (!dataZoomInfoMap.keys().length) { |
| disposeCoordSysRecord(coordSysRecordMap, coordSysRecord); |
| } |
| } |
| } |
| } |
| } |
| |
| function disposeCoordSysRecord( |
| coordSysRecordMap: HashMap<CoordSysRecord, string>, |
| coordSysRecord: CoordSysRecord |
| ): void { |
| if (coordSysRecord) { |
| coordSysRecordMap.removeKey(coordSysRecord.model.uid); |
| const controller = coordSysRecord.controller; |
| controller && controller.dispose(); |
| } |
| } |
| |
| function createCoordSysRecord(api: ExtensionAPI, coordSysModel: CoordinateSystemHostModel): CoordSysRecord { |
| // These init props will never change after record created. |
| const coordSysRecord: CoordSysRecord = { |
| model: coordSysModel, |
| containsPoint: curry(containsPoint, coordSysModel), |
| dispatchAction: curry(dispatchAction, api), |
| dataZoomInfoMap: null, |
| controller: null |
| }; |
| |
| // Must not do anything depends on coordSysRecord outside the event handler here, |
| // because coordSysRecord not completed yet. |
| const controller = coordSysRecord.controller = new RoamController(api.getZr()); |
| |
| each(['pan', 'zoom', 'scrollMove'] as const, function (eventName) { |
| controller.on(eventName, function (event) { |
| const batch: DataZoomPayloadBatchItem[] = []; |
| |
| coordSysRecord.dataZoomInfoMap.each(function (dzInfo) { |
| // Check whether the behaviors (zoomOnMouseWheel, moveOnMouseMove, |
| // moveOnMouseWheel, ...) enabled. |
| if (!event.isAvailableBehavior(dzInfo.model.option)) { |
| return; |
| } |
| |
| const method = (dzInfo.getRange || {} as DataZoomGetRangeHandlers)[eventName]; |
| const range = method && method( |
| dzInfo.dzReferCoordSysInfo, |
| coordSysRecord.model.mainType as DataZoomCoordSysMainType, |
| coordSysRecord.controller, |
| event as any |
| ); |
| |
| !dzInfo.model.get('disabled', true) && range && batch.push({ |
| dataZoomId: dzInfo.model.id, |
| start: range[0], |
| end: range[1] |
| }); |
| }); |
| |
| batch.length && coordSysRecord.dispatchAction(batch); |
| }); |
| }); |
| |
| return coordSysRecord; |
| } |
| |
| /** |
| * This action will be throttled. |
| */ |
| function dispatchAction(api: ExtensionAPI, batch: DataZoomPayloadBatchItem[]) { |
| if (!api.isDisposed()) { |
| api.dispatchAction({ |
| type: 'dataZoom', |
| animation: { |
| easing: 'cubicOut', |
| duration: 100 |
| }, |
| batch: batch |
| }); |
| } |
| } |
| |
| function containsPoint( |
| coordSysModel: CoordinateSystemHostModel, e: ZRElementEvent, x: number, y: number |
| ): boolean { |
| return coordSysModel.coordinateSystem.containPoint([x, y]); |
| } |
| |
| /** |
| * Merge roamController settings when multiple dataZooms share one roamController. |
| */ |
| function mergeControllerParams(dataZoomInfoMap: HashMap<{ model: InsideZoomModel }>) { |
| let controlType: RoamType; |
| // DO NOT use reserved word (true, false, undefined) as key literally. Even if encapsulated |
| // as string, it is probably revert to reserved word by compress tool. See #7411. |
| const prefix = 'type_'; |
| const typePriority: Dictionary<number> = { |
| 'type_true': 2, |
| 'type_move': 1, |
| 'type_false': 0, |
| 'type_undefined': -1 |
| }; |
| let preventDefaultMouseMove = true; |
| |
| dataZoomInfoMap.each(function (dataZoomInfo) { |
| const dataZoomModel = dataZoomInfo.model; |
| const oneType = dataZoomModel.get('disabled', true) |
| ? false |
| : dataZoomModel.get('zoomLock', true) |
| ? 'move' as const |
| : true; |
| if (typePriority[prefix + oneType] > typePriority[prefix + controlType]) { |
| controlType = oneType; |
| } |
| |
| // Prevent default move event by default. If one false, do not prevent. Otherwise |
| // users may be confused why it does not work when multiple insideZooms exist. |
| preventDefaultMouseMove = preventDefaultMouseMove |
| && dataZoomModel.get('preventDefaultMouseMove', true); |
| }); |
| |
| return { |
| controlType: controlType, |
| opt: { |
| // RoamController will enable all of these functionalities, |
| // and the final behavior is determined by its event listener |
| // provided by each inside zoom. |
| zoomOnMouseWheel: true, |
| moveOnMouseMove: true, |
| moveOnMouseWheel: true, |
| preventDefaultMouseMove: !!preventDefaultMouseMove |
| } |
| }; |
| } |
| |
| export function installDataZoomRoamProcessor(registers: EChartsExtensionInstallRegisters) { |
| |
| registers.registerProcessor( |
| registers.PRIORITY.PROCESSOR.FILTER, |
| function (ecModel: GlobalModel, api: ExtensionAPI): void { |
| const apiInner = inner(api); |
| const coordSysRecordMap = apiInner.coordSysRecordMap |
| || (apiInner.coordSysRecordMap = createHashMap<CoordSysRecord, string>()); |
| |
| coordSysRecordMap.each(function (coordSysRecord) { |
| // `coordSysRecordMap` always exists (because it holds the `roam controller`, which should |
| // better not re-create each time), but clear `dataZoomInfoMap` each round of the workflow. |
| coordSysRecord.dataZoomInfoMap = null; |
| }); |
| |
| ecModel.eachComponent( |
| { mainType: 'dataZoom', subType: 'inside' }, |
| function (dataZoomModel: InsideZoomModel) { |
| const dzReferCoordSysWrap = collectReferCoordSysModelInfo(dataZoomModel); |
| |
| each(dzReferCoordSysWrap.infoList, function (dzCoordSysInfo) { |
| |
| const coordSysUid = dzCoordSysInfo.model.uid; |
| const coordSysRecord = coordSysRecordMap.get(coordSysUid) |
| || coordSysRecordMap.set(coordSysUid, createCoordSysRecord(api, dzCoordSysInfo.model)); |
| |
| const dataZoomInfoMap = coordSysRecord.dataZoomInfoMap |
| || (coordSysRecord.dataZoomInfoMap = createHashMap<DataZoomInfo, string>()); |
| // Notice these props might be changed each time for a single dataZoomModel. |
| dataZoomInfoMap.set(dataZoomModel.uid, { |
| dzReferCoordSysInfo: dzCoordSysInfo, |
| model: dataZoomModel, |
| getRange: null |
| }); |
| }); |
| } |
| ); |
| |
| // (1) Merge dataZoom settings for each coord sys and set to the roam controller. |
| // (2) Clear coord sys if not refered by any dataZoom. |
| coordSysRecordMap.each(function (coordSysRecord) { |
| const controller = coordSysRecord.controller; |
| let firstDzInfo: DataZoomInfo; |
| const dataZoomInfoMap = coordSysRecord.dataZoomInfoMap; |
| |
| if (dataZoomInfoMap) { |
| const firstDzKey = dataZoomInfoMap.keys()[0]; |
| if (firstDzKey != null) { |
| firstDzInfo = dataZoomInfoMap.get(firstDzKey); |
| } |
| } |
| |
| if (!firstDzInfo) { |
| disposeCoordSysRecord(coordSysRecordMap, coordSysRecord); |
| return; |
| } |
| |
| const controllerParams = mergeControllerParams(dataZoomInfoMap); |
| controller.enable(controllerParams.controlType, controllerParams.opt); |
| |
| controller.setPointerChecker(coordSysRecord.containsPoint); |
| |
| throttleUtil.createOrUpdate( |
| coordSysRecord, |
| 'dispatchAction', |
| firstDzInfo.model.get('throttle', true), |
| 'fixRate' |
| ); |
| }); |
| }); |
| |
| } |