blob: b5cfd99684ec25bb6fb6481f54de11235563f182 [file] [log] [blame]
/*
* 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;