You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@echarts.apache.org by su...@apache.org on 2018/09/19 18:54:14 UTC

[incubator-echarts] branch custom-enhance created (now ba37b28)

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

sushuang pushed a change to branch custom-enhance
in repository https://gitbox.apache.org/repos/asf/incubator-echarts.git.


      at ba37b28  Try to support animation from and custom-series animation.

This branch includes the following new commits:

     new ba37b28  Try to support animation from and custom-series animation.

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



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


[incubator-echarts] 01/01: Try to support animation from and custom-series animation.

Posted by su...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

sushuang pushed a commit to branch custom-enhance
in repository https://gitbox.apache.org/repos/asf/incubator-echarts.git

commit ba37b28240bb657a05488a80a96e4c37b2d80b91
Author: sushuang <su...@gmail.com>
AuthorDate: Thu Sep 20 02:53:39 2018 +0800

    Try to support animation from and custom-series animation.
---
 src/chart/custom.js               | 133 ++++++++++++++++++++++++--------------
 src/chart/line/LineView.js        |  16 ++---
 src/component/axis/AxisBuilder.js |  34 ++++++++++
 src/export.js                     |   2 +-
 src/processor/dataStack.js        |   4 +-
 src/util/graphic.js               |  44 ++++++++++---
 test/custom-feature.html          | 101 ++++++++++++++++++++++++++++-
 test/tmp-base.html                |   4 +-
 8 files changed, 266 insertions(+), 72 deletions(-)

diff --git a/src/chart/custom.js b/src/chart/custom.js
index 98f7ead..ce7ae3a 100644
--- a/src/chart/custom.js
+++ b/src/chart/custom.js
@@ -42,6 +42,10 @@ var LABEL_EMPHASIS = ['emphasis', 'label'];
 // which will cause weird udpate animation.
 var GROUP_DIFF_PREFIX = 'e\0\0';
 
