You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@echarts.apache.org by ov...@apache.org on 2020/09/08 09:54:29 UTC

[incubator-echarts] 06/06: feat: line label animation on the first setOption

This is an automated email from the ASF dual-hosted git repository.

ovilia pushed a commit to branch line-label
in repository https://gitbox.apache.org/repos/asf/incubator-echarts.git

commit d5ea2e818b27561fb01bfd9a3ef823481471bbb6
Author: Ovilia <zw...@gmail.com>
AuthorDate: Tue Sep 8 16:47:13 2020 +0800

    feat: line label animation on the first setOption
---
 src/chart/helper/createClipPathFromCoordSys.ts |  13 +-
 src/chart/line/LineSeries.ts                   |  15 +-
 src/chart/line/LineView.ts                     | 251 ++++++++++++++++++++-----
 test/line-animation.html                       |  22 ++-
 4 files changed, 250 insertions(+), 51 deletions(-)

diff --git a/src/chart/helper/createClipPathFromCoordSys.ts b/src/chart/helper/createClipPathFromCoordSys.ts
index 1eb778b..85695cc 100644
--- a/src/chart/helper/createClipPathFromCoordSys.ts
+++ b/src/chart/helper/createClipPathFromCoordSys.ts
@@ -63,12 +63,20 @@ function createGridClipPath(
     });
 
     if (hasAnimation) {
-        const isHorizontal = cartesian.getBaseAxis().isHorizontal();
+        const baseAxis = cartesian.getBaseAxis();
+        const isHorizontal = baseAxis.isHorizontal();
+        const isAxisInversed = baseAxis.inverse;
+
         if (isHorizontal) {
+            if (isAxisInversed) {
+                clipPath.shape.x += width;
+            }
             clipPath.shape.width = 0;
         }
         else {
-            clipPath.shape.y = y + height;
+            if (!isAxisInversed) {
+                clipPath.shape.y += height;
+            }
             clipPath.shape.height = 0;
         }
 
@@ -82,6 +90,7 @@ function createGridClipPath(
             shape: {
                 width: width,
                 height: height,
+                x: x,
                 y: y
             }
         }, seriesModel, null, done, duringCb);
diff --git a/src/chart/line/LineSeries.ts b/src/chart/line/LineSeries.ts
index 432bf92..fef653d 100644
--- a/src/chart/line/LineSeries.ts
+++ b/src/chart/line/LineSeries.ts
@@ -57,6 +57,10 @@ export interface LineDataItemOption extends SymbolOptionMixin,
     value?: LineDataValue
 }
 
