| /* | 
 | * 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 * as zrUtil from 'zrender/src/core/util'; | 
 | import {parsePercent} from '../util/number'; | 
 | import {isDimensionStacked} from '../data/helper/dataStackHelper'; | 
 | import type BarSeriesModel from '../chart/bar/BarSeries'; | 
 | import type Polar from '../coord/polar/Polar'; | 
 | import AngleAxis from '../coord/polar/AngleAxis'; | 
 | import RadiusAxis from '../coord/polar/RadiusAxis'; | 
 | import GlobalModel from '../model/Global'; | 
 | import ExtensionAPI from '../core/ExtensionAPI'; | 
 | import { Dictionary } from '../util/types'; | 
 |  | 
 | type PolarAxis = AngleAxis | RadiusAxis; | 
 |  | 
 | interface StackInfo { | 
 |     width: number | 
 |     maxWidth: number | 
 | } | 
 | interface LayoutColumnInfo { | 
 |     autoWidthCount: number | 
 |     bandWidth: number | 
 |     remainedWidth: number | 
 |     categoryGap: string | number | 
 |     gap: string | number | 
 |     stacks: Dictionary<StackInfo> | 
 | } | 
 |  | 
 | interface BarWidthAndOffset { | 
 |     width: number | 
 |     offset: number | 
 | } | 
 |  | 
 | function getSeriesStackId(seriesModel: BarSeriesModel) { | 
 |     return seriesModel.get('stack') | 
 |         || '__ec_stack_' + seriesModel.seriesIndex; | 
 | } | 
 |  | 
 | function getAxisKey(polar: Polar, axis: PolarAxis) { | 
 |     return axis.dim + polar.model.componentIndex; | 
 | } | 
 |  | 
 | function barLayoutPolar(seriesType: string, ecModel: GlobalModel, api: ExtensionAPI) { | 
 |  | 
 |     const lastStackCoords: Dictionary<{p: number, n: number}[]> = {}; | 
 |  | 
 |     const barWidthAndOffset = calRadialBar( | 
 |         zrUtil.filter( | 
 |             ecModel.getSeriesByType(seriesType) as BarSeriesModel[], | 
 |             function (seriesModel) { | 
 |                 return !ecModel.isSeriesFiltered(seriesModel) | 
 |                     && seriesModel.coordinateSystem | 
 |                     && seriesModel.coordinateSystem.type === 'polar'; | 
 |             } | 
 |         ) | 
 |     ); | 
 |  | 
 |     ecModel.eachSeriesByType(seriesType, function (seriesModel: BarSeriesModel) { | 
 |  | 
 |         // Check series coordinate, do layout for polar only | 
 |         if (seriesModel.coordinateSystem.type !== 'polar') { | 
 |             return; | 
 |         } | 
 |  | 
 |         const data = seriesModel.getData(); | 
 |         const polar = seriesModel.coordinateSystem as Polar; | 
 |         const baseAxis = polar.getBaseAxis(); | 
 |         const axisKey = getAxisKey(polar, baseAxis); | 
 |  | 
 |         const stackId = getSeriesStackId(seriesModel); | 
 |         const columnLayoutInfo = barWidthAndOffset[axisKey][stackId]; | 
 |         const columnOffset = columnLayoutInfo.offset; | 
 |         const columnWidth = columnLayoutInfo.width; | 
 |         const valueAxis = polar.getOtherAxis(baseAxis); | 
 |  | 
 |         const cx = seriesModel.coordinateSystem.cx; | 
 |         const cy = seriesModel.coordinateSystem.cy; | 
 |  | 
 |         const barMinHeight = seriesModel.get('barMinHeight') || 0; | 
 |         const barMinAngle = seriesModel.get('barMinAngle') || 0; | 
 |  | 
 |         lastStackCoords[stackId] = lastStackCoords[stackId] || []; | 
 |  | 
 |         const valueDim = data.mapDimension(valueAxis.dim); | 
 |         const baseDim = data.mapDimension(baseAxis.dim); | 
 |         const stacked = isDimensionStacked(data, valueDim /* , baseDim */); | 
 |         const clampLayout = baseAxis.dim !== 'radius' | 
 |             || !seriesModel.get('roundCap', true); | 
 |  | 
 |         const valueAxisStart = valueAxis.dataToCoord(0); | 
 |         for (let idx = 0, len = data.count(); idx < len; idx++) { | 
 |             const value = data.get(valueDim, idx) as number; | 
 |             const baseValue = data.get(baseDim, idx) as number; | 
 |  | 
 |             const sign = value >= 0 ? 'p' : 'n' as 'p' | 'n'; | 
 |             let baseCoord = valueAxisStart; | 
 |  | 
 |             // Because of the barMinHeight, we can not use the value in | 
 |             // stackResultDimension directly. | 
 |             // Only ordinal axis can be stacked. | 
 |             if (stacked) { | 
 |  | 
 |                 if (!lastStackCoords[stackId][baseValue]) { | 
 |                     lastStackCoords[stackId][baseValue] = { | 
 |                         p: valueAxisStart, // Positive stack | 
 |                         n: valueAxisStart  // Negative stack | 
 |                     }; | 
 |                 } | 
 |                 // Should also consider #4243 | 
 |                 baseCoord = lastStackCoords[stackId][baseValue][sign]; | 
 |             } | 
 |  | 
 |             let r0; | 
 |             let r; | 
 |             let startAngle; | 
 |             let endAngle; | 
 |  | 
 |             // radial sector | 
 |             if (valueAxis.dim === 'radius') { | 
 |                 let radiusSpan = valueAxis.dataToCoord(value) - valueAxisStart; | 
 |                 const angle = baseAxis.dataToCoord(baseValue); | 
 |  | 
 |                 if (Math.abs(radiusSpan) < barMinHeight) { | 
 |                     radiusSpan = (radiusSpan < 0 ? -1 : 1) * barMinHeight; | 
 |                 } | 
 |  | 
 |                 r0 = baseCoord; | 
 |                 r = baseCoord + radiusSpan; | 
 |                 startAngle = angle - columnOffset; | 
 |                 endAngle = startAngle - columnWidth; | 
 |  | 
 |                 stacked && (lastStackCoords[stackId][baseValue][sign] = r); | 
 |             } | 
 |             // tangential sector | 
 |             else { | 
 |                 let angleSpan = valueAxis.dataToCoord(value, clampLayout) - valueAxisStart; | 
 |                 const radius = baseAxis.dataToCoord(baseValue); | 
 |  | 
 |                 if (Math.abs(angleSpan) < barMinAngle) { | 
 |                     angleSpan = (angleSpan < 0 ? -1 : 1) * barMinAngle; | 
 |                 } | 
 |  | 
 |                 r0 = radius + columnOffset; | 
 |                 r = r0 + columnWidth; | 
 |                 startAngle = baseCoord; | 
 |                 endAngle = baseCoord + angleSpan; | 
 |  | 
 |                 // if the previous stack is at the end of the ring, | 
 |                 // add a round to differentiate it from origin | 
 |                 // let extent = angleAxis.getExtent(); | 
 |                 // let stackCoord = angle; | 
 |                 // if (stackCoord === extent[0] && value > 0) { | 
 |                 //     stackCoord = extent[1]; | 
 |                 // } | 
 |                 // else if (stackCoord === extent[1] && value < 0) { | 
 |                 //     stackCoord = extent[0]; | 
 |                 // } | 
 |                 stacked && (lastStackCoords[stackId][baseValue][sign] = endAngle); | 
 |             } | 
 |  | 
 |             data.setItemLayout(idx, { | 
 |                 cx: cx, | 
 |                 cy: cy, | 
 |                 r0: r0, | 
 |                 r: r, | 
 |                 // Consider that positive angle is anti-clockwise, | 
 |                 // while positive radian of sector is clockwise | 
 |                 startAngle: -startAngle * Math.PI / 180, | 
 |                 endAngle: -endAngle * Math.PI / 180, | 
 |  | 
 |                 /** | 
 |                  * Keep the same logic with bar in catesion: use end value to | 
 |                  * control direction. Notice that if clockwise is true (by | 
 |                  * default), the sector will always draw clockwisely, no matter | 
 |                  * whether endAngle is greater or less than startAngle. | 
 |                  */ | 
 |                 clockwise: startAngle >= endAngle | 
 |             }); | 
 |  | 
 |         } | 
 |  | 
 |     }); | 
 |  | 
 | } | 
 |  | 
 | /** | 
 |  * Calculate bar width and offset for radial bar charts | 
 |  */ | 
 | function calRadialBar(barSeries: BarSeriesModel[]) { | 
 |     // Columns info on each category axis. Key is polar name | 
 |     const columnsMap: Dictionary<LayoutColumnInfo> = {}; | 
 |  | 
 |     zrUtil.each(barSeries, function (seriesModel, idx) { | 
 |         const data = seriesModel.getData(); | 
 |         const polar = seriesModel.coordinateSystem as Polar; | 
 |  | 
 |         const baseAxis = polar.getBaseAxis(); | 
 |         const axisKey = getAxisKey(polar, baseAxis); | 
 |  | 
 |         const axisExtent = baseAxis.getExtent(); | 
 |         const bandWidth = baseAxis.type === 'category' | 
 |             ? baseAxis.getBandWidth() | 
 |             : (Math.abs(axisExtent[1] - axisExtent[0]) / data.count()); | 
 |  | 
 |         const columnsOnAxis = columnsMap[axisKey] || { | 
 |             bandWidth: bandWidth, | 
 |             remainedWidth: bandWidth, | 
 |             autoWidthCount: 0, | 
 |             categoryGap: '20%', | 
 |             gap: '30%', | 
 |             stacks: {} | 
 |         }; | 
 |         const stacks = columnsOnAxis.stacks; | 
 |         columnsMap[axisKey] = columnsOnAxis; | 
 |  | 
 |         const stackId = getSeriesStackId(seriesModel); | 
 |  | 
 |         if (!stacks[stackId]) { | 
 |             columnsOnAxis.autoWidthCount++; | 
 |         } | 
 |         stacks[stackId] = stacks[stackId] || { | 
 |             width: 0, | 
 |             maxWidth: 0 | 
 |         }; | 
 |  | 
 |         let barWidth = parsePercent( | 
 |             seriesModel.get('barWidth'), | 
 |             bandWidth | 
 |         ); | 
 |         const barMaxWidth = parsePercent( | 
 |             seriesModel.get('barMaxWidth'), | 
 |             bandWidth | 
 |         ); | 
 |         const barGap = seriesModel.get('barGap'); | 
 |         const barCategoryGap = seriesModel.get('barCategoryGap'); | 
 |  | 
 |         if (barWidth && !stacks[stackId].width) { | 
 |             barWidth = Math.min(columnsOnAxis.remainedWidth, barWidth); | 
 |             stacks[stackId].width = barWidth; | 
 |             columnsOnAxis.remainedWidth -= barWidth; | 
 |         } | 
 |  | 
 |         barMaxWidth && (stacks[stackId].maxWidth = barMaxWidth); | 
 |         (barGap != null) && (columnsOnAxis.gap = barGap); | 
 |         (barCategoryGap != null) && (columnsOnAxis.categoryGap = barCategoryGap); | 
 |     }); | 
 |  | 
 |  | 
 |     const result: Dictionary<Dictionary<BarWidthAndOffset>> = {}; | 
 |  | 
 |     zrUtil.each(columnsMap, function (columnsOnAxis, coordSysName) { | 
 |  | 
 |         result[coordSysName] = {}; | 
 |  | 
 |         const stacks = columnsOnAxis.stacks; | 
 |         const bandWidth = columnsOnAxis.bandWidth; | 
 |         const categoryGap = parsePercent(columnsOnAxis.categoryGap, 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 | 
 |         zrUtil.each(stacks, function (column, stack) { | 
 |             let 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); | 
 |  | 
 |         let widthSum = 0; | 
 |         let lastColumn: StackInfo; | 
 |         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; | 
 |         } | 
 |  | 
 |         let offset = -widthSum / 2; | 
 |         zrUtil.each(stacks, function (column, stackId) { | 
 |             result[coordSysName][stackId] = result[coordSysName][stackId] || { | 
 |                 offset: offset, | 
 |                 width: column.width | 
 |             } as BarWidthAndOffset; | 
 |  | 
 |             offset += column.width * (1 + barGapPercent); | 
 |         }); | 
 |     }); | 
 |  | 
 |     return result; | 
 | } | 
 |  | 
 | export default barLayoutPolar; |