| /* | |
| * 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 { extend, retrieve3 } from 'zrender/src/core/util'; | |
| import * as graphic from '../../util/graphic'; | |
| import { setStatesStylesFromModel, toggleHoverEmphasis } from '../../util/states'; | |
| import ChartView from '../../view/Chart'; | |
| import GlobalModel from '../../model/Global'; | |
| import ExtensionAPI from '../../core/ExtensionAPI'; | |
| import { Payload, ColorString } from '../../util/types'; | |
| import SeriesData from '../../data/SeriesData'; | |
| import PieSeriesModel, {PieDataItemOption} from './PieSeries'; | |
| import labelLayout from './labelLayout'; | |
| import { setLabelLineStyle, getLabelLineStatesModels } from '../../label/labelGuideHelper'; | |
| import { setLabelStyle, getLabelStatesModels } from '../../label/labelStyle'; | |
| import { getSectorCornerRadius } from '../helper/pieHelper'; | |
| import { saveOldStyle } from '../../animation/basicTransition'; | |
| import { getBasicPieLayout } from './pieLayout'; | |
| /** | |
| * Piece of pie including Sector, Label, LabelLine | |
| */ | |
| class PiePiece extends graphic.Sector { | |
| constructor(data: SeriesData, idx: number, startAngle: number) { | |
| super(); | |
| this.z2 = 2; | |
| const text = new graphic.Text(); | |
| this.setTextContent(text); | |
| this.updateData(data, idx, startAngle, true); | |
| } | |
| updateData(data: SeriesData, idx: number, startAngle?: number, firstCreate?: boolean): void { | |
| const sector = this; | |
| const seriesModel = data.hostModel as PieSeriesModel; | |
| const itemModel = data.getItemModel<PieDataItemOption>(idx); | |
| const emphasisModel = itemModel.getModel('emphasis'); | |
| const layout = data.getItemLayout(idx) as graphic.Sector['shape']; | |
| // cornerRadius & innerCornerRadius doesn't exist in the item layout. Use `0` if null value is specified. | |
| // see `setItemLayout` in `pieLayout.ts`. | |
| const sectorShape = extend( | |
| getSectorCornerRadius(itemModel.getModel('itemStyle'), layout, true), | |
| layout | |
| ); | |
| // Ignore NaN data. | |
| if (isNaN(sectorShape.startAngle)) { | |
| // Use NaN shape to avoid drawing shape. | |
| sector.setShape(sectorShape); | |
| return; | |
| } | |
| if (firstCreate) { | |
| sector.setShape(sectorShape); | |
| const animationType = seriesModel.getShallow('animationType'); | |
| if (seriesModel.ecModel.ssr) { | |
| // Use scale animation in SSR mode(opacity?) | |
| // Because CSS SVG animation doesn't support very customized shape animation. | |
| graphic.initProps(sector, { | |
| scaleX: 0, | |
| scaleY: 0 | |
| }, seriesModel, { dataIndex: idx, isFrom: true}); | |
| sector.originX = sectorShape.cx; | |
| sector.originY = sectorShape.cy; | |
| } | |
| else if (animationType === 'scale') { | |
| sector.shape.r = layout.r0; | |
| graphic.initProps(sector, { | |
| shape: { | |
| r: layout.r | |
| } | |
| }, seriesModel, idx); | |
| } | |
| // Expansion | |
| else { | |
| if (startAngle != null) { | |
| sector.setShape({ startAngle, endAngle: startAngle }); | |
| graphic.initProps(sector, { | |
| shape: { | |
| startAngle: layout.startAngle, | |
| endAngle: layout.endAngle | |
| } | |
| }, seriesModel, idx); | |
| } | |
| else { | |
| sector.shape.endAngle = layout.startAngle; | |
| graphic.updateProps(sector, { | |
| shape: { | |
| endAngle: layout.endAngle | |
| } | |
| }, seriesModel, idx); | |
| } | |
| } | |
| } | |
| else { | |
| saveOldStyle(sector); | |
| // Transition animation from the old shape | |
| graphic.updateProps(sector, { | |
| shape: sectorShape | |
| }, seriesModel, idx); | |
| } | |
| sector.useStyle(data.getItemVisual(idx, 'style')); | |
| setStatesStylesFromModel(sector, itemModel); | |
| const midAngle = (layout.startAngle + layout.endAngle) / 2; | |
| const offset = seriesModel.get('selectedOffset'); | |
| const dx = Math.cos(midAngle) * offset; | |
| const dy = Math.sin(midAngle) * offset; | |
| const cursorStyle = itemModel.getShallow('cursor'); | |
| cursorStyle && sector.attr('cursor', cursorStyle); | |
| this._updateLabel(seriesModel, data, idx); | |
| sector.ensureState('emphasis').shape = extend({ | |
| r: layout.r + (emphasisModel.get('scale') | |
| ? (emphasisModel.get('scaleSize') || 0) : 0) | |
| }, getSectorCornerRadius(emphasisModel.getModel('itemStyle'), layout)); | |
| extend(sector.ensureState('select'), { | |
| x: dx, | |
| y: dy, | |
| shape: getSectorCornerRadius(itemModel.getModel(['select', 'itemStyle']), layout) | |
| }); | |
| extend(sector.ensureState('blur'), { | |
| shape: getSectorCornerRadius(itemModel.getModel(['blur', 'itemStyle']), layout) | |
| }); | |
| const labelLine = sector.getTextGuideLine(); | |
| const labelText = sector.getTextContent(); | |
| labelLine && extend(labelLine.ensureState('select'), { | |
| x: dx, | |
| y: dy | |
| }); | |
| // TODO: needs dx, dy in zrender? | |
| extend(labelText.ensureState('select'), { | |
| x: dx, | |
| y: dy | |
| }); | |
| toggleHoverEmphasis( | |
| this, emphasisModel.get('focus'), emphasisModel.get('blurScope'), emphasisModel.get('disabled') | |
| ); | |
| } | |
| private _updateLabel(seriesModel: PieSeriesModel, data: SeriesData, idx: number): void { | |
| const sector = this; | |
| const itemModel = data.getItemModel<PieDataItemOption>(idx); | |
| const labelLineModel = itemModel.getModel('labelLine'); | |
| const style = data.getItemVisual(idx, 'style'); | |
| const visualColor = style && style.fill as ColorString; | |
| const visualOpacity = style && style.opacity; | |
| setLabelStyle( | |
| sector, | |
| getLabelStatesModels(itemModel), | |
| { | |
| labelFetcher: data.hostModel as PieSeriesModel, | |
| labelDataIndex: idx, | |
| inheritColor: visualColor, | |
| defaultOpacity: visualOpacity, | |
| defaultText: seriesModel.getFormattedLabel(idx, 'normal') | |
| || data.getName(idx) | |
| } | |
| ); | |
| const labelText = sector.getTextContent(); | |
| // Set textConfig on sector. | |
| sector.setTextConfig({ | |
| // reset position, rotation | |
| position: null, | |
| rotation: null | |
| }); | |
| // Make sure update style on labelText after setLabelStyle. | |
| // Because setLabelStyle will replace a new style on it. | |
| labelText.attr({ | |
| z2: 10 | |
| }); | |
| const labelPosition = seriesModel.get(['label', 'position']); | |
| if (labelPosition !== 'outside' && labelPosition !== 'outer') { | |
| sector.removeTextGuideLine(); | |
| } | |
| else { | |
| let polyline = this.getTextGuideLine(); | |
| if (!polyline) { | |
| polyline = new graphic.Polyline(); | |
| this.setTextGuideLine(polyline); | |
| } | |
| // Default use item visual color | |
| setLabelLineStyle(this, getLabelLineStatesModels(itemModel), { | |
| stroke: visualColor, | |
| opacity: retrieve3(labelLineModel.get(['lineStyle', 'opacity']), visualOpacity, 1) | |
| }); | |
| } | |
| } | |
| } | |
| // Pie view | |
| class PieView extends ChartView { | |
| static type = 'pie'; | |
| ignoreLabelLineUpdate = true; | |
| private _data: SeriesData; | |
| private _emptyCircleSector: graphic.Sector; | |
| render(seriesModel: PieSeriesModel, ecModel: GlobalModel, api: ExtensionAPI, payload: Payload): void { | |
| const data = seriesModel.getData(); | |
| const oldData = this._data; | |
| const group = this.group; | |
| let startAngle: number; | |
| // First render | |
| if (!oldData && data.count() > 0) { | |
| let shape = data.getItemLayout(0) as graphic.Sector['shape']; | |
| for (let s = 1; isNaN(shape && shape.startAngle) && s < data.count(); ++s) { | |
| shape = data.getItemLayout(s); | |
| } | |
| if (shape) { | |
| startAngle = shape.startAngle; | |
| } | |
| } | |
| // remove empty-circle if it exists | |
| if (this._emptyCircleSector) { | |
| group.remove(this._emptyCircleSector); | |
| } | |
| // when all data are filtered, show lightgray empty circle | |
| if (data.count() === 0 && seriesModel.get('showEmptyCircle')) { | |
| const sector = new graphic.Sector({ | |
| shape: getBasicPieLayout(seriesModel, api) | |
| }); | |
| sector.useStyle(seriesModel.getModel('emptyCircleStyle').getItemStyle()); | |
| this._emptyCircleSector = sector; | |
| group.add(sector); | |
| } | |
| data.diff(oldData) | |
| .add(function (idx) { | |
| const piePiece = new PiePiece(data, idx, startAngle); | |
| data.setItemGraphicEl(idx, piePiece); | |
| group.add(piePiece); | |
| }) | |
| .update(function (newIdx, oldIdx) { | |
| const piePiece = oldData.getItemGraphicEl(oldIdx) as PiePiece; | |
| piePiece.updateData(data, newIdx, startAngle); | |
| piePiece.off('click'); | |
| group.add(piePiece); | |
| data.setItemGraphicEl(newIdx, piePiece); | |
| }) | |
| .remove(function (idx) { | |
| const piePiece = oldData.getItemGraphicEl(idx); | |
| graphic.removeElementWithFadeOut(piePiece, seriesModel, idx); | |
| }) | |
| .execute(); | |
| labelLayout(seriesModel); | |
| // Always use initial animation. | |
| if (seriesModel.get('animationTypeUpdate') !== 'expansion') { | |
| this._data = data; | |
| } | |
| } | |
| dispose() {} | |
| containPoint(point: number[], seriesModel: PieSeriesModel): boolean { | |
| const data = seriesModel.getData(); | |
| const itemLayout = data.getItemLayout(0); | |
| if (itemLayout) { | |
| const dx = point[0] - itemLayout.cx; | |
| const dy = point[1] - itemLayout.cy; | |
| const radius = Math.sqrt(dx * dx + dy * dy); | |
| return radius <= itemLayout.r && radius >= itemLayout.r0; | |
| } | |
| } | |
| } | |
| export default PieView; |