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