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

ovilia pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/echarts.git


The following commit(s) were added to refs/heads/master by this push:
     new 243856242 feat(animation): support multi-level drill-down for 
universal transition #17611
243856242 is described below

commit 243856242ce8f8924405f60792985ec139d42742
Author: Zhongxiang Wang <y...@all-my-life.cn>
AuthorDate: Thu Jan 18 19:56:38 2024 +0800

    feat(animation): support multi-level drill-down for universal transition 
#17611
---
 src/animation/universalTransition.ts              | 174 +++++---
 src/util/types.ts                                 |   5 +-
 test/universalTransition-multiLevelDrillDown.html | 476 ++++++++++++++++++++++
 3 files changed, 606 insertions(+), 49 deletions(-)

diff --git a/src/animation/universalTransition.ts 
b/src/animation/universalTransition.ts
index c82d3d78f..c1747bbe7 100644
--- a/src/animation/universalTransition.ts
+++ b/src/animation/universalTransition.ts
@@ -28,7 +28,14 @@ import { EChartsExtensionInstallRegisters } from 
'../extension';
 import { initProps } from '../util/graphic';
 import DataDiffer from '../data/DataDiffer';
 import SeriesData from '../data/SeriesData';
-import { Dictionary, DimensionLoose, OptionDataItemObject, 
UniversalTransitionOption } from '../util/types';
+import {
+    Dictionary,
+    DimensionLoose,
+    DimensionName,
+    DataVisualDimensions,
+    OptionDataItemObject,
+    UniversalTransitionOption
+} from '../util/types';
 import {
     UpdateLifecycleParams,
     UpdateLifecycleTransitionItem,
@@ -42,14 +49,17 @@ import Model from '../model/Model';
 import Displayable from 'zrender/src/graphic/Displayable';
 
 const DATA_COUNT_THRESHOLD = 1e4;
+const TRANSITION_NONE = 0;
+const TRANSITION_P2C = 1;
+const TRANSITION_C2P = 2;
 
 interface GlobalStore { oldSeries: SeriesModel[], oldDataGroupIds: string[], 
oldData: SeriesData[] };
 const getUniversalTransitionGlobalStore = makeInner<GlobalStore, 
ExtensionAPI>();
 
 interface DiffItem {
-    dataGroupId: string
     data: SeriesData
-    dim: DimensionLoose
+    groupId: string
+    childGroupId: string
     divide: UniversalTransitionOption['divideShape']
     dataIndex: number
 }
@@ -57,24 +67,61 @@ interface TransitionSeries {
     dataGroupId: string
     data: SeriesData
     divide: UniversalTransitionOption['divideShape']
-    dim?: DimensionLoose
+    groupIdDim?: DimensionLoose
 }
 
-function getGroupIdDimension(data: SeriesData) {
+function getDimension(data: SeriesData, visualDimension: string) {
     const dimensions = data.dimensions;
     for (let i = 0; i < dimensions.length; i++) {
         const dimInfo = data.getDimensionInfo(dimensions[i]);
-        if (dimInfo && dimInfo.otherDims.itemGroupId === 0) {
+        if (dimInfo && dimInfo.otherDims[visualDimension as keyof 
DataVisualDimensions] === 0) {
             return dimensions[i];
         }
     }
 }
 
+// get value by dimension. (only get value of itemGroupId or childGroupId, so 
convert it to string)
+function getValueByDimension(data: SeriesData, dataIndex: number, dimension: 
DimensionName) {
+    const dimInfo = data.getDimensionInfo(dimension);
+    const dimOrdinalMeta = dimInfo && dimInfo.ordinalMeta;
+    if (dimInfo) {
+        const value = data.get(dimInfo.name, dataIndex);
+        if (dimOrdinalMeta) {
+            return (dimOrdinalMeta.categories[value as number] as string) || 
value + '';
+        }
+        return value + '';
+    }
+}
+
+function getGroupId(data: SeriesData, dataIndex: number, dataGroupId: string, 
isChild: boolean) {
+    // try to get groupId from encode
+    const visualDimension = isChild ? 'itemChildGroupId' : 'itemGroupId';
+    const groupIdDim = getDimension(data, visualDimension);
+    if (groupIdDim) {
+        const groupId = getValueByDimension(data, dataIndex, groupIdDim);
+        return groupId;
+    }
+    // try to get groupId from raw data item
+    const rawDataItem = data.getRawDataItem(dataIndex) as 
OptionDataItemObject<unknown>;
+    const property = isChild ? 'childGroupId' : 'groupId';
+    if (rawDataItem && rawDataItem[property]) {
+        return rawDataItem[property] + '';
+    }
+    // fallback
+    if (isChild) {
+        return;
+    }
+    // try to use series.dataGroupId as groupId, otherwise use dataItem's id 
as groupId
+    return (dataGroupId || data.getId(dataIndex));
+}
+
+// flatten all data items from different serieses into one arrary
 function flattenDataDiffItems(list: TransitionSeries[]) {
     const items: DiffItem[] = [];
 
     each(list, seriesInfo => {
         const data = seriesInfo.data;
+        const dataGroupId = seriesInfo.dataGroupId;
         if (data.count() > DATA_COUNT_THRESHOLD) {
             if (__DEV__) {
                 warn('Universal transition is disabled on large data > 10k.');
@@ -82,12 +129,11 @@ function flattenDataDiffItems(list: TransitionSeries[]) {
             return;
         }
         const indices = data.getIndices();
-        const groupDim = getGroupIdDimension(data);
         for (let dataIndex = 0; dataIndex < indices.length; dataIndex++) {
             items.push({
-                dataGroupId: seriesInfo.dataGroupId,
                 data,
-                dim: seriesInfo.dim || groupDim,
+                groupId: getGroupId(data, dataIndex, dataGroupId, false), // 
either of groupId or childGroupId will be used as diffItem's key,
+                childGroupId: getGroupId(data, dataIndex, dataGroupId, true),  
  // depending on the transition direction (see below)
                 divide: seriesInfo.divide,
                 dataIndex
             });
@@ -185,18 +231,71 @@ function transitionBetween(
         }
     }
 
+    let hasMorphAnimation = false;
 
-    function findKeyDim(items: DiffItem[]) {
-        for (let i = 0; i < items.length; i++) {
-            if (items[i].dim) {
-                return items[i].dim;
-            }
+    /**
+     * With groupId and childGroupId, we can build parent-child relationships 
between dataItems.
+     * However, we should mind the parent-child "direction" between old and 
new options.
+     *
+     * For example, suppose we have two dataItems from two series.data:
+     *
+     * dataA: [                          dataB: [
+     *   {                                 {
+     *     value: 5,                         value: 3,
+     *     groupId: 'creatures',             groupId: 'animals',
+     *     childGroupId: 'animals'           childGroupId: 'dogs'
+     *   },                                },
+     *   ...                               ...
+     * ]                                 ]
+     *
+     * where dataA is belong to optionA and dataB is belong to optionB.
+     *
+     * When we `setOption(optionB)` from optionA, we choose childGroupId of 
dataItemA and groupId of
+     * dataItemB as keys so the two keys are matched (both are 'animals'), 
then universalTransition
+     * will work. This derection is "parent -> child".
+     *
+     * If we `setOption(optionA)` from optionB, we also choose groupId of 
dataItemB and childGroupId
+     * of dataItemA as keys and universalTransition will work. This derection 
is "child -> parent".
+     *
+     * If there is no childGroupId specified, which means no 
multiLevelDrillDown/Up is needed and no
+     * parent-child relationship exists. This direction is "none".
+     *
+     * So we need to know whether to use groupId or childGroupId as the key 
when we call the keyGetter
+     * functions. Thus, we need to decide the direction first.
+     *
+     * The rule is:
+     *
+     * if (all childGroupIds in oldDiffItems and all groupIds in newDiffItems 
have common value) {
+     *   direction = 'parent -> child';
+     * } else if (all groupIds in oldDiffItems and all childGroupIds in 
newDiffItems have common value) {
+     *   direction = 'child -> parent';
+     * } else {
+     *   direction = 'none';
+     * }
+     */
+    let direction = TRANSITION_NONE;
+
+    // find all groupIds and childGroupIds from oldDiffItems
+    const oldGroupIds = createHashMap();
+    const oldChildGroupIds = createHashMap();
+    oldDiffItems.forEach((item) => {
+        item.groupId && oldGroupIds.set(item.groupId, true);
+        item.childGroupId && oldChildGroupIds.set(item.childGroupId, true);
+
+    });
+    // traverse newDiffItems and decide the direction according to the rule
+    for (let i = 0; i < newDiffItems.length; i++) {
+        const newGroupId = newDiffItems[i].groupId;
+        if (oldChildGroupIds.get(newGroupId)) {
+            direction = TRANSITION_P2C;
+            break;
+        }
+        const newChildGroupId = newDiffItems[i].childGroupId;
+        if (newChildGroupId && oldGroupIds.get(newChildGroupId)) {
+            direction = TRANSITION_C2P;
+            break;
         }
     }
-    const oldKeyDim = findKeyDim(oldDiffItems);
-    const newKeyDim = findKeyDim(newDiffItems);
-
-    let hasMorphAnimation = false;
 
     function createKeyGetter(isOld: boolean, onlyGetId: boolean) {
         return function (diffItem: DiffItem): string {
@@ -206,36 +305,12 @@ function transitionBetween(
             if (onlyGetId) {
                 return data.getId(dataIndex);
             }
-
-            // Use group id as transition key by default.
-            // So we can achieve multiple to multiple animation like drilldown 
/ up naturally.
-            // If group id not exits. Use id instead. If so, only one to one 
transition will be applied.
-            const dataGroupId = diffItem.dataGroupId;
-
-            // If specified key dimension(itemGroupId by default). Use this 
same dimension from other data.
-            // PENDING: If only use key dimension of newData.
-            const keyDim = isOld
-                ? (oldKeyDim || newKeyDim)
-                : (newKeyDim || oldKeyDim);
-
-            const dimInfo = keyDim && data.getDimensionInfo(keyDim);
-            const dimOrdinalMeta = dimInfo && dimInfo.ordinalMeta;
-
-            if (dimInfo) {
-                // Get from encode.itemGroupId.
-                const key = data.get(dimInfo.name, dataIndex);
-                if (dimOrdinalMeta) {
-                    return dimOrdinalMeta.categories[key as number] as string 
|| (key + '');
-                }
-                return key + '';
+            if (isOld) {
+              return direction === TRANSITION_P2C ? diffItem.childGroupId : 
diffItem.groupId;
             }
-
-            // Get groupId from raw item. { groupId: '' }
-            const itemVal = data.getRawDataItem(dataIndex) as 
OptionDataItemObject<unknown>;
-            if (itemVal && itemVal.groupId) {
-                return itemVal.groupId + '';
+            else {
+              return direction === TRANSITION_C2P ? diffItem.childGroupId : 
diffItem.groupId;
             }
-            return (dataGroupId || data.getId(dataIndex));
         };
     }
 
@@ -541,6 +616,7 @@ function findTransitionSeriesBatches(
             }
             else {
                 // Transition from multiple series.
+                // e.g. 'female', 'male' -> ['female', 'male']
                 if (isArray(transitionKey)) {
                     if (__DEV__) {
                         checkTransitionSeriesKeyDuplicated(transitionKeyStr);
@@ -569,6 +645,7 @@ function findTransitionSeriesBatches(
                 }
                 else {
                     // Try transition to multiple series.
+                    // e.g. ['female', 'male'] -> 'female', 'male'
                     const oldData = oldDataMapForSplit.get(transitionKey);
                     if (oldData) {
                         let batch = updateBatches.get(oldData.key);
@@ -623,7 +700,7 @@ function transitionSeriesFromOpt(
                 data: globalStore.oldData[idx],
                 // TODO can specify divideShape in transition.
                 divide: getDivideShapeFromData(globalStore.oldData[idx]),
-                dim: finder.dimension
+                groupIdDim: finder.dimension
             });
         }
     });
@@ -635,7 +712,7 @@ function transitionSeriesFromOpt(
                 dataGroupId: globalStore.oldDataGroupIds[idx],
                 data,
                 divide: getDivideShapeFromData(data),
-                dim: finder.dimension
+                groupIdDim: finder.dimension
             });
         }
     });
@@ -665,6 +742,7 @@ export function installUniversalTransition(registers: 
EChartsExtensionInstallReg
 
         // TODO multiple to multiple series.
         if (globalStore.oldSeries && params.updatedSeries && 
params.optionChanged) {
+            // TODO transitionOpt was used in an old implementation and can be 
removed now
             // Use give transition config if its' give;
             const transitionOpt = params.seriesTransition;
             if (transitionOpt) {
diff --git a/src/util/types.ts b/src/util/types.ts
index c7b0c4ef3..b7c74abf5 100644
--- a/src/util/types.ts
+++ b/src/util/types.ts
@@ -432,7 +432,7 @@ export type DimensionLoose = DimensionName | 
DimensionIndexLoose;
 export type DimensionType = DataStoreDimensionType;
 
 export const VISUAL_DIMENSIONS = createHashMap<number, keyof 
DataVisualDimensions>([
-    'tooltip', 'label', 'itemName', 'itemId', 'itemGroupId', 'seriesName'
+    'tooltip', 'label', 'itemName', 'itemId', 'itemGroupId', 
'itemChildGroupId', 'seriesName'
 ]);
 // The key is VISUAL_DIMENSIONS
 export interface DataVisualDimensions {
@@ -444,6 +444,7 @@ export interface DataVisualDimensions {
     itemName?: DimensionIndex;
     itemId?: DimensionIndex;
     itemGroupId?: DimensionIndex;
+    itemChildGroupId?: DimensionIndex;
     seriesName?: DimensionIndex;
 }
 
@@ -618,6 +619,7 @@ export type OptionDataItemObject<T> = {
     id?: OptionId;
     name?: OptionName;
     groupId?: OptionId;
+    childGroupId?: OptionId;
     value?: T[] | T;
     selected?: boolean;
 };
@@ -667,6 +669,7 @@ export interface OptionEncodeVisualDimensions {
     // Which is useful in prepresenting the transition key of drilldown/up 
animation.
     // Or hover linking.
     itemGroupId?: OptionEncodeValue;
+    childGroupdId?: OptionEncodeValue;
 }
 export interface OptionEncode extends OptionEncodeVisualDimensions {
     [coordDim: string]: OptionEncodeValue | undefined
diff --git a/test/universalTransition-multiLevelDrillDown.html 
b/test/universalTransition-multiLevelDrillDown.html
new file mode 100644
index 000000000..7d40d1aaf
--- /dev/null
+++ b/test/universalTransition-multiLevelDrillDown.html
@@ -0,0 +1,476 @@
+<!DOCTYPE html>
+<!--
+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.
+-->
+
+<html>
+    <head>
+        <meta charset="utf-8" />
+        <meta name="viewport" content="width=device-width, initial-scale=1" />
+        <script src="lib/simpleRequire.js"></script>
+        <script src="lib/config.js"></script>
+        <script src="lib/jquery.min.js"></script>
+        <script src="lib/facePrint.js"></script>
+        <script src="lib/testHelper.js"></script>
+        <!-- <script src="ut/lib/canteen.js"></script> -->
+        <link rel="stylesheet" href="lib/reset.css" />
+    </head>
+    <body>
+        <style></style>
+
+        <div id="main0"></div>
+        <div id="main1"></div>
+
+        <script>
+            window.ANIMATION_DURATION_UPDATE = 1000;
+        </script>
+
+        <script>
+            require(['echarts'], function (echarts) {
+                var myChart = testHelper.create(echarts, 'main0', {
+                    title: [
+                        'Test Case 1',
+                        '(1) 3 levels: bar <--> bar <--> bar',
+                        '(2) only one-series-to-one-series transitions',
+                        '(3) groupId and childGroupID are read from encode (by 
dimension)'
+                    ],
+                    height: 300
+                });
+
+                // level 1 (root)
+                const data_things = [
+                    ['Animals', 3, 'things', 'animals'],
+                    ['Fruits', 3, 'things', 'fruits'],
+                    ['Cars', 2, 'things', 'cars']
+                ];
+                // level 2
+                const data_animals = [
+                    ['Dogs', 3, 'animals', 'dogs'],
+                    ['Cats', 4, 'animals', 'cats'],
+                    ['Birds', 3, 'animals', 'birds']
+                ];
+                const data_fruits = [
+                    ['Pomes', 3, 'fruits', 'pomes'],
+                    ['Berries', 4, 'fruits', 'berries'],
+                    ['Citrus', 9, 'fruits', 'citrus']
+                ];
+                const data_cars = [
+                    ['SUV', 5, 'cars', 'suv'],
+                    ['Sports', 3, 'cars', 'sports']
+                ];
+                // level 3
+                const data_dogs = [
+                    ['Corgi', 5, 'dogs'], // the "childest" data need not to 
be specified a `childGroupId`
+                    ['Bulldog', 6, 'dogs'],
+                    ['Shiba Inu', 7, 'dogs']
+                ];
+                const data_cats = [
+                    ['American Shorthair', 2, 'cats'],
+                    ['British Shorthair', 9, 'cats'],
+                    ['Bengal', 2, 'cats'],
+                    ['Birman', 2, 'cats']
+                ];
+                const data_birds = [
+                    ['Goose', 1, 'birds'],
+                    ['Owl', 2, 'birds'],
+                    ['Eagle', 8, 'birds']
+                ];
+                const data_pomes = [
+                    ['Apple', 9, 'pomes'],
+                    ['Pear', 2, 'pomes'],
+                    ['Kiwi', 1, 'pomes']
+                ];
+                const data_berries = [
+                    ['Blackberries', 7, 'berries'],
+                    ['Cranberries', 2, 'berries'],
+                    ['Strawberries', 9, 'berries'],
+                    ['Grapes', 4, 'berries']
+                ];
+                const data_citrus = [
+                    ['Oranges', 3, 'citrus'],
+                    ['Grapefruits', 7, 'citrus'],
+                    ['Tangerines', 8, 'citrus'],
+                    ['Lemons', 7, 'citrus'],
+                    ['Limes', 3, 'citrus'],
+                    ['Kumquats', 2, 'citrus'],
+                    ['Citrons', 3, 'citrus'],
+                    ['Tengelows', 3, 'citrus'],
+                    ['Uglifruit', 1, 'citrus']
+                ];
+                const data_suv = [
+                    ['Mazda CX-30', 7, 'suv'],
+                    ['BMW X2', 7, 'suv'],
+                    ['Ford Bronco Sport', 2, 'suv'],
+                    ['Toyota RAV4', 9, 'suv'],
+                    ['Porsche Macan', 4, 'suv']
+                ];
+                const data_sports = [
+                    ['Porsche 718 Cayman', 2, 'sports'],
+                    ['Porsche 911 Turbo', 2, 'sports'],
+                    ['Ferrari F8', 4, 'sports']
+                ];
+                const allLevelData = [
+                    data_things,
+                    data_animals,
+                    data_fruits,
+                    data_cars,
+                    data_dogs,
+                    data_cats,
+                    data_birds,
+                    data_pomes,
+                    data_berries,
+                    data_citrus,
+                    data_suv,
+                    data_sports
+                ];
+
+                const allOptions = {};
+
+                allLevelData.forEach((data, index) => {
+                    // since dataItems of each data have same groupId in this
+                    // example, we can use groupId as optionId for optionStack.
+                    const optionId = data[0][2];
+
+                    const option = {
+                        id: optionId, // option.id is not a property of 
emyCharts option model, but can be accessed if we provide it
+                        xAxis: {
+                            type: 'category'
+                        },
+                        yAxis: {
+                            minInterval: 1
+                        },
+                        animationDurationUpdate: ANIMATION_DURATION_UPDATE,
+                        series: {
+                            type: 'bar',
+                            dimensions: ['x', 'y', 'groupId', 'childGroupId'],
+                            encode: {
+                                x: 'x',
+                                y: 'y',
+                                itemGroupId: 'groupId',
+                                itemChildGroupId: 'childGroupId'
+                            },
+                            data,
+                            universalTransition: {
+                                enabled: true,
+                                divideShape: 'clone'
+                            }
+                        },
+                        graphic: [
+                            {
+                                type: 'text',
+                                left: 50,
+                                top: 20,
+                                style: {
+                                    text: 'Back',
+                                    fontSize: 18,
+                                    fill: 'grey'
+                                },
+                                onclick: function () {
+                                    goBack();
+                                }
+                            }
+                        ]
+                    };
+                    allOptions[optionId] = option;
+                });
+
+                // A stack to remember previous option id
+                const optionStack = [];
+
+                const goForward = (optionId) => {
+                    optionStack.push(myChart.getOption().id); // push current 
option id into stack.
+                    myChart.setOption(allOptions[optionId]);
+                };
+
+                const goBack = () => {
+                    if (optionStack.length === 0) {
+                        console.log('Already in root level!');
+                    } else {
+                        console.log('Go back to previous level.');
+                        myChart.setOption(allOptions[optionStack.pop()]);
+                    }
+                };
+
+                option = allOptions['things']; // The initial option is the 
root data option
+
+                myChart.on('click', 'series', (params) => {
+                    const dataItem = params.data;
+                    if (dataItem[3]) {
+                        // If current params is not belong to the "childest" 
data, it has data[3]
+                        const childGroupId = dataItem[3];
+                        // since we use groupId as optionId in this example,
+                        // we use childGroupId as the next level optionId.
+                        const nextOptionId = childGroupId;
+                        goForward(nextOptionId);
+                    }
+                });
+
+                option && myChart.setOption(option);
+
+                window.onresize = myChart.resize;
+            });
+        </script>
+
+        <script>
+            require(['echarts'], function (echarts) {
+                var myChart = testHelper.create(echarts, 'main1', {
+                    title: [
+                        'Test Case 2',
+                        '(1) 3 levels: bar <--> pie <--> line',
+                        '(2) only one-series-to-one-series transitions',
+                        '(3) groupId and childGroupID are read from raw 
dataItem'
+                    ],
+                    height: 300
+                });
+
+                // level 1 (root)
+                const data_orgs = [
+                    ['Org X', 15000, 'orgs', 'org_x'],
+                    ['Org Y', 10000, 'orgs', 'org_y']
+                ];
+                // level 2
+                const data_org_x = [
+                    ['Repo X1', 8000, 'org_x', 'repo_x1'],
+                    ['Repo X2', 5000, 'org_x', 'repo_x2'],
+                    ['Repo X3', 2000, 'org_x', 'repo_x3']
+                ];
+                const data_org_y = [
+                    ['Repo Y1', 7000, 'org_y', 'repo_y1'],
+                    ['Repo Y2', 3000, 'org_y', 'repo_y2']
+                ];
+                // level 3
+                const data_repo_x1 = [
+                    ['Q1', 1500, 'repo_x1'], // the "childest" data need not 
to be specified a `childGroupId`
+                    ['Q2', 2000, 'repo_x1'],
+                    ['Q3', 2000, 'repo_x1'],
+                    ['Q4', 2500, 'repo_x1']
+                ];
+                const data_repo_x2 = [
+                    ['Q1', 700, 'repo_x2'],
+                    ['Q2', 1000, 'repo_x2'],
+                    ['Q3', 1300, 'repo_x2'],
+                    ['Q4', 2000, 'repo_x2']
+                ];
+                const data_repo_x3 = [
+                    ['Q1', 500, 'repo_x3'],
+                    ['Q2', 400, 'repo_x3'],
+                    ['Q3', 500, 'repo_x3'],
+                    ['Q4', 600, 'repo_x3']
+                ];
+                const data_repo_y1 = [
+                    ['Q1', 1500, 'repo_y1'],
+                    ['Q2', 2000, 'repo_y1'],
+                    ['Q3', 2000, 'repo_y1'],
+                    ['Q4', 1500, 'repo_y1']
+                ];
+                const data_repo_y2 = [
+                    ['Q1', 1000, 'repo_y2'],
+                    ['Q2', 500, 'repo_y2'],
+                    ['Q3', 900, 'repo_y2'],
+                    ['Q4', 600, 'repo_y2']
+                ];
+                const barData = [data_orgs];
+                const pieData = [data_org_x, data_org_y];
+                const lineData = [data_repo_x1, data_repo_x2, data_repo_x3, 
data_repo_y1, data_repo_y2];
+                const allOptions = {};
+
+                barData.forEach((data) => {
+                    // since dataItems of each data have same groupId in this
+                    // example, we can use groupId as optionId for optionStack.
+                    const optionId = data[0][2];
+
+                    const option = {
+                        id: optionId, // option.id is not a property of 
emyCharts option model, but can be accessed if we provide it
+                        xAxis: {
+                            show: true,
+                            name: '',
+                            type: 'category',
+                            data: data.map((item) => item[0])
+                        },
+                        yAxis: {
+                            show: true,
+                            name: 'Git Commits',
+                            nameLocation: 'center',
+                            nameGap: 50,
+                            minInterval: 1
+                        },
+                        tooltip: {},
+                        animationDurationUpdate: ANIMATION_DURATION_UPDATE,
+                        series: {
+                            type: 'bar',
+                            data: data.map((item) => {
+                                return {
+                                    value: item[1],
+                                    groupId: item[2],
+                                    childGroupId: item[3]
+                                };
+                            }),
+                            universalTransition: {
+                                enabled: true,
+                                divideShape: 'split'
+                            }
+                        },
+                        graphic: [
+                            {
+                                type: 'text',
+                                left: 50,
+                                top: 20,
+                                style: {
+                                    text: 'Back',
+                                    fontSize: 18,
+                                    fill: 'grey'
+                                },
+                                onclick: function () {
+                                    goBack();
+                                }
+                            }
+                        ]
+                    };
+                    allOptions[optionId] = option;
+                });
+
+                pieData.forEach((data) => {
+                    // since dataItems of each data have same groupId in this
+                    // example, we can use groupId as optionId for optionStack.
+                    const optionId = data[0][2];
+
+                    const option = {
+                        id: optionId, // option.id is not a property of 
emyCharts option model, but can be accessed if we provide it
+                        xAxis: { show: false },
+                        yAxis: { show: false },
+                        tooltip: {},
+                        animationDurationUpdate: ANIMATION_DURATION_UPDATE,
+                        series: {
+                            type: 'pie',
+                            data: data.map((item) => {
+                                return {
+                                    name: item[0],
+                                    value: item[1],
+                                    groupId: item[2],
+                                    childGroupId: item[3]
+                                };
+                            }),
+                            universalTransition: {
+                                enabled: true,
+                                divideShape: 'split'
+                            }
+                        },
+                        graphic: [
+                            {
+                                type: 'text',
+                                left: 50,
+                                top: 20,
+                                style: {
+                                    text: 'Back',
+                                    fontSize: 18,
+                                    fill: 'grey'
+                                },
+                                onclick: function () {
+                                    goBack();
+                                }
+                            }
+                        ]
+                    };
+                    allOptions[optionId] = option;
+                });
+
+                lineData.forEach((data) => {
+                    // since dataItems of each data have same groupId in this
+                    // example, we can use groupId as optionId for optionStack.
+                    const optionId = data[0][2];
+
+                    const option = {
+                        id: optionId, // option.id is not a property of 
emyCharts option model, but can be accessed if we provide it
+                        xAxis: {
+                            show: true,
+                            type: 'category',
+                            name: optionId,
+                            data: data.map((item) => item[0])
+                        },
+                        yAxis: {
+                            show: true,
+                            minInterval: 1
+                        },
+                        tooltip: {},
+                        animationDurationUpdate: ANIMATION_DURATION_UPDATE,
+                        series: {
+                            type: 'line',
+                            data: data.map((item) => {
+                                return {
+                                    value: item[1],
+                                    groupId: item[2]
+                                };
+                            }),
+                            universalTransition: {
+                                enabled: true,
+                                divideShape: 'split'
+                            }
+                        },
+                        graphic: [
+                            {
+                                type: 'text',
+                                left: 50,
+                                top: 20,
+                                style: {
+                                    text: 'Back',
+                                    fontSize: 18,
+                                    fill: 'grey'
+                                },
+                                onclick: function () {
+                                    goBack();
+                                }
+                            }
+                        ]
+                    };
+                    allOptions[optionId] = option;
+                });
+
+                // A stack to remember previous option id
+                const optionStack = [];
+
+                const goForward = (optionId) => {
+                    optionStack.push(myChart.getOption().id); // push current 
option id into stack.
+                    myChart.setOption(allOptions[optionId]);
+                };
+
+                const goBack = () => {
+                    if (optionStack.length === 0) {
+                        console.log('Already in root level!');
+                    } else {
+                        console.log('Go back to previous level.');
+                        myChart.setOption(allOptions[optionStack.pop()]);
+                    }
+                };
+
+                option = allOptions['orgs']; // The initial option is the root 
data option
+
+                myChart.on('click', 'series', (params) => {
+                    const dataItem = params.data;
+                    if (dataItem.childGroupId) {
+                        const nextOptionId = dataItem.childGroupId;
+                        goForward(nextOptionId);
+                    }
+                });
+
+                option && myChart.setOption(option);
+
+                window.onresize = myChart.resize;
+            });
+        </script>
+    </body>
+</html>


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


Reply via email to