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