738 lines
30 KiB
JavaScript
738 lines
30 KiB
JavaScript
|
|
/*
|
|
* 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.
|
|
*/
|
|
/**
|
|
* Grid is a region which contains at most 4 cartesian systems
|
|
*
|
|
* TODO Default cartesian
|
|
*/
|
|
import { isObject, each, indexOf, retrieve3, keys, assert, eqNaN, find, retrieve2 } from 'zrender/lib/core/util.js';
|
|
import { createBoxLayoutReference, getLayoutRect } from '../../util/layout.js';
|
|
import { createScaleByModel, ifAxisCrossZero, niceScaleExtent, getDataDimensionsOnAxis, isNameLocationCenter, shouldAxisShow } from '../../coord/axisHelper.js';
|
|
import Cartesian2D, { cartesian2DDimensions } from './Cartesian2D.js';
|
|
import Axis2D from './Axis2D.js';
|
|
import { SINGLE_REFERRING } from '../../util/model.js';
|
|
// Depends on GridModel, AxisModel, which performs preprocess.
|
|
import { OUTER_BOUNDS_CLAMP_DEFAULT, OUTER_BOUNDS_DEFAULT } from './GridModel.js';
|
|
import { findAxisModels, createCartesianAxisViewCommonPartBuilder, updateCartesianAxisViewCommonPartBuilder, isCartesian2DInjectedAsDataCoordSys } from './cartesianAxisHelper.js';
|
|
import { isIntervalOrLogScale } from '../../scale/helper.js';
|
|
import { alignScaleTicks } from '../axisAlignTicks.js';
|
|
import { expandOrShrinkRect, WH, XY } from '../../util/graphic.js';
|
|
import { AxisBuilderSharedContext, resolveAxisNameOverlapDefault, moveIfOverlapByLinearLabels, getLabelInner } from '../../component/axis/AxisBuilder.js';
|
|
import { error, log } from '../../util/log.js';
|
|
import { AxisTickLabelComputingKind } from '../axisTickLabelBuilder.js';
|
|
import { injectCoordSysByOption } from '../../core/CoordinateSystem.js';
|
|
import { mathMax, parsePositionSizeOption } from '../../util/number.js';
|
|
// margin is [top, right, bottom, left]
|
|
var XY_TO_MARGIN_IDX = [[3, 1], [0, 2] // xyIdx 1 => 'y'
|
|
];
|
|
var Grid = /** @class */function () {
|
|
function Grid(gridModel, ecModel, api) {
|
|
// FIXME:TS where used (different from registered type 'cartesian2d')?
|
|
this.type = 'grid';
|
|
this._coordsMap = {};
|
|
this._coordsList = [];
|
|
this._axesMap = {};
|
|
this._axesList = [];
|
|
this.axisPointerEnabled = true;
|
|
this.dimensions = cartesian2DDimensions;
|
|
this._initCartesian(gridModel, ecModel, api);
|
|
this.model = gridModel;
|
|
}
|
|
Grid.prototype.getRect = function () {
|
|
return this._rect;
|
|
};
|
|
Grid.prototype.update = function (ecModel, api) {
|
|
var axesMap = this._axesMap;
|
|
this._updateScale(ecModel, this.model);
|
|
function updateAxisTicks(axes) {
|
|
var alignTo;
|
|
// Axis is added in order of axisIndex.
|
|
var axesIndices = keys(axes);
|
|
var len = axesIndices.length;
|
|
if (!len) {
|
|
return;
|
|
}
|
|
var axisNeedsAlign = [];
|
|
// Process once and calculate the ticks for those don't use alignTicks.
|
|
for (var i = len - 1; i >= 0; i--) {
|
|
var idx = +axesIndices[i]; // Convert to number.
|
|
var axis = axes[idx];
|
|
var model = axis.model;
|
|
var 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, function (axis) {
|
|
alignScaleTicks(axis.scale, axis.model, alignTo.scale);
|
|
});
|
|
}
|
|
}
|
|
updateAxisTicks(axesMap.x);
|
|
updateAxisTicks(axesMap.y);
|
|
// Key: axisDim_axisIndex, value: boolean, whether onZero target.
|
|
var onZeroRecords = {};
|
|
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.
|
|
*
|
|
* [NOTE]
|
|
* If both "grid.containLabel/grid.contain" and pixel-required-data-processing (such as, "dataSampling")
|
|
* exist, circular dependency occurs in logic.
|
|
* The final compromised sequence is:
|
|
* 1. Calculate "axis.extent" (pixel extent) and AffineTransform based on only "grid layout options".
|
|
* Not accurate if "grid.containLabel/grid.contain" is required, but it is a compromise to avoid
|
|
* circular dependency.
|
|
* 2. Perform "series data processing" (where "dataSampling" requires "axis.extent").
|
|
* 3. Calculate "scale.extent" (data extent) based on "processed series data".
|
|
* 4. Modify "axis.extent" for "grid.containLabel/grid.contain":
|
|
* 4.1. Calculate "axis labels" based on "scale.extent".
|
|
* 4.2. Modify "axis.extent" by the bounding rects of "axis labels and names".
|
|
*/
|
|
Grid.prototype.resize = function (gridModel, api, beforeDataProcessing) {
|
|
var layoutRef = createBoxLayoutReference(gridModel, api);
|
|
var gridRect = this._rect = getLayoutRect(gridModel.getBoxLayoutParams(), layoutRef.refContainer);
|
|
// PENDING: whether to support that if the input `coord` is out of the base coord sys,
|
|
// do not render anything. At present, the behavior is undefined.
|
|
var axesMap = this._axesMap;
|
|
var coordsList = this._coordsList;
|
|
var optionContainLabel = gridModel.get('containLabel'); // No `.get(, true)` for backward compat.
|
|
updateAllAxisExtentTransByGridRect(axesMap, gridRect);
|
|
if (!beforeDataProcessing) {
|
|
var axisBuilderSharedCtx = createAxisBiulders(gridRect, coordsList, axesMap, optionContainLabel, api);
|
|
var noPxChange = void 0;
|
|
if (optionContainLabel) {
|
|
if (legacyLayOutGridByContainLabel) {
|
|
// console.time('legacyLayOutGridByContainLabel');
|
|
legacyLayOutGridByContainLabel(this._axesList, gridRect);
|
|
updateAllAxisExtentTransByGridRect(axesMap, gridRect);
|
|
// console.timeEnd('legacyLayOutGridByContainLabel');
|
|
} else {
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
log('Specified `grid.containLabel` but no `use(LegacyGridContainLabel)`;' + 'use `grid.outerBounds` instead.', true);
|
|
}
|
|
noPxChange = layOutGridByOuterBounds(gridRect.clone(), 'axisLabel', null, gridRect, axesMap, axisBuilderSharedCtx, layoutRef);
|
|
}
|
|
} else {
|
|
var _a = prepareOuterBounds(gridModel, gridRect, layoutRef),
|
|
outerBoundsRect = _a.outerBoundsRect,
|
|
parsedOuterBoundsContain = _a.parsedOuterBoundsContain,
|
|
outerBoundsClamp = _a.outerBoundsClamp;
|
|
if (outerBoundsRect) {
|
|
// console.time('layOutGridByOuterBounds');
|
|
noPxChange = layOutGridByOuterBounds(outerBoundsRect, parsedOuterBoundsContain, outerBoundsClamp, gridRect, axesMap, axisBuilderSharedCtx, layoutRef);
|
|
// console.timeEnd('layOutGridByOuterBounds');
|
|
}
|
|
}
|
|
// console.time('buildAxesView_determine');
|
|
createOrUpdateAxesView(gridRect, axesMap, AxisTickLabelComputingKind.determine, null, noPxChange, layoutRef);
|
|
// console.timeEnd('buildAxesView_determine');
|
|
} // End of beforeDataProcessing
|
|
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();
|
|
});
|
|
};
|
|
Grid.prototype.getAxis = function (dim, axisIndex) {
|
|
var axesMapOnDim = this._axesMap[dim];
|
|
if (axesMapOnDim != null) {
|
|
return axesMapOnDim[axisIndex || 0];
|
|
}
|
|
};
|
|
Grid.prototype.getAxes = function () {
|
|
return this._axesList.slice();
|
|
};
|
|
Grid.prototype.getCartesian = function (xAxisIndex, yAxisIndex) {
|
|
if (xAxisIndex != null && yAxisIndex != null) {
|
|
var key = 'x' + xAxisIndex + 'y' + yAxisIndex;
|
|
return this._coordsMap[key];
|
|
}
|
|
if (isObject(xAxisIndex)) {
|
|
yAxisIndex = xAxisIndex.yAxisIndex;
|
|
xAxisIndex = xAxisIndex.xAxisIndex;
|
|
}
|
|
for (var 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];
|
|
}
|
|
}
|
|
};
|
|
Grid.prototype.getCartesians = function () {
|
|
return this._coordsList.slice();
|
|
};
|
|
/**
|
|
* @implements
|
|
*/
|
|
Grid.prototype.convertToPixel = function (ecModel, finder, value) {
|
|
var target = this._findConvertTarget(finder);
|
|
return target.cartesian ? target.cartesian.dataToPoint(value) : target.axis ? target.axis.toGlobalCoord(target.axis.dataToCoord(value)) : null;
|
|
};
|
|
/**
|
|
* @implements
|
|
*/
|
|
Grid.prototype.convertFromPixel = function (ecModel, finder, value) {
|
|
var target = this._findConvertTarget(finder);
|
|
return target.cartesian ? target.cartesian.pointToData(value) : target.axis ? target.axis.coordToData(target.axis.toLocalCoord(value)) : null;
|
|
};
|
|
Grid.prototype._findConvertTarget = function (finder) {
|
|
var seriesModel = finder.seriesModel;
|
|
var xAxisModel = finder.xAxisModel || seriesModel && seriesModel.getReferringComponents('xAxis', SINGLE_REFERRING).models[0];
|
|
var yAxisModel = finder.yAxisModel || seriesModel && seriesModel.getReferringComponents('yAxis', SINGLE_REFERRING).models[0];
|
|
var gridModel = finder.gridModel;
|
|
var coordsList = this._coordsList;
|
|
var cartesian;
|
|
var axis;
|
|
if (seriesModel) {
|
|
cartesian = seriesModel.coordinateSystem;
|
|
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) {
|
|
var grid = gridModel.coordinateSystem;
|
|
if (grid === this) {
|
|
cartesian = this._coordsList[0];
|
|
}
|
|
}
|
|
return {
|
|
cartesian: cartesian,
|
|
axis: axis
|
|
};
|
|
};
|
|
/**
|
|
* @implements
|
|
*/
|
|
Grid.prototype.containPoint = function (point) {
|
|
var coord = this._coordsList[0];
|
|
if (coord) {
|
|
return coord.containPoint(point);
|
|
}
|
|
};
|
|
/**
|
|
* Initialize cartesian coordinate systems
|
|
*/
|
|
Grid.prototype._initCartesian = function (gridModel, ecModel, api) {
|
|
var _this = this;
|
|
var grid = this;
|
|
var axisPositionUsed = {
|
|
left: false,
|
|
right: false,
|
|
top: false,
|
|
bottom: false
|
|
};
|
|
var axesMap = {
|
|
x: {},
|
|
y: {}
|
|
};
|
|
var 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 = {};
|
|
this._axesList = [];
|
|
return;
|
|
}
|
|
this._axesMap = axesMap;
|
|
// Create cartesian2d
|
|
each(axesMap.x, function (xAxis, xAxisIndex) {
|
|
each(axesMap.y, function (yAxis, yAxisIndex) {
|
|
var key = 'x' + xAxisIndex + 'y' + yAxisIndex;
|
|
var 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) {
|
|
return function (axisModel, idx) {
|
|
if (!isAxisUsedInTheGrid(axisModel, gridModel)) {
|
|
return;
|
|
}
|
|
var 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;
|
|
var axis = new Axis2D(dimName, createScaleByModel(axisModel), [0, 0], axisModel.get('type'), axisPosition);
|
|
var isCategory = axis.type === 'category';
|
|
axis.onBand = isCategory && axisModel.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.
|
|
*/
|
|
Grid.prototype._updateScale = function (ecModel, gridModel) {
|
|
// Reset scale
|
|
each(this._axesList, function (axis) {
|
|
axis.scale.setExtent(Infinity, -Infinity);
|
|
if (axis.type === 'category') {
|
|
var categorySortInfo = axis.model.get('categorySortInfo');
|
|
axis.scale.setSortInfo(categorySortInfo);
|
|
}
|
|
});
|
|
ecModel.eachSeries(function (seriesModel) {
|
|
// If pie (or other similar series) use cartesian2d, the unionExtent logic below is
|
|
// wrong, therefore skip it temporarily. See also in `defaultAxisExtentFromData.ts`.
|
|
// TODO: support union extent in this case.
|
|
if (isCartesian2DInjectedAsDataCoordSys(seriesModel)) {
|
|
var axesModelMap = findAxisModels(seriesModel);
|
|
var xAxisModel = axesModelMap.xAxisModel;
|
|
var yAxisModel = axesModelMap.yAxisModel;
|
|
if (!isAxisUsedInTheGrid(xAxisModel, gridModel) || !isAxisUsedInTheGrid(yAxisModel, gridModel)) {
|
|
return;
|
|
}
|
|
var cartesian = this.getCartesian(xAxisModel.componentIndex, yAxisModel.componentIndex);
|
|
var data = seriesModel.getData();
|
|
var xAxis = cartesian.getAxis('x');
|
|
var yAxis = cartesian.getAxis('y');
|
|
unionExtent(data, xAxis);
|
|
unionExtent(data, yAxis);
|
|
}
|
|
}, this);
|
|
function unionExtent(data, axis) {
|
|
each(getDataDimensionsOnAxis(data, axis.dim), function (dim) {
|
|
axis.scale.unionExtentFromData(data, dim);
|
|
});
|
|
}
|
|
};
|
|
/**
|
|
* @param dim 'x' or 'y' or 'auto' or null/undefined
|
|
*/
|
|
Grid.prototype.getTooltipAxes = function (dim) {
|
|
var baseAxes = [];
|
|
var otherAxes = [];
|
|
each(this.getCartesians(), function (cartesian) {
|
|
var baseAxis = dim != null && dim !== 'auto' ? cartesian.getAxis(dim) : cartesian.getBaseAxis();
|
|
var otherAxis = cartesian.getOtherAxis(baseAxis);
|
|
indexOf(baseAxes, baseAxis) < 0 && baseAxes.push(baseAxis);
|
|
indexOf(otherAxes, otherAxis) < 0 && otherAxes.push(otherAxis);
|
|
});
|
|
return {
|
|
baseAxes: baseAxes,
|
|
otherAxes: otherAxes
|
|
};
|
|
};
|
|
Grid.create = function (ecModel, api) {
|
|
var grids = [];
|
|
ecModel.eachComponent('grid', function (gridModel, idx) {
|
|
var 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) {
|
|
injectCoordSysByOption({
|
|
targetModel: seriesModel,
|
|
coordSysType: 'cartesian2d',
|
|
coordSysProvider: coordSysProvider
|
|
});
|
|
function coordSysProvider() {
|
|
var axesModelMap = findAxisModels(seriesModel);
|
|
var xAxisModel = axesModelMap.xAxisModel;
|
|
var yAxisModel = axesModelMap.yAxisModel;
|
|
var gridModel = xAxisModel.getCoordSysModel();
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
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');
|
|
}
|
|
}
|
|
var grid = gridModel.coordinateSystem;
|
|
return grid.getCartesian(xAxisModel.componentIndex, yAxisModel.componentIndex);
|
|
}
|
|
});
|
|
return grids;
|
|
};
|
|
// For deciding which dimensions to use when creating list data
|
|
Grid.dimensions = cartesian2DDimensions;
|
|
return Grid;
|
|
}();
|
|
/**
|
|
* Check if the axis is used in the specified grid.
|
|
*/
|
|
function isAxisUsedInTheGrid(axisModel, gridModel) {
|
|
return axisModel.getCoordSysModel() === gridModel;
|
|
}
|
|
function fixAxisOnZero(axesMap, otherAxisDim, axis,
|
|
// Key: see `getOnZeroRecordKey`
|
|
onZeroRecords) {
|
|
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.
|
|
var otherAxes = axesMap[otherAxisDim];
|
|
var otherAxisOnZeroOf;
|
|
var axisModel = axis.model;
|
|
var onZero = axisModel.get(['axisLine', 'onZero']);
|
|
var 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 (var 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) {
|
|
return axis.dim + '_' + axis.index;
|
|
}
|
|
}
|
|
function canOnZeroToAxis(axis) {
|
|
return axis && axis.type !== 'category' && axis.type !== 'time' && ifAxisCrossZero(axis);
|
|
}
|
|
function updateAxisTransform(axis, coordBase) {
|
|
var axisExtent = axis.getExtent();
|
|
var 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;
|
|
};
|
|
}
|
|
function updateAllAxisExtentTransByGridRect(axesMap, gridRect) {
|
|
each(axesMap.x, function (axis) {
|
|
return updateAxisExtentTransByGridRect(axis, gridRect.x, gridRect.width);
|
|
});
|
|
each(axesMap.y, function (axis) {
|
|
return updateAxisExtentTransByGridRect(axis, gridRect.y, gridRect.height);
|
|
});
|
|
}
|
|
function updateAxisExtentTransByGridRect(axis, gridXY, gridWH) {
|
|
var extent = [0, gridWH];
|
|
var idx = axis.inverse ? 1 : 0;
|
|
axis.setExtent(extent[idx], extent[1 - idx]);
|
|
updateAxisTransform(axis, gridXY);
|
|
}
|
|
var legacyLayOutGridByContainLabel;
|
|
export function registerLegacyGridContainLabelImpl(impl) {
|
|
legacyLayOutGridByContainLabel = impl;
|
|
}
|
|
// Return noPxChange.
|
|
function layOutGridByOuterBounds(outerBoundsRect, outerBoundsContain, outerBoundsClamp, gridRect, axesMap, axisBuilderSharedCtx, layoutRef) {
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
assert(outerBoundsContain === 'all' || outerBoundsContain === 'axisLabel');
|
|
}
|
|
// Assume `updateAllAxisExtentTransByGridRect` has been performed once before this call.
|
|
// [NOTE]:
|
|
// - The bounding rect of the axis elements might be sensitve to variations in `axis.extent` due to strategies
|
|
// like hideOverlap/moveOverlap. @see the comment in `LabelLayoutBase['suggestIgnore']`.
|
|
// - The final `gridRect` might be slightly smaller than the ideally expected result if labels are giant and
|
|
// get hidden due to overlapping. More iterations could improve precision, but not performant. We consider
|
|
// the current result acceptable, since no alignment among charts can be guaranteed when using this feature.
|
|
createOrUpdateAxesView(gridRect, axesMap, AxisTickLabelComputingKind.estimate, outerBoundsContain, false, layoutRef);
|
|
var margin = [0, 0, 0, 0];
|
|
fillLabelNameOverflowOnOneDimension(0);
|
|
fillLabelNameOverflowOnOneDimension(1);
|
|
// If axis is blank, no label can be used to detect overflow.
|
|
// gridRect itself should not overflow.
|
|
fillMarginOnOneDimension(gridRect, 0, NaN);
|
|
fillMarginOnOneDimension(gridRect, 1, NaN);
|
|
var noPxChange = find(margin, function (item) {
|
|
return item > 0;
|
|
}) == null;
|
|
expandOrShrinkRect(gridRect, margin, true, true, outerBoundsClamp);
|
|
updateAllAxisExtentTransByGridRect(axesMap, gridRect);
|
|
return noPxChange;
|
|
function fillLabelNameOverflowOnOneDimension(xyIdx) {
|
|
each(axesMap[XY[xyIdx]], function (axis) {
|
|
if (!shouldAxisShow(axis.model)) {
|
|
return;
|
|
}
|
|
// FIXME: zr Group.union may wrongly union (0, 0, 0, 0) and not performant.
|
|
// unionRect.union(axis.axisBuilder.group.getBoundingRect());
|
|
// If ussing Group.getBoundingRect to calculate shrink space, it is not strictly accurate when
|
|
// the outermost label is ignored and the secondary label is very long and contribute to the
|
|
// union extension:
|
|
// -|---|---|---|
|
|
// 1,000,000,000
|
|
// Therefore we calculate them one by one.
|
|
// Also considered axis may be blank or no labels.
|
|
var sharedRecord = axisBuilderSharedCtx.ensureRecord(axis.model);
|
|
var labelInfoList = sharedRecord.labelInfoList;
|
|
if (labelInfoList) {
|
|
for (var idx = 0; idx < labelInfoList.length; idx++) {
|
|
var labelInfo = labelInfoList[idx];
|
|
var proportion = axis.scale.normalize(getLabelInner(labelInfo.label).tickValue);
|
|
proportion = xyIdx === 1 ? 1 - proportion : proportion;
|
|
// xAxis use proportion on x, yAxis use proprotion on y, otherwise not.
|
|
fillMarginOnOneDimension(labelInfo.rect, xyIdx, proportion);
|
|
fillMarginOnOneDimension(labelInfo.rect, 1 - xyIdx, NaN);
|
|
}
|
|
}
|
|
var nameLayout = sharedRecord.nameLayout;
|
|
if (nameLayout) {
|
|
var proportion = isNameLocationCenter(sharedRecord.nameLocation) ? 0.5 : NaN;
|
|
fillMarginOnOneDimension(nameLayout.rect, xyIdx, proportion);
|
|
fillMarginOnOneDimension(nameLayout.rect, 1 - xyIdx, NaN);
|
|
}
|
|
});
|
|
}
|
|
function fillMarginOnOneDimension(itemRect, xyIdx, proportion // NaN mean no use proportion
|
|
) {
|
|
var overflow1 = outerBoundsRect[XY[xyIdx]] - itemRect[XY[xyIdx]];
|
|
var overflow2 = itemRect[WH[xyIdx]] + itemRect[XY[xyIdx]] - (outerBoundsRect[WH[xyIdx]] + outerBoundsRect[XY[xyIdx]]);
|
|
overflow1 = applyProportion(overflow1, 1 - proportion);
|
|
overflow2 = applyProportion(overflow2, proportion);
|
|
var minIdx = XY_TO_MARGIN_IDX[xyIdx][0];
|
|
var maxIdx = XY_TO_MARGIN_IDX[xyIdx][1];
|
|
margin[minIdx] = mathMax(margin[minIdx], overflow1);
|
|
margin[maxIdx] = mathMax(margin[maxIdx], overflow2);
|
|
}
|
|
function applyProportion(overflow, proportion) {
|
|
// proportion is not likely to near zero. If so, give up shrink
|
|
if (overflow > 0 && !eqNaN(proportion) && proportion > 1e-4) {
|
|
overflow /= proportion;
|
|
}
|
|
return overflow;
|
|
}
|
|
}
|
|
function createAxisBiulders(gridRect, cartesians, axesMap, optionContainLabel, api) {
|
|
var axisBuilderSharedCtx = new AxisBuilderSharedContext(resolveAxisNameOverlapForGrid);
|
|
each(axesMap, function (axisList) {
|
|
return each(axisList, function (axis) {
|
|
if (shouldAxisShow(axis.model)) {
|
|
// See `AxisBaseOptionCommon['nameMoveOverlap']`.
|
|
var defaultNameMoveOverlap = !optionContainLabel;
|
|
axis.axisBuilder = createCartesianAxisViewCommonPartBuilder(gridRect, cartesians, axis.model, api, axisBuilderSharedCtx, defaultNameMoveOverlap);
|
|
}
|
|
});
|
|
});
|
|
return axisBuilderSharedCtx;
|
|
}
|
|
/**
|
|
* Promote the axis-elements-building from "view render" stage to "coordinate system resize" stage.
|
|
* This is aimed to resovle overlap across multiple axes, since currently it's hard to reconcile
|
|
* multiple axes in "view render" stage.
|
|
*
|
|
* [CAUTION] But this promotion assumes that the subsequent "visual mapping" stage does not affect
|
|
* this axis-elements-building; otherwise we have to refactor it again.
|
|
*/
|
|
function createOrUpdateAxesView(gridRect, axesMap, kind, outerBoundsContain, noPxChange, layoutRef) {
|
|
var isDetermine = kind === AxisTickLabelComputingKind.determine;
|
|
each(axesMap, function (axisList) {
|
|
return each(axisList, function (axis) {
|
|
if (shouldAxisShow(axis.model)) {
|
|
updateCartesianAxisViewCommonPartBuilder(axis.axisBuilder, gridRect, axis.model);
|
|
axis.axisBuilder.build(isDetermine ? {
|
|
axisTickLabelDetermine: true
|
|
} : {
|
|
axisTickLabelEstimate: true
|
|
}, {
|
|
noPxChange: noPxChange
|
|
});
|
|
}
|
|
});
|
|
});
|
|
var nameMarginLevelMap = {
|
|
x: 0,
|
|
y: 0
|
|
};
|
|
calcNameMarginLevel(0);
|
|
calcNameMarginLevel(1);
|
|
function calcNameMarginLevel(xyIdx) {
|
|
nameMarginLevelMap[XY[1 - xyIdx]] = gridRect[WH[xyIdx]] <= layoutRef.refContainer[WH[xyIdx]] * 0.5 ? 0 : 1 - xyIdx === 1 ? 2 : 1;
|
|
}
|
|
each(axesMap, function (axisList, xy) {
|
|
return each(axisList, function (axis) {
|
|
if (shouldAxisShow(axis.model)) {
|
|
if (outerBoundsContain === 'all' || isDetermine) {
|
|
// To resolve overlap, `axisName` layout depends on `axisTickLabel` layout result
|
|
// (all of the axes of the same `grid`; consider multiple x or y axes).
|
|
axis.axisBuilder.build({
|
|
axisName: true
|
|
}, {
|
|
nameMarginLevel: nameMarginLevelMap[xy]
|
|
});
|
|
}
|
|
if (isDetermine) {
|
|
axis.axisBuilder.build({
|
|
axisLine: true
|
|
});
|
|
}
|
|
}
|
|
});
|
|
});
|
|
}
|
|
function prepareOuterBounds(gridModel, rawRridRect, layoutRef) {
|
|
var outerBoundsRect;
|
|
var optionOuterBoundsMode = gridModel.get('outerBoundsMode', true);
|
|
if (optionOuterBoundsMode === 'same') {
|
|
outerBoundsRect = rawRridRect.clone();
|
|
} else if (optionOuterBoundsMode == null || optionOuterBoundsMode === 'auto') {
|
|
outerBoundsRect = getLayoutRect(gridModel.get('outerBounds', true) || OUTER_BOUNDS_DEFAULT, layoutRef.refContainer);
|
|
} else if (optionOuterBoundsMode !== 'none') {
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
error("Invalid grid[" + gridModel.componentIndex + "].outerBoundsMode.");
|
|
}
|
|
}
|
|
var optionOuterBoundsContain = gridModel.get('outerBoundsContain', true);
|
|
var parsedOuterBoundsContain;
|
|
if (optionOuterBoundsContain == null || optionOuterBoundsContain === 'auto') {
|
|
parsedOuterBoundsContain = 'all';
|
|
} else if (indexOf(['all', 'axisLabel'], optionOuterBoundsContain) < 0) {
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
error("Invalid grid[" + gridModel.componentIndex + "].outerBoundsContain.");
|
|
}
|
|
parsedOuterBoundsContain = 'all';
|
|
} else {
|
|
parsedOuterBoundsContain = optionOuterBoundsContain;
|
|
}
|
|
var outerBoundsClamp = [parsePositionSizeOption(retrieve2(gridModel.get('outerBoundsClampWidth', true), OUTER_BOUNDS_CLAMP_DEFAULT[0]), rawRridRect.width), parsePositionSizeOption(retrieve2(gridModel.get('outerBoundsClampHeight', true), OUTER_BOUNDS_CLAMP_DEFAULT[1]), rawRridRect.height)];
|
|
return {
|
|
outerBoundsRect: outerBoundsRect,
|
|
parsedOuterBoundsContain: parsedOuterBoundsContain,
|
|
outerBoundsClamp: outerBoundsClamp
|
|
};
|
|
}
|
|
var resolveAxisNameOverlapForGrid = function (cfg, ctx, axisModel, nameLayoutInfo, nameMoveDirVec, thisRecord) {
|
|
var perpendicularDim = axisModel.axis.dim === 'x' ? 'y' : 'x';
|
|
resolveAxisNameOverlapDefault(cfg, ctx, axisModel, nameLayoutInfo, nameMoveDirVec, thisRecord);
|
|
// If nameLocation 'center', and there are multiple axes parallel to this axis, do not adjust by
|
|
// other axes, because the axis name should be close to its axis line as much as possible even
|
|
// if overlapping; otherwise it might cause misleading.
|
|
// If nameLocation 'center', do not adjust by perpendicular axes, since they are not likely to overlap.
|
|
// If nameLocation 'start'/'end', move name within the same direction to escape overlap with the
|
|
// perpendicular axes.
|
|
if (!isNameLocationCenter(cfg.nameLocation)) {
|
|
each(ctx.recordMap[perpendicularDim], function (perpenRecord) {
|
|
// perpendicular axis may be no name.
|
|
if (perpenRecord && perpenRecord.labelInfoList && perpenRecord.dirVec) {
|
|
moveIfOverlapByLinearLabels(perpenRecord.labelInfoList, perpenRecord.dirVec, nameLayoutInfo, nameMoveDirVec);
|
|
}
|
|
});
|
|
}
|
|
};
|
|
export default Grid; |