| /* | 
 | * 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 * as graphic from '../../util/graphic'; | 
 | import { toggleHoverEmphasis, SPECIAL_STATES, DISPLAY_STATES } from '../../util/states'; | 
 | import {createTextStyle} from '../../label/labelStyle'; | 
 | import { TreeNode } from '../../data/Tree'; | 
 | import SunburstSeriesModel, { SunburstSeriesNodeItemOption, SunburstSeriesOption } from './SunburstSeries'; | 
 | import GlobalModel from '../../model/Global'; | 
 | import { PathStyleProps } from 'zrender/src/graphic/Path'; | 
 | import { ColorString } from '../../util/types'; | 
 | import Model from '../../model/Model'; | 
 | import { getECData } from '../../util/innerStore'; | 
 | import { getSectorCornerRadius } from '../helper/pieHelper'; | 
 | import {createOrUpdatePatternFromDecal} from '../../util/decal'; | 
 | import ExtensionAPI from '../../core/ExtensionAPI'; | 
 | import { saveOldStyle } from '../../animation/basicTransition'; | 
 |  | 
 | const DEFAULT_SECTOR_Z = 2; | 
 | const DEFAULT_TEXT_Z = 4; | 
 | interface DrawTreeNode extends TreeNode { | 
 |     piece: SunburstPiece | 
 | } | 
 | /** | 
 |  * Sunburstce of Sunburst including Sector, Label, LabelLine | 
 |  */ | 
 | class SunburstPiece extends graphic.Sector { | 
 |  | 
 |     node: TreeNode; | 
 |  | 
 |     private _seriesModel: SunburstSeriesModel; | 
 |     private _ecModel: GlobalModel; | 
 |  | 
 |     constructor(node: TreeNode, seriesModel: SunburstSeriesModel, ecModel: GlobalModel, api: ExtensionAPI) { | 
 |         super(); | 
 |  | 
 |         this.z2 = DEFAULT_SECTOR_Z; | 
 |         this.textConfig = { | 
 |             inside: true | 
 |         }; | 
 |  | 
 |         getECData(this).seriesIndex = seriesModel.seriesIndex; | 
 |  | 
 |         const text = new graphic.Text({ | 
 |             z2: DEFAULT_TEXT_Z, | 
 |             silent: node.getModel<SunburstSeriesNodeItemOption>().get(['label', 'silent']) | 
 |         }); | 
 |         this.setTextContent(text); | 
 |  | 
 |         this.updateData(true, node, seriesModel, ecModel, api); | 
 |     } | 
 |  | 
 |     updateData( | 
 |         firstCreate: boolean, | 
 |         node: TreeNode, | 
 |         // state: 'emphasis' | 'normal' | 'highlight' | 'downplay', | 
 |         seriesModel: SunburstSeriesModel, | 
 |         ecModel: GlobalModel, | 
 |         api: ExtensionAPI | 
 |     ) { | 
 |         this.node = node; | 
 |         (node as DrawTreeNode).piece = this; | 
 |  | 
 |         seriesModel = seriesModel || this._seriesModel; | 
 |         ecModel = ecModel || this._ecModel; | 
 |  | 
 |         const sector = this; | 
 |         getECData(sector).dataIndex = node.dataIndex; | 
 |  | 
 |         const itemModel = node.getModel<SunburstSeriesNodeItemOption>(); | 
 |         const emphasisModel = itemModel.getModel('emphasis'); | 
 |         const layout = node.getLayout(); | 
 |  | 
 |         const sectorShape = zrUtil.extend({}, layout); | 
 |         sectorShape.label = null; | 
 |  | 
 |         const normalStyle = node.getVisual('style') as PathStyleProps; | 
 |         normalStyle.lineJoin = 'bevel'; | 
 |  | 
 |         const decal = node.getVisual('decal'); | 
 |         if (decal) { | 
 |             normalStyle.decal = createOrUpdatePatternFromDecal(decal, api); | 
 |         } | 
 |  | 
 |         const cornerRadius = getSectorCornerRadius(itemModel.getModel('itemStyle'), sectorShape, true); | 
 |         zrUtil.extend(sectorShape, cornerRadius); | 
 |  | 
 |         zrUtil.each(SPECIAL_STATES, function (stateName) { | 
 |             const state = sector.ensureState(stateName); | 
 |             const itemStyleModel = itemModel.getModel([stateName, 'itemStyle']); | 
 |             state.style = itemStyleModel.getItemStyle(); | 
 |             // border radius | 
 |             const cornerRadius = getSectorCornerRadius(itemStyleModel, sectorShape); | 
 |             if (cornerRadius) { | 
 |                 state.shape = cornerRadius; | 
 |             } | 
 |         }); | 
 |  | 
 |         if (firstCreate) { | 
 |             sector.setShape(sectorShape); | 
 |             sector.shape.r = layout.r0; | 
 |             graphic.updateProps( | 
 |                 sector, | 
 |                 { | 
 |                     shape: { | 
 |                         r: layout.r | 
 |                     } | 
 |                 }, | 
 |                 seriesModel, | 
 |                 node.dataIndex | 
 |             ); | 
 |         } | 
 |         else { | 
 |             // Disable animation for gradient since no interpolation method | 
 |             // is supported for gradient | 
 |             graphic.updateProps(sector, { | 
 |                 shape: sectorShape | 
 |             }, seriesModel); | 
 |  | 
 |             saveOldStyle(sector); | 
 |         } | 
 |  | 
 |         sector.useStyle(normalStyle); | 
 |  | 
 |         this._updateLabel(seriesModel); | 
 |  | 
 |         const cursorStyle = itemModel.getShallow('cursor'); | 
 |         cursorStyle && sector.attr('cursor', cursorStyle); | 
 |  | 
 |         this._seriesModel = seriesModel || this._seriesModel; | 
 |         this._ecModel = ecModel || this._ecModel; | 
 |  | 
 |         const focus = emphasisModel.get('focus'); | 
 |  | 
 |         const focusOrIndices = | 
 |             focus === 'ancestor' ? node.getAncestorsIndices() | 
 |             : focus === 'descendant' ? node.getDescendantIndices() | 
 |             : focus; | 
 |  | 
 |         toggleHoverEmphasis(this, focusOrIndices, emphasisModel.get('blurScope'), emphasisModel.get('disabled')); | 
 |     } | 
 |  | 
 |     _updateLabel( | 
 |         seriesModel: SunburstSeriesModel | 
 |     ) { | 
 |         const itemModel = this.node.getModel<SunburstSeriesNodeItemOption>(); | 
 |         const normalLabelModel = itemModel.getModel('label'); | 
 |  | 
 |         const layout = this.node.getLayout(); | 
 |         const angle = layout.endAngle - layout.startAngle; | 
 |  | 
 |         const midAngle = (layout.startAngle + layout.endAngle) / 2; | 
 |         const dx = Math.cos(midAngle); | 
 |         const dy = Math.sin(midAngle); | 
 |  | 
 |         const sector = this; | 
 |         const label = sector.getTextContent(); | 
 |         const dataIndex = this.node.dataIndex; | 
 |         const labelMinAngle = normalLabelModel.get('minAngle') / 180 * Math.PI; | 
 |         const isNormalShown = normalLabelModel.get('show') | 
 |             && !(labelMinAngle != null && Math.abs(angle) < labelMinAngle); | 
 |         label.ignore = !isNormalShown; | 
 |  | 
 |         // TODO use setLabelStyle | 
 |         zrUtil.each(DISPLAY_STATES, (stateName) => { | 
 |  | 
 |             const labelStateModel = stateName === 'normal' ? itemModel.getModel('label') | 
 |                 : itemModel.getModel([stateName, 'label']); | 
 |             const isNormal = stateName === 'normal'; | 
 |  | 
 |             const state = isNormal ? label : label.ensureState(stateName); | 
 |             let text = seriesModel.getFormattedLabel(dataIndex, stateName); | 
 |             if (isNormal) { | 
 |                 text = text || this.node.name; | 
 |             } | 
 |  | 
 |             state.style = createTextStyle(labelStateModel, {}, null, stateName !== 'normal', true); | 
 |             if (text) { | 
 |                 state.style.text = text; | 
 |             } | 
 |             // Not displaying text when angle is too small | 
 |             const isShown = labelStateModel.get('show'); | 
 |             if (isShown != null && !isNormal) { | 
 |                 state.ignore = !isShown; | 
 |             } | 
 |  | 
 |             const labelPosition = getLabelAttr(labelStateModel, 'position'); | 
 |  | 
 |             const sectorState = isNormal ? sector : sector.states[stateName]; | 
 |             const labelColor = sectorState.style.fill as ColorString; | 
 |             sectorState.textConfig = { | 
 |                 outsideFill: labelStateModel.get('color') === 'inherit' ? labelColor : null, | 
 |                 inside: labelPosition !== 'outside' | 
 |             }; | 
 |  | 
 |             let r; | 
 |             const labelPadding = getLabelAttr(labelStateModel, 'distance') || 0; | 
 |             let textAlign = getLabelAttr(labelStateModel, 'align'); | 
 |             if (labelPosition === 'outside') { | 
 |                 r = layout.r + labelPadding; | 
 |                 textAlign = midAngle > Math.PI / 2 ? 'right' : 'left'; | 
 |             } | 
 |             else { | 
 |                 if (!textAlign || textAlign === 'center') { | 
 |                     // Put label in the center if it's a circle | 
 |                     if (angle === 2 * Math.PI && layout.r0 === 0) { | 
 |                         r = 0; | 
 |                     } | 
 |                     else { | 
 |                         r = (layout.r + layout.r0) / 2; | 
 |                     } | 
 |                     textAlign = 'center'; | 
 |                 } | 
 |                 else if (textAlign === 'left') { | 
 |                     r = layout.r0 + labelPadding; | 
 |                     if (midAngle > Math.PI / 2) { | 
 |                         textAlign = 'right'; | 
 |                     } | 
 |                 } | 
 |                 else if (textAlign === 'right') { | 
 |                     r = layout.r - labelPadding; | 
 |                     if (midAngle > Math.PI / 2) { | 
 |                         textAlign = 'left'; | 
 |                     } | 
 |                 } | 
 |             } | 
 |  | 
 |             state.style.align = textAlign; | 
 |             state.style.verticalAlign = getLabelAttr(labelStateModel, 'verticalAlign') || 'middle'; | 
 |  | 
 |             state.x = r * dx + layout.cx; | 
 |             state.y = r * dy + layout.cy; | 
 |  | 
 |             const rotateType = getLabelAttr(labelStateModel, 'rotate'); | 
 |             let rotate = 0; | 
 |             if (rotateType === 'radial') { | 
 |                 rotate = -midAngle; | 
 |                 if (rotate < -Math.PI / 2) { | 
 |                     rotate += Math.PI; | 
 |                 } | 
 |             } | 
 |             else if (rotateType === 'tangential') { | 
 |                 rotate = Math.PI / 2 - midAngle; | 
 |                 if (rotate > Math.PI / 2) { | 
 |                     rotate -= Math.PI; | 
 |                 } | 
 |                 else if (rotate < -Math.PI / 2) { | 
 |                     rotate += Math.PI; | 
 |                 } | 
 |             } | 
 |             else if (zrUtil.isNumber(rotateType)) { | 
 |                 rotate = rotateType * Math.PI / 180; | 
 |             } | 
 |  | 
 |             state.rotation = rotate; | 
 |         }); | 
 |  | 
 |  | 
 |         type LabelOpt = SunburstSeriesOption['label']; | 
 |         function getLabelAttr<T extends keyof LabelOpt>(model: Model<LabelOpt>, name: T): LabelOpt[T] { | 
 |             const stateAttr = model.get(name); | 
 |             if (stateAttr == null) { | 
 |                 return normalLabelModel.get(name) as LabelOpt[T]; | 
 |             } | 
 |             return stateAttr; | 
 |         } | 
 |  | 
 |         label.dirtyStyle(); | 
 |     } | 
 | } | 
 |  | 
 |  | 
 | export default SunburstPiece; |