| /* |
| * 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' |
| // 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' |
| |
| 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 outter |
| // 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 canbe 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; |