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/11/01 09:39:09 UTC

[incubator-echarts] 04/05: feature: [decal] support decal to custom series.

This is an automated email from the ASF dual-hosted git repository.

sushuang pushed a commit to branch decal-custom
in repository https://gitbox.apache.org/repos/asf/incubator-echarts.git

commit 59e0ad35a53d6e6b31dfcf53d32c13c1811997b0
Author: 100pah <su...@gmail.com>
AuthorDate: Sun Nov 1 17:33:30 2020 +0800

    feature: [decal] support decal to custom series.
---
 src/chart/custom.ts                              | 111 +++++++++++++++++------
 test/ut/jest.config.js                           |   4 +-
 test/ut/spec/component/graphic/setOption.test.ts |   1 -
 test/ut/spec/series/custom.test.ts               |  72 +++++++++++++++
 4 files changed, 156 insertions(+), 32 deletions(-)

diff --git a/src/chart/custom.ts b/src/chart/custom.ts
index 4d4a1a6..8db0f0f 100644
--- a/src/chart/custom.ts
+++ b/src/chart/custom.ts
@@ -53,7 +53,9 @@ import {
     BlurScope,
     SeriesDataType,
     OrdinalRawValue,
-    PayloadAnimationPart
+    PayloadAnimationPart,
+    DecalObject,
+    InnerDecalObject
 } from '../util/types';
 import Element, { ElementProps, ElementTextConfig } from 'zrender/src/Element';
 import prepareCartesian2d from '../coord/cartesian/prepareCustom';
@@ -69,7 +71,7 @@ import ExtensionAPI from '../ExtensionAPI';
 import Displayable from 'zrender/src/graphic/Displayable';
 import Axis2D from '../coord/cartesian/Axis2D';
 import { RectLike } from 'zrender/src/core/BoundingRect';
-import { PathProps } from 'zrender/src/graphic/Path';
+import { PathProps, PathStyleProps } from 'zrender/src/graphic/Path';
 import { ImageStyleProps } from 'zrender/src/graphic/Image';
 import { CoordinateSystem } from '../coord/CoordinateSystem';
 import { TextStyleProps } from 'zrender/src/graphic/Text';
@@ -89,6 +91,8 @@ import {
 } from 'zrender/src/tool/morphPath';
 import { AnimationEasing } from 'zrender/src/animation/easing';
 import * as matrix from 'zrender/src/core/matrix';
