|  | /* | 
|  | * 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); | 
|  |  | 
|  | // center can be string or number when coordinateSystem is specified | 
|  | let center = seriesModel.get('center'); | 
|  | let radius = seriesModel.get('radius'); | 
|  |  | 
|  | if (!zrUtil.isArray(radius)) { | 
|  | radius = [0, radius]; | 
|  | } | 
|  | const width = parsePercent(viewRect.width, api.getWidth()); | 
|  | const height = parsePercent(viewRect.height, api.getHeight()); | 
|  | const size = Math.min(width, height); | 
|  | const r0 = parsePercent(radius[0], size / 2); | 
|  | const r = parsePercent(radius[1], size / 2); | 
|  |  | 
|  | let cx: number; | 
|  | let cy: number; | 
|  | const coordSys = seriesModel.coordinateSystem; | 
|  | if (coordSys) { | 
|  | // percentage is not allowed when coordinate system is specified | 
|  | const point = coordSys.dataToPoint(center); | 
|  | cx = point[0] || 0; | 
|  | cy = point[1] || 0; | 
|  | } | 
|  | else { | 
|  | if (!zrUtil.isArray(center)) { | 
|  | center = [center, center]; | 
|  | } | 
|  | cx = parsePercent(center[0], width) + viewRect.x; | 
|  | cy = parsePercent(center[1], height) + viewRect.y; | 
|  | } | 
|  |  | 
|  | 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; | 
|  | } | 
|  | }); | 
|  | } | 
|  | } | 
|  | }); | 
|  | } |