/* * 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 {createHashMap, each} from 'zrender/src/core/util'; import GlobalModel from '../model/Global'; import SeriesModel from '../model/Series'; import { SeriesOption, SeriesStackOptionMixin } from '../util/types'; import SeriesData, { DataCalculationInfo } from '../data/SeriesData'; import { addSafe } from '../util/number'; type StackInfo = Pick< DataCalculationInfo, 'stackedDimension' | 'isStackedByIndex' | 'stackedByDimension' | 'stackResultDimension' | 'stackedOverDimension' > & { data: SeriesData seriesModel: SeriesModel }; // (1) [Caution]: the logic is correct based on the premises: // data processing stage is blocked in stream. // See // (2) Only register once when import repeatedly. // Should be executed after series is filtered and before stack calculation. export default function dataStack(ecModel: GlobalModel) { const stackInfoMap = createHashMap(); ecModel.eachSeries(function (seriesModel: SeriesModel) { const stack = seriesModel.get('stack'); // Compatible: when `stack` is set as '', do not stack. if (stack) { const stackInfoList = stackInfoMap.get(stack) || stackInfoMap.set(stack, []); const data = seriesModel.getData(); const stackInfo: StackInfo = { // Used for calculate axis extent automatically. // TODO: Type getCalculationInfo return more specific type? stackResultDimension: data.getCalculationInfo('stackResultDimension'), stackedOverDimension: data.getCalculationInfo('stackedOverDimension'), stackedDimension: data.getCalculationInfo('stackedDimension'), stackedByDimension: data.getCalculationInfo('stackedByDimension'), isStackedByIndex: data.getCalculationInfo('isStackedByIndex'), data: data, seriesModel: seriesModel }; // If stacked on axis that do not support data stack. if (!stackInfo.stackedDimension || !(stackInfo.isStackedByIndex || stackInfo.stackedByDimension) ) { return; } stackInfoList.length && data.setCalculationInfo( 'stackedOnSeries', stackInfoList[stackInfoList.length - 1].seriesModel ); stackInfoList.push(stackInfo); } }); stackInfoMap.each(calculateStack); } function calculateStack(stackInfoList: StackInfo[]) { each(stackInfoList, function (targetStackInfo, idxInStack) { const resultVal: number[] = []; const resultNaN = [NaN, NaN]; const dims: [string, string] = [targetStackInfo.stackResultDimension, targetStackInfo.stackedOverDimension]; const targetData = targetStackInfo.data; const isStackedByIndex = targetStackInfo.isStackedByIndex; const stackStrategy = targetStackInfo.seriesModel.get('stackStrategy') || 'samesign'; // Should not write on raw data, because stack series model list changes // depending on legend selection. targetData.modify(dims, function (v0, v1, dataIndex) { let sum = targetData.get(targetStackInfo.stackedDimension, dataIndex) as number; // Consider `connectNulls` of line area, if value is NaN, stackedOver // should also be NaN, to draw a appropriate belt area. if (isNaN(sum)) { return resultNaN; } let byValue: number; let stackedDataRawIndex; if (isStackedByIndex) { stackedDataRawIndex = targetData.getRawIndex(dataIndex); } else { byValue = targetData.get(targetStackInfo.stackedByDimension, dataIndex) as number; } // If stackOver is NaN, chart view will render point on value start. let stackedOver = NaN; for (let j = idxInStack - 1; j >= 0; j--) { const stackInfo = stackInfoList[j]; // Has been optimized by inverted indices on `stackedByDimension`. if (!isStackedByIndex) { stackedDataRawIndex = stackInfo.data.rawIndexOf(stackInfo.stackedByDimension, byValue); } if (stackedDataRawIndex >= 0) { const val = stackInfo.data.getByRawIndex( stackInfo.stackResultDimension, stackedDataRawIndex ) as number; // Considering positive stack, negative stack and empty data if ( stackStrategy === 'all' // single stack group || (stackStrategy === 'positive' && val > 0) || (stackStrategy === 'negative' && val < 0) || (stackStrategy === 'samesign' && sum >= 0 && val > 0) // All positive stack || (stackStrategy === 'samesign' && sum <= 0 && val < 0) // All negative stack ) { // The sum has to be very small to be affected by the // floating arithmetic problem. An incorrect result will probably // cause axis min/max to be filtered incorrectly. sum = addSafe(sum, val); stackedOver = val; break; } } } resultVal[0] = sum; resultVal[1] = stackedOver; return resultVal; }); }); }