+import { PatternObject } from 'zrender/src/graphic/Pattern';
+import { createOrUpdatePatternFromDecal } from '../util/decal';
 
 
 const inner = makeInner<{
@@ -187,6 +191,11 @@ interface CustomGroupOption extends CustomBaseElementOption {
 }
 interface CustomZRPathOption extends CustomDisplayableOption, ShapeMorphingOption {
     shape?: PathProps['shape'] & TransitionAnyOption;
+    style?: CustomDisplayableOption['style'] & {
+        decal?: DecalObject;
+        // Only internal usage. Any user specified value will be overwritten.
+        __decalPattern?: PatternObject;
+    };
 }
 interface CustomSVGPathOption extends CustomDisplayableOption, ShapeMorphingOption {
     type: 'path';
@@ -219,14 +228,19 @@ type CustomElementOption = CustomZRPathOption | CustomSVGPathOption | CustomImag
 type CustomElementOptionOnState = CustomDisplayableOptionOnState | CustomImageOptionOnState;
 
 
-interface CustomSeriesRenderItemAPI extends
+export interface CustomSeriesRenderItemAPI extends
         CustomSeriesRenderItemCoordinateSystemAPI,
         Pick<ExtensionAPI, 'getWidth' | 'getHeight' | 'getZr' | 'getDevicePixelRatio'> {
     value(dim: DimensionLoose, dataIndexInside?: number): ParsedValue;
     ordinalRawValue(dim: DimensionLoose, dataIndexInside?: number): ParsedValue | OrdinalRawValue;
     style(userProps?: ZRStyleProps, dataIndexInside?: number): ZRStyleProps;
     styleEmphasis(userProps?: ZRStyleProps, dataIndexInside?: number): ZRStyleProps;
-    visual(visualType: string, dataIndexInside?: number): ReturnType<List['getItemVisual']>;
+    visual<VT extends NonStyleVisualProps | StyleVisualProps>(
+        visualType: VT,
+        dataIndexInside?: number
+    ): VT extends NonStyleVisualProps ? DefaultDataVisual[VT]
+        : VT extends StyleVisualProps ? PathStyleProps[typeof STYLE_VISUAL_TYPE[VT]]
+        : void;
     barLayout(opt: Omit<Parameters<typeof getLayoutOnAxis>[0], 'axis'>): ReturnType<typeof getLayoutOnAxis>;
     currentSeriesIndices(): ReturnType<GlobalModel['getCurrentSeriesIndices']>;
     font(opt: Parameters<typeof labelStyleHelper.getFont>[0]): ReturnType<typeof labelStyleHelper.getFont>;
@@ -245,7 +259,7 @@ interface CustomSeriesRenderItemCoordinateSystemAPI {
         dataItem: OptionDataValue | OptionDataValue[]
     ): number | number[];
 }
-interface CustomSeriesRenderItemParams {
+export interface CustomSeriesRenderItemParams {
     context: Dictionary<unknown>;
     seriesId: string;
     seriesName: string;
@@ -301,15 +315,18 @@ const STYLE_VISUAL_TYPE = {
     color: 'fill',
     borderColor: 'stroke'
 } as const;
+type StyleVisualProps = keyof typeof STYLE_VISUAL_TYPE;
 
-const VISUAL_PROPS = {
+const NON_STYLE_VISUAL_PROPS = {
     symbol: 1,
     symbolSize: 1,
     symbolKeepAspect: 1,
     legendSymbol: 1,
     visualMeta: 1,
-    liftZ: 1
+    liftZ: 1,
+    decal: 1
 } as const;
+type NonStyleVisualProps = keyof typeof NON_STYLE_VISUAL_PROPS;
 
 const EMPHASIS = 'emphasis' as const;
 const NORMAL = 'normal' as const;
@@ -482,7 +499,7 @@ class CustomSeriesView extends ChartView {
             });
             data.each(function (newIdx) {
                 createOrUpdateItem(
-                    null, newIdx, renderItem(newIdx, payload), customSeries, group, data, null
+                    api, null, newIdx, renderItem(newIdx, payload), customSeries, group, data, null
                 );
             });
         }
@@ -500,7 +517,7 @@ class CustomSeriesView extends ChartView {
             ))
             .add(function (newIdx) {
                 createOrUpdateItem(
-                    null, newIdx, renderItem(newIdx, payload), customSeries, group,
+                    api, null, newIdx, renderItem(newIdx, payload), customSeries, group,
                     data, null
                 );
             })
@@ -523,7 +540,7 @@ class CustomSeriesView extends ChartView {
                     oldEl = null;
                 }
                 createOrUpdateItem(
-                    oldEl, newIdx, renderItem(newIdx, payload), customSeries, group,
+                    api, oldEl, newIdx, renderItem(newIdx, payload), customSeries, group,
                     data, morphPreparation
                 );
                 morphPreparation.applyMorphing();
@@ -536,7 +553,7 @@ class CustomSeriesView extends ChartView {
                     removeElementDirectly(oldEl, group);
                 }
                 createOrUpdateItem(
-                    null, newIdx, renderItem(newIdx, payload), customSeries, group,
+                    api, null, newIdx, renderItem(newIdx, payload), customSeries, group,
                     data, morphPreparation
                 );
                 morphPreparation.applyMorphing();
@@ -550,7 +567,7 @@ class CustomSeriesView extends ChartView {
 
                 for (let i = 0; i < newLen; i++) {
                     createOrUpdateItem(
-                        null, newIndices[i], renderItem(newIndices[i], payload), customSeries, group,
+                        api, null, newIndices[i], renderItem(newIndices[i], payload), customSeries, group,
                         data, morphPreparation
                     );
                 }
@@ -599,7 +616,7 @@ class CustomSeriesView extends ChartView {
         }
         for (let idx = params.start; idx < params.end; idx++) {
             const el = createOrUpdateItem(
-                null, idx, renderItem(idx, payload), customSeries, this.group, data, null
+                null, null, idx, renderItem(idx, payload), customSeries, this.group, data, null
             );
             el.traverse(setIncrementalAndHoverLayer);
         }
@@ -779,6 +796,8 @@ function createEl(elOption: CustomElementOption): Element {
  * @return if `isMorphTo`, return `allPropsFinal`.
  */
 function updateElNormal(
+    // Can be null/undefined
+    api: ExtensionAPI,
     el: Element,
     // Whether be a morph target.
     isMorphTo: boolean,
@@ -823,6 +842,17 @@ function updateElNormal(
         );
     }
 
+    if (styleOpt) {
+        let decalPattern;
+        const decalObj = isPath(el) ? (styleOpt as CustomZRPathOption['style']).decal : null;
+        if (api && decalObj) {
+            (decalObj as InnerDecalObject).dirty = true;
+            decalPattern = createOrUpdatePatternFromDecal(decalObj, api);
+        }
+        // Always overwrite in case user specify this prop.
+        (styleOpt as CustomZRPathOption['style']).__decalPattern = decalPattern;
+    }
+
     !isMorphTo && prepareStyleTransitionFrom(el, null, elOption, styleOpt, transFromProps, isInit);
 
     if (elDisplayable) {
@@ -861,10 +891,22 @@ function applyPropsFinal(
     const elDisplayable = el.isGroup ? null : el as Displayable;
 
     if (elDisplayable && styleOpt) {
+
+        const decalPattern = (styleOpt as CustomZRPathOption['style']).__decalPattern;
+        let originalDecalObj;
+        if (decalPattern) {
+            originalDecalObj = (styleOpt as CustomZRPathOption['style']).decal;
+            (styleOpt as any).decal = decalPattern;
+        }
+
         // PENDING: here the input style object is used directly.
         // Good for performance but bad for compatibility control.
         elDisplayable.useStyle(styleOpt);
 
+        if (decalPattern) {
+            (styleOpt as CustomZRPathOption['style']).decal = originalDecalObj;
+        }
+
         // When style object changed, how to trade the existing animation?
         // It is probably conplicated and not needed to cover all the cases.
         // But still need consider the case:
@@ -1794,22 +1836,25 @@ function makeRenderItem(
      * @public
      * @param dataIndexInside by default `currDataIndexInside`.
      */
-    function visual(
-        visualType: keyof DefaultDataVisual,
+    function visual<VT extends NonStyleVisualProps | StyleVisualProps>(
+        visualType: VT,
         dataIndexInside?: number
-    ): ReturnType<List['getItemVisual']> {
+    ): VT extends NonStyleVisualProps ? DefaultDataVisual[VT]
+            : VT extends StyleVisualProps ? PathStyleProps[typeof STYLE_VISUAL_TYPE[VT]]
+            : never {
+
         dataIndexInside == null && (dataIndexInside = currDataIndexInside);
 
         if (hasOwn(STYLE_VISUAL_TYPE, visualType)) {
             const style = data.getItemVisual(dataIndexInside, 'style');
             return style
-                ? style[STYLE_VISUAL_TYPE[visualType as keyof typeof STYLE_VISUAL_TYPE]] as any
+                ? style[STYLE_VISUAL_TYPE[visualType as StyleVisualProps]] as any
                 : null;
         }
         // Only support these visuals. Other visual might be inner tricky
         // for performance (like `style`), do not expose to users.
-        if (hasOwn(VISUAL_PROPS, visualType)) {
-            return data.getItemVisual(dataIndexInside, visualType);
+        if (hasOwn(NON_STYLE_VISUAL_PROPS, visualType)) {
+            return data.getItemVisual(dataIndexInside, visualType as NonStyleVisualProps) as any;
         }
     }
 
@@ -1858,6 +1903,7 @@ function wrapEncodeDef(data: List<CustomSeriesModel>): Dictionary<number[]> {
 }
 
 function createOrUpdateItem(
+    api: ExtensionAPI,
     el: Element,
     dataIndex: number,
     elOption: CustomElementOption,
@@ -1878,7 +1924,7 @@ function createOrUpdateItem(
         removeElementDirectly(el, group);
         return;
     }
-    el = doCreateOrUpdateEl(el, dataIndex, elOption, seriesModel, group, true, morphPreparation);
+    el = doCreateOrUpdateEl(api, el, dataIndex, elOption, seriesModel, group, true, morphPreparation);
     el && data.setItemGraphicEl(dataIndex, el);
 
     enableHoverEmphasis(el, elOption.focus, elOption.blurScope);
@@ -1887,6 +1933,7 @@ function createOrUpdateItem(
 }
 
 function doCreateOrUpdateEl(
+    api: ExtensionAPI,
     el: Element,
     dataIndex: number,
     elOption: CustomElementOption,
@@ -1951,6 +1998,7 @@ function doCreateOrUpdateEl(
     );
 
     const pendingAllPropsFinal = updateElNormal(
+        api,
         el,
         thisElIsMorphTo,
         dataIndex,
@@ -1979,7 +2027,7 @@ function doCreateOrUpdateEl(
 
     if (elOption.type === 'group') {
         mergeChildren(
-            el as graphicUtil.Group, dataIndex, elOption as CustomGroupOption, seriesModel, morphPreparation
+            api, el as graphicUtil.Group, dataIndex, elOption as CustomGroupOption, seriesModel, morphPreparation
         );
     }
 
@@ -2052,7 +2100,7 @@ function doCreateOrUpdateClipPath(
             el.setClipPath(clipPath);
         }
         updateElNormal(
-            clipPath, null, dataIndex, clipPathOpt, null, null, seriesModel, isInit, false
+            null, clipPath, null, dataIndex, clipPathOpt, null, null, seriesModel, isInit, false
         );
     }
     // If not define `clipPath` in option, do nothing unnecessary.
@@ -2105,7 +2153,7 @@ function doCreateOrUpdateAttachedTx(
             const txConStlOptNormal = txConOptNormal && txConOptNormal.style;
 
             updateElNormal(
-                textContent, null, dataIndex, txConOptNormal, txConStlOptNormal, null, seriesModel, isInit, true
+                null, textContent, null, dataIndex, txConOptNormal, txConStlOptNormal, null, seriesModel, isInit, true
             );
             for (let i = 0; i < STATES.length; i++) {
                 const stateName = STATES[i];
@@ -2212,6 +2260,7 @@ function retrieveStyleOptionOnState(
 // child (otherwise the total indicies of the children array have to be modified).
 // User can remove a single child by set its `ignore` as `true`.
 function mergeChildren(
+    api: ExtensionAPI,
     el: graphicUtil.Group,
     dataIndex: number,
     elOption: CustomGroupOption,
@@ -2233,6 +2282,7 @@ function mergeChildren(
 
     if (byName) {
         diffGroupChildren({
+            api: api,
             oldChildren: el.children() || [],
             newChildren: newChildren || [],
             dataIndex: dataIndex,
@@ -2250,6 +2300,7 @@ function mergeChildren(
     let index = 0;
     for (; index < newLen; index++) {
         newChildren[index] && doCreateOrUpdateEl(
+            api,
             el.childAt(index),
             dataIndex,
             newChildren[index],
@@ -2268,12 +2319,13 @@ function mergeChildren(
 }
 
 type DiffGroupContext = {
-    oldChildren: Element[],
-    newChildren: CustomElementOption[],
-    dataIndex: number,
-    seriesModel: CustomSeriesModel,
-    group: graphicUtil.Group,
-    morphPreparation: MorphPreparation
+    api: ExtensionAPI;
+    oldChildren: Element[];
+    newChildren: CustomElementOption[];
+    dataIndex: number;
+    seriesModel: CustomSeriesModel;
+    group: graphicUtil.Group;
+    morphPreparation: MorphPreparation;
 };
 function diffGroupChildren(context: DiffGroupContext) {
     (new DataDiffer(
@@ -2304,6 +2356,7 @@ function processAddUpdate(
     const child = oldIndex != null ? context.oldChildren[oldIndex] : null;
 
     doCreateOrUpdateEl(
+        context.api,
         child,
         context.dataIndex,
         childOption,
diff --git a/test/ut/jest.config.js b/test/ut/jest.config.js
index ef57ca0..bc09902 100644
--- a/test/ut/jest.config.js
+++ b/test/ut/jest.config.js
@@ -35,10 +35,10 @@ module.exports = {
     testMatch: [
         '**/spec/api/*.test.ts',
         '**/spec/component/**/*.test.ts',
+        '**/spec/series/**/*.test.ts',
         '**/spec/data/*.test.ts',
         '**/spec/model/*.test.ts',
         '**/spec/scale/*.test.ts',
-        '**/spec/util/*.test.ts',
-        '**/spec/component/graphic/setOption.test.ts'
+        '**/spec/util/*.test.ts'
     ]
 };
diff --git a/test/ut/spec/component/graphic/setOption.test.ts b/test/ut/spec/component/graphic/setOption.test.ts
index b56f8b8..6dddca2 100755
--- a/test/ut/spec/component/graphic/setOption.test.ts
+++ b/test/ut/spec/component/graphic/setOption.test.ts
@@ -1,4 +1,3 @@
-
 /*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
diff --git a/test/ut/spec/series/custom.test.ts b/test/ut/spec/series/custom.test.ts
new file mode 100644
index 0000000..ec4565e
--- /dev/null
+++ b/test/ut/spec/series/custom.test.ts
@@ -0,0 +1,72 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements.  See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership.  The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License.  You may obtain a copy of the License at
+*
+*   http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied.  See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+import { EChartsType } from '../../../../src/echarts';
+import { createChart } from '../../core/utHelper';
+import { ZRColor } from '../../../../src/util/types';
+import { CustomSeriesRenderItemAPI, CustomSeriesRenderItemParams } from '../../../../src/chart/custom';
+
+
+describe('custom_series', function () {
+
+    let chart: EChartsType;
+    beforeEach(function () {
+        chart = createChart();
+    });
+
+    afterEach(function () {
+        chart.dispose();
+    });
+
+
+    it('visual_palette', function () {
+        const colors = ['#111111', '#222222', '#333333'];
+        const resultPaletteColors: ZRColor[] = [];
+
+        function renderItem(params: CustomSeriesRenderItemParams, api: CustomSeriesRenderItemAPI) {
+            const color = api.visual('color');
+            resultPaletteColors.push(color);
+            return {
+                type: 'circle'
+            };
+        }
+
+        chart.setOption({
+            color: colors,
+            xAxis: { data: ['a'] },
+            yAxis: {},
+            series: [{
+                type: 'custom',
+                renderItem: renderItem,
+                data: [11]
+            }, {
+                type: 'custom',
+                renderItem: renderItem,
+                data: [22]
+            }, {
+                type: 'custom',
+                renderItem: renderItem,
+                data: [33]
+            }]
+        }, true);
+
+        expect(resultPaletteColors).toEqual(colors);
+    });
+
+});


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org
For additional commands, e-mail: commits-help@echarts.apache.org