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/02 17:54:36 UTC
[incubator-echarts] 06/10: feature: support text animation on
custom series (via in `during`)
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 13093174904971bcacb8ab2bda079df079b0195d
Author: 100pah <su...@gmail.com>
AuthorDate: Tue May 19 04:45:26 2020 +0800
feature: support text animation on custom series (via in `during`)
---
src/chart/custom.ts | 88 ++++++++++++++--------
test/custom-feature.html | 190 +++++++++++++++++++++++++++++------------------
2 files changed, 174 insertions(+), 104 deletions(-)
diff --git a/src/chart/custom.ts b/src/chart/custom.ts
index a58633d..cfb9587 100644
--- a/src/chart/custom.ts
+++ b/src/chart/custom.ts
@@ -82,7 +82,7 @@ const inner = makeInner<{
customPathData: string;
customGraphicType: string;
customImagePath: CustomImageOption['style']['image'];
- customText: string;
+ // customText: string;
txConZ2Set: number;
orginalDuring: Element['updateDuringAnimation'];
customDuring: CustomZRPathOption['during'];
@@ -126,6 +126,7 @@ interface CustomGroupOption extends CustomBaseElementOption {
type: 'group';
width?: number;
height?: number;
+ // @deprecated
diffChildrenByName?: boolean;
children: CustomElementOption[];
$mergeChildren: false | 'byName' | 'byIndex';
@@ -134,6 +135,7 @@ interface CustomZRPathOption extends CustomDisplayableOption, Pick<PathProps, 's
}
interface CustomDuringElProps extends Partial<Pick<Element, TransformProps>> {
shape?: PathProps['shape'];
+ style?: { text: string };
}
interface CustomSVGPathOption extends CustomDisplayableOption {
type: 'path';
@@ -284,8 +286,7 @@ const Z2_SPECIFIED_BIT = {
emphasis: 1
} as const;
-const tmpDuringElProps = {} as CustomDuringElProps;
-
+const tmpDuringElProps = { style: {} } as CustomDuringElProps;
export type PrepareCustomInfo = (coordSys: CoordinateSystem) => {
coordSys: CustomSeriesRenderItemParamsCoordSys;
@@ -507,7 +508,7 @@ function createEl(elOption: CustomElementOption): Element {
}
else if (graphicType === 'text') {
el = new graphicUtil.Text({});
- inner(el).customText = (elOption.style as TextStyleProps).text;
+ // inner(el).customText = (elOption.style as TextStyleProps).text;
}
else if (graphicType === 'group') {
el = new graphicUtil.Group();
@@ -667,39 +668,58 @@ function updateElNormal(
zrUtil.hasOwn(elOption, 'info') && (inner(el).info = elOption.info);
}
- el.markRedraw();
+ styleOpt ? el.dirty() : el.markRedraw();
}
-function elUpdateDuringAnimation(this: graphicUtil.Path, key: string): void {
+function elUpdateDuringAnimation(this: Element, key: string): void {
const innerEl = inner(this);
// FIXME `this.markRedraw();` directly ?
innerEl.orginalDuring.call(this, key);
const customDuring = innerEl.customDuring;
+ const thisPath = this as graphicUtil.Path;
+ const thisText = this as graphicUtil.Text;
+ let dirtyStyle = false;
// Only provide these props. Usually other props do not need to be
// changed in animation during.
// Do not give `this` to user util really needed in future.
// Props in `shape` can be modified directly in the during callback.
- tmpDuringElProps.shape = this.shape;
- tmpDuringElProps.x = this.x;
- tmpDuringElProps.y = this.y;
- tmpDuringElProps.scaleX = this.scaleX;
- tmpDuringElProps.scaleX = this.scaleY;
- tmpDuringElProps.originX = this.originX;
- tmpDuringElProps.originY = this.originY;
- tmpDuringElProps.rotation = this.rotation;
+ const shapeCurr = tmpDuringElProps.shape = thisPath.shape;
+ const xCurr = tmpDuringElProps.x = this.x;
+ const yCurr = tmpDuringElProps.y = this.y;
+ const scaleXCurr = tmpDuringElProps.scaleX = this.scaleX;
+ const scaleYCurr = tmpDuringElProps.scaleY = this.scaleY;
+ const originXCurr = tmpDuringElProps.originX = this.originX;
+ const originYCurr = tmpDuringElProps.originY = this.originY;
+ const rotationCurr = tmpDuringElProps.rotation = this.rotation;
+
+ // PENDING:
+ // Do not expose other style in case that is not stable.
+ const isText = this.type === 'text';
+ const textCurr = tmpDuringElProps.style.text = isText ? thisText.style.text : null;
customDuring(tmpDuringElProps);
- tmpDuringElProps.shape !== this.shape && (this.shape = tmpDuringElProps.shape);
+ tmpDuringElProps.shape !== shapeCurr && (thisPath.shape = tmpDuringElProps.shape);
// Consider prop on prototype.
- tmpDuringElProps.x !== this.x && (this.x = tmpDuringElProps.x);
- tmpDuringElProps.y !== this.y && (this.y = tmpDuringElProps.y);
- tmpDuringElProps.scaleX !== this.scaleX && (this.scaleX = tmpDuringElProps.scaleX);
- tmpDuringElProps.scaleY !== this.scaleY && (this.scaleY = tmpDuringElProps.scaleY);
- tmpDuringElProps.originX !== this.originX && (this.originX = tmpDuringElProps.originX);
- tmpDuringElProps.originY !== this.originY && (this.originY = tmpDuringElProps.originY);
- tmpDuringElProps.rotation !== this.rotation && (this.rotation = tmpDuringElProps.rotation);
+ tmpDuringElProps.x !== xCurr && (this.x = tmpDuringElProps.x);
+ tmpDuringElProps.y !== yCurr && (this.y = tmpDuringElProps.y);
+ tmpDuringElProps.scaleX !== scaleXCurr && (this.scaleX = tmpDuringElProps.scaleX);
+ tmpDuringElProps.scaleY !== scaleYCurr && (this.scaleY = tmpDuringElProps.scaleY);
+ tmpDuringElProps.originX !== originXCurr && (this.originX = tmpDuringElProps.originX);
+ tmpDuringElProps.originY !== originYCurr && (this.originY = tmpDuringElProps.originY);
+ tmpDuringElProps.rotation !== rotationCurr && (this.rotation = tmpDuringElProps.rotation);
+
+ if (isText) {
+ const currTmpStl = tmpDuringElProps.style;
+ currTmpStl && currTmpStl.text !== textCurr && (thisText.style.text = currTmpStl.text, dirtyStyle = true);
+ }
+
+ dirtyStyle && this.dirty();
+ // markRedraw() will be called by default.
+
+ // FIXME: if in future meet the case that some prop will be both modified in `during` and `state`,
+ // consider the issue that the prop might be incorrect when return to "normal" state.
}
function updateElOnState(
@@ -1193,6 +1213,7 @@ function doCreateOrUpdate(
const elOptionType = elOption.type;
const elOptionShape = (elOption as CustomZRPathOption).shape;
const elOptionStyle = elOption.style;
+ let toBeReplacedIdx = -1;
if (el) {
const elInner = inner(el);
@@ -1210,13 +1231,14 @@ function doCreateOrUpdate(
&& zrUtil.hasOwn(elOptionStyle, 'image')
&& (elOptionStyle as CustomImageOption['style']).image !== elInner.customImagePath
)
- // FIXME test and remove this restriction?
- || (elOptionType === 'text'
- && zrUtil.hasOwn(elOptionStyle, 'text')
- && (elOptionStyle as TextStyleProps).text !== elInner.customText
- )
+ // // FIXME test and remove this restriction?
+ // || (elOptionType === 'text'
+ // && zrUtil.hasOwn(elOptionStyle, 'text')
+ // && (elOptionStyle as TextStyleProps).text !== elInner.customText
+ // )
) {
- group.remove(el);
+ // Should keep at the original index, otherwise "merge by index" will be incorrect.
+ toBeReplacedIdx = group.childrenRef().indexOf(el);
el = null;
}
}
@@ -1254,8 +1276,12 @@ function doCreateOrUpdate(
);
}
- // Always add whatever already added to ensure sequence.
- group.add(el);
+ if (toBeReplacedIdx >= 0) {
+ group.replaceAt(el, toBeReplacedIdx);
+ }
+ else {
+ group.add(el);
+ }
return el;
}
@@ -1316,7 +1342,7 @@ function doCreateOrUpdateAttachedTx(
const txConStlOptEmphasis = retrieveStyleOptionOnState(txConOptNormal, txConOptEmphasis, EMPHASIS);
updateElOnState(EMPHASIS, textContent, txConOptEmphasis, txConStlOptEmphasis, null, false, true);
- textContent.markRedraw();
+ txConStlOptNormal ? textContent.dirty() : textContent.markRedraw();
}
}
}
diff --git a/test/custom-feature.html b/test/custom-feature.html
index 56e106e..dabf8b1 100644
--- a/test/custom-feature.html
+++ b/test/custom-feature.html
@@ -636,7 +636,7 @@ under the License.
'echarts'/*, 'map/js/china' */
], function (echarts) {
var animationDuration = 5000;
- var animationDurationUpdate = 4000;
+ var animationDurationUpdate = 7000;
var animationEasingUpdate = 'elasticOut';
var angleLabel = ['Aries', 'Taurus', 'Gemini', 'Cancer', 'Leo', 'Virgo', 'Libra', 'Scorpius', 'Sagittarius', 'Capricornus', 'Aquarius', 'Pisces'];
var angleRoundValue = angleLabel.length;
@@ -644,85 +644,148 @@ under the License.
var angleStep = angleRoundValue / 90;
var barWidthValue = 0.4;
var radiusStep = 4;
- var colors = {
- 'A': { stroke: 'green', fill: 'rgba(0,152,0,0.6)' },
- 'B': { stroke: 'red', fill: 'rgba(152,0,0,0.6)' },
- 'C': { stroke: 'blue', fill: 'rgba(0,0, 152,0.6)' },
- };
- var allData = [[
- [[1, 3, 'A']],
- [[2, 6, 'B']],
- [[3, 9, 'C']],
- ], [
- [[1, 12, 'A']],
- [[2, 16, 'B']],
- [[3, 14, 'C']],
- ], [
- [[1, 17, 'A']],
- [[2, 22, 'B']],
- [[3, 19, 'C']],
- ]];
- var currentDataIndex = 0;
+ var colors = [
+ { border: 'green', inner: 'rgba(0,152,0,0.6)' },
+ { border: 'red', inner: 'rgba(152,0,0,0.6)' },
+ { border: 'blue', inner: 'rgba(0,0, 152,0.6)' },
+ ];
+ var currentDataIndex = 0;
+ var datasourceList = [
+ [[3, 6, 9]],
+ [[12, 16, 14]],
+ [[17, 22, 19]],
+ ];
+ var barValOnRadiusList = [1, 2, 3];
+
+ // PENDING:
+ // The radius max should be fixed rather than change dynamically.
+ // If need to support dynamic coord sys while animation:
+ // (A) The `api.coord` should be able to accept a customized extent and
+ // return value on the middle state.
+ // or (B) Use "data interpolate".
function getMaxRadius() {
var radius = 0;
- for (var j = 0; j < allData.length; j++) {
- var data = allData[j];
- for (var i = 0; i < data.length; i++) {
- radius = Math.max(radius, getSpiralValueRadius(data[i][0][0], data[i][0][1]));
+ for (var k = 0; k < barValOnRadiusList.length; k++) {
+ for (var i = 0; i < datasourceList.length; i++) {
+ var row = datasourceList[i][0];
+ for (var j = 0; j < row.length; j++) {
+ var valOnAngle = row[j];
+ radius = Math.max(
+ radius,
+ getSpiralValueRadius(barValOnRadiusList[k], valOnAngle)
+ );
+ }
}
}
return Math.ceil(radius * 1.2);
}
- function getSpiralValueRadius(valRadius, valAngle) {
- return valRadius + radiusStep * (valAngle / angleRoundValue);
+ function getSpiralValueRadius(valOnRadius, valOnAngle) {
+ return valOnRadius + radiusStep * (valOnAngle / angleRoundValue);
+ }
+
+ function addShapes(api, children, valOnRadius, valOnAngle, color) {
+ addPolygon(api, children, valOnRadius, valOnAngle, color);
+ addLabel(api, children, valOnRadius, valOnAngle, color);
}
- function makeShapePoints(api, valueRadius, valueAngle) {
+ function addPolygon(api, children, valOnRadius, valOnAngle, color) {
+ children.push({
+ type: 'polygon',
+ shape: {
+ points: makeShapePoints(api, valOnRadius, valOnAngle),
+ valOnAngle: valOnAngle
+ },
+ style: {
+ lineWidth: 1,
+ fill: color.inner,
+ stroke: color.border
+ },
+ during: function (elProps) {
+ elProps.shape.points = makeShapePoints(
+ api, valOnRadius, elProps.shape.valOnAngle
+ );
+ }
+ });
+ }
+
+ function makeShapePoints(api, valOnRadius, valOnAngle) {
var points = [];
- for (var iAngleVal = 0, end = valueAngle + angleStep; iAngleVal < end; iAngleVal += angleStep) {
- iAngleVal > valueAngle && (iAngleVal = valueAngle);
- var iRadiusVal = getSpiralValueRadius(valueRadius - barWidthValue, iAngleVal);
+ for (var iAngleVal = 0, end = valOnAngle + angleStep; iAngleVal < end; iAngleVal += angleStep) {
+ iAngleVal > valOnAngle && (iAngleVal = valOnAngle);
+ var iRadiusVal = getSpiralValueRadius(valOnRadius - barWidthValue, iAngleVal);
var point = api.coord([iRadiusVal, iAngleVal]).slice(0, 2);
points.push(point);
}
- for (var iAngleVal = valueAngle; iAngleVal > -angleStep; iAngleVal -= angleStep) {
+ for (var iAngleVal = valOnAngle; iAngleVal > -angleStep; iAngleVal -= angleStep) {
iAngleVal < 0 && (iAngleVal = 0);
- var iRadiusVal = getSpiralValueRadius(valueRadius + barWidthValue, iAngleVal);
+ var iRadiusVal = getSpiralValueRadius(valOnRadius + barWidthValue, iAngleVal);
var point = api.coord([iRadiusVal, iAngleVal]).slice(0, 2);
points.push(point);
}
return points;
}
- function renderItem(params, api) {
- var valueRadius = api.value(0);
- var valueAngle = api.value(1);
- var name = api.value(2);
- return {
- type: 'polygon',
+ function addLabel(api, children, valOnRadius, valOnAngle, color) {
+ var point = makeLabelPosition(api, valOnRadius, valOnAngle);
+ children.push({
+ type: 'text',
+ x: point[0],
+ y: point[1],
shape: {
- points: makeShapePoints(api, valueRadius, valueAngle),
- valueAngle: valueAngle
+ valOnAngle: valOnAngle
},
style: {
- lineWidth: 1,
- fill: colors[name].fill,
- stroke: colors[name].stroke
+ text: getText(valOnAngle),
+ fill: color.inner,
+ stroke: '#fff',
+ lineWidth: 3,
+ fontSize: 16,
+ align: 'center',
+ verticalAlign: 'middle'
},
+ z2: 50,
during: function (elProps) {
- elProps.shape.points = makeShapePoints(
- api, valueRadius, elProps.shape.valueAngle
- );
+ var iValOnAngle = elProps.shape.valOnAngle;
+ var point = makeLabelPosition(api, valOnRadius, iValOnAngle);
+ elProps.x = point[0];
+ elProps.y = point[1];
+ elProps.style.text = getText(iValOnAngle);
}
+ });
+
+ function getText(iValOnAngle) {
+ return (iValOnAngle / angleRoundValue * 100).toFixed(0) + '%'
+ }
+ }
+
+ function makeLabelPosition(api, valOnRadius, valOnAngle) {
+ var iRadiusVal = getSpiralValueRadius(valOnRadius, valOnAngle);
+ return api.coord([iRadiusVal, valOnAngle + 1 / iRadiusVal / (2 * Math.PI) * angleRoundValue]);
+ }
+
+ function renderItem(params, api) {
+ var children = [];
+
+ addShapes(api, children, barValOnRadiusList[0], api.value(0), colors[0]);
+ addShapes(api, children, barValOnRadiusList[1], api.value(1), colors[1]);
+ addShapes(api, children, barValOnRadiusList[2], api.value(2), colors[2]);
+
+ return {
+ type: 'group',
+ children: children
};
}
var option = {
+ // animation: false,
animationDuration: animationDuration,
animationDurationUpdate: animationDurationUpdate,
animationEasingUpdate: animationEasingUpdate,
+ dataset: {
+ source: datasourceList[currentDataIndex]
+ },
angleAxis: {
type: 'value',
// splitLine: { show: false },
@@ -745,49 +808,30 @@ under the License.
min: 0,
max: getMaxRadius()
},
- polar: {
- },
+ polar: {},
tooltip: {},
series: [{
type: 'custom',
- name: 'A',
- coordinateSystem: 'polar',
- renderItem: renderItem,
- data: allData[currentDataIndex][0]
- }, {
- type: 'custom',
- name: 'B',
coordinateSystem: 'polar',
- renderItem: renderItem,
- data: allData[currentDataIndex][1]
- }, {
- type: 'custom',
- name: 'C',
- coordinateSystem: 'polar',
- renderItem: renderItem,
- data: allData[currentDataIndex][2]
+ renderItem: renderItem
}]
};
var chart = testHelper.create(echarts, 'spiral2', {
title: [
- 'animation: ',
+ 'polygon animation should be corrent. (coordSys extent is fixed)',
],
option: option,
buttons: [{
text: 'next',
onclick: function () {
currentDataIndex++;
- currentDataIndex >= allData.length && (currentDataIndex = 0);
+ currentDataIndex >= datasourceList.length && (currentDataIndex = 0);
chart.setOption({
- series: [{
- data: allData[currentDataIndex][0]
- }, {
- data: allData[currentDataIndex][1]
- }, {
- data: allData[currentDataIndex][2]
- }]
- })
+ dataset: {
+ source: datasourceList[currentDataIndex]
+ }
+ });
}
}, {
text: 'enable animation',
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org
For additional commands, e-mail: commits-help@echarts.apache.org