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:15 UTC
[incubator-echarts] 01/01: Try to support animation from and
custom-series animation.
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