You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
538 lines
18 KiB
538 lines
18 KiB
|
|
/* |
|
* 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 startValue = void 0; // Because of the barMinHeight, we can not use the value in |
|
// stackResultDimension directly. |
|
|
|
if (stacked) { |
|
startValue = +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([startValue, 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, startValue]); |
|
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) { |
|
return valueAxis.toGlobalCoord(valueAxis.dataToCoord(valueAxis.type === 'log' ? 1 : 0)); |
|
} |