未验证 提交 fca7f985 编写于 作者: S sushuang 提交者: GitHub

Merge pull request #12832 from apache/extent_filtered_by_other_axis

Extent filtered by other axis
......@@ -26,9 +26,10 @@ import SeriesModel from '../../model/Series';
import ExtensionAPI from '../../ExtensionAPI';
import { Dictionary } from '../../util/types';
// TODO Polar?
import CartesianAxisModel from '../../coord/cartesian/AxisModel';
import DataZoomModel from './DataZoomModel';
import { AxisBaseModel } from '../../coord/AxisBaseModel';
import { unionAxisExtentFromData } from '../../coord/axisHelper';
import { ensureScaleRawExtentInfo } from '../../coord/scaleRawExtentInfo';
const each = zrUtil.each;
const asc = numberUtil.asc;
......@@ -127,32 +128,6 @@ class AxisProxy {
return this.ecModel.getComponent(this._dimName + 'Axis', this._axisIndex) as AxisBaseModel;
}
getOtherAxisModel() {
const axisDim = this._dimName;
const ecModel = this.ecModel;
const axisModel = this.getAxisModel();
const isCartesian = axisDim === 'x' || axisDim === 'y';
let otherAxisDim: 'x' | 'y' | 'radius' | 'angle';
let coordSysIndexName: 'gridIndex' | 'polarIndex';
if (isCartesian) {
coordSysIndexName = 'gridIndex';
otherAxisDim = axisDim === 'x' ? 'y' : 'x';
}
else {
coordSysIndexName = 'polarIndex';
otherAxisDim = axisDim === 'angle' ? 'radius' : 'angle';
}
let foundOtherAxisModel;
ecModel.eachComponent(otherAxisDim + 'Axis', function (otherAxisModel) {
if (((otherAxisModel as CartesianAxisModel).get(coordSysIndexName as 'gridIndex') || 0)
=== ((axisModel as CartesianAxisModel).get(coordSysIndexName as 'gridIndex') || 0)
) {
foundOtherAxisModel = otherAxisModel;
}
});
return foundOtherAxisModel;
}
getMinMaxSpan() {
return zrUtil.clone(this._minMaxSpan);
}
......@@ -291,15 +266,6 @@ class AxisProxy {
this._setAxisModel();
}
restore(dataZoomModel: DataZoomModel) {
if (dataZoomModel !== this._dataZoomModel) {
return;
}
this._valueWindow = this._percentWindow = null;
this._setAxisModel(true);
}
filterData(dataZoomModel: DataZoomModel, api: ExtensionAPI) {
if (dataZoomModel !== this._dataZoomModel) {
return;
......@@ -421,7 +387,7 @@ class AxisProxy {
}, this);
}
private _setAxisModel(isRestore?: boolean) {
private _setAxisModel() {
const axisModel = this.getAxisModel();
......@@ -435,34 +401,29 @@ class AxisProxy {
// [0, 500]: arbitrary value, guess axis extent.
let precision = numberUtil.getPixelPrecision(valueWindow, [0, 500]);
precision = Math.min(precision, 20);
// isRestore or isFull
const useOrigin = isRestore || (percentWindow[0] === 0 && percentWindow[1] === 100);
axisModel.setRange(
useOrigin ? null : +valueWindow[0].toFixed(precision),
useOrigin ? null : +valueWindow[1].toFixed(precision)
);
// For value axis, if min/max/scale are not set, we just use the extent obtained
// by series data, which may be a little different from the extent calculated by
// `axisHelper.getScaleExtent`. But the different just affects the experience a
// little when zooming. So it will not be fixed until some users require it strongly.
const rawExtentInfo = axisModel.axis.scale.rawExtentInfo;
if (percentWindow[0] !== 0) {
rawExtentInfo.setDeterminedMinMax('min', +valueWindow[0].toFixed(precision));
}
if (percentWindow[1] !== 100) {
rawExtentInfo.setDeterminedMinMax('max', +valueWindow[1].toFixed(precision));
}
rawExtentInfo.freeze();
}
}
function calculateDataExtent(axisProxy: AxisProxy, axisDim: string, seriesModels: SeriesModel[]) {
let dataExtent = [Infinity, -Infinity];
const dataExtent = [Infinity, -Infinity];
each(seriesModels, function (seriesModel) {
const seriesData = seriesModel.getData();
if (seriesData) {
each(seriesData.mapDimensionsAll(axisDim), function (dim) {
const seriesExtent = seriesData.getApproximateExtent(dim);
seriesExtent[0] < dataExtent[0] && (dataExtent[0] = seriesExtent[0]);
seriesExtent[1] > dataExtent[1] && (dataExtent[1] = seriesExtent[1]);
});
}
unionAxisExtentFromData(dataExtent, seriesModel.getData(), axisDim);
});
if (dataExtent[1] < dataExtent[0]) {
dataExtent = [NaN, NaN];
}
// It is important to get "consistent" extent when more then one axes is
// controlled by a `dataZoom`, otherwise those axes will not be synchronized
// when zooming. But it is difficult to know what is "consistent", considering
......@@ -472,46 +433,10 @@ function calculateDataExtent(axisProxy: AxisProxy, axisDim: string, seriesModels
// extent can be obtained from axis.data).
// Nevertheless, user can set min/max/scale on axes to make extent of axes
// consistent.
fixExtentByAxis(axisProxy, dataExtent);
return dataExtent as [number, number];
}
function fixExtentByAxis(axisProxy: AxisProxy, dataExtent: number[]) {
const axisModel = axisProxy.getAxisModel() as CartesianAxisModel;
const min = axisModel.getMin(true);
// For category axis, if min/max/scale are not set, extent is determined
// by axis.data by default.
const isCategoryAxis = axisModel.get('type') === 'category';
const axisDataLen = isCategoryAxis && axisModel.getCategories().length;
if (min != null && min !== 'dataMin' && typeof min !== 'function') {
dataExtent[0] = min;
}
else if (isCategoryAxis) {
dataExtent[0] = axisDataLen > 0 ? 0 : NaN;
}
const max = axisModel.getMax(true);
if (max != null && max !== 'dataMax' && typeof max !== 'function') {
dataExtent[1] = max;
}
else if (isCategoryAxis) {
dataExtent[1] = axisDataLen > 0 ? axisDataLen - 1 : NaN;
}
if (!axisModel.get('scale', true)) {
dataExtent[0] > 0 && (dataExtent[0] = 0);
dataExtent[1] < 0 && (dataExtent[1] = 0);
}
// For value axis, if min/max/scale are not set, we just use the extent obtained
// by series data, which may be a little different from the extent calculated by
// `axisHelper.getScaleExtent`. But the different just affects the experience a
// little when zooming. So it will not be fixed until some users require it strongly.
const axisModel = axisProxy.getAxisModel();
const rawExtentResult = ensureScaleRawExtentInfo(axisModel.axis.scale, axisModel, dataExtent).calculate();
return dataExtent;
return [rawExtentResult.min, rawExtentResult.max] as [number, number];
}
export default AxisProxy;
......@@ -268,10 +268,10 @@ class DataZoomModel<Opts extends DataZoomOption = DataZoomOption> extends Compon
this._resetTarget();
this._giveAxisProxies();
this._prepareAxisProxies();
}
private _giveAxisProxies() {
private _prepareAxisProxies() {
const axisProxies = this._axisProxies;
this.eachTargetAxis(function (dimNames, axisIndex, dataZoomModel, ecModel) {
......
......@@ -22,7 +22,7 @@ import {createHashMap, each} from 'zrender/src/core/util';
import SeriesModel from '../../model/Series';
import DataZoomModel from './DataZoomModel';
echarts.registerProcessor({
echarts.registerProcessor(echarts.PRIORITY.PROCESSOR.FILTER, {
// `dataZoomProcessor` will only be performed in needed series. Consider if
// there is a line series and a pie series, it is better not to update the
......
......@@ -32,6 +32,7 @@ export interface DataZoomPayloadBatchItem {
const AXIS_DIMS = ['x', 'y', 'z', 'radius', 'angle', 'single'] as const;
// Supported coords.
// FIXME: polar has been broken (but rarely used).
const COORDS = ['cartesian2d', 'polar', 'singleAxis'] as const;
export function isCoordSupported(coordType: string) {
......
......@@ -22,6 +22,7 @@ import * as zrUtil from 'zrender/src/core/util';
import * as graphic from '../util/graphic';
import './axis';
import '../coord/cartesian/defaultAxisExtentFromData';
import ComponentView from '../view/Component';
import GlobalModel from '../model/Global';
import GridModel from '../coord/cartesian/GridModel';
......
......@@ -20,7 +20,7 @@
import {
TextCommonOption, LineStyleOption, OrdinalRawValue, ZRColor,
AreaStyleOption, ComponentOption, ColorString,
AnimationOptionMixin, Dictionary
AnimationOptionMixin, Dictionary, ScaleDataValue
} from '../util/types';
......@@ -71,21 +71,15 @@ export interface AxisBaseOption extends ComponentOption,
boundaryGap?: boolean | [number | string, number | string];
// Min value of the axis. can be:
// + a number
// + ScaleDataValue
// + 'dataMin': use the min value in data.
// + null/undefined: auto decide min value (consider pretty look and boundaryGap).
min?: number | 'dataMin' | ((extent: {min: number, max: number}) => number);
min?: ScaleDataValue | 'dataMin' | ((extent: {min: number, max: number}) => ScaleDataValue);
// Max value of the axis. can be:
// + a number
// + ScaleDataValue
// + 'dataMax': use the max value in data.
// + null/undefined: auto decide max value (consider pretty look and boundaryGap).
max?: number | 'dataMax' | ((extent: {min: number, max: number}) => number);
// Readonly prop, specifies start value of the range when using data zoom.
// Only for internal usage.
rangeStart?: number;
// Readonly prop, specifies end value of the range when using data zoom.
// Only for internal usage.
rangeEnd?: number;
max?: ScaleDataValue | 'dataMax' | ((extent: {min: number, max: number}) => ScaleDataValue);
// Optional value can be:
// + `false`: always include value 0.
// + `true`: the extent do not consider value 0.
......
......@@ -17,12 +17,10 @@
* under the License.
*/
import {__DEV__} from '../config';
import * as zrUtil from 'zrender/src/core/util';
import OrdinalScale from '../scale/Ordinal';
import IntervalScale from '../scale/Interval';
import Scale from '../scale/Scale';
import * as numberUtil from '../util/number';
import {
prepareLayoutBarSeries,
makeColumnLayout,
......@@ -37,119 +35,31 @@ import LogScale from '../scale/Log';
import Axis from './Axis';
import { AxisBaseOption } from './axisCommonTypes';
import type CartesianAxisModel from './cartesian/AxisModel';
import List from '../data/List';
import { getStackedDimension } from '../data/helper/dataStackHelper';
import { Dictionary, ScaleDataValue } from '../util/types';
import { ensureScaleRawExtentInfo } from './scaleRawExtentInfo';
type BarWidthAndOffset = ReturnType<typeof makeColumnLayout>;
/**
* Get axis scale extent before niced.
* Item of returned array can only be number (including Infinity and NaN).
*
* Caution:
* Precondition of calling this method:
* The scale extent has been initailized using series data extent via
* `scale.setExtent` or `scale.unionExtentFromData`;
*/
export function getScaleExtent(scale: Scale, model: AxisBaseModel) {
const scaleType = scale.type;
const rawExtentResult = ensureScaleRawExtentInfo(scale, model, scale.getExtent()).calculate();
let min = model.getMin();
let max = model.getMax();
const originalExtent = scale.getExtent();
let axisDataLen;
let boundaryGapInner: number[];
let span;
if (scaleType === 'ordinal') {
axisDataLen = model.getCategories().length;
}
else {
const boundaryGap = model.get('boundaryGap');
const boundaryGapArr = zrUtil.isArray(boundaryGap)
? boundaryGap : [boundaryGap || 0, boundaryGap || 0];
if (typeof boundaryGapArr[0] === 'boolean' || typeof boundaryGapArr[1] === 'boolean') {
if (__DEV__) {
console.warn('Boolean type for boundaryGap is only '
+ 'allowed for ordinal axis. Please use string in '
+ 'percentage instead, e.g., "20%". Currently, '
+ 'boundaryGap is set to be 0.');
}
boundaryGapInner = [0, 0];
}
else {
boundaryGapInner = [
numberUtil.parsePercent(boundaryGapArr[0], 1),
numberUtil.parsePercent(boundaryGapArr[1], 1)
];
}
span = (originalExtent[1] - originalExtent[0])
|| Math.abs(originalExtent[0]);
}
// Notice: When min/max is not set (that is, when there are null/undefined,
// which is the most common case), these cases should be ensured:
// (1) For 'ordinal', show all axis.data.
// (2) For others:
// + `boundaryGap` is applied (if min/max set, boundaryGap is
// disabled).
// + If `needCrossZero`, min/max should be zero, otherwise, min/max should
// be the result that originalExtent enlarged by boundaryGap.
// (3) If no data, it should be ensured that `scale.setBlank` is set.
// FIXME
// (1) When min/max is 'dataMin' or 'dataMax', should boundaryGap be able to used?
// (2) When `needCrossZero` and all data is positive/negative, should it be ensured
// that the results processed by boundaryGap are positive/negative?
if (min === 'dataMin') {
min = originalExtent[0];
}
else if (typeof min === 'function') {
min = min({
min: originalExtent[0],
max: originalExtent[1]
});
}
if (max === 'dataMax') {
max = originalExtent[1];
}
else if (typeof max === 'function') {
max = max({
min: originalExtent[0],
max: originalExtent[1]
});
}
scale.setBlank(rawExtentResult.isBlank);
const fixMin = min != null;
const fixMax = max != null;
if (min == null) {
min = scaleType === 'ordinal'
? (axisDataLen ? 0 : NaN)
: originalExtent[0] - boundaryGapInner[0] * span;
}
if (max == null) {
max = scaleType === 'ordinal'
? (axisDataLen ? axisDataLen - 1 : NaN)
: originalExtent[1] + boundaryGapInner[1] * span;
}
(min == null || !isFinite(min)) && (min = NaN);
(max == null || !isFinite(max)) && (max = NaN);
scale.setBlank(
zrUtil.eqNaN(min)
|| zrUtil.eqNaN(max)
|| ((scale instanceof OrdinalScale) && !scale.getOrdinalMeta().categories.length)
);
// Evaluate if axis needs cross zero
if (model.getNeedCrossZero()) {
// Axis is over zero and min is not set
if (min > 0 && max > 0 && !fixMin) {
min = 0;
}
// Axis is under zero and max is not set
if (min < 0 && max < 0 && !fixMax) {
max = 0;
}
}
let min = rawExtentResult.min;
let max = rawExtentResult.max;
// If bars are placed on a base axis of type time or interval account for axis boundary overflow and current axis
// is base axis
......@@ -185,8 +95,8 @@ export function getScaleExtent(scale: Scale, model: AxisBaseModel) {
extent: [min, max],
// "fix" means "fixed", the value should not be
// changed in the subsequent steps.
fixMin: fixMin,
fixMax: fixMax
fixMin: rawExtentResult.minFixed,
fixMax: rawExtentResult.maxFixed
};
}
......@@ -230,6 +140,9 @@ function adjustScaleForOverflow(
return {min: min, max: max};
}
// Precondition of calling this method:
// The scale extent has been initailized using series data extent via
// `scale.setExtent` or `scale.unionExtentFromData`;
export function niceScaleExtent(scale: Scale, model: AxisBaseModel) {
const extentInfo = getScaleExtent(scale, model);
const extent = extentInfo.extent;
......@@ -427,3 +340,36 @@ export function shouldShowAllLabels(axis: Axis) {
&& getOptionCategoryInterval(axis.getLabelModel()) === 0;
}
export function getDataDimensionsOnAxis(data: List, axisDim: string) {
// Remove duplicated dat dimensions caused by `getStackedDimension`.
const dataDimMap = {} as Dictionary<boolean>;
// Currently `mapDimensionsAll` will contian stack result dimension ('__\0ecstackresult').
// PENDING: is it reasonable? Do we need to remove the original dim from "coord dim" since
// there has been stacked result dim?
zrUtil.each(data.mapDimensionsAll(axisDim), function (dataDim) {
// For example, the extent of the orginal dimension
// is [0.1, 0.5], the extent of the `stackResultDimension`
// is [7, 9], the final extent should NOT include [0.1, 0.5],
// because there is no graphic corresponding to [0.1, 0.5].
// See the case in `test/area-stack.html` `main1`, where area line
// stack needs `yAxis` not start from 0.
dataDimMap[getStackedDimension(data, dataDim)] = true;
});
return zrUtil.keys(dataDimMap);
}
export function unionAxisExtentFromData(dataExtent: number[], data: List, axisDim: string): void {
if (data) {
zrUtil.each(getDataDimensionsOnAxis(data, axisDim), function (dim) {
const seriesExtent = data.getApproximateExtent(dim);
seriesExtent[0] < dataExtent[0] && (dataExtent[0] = seriesExtent[0]);
seriesExtent[1] > dataExtent[1] && (dataExtent[1] = seriesExtent[1]);
});
}
}
export function parseAxisModelMinMax(scale: Scale, minMax: ScaleDataValue): number {
return minMax == null ? null
: zrUtil.eqNaN(minMax) ? NaN
: scale.parse(minMax);
}
......@@ -30,48 +30,9 @@ interface AxisModelCommonMixin<Opt extends AxisBaseOption> extends Pick<Model<Op
class AxisModelCommonMixin<Opt extends AxisBaseOption> {
/**
* @return min value or 'dataMin' or null/undefined (means auto) or NaN
*/
getMin(origin?: boolean): AxisBaseOption['min'] | number {
const option = this.option;
let min = (!origin && option.rangeStart != null)
? option.rangeStart : option.min;
if (this.axis
&& min != null
&& min !== 'dataMin'
&& typeof min !== 'function'
&& !zrUtil.eqNaN(min)
) {
min = this.axis.scale.parse(min);
}
return min;
}
/**
* @return max value or 'dataMax' or null/undefined (means auto) or NaN
*/
getMax(origin?: boolean): AxisBaseOption['max'] | number {
const option = this.option;
let max = (!origin && option.rangeEnd != null)
? option.rangeEnd : option.max;
if (this.axis
&& max != null
&& max !== 'dataMax'
&& typeof max !== 'function'
&& !zrUtil.eqNaN(max)
) {
max = this.axis.scale.parse(max);
}
return max;
}
getNeedCrossZero(): boolean {
const option = this.option;
return (option.rangeStart != null || option.rangeEnd != null)
? false : !option.scale;
return !option.scale;
}
/**
......@@ -82,22 +43,6 @@ class AxisModelCommonMixin<Opt extends AxisBaseOption> {
return;
}
/**
* @param rangeStart Can only be finite number or null/undefined or NaN.
* @param rangeEnd Can only be finite number or null/undefined or NaN.
*/
setRange(rangeStart: number, rangeEnd: number): void {
this.option.rangeStart = rangeStart;
this.option.rangeEnd = rangeEnd;
}
/**
* Reset range
*/
resetRange(): void {
// rangeStart and rangeEnd is readonly.
this.option.rangeStart = this.option.rangeEnd = null;
}
}
export {AxisModelCommonMixin};
......@@ -44,21 +44,6 @@ class CartesianAxisModel extends ComponentModel<CartesianAxisOption>
axis: Axis2D;
init(...args: any) {
super.init.apply(this, args);
this.resetRange();
}
mergeOption(...args: any) {
super.mergeOption.apply(this, args);
this.resetRange();
}
restoreData(...args: any) {
super.restoreData.apply(this, args);
this.resetRange();
}
getCoordSysModel(): GridModel {
return this.ecModel.queryComponents({
mainType: 'grid',
......
......@@ -24,18 +24,18 @@
*/
import {__DEV__} from '../../config';
import {isObject, each, map, indexOf, retrieve, retrieve3} from 'zrender/src/core/util';
import {isObject, each, indexOf, retrieve3} from 'zrender/src/core/util';
import {getLayoutRect, LayoutRect} from '../../util/layout';
import {
createScaleByModel,
ifAxisCrossZero,
niceScaleExtent,
estimateLabelUnionRect
estimateLabelUnionRect,
getDataDimensionsOnAxis
} from '../../coord/axisHelper';
import Cartesian2D, {cartesian2DDimensions} from './Cartesian2D';
import Axis2D from './Axis2D';
import CoordinateSystemManager from '../../CoordinateSystem';
import {getStackedDimension} from '../../data/helper/dataStackHelper';
import {ParsedModelFinder} from '../../util/model';
// Depends on GridModel, AxisModel, which performs preprocess.
......@@ -47,7 +47,7 @@ import { Dictionary } from 'zrender/src/core/types';
import {CoordinateSystemMaster} from '../CoordinateSystem';
import { ScaleDataValue } from '../../util/types';
import List from '../../data/List';
import SeriesModel from '../../model/Series';
import { isCartesian2DSeries, findAxisModels } from './cartesianAxisHelper';
type Cartesian2DDimensionName = 'x' | 'y';
......@@ -412,10 +412,10 @@ class Grid implements CoordinateSystemMaster {
axis.scale.setExtent(Infinity, -Infinity);
});
ecModel.eachSeries(function (seriesModel) {
if (isCartesian2D(seriesModel)) {
const axesModels = findAxesModels(seriesModel);
const xAxisModel = axesModels[0];
const yAxisModel = axesModels[1];
if (isCartesian2DSeries(seriesModel)) {
const axesModelMap = findAxisModels(seriesModel);
const xAxisModel = axesModelMap.xAxisModel;
const yAxisModel = axesModelMap.yAxisModel;
if (!isAxisUsedInTheGrid(xAxisModel, gridModel)
|| !isAxisUsedInTheGrid(yAxisModel, gridModel)
......@@ -438,13 +438,8 @@ class Grid implements CoordinateSystemMaster {
}, this);
function unionExtent(data: List, axis: Axis2D): void {
each(data.mapDimensionsAll(axis.dim), function (dim) {
axis.scale.unionExtentFromData(
// For example, the extent of the orginal dimension
// is [0.1, 0.5], the extent of the `stackResultDimension`
// is [7, 9], the final extent should not include [0.1, 0.5].
data, getStackedDimension(data, dim)
);
each(getDataDimensionsOnAxis(data, axis.dim), function (dim) {
axis.scale.unionExtentFromData(data, dim);
});
}
}
......@@ -486,13 +481,13 @@ class Grid implements CoordinateSystemMaster {
// Inject the coordinateSystems into seriesModel
ecModel.eachSeries(function (seriesModel) {
if (!isCartesian2D(seriesModel)) {
if (!isCartesian2DSeries(seriesModel)) {
return;
}
const axesModels = findAxesModels(seriesModel);
const xAxisModel = axesModels[0];
const yAxisModel = axesModels[1];
const axesModelMap = findAxisModels(seriesModel);
const xAxisModel = axesModelMap.xAxisModel;
const yAxisModel = axesModelMap.yAxisModel;
const gridModel = xAxisModel.getCoordSysModel();
......@@ -612,28 +607,6 @@ function updateAxisTransform(axis: Axis2D, coordBase: number) {
};
}
const axesTypes = ['xAxis', 'yAxis'];
function findAxesModels(seriesModel: SeriesModel): CartesianAxisModel[] {
return map(axesTypes, function (axisType) {
const axisModel = seriesModel.getReferringComponents(axisType)[0] as CartesianAxisModel;
if (__DEV__) {
if (!axisModel) {
throw new Error(axisType + ' "' + retrieve(
seriesModel.get(axisType + 'Index' as any),
seriesModel.get(axisType + 'Id' as any),
0
) + '" not found');
}
}
return axisModel;
});
}
function isCartesian2D(seriesModel: SeriesModel): boolean {
return seriesModel.get('coordinateSystem') === 'cartesian2d';
}
CoordinateSystemManager.register('cartesian2d', Grid);
export default Grid;
......@@ -21,6 +21,8 @@
import * as zrUtil from 'zrender/src/core/util';
import GridModel from './GridModel';
import CartesianAxisModel from './AxisModel';
import SeriesModel from '../../model/Series';
import { __DEV__ } from '../../config';
interface CartesianAxisLayout {
position: [number, number];
......@@ -95,3 +97,36 @@ export function layout(
return layout;
}
export function isCartesian2DSeries(seriesModel: SeriesModel): boolean {
return seriesModel.get('coordinateSystem') === 'cartesian2d';
}
export function findAxisModels(seriesModel: SeriesModel): {
xAxisModel: CartesianAxisModel;
yAxisModel: CartesianAxisModel;
} {
const axisModelMap = {
xAxisModel: null,
yAxisModel: null
} as ReturnType<typeof findAxisModels>;
zrUtil.each(axisModelMap, function (v, key) {
const axisType = key.replace(/Model$/, '');
const axisModel = seriesModel.getReferringComponents(axisType)[0] as CartesianAxisModel;
if (__DEV__) {
if (!axisModel) {
throw new Error(axisType + ' "' + zrUtil.retrieve3(
seriesModel.get(axisType + 'Index' as any),
seriesModel.get(axisType + 'Id' as any),
0
) + '" not found');
}
}
axisModelMap[key] = axisModel;
});
return axisModelMap;
}
/*
* 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 '../../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.
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 condAxisExtent: number[];
let tarAxisRecord: AxisRecord;
function addCondition(axis: Axis, axisRecord: AxisRecord) {
// But for simplicity and safty 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;
condAxisExtent = [rawExtentResult.min, rawExtentResult.max];
}
});
}
}
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 (condVal >= condAxisExtent[0] && condVal <= condAxisExtent[1]) {
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 (condVal >= condAxisExtent[0] && condVal <= condAxisExtent[1]) {
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);
}
......@@ -25,10 +25,10 @@ import Polar from './Polar';
import {parsePercent} from '../../util/number';
import {
createScaleByModel,
niceScaleExtent
niceScaleExtent,
getDataDimensionsOnAxis
} from '../../coord/axisHelper';
import CoordinateSystem from '../../CoordinateSystem';
import {getStackedDimension} from '../../data/helper/dataStackHelper';
import PolarModel from './PolarModel';
import ExtensionAPI from '../../ExtensionAPI';
......@@ -86,15 +86,11 @@ function updatePolarScale(this: Polar, ecModel: GlobalModel, api: ExtensionAPI)
ecModel.eachSeries(function (seriesModel) {
if (seriesModel.coordinateSystem === polar) {
const data = seriesModel.getData();
zrUtil.each(data.mapDimensionsAll('radius'), function (dim) {
radiusAxis.scale.unionExtentFromData(
data, getStackedDimension(data, dim)
);
zrUtil.each(getDataDimensionsOnAxis(data, 'radius'), function (dim) {
radiusAxis.scale.unionExtentFromData(data, dim);
});
zrUtil.each(data.mapDimensionsAll('angle'), function (dim) {
angleAxis.scale.unionExtentFromData(
data, getStackedDimension(data, dim)
);
zrUtil.each(getDataDimensionsOnAxis(data, 'angle'), function (dim) {
angleAxis.scale.unionExtentFromData(data, dim);
});
}
});
......
......@@ -25,7 +25,8 @@ import IntervalScale from '../../scale/Interval';
import * as numberUtil from '../../util/number';
import {
getScaleExtent,
niceScaleExtent
niceScaleExtent,
parseAxisModelMinMax
} from '../axisHelper';
import CoordinateSystemManager from '../../CoordinateSystem';
import { CoordinateSystemMaster, CoordinateSystem } from '../CoordinateSystem';
......@@ -192,8 +193,8 @@ class Radar implements CoordinateSystem, CoordinateSystemMaster {
const axisModel = indicatorAxis.model;
const scale = indicatorAxis.scale as IntervalScale;
const fixedMin = axisModel.getMin() as number;
const fixedMax = axisModel.getMax() as number;
const fixedMin = parseAxisModelMinMax(scale, axisModel.get('min', true) as ScaleDataValue);
const fixedMax = parseAxisModelMinMax(scale, axisModel.get('max', true) as ScaleDataValue);
let interval = scale.getInterval();
if (fixedMin != null && fixedMax != null) {
......
/*
* 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 { assert, isArray, eqNaN, isFunction } from 'zrender/src/core/util';
import { __DEV__ } from '../config';
import Scale from '../scale/Scale';
import { AxisBaseModel } from './AxisBaseModel';
import { parsePercent } from 'zrender/src/contain/text';
import { parseAxisModelMinMax } from './axisHelper';
import { AxisBaseOption } from './axisCommonTypes';
export interface ScaleRawExtentResult {
// `min`/`max` defines data available range, determined by
// `dataMin`/`dataMax` and explicit specified min max related option.
// The final extent will be based on the `min`/`max` and may be enlarge
// a little (say, "nice strategy", e.g., niceScale, boundaryGap).
// Ensure `min`/`max` be finite number or NaN here.
// (not to be null/undefined) `NaN` means min/max axis is blank.
readonly min: number;
readonly max: number;
// `minFixed`/`maxFixed` marks that `min`/`max` should be used
// in the final extent without other "nice strategy".
readonly minFixed: boolean;
readonly maxFixed: boolean;
// Mark that the axis should be blank.
readonly isBlank: boolean;
}
export class ScaleRawExtentInfo {
private _needCrossZero: boolean;
private _isOrdinal: boolean;
private _axisDataLen: number;
private _boundaryGapInner: number[];
// Accurate raw value get from model.
private _modelMinRaw: AxisBaseOption['min'];
private _modelMaxRaw: AxisBaseOption['max'];
// Can be `finite number`/`null`/`undefined`/`NaN`
private _modelMinNum: number;
private _modelMaxNum: number;
// Range union by series data on this axis.
// May be modified if data is filtered.
private _dataMin: number;
private _dataMax: number;
// Highest priority if specified.
private _determinedMin: number;
private _determinedMax: number;
// Make that the `rawExtentInfo` can not be modified any more.
readonly frozen: boolean;
constructor(
scale: Scale,
model: AxisBaseModel,
// Usually: data extent from all series on this axis.
originalExtent: number[]
) {
this._prepareParams(scale, model, originalExtent);
}
/**
* Parameters depending on ouside (like model, user callback)
* are prepared and fixed here.
*/
private _prepareParams(
scale: Scale,
model: AxisBaseModel,
// Usually: data extent from all series on this axis.
dataExtent: number[]
) {
if (dataExtent[1] < dataExtent[0]) {
dataExtent = [NaN, NaN];
}
this._dataMin = dataExtent[0];
this._dataMax = dataExtent[1];
const isOrdinal = this._isOrdinal = scale.type === 'ordinal';
this._needCrossZero = model.getNeedCrossZero();
const modelMinRaw = this._modelMinRaw = model.get('min', true);
if (isFunction(modelMinRaw)) {
// This callback alway provide users the full data extent (before data filtered).
this._modelMinNum = parseAxisModelMinMax(scale, modelMinRaw({
min: dataExtent[0],
max: dataExtent[1]
}));
}
else if (modelMinRaw !== 'dataMin') {
this._modelMinNum = parseAxisModelMinMax(scale, modelMinRaw);
}
const modelMaxRaw = this._modelMaxRaw = model.get('max', true);
if (isFunction(modelMaxRaw)) {
// This callback alway provide users the full data extent (before data filtered).
this._modelMaxNum = parseAxisModelMinMax(scale, modelMaxRaw({
min: dataExtent[0],
max: dataExtent[1]
}));
}
else if (modelMaxRaw !== 'dataMax') {
this._modelMaxNum = parseAxisModelMinMax(scale, modelMaxRaw);
}
if (isOrdinal) {
// FIXME: there is a flaw here: if there is no "block" data processor like `dataZoom`,
// and progressive rendering is using, here the category result might just only contain
// the processed chunk rather than the entire result.
this._axisDataLen = model.getCategories().length;
}
else {
const boundaryGap = model.get('boundaryGap');
const boundaryGapArr = isArray(boundaryGap)
? boundaryGap : [boundaryGap || 0, boundaryGap || 0];
if (typeof boundaryGapArr[0] === 'boolean' || typeof boundaryGapArr[1] === 'boolean') {
if (__DEV__) {
console.warn('Boolean type for boundaryGap is only '
+ 'allowed for ordinal axis. Please use string in '
+ 'percentage instead, e.g., "20%". Currently, '
+ 'boundaryGap is set to be 0.');
}
this._boundaryGapInner = [0, 0];
}
else {
this._boundaryGapInner = [
parsePercent(boundaryGapArr[0], 1),
parsePercent(boundaryGapArr[1], 1)
];
}
}
}
/**
* Calculate extent by prepared parameters.
* This method has no external dependency and can be called duplicatedly,
* getting the same result.
* If parameters changed, should call this method to recalcuate.
*/
calculate(): ScaleRawExtentResult {
// Notice: When min/max is not set (that is, when there are null/undefined,
// which is the most common case), these cases should be ensured:
// (1) For 'ordinal', show all axis.data.
// (2) For others:
// + `boundaryGap` is applied (if min/max set, boundaryGap is
// disabled).
// + If `needCrossZero`, min/max should be zero, otherwise, min/max should
// be the result that originalExtent enlarged by boundaryGap.
// (3) If no data, it should be ensured that `scale.setBlank` is set.
const isOrdinal = this._isOrdinal;
const dataMin = this._dataMin;
const dataMax = this._dataMax;
const axisDataLen = this._axisDataLen;
const boundaryGapInner = this._boundaryGapInner;
const span = !isOrdinal
? ((dataMax - dataMin) || Math.abs(dataMin))
: null;
// Currently if a `'value'` axis model min is specified as 'dataMin'/'dataMax',
// `boundaryGap` will not be used. It's the different from specifying as `null`/`undefined`.
let min = this._modelMinRaw === 'dataMin' ? dataMin : this._modelMinNum;
let max = this._modelMaxRaw === 'dataMax' ? dataMax : this._modelMaxNum;
// If `_modelMinNum`/`_modelMaxNum` is `null`/`undefined`, should not be fixed.
let minFixed = min != null;
let maxFixed = max != null;
if (min == null) {
min = isOrdinal
? (axisDataLen ? 0 : NaN)
: dataMin - boundaryGapInner[0] * span;
}
if (max == null) {
max = isOrdinal
? (axisDataLen ? axisDataLen - 1 : NaN)
: dataMax + boundaryGapInner[1] * span;
}
(min == null || !isFinite(min)) && (min = NaN);
(max == null || !isFinite(max)) && (max = NaN);
if (min > max) {
min = NaN;
max = NaN;
}
const isBlank = eqNaN(min)
|| eqNaN(max)
|| (isOrdinal && !axisDataLen);
// If data extent modified, need to recalculated to ensure cross zero.
if (this._needCrossZero) {
// Axis is over zero and min is not set
if (min > 0 && max > 0 && !minFixed) {
min = 0;
// minFixed = true;
}
// Axis is under zero and max is not set
if (min < 0 && max < 0 && !maxFixed) {
max = 0;
// maxFixed = true;
}
// PENDING:
// When `needCrossZero` and all data is positive/negative, should it be ensured
// that the results processed by boundaryGap are positive/negative?
// If so, here `minFixed`/`maxFixed` need to be set.
}
const determinedMin = this._determinedMin;
const determinedMax = this._determinedMax;
if (determinedMin != null) {
min = determinedMin;
minFixed = true;
}
if (determinedMax != null) {
max = determinedMax;
maxFixed = true;
}
// Ensure min/max be finite number or NaN here. (not to be null/undefined)
// `NaN` means min/max axis is blank.
return {
min: min,
max: max,
minFixed: minFixed,
maxFixed: maxFixed,
isBlank: isBlank
};
}
modifyDataMinMax(minMaxName: 'min' | 'max', val: number): void {
if (__DEV__) {
assert(!this.frozen);
}
this[DATA_MIN_MAX_ATTR[minMaxName]] = val;
}
setDeterminedMinMax(minMaxName: 'min' | 'max', val: number): void {
const attr = DETERMINED_MIN_MAX_ATTR[minMaxName];
if (__DEV__) {
assert(
!this.frozen
// Earse them usually means logic flaw.
&& (this[attr] == null)
);
}
this[attr] = val;
}
freeze() {
// @ts-ignore
this.frozen = true;
}
}
const DETERMINED_MIN_MAX_ATTR = { min: '_determinedMin', max: '_determinedMax' } as const;
const DATA_MIN_MAX_ATTR = { min: '_dataMin', max: '_dataMax' } as const;
/**
* Get scale min max and related info only depends on model settings.
* This method can be called after coordinate system created.
* For example, in data processing stage.
*
* Scale extent info probably be required multiple times during a workflow.
* For example:
* (1) `dataZoom` depends it to get the axis extent in "100%" state.
* (2) `processor/extentCalculator` depends it to make sure whethe axis extent is specified.
* (3) `coordSys.update` use it to finally decide the scale extent.
* But the callback of `min`/`max` should not be called multiple time.
* The code should not be duplicated either.
* So we cache the result in the scale instance, which will be recreated in the begining
* of the workflow.
*/
export function ensureScaleRawExtentInfo(
scale: Scale,
model: AxisBaseModel,
// Usually: data extent from all series on this axis.
originalExtent: number[]
): ScaleRawExtentInfo {
// Do not permit to recreate.
let rawExtentInfo = scale.rawExtentInfo;
if (rawExtentInfo) {
return rawExtentInfo;
}
rawExtentInfo = new ScaleRawExtentInfo(scale, model, originalExtent);
// @ts-ignore
scale.rawExtentInfo = rawExtentInfo;
return rawExtentInfo;
}
......@@ -876,6 +876,14 @@ class List<
}
/**
* PENDING: In fact currently this function is only used to short-circuit
* the calling of `scale.unionExtentFromData` when data have been filtered by modules
* like "dataZoom". `scale.unionExtentFromData` is used to calculate data extent for series on
* an axis, but if a "axis related data filter module" is used, the extent of the axis have
* been fixed and no need to calling `scale.unionExtentFromData` actually.
* But if we add "custom data filter" in future, which is not "axis related", this method may
* be still needed.
*
* Optimize for the scenario that data is filtered by a given extent.
* Consider that if data amount is more than hundreds of thousand,
* extent calculation will cost more than 10ms and the cache will
......@@ -883,9 +891,13 @@ class List<
*/
getApproximateExtent(dim: DimensionLoose): [number, number] {
dim = this.getDimension(dim);
return this._approximateExtent[dim] || this.getDataExtent(dim /*, stack */);
return this._approximateExtent[dim] || this.getDataExtent(dim);
}
/**
* Calculate extent on a filtered data might be time consuming.
* Approximate extent is only used for: calculte extent of filtered data outside.
*/
setApproximateExtent(extent: [number, number], dim: DimensionLoose): void {
dim = this.getDimension(dim);
this._approximateExtent[dim] = extent.slice() as [number, number];
......
......@@ -88,9 +88,14 @@ export const dependencies = {
const TEST_FRAME_REMAIN_TIME = 1;
const PRIORITY_PROCESSOR_FILTER = 1000;
const PRIORITY_PROCESSOR_SERIES_FILTER = 800;
// Some data processors depends on the stack result dimension (to calculate data extent).
// So data stack stage should be in front of data processing stage.
const PRIORITY_PROCESSOR_DATASTACK = 900;
// "Data filter" will block the stream, so it should be
// put at the begining of data processing.
const PRIORITY_PROCESSOR_FILTER = 1000;
const PRIORITY_PROCESSOR_DEFAULT = 2000;
const PRIORITY_PROCESSOR_STATISTIC = 5000;
const PRIORITY_VISUAL_LAYOUT = 1000;
......@@ -2131,7 +2136,7 @@ export function registerProcessor(
priority: number | StageHandler | StageHandlerOverallReset,
processor?: StageHandler | StageHandlerOverallReset
): void {
normalizeRegister(dataProcessorFuncs, priority, processor, PRIORITY_PROCESSOR_FILTER);
normalizeRegister(dataProcessorFuncs, priority, processor, PRIORITY_PROCESSOR_DEFAULT);
}
/**
......
......@@ -102,8 +102,6 @@ export function createScale(dataExtent: number[], option: object | AxisBaseModel
* `getMin(origin: boolean) => number`
* `getMax(origin: boolean) => number`
* `getNeedCrossZero() => boolean`
* `setRange(start: number, end: number)`
* `resetRange()`
*/
export function mixinAxisModelCommonMethods(Model: Model) {
zrUtil.mixin(Model, AxisModelCommonMixin);
......
......@@ -22,6 +22,8 @@ import * as clazzUtil from '../util/clazz';
import { Dictionary } from 'zrender/src/core/types';
import List from '../data/List';
import { DimensionName, ScaleDataValue, OptionDataValue } from '../util/types';
import { ScaleRawExtentInfo } from '../coord/scaleRawExtentInfo';
abstract class Scale {
......@@ -33,6 +35,9 @@ abstract class Scale {
private _isBlank: boolean;
// Inject
readonly rawExtentInfo: ScaleRawExtentInfo;
constructor(setting?: Dictionary<any>) {
this._setting = setting || {};
this._extent = [Infinity, -Infinity];
......
<!DOCTYPE html>
<!--
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.
-->
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script src="lib/esl.js"></script>
<script src="lib/config.js"></script>
<script src="lib/jquery.min.js"></script>
<script src="lib/facePrint.js"></script>
<script src="lib/testHelper.js"></script>
<!-- <script src="ut/lib/canteen.js"></script> -->
<link rel="stylesheet" href="lib/reset.css" />
</head>
<body>
<style>
</style>
<div id="main-no-dataZoom"></div>
<div id="main-has-dataZoom-x"></div>
<div id="main-has-dataZoom-y"></div>
<div id="main-has-stack"></div>
<div id="main-has-dataZoom-y-stack"></div>
<script>
var TOTAL_HEIGHT = 1200;
/**
* @param opt: {
* dataZoomX: boolean;
* dataZoomY: boolean;
* stack: boolean;
* }
*/
function makeMultiCartesian(echarts, opt) {
var gridHeight = 100;
var currTop = 40;
var gap = 80;
var option = {
grid: [],
xAxis: [],
yAxis: [],
series: [],
dataZoom: []
};
function makeCartesian(option, partialOption, label) {
option.grid.push({
top: currTop,
height: gridHeight
});
option.xAxis.push(echarts.util.defaults({
gridIndex: option.grid.length - 1
}, partialOption.xAxis));
option.yAxis.push(echarts.util.defaults({
name: label[
opt.dataZoomX ? 'dataZoomX' :
opt.dataZoomY ? 'dataZoomY' :
'noDataZoom'
],
nameTextStyle: {
align: 'left'
},
gridIndex: option.grid.length - 1
}, partialOption.yAxis));
var xAxisIndex = option.xAxis.length - 1;
var yAxisIndex = option.yAxis.length - 1;
var seriesList = partialOption.seriesList[opt.stack ? 'stackHas' : 'stackNon'];
echarts.util.each(seriesList, function (series) {
// FIXME:
// currently (20200619) if series.stack is the same but series
// on different grid, that would get wrong result.
if (opt.stack) {
series.stack = series.stack + '__' + xAxisIndex + '_' + yAxisIndex;
}
option.series.push(echarts.util.defaults({
label: { show: true },
itemStyle: {
opacity: 0.8
},
xAxisIndex: xAxisIndex,
yAxisIndex: yAxisIndex
}, series));
});
if (opt.dataZoomX) {
option.dataZoom.push({
type: 'slider',
filterMode: 'none',
height: 15,
top: currTop + gridHeight + 20,
xAxisIndex: xAxisIndex
}, {
type: 'inside',
filterMode: 'none',
xAxisIndex: xAxisIndex
});
}
if (opt.dataZoomY) {
option.dataZoom.push({
type: 'slider',
filterMode: 'none',
yAxisIndex: yAxisIndex
}, {
type: 'inside',
filterMode: 'none',
yAxisIndex: yAxisIndex
});
}
currTop += gridHeight + gap;
}
// ---------------------------------------
// Cases in a single chart instance BEGIN
// ---------------------------------------
makeCartesian(option, {
xAxis: {
type: 'category'
},
yAxis: {
},
seriesList: {
stackNon: [{
type: 'bar',
data: [['a', 22], ['b', 52], ['c', 659]]
}],
stackHas: [{
type: 'bar',
stack: 's',
data: [['a', 12], ['b', 32], ['c', 200]]
}, {
type: 'bar',
stack: 's',
data: [['a', 10], ['b', 20], ['c', 459]]
}]
}
}, {
noDataZoom: 'y-min: 0, y-max: niced from 659, x-min: "a", x-max: "c"',
dataZoomX: 'y-min: 0, y-max: niced from 659, x-min: "a", x-max: "c"',
dataZoomY: 'y-min: 0, y-max: niced from 659, x-min: "a", x-max: "c"',
});
makeCartesian(option, {
xAxis: {
type: 'category',
max: 1
},
yAxis: {
},
seriesList: {
stackNon: [{
type: 'bar',
data: [['a', 22], ['b', 52], ['c', 959]]
}],
stackHas: [{
type: 'bar',
stack: 's',
data: [['a', 12], ['b', 32], ['c', 359]]
}, {
type: 'bar',
stack: 's',
data: [['a', 10], ['b', 20], ['c', 600]]
}]
}
}, {
noDataZoom: 'y-min: 0, y-max: niced from 52, x-min: "a", x-max: "b"',
dataZoomX: 'y-min: 0, y-max: niced from 959, x-min: "a", x-max: "b"',
dataZoomY: 'y-min: 0, y-max: niced from 959, x-min: "a", x-max: "b"'
});
makeCartesian(option, {
xAxis: {
type: 'category',
max: 2,
min: 1
},
yAxis: {
},
seriesList: {
stackNon: [{
type: 'bar',
data: [['a', 959], ['b', 22], ['c', 52], ['d', -959]]
}],
stackHas: [{
type: 'bar',
stack: 's',
data: [['a', 300], ['b', 12], ['c', 32], ['d', -259]]
}, {
type: 'bar',
stack: 's',
data: [['a', 659], ['b', 10], ['c', 20], ['d', -700]]
}],
}
}, {
noDataZoom: 'y-min: 0, y-max: niced from 52, x-min: "b", x-max: "c"',
dataZoomX: 'y-min: niced from -959, y-max: niced from 959, x-min: "b", x-max: "c"',
dataZoomY: 'y-min: niced from -959, y-max: niced from 959, x-min: "b", x-max: "c"'
});
makeCartesian(option, {
xAxis: {
type: 'category',
max: 2,
min: 1
},
yAxis: {
max: 500
},
seriesList: {
stackNon: [{
type: 'bar',
data: [['a', 959], ['b', 22], ['c', 52], ['d', -959]]
}],
stackHas: [{
type: 'bar',
stack: 's',
data: [['a', 159], ['b', 2], ['c', 7], ['d', -59]]
}, {
type: 'bar',
stack: 's',
data: [['a', 800], ['b', 20], ['c', 45], ['d', -900]]
}]
}
}, {
noDataZoom: 'y-min: 0, y-max: 500, x-min: "b", x-max: "c"',
dataZoomX: 'y-min: niced from -959, y-max: 500, x-min: "b", x-max: "c"',
dataZoomY: 'y-min: niced from -959, y-max: 500, x-min: "b", x-max: "c"'
});
makeCartesian(option, {
xAxis: {
type: 'category',
min: 1
},
yAxis: {
scale: true
},
seriesList: {
stackNon: [{
type: 'line',
areaStyle: {},
data: [['a', 359], ['b', 122], ['c', 152]]
}],
stackHas: [{
type: 'line',
stack: 's',
data: [['a', 159], ['b', 62], ['c', 100]]
}, {
type: 'line',
stack: 's',
areaStyle: {},
data: [['a', 200], ['b', 60], ['c', 52]]
}]
}
}, {
noDataZoom: '(yAxis.scale: true) y-min: niced from (noStack ? 122 : 62), y-max: niced from 152, x-min: "b", x-max: "c"',
dataZoomX: '(yAxis.scale: true) y-min: niced from (noStack ? 122 : 62), y-max: niced from 359, x-min: "b", x-max: "c"',
dataZoomY: '(yAxis.scale: true) y-min: niced from (noStack ? 122 : 62), y-max: niced from 359, x-min: "b", x-max: "c"'
});
makeCartesian(option, {
xAxis: {
type: 'category',
min: 1,
max: 2
},
yAxis: {
boundaryGap: ['50%', '50%']
},
seriesList: {
stackNon: [{
type: 'bar',
data: [['a', 559], ['b', 82], ['c', 152], ['d', -559]]
}],
stackHas: [{
type: 'bar',
stack: 's',
data: [['a', 159], ['b', 22], ['c', 52], ['d', -159]]
}, {
type: 'bar',
stack: 's',
data: [['a', 400], ['b', 60], ['c', 100], ['d', -459]]
}]
}
}, {
// 187 = 152 + .5 * (152 - 82)
// 217 = 152 + .5 * (152 - 22)
// -43 = 22-.5 * (152 - 22)
noDataZoom: '(boundaryGap: [.5, .5]) y-min: (noStack ? 0 : -43), y-max: niced from (noStack ? 187 : 217), x-min: "b", x-max: "c"',
dataZoomX: '(boundaryGap: [.5, .5]) y-min: -1118, y-max: niced from 1118, x-min: "b", x-max: "c"',
dataZoomY: '(boundaryGap: [.5, .5]) y-min: -1118, y-max: niced from 1118, x-min: "b", x-max: "c"'
});
// -------------------------------------
// Cases in a single chart instance END
// -------------------------------------
return option;
}
</script>
<script>
require(['echarts'], function (echarts) {
var option = makeMultiCartesian(echarts, {});
var chart = testHelper.create(echarts, 'main-no-dataZoom', {
title: [
'Check xAxis, yAxis min/max'
],
option: option,
height: TOTAL_HEIGHT
});
});
</script>
<script>
require(['echarts'], function (echarts) {
var option = makeMultiCartesian(echarts, {
dataZoomX: true
});
var chart = testHelper.create(echarts, 'main-has-dataZoom-x', {
title: [
'[has **dataZoom on X** filterMode: "none"] Check xAxis, yAxis min/max'
],
option: option,
height: TOTAL_HEIGHT
});
});
</script>
<script>
require(['echarts'], function (echarts) {
var option = makeMultiCartesian(echarts, {
dataZoomY: true
});
var chart = testHelper.create(echarts, 'main-has-dataZoom-y', {
title: [
'[has **dataZoom on Y** filterMode: "none"] Check xAxis, yAxis min/max'
],
option: option,
height: TOTAL_HEIGHT
});
});
</script>
<script>
require(['echarts'], function (echarts) {
var option = makeMultiCartesian(echarts, {
stack: true
});
var chart = testHelper.create(echarts, 'main-has-stack', {
title: [
'[has **stack**] Check xAxis, yAxis min/max'
],
option: option,
height: TOTAL_HEIGHT
});
});
</script>
<script>
require(['echarts'], function (echarts) {
var option = makeMultiCartesian(echarts, {
stack: true,
dataZoomY: true
});
var chart = testHelper.create(echarts, 'main-has-dataZoom-y-stack', {
title: [
'[has **dataZoom on Y** has **stack**] Check xAxis, yAxis min/max'
],
option: option,
height: TOTAL_HEIGHT
});
});
</script>
</body>
</html>
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册