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 c56e2dcd1e553780b91c6a004c1a06b770f082bf Author: 100pah <[email protected]> AuthorDate: Wed May 13 04:08:33 2020 +0800 feature: add duration animation for custom series. --- src/chart/custom.ts | 54 +++++- test/custom-feature.html | 442 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 494 insertions(+), 2 deletions(-) diff --git a/src/chart/custom.ts b/src/chart/custom.ts index 3ed15b9..a58633d 100644 --- a/src/chart/custom.ts +++ b/src/chart/custom.ts @@ -84,6 +84,8 @@ const inner = makeInner<{ customImagePath: CustomImageOption['style']['image']; customText: string; txConZ2Set: number; + orginalDuring: Element['updateDuringAnimation']; + customDuring: CustomZRPathOption['during']; }, Element>(); type CustomExtraElementInfo = Dictionary<unknown>; @@ -103,6 +105,8 @@ interface CustomBaseElementOption extends Partial<Pick< info?: CustomExtraElementInfo; // `false` means remove the textContent. textContent?: CustomTextOption | false; + // updateDuringAnimation + during?(elProps: CustomDuringElProps): void; }; interface CustomDisplayableOption extends CustomBaseElementOption, Partial<Pick< Displayable, 'zlevel' | 'z' | 'z2' | 'invisible' @@ -128,6 +132,9 @@ interface CustomGroupOption extends CustomBaseElementOption { } interface CustomZRPathOption extends CustomDisplayableOption, Pick<PathProps, 'shape'> { } +interface CustomDuringElProps extends Partial<Pick<Element, TransformProps>> { + shape?: PathProps['shape']; +} interface CustomSVGPathOption extends CustomDisplayableOption { type: 'path'; shape?: { @@ -277,7 +284,7 @@ const Z2_SPECIFIED_BIT = { emphasis: 1 } as const; - +const tmpDuringElProps = {} as CustomDuringElProps; export type PrepareCustomInfo = (coordSys: CoordinateSystem) => { @@ -640,6 +647,19 @@ function updateElNormal( zrUtil.hasOwn(elOption, 'silent') && (el.silent = elOption.silent); zrUtil.hasOwn(elOption, 'ignore') && (el.ignore = elOption.ignore); + const customDuringMounted = el.updateDuringAnimation === elUpdateDuringAnimation; + if (elOption.during) { + const innerEl = inner(el); + if (!customDuringMounted) { + innerEl.orginalDuring = el.updateDuringAnimation; + el.updateDuringAnimation = elUpdateDuringAnimation; + } + innerEl.customDuring = elOption.during; + } + else if (customDuringMounted) { + el.updateDuringAnimation = inner(el).orginalDuring; + } + if (!isTextContent) { // `elOption.info` enables user to mount some info on // elements and use them in event handlers. @@ -650,6 +670,38 @@ function updateElNormal( el.markRedraw(); } +function elUpdateDuringAnimation(this: graphicUtil.Path, key: string): void { + const innerEl = inner(this); + // FIXME `this.markRedraw();` directly ? + innerEl.orginalDuring.call(this, key); + const customDuring = innerEl.customDuring; + + // 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; + + customDuring(tmpDuringElProps); + + tmpDuringElProps.shape !== this.shape && (this.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); +} + function updateElOnState( state: DisplayStateNonNormal, el: Element, diff --git a/test/custom-feature.html b/test/custom-feature.html index 03457ed..56e106e 100644 --- a/test/custom-feature.html +++ b/test/custom-feature.html @@ -38,7 +38,10 @@ under the License. <div id="main0"></div> <div id="main2"></div> <div id="main3"></div> - <!-- <div id="main1"></div> --> + <div id="init-animation-additive"></div> + <!-- <div id="spiral"></div> --> + <div id="spiral2"></div> + <div id="texture-bar"></div> <script> @@ -365,6 +368,443 @@ under the License. + <script> + + require(['echarts'], function (echarts) { + + var animationDuration = 5000; + var animationDurationUpdate = 4000; + var option = { + xAxis: {}, + yAxis: {}, + dataZoom: [ + { type: 'slider' }, + { type: 'inside' } + ], + animationDuration: animationDuration, + animationDurationUpdate: animationDurationUpdate, + 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: { + fill: 'blue' + }, + textContent: { + text: 'data', + style: { + fill: '#fff' + } + } + }] + }; + }, + data: [[121, 333], [29, 312]] + }] + }; + + var chart = testHelper.create(echarts, 'init-animation-additive', { + 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> + + + + +<!-- + + <script> + require([ + 'echarts'/*, 'map/js/china' */ + ], function (echarts) { + var animationDuration = 5000; + var animationDurationUpdate = 4000; + var angleLabel = ['Aries', 'Taurus', 'Gemini', 'Cancer', 'Leo', 'Virgo', 'Libra', 'Scorpius', 'Sagittarius', 'Capricornus', 'Aquarius', 'Pisces']; + var angleRoundValue = angleLabel.length; + var radiusOffset = 10; + 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; + + 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])); + } + } + return Math.ceil(radius * 1.2); + } + + function getSpiralValueRadius(valRadius, valAngle) { + return valRadius + radiusStep * (valAngle / angleRoundValue); + } + + function renderItem(params, api) { + var valueRadius = api.value(0); + var valueAngle = api.value(1); + var points = []; + for (var iAngleVal = 0, end = valueAngle + angleStep; iAngleVal < end; iAngleVal += angleStep) { + iAngleVal > valueAngle && (iAngleVal = valueAngle); + var iRadiusVal = getSpiralValueRadius(valueRadius - barWidthValue, iAngleVal); + var point = api.coord([iRadiusVal, iAngleVal]).slice(0, 2); + points.push(point); + } + for (var iAngleVal = valueAngle; iAngleVal > -angleStep; iAngleVal -= angleStep) { + iAngleVal < 0 && (iAngleVal = 0); + var iRadiusVal = getSpiralValueRadius(valueRadius + barWidthValue, iAngleVal); + var point = api.coord([iRadiusVal, iAngleVal]).slice(0, 2); + points.push(point); + } + var name = api.value(2); + return { + type: 'polygon', + shape: { points: points }, + style: { + lineWidth: 1, + fill: colors[name].fill, + stroke: colors[name].stroke + } + }; + } + + var option = { + animationDuration: animationDuration, + animationDurationUpdate: animationDurationUpdate, + angleAxis: { + type: 'value', + // splitLine: { show: false }, + splitArea: {show: true}, + axisLabel: { + formatter: function(val) { + return angleLabel[val]; + }, + color: 'rgba(0,0,0,0.2)' + }, + axisLine: { lineStyle: { color: 'rgba(0,0,0,0.2)' } }, + min: 0, + max: angleRoundValue + }, + radiusAxis: { + type: 'value', + splitLine: { show: false }, + axisLabel: { color: 'rgba(0,0,0,0.2)' }, + axisLine: { lineStyle: { color: 'rgba(0,0,0,0.2)' } }, + min: 0, + max: getMaxRadius() + }, + 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] + }] + }; + + var chart = testHelper.create(echarts, 'spiral', { + title: [ + 'animation: ', + ], + option: option, + buttons: [{ + text: 'next', + onclick: function () { + currentDataIndex++; + currentDataIndex >= allData.length && (currentDataIndex = 0); + chart.setOption({ + series: [{ + data: allData[currentDataIndex][0] + }, { + data: allData[currentDataIndex][1] + }, { + data: allData[currentDataIndex][2] + }] + }) + } + }, { + text: 'enable animation', + onclick: function () { + chart.setOption({ animation: true }); + } + }, { + text: 'disable animation', + onclick: function () { + chart.setOption({ animation: false }); + } + }] + }); + }); + </script> + --> + + + + + + + + <script> + require([ + 'echarts'/*, 'map/js/china' */ + ], function (echarts) { + var animationDuration = 5000; + var animationDurationUpdate = 4000; + var animationEasingUpdate = 'elasticOut'; + var angleLabel = ['Aries', 'Taurus', 'Gemini', 'Cancer', 'Leo', 'Virgo', 'Libra', 'Scorpius', 'Sagittarius', 'Capricornus', 'Aquarius', 'Pisces']; + var angleRoundValue = angleLabel.length; + var radiusOffset = 10; + 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; + + 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])); + } + } + return Math.ceil(radius * 1.2); + } + + function getSpiralValueRadius(valRadius, valAngle) { + return valRadius + radiusStep * (valAngle / angleRoundValue); + } + + function makeShapePoints(api, valueRadius, valueAngle) { + var points = []; + for (var iAngleVal = 0, end = valueAngle + angleStep; iAngleVal < end; iAngleVal += angleStep) { + iAngleVal > valueAngle && (iAngleVal = valueAngle); + var iRadiusVal = getSpiralValueRadius(valueRadius - barWidthValue, iAngleVal); + var point = api.coord([iRadiusVal, iAngleVal]).slice(0, 2); + points.push(point); + } + for (var iAngleVal = valueAngle; iAngleVal > -angleStep; iAngleVal -= angleStep) { + iAngleVal < 0 && (iAngleVal = 0); + var iRadiusVal = getSpiralValueRadius(valueRadius + 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', + shape: { + points: makeShapePoints(api, valueRadius, valueAngle), + valueAngle: valueAngle + }, + style: { + lineWidth: 1, + fill: colors[name].fill, + stroke: colors[name].stroke + }, + during: function (elProps) { + elProps.shape.points = makeShapePoints( + api, valueRadius, elProps.shape.valueAngle + ); + } + }; + } + + var option = { + animationDuration: animationDuration, + animationDurationUpdate: animationDurationUpdate, + animationEasingUpdate: animationEasingUpdate, + angleAxis: { + type: 'value', + // splitLine: { show: false }, + splitArea: {show: true}, + axisLabel: { + formatter: function(val) { + return angleLabel[val]; + }, + color: 'rgba(0,0,0,0.2)' + }, + axisLine: { lineStyle: { color: 'rgba(0,0,0,0.2)' } }, + min: 0, + max: angleRoundValue + }, + radiusAxis: { + type: 'value', + splitLine: { show: false }, + axisLabel: { color: 'rgba(0,0,0,0.2)' }, + axisLine: { lineStyle: { color: 'rgba(0,0,0,0.2)' } }, + min: 0, + max: getMaxRadius() + }, + 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] + }] + }; + + var chart = testHelper.create(echarts, 'spiral2', { + title: [ + 'animation: ', + ], + option: option, + buttons: [{ + text: 'next', + onclick: function () { + currentDataIndex++; + currentDataIndex >= allData.length && (currentDataIndex = 0); + chart.setOption({ + series: [{ + data: allData[currentDataIndex][0] + }, { + data: allData[currentDataIndex][1] + }, { + data: allData[currentDataIndex][2] + }] + }) + } + }, { + text: 'enable animation', + onclick: function () { + chart.setOption({ animation: true }); + } + }, { + text: 'disable animation', + onclick: function () { + chart.setOption({ animation: false }); + } + }] + }); + }); + </script> + + </body> </html> \ No newline at end of file --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
