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