Spaces:
Running
Running
| /* | |
| * 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 * as echarts from '../../core/echarts'; | |
| import { createHashMap, each, HashMap, hasOwn, keys, map } from 'zrender/src/core/util'; | |
| import SeriesModel from '../../model/Series'; | |
| import { isCartesian2DSeries, findAxisModels } from './cartesianAxisHelper'; | |
| import { getDataDimensionsOnAxis, unionAxisExtentFromData } from '../axisHelper'; | |
| import { AxisBaseModel } from '../AxisBaseModel'; | |
| import Axis from '../Axis'; | |
| import GlobalModel from '../../model/Global'; | |
| import { Dictionary } from '../../util/types'; | |
| import { ScaleRawExtentInfo, ScaleRawExtentResult, ensureScaleRawExtentInfo } from '../scaleRawExtentInfo'; | |
| type AxisRecord = { | |
| condExtent: number[]; | |
| rawExtentInfo?: ScaleRawExtentInfo; | |
| rawExtentResult?: ScaleRawExtentResult | |
| tarExtent?: number[]; | |
| }; | |
| type SeriesRecord = { | |
| seriesModel: SeriesModel; | |
| xAxisModel: AxisBaseModel; | |
| yAxisModel: AxisBaseModel; | |
| }; | |
| // A tricky: the priority is just after dataZoom processor. | |
| // If dataZoom has fixed the min/max, this processor do not need to work. | |
| // TODO: SELF REGISTERED. | |
| echarts.registerProcessor(echarts.PRIORITY.PROCESSOR.FILTER + 10, { | |
| getTargetSeries: function (ecModel) { | |
| const seriesModelMap = createHashMap<SeriesModel>(); | |
| ecModel.eachSeries(function (seriesModel: SeriesModel) { | |
| isCartesian2DSeries(seriesModel) && seriesModelMap.set(seriesModel.uid, seriesModel); | |
| }); | |
| return seriesModelMap; | |
| }, | |
| overallReset: function (ecModel, api) { | |
| const seriesRecords = [] as SeriesRecord[]; | |
| const axisRecordMap = createHashMap<AxisRecord>(); | |
| prepareDataExtentOnAxis(ecModel, axisRecordMap, seriesRecords); | |
| calculateFilteredExtent(axisRecordMap, seriesRecords); | |
| shrinkAxisExtent(axisRecordMap); | |
| } | |
| }); | |
| function prepareDataExtentOnAxis( | |
| ecModel: GlobalModel, | |
| axisRecordMap: HashMap<AxisRecord>, | |
| seriesRecords: SeriesRecord[] | |
| ): void { | |
| ecModel.eachSeries(function (seriesModel: SeriesModel) { | |
| if (!isCartesian2DSeries(seriesModel)) { | |
| return; | |
| } | |
| const axesModelMap = findAxisModels(seriesModel); | |
| const xAxisModel = axesModelMap.xAxisModel; | |
| const yAxisModel = axesModelMap.yAxisModel; | |
| const xAxis = xAxisModel.axis; | |
| const yAxis = yAxisModel.axis; | |
| const xRawExtentInfo = xAxis.scale.rawExtentInfo; | |
| const yRawExtentInfo = yAxis.scale.rawExtentInfo; | |
| const data = seriesModel.getData(); | |
| // If either axis controlled by other filter like "dataZoom", | |
| // use the rule of dataZoom rather than adopting the rules here. | |
| if ( | |
| (xRawExtentInfo && xRawExtentInfo.frozen) | |
| || (yRawExtentInfo && yRawExtentInfo.frozen) | |
| ) { | |
| return; | |
| } | |
| seriesRecords.push({ | |
| seriesModel: seriesModel, | |
| xAxisModel: xAxisModel, | |
| yAxisModel: yAxisModel | |
| }); | |
| // FIXME: this logic needs to be consistent with | |
| // `coord/cartesian/Grid.ts#_updateScale`. | |
| // It's not good to implement one logic in multiple places. | |
| unionAxisExtentFromData(prepareAxisRecord(axisRecordMap, xAxisModel).condExtent, data, xAxis.dim); | |
| unionAxisExtentFromData(prepareAxisRecord(axisRecordMap, yAxisModel).condExtent, data, yAxis.dim); | |
| }); | |
| } | |
| function calculateFilteredExtent( | |
| axisRecordMap: HashMap<AxisRecord>, | |
| seriesRecords: SeriesRecord[] | |
| ) { | |
| each(seriesRecords, function (seriesRecord) { | |
| const xAxisModel = seriesRecord.xAxisModel; | |
| const yAxisModel = seriesRecord.yAxisModel; | |
| const xAxis = xAxisModel.axis; | |
| const yAxis = yAxisModel.axis; | |
| const xAxisRecord = prepareAxisRecord(axisRecordMap, xAxisModel); | |
| const yAxisRecord = prepareAxisRecord(axisRecordMap, yAxisModel); | |
| xAxisRecord.rawExtentInfo = ensureScaleRawExtentInfo( | |
| xAxis.scale, xAxisModel, xAxisRecord.condExtent | |
| ); | |
| yAxisRecord.rawExtentInfo = ensureScaleRawExtentInfo( | |
| yAxis.scale, yAxisModel, yAxisRecord.condExtent | |
| ); | |
| xAxisRecord.rawExtentResult = xAxisRecord.rawExtentInfo.calculate(); | |
| yAxisRecord.rawExtentResult = yAxisRecord.rawExtentInfo.calculate(); | |
| // If the "xAxis" is set `min`/`max`, some data items might be out of the cartesian. | |
| // then the "yAxis" may needs to calculate extent only based on the data items inside | |
| // the cartesian (similar to what "dataZoom" did). | |
| // A typical case is bar-racing, where bars ara sort dynamically and may only need to | |
| // displayed part of the whole bars. | |
| const data = seriesRecord.seriesModel.getData(); | |
| // For duplication removal. | |
| const condDimMap: Dictionary<boolean> = {}; | |
| const tarDimMap: Dictionary<boolean> = {}; | |
| let condAxis: Axis; | |
| let tarAxisRecord: AxisRecord; | |
| function addCondition(axis: Axis, axisRecord: AxisRecord) { | |
| // But for simplicity and safety and performance, we only adopt this | |
| // feature on category axis at present. | |
| const condExtent = axisRecord.condExtent; | |
| const rawExtentResult = axisRecord.rawExtentResult; | |
| if (axis.type === 'category' | |
| && (condExtent[0] < rawExtentResult.min || rawExtentResult.max < condExtent[1]) | |
| ) { | |
| each(getDataDimensionsOnAxis(data, axis.dim), function (dataDim) { | |
| if (!hasOwn(condDimMap, dataDim)) { | |
| condDimMap[dataDim] = true; | |
| condAxis = axis; | |
| } | |
| }); | |
| } | |
| } | |
| function addTarget(axis: Axis, axisRecord: AxisRecord) { | |
| const rawExtentResult = axisRecord.rawExtentResult; | |
| if (axis.type !== 'category' | |
| && (!rawExtentResult.minFixed || !rawExtentResult.maxFixed) | |
| ) { | |
| each(getDataDimensionsOnAxis(data, axis.dim), function (dataDim) { | |
| if (!hasOwn(condDimMap, dataDim) && !hasOwn(tarDimMap, dataDim)) { | |
| tarDimMap[dataDim] = true; | |
| tarAxisRecord = axisRecord; | |
| } | |
| }); | |
| } | |
| } | |
| addCondition(xAxis, xAxisRecord); | |
| addCondition(yAxis, yAxisRecord); | |
| addTarget(xAxis, xAxisRecord); | |
| addTarget(yAxis, yAxisRecord); | |
| const condDims = keys(condDimMap); | |
| const tarDims = keys(tarDimMap); | |
| const tarDimExtents = map(tarDims, function () { | |
| return initExtent(); | |
| }); | |
| const condDimsLen = condDims.length; | |
| const tarDimsLen = tarDims.length; | |
| if (!condDimsLen || !tarDimsLen) { | |
| return; | |
| } | |
| const singleCondDim = condDimsLen === 1 ? condDims[0] : null; | |
| const singleTarDim = tarDimsLen === 1 ? tarDims[0] : null; | |
| const dataLen = data.count(); | |
| // Time consuming, because this is a "block task". | |
| // Simple optimization for the vast majority of cases. | |
| if (singleCondDim && singleTarDim) { | |
| for (let dataIdx = 0; dataIdx < dataLen; dataIdx++) { | |
| const condVal = data.get(singleCondDim, dataIdx) as number; | |
| if (condAxis.scale.isInExtentRange(condVal)) { | |
| unionExtent(tarDimExtents[0], data.get(singleTarDim, dataIdx) as number); | |
| } | |
| } | |
| } | |
| else { | |
| for (let dataIdx = 0; dataIdx < dataLen; dataIdx++) { | |
| for (let j = 0; j < condDimsLen; j++) { | |
| const condVal = data.get(condDims[j], dataIdx) as number; | |
| if (condAxis.scale.isInExtentRange(condVal)) { | |
| for (let k = 0; k < tarDimsLen; k++) { | |
| unionExtent(tarDimExtents[k], data.get(tarDims[k], dataIdx) as number); | |
| } | |
| // Any one dim is in range means satisfied. | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| each(tarDimExtents, function (tarDimExtent, i) { | |
| const dim = tarDims[i]; | |
| // FIXME: if there has been approximateExtent set? | |
| data.setApproximateExtent(tarDimExtent as [number, number], dim); | |
| const tarAxisExtent = tarAxisRecord.tarExtent = tarAxisRecord.tarExtent || initExtent(); | |
| unionExtent(tarAxisExtent, tarDimExtent[0]); | |
| unionExtent(tarAxisExtent, tarDimExtent[1]); | |
| }); | |
| }); | |
| } | |
| function shrinkAxisExtent(axisRecordMap: HashMap<AxisRecord>) { | |
| axisRecordMap.each(function (axisRecord) { | |
| const tarAxisExtent = axisRecord.tarExtent; | |
| if (tarAxisExtent) { | |
| const rawExtentResult = axisRecord.rawExtentResult; | |
| const rawExtentInfo = axisRecord.rawExtentInfo; | |
| // Shink the original extent. | |
| if (!rawExtentResult.minFixed && tarAxisExtent[0] > rawExtentResult.min) { | |
| rawExtentInfo.modifyDataMinMax('min', tarAxisExtent[0]); | |
| } | |
| if (!rawExtentResult.maxFixed && tarAxisExtent[1] < rawExtentResult.max) { | |
| rawExtentInfo.modifyDataMinMax('max', tarAxisExtent[1]); | |
| } | |
| } | |
| }); | |
| } | |
| function prepareAxisRecord( | |
| axisRecordMap: HashMap<AxisRecord>, | |
| axisModel: AxisBaseModel | |
| ): AxisRecord { | |
| return axisRecordMap.get(axisModel.uid) | |
| || axisRecordMap.set(axisModel.uid, { condExtent: initExtent() }); | |
| } | |
| function initExtent() { | |
| return [Infinity, -Infinity]; | |
| } | |
| function unionExtent(extent: number[], val: number) { | |
| val < extent[0] && (extent[0] = val); | |
| val > extent[1] && (extent[1] = val); | |
| } | |