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