| /* | |
| * 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' | |
| ); | |
| }); | |
| }); | |
| } |