You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@echarts.apache.org by sh...@apache.org on 2020/07/22 13:17:15 UTC

[incubator-echarts] branch optimize-style updated: feat(timeline): optimize timeline style. add progress config

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

shenyi pushed a commit to branch optimize-style
in repository https://gitbox.apache.org/repos/asf/incubator-echarts.git


The following commit(s) were added to refs/heads/optimize-style by this push:
     new 459a9d8  feat(timeline): optimize timeline style. add progress config
459a9d8 is described below

commit 459a9d8336da7efcd132dc892cf23158cd5ad1f5
Author: pissang <bm...@gmail.com>
AuthorDate: Wed Jul 22 21:16:35 2020 +0800

    feat(timeline): optimize timeline style. add progress config
---
 src/component/timeline/SliderTimelineModel.ts |  46 +++++---
 src/component/timeline/SliderTimelineView.ts  | 147 ++++++++++++++++++--------
 src/component/timeline/TimelineModel.ts       |  15 +++
 src/echarts.ts                                |  62 ++++++-----
 test/timeline-finance.html                    |   6 +-
 5 files changed, 181 insertions(+), 95 deletions(-)

diff --git a/src/component/timeline/SliderTimelineModel.ts b/src/component/timeline/SliderTimelineModel.ts
index 2791773..e6c175c 100644
--- a/src/component/timeline/SliderTimelineModel.ts
+++ b/src/component/timeline/SliderTimelineModel.ts
@@ -48,13 +48,13 @@ class SliderTimelineModel extends TimelineModel {
             trigger: 'item'                 // data item may also have tootip attr.
         },
 
-        symbol: 'emptyCircle',
-        symbolSize: 10,
+        symbol: 'circle',
+        symbolSize: 12,
 
         lineStyle: {
             show: true,
             width: 2,
-            color: '#304654'
+            color: '#A4BED7'
         },
         label: {                            // 文本标签
             position: 'auto',           // auto left right top bottom
@@ -66,19 +66,24 @@ class SliderTimelineModel extends TimelineModel {
             rotate: 0,
             // formatter: null,
             // 其余属性默认使用全局文本样式,详见TEXTSTYLE
-            color: '#304654'
+            color: '#A4BED7'
         },
         itemStyle: {
-            color: '#304654',
+            color: '#A4BED7',
             borderWidth: 1
         },
 
         checkpointStyle: {
             symbol: 'circle',
-            symbolSize: 13,
-            color: '#c23531',
-            borderWidth: 5,
-            borderColor: 'rgba(194,53,49, 0.5)',
+            symbolSize: 15,
+            color: '#316bf3',
+            borderColor: '#fff',
+            borderWidth: 2,
+            shadowBlur: 2,
+            shadowOffsetX: 1,
+            shadowOffsetY: 1,
+            shadowColor: 'rgba(0, 0, 0, 0.3)',
+            // borderColor: 'rgba(194,53,49, 0.5)',
             animation: true,
             animationDuration: 300,
             animationEasing: 'quinticInOut'
@@ -97,28 +102,37 @@ class SliderTimelineModel extends TimelineModel {
             nextIcon: 'path://M18.6,50.8l22.5-22.5c0.2-0.2,0.3-0.4,0.3-0.7c0-0.3-0.1-0.5-0.3-0.7L18.7,4.4c-0.1-0.1-0.2-0.3-0.2-0.5 c0-0.4,0.3-0.8,0.8-0.8c0.2,0,0.5,0.1,0.6,0.3l23.5,23.5l0,0c0.2,0.2,0.3,0.4,0.3,0.7c0,0.3-0.1,0.5-0.3,0.7l-0.1,0.1L19.7,52 c-0.1,0.1-0.3,0.2-0.5,0.2c-0.4,0-0.8-0.3-0.8-0.8C18.4,51.2,18.5,51,18.6,50.8z', // jshint ignore:line
             prevIcon: 'path://M43,52.8L20.4,30.3c-0.2-0.2-0.3-0.4-0.3-0.7c0-0.3,0.1-0.5,0.3-0.7L42.9,6.4c0.1-0.1,0.2-0.3,0.2-0.5 c0-0.4-0.3-0.8-0.8-0.8c-0.2,0-0.5,0.1-0.6,0.3L18.3,28.8l0,0c-0.2,0.2-0.3,0.4-0.3,0.7c0,0.3,0.1,0.5,0.3,0.7l0.1,0.1L41.9,54 c0.1,0.1,0.3,0.2,0.5,0.2c0.4,0,0.8-0.3,0.8-0.8C43.2,53.2,43.1,53,43,52.8z', // jshint ignore:line
 
-            color: '#304654',
-            borderColor: '#304654',
+            color: '#A4BED7',
+            borderColor: '#A4BED7',
             borderWidth: 1
         },
-
         emphasis: {
             label: {
                 show: true,
                 // 其余属性默认使用全局文本样式,详见TEXTSTYLE
-                color: '#c23531'
+                color: '#6f778d'
             },
 
             itemStyle: {
-                color: '#c23531'
+                color: '#316BF3'
             },
 
             controlStyle: {
-                color: '#c23531',
-                borderColor: '#c23531',
+                color: '#316BF3',
+                borderColor: '#316BF3',
                 borderWidth: 2
             }
         },
+
+        progress: {
+            lineStyle: {
+                color: '#316BF3'
+            },
+            itemStyle: {
+                color: '#316BF3'
+            }
+        },
+
         data: []
     });
 
