This is an automated email from the ASF dual-hosted git repository. shenyi pushed a commit to branch label-enhancement in repository https://gitbox.apache.org/repos/asf/incubator-echarts.git
commit 34e4cb0f6a282b493abef79426cc09083780e8f7 Author: pissang <bm2736...@gmail.com> AuthorDate: Thu May 28 13:39:34 2020 +0800 feat: label and labelLine animation --- src/chart/pie/PieView.ts | 31 ++++------- src/chart/pie/labelLayout.ts | 5 ++ src/echarts.ts | 15 +++--- src/label/LabelManager.ts | 118 ++++++++++++++++++++++++++++++++++-------- src/label/labelGuideHelper.ts | 2 +- 5 files changed, 120 insertions(+), 51 deletions(-) diff --git a/src/chart/pie/PieView.ts b/src/chart/pie/PieView.ts index 0f2865a..8187c6a 100644 --- a/src/chart/pie/PieView.ts +++ b/src/chart/pie/PieView.ts @@ -136,9 +136,7 @@ class PiePiece extends graphic.Sector { const cursorStyle = itemModel.getShallow('cursor'); cursorStyle && sector.attr('cursor', cursorStyle); - // Label and text animation should be applied only for transition type animation when update - const withAnimation = !firstCreate && animationTypeUpdate === 'transition'; - this._updateLabel(data, idx, withAnimation); + this._updateLabel(data, idx); const emphasisState = sector.ensureState('emphasis'); emphasisState.shape = { @@ -165,12 +163,11 @@ class PiePiece extends graphic.Sector { (sector as ECElement).selected = seriesModel.isSelected(data.getName(idx)); } - private _updateLabel(data: List, idx: number, withAnimation: boolean): void { + private _updateLabel(data: List, idx: number): void { const sector = this; const labelLine = sector.getTextGuideLine(); const labelText = sector.getTextContent(); - const seriesModel = data.hostModel; const itemModel = data.getItemModel<PieDataItemOption>(idx); const layout = data.getItemLayout(idx); const labelLayout = layout.label; @@ -225,25 +222,15 @@ class PiePiece extends graphic.Sector { outsideFill: visualColor }); - const targetTextPos = { + labelLine.attr({ + shape: targetLineShape + }); + // Make sure update style on labelText after setLabelStyle. + // Because setLabelStyle will replace a new style on it. + labelText.attr({ x: labelLayout.x, y: labelLayout.y - }; - if (withAnimation) { - graphic.updateProps(labelLine, { - shape: targetLineShape - }, seriesModel, idx); - - graphic.updateProps(labelText, targetTextPos, seriesModel, idx); - } - else { - labelLine.attr({ - shape: targetLineShape - }); - // Make sure update style on labelText after setLabelStyle. - // Because setLabelStyle will replace a new style on it. - labelText.attr(targetTextPos); - } + }); labelText.attr({ rotation: labelLayout.rotation, diff --git a/src/chart/pie/labelLayout.ts b/src/chart/pie/labelLayout.ts index 04542b5..d1da84d 100644 --- a/src/chart/pie/labelLayout.ts +++ b/src/chart/pie/labelLayout.ts @@ -323,6 +323,11 @@ export default function ( const text = seriesModel.getFormattedLabel(idx, 'normal') || data.getName(idx); const textRect = textContain.getBoundingRect(text, font, textAlign, 'top'); + // Text has a default 1px stroke. Exclude this. + textRect.x -= 1; + textRect.y -= 1; + textRect.width += 2.1; + textRect.height += 2.1; const isLabelInside = labelPosition === 'inside' || labelPosition === 'inner'; if (labelPosition === 'center') { diff --git a/src/echarts.ts b/src/echarts.ts index 6723c9d..3a68ce7 100644 --- a/src/echarts.ts +++ b/src/echarts.ts @@ -1097,6 +1097,7 @@ class ECharts extends Eventful { const labelManager = this._labelManager; labelManager.updateLayoutConfig(this._api); labelManager.layout(); + labelManager.animateLabels(); } appendData(params: { @@ -1722,16 +1723,20 @@ class ECharts extends Eventful { // Add labels. labelManager.addLabelsOfSeries(chartView); - // NOTE: Update states after label is added. - // Because in LabelManager#addLabel. It will cache the properties(transform, textConfig) of label. - // We need to cache the normal state. Not other states. - updateStates(seriesModel, chartView); }); scheduler.unfinished = unfinished || scheduler.unfinished; labelManager.updateLayoutConfig(api); labelManager.layout(); + labelManager.animateLabels(); + + ecModel.eachSeries(function (seriesModel) { + const chartView = ecIns._chartsMap[seriesModel.__viewId]; + // NOTE: Update states after label is updated. + // label should be in normal status when layouting. + updateStates(seriesModel, chartView); + }); // If use hover layer // TODO @@ -1877,8 +1882,6 @@ class ECharts extends Eventful { states.push('emphasis'); } el.useStates(states); - // el.toggleState('select', (el as ECElement).selected); - // el.toggleState('emphasis', (el as ECElement).highlighted); } }); }; diff --git a/src/label/LabelManager.ts b/src/label/LabelManager.ts index 8d203e4..3cbdef5 100644 --- a/src/label/LabelManager.ts +++ b/src/label/LabelManager.ts @@ -25,7 +25,9 @@ import { Point, BoundingRect, getECData, - Polyline + Polyline, + updateProps, + initProps } from '../util/graphic'; import { MatrixArray } from 'zrender/src/core/matrix'; import ExtensionAPI from '../ExtensionAPI'; @@ -38,10 +40,13 @@ import { } from '../util/types'; import { parsePercent } from '../util/number'; import ChartView from '../view/Chart'; -import { ElementTextConfig, ElementTextGuideLineConfig } from 'zrender/src/Element'; +import { ElementTextConfig } from 'zrender/src/Element'; import { RectLike } from 'zrender/src/core/BoundingRect'; import Transformable from 'zrender/src/core/Transformable'; import { updateLabelGuideLine } from './labelGuideHelper'; +import SeriesModel from '../model/Series'; +import { makeInner } from '../util/model'; +import { retrieve2, guid, each } from 'zrender/src/core/util'; interface DisplayedLabelItem { label: ZRText @@ -56,7 +61,7 @@ interface LabelLayoutDesc { label: ZRText labelGuide: Polyline - seriesIndex: number + seriesModel: SeriesModel dataIndex: number layoutOption: LabelLayoutOptionCallback | LabelLayoutOption @@ -100,7 +105,7 @@ function prepareLayoutCallbackParams(labelItem: LabelLayoutDesc): LabelLayoutOpt const label = labelItem.label; return { dataIndex: labelItem.dataIndex, - seriesIndex: labelItem.seriesIndex, + seriesIndex: labelItem.seriesModel.seriesIndex, text: labelItem.label.style.text, rect: labelItem.hostRect, labelRect: labelAttr.rect, @@ -115,26 +120,38 @@ const LABEL_OPTION_TO_STYLE_KEYS = ['align', 'verticalAlign', 'width', 'height'] const dummyTransformable = new Transformable(); +const labelAnimationStore = makeInner<{ + oldLayout: { + x: number, + y: number, + rotation: number + } +}, ZRText>(); + +const labelLineAnimationStore = makeInner<{ + oldLayout: { + points: number[][] + } +}, Polyline>(); + class LabelManager { private _labelList: LabelLayoutDesc[] = []; + private _chartViewList: ChartView[] = []; constructor() {} clearLabels() { this._labelList = []; + this._chartViewList = []; } /** * Add label to manager - * @param dataIndex - * @param seriesIndex - * @param label - * @param layoutOption */ addLabel( dataIndex: number, - seriesIndex: number, + seriesModel: SeriesModel, label: ZRText, layoutOption: LabelLayoutDesc['layoutOption'] ) { @@ -171,7 +188,7 @@ class LabelManager { label, labelGuide: labelGuide, - seriesIndex, + seriesModel, dataIndex, layoutOption, @@ -215,6 +232,8 @@ class LabelManager { } addLabelsOfSeries(chartView: ChartView) { + this._chartViewList.push(chartView); + const seriesModel = chartView.__model; const layoutOption = seriesModel.get('labelLayout'); chartView.group.traverse((child) => { @@ -226,7 +245,7 @@ class LabelManager { const textEl = child.getTextContent(); const dataIndex = getECData(child).dataIndex; if (textEl && dataIndex != null) { - this.addLabel(dataIndex, seriesModel.seriesIndex, textEl, layoutOption); + this.addLabel(dataIndex, seriesModel, textEl, layoutOption); } }); } @@ -251,6 +270,7 @@ class LabelManager { } layoutOption = layoutOption || {}; + if (hostEl) { hostEl.setTextConfig({ // Force to set local false. @@ -288,10 +308,7 @@ class LabelManager { for (let k = 0; k < LABEL_OPTION_TO_STYLE_KEYS.length; k++) { const key = LABEL_OPTION_TO_STYLE_KEYS[k]; - label.setStyle( - key, - layoutOption[key] != null ? layoutOption[key] : defaultLabelAttr.style[key] - ); + label.setStyle(key, layoutOption[key] != null ? layoutOption[key] : defaultLabelAttr.style[key]); } labelItem.overlap = layoutOption.overlap; @@ -325,9 +342,6 @@ class LabelManager { const globalRect = localRect.clone(); globalRect.applyTransform(transform); - // Text has a default 1px stroke. Exclude this. - globalRect.width -= 3; - globalRect.height -= 3; let obb = isAxisAligned ? new OrientedBoundingRect(localRect, transform) : null; let overlapped = false; @@ -362,14 +376,10 @@ class LabelManager { const labelGuide = labelItem.labelGuide; // TODO Callback to determine if this overlap should be handled? if (overlapped) { - // label.setStyle({ opacity: 0.1 }); - // label.z = 0; label.hide(); labelGuide && labelGuide.hide(); } else { - // TODO Restore z - // label.setStyle({ opacity: 1 }); label.attr('ignore', labelItem.defaultAttr.ignore); labelGuide && labelGuide.attr('ignore', labelItem.defaultAttr.labelGuideIgnore); @@ -391,6 +401,70 @@ class LabelManager { ); } } + + animateLabels() { + each(this._chartViewList, function (chartView) { + const seriesModel = chartView.__model; + if (!seriesModel.isAnimationEnabled()) { + return; + } + + chartView.group.traverse((child) => { + if (child.ignore) { + return true; // Stop traverse descendants. + } + + // Only support label being hosted on graphic elements. + const textEl = child.getTextContent(); + const guideLine = child.getTextGuideLine(); + + if (textEl && !textEl.ignore && !textEl.invisible) { + const layoutStore = labelAnimationStore(textEl); + const oldLayout = layoutStore.oldLayout; + const newProps = { + x: textEl.x, + y: textEl.y, + rotation: textEl.rotation + }; + if (!oldLayout) { + textEl.attr(newProps); + const oldOpacity = retrieve2(textEl.style.opacity, 1); + // Fade in animation + textEl.style.opacity = 0; + initProps(textEl, { + style: { opacity: oldOpacity } + }, seriesModel); + } + else { + textEl.attr(oldLayout); + updateProps(textEl, newProps, seriesModel); + } + layoutStore.oldLayout = newProps; + } + + if (guideLine && !guideLine.ignore && !guideLine.invisible) { + const layoutStore = labelLineAnimationStore(guideLine); + const oldLayout = layoutStore.oldLayout; + const newLayout = { points: guideLine.shape.points }; + if (!oldLayout) { + guideLine.setShape(newLayout); + guideLine.style.strokePercent = 0; + initProps(guideLine, { + style: { strokePercent: 1 } + }, seriesModel); + } + else { + guideLine.attr({ shape: oldLayout }); + updateProps(guideLine, { + shape: newLayout + }, seriesModel); + } + + layoutStore.oldLayout = newLayout; + } + }); + }); + } } diff --git a/src/label/labelGuideHelper.ts b/src/label/labelGuideHelper.ts index b20511a..da4848b 100644 --- a/src/label/labelGuideHelper.ts +++ b/src/label/labelGuideHelper.ts @@ -346,7 +346,7 @@ export function updateLabelGuideLine( for (let i = 0; i < searchSpace.length; i++) { const candidate = searchSpace[i]; getCandidateAnchor(candidate, 0, labelRect, pt0, dir); - Point.scaleAndAdd(pt1, pt0, dir, labelGuideConfig.len); + Point.scaleAndAdd(pt1, pt0, dir, labelGuideConfig.len == null ? 15 : labelGuideConfig.len); const dist = anchorPoint ? anchorPoint.distance(pt1) : (target instanceof Path --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@echarts.apache.org For additional commands, e-mail: commits-h...@echarts.apache.org