| /* | 
 | * 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/src/core/util'; | 
 | import { parsePercent } from '../util/number'; | 
 | import { isDimensionStacked } from '../data/helper/dataStackHelper'; | 
 | import createRenderPlanner from '../chart/helper/createRenderPlanner'; | 
 | import BarSeriesModel from '../chart/bar/BarSeries'; | 
 | import Axis2D from '../coord/cartesian/Axis2D'; | 
 | import GlobalModel from '../model/Global'; | 
 | import type Cartesian2D from '../coord/cartesian/Cartesian2D'; | 
 | import { StageHandler, Dictionary } from '../util/types'; | 
 | import { createFloat32Array } from '../util/vendor'; | 
 |  | 
 | const STACK_PREFIX = '__ec_stack_'; | 
 |  | 
 | function getSeriesStackId(seriesModel: BarSeriesModel): string { | 
 |     return seriesModel.get('stack') || STACK_PREFIX + seriesModel.seriesIndex; | 
 | } | 
 |  | 
 | function getAxisKey(axis: Axis2D): string { | 
 |     return axis.dim + axis.index; | 
 | } | 
 |  | 
 | interface LayoutSeriesInfo { | 
 |     bandWidth: number | 
 |     barWidth: number | 
 |     barMaxWidth: number | 
 |     barMinWidth: number | 
 |     barGap: number | string | 
 |     barCategoryGap: number | string | 
 |     axisKey: string | 
 |     stackId: string | 
 | } | 
 |  | 
 | interface StackInfo { | 
 |     width: number | 
 |     maxWidth: number | 
 |     minWidth?: number | 
 | } | 
 |  | 
 | /** | 
 |  * { | 
 |  *  [coordSysId]: { | 
 |  *      [stackId]: {bandWidth, offset, width} | 
 |  *  } | 
 |  * } | 
 |  */ | 
 | type BarWidthAndOffset = Dictionary<Dictionary<{ | 
 |     bandWidth: number | 
 |     offset: number | 
 |     offsetCenter: number | 
 |     width: number | 
 | }>>; | 
 |  | 
 | export interface BarGridLayoutOptionForCustomSeries { | 
 |     count: number | 
 |  | 
 |     barWidth?: number | string | 
 |     barMaxWidth?: number | string | 
 |     barMinWidth?: number | string | 
 |     barGap?: number | string | 
 |     barCategoryGap?: number | string | 
 | } | 
 | interface LayoutOption extends BarGridLayoutOptionForCustomSeries { | 
 |     axis: Axis2D | 
 | } | 
 |  | 
 | export type BarGridLayoutResult = BarWidthAndOffset[string][string][]; | 
 | /** | 
 |  * @return {Object} {width, offset, offsetCenter} If axis.type is not 'category', return undefined. | 
 |  */ | 
 | export function getLayoutOnAxis(opt: LayoutOption): BarGridLayoutResult { | 
 |     const params: LayoutSeriesInfo[] = []; | 
 |     const baseAxis = opt.axis; | 
 |     const axisKey = 'axis0'; | 
 |  | 
 |     if (baseAxis.type !== 'category') { | 
 |         return; | 
 |     } | 
 |     const bandWidth = baseAxis.getBandWidth(); | 
 |  | 
 |     for (let i = 0; i < opt.count || 0; i++) { | 
 |         params.push(defaults({ | 
 |             bandWidth: bandWidth, | 
 |             axisKey: axisKey, | 
 |             stackId: STACK_PREFIX + i | 
 |         }, opt) as LayoutSeriesInfo); | 
 |     } | 
 |     const widthAndOffsets = doCalBarWidthAndOffset(params); | 
 |  | 
 |     const result = []; | 
 |     for (let i = 0; i < opt.count; i++) { | 
 |         const item = widthAndOffsets[axisKey][STACK_PREFIX + i]; | 
 |         item.offsetCenter = item.offset + item.width / 2; | 
 |         result.push(item); | 
 |     } | 
 |  | 
 |     return result; | 
 | } | 
 |  | 
 | export function prepareLayoutBarSeries(seriesType: string, ecModel: GlobalModel): BarSeriesModel[] { | 
 |     const seriesModels: BarSeriesModel[] = []; | 
 |     ecModel.eachSeriesByType(seriesType, function (seriesModel: BarSeriesModel) { | 
 |         // 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: BarSeriesModel[]) { | 
 |     /** | 
 |      * 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. | 
 |      */ | 
 |     const axisValues: Dictionary<number[]> = {}; | 
 |     each(barSeries, function (seriesModel) { | 
 |         const cartesian = seriesModel.coordinateSystem as Cartesian2D; | 
 |         const baseAxis = cartesian.getBaseAxis(); | 
 |         if (baseAxis.type !== 'time' && baseAxis.type !== 'value') { | 
 |             return; | 
 |         } | 
 |  | 
 |         const data = seriesModel.getData(); | 
 |         const key = baseAxis.dim + '_' + baseAxis.index; | 
 |         const dimIdx = data.getDimensionIndex(data.mapDimension(baseAxis.dim)); | 
 |         const store = data.getStore(); | 
 |         for (let i = 0, cnt = store.count(); i < cnt; ++i) { | 
 |             const value = store.get(dimIdx, i) as number; | 
 |             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 | 
 |         } | 
 |     }); | 
 |  | 
 |     const axisMinGaps: Dictionary<number> = {}; | 
 |     for (const key in axisValues) { | 
 |         if (axisValues.hasOwnProperty(key)) { | 
 |             const valuesInAxis = axisValues[key]; | 
 |             if (valuesInAxis) { | 
 |                 // Sort axis values into ascending order to calculate gaps | 
 |                 valuesInAxis.sort(function (a, b) { | 
 |                     return a - b; | 
 |                 }); | 
 |  | 
 |                 let min = null; | 
 |                 for (let j = 1; j < valuesInAxis.length; ++j) { | 
 |                     const 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: BarSeriesModel[]) { | 
 |     const axisMinGaps = getValueAxesMinGaps(barSeries); | 
 |  | 
 |     const seriesInfoList: LayoutSeriesInfo[] = []; | 
 |     each(barSeries, function (seriesModel) { | 
 |         const cartesian = seriesModel.coordinateSystem as Cartesian2D; | 
 |         const baseAxis = cartesian.getBaseAxis(); | 
 |         const axisExtent = baseAxis.getExtent(); | 
 |  | 
 |         let bandWidth; | 
 |         if (baseAxis.type === 'category') { | 
 |             bandWidth = baseAxis.getBandWidth(); | 
 |         } | 
 |         else if (baseAxis.type === 'value' || baseAxis.type === 'time') { | 
 |             const key = baseAxis.dim + '_' + baseAxis.index; | 
 |             const minGap = axisMinGaps[key]; | 
 |             const extentSpan = Math.abs(axisExtent[1] - axisExtent[0]); | 
 |             const scale = baseAxis.scale.getExtent(); | 
 |             const scaleSpan = Math.abs(scale[1] - scale[0]); | 
 |             bandWidth = minGap | 
 |                 ? extentSpan / scaleSpan * minGap | 
 |                 : extentSpan; // When there is only one data value | 
 |         } | 
 |         else { | 
 |             const data = seriesModel.getData(); | 
 |             bandWidth = Math.abs(axisExtent[1] - axisExtent[0]) / data.count(); | 
 |         } | 
 |  | 
 |         const barWidth = parsePercent( | 
 |             seriesModel.get('barWidth'), bandWidth | 
 |         ); | 
 |         const barMaxWidth = parsePercent( | 
 |             seriesModel.get('barMaxWidth'), bandWidth | 
 |         ); | 
 |         const 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 | 
 |         ); | 
 |         const barGap = seriesModel.get('barGap'); | 
 |         const 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: LayoutSeriesInfo[]) { | 
 |     interface ColumnOnAxisInfo { | 
 |         bandWidth: number | 
 |         remainedWidth: number | 
 |         autoWidthCount: number | 
 |         categoryGap: number | string | 
 |         gap: number | string | 
 |         stacks: Dictionary<StackInfo> | 
 |     } | 
 |  | 
 |     // Columns info on each category axis. Key is cartesian name | 
 |     const columnsMap: Dictionary<ColumnOnAxisInfo> = {}; | 
 |  | 
 |     each(seriesInfoList, function (seriesInfo, idx) { | 
 |         const axisKey = seriesInfo.axisKey; | 
 |         const bandWidth = seriesInfo.bandWidth; | 
 |         const columnsOnAxis: ColumnOnAxisInfo = columnsMap[axisKey] || { | 
 |             bandWidth: bandWidth, | 
 |             remainedWidth: bandWidth, | 
 |             autoWidthCount: 0, | 
 |             categoryGap: null, | 
 |             gap: '20%', | 
 |             stacks: {} | 
 |         }; | 
 |         const stacks = columnsOnAxis.stacks; | 
 |         columnsMap[axisKey] = columnsOnAxis; | 
 |  | 
 |         const 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. | 
 |  | 
 |         let 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; | 
 |         } | 
 |  | 
 |         const barMaxWidth = seriesInfo.barMaxWidth; | 
 |         barMaxWidth && (stacks[stackId].maxWidth = barMaxWidth); | 
 |         const barMinWidth = seriesInfo.barMinWidth; | 
 |         barMinWidth && (stacks[stackId].minWidth = barMinWidth); | 
 |         const barGap = seriesInfo.barGap; | 
 |         (barGap != null) && (columnsOnAxis.gap = barGap); | 
 |         const barCategoryGap = seriesInfo.barCategoryGap; | 
 |         (barCategoryGap != null) && (columnsOnAxis.categoryGap = barCategoryGap); | 
 |     }); | 
 |  | 
 |     const result: BarWidthAndOffset = {}; | 
 |  | 
 |     each(columnsMap, function (columnsOnAxis, coordSysName) { | 
 |  | 
 |         result[coordSysName] = {}; | 
 |  | 
 |         const stacks = columnsOnAxis.stacks; | 
 |         const bandWidth = columnsOnAxis.bandWidth; | 
 |         let categoryGapPercent = columnsOnAxis.categoryGap; | 
 |         if (categoryGapPercent == null) { | 
 |             const 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) + '%'; | 
 |         } | 
 |  | 
 |         const categoryGap = parsePercent(categoryGapPercent, bandWidth); | 
 |         const barGapPercent = parsePercent(columnsOnAxis.gap, 1); | 
 |  | 
 |         let remainedWidth = columnsOnAxis.remainedWidth; | 
 |         let autoWidthCount = columnsOnAxis.autoWidthCount; | 
 |         let autoWidth = (remainedWidth - categoryGap) | 
 |             / (autoWidthCount + (autoWidthCount - 1) * barGapPercent); | 
 |         autoWidth = Math.max(autoWidth, 0); | 
 |  | 
 |         // Find if any auto calculated bar exceeded maxBarWidth | 
 |         each(stacks, function (column) { | 
 |             const maxWidth = column.maxWidth; | 
 |             const minWidth = column.minWidth; | 
 |  | 
 |             if (!column.width) { | 
 |                 let 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. | 
 |                 let 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); | 
 |  | 
 |  | 
 |         let widthSum = 0; | 
 |         let lastColumn: StackInfo; | 
 |         each(stacks, function (column, idx) { | 
 |             if (!column.width) { | 
 |                 column.width = autoWidth; | 
 |             } | 
 |             lastColumn = column; | 
 |             widthSum += column.width * (1 + barGapPercent); | 
 |         }); | 
 |         if (lastColumn) { | 
 |             widthSum -= lastColumn.width * barGapPercent; | 
 |         } | 
 |  | 
 |         let offset = -widthSum / 2; | 
 |         each(stacks, function (column, stackId) { | 
 |             result[coordSysName][stackId] = result[coordSysName][stackId] || { | 
 |                 bandWidth: bandWidth, | 
 |                 offset: offset, | 
 |                 width: column.width | 
 |             } as BarWidthAndOffset[string][string]; | 
 |  | 
 |             offset += column.width * (1 + barGapPercent); | 
 |         }); | 
 |     }); | 
 |  | 
 |     return result; | 
 | } | 
 |  | 
 | /** | 
 |  * @param barWidthAndOffset The result of makeColumnLayout | 
 |  * @param seriesModel If not provided, return all. | 
 |  * @return {stackId: {offset, width}} or {offset, width} if seriesModel provided. | 
 |  */ | 
 | function retrieveColumnLayout(barWidthAndOffset: BarWidthAndOffset, axis: Axis2D): typeof barWidthAndOffset[string]; | 
 | // eslint-disable-next-line max-len | 
 | function retrieveColumnLayout(barWidthAndOffset: BarWidthAndOffset, axis: Axis2D, seriesModel: BarSeriesModel): typeof barWidthAndOffset[string][string]; | 
 | function retrieveColumnLayout( | 
 |     barWidthAndOffset: BarWidthAndOffset, | 
 |     axis: Axis2D, | 
 |     seriesModel?: BarSeriesModel | 
 | ) { | 
 |     if (barWidthAndOffset && axis) { | 
 |         const result = barWidthAndOffset[getAxisKey(axis)]; | 
 |         if (result != null && seriesModel != null) { | 
 |             return result[getSeriesStackId(seriesModel)]; | 
 |         } | 
 |         return result; | 
 |     } | 
 | } | 
 | export {retrieveColumnLayout}; | 
 |  | 
 | export function layout(seriesType: string, ecModel: GlobalModel) { | 
 |  | 
 |     const seriesModels = prepareLayoutBarSeries(seriesType, ecModel); | 
 |     const barWidthAndOffset = makeColumnLayout(seriesModels); | 
 |  | 
 |     each(seriesModels, function (seriesModel) { | 
 |  | 
 |         const data = seriesModel.getData(); | 
 |         const cartesian = seriesModel.coordinateSystem as Cartesian2D; | 
 |         const baseAxis = cartesian.getBaseAxis(); | 
 |  | 
 |         const stackId = getSeriesStackId(seriesModel); | 
 |         const columnLayoutInfo = barWidthAndOffset[getAxisKey(baseAxis)][stackId]; | 
 |         const columnOffset = columnLayoutInfo.offset; | 
 |         const 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: string): StageHandler { | 
 |     return { | 
 |         seriesType, | 
 |  | 
 |         plan: createRenderPlanner(), | 
 |  | 
 |         reset: function (seriesModel: BarSeriesModel) { | 
 |             if (!isOnCartesian(seriesModel)) { | 
 |                 return; | 
 |             } | 
 |  | 
 |             const data = seriesModel.getData(); | 
 |  | 
 |             const cartesian = seriesModel.coordinateSystem as Cartesian2D; | 
 |             const baseAxis = cartesian.getBaseAxis(); | 
 |             const valueAxis = cartesian.getOtherAxis(baseAxis); | 
 |             const valueDimIdx = data.getDimensionIndex(data.mapDimension(valueAxis.dim)); | 
 |             const baseDimIdx = data.getDimensionIndex(data.mapDimension(baseAxis.dim)); | 
 |             const drawBackground = seriesModel.get('showBackground', true); | 
 |             const valueDim = data.mapDimension(valueAxis.dim); | 
 |             const stackResultDim = data.getCalculationInfo('stackResultDimension'); | 
 |             const stacked = isDimensionStacked(data, valueDim) && !!data.getCalculationInfo('stackedOnSeries'); | 
 |             const isValueAxisH = valueAxis.isHorizontal(); | 
 |             const valueAxisStart = getValueAxisStart(baseAxis, valueAxis); | 
 |             const isLarge = isInLargeMode(seriesModel); | 
 |             const barMinHeight = seriesModel.get('barMinHeight') || 0; | 
 |  | 
 |             const stackedDimIdx = stackResultDim && data.getDimensionIndex(stackResultDim); | 
 |  | 
 |             // Layout info. | 
 |             const columnWidth = data.getLayout('size'); | 
 |             const columnOffset = data.getLayout('offset'); | 
 |  | 
 |             return { | 
 |                 progress: function (params, data) { | 
 |                     const count = params.count; | 
 |                     const largePoints = isLarge && createFloat32Array(count * 3); | 
 |                     const largeBackgroundPoints = isLarge && drawBackground && createFloat32Array(count * 3); | 
 |                     const largeDataIndices = isLarge && createFloat32Array(count); | 
 |                     const coordLayout = cartesian.master.getRect(); | 
 |                     const bgSize = isValueAxisH ? coordLayout.width : coordLayout.height; | 
 |  | 
 |                     let dataIndex; | 
 |                     const store = data.getStore(); | 
 |  | 
 |                     let idxOffset = 0; | 
 |  | 
 |                     while ((dataIndex = params.next()) != null) { | 
 |                         const value = store.get(stacked ? stackedDimIdx : valueDimIdx, dataIndex); | 
 |                         const baseValue = store.get(baseDimIdx, dataIndex) as number; | 
 |  | 
 |                         let baseCoord = valueAxisStart; | 
 |                         let startValue; | 
 |  | 
 |                         // Because of the barMinHeight, we can not use the value in | 
 |                         // stackResultDimension directly. | 
 |                         if (stacked) { | 
 |                             startValue = +value - (store.get(valueDimIdx, dataIndex) as number); | 
 |                         } | 
 |  | 
 |                         let x; | 
 |                         let y; | 
 |                         let width; | 
 |                         let height; | 
 |  | 
 |                         if (isValueAxisH) { | 
 |                             const coord = cartesian.dataToPoint([value, baseValue]); | 
 |                             if (stacked) { | 
 |                                 const 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 { | 
 |                             const coord = cartesian.dataToPoint([baseValue, value]); | 
 |                             if (stacked) { | 
 |                                 const 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, y, width, 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, | 
 |                             largeDataIndices, | 
 |                             largeBackgroundPoints, | 
 |                             valueAxisHorizontal: isValueAxisH | 
 |                         }); | 
 |                     } | 
 |                 } | 
 |             }; | 
 |         } | 
 |     }; | 
 | } | 
 |  | 
 | function isOnCartesian(seriesModel: BarSeriesModel) { | 
 |     return seriesModel.coordinateSystem && seriesModel.coordinateSystem.type === 'cartesian2d'; | 
 | } | 
 |  | 
 | function isInLargeMode(seriesModel: BarSeriesModel) { | 
 |     return seriesModel.pipelineContext && seriesModel.pipelineContext.large; | 
 | } | 
 |  | 
 | // See cases in `test/bar-start.html` and `#7412`, `#8747`. | 
 | function getValueAxisStart(baseAxis: Axis2D, valueAxis: Axis2D) { | 
 |     return valueAxis.toGlobalCoord(valueAxis.dataToCoord(valueAxis.type === 'log' ? 1 : 0)); | 
 | } |