610 lines
17 KiB
JavaScript
610 lines
17 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.
|
|
*/
|
|
|
|
var _config = require("../../config");
|
|
|
|
var __DEV__ = _config.__DEV__;
|
|
|
|
var _util = require("zrender/lib/core/util");
|
|
|
|
var isObject = _util.isObject;
|
|
var each = _util.each;
|
|
var map = _util.map;
|
|
var indexOf = _util.indexOf;
|
|
var retrieve = _util.retrieve;
|
|
|
|
var _layout = require("../../util/layout");
|
|
|
|
var getLayoutRect = _layout.getLayoutRect;
|
|
|
|
var _axisHelper = require("../../coord/axisHelper");
|
|
|
|
var createScaleByModel = _axisHelper.createScaleByModel;
|
|
var ifAxisCrossZero = _axisHelper.ifAxisCrossZero;
|
|
var niceScaleExtent = _axisHelper.niceScaleExtent;
|
|
var estimateLabelUnionRect = _axisHelper.estimateLabelUnionRect;
|
|
|
|
var Cartesian2D = require("./Cartesian2D");
|
|
|
|
var Axis2D = require("./Axis2D");
|
|
|
|
var CoordinateSystem = require("../../CoordinateSystem");
|
|
|
|
var _dataStackHelper = require("../../data/helper/dataStackHelper");
|
|
|
|
var getStackedDimension = _dataStackHelper.getStackedDimension;
|
|
|
|
require("./GridModel");
|
|
|
|
/*
|
|
* 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
|
|
*/
|
|
// Depends on GridModel, AxisModel, which performs preprocess.
|
|
|
|
/**
|
|
* Check if the axis is used in the specified grid
|
|
* @inner
|
|
*/
|
|
function isAxisUsedInTheGrid(axisModel, gridModel, ecModel) {
|
|
return axisModel.getCoordSysModel() === gridModel;
|
|
}
|
|
|
|
function Grid(gridModel, ecModel, api) {
|
|
/**
|
|
* @type {Object.<string, module:echarts/coord/cartesian/Cartesian2D>}
|
|
* @private
|
|
*/
|
|
this._coordsMap = {};
|
|
/**
|
|
* @type {Array.<module:echarts/coord/cartesian/Cartesian>}
|
|
* @private
|
|
*/
|
|
|
|
this._coordsList = [];
|
|
/**
|
|
* @type {Object.<string, Array.<module:echarts/coord/cartesian/Axis2D>>}
|
|
* @private
|
|
*/
|
|
|
|
this._axesMap = {};
|
|
/**
|
|
* @type {Array.<module:echarts/coord/cartesian/Axis2D>}
|
|
* @private
|
|
*/
|
|
|
|
this._axesList = [];
|
|
|
|
this._initCartesian(gridModel, ecModel, api);
|
|
|
|
this.model = gridModel;
|
|
}
|
|
|
|
var gridProto = Grid.prototype;
|
|
gridProto.type = 'grid';
|
|
gridProto.axisPointerEnabled = true;
|
|
|
|
gridProto.getRect = function () {
|
|
return this._rect;
|
|
};
|
|
|
|
gridProto.update = function (ecModel, api) {
|
|
var axesMap = this._axesMap;
|
|
|
|
this._updateScale(ecModel, this.model);
|
|
|
|
each(axesMap.x, function (xAxis) {
|
|
niceScaleExtent(xAxis.scale, xAxis.model);
|
|
});
|
|
each(axesMap.y, function (yAxis) {
|
|
niceScaleExtent(yAxis.scale, yAxis.model);
|
|
}); // 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);
|
|
};
|
|
|
|
function fixAxisOnZero(axesMap, otherAxisDim, axis, 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);
|
|
}
|
|
/**
|
|
* Resize the grid
|
|
* @param {module:echarts/coord/cartesian/GridModel} gridModel
|
|
* @param {module:echarts/ExtensionAPI} api
|
|
*/
|
|
|
|
|
|
gridProto.resize = function (gridModel, api, ignoreContainLabel) {
|
|
var gridRect = getLayoutRect(gridModel.getBoxLayoutParams(), {
|
|
width: api.getWidth(),
|
|
height: api.getHeight()
|
|
});
|
|
this._rect = gridRect;
|
|
var axesList = this._axesList;
|
|
adjustAxes(); // Minus label size
|
|
|
|
if (!ignoreContainLabel && gridModel.get('containLabel')) {
|
|
each(axesList, function (axis) {
|
|
if (!axis.model.get('axisLabel.inside')) {
|
|
var labelUnionRect = estimateLabelUnionRect(axis);
|
|
|
|
if (labelUnionRect) {
|
|
var dim = axis.isHorizontal() ? 'height' : 'width';
|
|
var 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();
|
|
}
|
|
|
|
function adjustAxes() {
|
|
each(axesList, function (axis) {
|
|
var isHorizontal = axis.isHorizontal();
|
|
var extent = isHorizontal ? [0, gridRect.width] : [0, gridRect.height];
|
|
var idx = axis.inverse ? 1 : 0;
|
|
axis.setExtent(extent[idx], extent[1 - idx]);
|
|
updateAxisTransform(axis, isHorizontal ? gridRect.x : gridRect.y);
|
|
});
|
|
}
|
|
};
|
|
/**
|
|
* @param {string} axisType
|
|
* @param {number} [axisIndex]
|
|
*/
|
|
|
|
|
|
gridProto.getAxis = function (axisType, axisIndex) {
|
|
var axesMapOnDim = this._axesMap[axisType];
|
|
|
|
if (axesMapOnDim != null) {
|
|
if (axisIndex == null) {
|
|
// Find first axis
|
|
for (var name in axesMapOnDim) {
|
|
if (axesMapOnDim.hasOwnProperty(name)) {
|
|
return axesMapOnDim[name];
|
|
}
|
|
}
|
|
}
|
|
|
|
return axesMapOnDim[axisIndex];
|
|
}
|
|
};
|
|
/**
|
|
* @return {Array.<module:echarts/coord/Axis>}
|
|
*/
|
|
|
|
|
|
gridProto.getAxes = function () {
|
|
return this._axesList.slice();
|
|
};
|
|
/**
|
|
* Usage:
|
|
* grid.getCartesian(xAxisIndex, yAxisIndex);
|
|
* grid.getCartesian(xAxisIndex);
|
|
* grid.getCartesian(null, yAxisIndex);
|
|
* grid.getCartesian({xAxisIndex: ..., yAxisIndex: ...});
|
|
*
|
|
* @param {number|Object} [xAxisIndex]
|
|
* @param {number} [yAxisIndex]
|
|
*/
|
|
|
|
|
|
gridProto.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;
|
|
} // When only xAxisIndex or yAxisIndex given, find its first cartesian.
|
|
|
|
|
|
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];
|
|
}
|
|
}
|
|
};
|
|
|
|
gridProto.getCartesians = function () {
|
|
return this._coordsList.slice();
|
|
};
|
|
/**
|
|
* @implements
|
|
* see {module:echarts/CoodinateSystem}
|
|
*/
|
|
|
|
|
|
gridProto.convertToPixel = function (ecModel, finder, value) {
|
|
var target = this._findConvertTarget(ecModel, finder);
|
|
|
|
return target.cartesian ? target.cartesian.dataToPoint(value) : target.axis ? target.axis.toGlobalCoord(target.axis.dataToCoord(value)) : null;
|
|
};
|
|
/**
|
|
* @implements
|
|
* see {module:echarts/CoodinateSystem}
|
|
*/
|
|
|
|
|
|
gridProto.convertFromPixel = function (ecModel, finder, value) {
|
|
var target = this._findConvertTarget(ecModel, finder);
|
|
|
|
return target.cartesian ? target.cartesian.pointToData(value) : target.axis ? target.axis.coordToData(target.axis.toLocalCoord(value)) : null;
|
|
};
|
|
/**
|
|
* @inner
|
|
*/
|
|
|
|
|
|
gridProto._findConvertTarget = function (ecModel, finder) {
|
|
var seriesModel = finder.seriesModel;
|
|
var xAxisModel = finder.xAxisModel || seriesModel && seriesModel.getReferringComponents('xAxis')[0];
|
|
var yAxisModel = finder.yAxisModel || seriesModel && seriesModel.getReferringComponents('yAxis')[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
|
|
* see {module:echarts/CoodinateSystem}
|
|
*/
|
|
|
|
|
|
gridProto.containPoint = function (point) {
|
|
var coord = this._coordsList[0];
|
|
|
|
if (coord) {
|
|
return coord.containPoint(point);
|
|
}
|
|
};
|
|
/**
|
|
* Initialize cartesian coordinate systems
|
|
* @private
|
|
*/
|
|
|
|
|
|
gridProto._initCartesian = function (gridModel, ecModel, api) {
|
|
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.grid = this;
|
|
cartesian.model = gridModel;
|
|
this._coordsMap[key] = cartesian;
|
|
|
|
this._coordsList.push(cartesian);
|
|
|
|
cartesian.addAxis(xAxis);
|
|
cartesian.addAxis(yAxis);
|
|
}, this);
|
|
}, this);
|
|
|
|
function createAxisCreator(axisType) {
|
|
return function (axisModel, idx) {
|
|
if (!isAxisUsedInTheGrid(axisModel, gridModel, ecModel)) {
|
|
return;
|
|
}
|
|
|
|
var axisPosition = axisModel.get('position');
|
|
|
|
if (axisType === '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(axisType, 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 = this; // Index of axis, can be used as key
|
|
|
|
axis.index = idx;
|
|
|
|
this._axesList.push(axis);
|
|
|
|
axesMap[axisType][idx] = axis;
|
|
axesCount[axisType]++;
|
|
};
|
|
}
|
|
};
|
|
/**
|
|
* Update cartesian properties from series
|
|
* @param {module:echarts/model/Option} option
|
|
* @private
|
|
*/
|
|
|
|
|
|
gridProto._updateScale = function (ecModel, gridModel) {
|
|
// Reset scale
|
|
each(this._axesList, function (axis) {
|
|
axis.scale.setExtent(Infinity, -Infinity);
|
|
});
|
|
ecModel.eachSeries(function (seriesModel) {
|
|
if (isCartesian2D(seriesModel)) {
|
|
var axesModels = findAxesModels(seriesModel, ecModel);
|
|
var xAxisModel = axesModels[0];
|
|
var yAxisModel = axesModels[1];
|
|
|
|
if (!isAxisUsedInTheGrid(xAxisModel, gridModel, ecModel) || !isAxisUsedInTheGrid(yAxisModel, gridModel, ecModel)) {
|
|
return;
|
|
}
|
|
|
|
var cartesian = this.getCartesian(xAxisModel.componentIndex, yAxisModel.componentIndex);
|
|
var data = seriesModel.getData();
|
|
var xAxis = cartesian.getAxis('x');
|
|
var yAxis = cartesian.getAxis('y');
|
|
|
|
if (data.type === 'list') {
|
|
unionExtent(data, xAxis, seriesModel);
|
|
unionExtent(data, yAxis, seriesModel);
|
|
}
|
|
}
|
|
}, this);
|
|
|
|
function unionExtent(data, axis, seriesModel) {
|
|
each(data.mapDimension(axis.dim, true), 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));
|
|
});
|
|
}
|
|
};
|
|
/**
|
|
* @param {string} [dim] 'x' or 'y' or 'auto' or null/undefined
|
|
* @return {Object} {baseAxes: [], otherAxes: []}
|
|
*/
|
|
|
|
|
|
gridProto.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
|
|
};
|
|
};
|
|
/**
|
|
* @inner
|
|
*/
|
|
|
|
|
|
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;
|
|
};
|
|
}
|
|
|
|
var axesTypes = ['xAxis', 'yAxis'];
|
|
/**
|
|
* @inner
|
|
*/
|
|
|
|
function findAxesModels(seriesModel, ecModel) {
|
|
return map(axesTypes, function (axisType) {
|
|
var axisModel = seriesModel.getReferringComponents(axisType)[0];
|
|
return axisModel;
|
|
});
|
|
}
|
|
/**
|
|
* @inner
|
|
*/
|
|
|
|
|
|
function isCartesian2D(seriesModel) {
|
|
return seriesModel.get('coordinateSystem') === 'cartesian2d';
|
|
}
|
|
|
|
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) {
|
|
if (!isCartesian2D(seriesModel)) {
|
|
return;
|
|
}
|
|
|
|
var axesModels = findAxesModels(seriesModel, ecModel);
|
|
var xAxisModel = axesModels[0];
|
|
var yAxisModel = axesModels[1];
|
|
var gridModel = xAxisModel.getCoordSysModel();
|
|
var grid = gridModel.coordinateSystem;
|
|
seriesModel.coordinateSystem = grid.getCartesian(xAxisModel.componentIndex, yAxisModel.componentIndex);
|
|
});
|
|
return grids;
|
|
}; // For deciding which dimensions to use when creating list data
|
|
|
|
|
|
Grid.dimensions = Grid.prototype.dimensions = Cartesian2D.prototype.dimensions;
|
|
CoordinateSystem.register('cartesian2d', Grid);
|
|
var _default = Grid;
|
|
module.exports = _default; |