| /* |
| * 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 { parsePercent, linearMap } from '../../util/number'; |
| import * as layout from '../../util/layout'; |
| import * as zrUtil from 'zrender/src/core/util'; |
| import GlobalModel from '../../model/Global'; |
| import ExtensionAPI from '../../core/ExtensionAPI'; |
| import PieSeriesModel from './PieSeries'; |
| import { SectorShape } from 'zrender/src/graphic/shape/Sector'; |
| |
| const PI2 = Math.PI * 2; |
| const RADIAN = Math.PI / 180; |
| |
| function getViewRect(seriesModel: PieSeriesModel, api: ExtensionAPI) { |
| return layout.getLayoutRect( |
| seriesModel.getBoxLayoutParams(), { |
| width: api.getWidth(), |
| height: api.getHeight() |
| } |
| ); |
| } |
| |
| export function getBasicPieLayout(seriesModel: PieSeriesModel, api: ExtensionAPI): |
| Pick<SectorShape, 'cx' | 'cy' | 'r' | 'r0'> { |
| const viewRect = getViewRect(seriesModel, api); |
| |
| let center = seriesModel.get('center'); |
| let radius = seriesModel.get('radius'); |
| |
| if (!zrUtil.isArray(radius)) { |
| radius = [0, radius]; |
| } |
| if (!zrUtil.isArray(center)) { |
| center = [center, center]; |
| } |
| const width = parsePercent(viewRect.width, api.getWidth()); |
| const height = parsePercent(viewRect.height, api.getHeight()); |
| const size = Math.min(width, height); |
| const cx = parsePercent(center[0], width) + viewRect.x; |
| const cy = parsePercent(center[1], height) + viewRect.y; |
| const r0 = parsePercent(radius[0], size / 2); |
| const r = parsePercent(radius[1], size / 2); |
| return { |
| cx, |
| cy, |
| r0, |
| r |
| }; |
| } |
| |
| export default function pieLayout( |
| seriesType: 'pie', |
| ecModel: GlobalModel, |
| api: ExtensionAPI |
| ) { |
| ecModel.eachSeriesByType(seriesType, function (seriesModel: PieSeriesModel) { |
| const data = seriesModel.getData(); |
| const valueDim = data.mapDimension('value'); |
| const viewRect = getViewRect(seriesModel, api); |
| |
| const { cx, cy, r, r0 } = getBasicPieLayout(seriesModel, api); |
| |
| const startAngle = -seriesModel.get('startAngle') * RADIAN; |
| |
| const minAngle = seriesModel.get('minAngle') * RADIAN; |
| |
| let validDataCount = 0; |
| data.each(valueDim, function (value: number) { |
| !isNaN(value) && validDataCount++; |
| }); |
| |
| const sum = data.getSum(valueDim); |
| // Sum may be 0 |
| let unitRadian = Math.PI / (sum || validDataCount) * 2; |
| |
| const clockwise = seriesModel.get('clockwise'); |
| |
| const roseType = seriesModel.get('roseType'); |
| const stillShowZeroSum = seriesModel.get('stillShowZeroSum'); |
| |
| // [0...max] |
| const extent = data.getDataExtent(valueDim); |
| extent[0] = 0; |
| |
| // In the case some sector angle is smaller than minAngle |
| let restAngle = PI2; |
| let valueSumLargerThanMinAngle = 0; |
| |
| let currentAngle = startAngle; |
| const dir = clockwise ? 1 : -1; |
| |
| data.setLayout({ viewRect, r }); |
| |
| data.each(valueDim, function (value: number, idx: number) { |
| let angle; |
| if (isNaN(value)) { |
| data.setItemLayout(idx, { |
| angle: NaN, |
| startAngle: NaN, |
| endAngle: NaN, |
| clockwise: clockwise, |
| cx: cx, |
| cy: cy, |
| r0: r0, |
| r: roseType |
| ? NaN |
| : r |
| }); |
| return; |
| } |
| |
| // FIXME 兼容 2.0 但是 roseType 是 area 的时候才是这样? |
| if (roseType !== 'area') { |
| angle = (sum === 0 && stillShowZeroSum) |
| ? unitRadian : (value * unitRadian); |
| } |
| else { |
| angle = PI2 / validDataCount; |
| } |
| |
| if (angle < minAngle) { |
| angle = minAngle; |
| restAngle -= minAngle; |
| } |
| else { |
| valueSumLargerThanMinAngle += value; |
| } |
| |
| const endAngle = currentAngle + dir * angle; |
| data.setItemLayout(idx, { |
| angle: angle, |
| startAngle: currentAngle, |
| endAngle: endAngle, |
| clockwise: clockwise, |
| cx: cx, |
| cy: cy, |
| r0: r0, |
| r: roseType |
| ? linearMap(value, extent, [r0, r]) |
| : r |
| }); |
| |
| currentAngle = endAngle; |
| }); |
| |
| // Some sector is constrained by minAngle |
| // Rest sectors needs recalculate angle |
| if (restAngle < PI2 && validDataCount) { |
| // Average the angle if rest angle is not enough after all angles is |
| // Constrained by minAngle |
| if (restAngle <= 1e-3) { |
| const angle = PI2 / validDataCount; |
| data.each(valueDim, function (value: number, idx: number) { |
| if (!isNaN(value)) { |
| const layout = data.getItemLayout(idx); |
| layout.angle = angle; |
| layout.startAngle = startAngle + dir * idx * angle; |
| layout.endAngle = startAngle + dir * (idx + 1) * angle; |
| } |
| }); |
| } |
| else { |
| unitRadian = restAngle / valueSumLargerThanMinAngle; |
| currentAngle = startAngle; |
| data.each(valueDim, function (value: number, idx: number) { |
| if (!isNaN(value)) { |
| const layout = data.getItemLayout(idx); |
| const angle = layout.angle === minAngle |
| ? minAngle : value * unitRadian; |
| layout.startAngle = currentAngle; |
| layout.endAngle = currentAngle + dir * angle; |
| currentAngle += dir * angle; |
| } |
| }); |
| } |
| } |
| }); |
| } |