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 <[email protected]> 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: [email protected] For additional commands, e-mail: [email protected]
