/* * 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 { each, defaults, keys } from 'zrender/lib/core/util.js'; import { parsePercent } from '../util/number.js'; import { isDimensionStacked } from '../data/helper/dataStackHelper.js'; import createRenderPlanner from '../chart/helper/createRenderPlanner.js'; import { createFloat32Array } from '../util/vendor.js'; var STACK_PREFIX = '__ec_stack_'; function getSeriesStackId(seriesModel) { return seriesModel.get('stack') || STACK_PREFIX + seriesModel.seriesIndex; } function getAxisKey(axis) { return axis.dim + axis.index; } /** * @return {Object} {width, offset, offsetCenter} If axis.type is not 'category', return undefined. */ export function getLayoutOnAxis(opt) { 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(defaults({ bandWidth: bandWidth, axisKey: axisKey, stackId: STACK_PREFIX + i }, opt)); } var widthAndOffsets = doCalBarWidthAndOffset(params); 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; } export function prepareLayoutBarSeries(seriesType, ecModel) { var seriesModels = []; ecModel.eachSeriesByType(seriesType, function (seriesModel) { // Check series coordinate, do layout for cartesian2d only if (isOnCartesian(seriesModel)) { seriesModels.push(seriesModel); } }); return seriesModels; } /** * Map from (baseAxis.dim + '_' + baseAxis.index) to min gap of two adjacent * values. * This works for time axes, value axes, and log axes. * For a single time axis, return value is in the form like * {'x_0': [1000000]}. * The value of 1000000 is in milliseconds. */ function getValueAxesMinGaps(barSeries) { /** * Map from axis.index to values. * For a single time axis, axisValues is in the form like * {'x_0': [1495555200000, 1495641600000, 1495728000000]}. * Items in axisValues[x], e.g. 1495555200000, are time values of all * series. */ var axisValues = {}; each(barSeries, function (seriesModel) { var cartesian = seriesModel.coordinateSystem; var baseAxis = cartesian.getBaseAxis(); if (baseAxis.type !== 'time' && baseAxis.type !== 'value') { return; } var data = seriesModel.getData(); var key = baseAxis.dim + '_' + baseAxis.index; var dimIdx = data.getDimensionIndex(data.mapDimension(baseAxis.dim)); var store = data.getStore(); for (var i = 0, cnt = store.count(); i < cnt; ++i) { var value = store.get(dimIdx, i); if (!axisValues[key]) { // No previous data for the axis axisValues[key] = [value]; } else { // No value in previous series axisValues[key].push(value); } // Ignore duplicated time values in the same axis } }); var axisMinGaps = {}; for (var key in axisValues) { if (axisValues.hasOwnProperty(key)) { var valuesInAxis = axisValues[key]; if (valuesInAxis) { // Sort axis values into ascending order to calculate gaps valuesInAxis.sort(function (a, b) { return a - b; }); var min = null; for (var j = 1; j < valuesInAxis.length; ++j) { var delta = valuesInAxis[j] - valuesInAxis[j - 1]; if (delta > 0) { // Ignore 0 delta because they are of the same axis value min = min === null ? delta : Math.min(min, delta); } } // Set to null if only have one data axisMinGaps[key] = min; } } } return axisMinGaps; } export function makeColumnLayout(barSeries) { var axisMinGaps = getValueAxesMinGaps(barSeries); var seriesInfoList = []; each(barSeries, function (seriesModel) { var cartesian = seriesModel.coordinateSystem; var baseAxis = cartesian.getBaseAxis(); var axisExtent = baseAxis.getExtent(); var bandWidth; if (baseAxis.type === 'category') { bandWidth = baseAxis.getBandWidth(); } else if (baseAxis.type === 'value' || baseAxis.type === 'time') { var key = baseAxis.dim + '_' + baseAxis.index; var minGap = axisMinGaps[key]; var extentSpan = Math.abs(axisExtent[1] - axisExtent[0]); var scale = baseAxis.scale.getExtent(); var scaleSpan = Math.abs(scale[1] - scale[0]); bandWidth = minGap ? extentSpan / scaleSpan * minGap : extentSpan; // When there is only one data value } else { var data = seriesModel.getData(); bandWidth = Math.abs(axisExtent[1] - axisExtent[0]) / data.count(); } var barWidth = parsePercent(seriesModel.get('barWidth'), bandWidth); var barMaxWidth = parsePercent(seriesModel.get('barMaxWidth'), bandWidth); var barMinWidth = parsePercent( // barMinWidth by default is 0.5 / 1 in cartesian. Because in value axis, // the auto-calculated bar width might be less than 0.5 / 1. seriesModel.get('barMinWidth') || (isInLargeMode(seriesModel) ? 0.5 : 1), bandWidth); var barGap = seriesModel.get('barGap'); var barCategoryGap = seriesModel.get('barCategoryGap'); seriesInfoList.push({ bandWidth: bandWidth, barWidth: barWidth, barMaxWidth: barMaxWidth, barMinWidth: barMinWidth, barGap: barGap, barCategoryGap: barCategoryGap, axisKey: getAxisKey(baseAxis), stackId: getSeriesStackId(seriesModel) }); }); return doCalBarWidthAndOffset(seriesInfoList); } function doCalBarWidthAndOffset(seriesInfoList) { // Columns info on each category axis. Key is cartesian name var columnsMap = {}; each(seriesInfoList, function (seriesInfo, idx) { var axisKey = seriesInfo.axisKey; var bandWidth = seriesInfo.bandWidth; var columnsOnAxis = columnsMap[axisKey] || { bandWidth: bandWidth, remainedWidth: bandWidth, autoWidthCount: 0, categoryGap: null, gap: '20%', 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. 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 barMinWidth = seriesInfo.barMinWidth; barMinWidth && (stacks[stackId].minWidth = barMinWidth); var barGap = seriesInfo.barGap; barGap != null && (columnsOnAxis.gap = barGap); var barCategoryGap = seriesInfo.barCategoryGap; barCategoryGap != null && (columnsOnAxis.categoryGap = barCategoryGap); }); var result = {}; each(columnsMap, function (columnsOnAxis, coordSysName) { result[coordSysName] = {}; var stacks = columnsOnAxis.stacks; var bandWidth = columnsOnAxis.bandWidth; var categoryGapPercent = columnsOnAxis.categoryGap; if (categoryGapPercent == null) { var columnCount = keys(stacks).length; // More columns in one group // the spaces between group is smaller. Or the column will be too thin. categoryGapPercent = Math.max(35 - columnCount * 4, 15) + '%'; } var categoryGap = parsePercent(categoryGapPercent, 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 each(stacks, function (column) { var maxWidth = column.maxWidth; var minWidth = column.minWidth; if (!column.width) { var finalWidth = autoWidth; if (maxWidth && maxWidth < finalWidth) { finalWidth = Math.min(maxWidth, remainedWidth); } // `minWidth` has higher priority. `minWidth` decide that whether the // bar is able to be visible. So `minWidth` should not be restricted // by `maxWidth` or `remainedWidth` (which is from `bandWidth`). In // the extreme cases for `value` axis, bars are allowed to overlap // with each other if `minWidth` specified. if (minWidth && minWidth > finalWidth) { finalWidth = minWidth; } if (finalWidth !== autoWidth) { column.width = finalWidth; remainedWidth -= finalWidth + barGapPercent * finalWidth; autoWidthCount--; } } else { // `barMinWidth/barMaxWidth` has higher priority than `barWidth`, as // CSS does. Because barWidth can be a percent value, where // `barMaxWidth` can be used to restrict the final width. var finalWidth = column.width; if (maxWidth) { finalWidth = Math.min(finalWidth, maxWidth); } // `minWidth` has higher priority, as described above if (minWidth) { finalWidth = Math.max(finalWidth, minWidth); } column.width = finalWidth; remainedWidth -= finalWidth + barGapPercent * finalWidth; autoWidthCount--; } }); // Recalculate width again autoWidth = (remainedWidth - categoryGap) / (autoWidthCount + (autoWidthCount - 1) * barGapPercent); autoWidth = Math.max(autoWidth, 0); var widthSum = 0; var lastColumn; 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; each(stacks, function (column, stackId) { result[coordSysName][stackId] = result[coordSysName][stackId] || { bandWidth: bandWidth, offset: offset, width: column.width }; offset += column.width * (1 + barGapPercent); }); }); return result; } function retrieveColumnLayout(barWidthAndOffset, axis, seriesModel) { if (barWidthAndOffset && axis) { var result = barWidthAndOffset[getAxisKey(axis)]; if (result != null && seriesModel != null) { return result[getSeriesStackId(seriesModel)]; } return result; } } export { retrieveColumnLayout }; export function layout(seriesType, ecModel) { var seriesModels = prepareLayoutBarSeries(seriesType, ecModel); var barWidthAndOffset = makeColumnLayout(seriesModels); each(seriesModels, function (seriesModel) { 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; data.setLayout({ bandWidth: columnLayoutInfo.bandWidth, offset: columnOffset, size: columnWidth }); }); } // TODO: Do not support stack in large mode yet. export function createProgressiveLayout(seriesType) { return { seriesType: seriesType, plan: createRenderPlanner(), reset: function (seriesModel) { if (!isOnCartesian(seriesModel)) { return; } var data = seriesModel.getData(); var cartesian = seriesModel.coordinateSystem; var baseAxis = cartesian.getBaseAxis(); var valueAxis = cartesian.getOtherAxis(baseAxis); var valueDimIdx = data.getDimensionIndex(data.mapDimension(valueAxis.dim)); var baseDimIdx = data.getDimensionIndex(data.mapDimension(baseAxis.dim)); var drawBackground = seriesModel.get('showBackground', true); var valueDim = data.mapDimension(valueAxis.dim); var stackResultDim = data.getCalculationInfo('stackResultDimension'); var stacked = isDimensionStacked(data, valueDim) && !!data.getCalculationInfo('stackedOnSeries'); var isValueAxisH = valueAxis.isHorizontal(); var valueAxisStart = getValueAxisStart(baseAxis, valueAxis); var isLarge = isInLargeMode(seriesModel); var barMinHeight = seriesModel.get('barMinHeight') || 0; var stackedDimIdx = stackResultDim && data.getDimensionIndex(stackResultDim); // Layout info. var columnWidth = data.getLayout('size'); var columnOffset = data.getLayout('offset'); return { progress: function (params, data) { var count = params.count; var largePoints = isLarge && createFloat32Array(count * 3); var largeBackgroundPoints = isLarge && drawBackground && createFloat32Array(count * 3); var largeDataIndices = isLarge && createFloat32Array(count); var coordLayout = cartesian.master.getRect(); var bgSize = isValueAxisH ? coordLayout.width : coordLayout.height; var dataIndex; var store = data.getStore(); var idxOffset = 0; while ((dataIndex = params.next()) != null) { var value = store.get(stacked ? stackedDimIdx : valueDimIdx, dataIndex); var baseValue = store.get(baseDimIdx, dataIndex); var baseCoord = valueAxisStart; var stackStartValue = void 0; // Because of the barMinHeight, we can not use the value in // stackResultDimension directly. if (stacked) { stackStartValue = +value - store.get(valueDimIdx, dataIndex); } var x = void 0; var y = void 0; var width = void 0; var height = void 0; if (isValueAxisH) { var coord = cartesian.dataToPoint([value, baseValue]); if (stacked) { var startCoord = cartesian.dataToPoint([stackStartValue, baseValue]); baseCoord = startCoord[0]; } x = baseCoord; y = coord[1] + columnOffset; width = coord[0] - baseCoord; height = columnWidth; if (Math.abs(width) < barMinHeight) { width = (width < 0 ? -1 : 1) * barMinHeight; } } else { var coord = cartesian.dataToPoint([baseValue, value]); if (stacked) { var startCoord = cartesian.dataToPoint([baseValue, stackStartValue]); baseCoord = startCoord[1]; } x = coord[0] + columnOffset; y = baseCoord; width = columnWidth; height = coord[1] - baseCoord; if (Math.abs(height) < barMinHeight) { // Include zero to has a positive bar height = (height <= 0 ? -1 : 1) * barMinHeight; } } if (!isLarge) { data.setItemLayout(dataIndex, { x: x, y: y, width: width, height: height }); } else { largePoints[idxOffset] = x; largePoints[idxOffset + 1] = y; largePoints[idxOffset + 2] = isValueAxisH ? width : height; if (largeBackgroundPoints) { largeBackgroundPoints[idxOffset] = isValueAxisH ? coordLayout.x : x; largeBackgroundPoints[idxOffset + 1] = isValueAxisH ? y : coordLayout.y; largeBackgroundPoints[idxOffset + 2] = bgSize; } largeDataIndices[dataIndex] = dataIndex; } idxOffset += 3; } if (isLarge) { data.setLayout({ largePoints: largePoints, largeDataIndices: largeDataIndices, largeBackgroundPoints: largeBackgroundPoints, valueAxisHorizontal: isValueAxisH }); } } }; } }; } function isOnCartesian(seriesModel) { return seriesModel.coordinateSystem && seriesModel.coordinateSystem.type === 'cartesian2d'; } function isInLargeMode(seriesModel) { return seriesModel.pipelineContext && seriesModel.pipelineContext.large; } // See cases in `test/bar-start.html` and `#7412`, `#8747`. function getValueAxisStart(baseAxis, valueAxis) { var startValue = valueAxis.model.get('startValue'); if (!startValue) { startValue = 0; } return valueAxis.toGlobalCoord(valueAxis.dataToCoord(valueAxis.type === 'log' ? startValue > 0 ? startValue : 1 : startValue)); }