| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581 |
- /*
- * 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.
- */
- /**
- * AUTO-GENERATED FILE. DO NOT MODIFY.
- */
- /*
- * 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, each, createHashMap } from 'zrender/lib/core/util.js';
- import { parsePercent } from 'zrender/lib/contain/text.js';
- import { isIntervalScale, isLogScale, isOrdinalScale, isTimeScale } from '../scale/helper.js';
- import { makeInner, initExtentForUnion, unionExtentFromNumber, isValidNumberForExtent, extentHasValue, unionExtentFromExtent, unionExtentStartFromNumber, unionExtentEndFromNumber, ensureExtentAscSimply } from '../util/model.js';
- import { discourageOnAxisZero, getDataDimensionsOnAxis, isAxisOnBand } from './axisHelper.js';
- import { getCoordForCoordSysUsageKindBox } from '../core/CoordinateSystem.js';
- import { error } from '../util/log.js';
- import { isNullableNumberFinite, mathMax, mathMin } from '../util/number.js';
- import { SCALE_EXTENT_KIND_MAPPING } from '../scale/scaleMapper.js';
- import { eachKeyOnAxis, eachSeriesOnAxis } from './axisStatistics.js';
- /**
- * NOTICE: Can be only used in `ensureScaleStore(axisLike)`.
- *
- * In most cases the instances of `Axis` and `Scale` are one-to-one mapping and share the same lifecycle.
- * But in some external usage (such as echarts-gl), axis instance does not necessarily exist, and only
- * scale instance and axisModel are used. Therefore we store the internal info on scale instance directly.
- */
- var scaleInner = makeInner();
- export var AXIS_EXTENT_INFO_BUILD_FROM_COORD_SYS_UPDATE = 1;
- export var AXIS_EXTENT_INFO_BUILD_FROM_DATA_ZOOM = 2;
- var AXIS_EXTENT_INFO_BUILD_FROM_EMPTY = 3;
- var ScaleRawExtentInfo = /** @class */function () {
- function ScaleRawExtentInfo(scale, model,
- // Typically: data extent from all series on this axis.
- dataExtent, requireStartValue, requireContainShape) {
- var isOrdinal = isOrdinalScale(scale);
- var axisDataLen = 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.
- ? model.getCategories().length : null;
- // [CATEGORY_AXIS_MODEL_DATA_IS_EMPTY_ARRAY]:
- // This is only for backward compatibility - `xxxAxis: {data: []}` can declare this axis as
- // a "category" axis and use `series.data` to determine its extent but the axis is blank - only
- // axis line is displayed. This is a conincidence, but it is used in some cases.
- var categoryAxisModelDataIsEmptyArray;
- if (isOrdinal) {
- var axisModelDataArray = model.getCategories(true);
- categoryAxisModelDataIsEmptyArray = axisModelDataArray && !axisModelDataArray.length;
- }
- // NOTE: also considered the input dataExtent may be still in the initialized state `[Infinity, -Infinity]`.
- var dataMM = dataExtent.slice();
- // custom dataMin/dataMax.
- // Also considered `modelDataMinMax[0] > modelDataMinMax[1]` may occur.
- if (isIntervalScale(scale) || isLogScale(scale) || isTimeScale(scale)) {
- unionExtentStartFromNumber(dataMM, parseAxisModelMinMax(scale, model.get('dataMin', true)));
- unionExtentEndFromNumber(dataMM, parseAxisModelMinMax(scale, model.get('dataMax', true)));
- }
- if (!extentHasValue(dataMM)) {
- // dataMM may be still `[Infinity, -Infinity]`, we use `NaN` on the subsequent calculations
- // to force the `noZoomEffMM` to be `[NaN, NaN]` if needed.
- dataMM[0] = dataMM[1] = NaN;
- }
- var noZoomEffMM = [];
- var fixMM = [false, false];
- // 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 `needIncludeZero`, 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.
- var modelMinRaw = model.get('min', true);
- if (modelMinRaw === 'dataMin') {
- noZoomEffMM[0] = dataMM[0];
- fixMM[0] = true;
- } else {
- noZoomEffMM[0] = parseAxisModelMinMax(scale, isFunction(modelMinRaw)
- // This callback always provides users the full data extent (before data is filtered).
- ? modelMinRaw({
- min: dataMM[0],
- max: dataMM[1]
- }) : modelMinRaw);
- // If `xxxAxis.min: null/undefined`, min should not be fixed.
- fixMM[0] = noZoomEffMM[0] != null;
- }
- var modelMaxRaw = model.get('max', true);
- if (modelMaxRaw === 'dataMax') {
- noZoomEffMM[1] = dataMM[1];
- fixMM[1] = true;
- } else {
- noZoomEffMM[1] = parseAxisModelMinMax(scale, isFunction(modelMaxRaw)
- // This callback always provides users the full data extent (before data is filtered).
- ? modelMaxRaw({
- min: dataMM[0],
- max: dataMM[1]
- }) : modelMaxRaw);
- // If `xxxAxis.max: null/undefined`, max should not be fixed.
- fixMM[1] = noZoomEffMM[1] != null;
- }
- var boundaryGap = parseBoundaryGapOption(scale, model);
- var span = !isOrdinal
- // PENDING: Historicall behavior but may not reasonable enough.
- ? dataMM[1] - dataMM[0] || Math.abs(dataMM[0]) : null;
- // NOTE: If a numeric axis min/max is specified as 'dataMin'/'dataMax',
- // `boundaryGap` will not be used.
- if (noZoomEffMM[0] == null) {
- noZoomEffMM[0] = isOrdinal ? categoryAxisModelDataIsEmptyArray ? dataMM[0] : axisDataLen ? 0 : NaN : dataMM[0] - boundaryGap[0] * span;
- }
- if (noZoomEffMM[1] == null) {
- noZoomEffMM[1] = isOrdinal ? categoryAxisModelDataIsEmptyArray ? dataMM[1] : axisDataLen ? axisDataLen - 1 : NaN : dataMM[1] + boundaryGap[1] * span;
- }
- // Normalize to `NaN` if invalid; e.g., this may occur when `dataMM` has Infinity.
- !isValidNumberForExtent(noZoomEffMM[0]) && (noZoomEffMM[0] = NaN);
- !isValidNumberForExtent(noZoomEffMM[1]) && (noZoomEffMM[1] = NaN);
- var isBlank = categoryAxisModelDataIsEmptyArray || eqNaN(noZoomEffMM[0]) || eqNaN(noZoomEffMM[1]) || isOrdinal && !axisDataLen;
- // NOTE: `needIncludeZero` is not applicable to LogScale, TimeScale, OrdinalScale.
- var needIncludeZeroApplicable = isIntervalScale(scale);
- var needIncludeZero = needIncludeZeroApplicable && model.needIncludeZero && model.needIncludeZero();
- if (needIncludeZero) {
- if (noZoomEffMM[0] > 0 && noZoomEffMM[1] > 0 && !fixMM[0]) {
- noZoomEffMM[0] = 0;
- // fixMM[0] = true;
- }
- if (noZoomEffMM[0] < 0 && noZoomEffMM[1] < 0 && !fixMM[1]) {
- noZoomEffMM[1] = 0;
- // fixMM[1] = true;
- }
- }
- var needToggleAxisInverse = false;
- if (noZoomEffMM[0] > noZoomEffMM[1]) {
- // Historically, if users set `xxxAxis.min > xxxAxis.max`, or `xxxAxis.max < dataExtent[0]`,
- // or `xxxAxis.min > dataExtent[1]` the behavior is sometimes like `xxxAxis.inverse = true`,
- // sometimes abnormal. We remain backward compatible with the former one, though this feature
- // may not be reasonable.
- // And handle it after "needIncludeZero" is also for backward compatibility.
- noZoomEffMM.reverse();
- needToggleAxisInverse = true;
- }
- var startValue = parseAxisModelMinMax(scale, model.get('startValue', true));
- var startValueSpecified = startValue != null;
- if (!isNullableNumberFinite(startValue) && requireStartValue) {
- startValue = scale.getDefaultStartValue ? scale.getDefaultStartValue() : 0;
- }
- if (isNullableNumberFinite(startValue)
- // Keep backward compatibility and enable `xxxAxis.scale: true` enabled on bar series:
- // if `xxxAxis.scale: true` and `startValue` is not specified, do not union the default `startValue`,
- && (startValueSpecified || !needIncludeZeroApplicable || needIncludeZero)) {
- if (startValue < noZoomEffMM[0] && !fixMM[0]) {
- noZoomEffMM[0] = startValue;
- fixMM[0] = true;
- } else if (startValue > noZoomEffMM[1] && !fixMM[1]) {
- noZoomEffMM[1] = startValue;
- fixMM[1] = true;
- }
- }
- var internal = this._i = {
- scale: scale,
- dataMM: dataMM,
- noZoomEffMM: noZoomEffMM,
- zoomMM: [],
- fixMM: fixMM,
- zoomFixMM: [false, false],
- startValue: startValue,
- isBlank: isBlank,
- incl0: needIncludeZero,
- tggAxInv: needToggleAxisInverse,
- ctnShp: requireContainShape
- };
- sanitizeExtent(internal, noZoomEffMM);
- }
- ScaleRawExtentInfo.prototype.makeNoZoom = function () {
- return this._i.noZoomEffMM.slice();
- };
- ScaleRawExtentInfo.prototype.makeFinal = function () {
- var internal = this._i;
- var zoomMM = internal.zoomMM;
- var noZoomEffMM = internal.noZoomEffMM;
- var zoomFixMM = internal.zoomFixMM;
- var fixMM = internal.fixMM;
- var result = {
- fixMM: fixMM,
- zoomFixMM: zoomFixMM,
- isBlank: internal.isBlank,
- incl0: internal.incl0,
- tggAxInv: internal.tggAxInv,
- ctnShp: internal.ctnShp,
- effMM: noZoomEffMM.slice()
- };
- var effMM = result.effMM;
- // NOTE: Switching `fixMM` probably leads to abrupt extent changes when draging a `dataZoom`
- // handle, since `fixMM` impact the "nice extent" and "nice ticks" calculation.
- // Consider a case:
- // dataZoom `start` is 2% but its `end` is 100%, (or vice versa), we currently only set `fixMM[0]`
- // as `true` but remain `fixMM[1]` as `false` for this case to avoid unnecessary abrupt change.
- // Incidentally, the effect is not unacceptable if we set both `fixMM[0]/[1]` as `true`.
- if (zoomMM[0] != null) {
- effMM[0] = zoomMM[0];
- fixMM[0] = zoomFixMM[0] = true;
- }
- if (zoomMM[1] != null) {
- effMM[1] = zoomMM[1];
- fixMM[1] = zoomFixMM[1] = true;
- }
- sanitizeExtent(internal, effMM);
- return result;
- };
- ScaleRawExtentInfo.prototype.makeRenderInfo = function () {
- return {
- startValue: this._i.startValue
- };
- };
- /**
- * NOTICE:
- * - Do not set them if the percent are 0% or 100%. (See `AxisProxy['reset']`.)
- * - The caller must ensure `start <= end` and the range is equal or less then `noZoomEffMM`.
- * (See `AxisProxy['calculateDataWindow']`.)
- * - The outcome `_zoomMM` may have both `NullUndefined` and a finite value, like `[undefined, 123]`.
- */
- ScaleRawExtentInfo.prototype.setZoomMM = function (idxMinMax, val) {
- this._i.zoomMM[idxMinMax] = val;
- };
- return ScaleRawExtentInfo;
- }();
- export { ScaleRawExtentInfo };
- /**
- * Should be called when a new extent is created or modified.
- */
- function sanitizeExtent(internal, mm) {
- var scale = internal.scale;
- var dataMM = internal.dataMM;
- if (scale.sanitize) {
- mm[0] = scale.sanitize(mm[0], dataMM);
- mm[1] = scale.sanitize(mm[1], dataMM);
- ensureExtentAscSimply(mm);
- }
- }
- function parseAxisModelMinMax(scale, minMax) {
- return minMax == null ? null // null/undefined means not specified and other default values can be applied.
- : eqNaN(minMax) ? NaN // NaN means a deliberate invalid number.
- : scale.parse(minMax);
- }
- function parseBoundaryGapOption(scale, model) {
- var boundaryGapOptionArr;
- if (isOrdinalScale(scale)) {
- boundaryGapOptionArr = [0, 0];
- } else {
- var boundaryGap = model.get('boundaryGap');
- if (typeof boundaryGap === 'boolean') {
- if (process.env.NODE_ENV !== 'production') {
- if (boundaryGap === true) {
- 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 0.');
- }
- }
- boundaryGap = null;
- }
- boundaryGapOptionArr = isArray(boundaryGap) ? boundaryGap : [boundaryGap, boundaryGap];
- }
- return [parseBoundaryGapOptionItem(boundaryGapOptionArr[0]), parseBoundaryGapOptionItem(boundaryGapOptionArr[1])];
- }
- function parseBoundaryGapOptionItem(opt) {
- return parsePercent(typeof opt === 'boolean' ? 0 : opt, 1) || 0;
- }
- /**
- * NOTE: `associateSeriesWithAxis` is not necessarily called, e.g., when
- * an axis is not used by any series.
- */
- function ensureScaleStore(axisLike) {
- var store = scaleInner(axisLike.scale);
- if (!store.extent) {
- store.extent = initExtentForUnion();
- }
- return store;
- }
- /**
- * This supports union extent on case like: pie (or other similar series)
- * lays out on cartesian2d.
- * @see scaleRawExtentInfoCreate
- */
- export function scaleRawExtentInfoEnableBoxCoordSysUsage(axisLike, coordSysDimIdxMap) {
- ensureScaleStore(axisLike).dimIdxInCoord = coordSysDimIdxMap.get(axisLike.dim);
- }
- /**
- * @usage
- * class SomeCoordSys {
- * static create() {
- * ecModel.eachSeries(function (seriesModel) {
- * associateSeriesWithAxis(axis1, seriesModel, ...);
- * associateSeriesWithAxis(axis2, seriesModel, ...);
- * // ...
- * });
- * }
- * update() {
- * scaleRawExtentInfoCreate(axis1);
- * scaleRawExtentInfoCreate(axis2);
- * }
- * }
- * class AxisProxy {
- * reset() {
- * scaleRawExtentInfoCreate(axis1);
- * }
- * }
- *
- * NOTICE:
- * - `associateSeriesWithAxis`(in `axisStatistics.ts`) should be called in:
- * - Coord sys create method.
- * - `scaleRawExtentInfoCreate` should be typically called in:
- * - `dataZoom` processor. It requires processing like:
- * 1. Filter series data by dataZoom1;
- * 2. Union the filtered data and init the extent of the orthogonal axes, which is the 100% of dataZoom2;
- * 3. Filter series data by dataZoom2;
- * 4. ...
- * - Coord sys update method, for other axes that not covered by `dataZoom`.
- * NOTE: If a `dataZoom` covers this series, this data and its extent has been dataZoom-filtered.
- * Therefore this handling should not before `dataZoom`.
- * - The callback of `min`/`max` in ec option should NOT be called multiple times,
- * therefore, we initialize `ScaleRawExtentInfo` uniformly in `scaleRawExtentInfoCreate`.
- *
- * @see SCALE_EXTENT_CONSTRUCTION for the full processing flow.
- */
- export function scaleRawExtentInfoCreate(axis, from) {
- var scale = axis.scale;
- var model = axis.model;
- var axisDim = axis.dim;
- if (process.env.NODE_ENV !== 'production') {
- assert(scale && model && axisDim);
- }
- if (scale.rawExtentInfo) {
- if (process.env.NODE_ENV !== 'production') {
- // Check for incorrect impl - the duplicated calling of this method is only allowed in
- // these cases:
- // - First in `AxisProxy['reset']` (for dataZoom)
- // - Then in `CoordinateSystem['update']`.
- // - Then after `chart.appendData()` due to `dirtyOnOverallProgress: true`
- assert(scale.rawExtentInfo.from !== from || from === AXIS_EXTENT_INFO_BUILD_FROM_DATA_ZOOM);
- }
- return;
- }
- scaleRawExtentInfoCreateDeal(scale, axis, axisDim, model, from);
- }
- function scaleRawExtentInfoCreateDeal(scale, axis, axisDim, model, from) {
- var scaleStore = ensureScaleStore(axis);
- var extent = scaleStore.extent;
- var requireStartValue = false;
- eachSeriesOnAxis(axis, function (seriesModel) {
- if (seriesModel.boxCoordinateSystem) {
- // This supports union extent on case like: pie (or other similar series)
- // lays out on cartesian2d.
- var coord = getCoordForCoordSysUsageKindBox(seriesModel).coord;
- var dimIdx = scaleStore.dimIdxInCoord;
- if (!(dimIdx >= 0)) {
- if (process.env.NODE_ENV !== 'production') {
- // Require `scaleRawExtentInfoEnableBoxCoordSysUsage` have been called to support it.
- // But if users set it, give a error log but no exceptions.
- error("Property \"series.coord\" is not supported on axis " + seriesModel.boxCoordinateSystem.type + ".");
- }
- }
- // Only `[val1, val2]` case needs to be supported currently.
- else if (isArray(coord)) {
- var coordItem = coord[dimIdx];
- if (coordItem != null && !isArray(coordItem)) {
- unionExtentFromNumber(extent, scale.parse(coordItem));
- }
- }
- } else if (seriesModel.coordinateSystem) {
- // NOTE: This data may have been filtered by dataZoom on orthogonal axes.
- var data_1 = seriesModel.getData();
- if (data_1) {
- var filter_1 = scale.getFilter ? scale.getFilter() : null;
- each(getDataDimensionsOnAxis(data_1, axisDim), function (dim) {
- unionExtentFromExtent(extent, data_1.getApproximateExtent(dim, filter_1));
- });
- }
- if (seriesModel.__requireStartValue && seriesModel.__requireStartValue(axis)) {
- requireStartValue = true;
- }
- }
- });
- var requireContainShape = determineRequireContainShape(scale, axis, model);
- var rawExtentInfo = new ScaleRawExtentInfo(scale, model, extent, requireStartValue, requireContainShape);
- injectScaleRawExtentInfo(scale, rawExtentInfo, from);
- scaleStore.extent = null; // Clean up
- }
- /**
- * `rawExtentInfo` may not be created in some cases, such as no series declared or extra useless
- * axes declared in ec option. In this case we still create a default one for that empty axis.
- */
- function scaleRawExtentInfoBuildDefault(axisLike, dataExtent) {
- var scale = axisLike.scale;
- if (process.env.NODE_ENV !== 'production') {
- assert(!scale.rawExtentInfo);
- }
- injectScaleRawExtentInfo(scale, new ScaleRawExtentInfo(scale, axisLike.model, dataExtent, false, false), AXIS_EXTENT_INFO_BUILD_FROM_EMPTY);
- }
- function injectScaleRawExtentInfo(scale, scaleRawExtentInfo, from) {
- // @ts-ignore
- scale.rawExtentInfo = scaleRawExtentInfo;
- // @ts-ignore
- scaleRawExtentInfo.from = from;
- }
- /**
- * See `axisSnippets.ts` for some commonly used handlers.
- *
- * FIXME:
- * `boundaryGap: true` (i.e., `onBand: true` in code) has long been supported on category axis.
- * And it is implemented in different code and not merged to this implementation yet.
- */
- export function registerAxisContainShapeHandler(
- // `axisStatKey` is used to quickly omit irrelevant handlers,
- // since handlers need to be iterated per axis.
- axisStatKey, handler) {
- if (process.env.NODE_ENV !== 'production') {
- assert(!axisContainShapeHandlerMap.get(axisStatKey));
- }
- axisContainShapeHandlerMap.set(axisStatKey, handler);
- }
- var axisContainShapeHandlerMap = createHashMap();
- /**
- * Prepare axis scale extent before "nice".
- * Item of returned array can only be number (including Infinity and NaN).
- */
- export function adoptScaleRawExtentInfoAndPrepare(scale, model, ecModel, axis, externalDataExtent) {
- if (process.env.NODE_ENV !== 'production') {
- assert(!externalDataExtent || !scale.rawExtentInfo);
- }
- if (!scale.rawExtentInfo) {
- scaleRawExtentInfoBuildDefault({
- scale: scale,
- model: model
- }, externalDataExtent || initExtentForUnion());
- }
- var rawExtentResult = scale.rawExtentInfo.makeFinal();
- // NOTE: This `scale.setExtent()` is required by:
- // - `axisNiceTicks.ts` and `axisAlignTicks.ts`, where the internal `scaleMapper` may be required.
- var effectiveMinMax = rawExtentResult.effMM;
- scale.setExtent(effectiveMinMax[0], effectiveMinMax[1]);
- scale.setBlank(rawExtentResult.isBlank);
- if (axis && rawExtentResult.tggAxInv && ecModel && !ecModel.get('legacyMinMaxDontInverseAxis')) {
- axis.inverse = !axis.inverse;
- }
- return rawExtentResult;
- }
- function determineRequireContainShape(scale, axis, model) {
- var onBand = isAxisOnBand(scale, model);
- var modelContainShape = model.get('containShape', true);
- if (modelContainShape == null && !onBand) {
- modelContainShape = true;
- }
- if (!modelContainShape) {
- return false;
- }
- var requireContainShape = false;
- eachKeyOnAxis(axis, function (axisStatKey) {
- requireContainShape = !!axisContainShapeHandlerMap.get(axisStatKey) || requireContainShape;
- });
- return requireContainShape;
- }
- /**
- * This implements ec option `someAxis.containShape`. That is, expand scale extent slightly to
- * ensure shapes of specific series are fully contained in the axis extent without overflow.
- *
- * NOTICE:
- * Scale extent (data extent) and axis pixel extent (pixel extent) and are required as inputs.
- * - See BAND_WIDTH_USED_SCALE_LINEAR_SPAN.
- * - Axis pixel extent has been set outside, though it may be modified later (e.g., via `outerBounds`).
- *
- * @tutorial [AXIS_CONTAIN_SHAPE_PROCESSING_ORDER]
- * This is a trade-off between the following 2 approaches:
- * - Steps: (the current implementation)
- * 1. Process `dataZoom` based on a full window `noZoomEffMM`.
- * 2. Perform "nice" or "align" scale, where `intervalScaleEnsureValidExtent`-ish may be performed to
- * expand extent to avoid `extent[0] === extent[1]`.
- * 3. Calculate linear supplement of containShape based on the final result.
- * Cons:
- * - Abrupt changes occur when zooming away from 0% or 100%.
- * - Edge shapes are clipped in "dataZoom shadow".
- * - Steps: (discarded)
- * 1. Calculate linear supplement of containShape based on `noZoomEffMM` and
- * `intervalScaleEnsureValidExtent`-ish.
- * 2. Process `dataZoom` based on a full window `noZoomEffMM + linearSupplement`.
- * 3. Perform "nice"/"align" scale.
- * Cons:
- * - Input `startValue: 0` (in ec option or action) does not corresponds to `0%`, which is unacceptable.
- * - Not easy to perform `intervalScaleEnsureValidExtent`-ish before "nice"/"align" processing.
- *
- * @see SCALE_EXTENT_CONSTRUCTION for the full processing flow.
- */
- export function adoptScaleExtentKindMapping(axis, scale, rawExtentResult, ecModel) {
- if (!rawExtentResult.ctnShp) {
- return;
- }
- var linearSupplement;
- eachKeyOnAxis(axis, function (axisStatKey) {
- var handler = axisContainShapeHandlerMap.get(axisStatKey);
- if (handler) {
- // This feature can be implemented by either expanding axis extent or scale extent. The choice depends
- // on whether series shape sizes are defined in pixels or data space. For example, scatter series glyph
- // sizes is mainly defined in pixel, while bar series `bandWidth` is mainly determined by given percents
- // of data scale. Since currently scatter does not require this feature, we implement it only on the
- // data scale.
- var singleLinearSupplement = handler(axis, ecModel);
- if (singleLinearSupplement) {
- linearSupplement = linearSupplement || [0, 0];
- unionExtentStartFromNumber(linearSupplement, singleLinearSupplement[0]);
- unionExtentEndFromNumber(linearSupplement, singleLinearSupplement[1]);
- // Consider the consistency of `onZero` behavior (not varying by series data; otherwise error-prone
- // to users), if any `containShape` really performed on this axis, discourage `onZero`.
- discourageOnAxisZero(axis);
- }
- }
- });
- if (!linearSupplement) {
- return;
- }
- var scaleExtent = scale.getExtent();
- if (isOrdinalScale(scale)) {
- if (!axis.onBand) {
- // - Zooming on `OrdinalScale` auto "snaps" to integer ticks, which causes edge shapes to
- // always overlap and be clipped at the boundaries. Therefore we always supplement it with
- // half bandWith to avoid that overlapping.
- // - `linearSupplement` is typically [-0.5, 0.5] in this case. `linearSupplement` exists only
- // if any series call `registerAxisContainShapeHandler`.
- // - PENDING: For historical reason, `onBand: true` has another implementation to handle
- // this case. Merge them to this?
- scale.setExtent2(SCALE_EXTENT_KIND_MAPPING, mathMin(scaleExtent[0], scaleExtent[0] + linearSupplement[0]), mathMax(scaleExtent[1], scaleExtent[1] + linearSupplement[1]));
- }
- } else {
- // For other cases, `SCALE_EXTENT_KIND_MAPPING` is only used on the full window of `dataZoom`,
- // where the visual result is more intuitive when zooming: when dataZoom is applied and its ends
- // (i.e., `zoomMM`) do not reach 0% or 100%, the axis ends should exactly respect to the dataZoom
- // ends, and shapes are clipped if overflowing.
- var scaleExtentExpanded = scaleExtent.slice();
- if (!rawExtentResult.zoomFixMM[0]) {
- scaleExtentExpanded[0] = mathMin(scaleExtentExpanded[0], scale.transformOut(scale.transformIn(scaleExtentExpanded[0], null) + linearSupplement[0], null));
- }
- if (!rawExtentResult.zoomFixMM[1]) {
- scaleExtentExpanded[1] = mathMax(scaleExtentExpanded[1], scale.transformOut(scale.transformIn(scaleExtentExpanded[1], null) + linearSupplement[1], null));
- }
- if (scaleExtentExpanded[0] < scaleExtent[0] || scaleExtentExpanded[1] > scaleExtent[1]) {
- scale.setExtent2(SCALE_EXTENT_KIND_MAPPING, scaleExtentExpanded[0], scaleExtentExpanded[1]);
- }
- }
- // NOTE: since currently `SCALE_EXTENT_KIND_MAPPING` is never required to be displayed, we
- // do not need to find a proper precision for that. But if it is required in the future, We
- // can use `getAcceptableTickPrecision` to find a proper precision.
- }
|