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