diff --git a/src/component/timeline/SliderTimelineView.ts b/src/component/timeline/SliderTimelineView.ts
index e0e6083..f12febd 100644
--- a/src/component/timeline/SliderTimelineView.ts
+++ b/src/component/timeline/SliderTimelineView.ts
@@ -30,19 +30,20 @@ import * as numberUtil from '../../util/number';
 import {encodeHTML} from '../../util/format';
 import GlobalModel from '../../model/Global';
 import ExtensionAPI from '../../ExtensionAPI';
-import { merge, each, extend, clone, isString, bind } from 'zrender/src/core/util';
+import { merge, each, extend, clone, isString, bind, defaults } from 'zrender/src/core/util';
 import SliderTimelineModel from './SliderTimelineModel';
 import ComponentView from '../../view/Component';
 import { LayoutOrient, ZRTextAlign, ZRTextVerticalAlign, ZRElementEvent } from '../../util/types';
 import TimelineModel, { TimelineDataItemOption, TimelineCheckpointStyle } from './TimelineModel';
 import { TimelineChangePayload, TimelinePlayChangePayload } from './timelineAction';
 import Model from '../../model/Model';
-import { PathProps } from 'zrender/src/graphic/Path';
+import { PathProps, PathStyleProps } from 'zrender/src/graphic/Path';
 import Scale from '../../scale/Scale';
 import OrdinalScale from '../../scale/Ordinal';
 import TimeScale from '../../scale/Time';
 import IntervalScale from '../../scale/Interval';
 import { VectorArray } from 'zrender/src/core/vector';
+import ZRText from 'zrender/src/graphic/Text';
 
 const PI = Math.PI;
 
