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 2020/06/27 04:38:44 UTC
[incubator-echarts] 02/03: feature: custom series,
add "extra" in el options for users to config their own properties
for animation.
This is an automated email from the ASF dual-hosted git repository.
sushuang pushed a commit to branch custom-series-enhance
in repository https://gitbox.apache.org/repos/asf/incubator-echarts.git
commit 4d76f23e3727b296de85d8827ee05bd30d3bf2a3
Author: 100pah <su...@gmail.com>
AuthorDate: Wed Jun 24 00:45:27 2020 +0800
feature: custom series, add "extra" in el options for users to config their own properties for animation.
---
src/chart/custom.ts | 116 ++++++++++++++++++++++-----------
test/custom-transition.html | 36 +++++-----
test/custom-transition2.html | 152 ++++++++++++++++++++++++++++++++++++++-----
3 files changed, 233 insertions(+), 71 deletions(-)
diff --git a/src/chart/custom.ts b/src/chart/custom.ts
index a29d872..05a9fd7 100644
--- a/src/chart/custom.ts
+++ b/src/chart/custom.ts
@@ -78,7 +78,6 @@ import {
import Transformable from 'zrender/src/core/Transformable';
import { ItemStyleProps } from '../model/mixin/itemStyle';
import { cloneValue } from 'zrender/src/animation/Animator';
-import { number } from '../export';
const inner = makeInner<{
@@ -133,8 +132,8 @@ interface CustomBaseElementOption extends Partial<Pick<
textContent?: CustomTextOption | false;
// `false` means remove the clipPath
clipPath?: CustomZRPathOption | false;
- // Shape can be set in any el option for custom prop for annimation duration.
- shape?: TransitionAnyOption;
+ // `extra` can be set in any el option for custom prop for annimation duration.
+ extra?: TransitionAnyOption;
// updateDuringAnimation
during?(params: typeof customDuringAPI): void;
};
@@ -197,8 +196,8 @@ interface CustomSeriesRenderItemAPI extends
CustomSeriesRenderItemCoordinateSystemAPI,
Pick<ExtensionAPI, 'getWidth' | 'getHeight' | 'getZr' | 'getDevicePixelRatio'> {
value(dim: DimensionLoose, dataIndexInside?: number): ParsedValue;
- style(extra?: ZRStyleProps, dataIndexInside?: number): ZRStyleProps;
- styleEmphasis(extra?: ZRStyleProps, dataIndexInside?: number): ZRStyleProps;
+ style(userProps?: ZRStyleProps, dataIndexInside?: number): ZRStyleProps;
+ styleEmphasis(userProps?: ZRStyleProps, dataIndexInside?: number): ZRStyleProps;
visual(visualType: string, dataIndexInside?: number): ReturnType<List['getItemVisual']>;
barLayout(opt: Omit<Parameters<typeof getLayoutOnAxis>[0], 'axis'>): ReturnType<typeof getLayoutOnAxis>;
currentSeriesIndices(): ReturnType<GlobalModel['getCurrentSeriesIndices']>;
@@ -264,6 +263,7 @@ interface CustomSeriesOption extends
interface LooseElementProps extends ElementProps {
style?: ZRStyleProps;
shape?: Dictionary<unknown>;
+ extra?: Dictionary<unknown>;
}
// Also compat with ec4, where
@@ -635,7 +635,8 @@ function updateElNormal(
const allProps = {} as ElementProps;
const elDisplayable = el.isGroup ? null : el as Displayable;
- prepareShapeUpdate(el, elOption, allProps, transFromProps, isInit);
+ prepareShapeOrExtraUpdate('shape', el, elOption, allProps, transFromProps, isInit);
+ prepareShapeOrExtraUpdate('extra', el, elOption, allProps, transFromProps, isInit);
prepareTransformUpdate(el, elOption, allProps, transFromProps, isInit);
const txCfgOpt = attachedTxInfo && attachedTxInfo.normal.cfg;
@@ -723,7 +724,8 @@ function updateElNormal(
}
// See [STRATEGY_TRANSITION]
-function prepareShapeUpdate(
+function prepareShapeOrExtraUpdate(
+ mainAttr: 'shape' | 'extra',
el: Element,
elOption: CustomElementOption,
allProps: LooseElementProps,
@@ -731,58 +733,58 @@ function prepareShapeUpdate(
isInit: boolean
): void {
- const shapeOpt = (elOption as CustomElementOption).shape;
- if (!shapeOpt) {
+ const attrOpt: Dictionary<unknown> & TransitionAnyOption = (elOption as any)[mainAttr];
+ if (!attrOpt) {
return;
}
- const elShape = (el as LooseElementProps).shape;
- let tranFromShapeProps: LooseElementProps['shape'];
+ const elPropsInAttr = (el as LooseElementProps)[mainAttr];
+ let transFromPropsInAttr: Dictionary<unknown>;
- const enterFrom = shapeOpt.enterFrom;
+ const enterFrom = attrOpt.enterFrom;
if (isInit && enterFrom) {
- !tranFromShapeProps && (tranFromShapeProps = transFromProps.shape = {});
+ !transFromPropsInAttr && (transFromPropsInAttr = transFromProps[mainAttr] = {});
const enterFromKeys = keys(enterFrom);
for (let i = 0; i < enterFromKeys.length; i++) {
// `enterFrom` props are not necessarily also declared in `shape`/`style`/...,
// for example, `opacity` can only declared in `enterFrom` but not in `style`.
const key = enterFromKeys[i];
// Do not clone, animator will perform that clone.
- tranFromShapeProps[key] = enterFrom[key];
+ transFromPropsInAttr[key] = enterFrom[key];
}
}
- if (!isInit && elShape && shapeOpt.transition) {
- !tranFromShapeProps && (tranFromShapeProps = transFromProps.shape = {});
- const transitionKeys = normalizeToArray(shapeOpt.transition);
+ if (!isInit && elPropsInAttr && attrOpt.transition) {
+ !transFromPropsInAttr && (transFromPropsInAttr = transFromProps[mainAttr] = {});
+ const transitionKeys = normalizeToArray(attrOpt.transition);
for (let i = 0; i < transitionKeys.length; i++) {
const key = transitionKeys[i];
- const elVal = elShape[key];
+ const elVal = elPropsInAttr[key];
if (__DEV__) {
- checkTansitionRefer(key, (shapeOpt as any)[key], elVal);
+ checkTansitionRefer(key, (attrOpt as any)[key], elVal);
}
// Do not clone, see `checkTansitionRefer`.
- tranFromShapeProps[key] = elVal;
+ transFromPropsInAttr[key] = elVal;
}
}
- const allPropsShape = allProps.shape = {} as LooseElementProps['shape'];
- const shapeOptKeys = keys(shapeOpt);
- for (let i = 0; i < shapeOptKeys.length; i++) {
- const key = shapeOptKeys[i];
+ const allPropsInAttr = allProps[mainAttr] = {} as Dictionary<unknown>;
+ const keysInAttr = keys(attrOpt);
+ for (let i = 0; i < keysInAttr.length; i++) {
+ const key = keysInAttr[i];
// To avoid share one object with different element, and
// to avoid user modify the object inexpectedly, have to clone.
- allPropsShape[key] = cloneValue((shapeOpt as any)[key]);
+ allPropsInAttr[key] = cloneValue((attrOpt as any)[key]);
}
- const leaveTo = shapeOpt.leaveTo;
+ const leaveTo = attrOpt.leaveTo;
if (leaveTo) {
const leaveToProps = getOrCreateLeaveToPropsFromEl(el);
- const leaveToShapeProps = leaveToProps.shape || (leaveToProps.shape = {});
+ const leaveToPropsInAttr: Dictionary<unknown> = leaveToProps[mainAttr] || (leaveToProps[mainAttr] = {});
const leaveToKeys = keys(leaveTo);
for (let i = 0; i < leaveToKeys.length; i++) {
const key = leaveToKeys[i];
- leaveToShapeProps[key] = leaveTo[key];
+ leaveToPropsInAttr[key] = leaveTo[key];
}
}
}
@@ -955,19 +957,28 @@ const customDuringAPI = {
return tmpDuringScope.el[key];
},
setShape(key: string, val: unknown) {
- // In custom series, el other than Path can also has `shape` for intepolating props.
- const shape = (tmpDuringScope.el as any).shape || ((tmpDuringScope.el as any).shape = {});
+ if (__DEV__) {
+ assertNotReserved(key);
+ }
+ const shape = (tmpDuringScope.el as graphicUtil.Path).shape
+ || ((tmpDuringScope.el as graphicUtil.Path).shape = {});
shape[key] = val;
tmpDuringScope.isShapeDirty = true;
return this;
},
getShape(key: string): unknown {
- const shape = (tmpDuringScope.el as any).shape;
+ if (__DEV__) {
+ assertNotReserved(key);
+ }
+ const shape = (tmpDuringScope.el as graphicUtil.Path).shape;
if (shape) {
return shape[key];
}
},
setStyle(key: string, val: unknown) {
+ if (__DEV__) {
+ assertNotReserved(key);
+ }
const style = (tmpDuringScope.el as Displayable).style;
if (style) {
style[key] = val;
@@ -976,13 +987,42 @@ const customDuringAPI = {
return this;
},
getStyle(key: string): unknown {
+ if (__DEV__) {
+ assertNotReserved(key);
+ }
const style = (tmpDuringScope.el as Displayable).style;
if (style) {
return style[key];
}
+ },
+ setExtra(key: string, val: unknown) {
+ if (__DEV__) {
+ assertNotReserved(key);
+ }
+ const extra = (tmpDuringScope.el as LooseElementProps).extra
+ || ((tmpDuringScope.el as LooseElementProps).extra = {});
+ extra[key] = val;
+ return this;
+ },
+ getExtra(key: string): unknown {
+ if (__DEV__) {
+ assertNotReserved(key);
+ }
+ const extra = (tmpDuringScope.el as LooseElementProps).extra;
+ if (extra) {
+ return extra[key];
+ }
}
};
+function assertNotReserved(key: string) {
+ if (__DEV__) {
+ if (key === 'transition' || key === 'enterFrom' || key === 'leaveTo') {
+ throw new Error('key must not be "' + key + '"');
+ }
+ }
+}
+
function elUpdateDuringAnimation(this: Element, key: string): void {
// Do not provide "percent" until some requirements come.
// Because consider thies case:
@@ -1297,7 +1337,7 @@ function makeRenderItem(
* @public
* @param dataIndexInside by default `currDataIndexInside`.
*/
- function style(extra?: ZRStyleProps, dataIndexInside?: number): ZRStyleProps {
+ function style(userProps?: ZRStyleProps, dataIndexInside?: number): ZRStyleProps {
if (__DEV__) {
warnDeprecated('api.style', 'Please write literal style directly instead.');
}
@@ -1326,10 +1366,10 @@ function makeRenderItem(
: null;
const textConfig = graphicUtil.createTextConfig(textStyle, labelModel, opt, false);
- preFetchFromExtra(extra, itemStyle);
+ preFetchFromExtra(userProps, itemStyle);
itemStyle = convertToEC4StyleForCustomSerise(itemStyle, textStyle, textConfig);
- extra && applyExtraAfter(itemStyle, extra);
+ userProps && applyUserPropsAfter(itemStyle, userProps);
(itemStyle as LegacyStyleProps).legacy = true;
return itemStyle;
@@ -1340,7 +1380,7 @@ function makeRenderItem(
* @public
* @param dataIndexInside by default `currDataIndexInside`.
*/
- function styleEmphasis(extra?: ZRStyleProps, dataIndexInside?: number): ZRStyleProps {
+ function styleEmphasis(userProps?: ZRStyleProps, dataIndexInside?: number): ZRStyleProps {
if (__DEV__) {
warnDeprecated('api.styleEmphasis', 'Please write literal style directly instead.');
}
@@ -1359,16 +1399,16 @@ function makeRenderItem(
: null;
const textConfig = graphicUtil.createTextConfig(textStyle, labelModel, null, true);
- preFetchFromExtra(extra, itemStyle);
+ preFetchFromExtra(userProps, itemStyle);
itemStyle = convertToEC4StyleForCustomSerise(itemStyle, textStyle, textConfig);
- extra && applyExtraAfter(itemStyle, extra);
+ userProps && applyUserPropsAfter(itemStyle, userProps);
(itemStyle as LegacyStyleProps).legacy = true;
return itemStyle;
}
- function applyExtraAfter(itemStyle: ZRStyleProps, extra: ZRStyleProps): void {
+ function applyUserPropsAfter(itemStyle: ZRStyleProps, extra: ZRStyleProps): void {
for (const key in extra) {
if (hasOwn(extra, key)) {
(itemStyle as any)[key] = (extra as any)[key];
diff --git a/test/custom-transition.html b/test/custom-transition.html
index 27c0a0b..24bd2fc 100644
--- a/test/custom-transition.html
+++ b/test/custom-transition.html
@@ -177,7 +177,9 @@ under the License.
children.push({
type: 'polygon',
shape: {
- points: makeShapePoints(api, valOnRadius, valOnAngle),
+ points: makeShapePoints(api, valOnRadius, valOnAngle)
+ },
+ extra: {
valOnAngle: valOnAngle,
transition: 'valOnAngle'
},
@@ -188,7 +190,7 @@ under the License.
},
during: function (apiDuring) {
apiDuring.setShape('points', makeShapePoints(
- api, valOnRadius, apiDuring.getShape('valOnAngle')
+ api, valOnRadius, apiDuring.getExtra('valOnAngle')
));
}
});
@@ -217,7 +219,7 @@ under the License.
type: 'text',
x: point[0],
y: point[1],
- shape: {
+ extra: {
valOnAngle: valOnAngle,
transition: 'valOnAngle'
},
@@ -232,7 +234,7 @@ under the License.
},
z2: 50,
during: function (apiDuring) {
- var iValOnAngle = apiDuring.getShape('valOnAngle');
+ var iValOnAngle = apiDuring.getExtra('valOnAngle');
var point = makeLabelPosition(api, valOnRadius, iValOnAngle);
apiDuring.setTransform('x', point[0]).setTransform('y', point[1]);
apiDuring.setStyle('text', getText(iValOnAngle));
@@ -403,7 +405,9 @@ under the License.
children.push({
type: 'polygon',
shape: {
- points: makeShapePoints(params, widthRadius, startRadius, endRadian),
+ points: makeShapePoints(params, widthRadius, startRadius, endRadian)
+ },
+ extra: {
widthRadius: widthRadius,
startRadius: startRadius,
endRadian: endRadian,
@@ -417,9 +421,9 @@ under the License.
during: function (apiDuring) {
apiDuring.setShape('points', makeShapePoints(
params,
- apiDuring.getShape('widthRadius'),
- apiDuring.getShape('startRadius'),
- apiDuring.getShape('endRadian')
+ apiDuring.getExtra('widthRadius'),
+ apiDuring.getExtra('startRadius'),
+ apiDuring.getExtra('endRadian')
));
}
});
@@ -460,7 +464,7 @@ under the License.
type: 'text',
x: point[0],
y: point[1],
- shape: {
+ extra: {
startRadius: startRadius,
endRadian: endRadian,
widthRadius: widthRadius,
@@ -481,11 +485,11 @@ under the License.
},
z2: 50,
during: function (apiDuring) {
- var endRadian = apiDuring.getShape('endRadian');
+ var endRadian = apiDuring.getExtra('endRadian');
var point = makeLabelPosition(
params,
- apiDuring.getShape('widthRadius'),
- apiDuring.getShape('startRadius'),
+ apiDuring.getExtra('widthRadius'),
+ apiDuring.getExtra('startRadius'),
endRadian
);
apiDuring.setTransform('x', point[0]).setTransform('y', point[1]);
@@ -663,6 +667,8 @@ under the License.
type: 'polygon',
shape: {
points: makePionterPoints(params, polarEndRadian),
+ },
+ extra: {
polarEndRadian: polarEndRadian,
transition: 'polarEndRadian',
enterFrom: { polarEndRadian: 0 }
@@ -670,7 +676,7 @@ under the License.
during: function (apiDuring) {
apiDuring.setShape(
'points',
- makePionterPoints(params, apiDuring.getShape('polarEndRadian'))
+ makePionterPoints(params, apiDuring.getExtra('polarEndRadian'))
);
}
},
@@ -690,7 +696,7 @@ under the License.
}
}, {
type: 'text',
- shape: {
+ extra: {
valOnRadian: valOnRadian,
transition: 'valOnRadian',
enterFrom: { valOnRadian: 0 }
@@ -706,7 +712,7 @@ under the License.
enterFrom: { opacity: 0 }
},
during: function (apiDuring) {
- apiDuring.setStyle('text', makeText(apiDuring.getShape('valOnRadian')));
+ apiDuring.setStyle('text', makeText(apiDuring.getExtra('valOnRadian')));
}
}]
};
diff --git a/test/custom-transition2.html b/test/custom-transition2.html
index a2a4caf..3e9eb56 100644
--- a/test/custom-transition2.html
+++ b/test/custom-transition2.html
@@ -148,15 +148,12 @@ under the License.
var clusterIdx = api.value(2);
var isNewCluster = clusterIdx === api.value(3);
- var shape = {
- cx: 0,
- cy: 0,
- r: 10,
+ var extra = {
transition: []
};
var contentColor = colorAll[clusterIdx];
- addColorTransition(shape, contentColor, 'content');
+ addColorTransition(extra, contentColor, 'content');
// var borderColor = isNewCluster ? '#333' : '#fff';
// addColorTransition(shape, borderColor, 'border');
@@ -167,7 +164,12 @@ under the License.
// scaleX: isNewCluster ? 1.2 : 1,
// scaleY: isNewCluster ? 1.2 : 1,
// transition: ['scaleX', 'scaleY'],
- shape: shape,
+ shape: {
+ cx: 0,
+ cy: 0,
+ r: 10
+ },
+ extra: extra,
style: {
fill: contentColor,
stroke: '#333',
@@ -197,10 +199,10 @@ under the License.
function getColorInTransition(apiDuring, key) {
var colorArr = [
- apiDuring.getShape(key + 'R'),
- apiDuring.getShape(key + 'G'),
- apiDuring.getShape(key + 'B'),
- apiDuring.getShape(key + 'A')
+ apiDuring.getExtra(key + 'R'),
+ apiDuring.getExtra(key + 'G'),
+ apiDuring.getExtra(key + 'B'),
+ apiDuring.getExtra(key + 'A')
];
return echarts.color.stringify(colorArr, 'rgba');
}
@@ -216,10 +218,16 @@ under the License.
return {
type: 'circle',
shape: {
- cx: isNaN(center[0]) ? 0 : center[0],
- cy: isNaN(center[1]) ? 0 : center[1],
- r: isNaN(radius) ? 0 : radius,
- // transition: 'newCluIdx'
+ cx: 0,
+ cy: 0,
+ r: 0
+ },
+ extra: {
+ cxNext: isNaN(center[0]) ? 0 : center[0],
+ cyNext: isNaN(center[1]) ? 0 : center[1],
+ rNext: isNaN(radius) ? 0 : radius,
+ renderNumber: ++renderNumber,
+ transition: 'renderNumber'
},
style: {
fill: null,
@@ -229,12 +237,118 @@ under the License.
// opacity: 0
},
during: function (apiDuring) {
- apiDuring.setShape('cx', Math.random() * 10);
- // var cluIdx = apiDuring.setShape('newCluIdx');
- // var percent = Math.ceil(cluIdx) - cluIdx;
- // var opacity = (Math.ceil(cluIdx) - cluIdx) > 0.5 ?
+ var currNum = apiDuring.getExtra('renderNumber');
+ if (apiDuring.getStyle('opacity') < 0.9) {
+ apiDuring
+ .setShape('cx', apiDuring.getExtra('cxNext'))
+ .setShape('cy', apiDuring.getExtra('cyNext'))
+ .setShape('r', apiDuring.getExtra('rNext'));
+ }
+ apiDuring.setStyle('opacity', 1 - (renderNumber - currNum));
}
};
+
+ // return {
+ // type: 'group',
+ // children: [{
+ // type: 'circle',
+ // shape: {
+ // cx: 0,
+ // cy: 0,
+ // r: 0,
+ // },
+ // extra: {
+ // renderNumber: ++renderNumber,
+ // cxNext: isNaN(center[0]) ? 0 : center[0],
+ // cyNext: isNaN(center[1]) ? 0 : center[1],
+ // rNext: isNaN(radius) ? 0 : radius,
+ // transition: 'renderNumber'
+ // },
+ // style: {
+ // fill: null,
+ // stroke: '#bbb',
+ // lineDash: [5, 5],
+ // lineWidth: 3
+ // },
+ // during: function (apiDuring) {
+ // var currNum = apiDuring.getExtra('renderNumber');
+ // var progress = 0.5 - (renderNumber - currNum);
+ // if (progress >= 0) {
+ // apiDuring
+ // .setShape('cx', apiDuring.getExtra('cxNext'))
+ // .setShape('cy', apiDuring.getExtra('cyNext'))
+ // .setShape('r', apiDuring.getExtra('rNext'));
+ // }
+ // var opacity = Math.abs(progress) / 0.5;
+ // apiDuring.setStyle('opacity', opacity);
+ // }
+ // }, {
+ // type: 'circle',
+ // shape: {
+ // cx: 0,
+ // cy: 0,
+ // r: 0,
+ // },
+ // extra: {
+ // renderNumber: ++renderNumber,
+ // cxNext: isNaN(center[0]) ? 0 : center[0],
+ // cyNext: isNaN(center[1]) ? 0 : center[1],
+ // rNext: isNaN(radius) ? 0 : radius,
+ // transition: 'renderNumber'
+ // },
+ // style: {
+ // fill: null,
+ // stroke: '#bbb',
+ // lineDash: [5, 5],
+ // lineWidth: 3
+ // },
+ // during: function (apiDuring) {
+ // var currNum = apiDuring.getExtra('renderNumber');
+ // var progress = 0.5 - (renderNumber - currNum);
+ // if (progress >= 0) {
+ // apiDuring
+ // .setShape('cx', apiDuring.getExtra('cxNext'))
+ // .setShape('cy', apiDuring.getExtra('cyNext'))
+ // .setShape('r', apiDuring.getExtra('rNext'));
+ // }
+ // var opacity = Math.abs(progress) / 0.5;
+ // apiDuring.setStyle('opacity', opacity);
+ // }
+ // }]
+ // };
+ // return {
+ // type: 'circle',
+ // shape: {
+ // cx: 0,
+ // cy: 0,
+ // r: 0,
+ // },
+ // extra: {
+ // renderNumber: ++renderNumber,
+ // cxNext: isNaN(center[0]) ? 0 : center[0],
+ // cyNext: isNaN(center[1]) ? 0 : center[1],
+ // rNext: isNaN(radius) ? 0 : radius,
+ // transition: 'renderNumber'
+ // },
+ // style: {
+ // fill: null,
+ // stroke: '#bbb',
+ // lineDash: [5, 5],
+ // lineWidth: 3
+ // },
+ // during: function (apiDuring) {
+ // var currNum = apiDuring.getExtra('renderNumber');
+ // var progress = 0.5 - (renderNumber - currNum);
+ // if (progress >= 0) {
+ // apiDuring
+ // .setShape('cx', apiDuring.getExtra('cxNext'))
+ // .setShape('cy', apiDuring.getExtra('cyNext'))
+ // .setShape('r', apiDuring.getExtra('rNext'));
+ // }
+ // var opacity = Math.abs(progress) / 0.5;
+ // apiDuring.setStyle('opacity', opacity);
+ // }
+ // };
}
function makeStepOption(option, stepResult) {
@@ -268,6 +382,8 @@ under the License.
});
}
+ var renderNumber = 0;
+
var option = {
timeline: {
top: 'center',
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org
For additional commands, e-mail: commits-help@echarts.apache.org