| /* |
| * 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 { enableHoverEmphasis, 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/basicTrasition'; |
| |
| 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; |
| |
| enableHoverEmphasis(this, focusOrIndices, emphasisModel.get('blurScope')); |
| } |
| |
| _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') { |
| 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 (typeof rotateType === 'number') { |
| 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; |