@@ -89,10 +90,15 @@ class SliderTimelineView extends TimelineView {
 
     private _currentPointer: TimelineSymbol;
 
+    private _progressLine: graphic.Line;
+
     private _mainGroup: graphic.Group;
 
     private _labelGroup: graphic.Group;
 
+    private _tickSymbols: graphic.Path[];
+    private _tickLabels: graphic.Text[];
+
 
     init(ecModel: GlobalModel, api: ExtensionAPI) {
         this.api = api;
@@ -133,6 +139,8 @@ class SliderTimelineView extends TimelineView {
         }
 
         this._doPlayStop();
+
+        this._updateTicksStatus();
     }
 
     /**
@@ -150,7 +158,7 @@ class SliderTimelineView extends TimelineView {
         this._clearTimer();
     }
 
-    _layout(timelineModel: SliderTimelineModel, api: ExtensionAPI): LayoutInfo {
+    private _layout(timelineModel: SliderTimelineModel, api: ExtensionAPI): LayoutInfo {
         const labelPosOpt = timelineModel.get(['label', 'position']);
         const orient = timelineModel.get('orient');
         const viewRect = getViewRect(timelineModel, api);
@@ -250,7 +258,7 @@ class SliderTimelineView extends TimelineView {
         };
     }
 
-    _position(layoutInfo: LayoutInfo, timelineModel: SliderTimelineModel) {
+    private _position(layoutInfo: LayoutInfo, timelineModel: SliderTimelineModel) {
         // Position is be called finally, because bounding rect is needed for
         // adapt content to fill viewRect (auto adapt offset).
 
@@ -321,7 +329,7 @@ class SliderTimelineView extends TimelineView {
         }
     }
 
-    _createAxis(layoutInfo: LayoutInfo, timelineModel: SliderTimelineModel) {
+    private _createAxis(layoutInfo: LayoutInfo, timelineModel: SliderTimelineModel) {
         const data = timelineModel.getData();
         const axisType = timelineModel.get('axisType');
 
@@ -344,13 +352,13 @@ class SliderTimelineView extends TimelineView {
         return axis;
     }
 
-    _createGroup(key: '_mainGroup' | '_labelGroup') {
+    private _createGroup(key: '_mainGroup' | '_labelGroup') {
         const newGroup = this[key] = new graphic.Group();
         this.group.add(newGroup);
         return newGroup;
     }
 
-    _renderAxisLine(
+    private _renderAxisLine(
         layoutInfo: LayoutInfo,
         group: graphic.Group,
         axis: TimelineAxis,
@@ -362,7 +370,7 @@ class SliderTimelineView extends TimelineView {
             return;
         }
 
-        group.add(new graphic.Line({
+        const line = new graphic.Line({
             shape: {
                 x1: axisExtent[0], y1: 0,
                 x2: axisExtent[1], y2: 0
@@ -373,13 +381,27 @@ class SliderTimelineView extends TimelineView {
             ),
             silent: true,
             z2: 1
-        }));
+        });
+        group.add(line);
+
+        const progressLine = this._progressLine = new graphic.Line({
+            shape: {
+                x1: axisExtent[0],
+                x2: this._currentPointer
+                    ? this._currentPointer.x : axisExtent[0],
+                y1: 0, y2: 0
+            },
+            style: defaults(
+                { lineCap: 'round', lineWidth: line.style.lineWidth } as PathStyleProps,
+                timelineModel.getModel(['progress', 'lineStyle']).getLineStyle()
+            ),
+            silent: true,
+            z2: 1
+        });
+        group.add(progressLine);
     }
 
-    /**
-     * @private
-     */
-    _renderAxisTick(
+    private _renderAxisTick(
         layoutInfo: LayoutInfo,
         group: graphic.Group,
         axis: TimelineAxis,
@@ -389,18 +411,24 @@ class SliderTimelineView extends TimelineView {
         // Show all ticks, despite ignoring strategy.
         const ticks = axis.scale.getTicks();
 
+        this._tickSymbols = [];
+
         // The value is dataIndex, see the costomized scale.
-        each(ticks, function (value) {
+        each(ticks, (value) => {
             const tickCoord = axis.dataToCoord(value);
             const itemModel = data.getItemModel<TimelineDataItemOption>(value);
             const itemStyleModel = itemModel.getModel('itemStyle');
             const hoverStyleModel = itemModel.getModel(['emphasis', 'itemStyle']);
+            const progressStyleModel = itemModel.getModel(['progress', 'itemStyle']);
+
             const symbolOpt = {
                 position: [tickCoord, 0],
                 onclick: bind(this._changeTimeline, this, value)
             };
             const el = giveSymbol(itemModel, itemStyleModel, group, symbolOpt);
             el.ensureState('emphasis').style = hoverStyleModel.getItemStyle();
+            el.ensureState('progress').style = progressStyleModel.getItemStyle();
+
             enableHoverEmphasis(el);
 
             const ecData = graphic.getECData(el);
@@ -412,13 +440,11 @@ class SliderTimelineView extends TimelineView {
                 ecData.dataIndex = ecData.dataModel = null;
             }
 
-        }, this);
+            this._tickSymbols.push(el);
+        });
     }
 
-    /**
-     * @private
-     */
-    _renderAxisLabel(
+    private _renderAxisLabel(
         layoutInfo: LayoutInfo,
         group: graphic.Group,
         axis: TimelineAxis,
@@ -433,13 +459,17 @@ class SliderTimelineView extends TimelineView {
         const data = timelineModel.getData();
         const labels = axis.getViewLabels();
 
-        each(labels, function (labelItem) {
+        this._tickLabels = [];
+
+        each(labels, (labelItem) => {
             // The tickValue is dataIndex, see the costomized scale.
             const dataIndex = labelItem.tickValue;
 
             const itemModel = data.getItemModel<TimelineDataItemOption>(dataIndex);
             const normalLabelModel = itemModel.getModel('label');
             const hoverLabelModel = itemModel.getModel(['emphasis', 'label']);
+            const progressLabelModel = itemModel.getModel(['progress', 'label']);
+
             const tickCoord = axis.dataToCoord(labelItem.tickValue);
             const textEl = new graphic.Text({
                 x: tickCoord,
@@ -455,17 +485,17 @@ class SliderTimelineView extends TimelineView {
             });
 
             textEl.ensureState('emphasis').style = createTextStyle(hoverLabelModel);
+            textEl.ensureState('progress').style = createTextStyle(progressLabelModel);
 
             group.add(textEl);
             enableHoverEmphasis(textEl);
 
-        }, this);
+            this._tickLabels.push(textEl);
+
+        });
     }
 
-    /**
-     * @private
-     */
-    _renderControl(
+    private _renderControl(
         layoutInfo: LayoutInfo,
         group: graphic.Group,
         axis: TimelineAxis,
@@ -521,7 +551,7 @@ class SliderTimelineView extends TimelineView {
         }
     }
 
-    _renderCurrentPointer(
+    private _renderCurrentPointer(
         layoutInfo: LayoutInfo,
         group: graphic.Group,
         axis: TimelineAxis,
@@ -538,10 +568,10 @@ class SliderTimelineView extends TimelineView {
                 pointer.draggable = true;
                 pointer.drift = bind(me._handlePointerDrag, me);
                 pointer.ondragend = bind(me._handlePointerDragend, me);
-                pointerMoveTo(pointer, currentIndex, axis, timelineModel, true);
+                pointerMoveTo(pointer, me._progressLine, currentIndex, axis, timelineModel, true);
             },
             onUpdate(pointer: TimelineSymbol) {
-                pointerMoveTo(pointer, currentIndex, axis, timelineModel);
+                pointerMoveTo(pointer, me._progressLine, currentIndex, axis, timelineModel);
             }
         };
 
@@ -551,7 +581,7 @@ class SliderTimelineView extends TimelineView {
         );
     }
 
-    _handlePlayClick(nextState: boolean) {
+    private _handlePlayClick(nextState: boolean) {
         this._clearTimer();
         this.api.dispatchAction({
             type: 'timelinePlayChange',
@@ -560,16 +590,16 @@ class SliderTimelineView extends TimelineView {
         } as TimelinePlayChangePayload);
     }
 
-    _handlePointerDrag(dx: number, dy: number, e: ZRElementEvent) {
+    private _handlePointerDrag(dx: number, dy: number, e: ZRElementEvent) {
         this._clearTimer();
         this._pointerChangeTimeline([e.offsetX, e.offsetY]);
     }
 
-    _handlePointerDragend(e: ZRElementEvent) {
+    private _handlePointerDragend(e: ZRElementEvent) {
         this._pointerChangeTimeline([e.offsetX, e.offsetY], true);
     }
 
-    _pointerChangeTimeline(mousePos: number[], trigger?: boolean) {
+    private _pointerChangeTimeline(mousePos: number[], trigger?: boolean) {
         let toCoord = this._toAxisCoord(mousePos)[0];
 
         const axis = this._axis;
@@ -581,6 +611,9 @@ class SliderTimelineView extends TimelineView {
         this._currentPointer.x = toCoord;
         this._currentPointer.markRedraw();
 
+        this._progressLine.shape.x2 = toCoord;
+        this._progressLine.dirty();
+
         const targetDataIndex = this._findNearestTick(toCoord);
         const timelineModel = this.model;
 
@@ -592,7 +625,7 @@ class SliderTimelineView extends TimelineView {
         }
     }
 
-    _doPlayStop() {
+    private _doPlayStop() {
         this._clearTimer();
 
         if (this.model.getPlayState()) {
@@ -610,12 +643,12 @@ class SliderTimelineView extends TimelineView {
         }
     }
 
-    _toAxisCoord(vertex: number[]) {
+    private _toAxisCoord(vertex: number[]) {
         const trans = this._mainGroup.getLocalTransform();
         return graphic.applyTransform(vertex, trans, true);
     }
 
-    _findNearestTick(axisCoord: number) {
+    private _findNearestTick(axisCoord: number) {
         const data = this.model.getData();
         let dist = Infinity;
         let targetDataIndex;
@@ -633,14 +666,14 @@ class SliderTimelineView extends TimelineView {
         return targetDataIndex;
     }
 
-    _clearTimer() {
+    private _clearTimer() {
         if (this._timer) {
             clearTimeout(this._timer);
             this._timer = null;
         }
     }
 
-    _changeTimeline(nextIndex: number | '+' | '-') {
+    private _changeTimeline(nextIndex: number | '+' | '-') {
         const currentIndex = this.model.getCurrentIndex();
 
         if (nextIndex === '+') {
@@ -657,6 +690,23 @@ class SliderTimelineView extends TimelineView {
         } as TimelineChangePayload);
     }
 
+    private _updateTicksStatus() {
+        const currentIndex = this.model.getCurrentIndex();
+        const tickSymbols = this._tickSymbols;
+        const tickLabels = this._tickLabels;
+        if (!(tickSymbols || tickLabels)) {
+            return;
+        }
+
+        const len = (tickSymbols || tickLabels).length;
+
+        for (let i = 0; i < len; i++) {
+            tickSymbols && tickSymbols[i]
+                && tickSymbols[i].toggleState('progress', i <= currentIndex);
+            tickLabels && tickLabels[i]
+                && tickLabels[i].toggleState('progress', i <= currentIndex);
+        }
+    }
 }
 
 function createScaleByModel(model: SliderTimelineModel, axisType?: string): Scale {
@@ -782,6 +832,7 @@ function giveSymbol(
 
 function pointerMoveTo(
     pointer: TimelineSymbol,
+    progressLine: graphic.Line,
     dataIndex: number,
     axis: TimelineAxis,
     timelineModel: SliderTimelineModel,
@@ -795,20 +846,28 @@ function pointerMoveTo(
     const toCoord = axis.dataToCoord(timelineModel.getData().get('value', dataIndex));
 
     if (noAnimation || !pointerModel.get('animation', true)) {
-        pointer.x = toCoord;
-        pointer.y = 0;
+        pointer.attr({
+            x: toCoord,
+            y: 0
+        });
+        progressLine && progressLine.attr({
+            shape: { x2: toCoord }
+        });
     }
     else {
+        const animationCfg = {
+            duration: pointerModel.get('animationDuration', true),
+            easing: pointerModel.get('animationEasing', true)
+        };
         pointer.stopAnimation(null, true);
         pointer.animateTo({
             x: toCoord,
             y: 0
-        }, {
-                duration: pointerModel.get('animationDuration', true),
-                easing: pointerModel.get('animationEasing', true)
-        });
+        }, animationCfg);
+        progressLine && progressLine.animateTo({
+            shape: { x2: toCoord }
+        }, animationCfg);
     }
-    pointer.markRedraw();
 }
 
 
diff --git a/src/component/timeline/TimelineModel.ts b/src/component/timeline/TimelineModel.ts
index 385854c..2ddea11 100644
--- a/src/component/timeline/TimelineModel.ts
+++ b/src/component/timeline/TimelineModel.ts
@@ -84,6 +84,13 @@ export interface TimelineDataItemOption extends SymbolOptionMixin {
         checkpointStyle?: TimelineCheckpointStyle
     }
 
+    // Style in progress
+    progress?: {
+        lineStyle?: TimelineLineStyleOption
+        itemStyle?: ItemStyleOption
+        label?: TimelineLabelOption
+    }
+
     tooltip?: boolean
 }
 
@@ -139,6 +146,14 @@ export interface TimelineOption extends ComponentOption, BoxLayoutOptionMixin, S
         label?: TimelineLabelOption
     }
 
+
+    // Style in progress
+    progress?: {
+        lineStyle?: TimelineLineStyleOption
+        itemStyle?: ItemStyleOption
+        label?: TimelineLabelOption
+    }
+
     data?: (OptionDataValue | TimelineDataItemOption)[]
 }
 class TimelineModel extends ComponentModel<TimelineOption> {
diff --git a/src/echarts.ts b/src/echarts.ts
index de8aeab..3e745df 100644
--- a/src/echarts.ts
+++ b/src/echarts.ts
@@ -1968,45 +1968,50 @@ class ECharts extends Eventful {
             }
 
             ecIns.getZr().storage.traverse(function (el: ECElement) {
-                const newStates = [];
-                const oldStates = el.currentStates;
-
                 // Not applied on removed elements, it may still in fading.
                 if (graphic.isElementRemoved(el)) {
                     return;
                 }
-
-                // Keep other states.
-                for (let i = 0; i < oldStates.length; i++) {
-                    const stateName = oldStates[i];
-                    if (!(stateName === 'emphasis' || stateName === 'blur' || stateName === 'select')) {
-                        newStates.push(stateName);
-                    }
-                }
-
-                // Only use states when it's exists.
-                if (el.selected && el.states.select) {
-                    newStates.push('select');
-                }
-                if (el.hoverState === HOVER_STATE_EMPHASIS && el.states.emphasis) {
-                    newStates.push('emphasis');
-                }
-                else if (el.hoverState === HOVER_STATE_BLUR && el.states.blur) {
-                    newStates.push('blur');
-                }
-                el.useStates(newStates);
+                applyElementStates(el);
             });
 
             ecIns[STATUS_NEEDS_UPDATE_KEY] = false;
         };
 
+        function applyElementStates(el: ECElement) {
+            const newStates = [];
+
+            const oldStates = el.currentStates;
+            // Keep other states.
+            for (let i = 0; i < oldStates.length; i++) {
+                const stateName = oldStates[i];
+                if (!(stateName === 'emphasis' || stateName === 'blur' || stateName === 'select')) {
+                    newStates.push(stateName);
+                }
+            }
+
+            // Only use states when it's exists.
+            if (el.selected && el.states.select) {
+                newStates.push('select');
+            }
+            if (el.hoverState === HOVER_STATE_EMPHASIS && el.states.emphasis) {
+                newStates.push('emphasis');
+            }
+            else if (el.hoverState === HOVER_STATE_BLUR && el.states.blur) {
+                newStates.push('blur');
+            }
+            el.useStates(newStates);
+        }
+
         function updateHoverLayerStatus(ecIns: ECharts, ecModel: GlobalModel): void {
             const zr = ecIns._zr;
             const storage = zr.storage;
             let elCount = 0;
 
             storage.traverse(function (el) {
-                elCount++;
+                if (!el.isGroup) {
+                    elCount++;
+                }
             });
 
             if (elCount > ecModel.get('hoverLayerThreshold') && !env.node) {
@@ -2159,14 +2164,7 @@ class ECharts extends Eventful {
 
                     // The use higlighted and selected flag to toggle states.
                     if (el.__dirty) {
-                        const states = [];
-                        if ((el as ECElement).selected) {
-                            states.push('select');
-                        }
-                        if ((el as ECElement).hoverState) {
-                            states.push('emphasis');
-                        }
-                        el.useStates(states);
+                        applyElementStates(el);
                     }
                 }
             });
diff --git a/test/timeline-finance.html b/test/timeline-finance.html
index 29e9c44..3006f01 100644
--- a/test/timeline-finance.html
+++ b/test/timeline-finance.html
@@ -83,12 +83,12 @@ var option = {
             axisType: 'category',
             // realtime: false,
             // loop: false,
-            autoPlay: true,
+            autoPlay: false,
             // currentIndex: 2,
             playInterval: 1000,
             controlStyle: {
-                showNextBtn: false,
-                showPrevBtn: false,
+                showNextBtn: true,
+                showPrevBtn: true,
                 position: 'left'
             },
             data: [


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