|  | /* | 
|  | * 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; |