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 <[email protected]>
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: [email protected]
For additional commands, e-mail: [email protected]