| /* | 
 | * 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 SeriesModel from '../../model/Series'; | 
 | import Tree, { TreeNode } from '../../data/Tree'; | 
 | import {wrapTreePathInfo} from '../helper/treeHelper'; | 
 | import { | 
 |     SeriesOption, | 
 |     CircleLayoutOptionMixin, | 
 |     SeriesLabelOption, | 
 |     ItemStyleOption, | 
 |     OptionDataValue, | 
 |     CallbackDataParams, | 
 |     StatesOptionMixin, | 
 |     OptionDataItemObject, | 
 |     DefaultEmphasisFocus, | 
 |     SunburstColorByMixin | 
 | } from '../../util/types'; | 
 | import GlobalModel from '../../model/Global'; | 
 | import SeriesData from '../../data/SeriesData'; | 
 | import Model from '../../model/Model'; | 
 | import enableAriaDecalForTree from '../helper/enableAriaDecalForTree'; | 
 |  | 
 | interface SunburstItemStyleOption<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 | 
 | } | 
 |  | 
 | interface SunburstLabelOption extends Omit<SeriesLabelOption, 'rotate' | 'position'> { | 
 |     rotate?: 'radial' | 'tangential' | number | 
 |     minAngle?: number | 
 |     silent?: boolean | 
 |     position?: SeriesLabelOption['position'] | 'outside' | 
 | } | 
 |  | 
 | interface SunburstDataParams extends CallbackDataParams { | 
 |     treePathInfo: { | 
 |         name: string, | 
 |         dataIndex: number | 
 |         value: SunburstSeriesNodeItemOption['value'] | 
 |     }[] | 
 | } | 
 |  | 
 | interface SunburstStatesMixin { | 
 |     emphasis?: { | 
 |         focus?: DefaultEmphasisFocus | 'descendant' | 'ancestor' | 
 |     } | 
 | } | 
 |  | 
 | export interface SunburstStateOption<TCbParams = never> { | 
 |     itemStyle?: SunburstItemStyleOption<TCbParams> | 
 |     label?: SunburstLabelOption | 
 | } | 
 |  | 
 | export interface SunburstSeriesNodeItemOption extends | 
 |     SunburstStateOption<CallbackDataParams>, | 
 |     StatesOptionMixin<SunburstStateOption<CallbackDataParams>, SunburstStatesMixin>, | 
 |     OptionDataItemObject<OptionDataValue> | 
 | { | 
 |     nodeClick?: 'rootToNode' | 'link' | false | 
 |     // Available when nodeClick is link | 
 |     link?: string | 
 |     target?: string | 
 |  | 
 |     children?: SunburstSeriesNodeItemOption[] | 
 |  | 
 |     collapsed?: boolean | 
 |  | 
 |     cursor?: string | 
 | } | 
 | export interface SunburstSeriesLevelOption | 
 |     extends SunburstStateOption, StatesOptionMixin<SunburstStateOption, SunburstStatesMixin> { | 
 |  | 
 |     radius?: (number | string)[] | 
 |     /** | 
 |      * @deprecated use radius instead | 
 |      */ | 
 |     r?: number | string | 
 |     /** | 
 |      * @deprecated use radius instead | 
 |      */ | 
 |     r0?: number | string | 
 |  | 
 |     highlight?: { | 
 |         itemStyle?: SunburstItemStyleOption | 
 |         label?: SunburstLabelOption | 
 |     } | 
 | } | 
 |  | 
 | interface SortParam { | 
 |     dataIndex: number | 
 |     depth: number | 
 |     height: number | 
 |     getValue(): number | 
 | } | 
 | export interface SunburstSeriesOption extends | 
 |     SeriesOption<SunburstStateOption, SunburstStatesMixin>, SunburstStateOption, | 
 |     SunburstColorByMixin, | 
 |     CircleLayoutOptionMixin { | 
 |  | 
 |     type?: 'sunburst' | 
 |  | 
 |     clockwise?: boolean | 
 |     startAngle?: number | 
 |     minAngle?: number | 
 |     /** | 
 |      * If still show when all data zero. | 
 |      */ | 
 |     stillShowZeroSum?: boolean | 
 |     /** | 
 |      * Policy of highlighting pieces when hover on one | 
 |      * Valid values: 'none' (for not downplay others), 'descendant', | 
 |      * 'ancestor', 'self' | 
 |      */ | 
 |     // highlightPolicy?: 'descendant' | 'ancestor' | 'self' | 
 |  | 
 |     nodeClick?: 'rootToNode' | 'link' | false | 
 |  | 
 |     renderLabelForZeroData?: boolean | 
 |  | 
 |     levels?: SunburstSeriesLevelOption[] | 
 |  | 
 |     animationType?: 'expansion' | 'scale' | 
 |  | 
 |     sort?: 'desc' | 'asc' | ((a: SortParam, b: SortParam) => number) | 
 | } | 
 |  | 
 | interface SunburstSeriesModel { | 
 |     getFormattedLabel( | 
 |         dataIndex: number, | 
 |         state?: 'emphasis' | 'normal' | 'highlight' | 'blur' | 'select' | 
 |     ): string | 
 | } | 
 | class SunburstSeriesModel extends SeriesModel<SunburstSeriesOption> { | 
 |  | 
 |     static readonly type = 'series.sunburst'; | 
 |     readonly type = SunburstSeriesModel.type; | 
 |  | 
 |     ignoreStyleOnData = true; | 
 |  | 
 |     private _viewRoot: TreeNode; | 
 |     private _levelModels: Model<SunburstSeriesLevelOption>[]; | 
 |  | 
 |     getInitialData(option: SunburstSeriesOption, ecModel: GlobalModel) { | 
 |         // Create a virtual root. | 
 |         const root = { name: option.name, children: option.data } as SunburstSeriesNodeItemOption; | 
 |  | 
 |         completeTreeValue(root); | 
 |  | 
 |         const levelModels = this._levelModels = | 
 |             zrUtil.map(option.levels || [], function (levelDefine) { | 
 |                 return new Model(levelDefine, this, ecModel); | 
 |             }, this); | 
 |  | 
 |         // Make sure always a new tree is created when setOption, | 
 |         // in TreemapView, we check whether oldTree === newTree | 
 |         // to choose mappings approach among old shapes and new shapes. | 
 |         const tree = Tree.createTree(root, this, beforeLink); | 
 |  | 
 |         function beforeLink(nodeData: SeriesData) { | 
 |             nodeData.wrapMethod('getItemModel', function (model, idx) { | 
 |                 const node = tree.getNodeByDataIndex(idx); | 
 |                 const levelModel = levelModels[node.depth]; | 
 |                 levelModel && (model.parentModel = levelModel); | 
 |                 return model; | 
 |             }); | 
 |         } | 
 |         return tree.data; | 
 |     } | 
 |  | 
 |     optionUpdated() { | 
 |         this.resetViewRoot(); | 
 |     } | 
 |  | 
 |     /* | 
 |      * @override | 
 |      */ | 
 |     getDataParams(dataIndex: number) { | 
 |         const params = super.getDataParams.apply(this, arguments as any) as SunburstDataParams; | 
 |  | 
 |         const node = this.getData().tree.getNodeByDataIndex(dataIndex); | 
 |         params.treePathInfo = wrapTreePathInfo<SunburstSeriesNodeItemOption['value']>(node, this); | 
 |  | 
 |         return params; | 
 |     } | 
 |  | 
 |     getLevelModel(node: TreeNode) { | 
 |         return this._levelModels && this._levelModels[node.depth]; | 
 |     } | 
 |  | 
 |     static defaultOption: SunburstSeriesOption = { | 
 |         // zlevel: 0, | 
 |         z: 2, | 
 |  | 
 |         // 默认全局居中 | 
 |         center: ['50%', '50%'], | 
 |         radius: [0, '75%'], | 
 |         // 默认顺时针 | 
 |         clockwise: true, | 
 |         startAngle: 90, | 
 |         // 最小角度改为0 | 
 |         minAngle: 0, | 
 |  | 
 |         // If still show when all data zero. | 
 |         stillShowZeroSum: true, | 
 |  | 
 |         // 'rootToNode', 'link', or false | 
 |         nodeClick: 'rootToNode', | 
 |  | 
 |         renderLabelForZeroData: false, | 
 |  | 
 |         label: { | 
 |             // could be: 'radial', 'tangential', or 'none' | 
 |             rotate: 'radial', | 
 |             show: true, | 
 |             opacity: 1, | 
 |             // 'left' is for inner side of inside, and 'right' is for outer | 
 |             // side for inside | 
 |             align: 'center', | 
 |             position: 'inside', | 
 |             distance: 5, | 
 |             silent: true | 
 |         }, | 
 |         itemStyle: { | 
 |             borderWidth: 1, | 
 |             borderColor: 'white', | 
 |             borderType: 'solid', | 
 |             shadowBlur: 0, | 
 |             shadowColor: 'rgba(0, 0, 0, 0.2)', | 
 |             shadowOffsetX: 0, | 
 |             shadowOffsetY: 0, | 
 |             opacity: 1 | 
 |         }, | 
 |  | 
 |         emphasis: { | 
 |             focus: 'descendant' | 
 |         }, | 
 |  | 
 |         blur: { | 
 |             itemStyle: { | 
 |                 opacity: 0.2 | 
 |             }, | 
 |             label: { | 
 |                 opacity: 0.1 | 
 |             } | 
 |         }, | 
 |  | 
 |         // Animation type can be expansion, scale. | 
 |         animationType: 'expansion', | 
 |         animationDuration: 1000, | 
 |         animationDurationUpdate: 500, | 
 |  | 
 |         data: [], | 
 |  | 
 |         /** | 
 |          * Sort order. | 
 |          * | 
 |          * Valid values: 'desc', 'asc', null, or callback function. | 
 |          * 'desc' and 'asc' for descend and ascendant order; | 
 |          * null for not sorting; | 
 |          * example of callback function: | 
 |          * function(nodeA, nodeB) { | 
 |          *     return nodeA.getValue() - nodeB.getValue(); | 
 |          * } | 
 |          */ | 
 |         sort: 'desc' | 
 |     }; | 
 |  | 
 |     getViewRoot() { | 
 |         return this._viewRoot; | 
 |     } | 
 |  | 
 |     resetViewRoot(viewRoot?: TreeNode) { | 
 |         viewRoot | 
 |             ? (this._viewRoot = viewRoot) | 
 |             : (viewRoot = this._viewRoot); | 
 |  | 
 |         const root = this.getRawData().tree.root; | 
 |  | 
 |         if (!viewRoot | 
 |             || (viewRoot !== root && !root.contains(viewRoot)) | 
 |         ) { | 
 |             this._viewRoot = root; | 
 |         } | 
 |     } | 
 |  | 
 |     enableAriaDecal() { | 
 |         enableAriaDecalForTree(this); | 
 |     } | 
 | } | 
 |  | 
 |  | 
 |  | 
 | function completeTreeValue(dataNode: SunburstSeriesNodeItemOption) { | 
 |     // Postorder travel tree. | 
 |     // If value of none-leaf node is not set, | 
 |     // calculate it by suming up the value of all children. | 
 |     let sum = 0; | 
 |  | 
 |     zrUtil.each(dataNode.children, function (child) { | 
 |  | 
 |         completeTreeValue(child); | 
 |  | 
 |         let childValue = child.value; | 
 |         // TODO First value of array must be a number | 
 |         zrUtil.isArray(childValue) && (childValue = childValue[0]); | 
 |         sum += childValue as number; | 
 |     }); | 
 |  | 
 |     let thisValue = dataNode.value as number; | 
 |     if (zrUtil.isArray(thisValue)) { | 
 |         thisValue = thisValue[0]; | 
 |     } | 
 |  | 
 |     if (thisValue == null || isNaN(thisValue)) { | 
 |         thisValue = sum; | 
 |     } | 
 |     // Value should not less than 0. | 
 |     if (thisValue < 0) { | 
 |         thisValue = 0; | 
 |     } | 
 |  | 
 |     zrUtil.isArray(dataNode.value) | 
 |         ? (dataNode.value[0] = thisValue) | 
 |         : (dataNode.value = thisValue); | 
 | } | 
 |  | 
 |  | 
 | export default SunburstSeriesModel; |