blob: 3479eb90efe38a959f77996656bcdbd10f21fbd8 [file] [log] [blame]
import * as zrUtil from 'zrender/src/core/util';
import { parsePercent } from '../util/number';
var STACK_PREFIX = '__ec_stack_';
function getSeriesStackId(seriesModel) {
return seriesModel.get('stack') || STACK_PREFIX + seriesModel.seriesIndex;
}
function getAxisKey(axis) {
return axis.dim + axis.index;
}
/**
* @param {Object} opt
* @param {module:echarts/coord/Axis} opt.axis Only support category axis currently.
* @param {number} opt.count Positive interger.
* @param {number} [opt.barWidth]
* @param {number} [opt.barMaxWidth]
* @param {number} [opt.barGap]
* @param {number} [opt.barCategoryGap]
* @return {Object} {width, offset, offsetCenter} If axis.type is not 'category', return undefined.
*/
function getLayoutOnAxis(opt, api) {
var params = [];
var baseAxis = opt.axis;
var axisKey = 'axis0';
if (baseAxis.type !== 'category') {
return;
}
var bandWidth = baseAxis.getBandWidth();
for (var i = 0; i < opt.count || 0; i++) {
params.push(zrUtil.defaults({
bandWidth: bandWidth,
axisKey: axisKey,
stackId: STACK_PREFIX + i
}, opt));
}
var widthAndOffsets = doCalBarWidthAndOffset(params, api);
var result = [];
for (var i = 0; i < opt.count; i++) {
var item = widthAndOffsets[axisKey][STACK_PREFIX + i];
item.offsetCenter = item.offset + item.width / 2;
result.push(item);
}
return result;
}
function calBarWidthAndOffset(barSeries, api) {
var seriesInfoList = zrUtil.map(barSeries, function (seriesModel) {
var data = seriesModel.getData();
var cartesian = seriesModel.coordinateSystem;
var baseAxis = cartesian.getBaseAxis();
var axisExtent = baseAxis.getExtent();
var bandWidth = baseAxis.type === 'category' ? baseAxis.getBandWidth() : Math.abs(axisExtent[1] - axisExtent[0]) / data.count();
var barWidth = parsePercent(seriesModel.get('barWidth'), bandWidth);
var barMaxWidth = parsePercent(seriesModel.get('barMaxWidth'), bandWidth);
var barGap = seriesModel.get('barGap');
var barCategoryGap = seriesModel.get('barCategoryGap');
return {
bandWidth: bandWidth,
barWidth: barWidth,
barMaxWidth: barMaxWidth,
barGap: barGap,
barCategoryGap: barCategoryGap,
axisKey: getAxisKey(baseAxis),
stackId: getSeriesStackId(seriesModel)
};
});
return doCalBarWidthAndOffset(seriesInfoList, api);
}
function doCalBarWidthAndOffset(seriesInfoList, api) {
// Columns info on each category axis. Key is cartesian name
var columnsMap = {};
zrUtil.each(seriesInfoList, function (seriesInfo, idx) {
var axisKey = seriesInfo.axisKey;
var bandWidth = seriesInfo.bandWidth;
var columnsOnAxis = columnsMap[axisKey] || {
bandWidth: bandWidth,
remainedWidth: bandWidth,
autoWidthCount: 0,
categoryGap: '20%',
gap: '30%',
stacks: {}
};
var stacks = columnsOnAxis.stacks;
columnsMap[axisKey] = columnsOnAxis;
var stackId = seriesInfo.stackId;
if (!stacks[stackId]) {
columnsOnAxis.autoWidthCount++;
}
stacks[stackId] = stacks[stackId] || {
width: 0,
maxWidth: 0
}; // Caution: In a single coordinate system, these barGrid attributes
// will be shared by series. Consider that they have default values,
// only the attributes set on the last series will work.
// Do not change this fact unless there will be a break change.
// TODO
var barWidth = seriesInfo.barWidth;
if (barWidth && !stacks[stackId].width) {
// See #6312, do not restrict width.
stacks[stackId].width = barWidth;
barWidth = Math.min(columnsOnAxis.remainedWidth, barWidth);
columnsOnAxis.remainedWidth -= barWidth;
}
var barMaxWidth = seriesInfo.barMaxWidth;
barMaxWidth && (stacks[stackId].maxWidth = barMaxWidth);
var barGap = seriesInfo.barGap;
barGap != null && (columnsOnAxis.gap = barGap);
var barCategoryGap = seriesInfo.barCategoryGap;
barCategoryGap != null && (columnsOnAxis.categoryGap = barCategoryGap);
});
var result = {};
zrUtil.each(columnsMap, function (columnsOnAxis, coordSysName) {
result[coordSysName] = {};
var stacks = columnsOnAxis.stacks;
var bandWidth = columnsOnAxis.bandWidth;
var categoryGap = parsePercent(columnsOnAxis.categoryGap, bandWidth);
var barGapPercent = parsePercent(columnsOnAxis.gap, 1);
var remainedWidth = columnsOnAxis.remainedWidth;
var autoWidthCount = columnsOnAxis.autoWidthCount;
var autoWidth = (remainedWidth - categoryGap) / (autoWidthCount + (autoWidthCount - 1) * barGapPercent);
autoWidth = Math.max(autoWidth, 0); // Find if any auto calculated bar exceeded maxBarWidth
zrUtil.each(stacks, function (column, stack) {
var maxWidth = column.maxWidth;
if (maxWidth && maxWidth < autoWidth) {
maxWidth = Math.min(maxWidth, remainedWidth);
if (column.width) {
maxWidth = Math.min(maxWidth, column.width);
}
remainedWidth -= maxWidth;
column.width = maxWidth;
autoWidthCount--;
}
}); // Recalculate width again
autoWidth = (remainedWidth - categoryGap) / (autoWidthCount + (autoWidthCount - 1) * barGapPercent);
autoWidth = Math.max(autoWidth, 0);
var widthSum = 0;
var lastColumn;
zrUtil.each(stacks, function (column, idx) {
if (!column.width) {
column.width = autoWidth;
}
lastColumn = column;
widthSum += column.width * (1 + barGapPercent);
});
if (lastColumn) {
widthSum -= lastColumn.width * barGapPercent;
}
var offset = -widthSum / 2;
zrUtil.each(stacks, function (column, stackId) {
result[coordSysName][stackId] = result[coordSysName][stackId] || {
offset: offset,
width: column.width
};
offset += column.width * (1 + barGapPercent);
});
});
return result;
}
/**
* @param {string} seriesType
* @param {module:echarts/model/Global} ecModel
* @param {module:echarts/ExtensionAPI} api
*/
function barLayoutGrid(seriesType, ecModel, api) {
var barWidthAndOffset = calBarWidthAndOffset(zrUtil.filter(ecModel.getSeriesByType(seriesType), function (seriesModel) {
return !ecModel.isSeriesFiltered(seriesModel) && seriesModel.coordinateSystem && seriesModel.coordinateSystem.type === 'cartesian2d';
}));
var lastStackCoords = {};
var lastStackCoordsOrigin = {};
ecModel.eachSeriesByType(seriesType, function (seriesModel) {
// Check series coordinate, do layout for cartesian2d only
if (seriesModel.coordinateSystem.type !== 'cartesian2d') {
return;
}
var data = seriesModel.getData();
var cartesian = seriesModel.coordinateSystem;
var baseAxis = cartesian.getBaseAxis();
var stackId = getSeriesStackId(seriesModel);
var columnLayoutInfo = barWidthAndOffset[getAxisKey(baseAxis)][stackId];
var columnOffset = columnLayoutInfo.offset;
var columnWidth = columnLayoutInfo.width;
var valueAxis = cartesian.getOtherAxis(baseAxis);
var barMinHeight = seriesModel.get('barMinHeight') || 0;
var valueAxisStart = baseAxis.onZero ? valueAxis.toGlobalCoord(valueAxis.dataToCoord(0)) : valueAxis.getGlobalExtent()[0];
var coordDims = [seriesModel.coordDimToDataDim('x')[0], seriesModel.coordDimToDataDim('y')[0]];
var coords = data.mapArray(coordDims, function (x, y) {
return cartesian.dataToPoint([x, y]);
}, true);
lastStackCoords[stackId] = lastStackCoords[stackId] || [];
lastStackCoordsOrigin[stackId] = lastStackCoordsOrigin[stackId] || []; // Fix #4243
data.setLayout({
offset: columnOffset,
size: columnWidth
});
data.each(seriesModel.coordDimToDataDim(valueAxis.dim)[0], function (value, idx) {
if (isNaN(value)) {
return;
}
if (!lastStackCoords[stackId][idx]) {
lastStackCoords[stackId][idx] = {
p: valueAxisStart,
// Positive stack
n: valueAxisStart // Negative stack
};
lastStackCoordsOrigin[stackId][idx] = {
p: valueAxisStart,
// Positive stack
n: valueAxisStart // Negative stack
};
}
var sign = value >= 0 ? 'p' : 'n';
var coord = coords[idx];
var lastCoord = lastStackCoords[stackId][idx][sign];
var lastCoordOrigin = lastStackCoordsOrigin[stackId][idx][sign];
var x;
var y;
var width;
var height;
if (valueAxis.isHorizontal()) {
x = lastCoord;
y = coord[1] + columnOffset;
width = coord[0] - lastCoordOrigin;
height = columnWidth;
lastStackCoordsOrigin[stackId][idx][sign] += width;
if (Math.abs(width) < barMinHeight) {
width = (width < 0 ? -1 : 1) * barMinHeight;
}
lastStackCoords[stackId][idx][sign] += width;
} else {
x = coord[0] + columnOffset;
y = lastCoord;
width = columnWidth;
height = coord[1] - lastCoordOrigin;
lastStackCoordsOrigin[stackId][idx][sign] += height;
if (Math.abs(height) < barMinHeight) {
// Include zero to has a positive bar
height = (height <= 0 ? -1 : 1) * barMinHeight;
}
lastStackCoords[stackId][idx][sign] += height;
}
data.setItemLayout(idx, {
x: x,
y: y,
width: width,
height: height
});
}, true);
}, this);
}
barLayoutGrid.getLayoutOnAxis = getLayoutOnAxis;
export default barLayoutGrid;