|  | /* | 
|  | * 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. | 
|  | */ | 
|  |  | 
|  | /** | 
|  | * Grid is a region which contains at most 4 cartesian systems | 
|  | * | 
|  | * TODO Default cartesian | 
|  | */ | 
|  |  | 
|  | import {isObject, each, indexOf, retrieve3, keys} from 'zrender/src/core/util'; | 
|  | import {getLayoutRect, LayoutRect} from '../../util/layout'; | 
|  | import { | 
|  | createScaleByModel, | 
|  | ifAxisCrossZero, | 
|  | niceScaleExtent, | 
|  | estimateLabelUnionRect, | 
|  | getDataDimensionsOnAxis | 
|  | } from '../../coord/axisHelper'; | 
|  | import Cartesian2D, {cartesian2DDimensions} from './Cartesian2D'; | 
|  | import Axis2D from './Axis2D'; | 
|  | import {ParsedModelFinder, ParsedModelFinderKnown, SINGLE_REFERRING} from '../../util/model'; | 
|  |  | 
|  | // Depends on GridModel, AxisModel, which performs preprocess. | 
|  | import GridModel from './GridModel'; | 
|  | import CartesianAxisModel from './AxisModel'; | 
|  | import GlobalModel from '../../model/Global'; | 
|  | import ExtensionAPI from '../../core/ExtensionAPI'; | 
|  | import { Dictionary } from 'zrender/src/core/types'; | 
|  | import {CoordinateSystemMaster} from '../CoordinateSystem'; | 
|  | import { ScaleDataValue } from '../../util/types'; | 
|  | import SeriesData from '../../data/SeriesData'; | 
|  | import OrdinalScale from '../../scale/Ordinal'; | 
|  | import { isCartesian2DSeries, findAxisModels } from './cartesianAxisHelper'; | 
|  | import { CategoryAxisBaseOption, NumericAxisBaseOptionCommon } from '../axisCommonTypes'; | 
|  | import { AxisBaseModel } from '../AxisBaseModel'; | 
|  | import { isIntervalOrLogScale } from '../../scale/helper'; | 
|  | import { alignScaleTicks } from '../axisAlignTicks'; | 
|  | import IntervalScale from '../../scale/Interval'; | 
|  | import LogScale from '../../scale/Log'; | 
|  |  | 
|  |  | 
|  | type Cartesian2DDimensionName = 'x' | 'y'; | 
|  |  | 
|  | type FinderAxisIndex = {xAxisIndex?: number, yAxisIndex?: number}; | 
|  | type AxesMap = { | 
|  | x: Axis2D[], | 
|  | y: Axis2D[] | 
|  | }; | 
|  |  | 
|  | class Grid implements CoordinateSystemMaster { | 
|  |  | 
|  | // FIXME:TS where used (different from registered type 'cartesian2d')? | 
|  | readonly type: string = 'grid'; | 
|  |  | 
|  | private _coordsMap: Dictionary<Cartesian2D> = {}; | 
|  | private _coordsList: Cartesian2D[] = []; | 
|  | private _axesMap: AxesMap = {} as AxesMap; | 
|  | private _axesList: Axis2D[] = []; | 
|  | private _rect: LayoutRect; | 
|  |  | 
|  | readonly model: GridModel; | 
|  | readonly axisPointerEnabled = true; | 
|  |  | 
|  | // Injected: | 
|  | name: string; | 
|  |  | 
|  | // For deciding which dimensions to use when creating list data | 
|  | static dimensions = cartesian2DDimensions; | 
|  | readonly dimensions = cartesian2DDimensions; | 
|  |  | 
|  | constructor(gridModel: GridModel, ecModel: GlobalModel, api: ExtensionAPI) { | 
|  | this._initCartesian(gridModel, ecModel, api); | 
|  | this.model = gridModel; | 
|  | } | 
|  |  | 
|  | getRect(): LayoutRect { | 
|  | return this._rect; | 
|  | } | 
|  |  | 
|  | update(ecModel: GlobalModel, api: ExtensionAPI): void { | 
|  |  | 
|  | const axesMap = this._axesMap; | 
|  |  | 
|  | this._updateScale(ecModel, this.model); | 
|  |  | 
|  | function updateAxisTicks(axes: Record<number, Axis2D>) { | 
|  | let alignTo: Axis2D; | 
|  | // Axis is added in order of axisIndex. | 
|  | const axesIndices = keys(axes); | 
|  | const len = axesIndices.length; | 
|  | if (!len) { | 
|  | return; | 
|  | } | 
|  | const axisNeedsAlign: Axis2D[] = []; | 
|  | // Process once and calculate the ticks for those don't use alignTicks. | 
|  | for (let i = len - 1; i >= 0; i--) { | 
|  | const idx = +axesIndices[i];    // Convert to number. | 
|  | const axis = axes[idx]; | 
|  | const model = axis.model as AxisBaseModel<NumericAxisBaseOptionCommon>; | 
|  | const scale = axis.scale; | 
|  | if (// Only value and log axis without interval support alignTicks. | 
|  | isIntervalOrLogScale(scale) | 
|  | && model.get('alignTicks') | 
|  | && model.get('interval') == null | 
|  | ) { | 
|  | axisNeedsAlign.push(axis); | 
|  | } | 
|  | else { | 
|  | niceScaleExtent(scale, model); | 
|  | if (isIntervalOrLogScale(scale)) {  // Can only align to interval or log axis. | 
|  | alignTo = axis; | 
|  | } | 
|  | } | 
|  | }; | 
|  | // All axes has set alignTicks. Pick the first one. | 
|  | // PENDING. Should we find the axis that both set interval, min, max and align to this one? | 
|  | if (axisNeedsAlign.length) { | 
|  | if (!alignTo) { | 
|  | alignTo = axisNeedsAlign.pop(); | 
|  | niceScaleExtent(alignTo.scale, alignTo.model); | 
|  | } | 
|  |  | 
|  | each(axisNeedsAlign, axis => { | 
|  | alignScaleTicks( | 
|  | axis.scale as IntervalScale | LogScale, | 
|  | axis.model, | 
|  | alignTo.scale as IntervalScale | LogScale | 
|  | ); | 
|  | }); | 
|  | } | 
|  | } | 
|  |  | 
|  | updateAxisTicks(axesMap.x); | 
|  | updateAxisTicks(axesMap.y); | 
|  |  | 
|  | // Key: axisDim_axisIndex, value: boolean, whether onZero target. | 
|  | const onZeroRecords = {} as Dictionary<boolean>; | 
|  |  | 
|  | each(axesMap.x, function (xAxis) { | 
|  | fixAxisOnZero(axesMap, 'y', xAxis, onZeroRecords); | 
|  | }); | 
|  | each(axesMap.y, function (yAxis) { | 
|  | fixAxisOnZero(axesMap, 'x', yAxis, onZeroRecords); | 
|  | }); | 
|  |  | 
|  | // Resize again if containLabel is enabled | 
|  | // FIXME It may cause getting wrong grid size in data processing stage | 
|  | this.resize(this.model, api); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Resize the grid | 
|  | */ | 
|  | resize(gridModel: GridModel, api: ExtensionAPI, ignoreContainLabel?: boolean): void { | 
|  |  | 
|  | const boxLayoutParams = gridModel.getBoxLayoutParams(); | 
|  | const isContainLabel = !ignoreContainLabel && gridModel.get('containLabel'); | 
|  |  | 
|  | const gridRect = getLayoutRect( | 
|  | boxLayoutParams, { | 
|  | width: api.getWidth(), | 
|  | height: api.getHeight() | 
|  | }); | 
|  |  | 
|  | this._rect = gridRect; | 
|  |  | 
|  | const axesList = this._axesList; | 
|  |  | 
|  | adjustAxes(); | 
|  |  | 
|  | // Minus label size | 
|  | if (isContainLabel) { | 
|  | each(axesList, function (axis) { | 
|  | if (!axis.model.get(['axisLabel', 'inside'])) { | 
|  | const labelUnionRect = estimateLabelUnionRect(axis); | 
|  | if (labelUnionRect) { | 
|  | const dim: 'height' | 'width' = axis.isHorizontal() ? 'height' : 'width'; | 
|  | const margin = axis.model.get(['axisLabel', 'margin']); | 
|  | gridRect[dim] -= labelUnionRect[dim] + margin; | 
|  | if (axis.position === 'top') { | 
|  | gridRect.y += labelUnionRect.height + margin; | 
|  | } | 
|  | else if (axis.position === 'left') { | 
|  | gridRect.x += labelUnionRect.width + margin; | 
|  | } | 
|  | } | 
|  | } | 
|  | }); | 
|  |  | 
|  | adjustAxes(); | 
|  | } | 
|  |  | 
|  | each(this._coordsList, function (coord) { | 
|  | // Calculate affine matrix to accelerate the data to point transform. | 
|  | // If all the axes scales are time or value. | 
|  | coord.calcAffineTransform(); | 
|  | }); | 
|  |  | 
|  | function adjustAxes() { | 
|  | each(axesList, function (axis) { | 
|  | const isHorizontal = axis.isHorizontal(); | 
|  | const extent = isHorizontal ? [0, gridRect.width] : [0, gridRect.height]; | 
|  | const idx = axis.inverse ? 1 : 0; | 
|  | axis.setExtent(extent[idx], extent[1 - idx]); | 
|  | updateAxisTransform(axis, isHorizontal ? gridRect.x : gridRect.y); | 
|  | }); | 
|  | } | 
|  | } | 
|  |  | 
|  | getAxis(dim: Cartesian2DDimensionName, axisIndex?: number): Axis2D { | 
|  | const axesMapOnDim = this._axesMap[dim]; | 
|  | if (axesMapOnDim != null) { | 
|  | return axesMapOnDim[axisIndex || 0]; | 
|  | } | 
|  | } | 
|  |  | 
|  | getAxes(): Axis2D[] { | 
|  | return this._axesList.slice(); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Usage: | 
|  | *      grid.getCartesian(xAxisIndex, yAxisIndex); | 
|  | *      grid.getCartesian(xAxisIndex); | 
|  | *      grid.getCartesian(null, yAxisIndex); | 
|  | *      grid.getCartesian({xAxisIndex: ..., yAxisIndex: ...}); | 
|  | * | 
|  | * When only xAxisIndex or yAxisIndex given, find its first cartesian. | 
|  | */ | 
|  | getCartesian(finder: FinderAxisIndex): Cartesian2D; | 
|  | getCartesian(xAxisIndex?: number, yAxisIndex?: number): Cartesian2D; | 
|  | getCartesian(xAxisIndex?: number | FinderAxisIndex, yAxisIndex?: number) { | 
|  | if (xAxisIndex != null && yAxisIndex != null) { | 
|  | const key = 'x' + xAxisIndex + 'y' + yAxisIndex; | 
|  | return this._coordsMap[key]; | 
|  | } | 
|  |  | 
|  | if (isObject(xAxisIndex)) { | 
|  | yAxisIndex = (xAxisIndex as FinderAxisIndex).yAxisIndex; | 
|  | xAxisIndex = (xAxisIndex as FinderAxisIndex).xAxisIndex; | 
|  | } | 
|  | for (let i = 0, coordList = this._coordsList; i < coordList.length; i++) { | 
|  | if (coordList[i].getAxis('x').index === xAxisIndex | 
|  | || coordList[i].getAxis('y').index === yAxisIndex | 
|  | ) { | 
|  | return coordList[i]; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | getCartesians(): Cartesian2D[] { | 
|  | return this._coordsList.slice(); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @implements | 
|  | */ | 
|  | convertToPixel( | 
|  | ecModel: GlobalModel, finder: ParsedModelFinder, value: ScaleDataValue | ScaleDataValue[] | 
|  | ): number | number[] { | 
|  | const target = this._findConvertTarget(finder); | 
|  |  | 
|  | return target.cartesian | 
|  | ? target.cartesian.dataToPoint(value as ScaleDataValue[]) | 
|  | : target.axis | 
|  | ? target.axis.toGlobalCoord(target.axis.dataToCoord(value as ScaleDataValue)) | 
|  | : null; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @implements | 
|  | */ | 
|  | convertFromPixel( | 
|  | ecModel: GlobalModel, finder: ParsedModelFinder, value: number | number[] | 
|  | ): number | number[] { | 
|  | const target = this._findConvertTarget(finder); | 
|  |  | 
|  | return target.cartesian | 
|  | ? target.cartesian.pointToData(value as number[]) | 
|  | : target.axis | 
|  | ? target.axis.coordToData(target.axis.toLocalCoord(value as number)) | 
|  | : null; | 
|  | } | 
|  |  | 
|  | private _findConvertTarget(finder: ParsedModelFinderKnown): { | 
|  | cartesian: Cartesian2D, | 
|  | axis: Axis2D | 
|  | } { | 
|  | const seriesModel = finder.seriesModel; | 
|  | const xAxisModel = finder.xAxisModel | 
|  | || (seriesModel && seriesModel.getReferringComponents('xAxis', SINGLE_REFERRING).models[0]); | 
|  | const yAxisModel = finder.yAxisModel | 
|  | || (seriesModel && seriesModel.getReferringComponents('yAxis', SINGLE_REFERRING).models[0]); | 
|  | const gridModel = finder.gridModel; | 
|  | const coordsList = this._coordsList; | 
|  | let cartesian: Cartesian2D; | 
|  | let axis; | 
|  |  | 
|  | if (seriesModel) { | 
|  | cartesian = seriesModel.coordinateSystem as Cartesian2D; | 
|  | indexOf(coordsList, cartesian) < 0 && (cartesian = null); | 
|  | } | 
|  | else if (xAxisModel && yAxisModel) { | 
|  | cartesian = this.getCartesian(xAxisModel.componentIndex, yAxisModel.componentIndex); | 
|  | } | 
|  | else if (xAxisModel) { | 
|  | axis = this.getAxis('x', xAxisModel.componentIndex); | 
|  | } | 
|  | else if (yAxisModel) { | 
|  | axis = this.getAxis('y', yAxisModel.componentIndex); | 
|  | } | 
|  | // Lowest priority. | 
|  | else if (gridModel) { | 
|  | const grid = gridModel.coordinateSystem; | 
|  | if (grid === this) { | 
|  | cartesian = this._coordsList[0]; | 
|  | } | 
|  | } | 
|  |  | 
|  | return {cartesian: cartesian, axis: axis}; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @implements | 
|  | */ | 
|  | containPoint(point: number[]): boolean { | 
|  | const coord = this._coordsList[0]; | 
|  | if (coord) { | 
|  | return coord.containPoint(point); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Initialize cartesian coordinate systems | 
|  | */ | 
|  | private _initCartesian( | 
|  | gridModel: GridModel, ecModel: GlobalModel, api: ExtensionAPI | 
|  | ): void { | 
|  | const grid = this; | 
|  | const axisPositionUsed = { | 
|  | left: false, | 
|  | right: false, | 
|  | top: false, | 
|  | bottom: false | 
|  | }; | 
|  |  | 
|  | const axesMap = { | 
|  | x: {}, | 
|  | y: {} | 
|  | } as AxesMap; | 
|  | const axesCount = { | 
|  | x: 0, | 
|  | y: 0 | 
|  | }; | 
|  |  | 
|  | // Create axis | 
|  | ecModel.eachComponent('xAxis', createAxisCreator('x'), this); | 
|  | ecModel.eachComponent('yAxis', createAxisCreator('y'), this); | 
|  |  | 
|  | if (!axesCount.x || !axesCount.y) { | 
|  | // Roll back when there no either x or y axis | 
|  | this._axesMap = {} as AxesMap; | 
|  | this._axesList = []; | 
|  | return; | 
|  | } | 
|  |  | 
|  | this._axesMap = axesMap; | 
|  |  | 
|  | // Create cartesian2d | 
|  | each(axesMap.x, (xAxis, xAxisIndex) => { | 
|  | each(axesMap.y, (yAxis, yAxisIndex) => { | 
|  | const key = 'x' + xAxisIndex + 'y' + yAxisIndex; | 
|  | const cartesian = new Cartesian2D(key); | 
|  |  | 
|  | cartesian.master = this; | 
|  | cartesian.model = gridModel; | 
|  |  | 
|  | this._coordsMap[key] = cartesian; | 
|  | this._coordsList.push(cartesian); | 
|  |  | 
|  | cartesian.addAxis(xAxis); | 
|  | cartesian.addAxis(yAxis); | 
|  | }); | 
|  | }); | 
|  |  | 
|  | function createAxisCreator(dimName: Cartesian2DDimensionName) { | 
|  | return function (axisModel: CartesianAxisModel, idx: number): void { | 
|  | if (!isAxisUsedInTheGrid(axisModel, gridModel)) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | let axisPosition = axisModel.get('position'); | 
|  | if (dimName === 'x') { | 
|  | // Fix position | 
|  | if (axisPosition !== 'top' && axisPosition !== 'bottom') { | 
|  | // Default bottom of X | 
|  | axisPosition = axisPositionUsed.bottom ? 'top' : 'bottom'; | 
|  | } | 
|  | } | 
|  | else { | 
|  | // Fix position | 
|  | if (axisPosition !== 'left' && axisPosition !== 'right') { | 
|  | // Default left of Y | 
|  | axisPosition = axisPositionUsed.left ? 'right' : 'left'; | 
|  | } | 
|  | } | 
|  | axisPositionUsed[axisPosition] = true; | 
|  |  | 
|  | const axis = new Axis2D( | 
|  | dimName, | 
|  | createScaleByModel(axisModel), | 
|  | [0, 0], | 
|  | axisModel.get('type'), | 
|  | axisPosition | 
|  | ); | 
|  |  | 
|  | const isCategory = axis.type === 'category'; | 
|  | axis.onBand = isCategory && (axisModel as AxisBaseModel<CategoryAxisBaseOption>).get('boundaryGap'); | 
|  | axis.inverse = axisModel.get('inverse'); | 
|  |  | 
|  | // Inject axis into axisModel | 
|  | axisModel.axis = axis; | 
|  |  | 
|  | // Inject axisModel into axis | 
|  | axis.model = axisModel; | 
|  |  | 
|  | // Inject grid info axis | 
|  | axis.grid = grid; | 
|  |  | 
|  | // Index of axis, can be used as key | 
|  | axis.index = idx; | 
|  |  | 
|  | grid._axesList.push(axis); | 
|  |  | 
|  | axesMap[dimName][idx] = axis; | 
|  | axesCount[dimName]++; | 
|  | }; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Update cartesian properties from series. | 
|  | */ | 
|  | private _updateScale(ecModel: GlobalModel, gridModel: GridModel): void { | 
|  | // Reset scale | 
|  | each(this._axesList, function (axis) { | 
|  | axis.scale.setExtent(Infinity, -Infinity); | 
|  | if (axis.type === 'category') { | 
|  | const categorySortInfo = axis.model.get('categorySortInfo'); | 
|  | (axis.scale as OrdinalScale).setSortInfo(categorySortInfo); | 
|  | } | 
|  | }); | 
|  |  | 
|  | ecModel.eachSeries(function (seriesModel) { | 
|  | if (isCartesian2DSeries(seriesModel)) { | 
|  | const axesModelMap = findAxisModels(seriesModel); | 
|  | const xAxisModel = axesModelMap.xAxisModel; | 
|  | const yAxisModel = axesModelMap.yAxisModel; | 
|  |  | 
|  | if (!isAxisUsedInTheGrid(xAxisModel, gridModel) | 
|  | || !isAxisUsedInTheGrid(yAxisModel, gridModel) | 
|  | ) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | const cartesian = this.getCartesian( | 
|  | xAxisModel.componentIndex, yAxisModel.componentIndex | 
|  | ); | 
|  | const data = seriesModel.getData(); | 
|  | const xAxis = cartesian.getAxis('x'); | 
|  | const yAxis = cartesian.getAxis('y'); | 
|  |  | 
|  | unionExtent(data, xAxis); | 
|  | unionExtent(data, yAxis); | 
|  | } | 
|  | }, this); | 
|  |  | 
|  | function unionExtent(data: SeriesData, axis: Axis2D): void { | 
|  | each(getDataDimensionsOnAxis(data, axis.dim), function (dim) { | 
|  | axis.scale.unionExtentFromData(data, dim); | 
|  | }); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param dim 'x' or 'y' or 'auto' or null/undefined | 
|  | */ | 
|  | getTooltipAxes(dim: Cartesian2DDimensionName | 'auto'): { | 
|  | baseAxes: Axis2D[], otherAxes: Axis2D[] | 
|  | } { | 
|  | const baseAxes = [] as Axis2D[]; | 
|  | const otherAxes = [] as Axis2D[]; | 
|  |  | 
|  | each(this.getCartesians(), function (cartesian) { | 
|  | const baseAxis = (dim != null && dim !== 'auto') | 
|  | ? cartesian.getAxis(dim) : cartesian.getBaseAxis(); | 
|  | const otherAxis = cartesian.getOtherAxis(baseAxis); | 
|  | indexOf(baseAxes, baseAxis) < 0 && baseAxes.push(baseAxis); | 
|  | indexOf(otherAxes, otherAxis) < 0 && otherAxes.push(otherAxis); | 
|  | }); | 
|  |  | 
|  | return {baseAxes: baseAxes, otherAxes: otherAxes}; | 
|  | } | 
|  |  | 
|  |  | 
|  | static create(ecModel: GlobalModel, api: ExtensionAPI): Grid[] { | 
|  | const grids = [] as Grid[]; | 
|  | ecModel.eachComponent('grid', function (gridModel: GridModel, idx) { | 
|  | const grid = new Grid(gridModel, ecModel, api); | 
|  | grid.name = 'grid_' + idx; | 
|  | // dataSampling requires axis extent, so resize | 
|  | // should be performed in create stage. | 
|  | grid.resize(gridModel, api, true); | 
|  |  | 
|  | gridModel.coordinateSystem = grid; | 
|  |  | 
|  | grids.push(grid); | 
|  | }); | 
|  |  | 
|  | // Inject the coordinateSystems into seriesModel | 
|  | ecModel.eachSeries(function (seriesModel) { | 
|  | if (!isCartesian2DSeries(seriesModel)) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | const axesModelMap = findAxisModels(seriesModel); | 
|  | const xAxisModel = axesModelMap.xAxisModel; | 
|  | const yAxisModel = axesModelMap.yAxisModel; | 
|  |  | 
|  | const gridModel = xAxisModel.getCoordSysModel(); | 
|  |  | 
|  | if (__DEV__) { | 
|  | if (!gridModel) { | 
|  | throw new Error( | 
|  | 'Grid "' + retrieve3( | 
|  | xAxisModel.get('gridIndex'), | 
|  | xAxisModel.get('gridId'), | 
|  | 0 | 
|  | ) + '" not found' | 
|  | ); | 
|  | } | 
|  | if (xAxisModel.getCoordSysModel() !== yAxisModel.getCoordSysModel()) { | 
|  | throw new Error('xAxis and yAxis must use the same grid'); | 
|  | } | 
|  | } | 
|  |  | 
|  | const grid = gridModel.coordinateSystem as Grid; | 
|  |  | 
|  | seriesModel.coordinateSystem = grid.getCartesian( | 
|  | xAxisModel.componentIndex, yAxisModel.componentIndex | 
|  | ); | 
|  | }); | 
|  |  | 
|  | return grids; | 
|  | } | 
|  |  | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Check if the axis is used in the specified grid. | 
|  | */ | 
|  | function isAxisUsedInTheGrid(axisModel: CartesianAxisModel, gridModel: GridModel): boolean { | 
|  | return axisModel.getCoordSysModel() === gridModel; | 
|  | } | 
|  |  | 
|  | function fixAxisOnZero( | 
|  | axesMap: AxesMap, | 
|  | otherAxisDim: Cartesian2DDimensionName, | 
|  | axis: Axis2D, | 
|  | // Key: see `getOnZeroRecordKey` | 
|  | onZeroRecords: Dictionary<boolean> | 
|  | ): void { | 
|  |  | 
|  | axis.getAxesOnZeroOf = function () { | 
|  | // TODO: onZero of multiple axes. | 
|  | return otherAxisOnZeroOf ? [otherAxisOnZeroOf] : []; | 
|  | }; | 
|  |  | 
|  | // onZero can not be enabled in these two situations: | 
|  | // 1. When any other axis is a category axis. | 
|  | // 2. When no axis is cross 0 point. | 
|  | const otherAxes = axesMap[otherAxisDim]; | 
|  |  | 
|  | let otherAxisOnZeroOf: Axis2D; | 
|  | const axisModel = axis.model; | 
|  | const onZero = axisModel.get(['axisLine', 'onZero']); | 
|  | const onZeroAxisIndex = axisModel.get(['axisLine', 'onZeroAxisIndex']); | 
|  |  | 
|  | if (!onZero) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | // If target axis is specified. | 
|  | if (onZeroAxisIndex != null) { | 
|  | if (canOnZeroToAxis(otherAxes[onZeroAxisIndex])) { | 
|  | otherAxisOnZeroOf = otherAxes[onZeroAxisIndex]; | 
|  | } | 
|  | } | 
|  | else { | 
|  | // Find the first available other axis. | 
|  | for (const idx in otherAxes) { | 
|  | if (otherAxes.hasOwnProperty(idx) | 
|  | && canOnZeroToAxis(otherAxes[idx]) | 
|  | // Consider that two Y axes on one value axis, | 
|  | // if both onZero, the two Y axes overlap. | 
|  | && !onZeroRecords[getOnZeroRecordKey(otherAxes[idx])] | 
|  | ) { | 
|  | otherAxisOnZeroOf = otherAxes[idx]; | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | if (otherAxisOnZeroOf) { | 
|  | onZeroRecords[getOnZeroRecordKey(otherAxisOnZeroOf)] = true; | 
|  | } | 
|  |  | 
|  | function getOnZeroRecordKey(axis: Axis2D) { | 
|  | return axis.dim + '_' + axis.index; | 
|  | } | 
|  | } | 
|  |  | 
|  | function canOnZeroToAxis(axis: Axis2D): boolean { | 
|  | return axis && axis.type !== 'category' && axis.type !== 'time' && ifAxisCrossZero(axis); | 
|  | } | 
|  |  | 
|  | function updateAxisTransform(axis: Axis2D, coordBase: number) { | 
|  | const axisExtent = axis.getExtent(); | 
|  | const axisExtentSum = axisExtent[0] + axisExtent[1]; | 
|  |  | 
|  | // Fast transform | 
|  | axis.toGlobalCoord = axis.dim === 'x' | 
|  | ? function (coord) { | 
|  | return coord + coordBase; | 
|  | } | 
|  | : function (coord) { | 
|  | return axisExtentSum - coord + coordBase; | 
|  | }; | 
|  | axis.toLocalCoord = axis.dim === 'x' | 
|  | ? function (coord) { | 
|  | return coord - coordBase; | 
|  | } | 
|  | : function (coord) { | 
|  | return axisExtentSum - coord + coordBase; | 
|  | }; | 
|  | } | 
|  |  | 
|  | export default Grid; |