-
Notifications
You must be signed in to change notification settings - Fork 19.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Feature] Multi-level drill-down #16152
Comments
@dirslashls Please provide a demo for the issue either with https://codepen.io/Ovilia/pen/dyYWXWM , https://www.makeapie.com/editor.html or https://codesandbox.io/s/mystifying-bash-2uthz. |
Hello please use the following code which is a slight modification of the bar chart animation at https://echarts.apache.org/examples/en/editor.html?c=bar-drilldown. That one has 2 levels and I am trying to create 3 levels. In the below code, I have an option indicating commenting "groupId" shows the universal transition from level 1 to 2. But drilling level 2 to 3 will not be possible as I won't know the level 3 grouping from level 2 data. To support multi-level drill-down I think there needs to be two group ids. One about the group a data item belongs to w.r.t the parent and one about the group the child data elements belong to. option = {
xAxis: {
data: ['Animals', 'Fruits', 'Cars']
},
yAxis: {},
dataGroupId: '',
animationDurationUpdate: 500,
series: {
type: 'bar',
id: 'sales',
data: [
{
value: 5,
groupId: 'animals'
},
{
value: 2,
groupId: 'fruits'
},
{
value: 4,
groupId: 'cars'
}
],
universalTransition: {
enabled: true,
divideShape: 'clone'
}
}
};
const drilldownData = [
{
dataGroupId: 'animals',
data: [
['Cats', 4,'household'],
['Dogs', 2,'household'],
['Cows', 1,'farm'],
['Sheep', 2,'farm'],
['Pigs', 1,'farm']
]
},
{
dataGroupId: 'fruits',
data: [
['Apples', 4,'sweet'],
['Oranges', 2,'sour']
]
},
{
dataGroupId: 'cars',
data: [
['Toyota', 4,'japanese'],
['Opel', 2,'german'],
['Volkswagen', 2,'german']
]
}
];
const drilldownData2 = [
{ dataGroupId: 'household',
data: [['Canine',2],['Feline',1]]
},
]
let level = 0;
myChart.on('click', function (event) {
level = (level+1)%3;
console.debug(level);
if (event.data) {
var subData = (level === 1 ? drilldownData : drilldownData2).find(function (data) {
return data.dataGroupId === event.data.groupId;
});
if (!subData) {
return;
}
myChart.setOption({
xAxis: {
data: subData.data.map(function (item) {
return item[0];
})
},
series: {
type: 'bar',
id: 'sales',
dataGroupId: subData.dataGroupId,
data: subData.data.map(function (item) {
return { value: item[1] ,
groupId: item[2] /* comment groupId and the universal transition works but not 2nd level drill */
};
}),
universalTransition: {
enabled: true,
divideShape: 'clone'
}
},
graphic: [
{
type: 'text',
left: 50,
top: 20,
style: {
text: 'Back',
fontSize: 18
},
onclick: function () {
level = -1;
myChart.setOption(option);
}
}
]
});
}
}); |
Hi, @dirslashls, it is the second time I commenting on this issue. I commented here 3 days ago but I found I was wrong, so I deleted those wrong info and reworked with this problem today. Well, 3 days ago, I posted this gif and I thought I successfully did it: Apparently it was not what you want. What you request for is this: I used a very dirty way to achieve this. If you are interested in this workaround, I will put it in another comment. |
Hi @tyn1998 , yes, this new version is what I am looking for. Thanks for figuring out a way to achieve it. I am interested in the write-up of how to do it. |
Hi @dirslashls, below is the snippet. You can run it in online editor to have a try: // level 1 (root)
const data_1 = {
dataGroupId: '1',
data: [
['1_1', 5, '1_1'], // x, y, groupId
['1_2', 2, '1_2']
]
};
// level 2
const data_1_1 = {
dataGroupId: '1_1',
data: [
['1_1_1', 2, '1_1_1'],
['1_1_2', 2, '1_1_2'],
['1_1_3', 3, '1_1_3']
]
};
const data_1_2 = {
dataGroupId: '1_2',
data: [
['1_2_1', 6, '1_2_1'],
['1_2_2', 7, '1_2_2']
]
};
// level 3
const data_1_1_1 = {
dataGroupId: '1_1_1',
data: [
['1_1_1_A', 5],
['1_1_1_B', 6],
['1_1_1_C', 7],
['1_1_1_D', 8]
]
};
const data_1_1_2 = {
dataGroupId: '1_1_2',
data: [
['1_1_2_A', 2],
['1_1_2_B', 9]
]
};
const data_1_1_3 = {
dataGroupId: '1_1_3',
data: [
['1_1_3_A', 1],
['1_1_3_B', 2],
['1_1_3_C', 8]
]
};
const data_1_2_1 = {
dataGroupId: '1_2_1',
data: [
['1_2_1_A', 9],
['1_2_1_B', 2],
['1_2_1_C', 1]
]
};
const data_1_2_2 = {
dataGroupId: '1_2_2',
data: [
['1_2_2_A', 7],
['1_2_2_B', 7]
]
};
const allDataGroups = [
data_1,
data_1_1,
data_1_2,
data_1_1_1,
data_1_1_2,
data_1_1_3,
data_1_2_1,
data_1_2_2
];
// Generate 1+1 options for each data
const allOptionsWithItemGroupId = {};
const allOptionsWithoutItemGroupId = {};
allDataGroups.forEach((dataGroup, index) => {
const { dataGroupId, data } = dataGroup;
const optionWithItemGroupId = {
xAxis: {
type: 'category'
},
yAxis: {},
// dataGroupId: dataGroupId,
animationDurationUpdate: 500,
series: {
type: 'bar',
// id: "sales",
dataGroupId: dataGroupId,
encode: {
x: 0,
y: 1,
itemGroupId: 2
},
data: data,
universalTransition: {
enabled: true,
divideShape: 'clone'
}
},
graphic: [
{
type: 'text',
left: 50,
top: 20,
style: {
text: 'Back',
fontSize: 18
},
onclick: function () {
goBack();
}
}
]
};
const optionWithoutItemGroupId = {
xAxis: {
type: 'category'
},
yAxis: {},
// dataGroupId: dataGroupId,
animationDurationUpdate: 500,
series: {
type: 'bar',
// id: "sales",
dataGroupId: dataGroupId,
encode: {
x: 0,
y: 1
// itemGroupId: 2,
},
data: data.map((item, index) => {
return item.slice(0, 2); // This is what "without itemGroupId" means
}),
universalTransition: {
enabled: true,
divideShape: 'clone'
}
},
graphic: [
{
type: 'text',
left: 50,
top: 20,
style: {
text: 'Back',
fontSize: 18
},
onclick: function () {
goBack();
}
}
]
};
allOptionsWithItemGroupId[dataGroupId] = optionWithItemGroupId;
allOptionsWithoutItemGroupId[dataGroupId] = optionWithoutItemGroupId;
});
// A stack to remember previous dataGroupsId
const dataGroupIdStack = [];
const goForward = (dataGroupId) => {
dataGroupIdStack.push(myChart.getOption().series[0].dataGroupId); // push current dataGroupId into stack.
myChart.setOption(allOptionsWithoutItemGroupId[dataGroupId], false);
myChart.setOption(allOptionsWithItemGroupId[dataGroupId], false); // setOption twice? Yeah, it is dirty.
};
const goBack = () => {
if (dataGroupIdStack.length === 0) {
console.log('Already in root dataGroup!');
} else {
console.log('Go back to previous level');
myChart.setOption(
allOptionsWithoutItemGroupId[myChart.getOption().series[0].dataGroupId],
false
);
myChart.setOption(allOptionsWithItemGroupId[dataGroupIdStack.pop()], true); // Note: the parameter notMerge is set true
}
};
option = allOptionsWithItemGroupId['1']; // The initial option is the root data option
myChart.on('click', 'series.bar', (params) => {
if (params.data[2]) { // If current params is not belong to the "childest" data, then it has data[2]
const dataGroupId = params.data[2];
goForward(dataGroupId);
}
}); |
Actually this issue is linked to one project for OSPP Summer2022. I would like to apply for this project, good luck to me! 😄 |
@tyn1998 Thanks for the detailed plan. I have some questions about this: If we consider drilling-down only, then all If I understand correctly, you want the developers to listen to all kinds of click events and call chart.setOption(/* old option */);
chart.on('click', () => {
// check which item is clicked and find its children
chart.setOption(/* new option with its children */);
}) One problem is that, in this design, the developer has to consider about all possible drilled down situations in the click callback and handle the corresponding new option. I think a better idea would be encapsulating the logic by ECharts like: chart.setOption({
series: [{
// ...
data: [{
value: 10,
name: 'parent A',
drilldown: {
data: [{
value: 2,
name: 'first child of parent A'
}],
// other options for drilldown if you think is needed
}
}]
}]
}); So the developers don't have to think about drilling down at all. Apache ECharts will update the chart just as it does with treemap drilldown right now. That's my personal point of view. Let's also hear about what other mentors think. |
Hi @tyn1998 , thank you for sharing this. But I need your help as i was trying to create a chart with Series data. Please see sample photo but when i clicked 1 bar for example the green bar, the 1st photo is still in the next graph document.addEventListener("DOMContentLoaded", () => {
hope you could check on this. My goal is when i click the green bar - only the green bar will appear in the next graph. the blue and orange should not appear. TIA |
Hi @JerichoBarquez, I tried to read your code but could not understand it well. Maybe you would like to try the workaround provided in #16152 (comment). And especially pay attention to BTW, I added multi-level drill-down support to the codebase last summer #17611 and the feature is supposed to be released with the version 5.5.0. You may want for a try then :) |
Hi @tyn1998 I want to achieve the drill down similar to this after clicking one bar under the month of January but without the green and orange bars. Thanks in advance |
What problem does this feature solve?
The 5.2.0 feature of Universal Transition works great for drilling down 1 level. I am trying to create drilling down to multi-levels and can't figure out how to do it. My idea was that the groupId from the previous level becomes dataGroupId of the current level and the current level will use the itemGroupId for the groups of the current dataset. However, specifying itemGroupId in the second level completely stopped the animation.
What does the proposed API look like?
I think there should be a way to distinguish between the group a data item belongs to from the drill-through group and also a group it forms for further drill-down.
The text was updated successfully, but these errors were encountered: