| /* | |
| * 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 createSeriesDataSimply from '../helper/createSeriesDataSimply'; | |
| import * as zrUtil from 'zrender/src/core/util'; | |
| import * as modelUtil from '../../util/model'; | |
| import { getPercentSeats } from '../../util/number'; | |
| import { makeSeriesEncodeForNameBased } from '../../data/helper/sourceHelper'; | |
| import LegendVisualProvider from '../../visual/LegendVisualProvider'; | |
| import SeriesModel from '../../model/Series'; | |
| import { | |
| SeriesOption, | |
| CallbackDataParams, | |
| CircleLayoutOptionMixin, | |
| LabelLineOption, | |
| ItemStyleOption, | |
| BoxLayoutOptionMixin, | |
| OptionDataValueNumeric, | |
| SeriesEncodeOptionMixin, | |
| OptionDataItemObject, | |
| StatesOptionMixin, | |
| SeriesLabelOption, | |
| DefaultEmphasisFocus | |
| } from '../../util/types'; | |
| import type SeriesData from '../../data/SeriesData'; | |
| interface PieItemStyleOption<TCbParams = never> extends ItemStyleOption<TCbParams> { | |
| // can be 10 | |
| // which means that both innerCornerRadius and outerCornerRadius are 10 | |
| // can also be an array [20, 10] | |
| // which means that innerCornerRadius is 20 | |
| // and outerCornerRadius is 10 | |
| // can also be a string or string array, such as ['20%', '50%'] | |
| // which means that innerCornerRadius is 20% of the innerRadius | |
| // and outerCornerRadius is half of outerRadius. | |
| borderRadius?: (number | string)[] | number | string | |
| } | |
| export interface PieCallbackDataParams extends CallbackDataParams { | |
| percent: number | |
| } | |
| export interface PieStateOption<TCbParams = never> { | |
| // TODO: TYPE Color Callback | |
| itemStyle?: PieItemStyleOption<TCbParams> | |
| label?: PieLabelOption | |
| labelLine?: PieLabelLineOption | |
| } | |
| interface PieLabelOption extends Omit<SeriesLabelOption, 'rotate' | 'position'> { | |
| rotate?: number | boolean | 'radial' | 'tangential' | |
| alignTo?: 'none' | 'labelLine' | 'edge' | |
| edgeDistance?: string | number | |
| /** | |
| * @deprecated Use `edgeDistance` instead | |
| */ | |
| margin?: string | number | |
| bleedMargin?: number | |
| distanceToLabelLine?: number | |
| position?: SeriesLabelOption['position'] | 'outer' | 'inner' | 'center' | 'outside' | |
| } | |
| interface PieLabelLineOption extends LabelLineOption { | |
| /** | |
| * Max angle between labelLine and surface normal. | |
| * 0 - 180 | |
| */ | |
| maxSurfaceAngle?: number | |
| } | |
| interface ExtraStateOption { | |
| emphasis?: { | |
| focus?: DefaultEmphasisFocus | |
| scale?: boolean | |
| scaleSize?: number | |
| } | |
| } | |
| export interface PieDataItemOption extends | |
| OptionDataItemObject<OptionDataValueNumeric>, | |
| PieStateOption, StatesOptionMixin<PieStateOption, ExtraStateOption> { | |
| cursor?: string | |
| } | |
| export interface PieSeriesOption extends | |
| Omit<SeriesOption<PieStateOption<PieCallbackDataParams>, ExtraStateOption>, 'labelLine'>, | |
| PieStateOption<PieCallbackDataParams>, | |
| Omit<CircleLayoutOptionMixin, 'center'>, | |
| BoxLayoutOptionMixin, | |
| SeriesEncodeOptionMixin { | |
| type?: 'pie' | |
| roseType?: 'radius' | 'area' | |
| center?: string | number | (string | number)[] | |
| clockwise?: boolean | |
| startAngle?: number | |
| minAngle?: number | |
| minShowLabelAngle?: number | |
| selectedOffset?: number | |
| avoidLabelOverlap?: boolean | |
| percentPrecision?: number | |
| stillShowZeroSum?: boolean | |
| animationType?: 'expansion' | 'scale' | |
| animationTypeUpdate?: 'transition' | 'expansion' | |
| showEmptyCircle?: boolean; | |
| emptyCircleStyle?: PieItemStyleOption; | |
| data?: (OptionDataValueNumeric | OptionDataValueNumeric[] | PieDataItemOption)[] | |
| } | |
| const innerData = modelUtil.makeInner<{ | |
| seats?: number[] | |
| }, SeriesData>(); | |
| class PieSeriesModel extends SeriesModel<PieSeriesOption> { | |
| static type = 'series.pie' as const; | |
| /** | |
| * @overwrite | |
| */ | |
| init(option: PieSeriesOption): void { | |
| super.init.apply(this, arguments as any); | |
| // Enable legend selection for each data item | |
| // Use a function instead of direct access because data reference may changed | |
| this.legendVisualProvider = new LegendVisualProvider( | |
| zrUtil.bind(this.getData, this), zrUtil.bind(this.getRawData, this) | |
| ); | |
| this._defaultLabelLine(option); | |
| } | |
| /** | |
| * @overwrite | |
| */ | |
| mergeOption(): void { | |
| super.mergeOption.apply(this, arguments as any); | |
| } | |
| /** | |
| * @overwrite | |
| */ | |
| getInitialData(this: PieSeriesModel): SeriesData { | |
| return createSeriesDataSimply(this, { | |
| coordDimensions: ['value'], | |
| encodeDefaulter: zrUtil.curry(makeSeriesEncodeForNameBased, this) | |
| }); | |
| } | |
| /** | |
| * @overwrite | |
| */ | |
| getDataParams(dataIndex: number): PieCallbackDataParams { | |
| const data = this.getData(); | |
| // update seats when data is changed | |
| const dataInner = innerData(data); | |
| let seats = dataInner.seats; | |
| if (!seats) { | |
| const valueList: number[] = []; | |
| data.each(data.mapDimension('value'), function (value: number) { | |
| valueList.push(value); | |
| }); | |
| seats = dataInner.seats = getPercentSeats(valueList, data.hostModel.get('percentPrecision')); | |
| } | |
| const params = super.getDataParams(dataIndex) as PieCallbackDataParams; | |
| // seats may be empty when sum is 0 | |
| params.percent = seats[dataIndex] || 0; | |
| params.$vars.push('percent'); | |
| return params; | |
| } | |
| private _defaultLabelLine(option: PieSeriesOption): void { | |
| // Extend labelLine emphasis | |
| modelUtil.defaultEmphasis(option, 'labelLine', ['show']); | |
| const labelLineNormalOpt = option.labelLine; | |
| const labelLineEmphasisOpt = option.emphasis.labelLine; | |
| // Not show label line if `label.normal.show = false` | |
| labelLineNormalOpt.show = labelLineNormalOpt.show | |
| && option.label.show; | |
| labelLineEmphasisOpt.show = labelLineEmphasisOpt.show | |
| && option.emphasis.label.show; | |
| } | |
| static defaultOption: Omit<PieSeriesOption, 'type'> = { | |
| // zlevel: 0, | |
| z: 2, | |
| legendHoverLink: true, | |
| colorBy: 'data', | |
| // 默认全局居中 | |
| center: ['50%', '50%'], | |
| radius: [0, '75%'], | |
| // 默认顺时针 | |
| clockwise: true, | |
| startAngle: 90, | |
| // 最小角度改为0 | |
| minAngle: 0, | |
| // If the angle of a sector less than `minShowLabelAngle`, | |
| // the label will not be displayed. | |
| minShowLabelAngle: 0, | |
| // 选中时扇区偏移量 | |
| selectedOffset: 10, | |
| // 选择模式,默认关闭,可选single,multiple | |
| // selectedMode: false, | |
| // 南丁格尔玫瑰图模式,'radius'(半径) | 'area'(面积) | |
| // roseType: null, | |
| percentPrecision: 2, | |
| // If still show when all data zero. | |
| stillShowZeroSum: true, | |
| // cursor: null, | |
| left: 0, | |
| top: 0, | |
| right: 0, | |
| bottom: 0, | |
| width: null, | |
| height: null, | |
| label: { | |
| // color: 'inherit', | |
| // If rotate around circle | |
| rotate: 0, | |
| show: true, | |
| overflow: 'truncate', | |
| // 'outer', 'inside', 'center' | |
| position: 'outer', | |
| // 'none', 'labelLine', 'edge'. Works only when position is 'outer' | |
| alignTo: 'none', | |
| // Closest distance between label and chart edge. | |
| // Works only position is 'outer' and alignTo is 'edge'. | |
| edgeDistance: '25%', | |
| // Works only position is 'outer' and alignTo is not 'edge'. | |
| bleedMargin: 10, | |
| // Distance between text and label line. | |
| distanceToLabelLine: 5 | |
| // formatter: 标签文本格式器,同 tooltip.formatter,不支持异步回调 | |
| // 默认使用全局文本样式,详见 textStyle | |
| // distance: 当position为inner时有效,为label位置到圆心的距离与圆半径(环状图为内外半径和)的比例系数 | |
| }, | |
| // Enabled when label.normal.position is 'outer' | |
| labelLine: { | |
| show: true, | |
| // 引导线两段中的第一段长度 | |
| length: 15, | |
| // 引导线两段中的第二段长度 | |
| length2: 15, | |
| smooth: false, | |
| minTurnAngle: 90, | |
| maxSurfaceAngle: 90, | |
| lineStyle: { | |
| // color: 各异, | |
| width: 1, | |
| type: 'solid' | |
| } | |
| }, | |
| itemStyle: { | |
| borderWidth: 1, | |
| borderJoin: 'round' | |
| }, | |
| showEmptyCircle: true, | |
| emptyCircleStyle: { | |
| color: 'lightgray', | |
| opacity: 1 | |
| }, | |
| labelLayout: { | |
| // Hide the overlapped label. | |
| hideOverlap: true | |
| }, | |
| emphasis: { | |
| scale: true, | |
| scaleSize: 5 | |
| }, | |
| // If use strategy to avoid label overlapping | |
| avoidLabelOverlap: true, | |
| // Animation type. Valid values: expansion, scale | |
| animationType: 'expansion', | |
| animationDuration: 1000, | |
| // Animation type when update. Valid values: transition, expansion | |
| animationTypeUpdate: 'transition', | |
| animationEasingUpdate: 'cubicInOut', | |
| animationDurationUpdate: 500, | |
| animationEasing: 'cubicInOut' | |
| }; | |
| } | |
| export default PieSeriesModel; |