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:40 UTC
[incubator-echarts] 10/10: feature: support clipPath and clipPath
animation in custom series.
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 1dffebc9bf846f8ea0d75fe97407e8d12a261eab
Author: 100pah <su...@gmail.com>
AuthorDate: Thu May 28 17:26:56 2020 +0800
feature: support clipPath and clipPath animation in custom series.
---
src/chart/custom.ts | 124 +++++++++++++++--------
test/custom-transition.html | 241 +++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 323 insertions(+), 42 deletions(-)
diff --git a/src/chart/custom.ts b/src/chart/custom.ts
index 7af3ec0..2bb85c9 100644
--- a/src/chart/custom.ts
+++ b/src/chart/custom.ts
@@ -105,6 +105,8 @@ interface CustomBaseElementOption extends Partial<Pick<
info?: CustomExtraElementInfo;
// `false` means remove the textContent.
textContent?: CustomTextOption | false;
+ // `false` means remove the clipPath
+ clipPath?: CustomZRPathOption | false;
// updateDuringAnimation
during?(elProps: CustomDuringElProps): void;
};
@@ -1180,7 +1182,7 @@ function createOrUpdate(
group: ViewRootGroup,
data: List<CustomSeriesModel>
): Element {
- el = doCreateOrUpdate(el, dataIndex, elOption, seriesModel, group, data, true);
+ el = doCreateOrUpdate(el, dataIndex, elOption, seriesModel, group, true);
el && data.setItemGraphicEl(dataIndex, el);
return el;
@@ -1192,7 +1194,6 @@ function doCreateOrUpdate(
elOption: CustomElementOption,
seriesModel: CustomSeriesModel,
group: ViewRootGroup,
- data: List<CustomSeriesModel>,
isRoot: boolean
): Element {
@@ -1213,37 +1214,12 @@ function doCreateOrUpdate(
}
elOption = elOption || {} as CustomElementOption;
- const elOptionType = elOption.type;
- const elOptionShape = (elOption as CustomZRPathOption).shape;
- const elOptionStyle = elOption.style;
let toBeReplacedIdx = -1;
- if (el) {
- const elInner = inner(el);
- if (
- // || elOption.$merge === false
- // If `elOptionType` is `null`, follow the merge principle.
- (elOptionType != null
- && elOptionType !== elInner.customGraphicType
- )
- || (elOptionType === 'path'
- && hasOwnPathData(elOptionShape)
- && getPathData(elOptionShape) !== elInner.customPathData
- )
- || (elOptionType === 'image'
- && 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
- // )
- ) {
- // Should keep at the original index, otherwise "merge by index" will be incorrect.
- toBeReplacedIdx = group.childrenRef().indexOf(el);
- el = null;
- }
+ if (el && doesElNeedRecreate(el, elOption)) {
+ // Should keep at the original index, otherwise "merge by index" will be incorrect.
+ toBeReplacedIdx = group.childrenRef().indexOf(el);
+ el = null;
}
const isInit = !el;
@@ -1252,6 +1228,7 @@ function doCreateOrUpdate(
el = createEl(elOption);
}
else {
+ // FIMXE:NEXT unified clearState?
// If in some case the performance issue arised, consider
// do not clearState but update cached normal state directly.
el.clearStates();
@@ -1265,6 +1242,10 @@ function doCreateOrUpdate(
el, dataIndex, elOption, seriesModel, isInit, attachedTxInfoTmp
);
+ doCreateOrUpdateClipPath(
+ el, dataIndex, elOption, seriesModel, isInit
+ );
+
const stateOptEmphasis = retrieveStateOption(elOption, EMPHASIS);
const styleOptEmphasis = retrieveStyleOptionOnState(elOption, stateOptEmphasis, EMPHASIS);
@@ -1273,9 +1254,9 @@ function doCreateOrUpdate(
updateZ(el, elOption, seriesModel, attachedTxInfoTmp);
- if (elOptionType === 'group') {
+ if (elOption.type === 'group') {
mergeChildren(
- el as graphicUtil.Group, dataIndex, elOption as CustomGroupOption, seriesModel, data
+ el as graphicUtil.Group, dataIndex, elOption as CustomGroupOption, seriesModel
);
}
@@ -1289,6 +1270,72 @@ function doCreateOrUpdate(
return el;
}
+// `el` must not be null/undefined.
+function doesElNeedRecreate(el: Element, elOption: CustomElementOption): boolean {
+ const elInner = inner(el);
+ const elOptionType = elOption.type;
+ const elOptionShape = (elOption as CustomZRPathOption).shape;
+ const elOptionStyle = elOption.style;
+ return (
+ // || elOption.$merge === false
+ // If `elOptionType` is `null`, follow the merge principle.
+ (elOptionType != null
+ && elOptionType !== elInner.customGraphicType
+ )
+ || (elOptionType === 'path'
+ && hasOwnPathData(elOptionShape)
+ && getPathData(elOptionShape) !== elInner.customPathData
+ )
+ || (elOptionType === 'image'
+ && 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
+ // )
+ );
+}
+
+function doCreateOrUpdateClipPath(
+ el: Element,
+ dataIndex: number,
+ elOption: CustomElementOption,
+ seriesModel: CustomSeriesModel,
+ isInit: boolean
+): void {
+ // Based on the "merge" principle, if no clipPath provided,
+ // do nothing. The exists clip will be totally removed only if
+ // `el.clipPath` is `false`. Otherwise it will be merged/replaced.
+ const clipPathOpt = elOption.clipPath;
+ if (clipPathOpt === false) {
+ if (el && el.getClipPath()) {
+ el.removeClipPath();
+ }
+ }
+ else if (clipPathOpt) {
+ let clipPath = el.getClipPath();
+ if (clipPath && doesElNeedRecreate(clipPath, clipPathOpt)) {
+ clipPath = null;
+ }
+ if (!clipPath) {
+ clipPath = createEl(clipPathOpt) as graphicUtil.Path;
+ if (__DEV__) {
+ zrUtil.assert(
+ clipPath instanceof graphicUtil.Path,
+ 'Only any type of `path` can be used in `clipPath`, rather than ' + clipPath.type + '.'
+ );
+ }
+ el.setClipPath(clipPath);
+ }
+ updateElNormal(
+ clipPath, dataIndex, clipPathOpt, null, null, seriesModel, isInit, false
+ );
+ }
+ // If not define `clipPath` in option, do nothing unnecessary.
+}
+
function doCreateOrUpdateAttachedTx(
el: Element,
dataIndex: number,
@@ -1440,8 +1487,7 @@ function mergeChildren(
el: graphicUtil.Group,
dataIndex: number,
elOption: CustomGroupOption,
- seriesModel: CustomSeriesModel,
- data: List<CustomSeriesModel>
+ seriesModel: CustomSeriesModel
): void {
const newChildren = elOption.children;
@@ -1462,8 +1508,7 @@ function mergeChildren(
newChildren: newChildren || [],
dataIndex: dataIndex,
seriesModel: seriesModel,
- group: el,
- data: data
+ group: el
});
return;
}
@@ -1480,7 +1525,6 @@ function mergeChildren(
newChildren[index],
seriesModel,
el,
- data,
false
);
}
@@ -1497,8 +1541,7 @@ type DiffGroupContext = {
newChildren: CustomElementOption[],
dataIndex: number,
seriesModel: CustomSeriesModel,
- group: graphicUtil.Group,
- data: List<CustomSeriesModel>
+ group: graphicUtil.Group
};
function diffGroupChildren(context: DiffGroupContext) {
(new DataDiffer(
@@ -1534,7 +1577,6 @@ function processAddUpdate(
childOption,
context.seriesModel,
context.group,
- context.data,
false
);
}
diff --git a/test/custom-transition.html b/test/custom-transition.html
index 77a6aab..2d8c718 100644
--- a/test/custom-transition.html
+++ b/test/custom-transition.html
@@ -28,6 +28,7 @@ under the License.
<script src="lib/jquery.min.js"></script>
<script src="lib/facePrint.js"></script>
<script src="lib/testHelper.js"></script>
+ <script src="./custom-transition-texture.js"></script>
<link rel="stylesheet" href="lib/reset.css" />
</head>
<body>
@@ -38,7 +39,8 @@ under the License.
<div id="init-animation-additive"></div>
<div id="spiral-fixed-extent"></div>
<div id="spiral-dynamic-extent"></div>
- <div id="texture-bar"></div>
+ <div id="texture-bar-by-clipPath"></div>
+ <!-- <div id="texture-bar-texture-maker"></div> -->
@@ -623,5 +625,242 @@ under the License.
+
+
+
+
+
+ <script>
+ require(['echarts'], function (echarts) {
+ var _animationDuration = 1000;
+ var _animationDurationUpdate = 1000;
+ var _animationEasingUpdate = 'elasticOut';
+ var _datasourceList = [
+ [[1, 156]],
+ [[1, 54]],
+ [[1, 131]],
+ [[1, 32]],
+ [[1, 103]],
+ [[1, 66]],
+ ];
+ var _valOnRadianMax = 200;
+ var _outerRadius = 100;
+ var _innerRadius = 85;
+ var _pointerInnerRadius = 40;
+ var _insidePanelRadius = 65;
+ var _currentDataIndex = 0;
+
+ function renderItem(params, api) {
+ var children = [];
+ var dataIdx = params.dataIndex;
+ var valOnRadian = api.value(1);
+ var coords = api.coord([api.value(0), valOnRadian]);
+ var polarEndRadian = coords[3];
+ var imageStyle = {
+ image: window.BAR_ROUND_GRADIENT_TEXTURE,
+ x: params.coordSys.cx - _outerRadius,
+ y: params.coordSys.cy - _outerRadius,
+ width: _outerRadius * 2,
+ height: _outerRadius * 2
+ };
+
+ return {
+ type: 'group',
+ children: [{
+ type: 'image',
+ style: imageStyle,
+ clipPath: {
+ type: 'sector',
+ shape: {
+ cx: params.coordSys.cx,
+ cy: params.coordSys.cy,
+ r: _outerRadius,
+ r0: _innerRadius,
+ startAngle: 0,
+ // polor: anticlockwise-positive radian
+ // sector: clockwise-positive radian
+ endAngle: -polarEndRadian
+ },
+ }
+ }, {
+ type: 'image',
+ style: imageStyle,
+ clipPath: {
+ type: 'polygon',
+ shape: {
+ points: makePionterPoints(params, polarEndRadian),
+ polarEndRadian: polarEndRadian
+ },
+ during: function (elProps) {
+ elProps.shape.points = makePionterPoints(params, elProps.shape.polarEndRadian);
+ }
+ },
+ }, {
+ type: 'circle',
+ shape: {
+ cx: params.coordSys.cx,
+ cy: params.coordSys.cy,
+ r: _insidePanelRadius
+ },
+ style: {
+ fill: '#fff',
+ shadowBlur: 25,
+ shadowOffsetX: 0,
+ shadowOffsetY: 0,
+ shadowColor: 'rgb(0,0,50)'
+ }
+ }, {
+ type: 'text',
+ shape: {
+ valOnRadian: valOnRadian
+ },
+ style: {
+ text: makeText(valOnRadian),
+ fontSize: 40,
+ x: params.coordSys.cx,
+ y: params.coordSys.cy,
+ fill: 'rgb(0,50,190)',
+ align: 'center',
+ verticalAlign: 'middle',
+ },
+ during: function (elProps) {
+ elProps.style.text = makeText(elProps.shape.valOnRadian);
+ }
+ }]
+ };
+ }
+
+ function convertToPolarPoint(renderItemParams, radius, radian) {
+ return [
+ Math.cos(radian) * radius + renderItemParams.coordSys.cx,
+ -Math.sin(radian) * radius + renderItemParams.coordSys.cy
+ ];
+ }
+
+ function makePionterPoints(renderItemParams, polarEndRadian) {
+ return [
+ convertToPolarPoint(renderItemParams, _outerRadius, polarEndRadian),
+ convertToPolarPoint(renderItemParams, _outerRadius, polarEndRadian + Math.PI * 0.03),
+ convertToPolarPoint(renderItemParams, _pointerInnerRadius, polarEndRadian)
+ ];
+ }
+
+ function makeText(valOnRadian) {
+ return (valOnRadian / _valOnRadianMax * 100).toFixed(0) + '%'
+ }
+
+ var option = {
+ animationDuration: _animationDuration,
+ animationDurationUpdate: _animationDurationUpdate,
+ animationEasingUpdate: _animationEasingUpdate,
+ dataset: {
+ source: _datasourceList[_currentDataIndex]
+ },
+ tooltip: {},
+ angleAxis: {
+ type: 'value',
+ startAngle: 0,
+ axisLine: { show: false },
+ axisTick: { show: false },
+ axisLabel: { show: false },
+ splitLine: { show: false },
+ min: 0,
+ max: _valOnRadianMax
+ },
+ radiusAxis: {
+ type: 'value',
+ axisLine: { show: false },
+ axisTick: { show: false },
+ axisLabel: { show: false },
+ splitLine: { show: false }
+ },
+ polar: {},
+ series: [{
+ type: 'custom',
+ coordinateSystem: 'polar',
+ renderItem: renderItem
+ }]
+ };
+
+ var chart = testHelper.create(echarts, 'texture-bar-by-clipPath', {
+ title: [
+ 'Angle gradient | clipPath animation',
+ 'click **next** to check the transition animation in both bar and text.'
+ ],
+ option: option,
+ height: 300,
+ buttons: [{
+ text: 'next',
+ onclick: function () {
+ _currentDataIndex++;
+ _currentDataIndex >= _datasourceList.length && (_currentDataIndex = 0);
+ chart.setOption({
+ dataset: {
+ source: _datasourceList[_currentDataIndex]
+ }
+ });
+ }
+ }]
+ });
+ });
+ </script>
+
+
+ <script>
+ require(['echarts'], function (echarts) {
+ var chart = testHelper.create(echarts, 'texture-bar-texture-maker', {
+ title: [],
+ width: 200,
+ height: 200,
+ option: {},
+ buttons: [{
+ text: 'dataURL',
+ onclick: function () {
+ console.log(chart.getDataURL({
+ type: 'png',
+ backgroundColor: 'rgba(0,0,0,0)'
+ }));
+ }
+ }]
+ });
+ if (!chart) {
+ return;
+ }
+
+ var zr = chart.getZr();
+ var eles = [];
+ var extent = [0.0, 0.95];
+ var count = 200;
+ var unit = (extent[1] - extent[0]) / count;
+ var baseColor = 'rgb(0,0,255)';
+ for (var i = 0; i < count; i++) {
+ var oo = extent[0] + (count - i) * unit;
+ var color = echarts.color.modifyHSL(baseColor, null, null, oo);
+ var startAngle = 2 * Math.PI / count * i;
+ var endAngle = Math.min((2 * Math.PI / count * (i + 1) + 0.05), Math.PI * 2);
+ zr.add(new echarts.graphic.Sector({
+ type: 'sector',
+ shape: {
+ cx: 100,
+ cy: 100,
+ r: 100,
+ r0: 60,
+ startAngle: startAngle,
+ endAngle: endAngle
+ },
+ style: {
+ fill: color
+ }
+ }));
+ }
+ });
+ </script>
+
+
+
+
+
+
+
</body>
</html>
\ No newline at end of file
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org
For additional commands, e-mail: commits-help@echarts.apache.org