+var IMAGE_TRANSITION_STYLES = ['x', 'y', 'width', 'height', 'opacity'];
+var TEXT_TRANSITION_STYLES = ['x', 'y', 'opacity'];
+var PATH_TRANSITION_STYLES = ['opacity', 'lineWidth'];
+
 /**
  * To reduce total package size of each coordinate systems, the modules `prepareCustom`
  * of each coordinate systems are not required by each coordinate systems directly, but
@@ -252,54 +256,78 @@ function createEl(elOption) {
 }
 
 function updateEl(el, dataIndex, elOption, animatableModel, data, isInit, isRoot) {
-    var transitionProps = {};
-    var elOptionStyle = elOption.style || {};
-
-    elOption.shape && (transitionProps.shape = zrUtil.clone(elOption.shape));
-    elOption.position && (transitionProps.position = elOption.position.slice());
-    elOption.scale && (transitionProps.scale = elOption.scale.slice());
-    elOption.origin && (transitionProps.origin = elOption.origin.slice());
-    elOption.rotation && (transitionProps.rotation = elOption.rotation);
-
-    if (el.type === 'image' && elOption.style) {
-        var targetStyle = transitionProps.style = {};
-        zrUtil.each(['x', 'y', 'width', 'height'], function (prop) {
-            prepareStyleTransition(prop, targetStyle, elOptionStyle, el.style, isInit);
-        });
+    var elType = el.type;
+    var isGroup = el.isGroup;
+    var isImage = elType === 'image';
+    var isText = elType === 'text';
+    var notCopy = elOption.copy === false;
+
+    var userInitState = elOption.initState;
+    var styleToBeSet = elOption.style;
+
+    // Make `elOptionValid` and `initStateValid`.
+    var elOptionValid = fetchDisplayableProps(elOption, notCopy);
+    var initStateValid = isInit && userInitState && fetchDisplayableProps(userInitState, notCopy);
+
+    // Prepare `styleTransValues`.
+    var styleTransValues;
+    if (isInit && !userInitState && !isGroup) {
+        // Default init animation.
+        styleTransValues = {opacity: 0};
+    }
+    else {
+        var styleTransCandidates = isInit ? (userInitState && userInitState.style) : styleToBeSet;
+        // Enable style transition.
+        var styleTransProps = isImage
+            ? IMAGE_TRANSITION_STYLES
+            : isText
+            ? TEXT_TRANSITION_STYLES
+            : !isGroup
+            ? PATH_TRANSITION_STYLES
+            : null;
+        if (styleTransProps && styleTransCandidates) {
+            for (var i = 0; i < styleTransProps.length; i++) {
+                var prop = styleTransProps[i];
+                if (styleTransCandidates[prop] != null) {
+                    // Condider animation performance, do not give until necessary.
+                    (styleTransValues = styleTransValues || {})[prop] = styleTransCandidates[prop];
+                    !isInit && (styleToBeSet[prop] = el.style[prop]);
+                }
+            }
+        }
     }
 
-    if (el.type === 'text' && elOption.style) {
-        var targetStyle = transitionProps.style = {};
-        zrUtil.each(['x', 'y'], function (prop) {
-            prepareStyleTransition(prop, targetStyle, elOptionStyle, el.style, isInit);
-        });
-        // Compatible with previous: both support
-        // textFill and fill, textStroke and stroke in 'text' element.
-        !elOptionStyle.hasOwnProperty('textFill') && elOptionStyle.fill && (
-            elOptionStyle.textFill = elOptionStyle.fill
+    // Compatible with previous: both support
+    // textFill and fill, textStroke and stroke in 'text' element.
+    if (isText) {
+        !styleToBeSet.hasOwnProperty('textFill') && styleToBeSet.fill && (
+            styleToBeSet.textFill = styleToBeSet.fill
         );
-        !elOptionStyle.hasOwnProperty('textStroke') && elOptionStyle.stroke && (
-            elOptionStyle.textStroke = elOptionStyle.stroke
+        !styleToBeSet.hasOwnProperty('textStroke') && styleToBeSet.stroke && (
+            styleToBeSet.textStroke = styleToBeSet.stroke
         );
     }
 
-    if (el.type !== 'group') {
-        el.useStyle(elOptionStyle);
-
-        // Init animation.
-        if (isInit) {
-            el.style.opacity = 0;
-            var targetOpacity = elOptionStyle.opacity;
-            targetOpacity == null && (targetOpacity = 1);
-            graphicUtil.initProps(el, {style: {opacity: targetOpacity}}, animatableModel, dataIndex);
-        }
+    if (!isGroup) {
+        // Follow the convention and consider performance, merge by default.
+        // If `el.merge` is false, here the `el` is a new element just created.
+        (!isInit && elOption.mergeStyle === false)
+            ? el.useStyle(styleToBeSet || {})
+            : (styleToBeSet && el.setStyle(styleToBeSet));
     }
 
     if (isInit) {
-        el.attr(transitionProps);
+        elOptionValid && el.attr(elOptionValid);
+        styleTransValues && ((initStateValid = initStateValid || {}).style = styleTransValues);
+        // When "init", the current props represents the final state, while the `initState` is a subset of
+        // props. When "update", the current props represents the current state, while the final state is
+        // a subset of props. So we do not use `graphicUtil.initProps`. See the comment of
+        // `graphicUtil.initFromProps` for more details.
+        initStateValid && graphicUtil.initFromProps(el, initStateValid, animatableModel, dataIndex);
     }
     else {
-        graphicUtil.updateProps(el, transitionProps, animatableModel, dataIndex);
+        styleTransValues && ((elOptionValid = elOptionValid || {}).style = styleTransValues);
+        elOptionValid && graphicUtil.updateProps(el, elOptionValid, animatableModel, dataIndex);
     }
 
     // Merge by default.
@@ -313,6 +341,7 @@ function updateEl(el, dataIndex, elOption, animatableModel, data, isInit, isRoot
     // Update them only when user specified, otherwise, remain.
     elOption.hasOwnProperty('info') && el.attr('info', elOption.info);
 
+    // Set `styleEmphasis`.
     // If `elOption.styleEmphasis` is `false`, remove hover style. The
     // logic is ensured by `graphicUtil.setElementHoverStyle`.
     var styleEmphasis = elOption.styleEmphasis;
@@ -330,11 +359,19 @@ function updateEl(el, dataIndex, elOption, animatableModel, data, isInit, isRoot
     isRoot && graphicUtil.setAsHoverStyleTrigger(el, !disableStyleEmphasis);
 }
 
-function prepareStyleTransition(prop, targetStyle, elOptionStyle, oldElStyle, isInit) {
-    if (elOptionStyle[prop] != null && !isInit) {
-        targetStyle[prop] = elOptionStyle[prop];
-        elOptionStyle[prop] = oldElStyle[prop];
-    }
+function fetchDisplayableProps(option, notCopy) {
+    // Condider performance of `graphic.initFromProps/updateProps`, do not give until necessary.
+    var props;
+    var v;
+    // Consider performance of large shape, enable user to disable copy.
+    (v = option.shape) && ((props = props || {}).shape = notCopy ? v : zrUtil.clone(v));
+    // Transform attributes will be copied inside `el.attr()` and `graphic.updateProps()`.
+    (v = option.position) && ((props = props || {}).position = v);
+    (v = option.scale) && ((props = props || {}).scale = v);
+    (v = option.origin) && ((props = props || {}).origin = v);
+    (v = option.rotation) != null && ((props = props || {}).rotation = v);
+
+    return props;
 }
 
 function makeRenderItem(customSeries, data, ecModel, api) {
@@ -620,25 +657,25 @@ function doCreateOrUpdate(el, dataIndex, elOption, animatableModel, group, data,
 }
 
 // Usage:
-// (1) By default, `elOption.$mergeChildren` is `'byIndex'`, which indicates that
+// (1) By default, `elOption.mergeChildren` is `'byIndex'`, which indicates that
 //     the existing children will not be removed, and enables the feature that
 //     update some of the props of some of the children simply by construct
 //     the returned children of `renderItem` like:
 //     `var children = group.children = []; children[3] = {opacity: 0.5};`
-// (2) If `elOption.$mergeChildren` is `'byName'`, add/update/remove children
+// (2) If `elOption.mergeChildren` is `'byName'`, add/update/remove children
 //     by child.name. But that might be lower performance.
-// (3) If `elOption.$mergeChildren` is `false`, the existing children will be
+// (3) If `elOption.mergeChildren` is `false`, the existing children will be
 //     replaced totally.
 // (4) If `!elOption.children`, following the "merge" principle, nothing will happen.
 //
 // For implementation simpleness, do not provide a direct way to remove sinlge
 // child (otherwise the total indicies of the children array have to be modified).
 // User can remove a single child by set its `ignore` as `true` or replace
-// it by another element, where its `$merge` can be set as `true` if necessary.
+// it by another element, where its `merge` can be set as `true` if necessary.
 function mergeChildren(el, dataIndex, elOption, animatableModel, data) {
     var newChildren = elOption.children;
     var newLen = newChildren ? newChildren.length : 0;
-    var mergeChildren = elOption.$mergeChildren;
+    var mergeChildren = elOption.mergeChildren;
     // `diffChildrenByName` has been deprecated.
     var byName = mergeChildren === 'byName' || elOption.diffChildrenByName;
     var notMerge = mergeChildren === false;
@@ -678,7 +715,7 @@ function mergeChildren(el, dataIndex, elOption, animatableModel, data) {
     if (__DEV__) {
         zrUtil.assert(
             !notMerge || el.childCount() === index,
-            'MUST NOT contain empty item in children array when `group.$mergeChildren` is `false`.'
+            'MUST NOT contain empty item in children array when `group.mergeChildren` is `false`.'
         );
     }
 }
diff --git a/src/chart/line/LineView.js b/src/chart/line/LineView.js
index 501f026..97a59ed 100644
--- a/src/chart/line/LineView.js
+++ b/src/chart/line/LineView.js
@@ -733,7 +733,7 @@ export default ChartView.extend({
             shape: {
                 points: next
             }
-        }, seriesModel);
+        }, seriesModel, null, {during: duringAnimation});
 
         if (polygon) {
             polygon.setShape({
@@ -758,19 +758,17 @@ export default ChartView.extend({
                 if (el) {
                     updatedDataInfo.push({
                         el: el,
-                        ptIdx: i    // Index of points
+                        ptIdx: i // Index of points
                     });
                 }
             }
         }
 
-        if (polyline.animators && polyline.animators.length) {
-            polyline.animators[0].during(function () {
-                for (var i = 0; i < updatedDataInfo.length; i++) {
-                    var el = updatedDataInfo[i].el;
-                    el.attr('position', polyline.shape.__points[updatedDataInfo[i].ptIdx]);
-                }
-            });
+        function duringAnimation() {
+            for (var i = 0; i < updatedDataInfo.length; i++) {
+                var el = updatedDataInfo[i].el;
+                el.attr('position', polyline.shape.__points[updatedDataInfo[i].ptIdx]);
+            }
         }
     },
 
diff --git a/src/component/axis/AxisBuilder.js b/src/component/axis/AxisBuilder.js
index 1f0ff7e..5a5254d 100644
--- a/src/component/axis/AxisBuilder.js
+++ b/src/component/axis/AxisBuilder.js
@@ -612,6 +612,40 @@ function buildAxisTick(axisBuilder, axisModel, opt) {
             z2: 2,
             silent: true
         }));
+
+// if (window.__basesss == null) {
+//     window.__basesss = 1;
+// }
+// (function () {
+//     var uuid = window.__basesss++;
+//     Object.defineProperty(tickEl.shape, 'uuid', {
+//         get() {
+//             return uuid;
+//         },
+//         set() {
+//             debugger;
+//         }
+//     });
+// })();
+
+// Object.defineProperty(tickEl.shape, 'aaaa', {
+//     get() {
+//         return 1;
+//     },
+//     enumerable: true,
+//     set(val) {
+//         console.log(val, '???????????????????????');
+//         debugger;
+//     }
+// });
+// if (!window.__xxx) {
+//     console.log('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
+//     window.__xxx = tickEl;
+//     // tickEl.shape.aaaa = 1;
+//     console.log(tickEl.shape.aaaa);
+//     console.log(tickEl.shape.uuid, '=++++++++++++++');
+// }
+
         axisBuilder.group.add(tickEl);
         tickEls.push(tickEl);
     }
diff --git a/src/export.js b/src/export.js
index dded75c..0542ab0 100644
--- a/src/export.js
+++ b/src/export.js
@@ -69,7 +69,7 @@ zrUtil.each(
         'extendShape', 'extendPath', 'makePath', 'makeImage',
         'mergePath', 'resizePath', 'createIcon',
         'setHoverStyle', 'setLabelStyle', 'setTextStyle', 'setText',
-        'getFont', 'updateProps', 'initProps', 'getTransform',
+        'getFont', 'updateProps', 'initProps', 'initFromProps', 'getTransform',
         'clipPointsByRect', 'clipRectByRect',
         'Group',
         'Image',
diff --git a/src/processor/dataStack.js b/src/processor/dataStack.js
index 22b9202..9ca423e 100644
--- a/src/processor/dataStack.js
+++ b/src/processor/dataStack.js
@@ -19,11 +19,11 @@
 
 import {createHashMap, each} from 'zrender/src/core/util';
 
-// (1) [Caution]: the logic is correct based on the premises:
+// (1) [Caution]: the logic is correct based on the promises:
 //     data processing stage is blocked in stream.
 //     See <module:echarts/stream/Scheduler#performDataProcessorTasks>
 // (2) Only register once when import repeatly.
-//     Should be executed before after series filtered and before stack calculation.
+//     Should be executed after series filtered and before stack calculation.
 export default function (ecModel) {
     var stackInfoMap = createHashMap();
     ecModel.eachSeries(function (seriesModel) {
diff --git a/src/util/graphic.js b/src/util/graphic.js
index f44b844..71befb7 100644
--- a/src/util/graphic.js
+++ b/src/util/graphic.js
@@ -886,7 +886,7 @@ export function getFont(opt, ecModel) {
     ].join(' '));
 }
 
-function animateOrSetProps(isUpdate, el, props, animatableModel, dataIndex, cb) {
+function animateOrSetProps(isUpdate, isFrom, el, props, animatableModel, dataIndex, cb) {
     if (typeof dataIndex === 'function') {
         cb = dataIndex;
         dataIndex = null;
@@ -914,11 +914,14 @@ function animateOrSetProps(isUpdate, el, props, animatableModel, dataIndex, cb)
         }
 
         duration > 0
-            ? el.animateTo(props, duration, animationDelay || 0, animationEasing, cb, !!cb)
+            ? el[isFrom ? 'animateFrom' : 'animateTo'](
+                props, duration, animationDelay || 0, animationEasing, cb, !!cb
+            )
+            // Consider that the new `props` maybe different from the last `props`,
+            // forward to last to ensure the last settings work.
             : (el.stopAnimation(), el.attr(props), cb && cb());
     }
-    else {
-        el.stopAnimation();
+    else if (!isFrom) {
         el.attr(props);
         cb && cb();
     }
@@ -936,7 +939,7 @@ function animateOrSetProps(isUpdate, el, props, animatableModel, dataIndex, cb)
  * @param {Object} props
  * @param {module:echarts/model/Model} [animatableModel]
  * @param {number} [dataIndex]
- * @param {Function} [cb]
+ * @param {Function|Object} [cb] Function: done, Object: {done: function, during: function}
  * @example
  *     graphic.updateProps(el, {
  *         position: [100, 100]
@@ -947,7 +950,7 @@ function animateOrSetProps(isUpdate, el, props, animatableModel, dataIndex, cb)
  *     }, seriesModel, function () { console.log('Animation done!'); });
  */
 export function updateProps(el, props, animatableModel, dataIndex, cb) {
-    animateOrSetProps(true, el, props, animatableModel, dataIndex, cb);
+    animateOrSetProps(true, false, el, props, animatableModel, dataIndex, cb);
 }
 
 /**
@@ -962,10 +965,34 @@ export function updateProps(el, props, animatableModel, dataIndex, cb) {
  * @param {Object} props
  * @param {module:echarts/model/Model} [animatableModel]
  * @param {number} [dataIndex]
- * @param {Function} cb
+ * @param {Function|Object} [cb] Function: done, Object: {done: function, during: function}
  */
 export function initProps(el, props, animatableModel, dataIndex, cb) {
-    animateOrSetProps(false, el, props, animatableModel, dataIndex, cb);
+    animateOrSetProps(false, false, el, props, animatableModel, dataIndex, cb);
+}
+
+/**
+ * Animate from the given props to el current state with "init" animation settings.
+ * The rest definitions are the same as `initPorps`.
+ *
+ * Consider that only props that existing in `initState` can be animated, and sometimes
+ * the target state is the complete set of props but the initial state is just a subset,
+ * using `initFromProps` is simpler. For example:
+ *
+ * Use `initProps` to make init animation:
+ * ```js
+ * var {targetStateNotInInitState, targetStateInInitState} = split(targetState, initState);
+ * el.attr(targetStateNotInInitState);
+ * graphicUtil.initProps(elInInitState, targetStateInInitState);
+ * ```
+ *
+ * Use `initFromProps` to make init animation.
+ * ```js
+ * graphicUtil.initFromProps(elInTargetState, initState, ...)`;
+ * ```
+ */
+export function initFromProps(el, props, animatableModel, dataIndex, cb) {
+    animateOrSetProps(false, true, el, props, animatableModel, dataIndex, cb);
 }
 
 /**
@@ -1066,6 +1093,7 @@ export function groupTransition(g1, g2, animatableModel, cb) {
         if (!el.isGroup && el.anid) {
             var oldEl = elMap1[el.anid];
             if (oldEl) {
+                // ??? animateFrom
                 var newProp = getAnimatableProps(el);
                 el.attr(getAnimatableProps(oldEl));
                 updateProps(el, newProp, animatableModel, el.dataIndex);
diff --git a/test/custom-feature.html b/test/custom-feature.html
index 7c568b1..c27d75b 100644
--- a/test/custom-feature.html
+++ b/test/custom-feature.html
@@ -42,7 +42,7 @@ under the License.
         <div id="main0"></div>
         <div id="main2"></div>
         <div id="main3"></div>
-        <!-- <div id="main1"></div> -->
+        <div id="main4"></div>
 
 
         <script>
@@ -453,5 +453,104 @@ under the License.
 
 
 
+
+
+
+
+        <script>
+
+            require(['echarts'/*, 'map/js/china' */], function (echarts) {
+
+                var animationDuration = 5000;
+                var animationDurationUpdate = 4000;
+                var option = {
+                    xAxis: {},
+                    yAxis: {},
+                    dataZoom: [
+                        {
+                        },
+                        // {
+                        //     type: 'inside'
+                        // }
+                    ],
+                    animationDuration: animationDuration,
+                    animationDurationUpdate: animationDurationUpdate,
+                    // animation: false,
+                    series: [{
+                        type: 'custom',
+                        renderItem: function (params, api) {
+                            return {
+                                type: 'group',
+                                position: api.coord([api.value(0), api.value(1)]),
+                                children: [{
+                                    type: 'rect',
+                                    shape: {
+                                        x: -50,
+                                        y: 50,
+                                        width: 100,
+                                        height: 150,
+                                        r: 10
+                                    },
+                                    style: {
+                                        fill: 'rgba(102,241,98,0.9)'
+                                    }
+                                }, {
+                                    type: 'circle',
+                                    shape: {
+                                        cx: -50,
+                                        cy: 50,
+                                        r: 30
+                                    },
+                                    style: {
+                                        text: 'data',
+                                        textFill: '#fff',
+                                        fill: 'blue'
+                                    }
+                                }]
+                            };
+                        },
+                        data: [[121, 333], [29, 312]]
+                    }]
+                };
+
+                var chart = testHelper.create(echarts, 'main4', {
+                    title: [
+                        'Style merge',
+                        '(1) dataZoom hide a data item, and then show it, ensure the fade in animation normal.',
+                        '(2) click button to setOption merge.'
+                    ],
+                    option: option,
+                    info: {
+                        animationDuration: animationDuration,
+                        animationDurationUpdate: animationDurationUpdate
+                    },
+                    buttons: [{
+                        text: 'merge style: border become blue, but background not changed',
+                        onclick: function () {
+                            chart.setOption({
+                                type: 'custom',
+                                renderItem: function (params, api) {
+                                    return {
+                                        type: 'group',
+                                        children: [{
+                                            type: 'rect',
+                                            style: {
+                                                stroke: 'red',
+                                                lineWidth: 5
+                                            }
+                                        }]
+                                    };
+                                }
+                            });
+                        }
+                    }]
+                });
+            });
+
+        </script>
+
+
+
+
     </body>
 </html>
\ No newline at end of file
diff --git a/test/tmp-base.html b/test/tmp-base.html
index 3f3693c..dbb80e6 100644
--- a/test/tmp-base.html
+++ b/test/tmp-base.html
@@ -48,9 +48,7 @@ under the License.
             var myChart;
             var option;
 
-            require([
-                'echarts'/*, 'map/js/china' */
-            ], function (echarts) {
+            require(['echarts'/*, 'map/js/china' */], function (echarts) {
 
                 // $.getJSON('./data/nutrients.json', function (data) {
                 // });


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