+interface LineEndLabelOption extends LabelOption {
+    valueAnimation: boolean
+}
+
 
 export interface LineSeriesOption extends SeriesOption<LineStateOption, ExtraStateOption & {
     emphasis?: {
@@ -82,7 +86,8 @@ export interface LineSeriesOption extends SeriesOption<LineStateOption, ExtraSta
     // If clip the overflow value
     clip?: boolean
 
-    label?: LabelOption & {showDuringLabel: boolean}
+    label?: LabelOption
+    endLabel?: LineEndLabelOption
 
     lineStyle?: LineStyleOption
 
@@ -138,8 +143,12 @@ class LineSeriesModel extends SeriesModel<LineSeriesOption> {
         clip: true,
 
         label: {
-            position: 'top',
-            showDuringLabel: false
+            position: 'top'
+        },
+
+        endLabel: {
+            show: false,
+            valueAnimation: false
         },
 
         lineStyle: {
diff --git a/src/chart/line/LineView.ts b/src/chart/line/LineView.ts
index 556e602..becee79 100644
--- a/src/chart/line/LineView.ts
+++ b/src/chart/line/LineView.ts
@@ -26,6 +26,7 @@ import SymbolClz from '../helper/Symbol';
 import lineAnimationDiff from './lineAnimationDiff';
 import * as graphic from '../../util/graphic';
 import * as modelUtil from '../../util/model';
+import * as numberUtil from '../../util/number';
 import {ECPolyline, ECPolygon} from './poly';
 import ChartView from '../../view/Chart';
 import {prepareDataCoordInfo, getStackedOnPoint} from './helper';
@@ -37,7 +38,7 @@ import type ExtensionAPI from '../../ExtensionAPI';
 import Cartesian2D from '../../coord/cartesian/Cartesian2D';
 import Polar from '../../coord/polar/Polar';
 import type List from '../../data/List';
-import type { Payload, Dictionary, ColorString, ECElement, DisplayState, ComponentOption } from '../../util/types';
+import type {Payload, Dictionary, ColorString, ECElement, DisplayState} from '../../util/types';
 import type OrdinalScale from '../../scale/Ordinal';
 import type Axis2D from '../../coord/cartesian/Axis2D';
 import { CoordinateSystemClipArea } from '../../coord/CoordinateSystem';
@@ -45,10 +46,11 @@ import { setStatesStylesFromModel, setStatesFlag, enableHoverEmphasis } from '..
 import { getECData } from '../../util/ecData';
 import Displayable from 'zrender/src/graphic/Displayable';
 import {makeInner} from '../../util/model';
-import ComponentModel from '../../model/Component';
 
 const inner = makeInner<{
-    defaultOption: ComponentOption
+    lastSplitId: number,
+    precision: number,
+    isStopped: boolean
 }, graphic.Text>();
 
 type PolarArea = ReturnType<Polar['getArea']>;
@@ -337,23 +339,17 @@ function createLineClipPath(
     seriesModel: LineSeriesModel
 ) {
     if (coordSys.type === 'cartesian2d') {
-        const labelModel = seriesModel.getModel('label');
-        let showDuringLabel = labelModel.get('showDuringLabel');
-
-        const done = showDuringLabel
-            ? () => {
-
-            }
-            : null;
+        const endLabelModel = seriesModel.getModel('endLabel');
+        let showDuringLabel = endLabelModel.get('show');
 
         const during = showDuringLabel
             ? (percent: number, clipRect: graphic.Rect) => {
-                lineView._updateDuringLabel(percent, clipRect, lineView._data);
+                lineView._endLabelOnDuring(percent, clipRect, lineView._data);
             }
             : null;
 
         const isHorizontal = coordSys.getBaseAxis().isHorizontal();
-        const clipPath = createGridClipPath(coordSys, hasAnimation, seriesModel, done, during);
+        const clipPath = createGridClipPath(coordSys, hasAnimation, seriesModel, null, during);
         // Expand clip shape to avoid clipping when line value exceeds axis
         if (!seriesModel.get('clip', true)) {
             const rectShape = clipPath.shape;
@@ -370,13 +366,36 @@ function createLineClipPath(
         return clipPath;
     }
     else {
-        const labelModel = seriesModel.getModel('label');
-        const showDuringLabel = labelModel.get('showDuringLabel');
+        if (__DEV__) {
+            const endLabelModel = seriesModel.getModel('endLabel');
+            let showDuringLabel = endLabelModel.get('show');
+            if (showDuringLabel) {
+                console.warn('showDuringLabel is not supported for lines in polar systems.');
+            }
+        }
         return createPolarClipPath(coordSys, hasAnimation, seriesModel);
     }
 
 }
 
+function getDataItemDetail(coordSys: Cartesian2D, data: List, idx: number) {
+    const xDim = data.mapDimension('x');
+    const yDim = data.mapDimension('y');
+    const x = data.get(xDim, idx);
+    const y = data.get(yDim, idx);
+    const point = coordSys.dataToPoint([x, y]);
+
+    const baseAxis = coordSys.getBaseAxis();
+    const isHorizontal = baseAxis.isHorizontal();
+    const value = isHorizontal ? y : x;
+
+    return {
+        x: point[0],
+        y: point[1],
+        value: typeof value === 'number' ? value : parseFloat(value)
+    };
+}
+
 class LineView extends ChartView {
 
     static readonly type = 'line';
@@ -386,7 +405,7 @@ class LineView extends ChartView {
     _lineGroup: graphic.Group;
     _coordSys: Cartesian2D | Polar;
 
-    _duringLabel: graphic.Text;
+    _endLabel: graphic.Text;
 
     _polyline: ECPolyline;
     _polygon: ECPolygon;
@@ -459,6 +478,10 @@ class LineView extends ChartView {
 
         group.add(lineGroup);
 
+        if (!isCoordSysPolar) {
+            this._initEndLabel(seriesModel, coordSys as Cartesian2D, data, !!oldData);
+        }
+
         // FIXME step not support polar
         const step = !isCoordSysPolar ? seriesModel.get('step') : false;
         let clipShapeForSymbol: PolarArea | Cartesian2DArea;
@@ -493,8 +516,6 @@ class LineView extends ChartView {
                 clipShapeForSymbol
             );
 
-            this._initDuringLabel(seriesModel, data, true);
-
             if (step) {
                 // TODO If stacked series is not step
                 points = turnPointsIntoStep(points, coordSys, step);
@@ -794,6 +815,7 @@ class LineView extends ChartView {
         let isHorizontalOrRadial: boolean;
         let isCoordSysPolar: boolean;
         const baseAxis = coordSys.getBaseAxis();
+        const isAxisInverse = baseAxis.inverse;
         if (coordSys.type === 'cartesian2d') {
             isHorizontalOrRadial = (baseAxis as Axis2D).isHorizontal();
             isCoordSysPolar = false;
@@ -845,7 +867,10 @@ class LineView extends ChartView {
                         current = symbol.y;
                     }
                 }
-                const ratio = end === start ? 0 : (current - start) / (end - start);
+                let ratio = end === start ? 0 : (current - start) / (end - start);
+                if (isAxisInverse) {
+                    ratio = 1 - ratio;
+                }
 
                 let delay;
                 if (typeof seriesDalay === 'function') {
@@ -909,56 +934,192 @@ class LineView extends ChartView {
         });
     }
 
-    _initDuringLabel(
+    _initEndLabel(
         seriesModel: LineSeriesModel,
+        coordSys: Cartesian2D,
         data: List,
         isUpdate: boolean
     ) {
-        const labelModel = seriesModel.getModel('label');
-        const showDuringLabel = labelModel.get('showDuringLabel');
+        const endLabelModel = seriesModel.getModel('endLabel');
+        let showDuringLabel = endLabelModel.get('show');
 
         if (showDuringLabel) {
-            if (!this._duringLabel) {
-                this._duringLabel = new graphic.Text({
+            if (!this._endLabel) {
+                this._endLabel = new graphic.Text({
                     style: {
-                        text: 'abcd'
-                    }
+                        text: '',
+                        verticalAlign: 'middle'
+                    },
+                    ignore: true,
+                    z2: 200 // should be higher than item symbol
                 });
-                this.group.add(this._duringLabel);
+                this.group.add(this._endLabel);
             }
 
-            // const defaultTextGetter = (values: ParsedValue | ParsedValue[]) => {
-            //     return getDefaultLabel(seriesModel.getData(), 0, values);
-            // };
+            const precisionOption = endLabelModel.get('precision');
+            const precision: number = !precisionOption || precisionOption === 'auto'
+                ? 0
+                : precisionOption;
+            const host = inner(this._endLabel);
+            host.precision = precision;
+            host.isStopped = false;
+
+            if (isUpdate) {
+                let duration = seriesModel.get('animationDurationUpdate');
+                if (typeof duration === 'function') {
+                    duration = duration(null);
+                }
 
-            // (isUpdate ? updateLabel : initLabel)(
-            //     this._duringLabel, data, 0, labelModel, seriesModel, seriesModel, defaultTextGetter
-            // );
+                // Find last non-NaN data to display data
+                let lastFound = false;
+                for (let idx = data.count() - 1; idx >= 0; --idx) {
+                    const info = getDataItemDetail(coordSys, data, idx);
+                    if (!isNaN(info.value)) {
+                        this._endLabel.attr({
+                            x: info.x + 10,
+                            y: info.y,
+                            style: {
+                                text: numberUtil.round(info.value, precision) + ''
+                            },
+                            ignore: false
+                        });
+                        lastFound = true;
+                        break;
+                    }
+                }
+
+                if (!lastFound) {
+                    this._endLabel.attr({
+                        ignore: true
+                    });
+                }
+            }
         }
     }
 
-    _updateDuringLabel(
+    _endLabelOnDuring(
         percent: number,
         clipRect: graphic.Rect,
         data: List
     ) {
-        console.log(percent, clipRect.shape)
-        if (this._duringLabel) {
-            this._duringLabel.attr({
-                x: clipRect.shape.x + clipRect.shape.width + 10,
-                y: 0
-            });
+        if (this._endLabel) {
+            const host = inner(this._endLabel);
+            if (host.isStopped) {
+                return;
+            }
+
+            const seriesModel = data.hostModel;
+            const connectNulls = seriesModel.get('connectNulls');
 
-            const baseAxis = this._coordSys.getBaseAxis();
+            const coordSys = this._coordSys as Cartesian2D;
+            const baseAxis = coordSys.getBaseAxis();
+            const isHorizontal = baseAxis.isHorizontal();
+            const isBaseInversed = baseAxis.inverse;
 
             let splitFound = false;
-            let left = null;
+            let lx: number = null;
+            let ly: number = null;
+            let lValue: number = null;
+            const that = this;
+
+            const cx = clipRect.shape.x;
+            const cy = clipRect.shape.y;
+            const cx2 = clipRect.shape.width + cx;
+            const cy2 = clipRect.shape.height + cy;
+
+            let lastNonNullId: number = null;
             data.each(function (idx) {
-                const right = data.getValues(idx);
-                console.log(right);
+                const detail = getDataItemDetail(coordSys, data, idx);
+                if (!isNaN(detail.value)) {
+                    lastNonNullId = idx;
+                }
             });
 
-            const host = inner(this._duringLabel);
+            data.each(function (idx) {
+                if (splitFound) {
+                    return;
+                }
+
+                const rightItem = getDataItemDetail(coordSys, data, idx);
+                const rx = rightItem.x;
+                const ry = rightItem.y;
+                const rValue = rightItem.value;
+
+                const clipPos = isHorizontal
+                    ? (isBaseInversed ? cx : cx2)
+                    : (isBaseInversed ? cy2 : cy);
+
+                // Find the split point on the two sides of current clipRect
+                const valueNotNull = !isNaN(lValue)
+                    && (!connectNulls || !isNaN(rValue));
+                const inClipRange = isHorizontal
+                    ? valueNotNull && lx != null
+                        && (isBaseInversed
+                            ? lx > cx && rx <= cx
+                            : lx < cx2 && rx >= cx2
+                        )
+                    : valueNotNull && ly != null
+                        && (isBaseInversed
+                            ? ly < cy2 && ry >= cy2
+                            : ly > cy && ry <= cy
+                        );
+
+                let ratio = isHorizontal
+                    ? (rx === lx ? 0 : (clipPos - lx) / (rx - lx))
+                    : (ry === ly ? 0 : (clipPos - ly) / (ry - ly));
+
+                const splitValue = ratio * (rValue - lValue) + lValue;
+                const splitStr = numberUtil.round(splitValue, host.precision) + '';
+                const textX = isHorizontal
+                    ? (clipPos + 10 * (isBaseInversed ? -1 : 1))
+                    : (ratio * (rx - lx) + lx);
+                const textY = isHorizontal
+                    ? (ratio * (ry - ly) + ly)
+                    : (clipPos + 10 * (isBaseInversed ? -1 : 1));
+                const align = isHorizontal
+                    ? isBaseInversed ? 'right' : 'left'
+                    : 'center';
+                const verticalAlign = isHorizontal
+                    ? 'middle'
+                    : (isBaseInversed ? 'bottom' : 'top');
+
+                const isAnimationNotStarted = idx === 0
+                && (isHorizontal
+                    ? (isBaseInversed ? rx < clipPos : rx > clipPos)
+                    : (isBaseInversed ? ry > clipPos : ry < clipPos)
+                );
+                const isAnimationFinished = idx === lastNonNullId
+                    && (isHorizontal
+                        ? (isBaseInversed ? rx > clipPos : rx < clipPos)
+                        : (isBaseInversed ? ry < clipPos : ry > clipPos)
+                    );
+
+                if (inClipRange || isAnimationFinished || isAnimationNotStarted) {
+                    if (connectNulls || !isNaN(lValue) && !isNaN(rValue)) {
+                        that._endLabel.attr({
+                            x: textX,
+                            y: textY,
+                            style: {
+                                text: splitStr,
+                                align: align,
+                                verticalAlign: verticalAlign
+                            },
+                            ignore: false
+                        });
+                    }
+
+                    splitFound = true;
+                }
+                else if (!connectNulls || !isNaN(rValue)) {
+                    lx = rx;
+                    ly = ry;
+                    lValue = rValue;
+                }
+
+                if (isAnimationFinished) {
+                    host.isStopped = true;
+                }
+            });
         }
     }
 
diff --git a/test/line-animation.html b/test/line-animation.html
index f89818c..71fbd50 100644
--- a/test/line-animation.html
+++ b/test/line-animation.html
@@ -96,10 +96,30 @@ under the License.
                         left: '50%',
                         top: 0
                     }, {
-                        subtext: '(axis inversed)',
+                        subtext: 'axis inversed',
                         textAlign: 'center',
                         left: '50%',
                         top: '52%'
+                    }, {
+                        text: 'left-to-right',
+                        left: '25%',
+                        textAlign: 'center',
+                        top: 70
+                    }, {
+                        text: 'bottom-to-top',
+                        left: '75%',
+                        textAlign: 'center',
+                        top: 70
+                    }, {
+                        text: 'right-to-left',
+                        left: '25%',
+                        textAlign: 'center',
+                        top: '55%'
+                    }, {
+                        text: 'top-to-bottom',
+                        left: '75%',
+                        textAlign: 'center',
+                        top: '55%'
                     }],
                     xAxis: [{
                         data: xData


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org
For additional commands, e-mail: commits-help@echarts.apache.org