Reputation: 159
I've a stacked bar plot(as shown in below diagram) plotted using plotly in python
What I want is whenever I click on any particular bar of any stacked bar, it show a corresponding line plot. eg :- Let suppose I click on A's bar then it should show the below line plot separately.
Upvotes: 2
Views: 1610
Reputation: 18714
There are three ways that I know of to do this.
dash
, like this exampleplotly.io
's argument post_script
to add Javascript (and write the code to change the plot this way in Javascript)I couldn't find any examples of a drill down in using traditional Python & Plotly (I found that really surprising).
It looks like you’re pretty new to SO, welcome to the community. To get great answers quickly, it’s best to make your question reproducible. This includes things like the data you used. If you used a
pandas
data frame, extract the data withpd.DataFrame.to_dict
and copy the output exactly as it appears. If you used base Python data structures, tryrepr
(no import needed).
I've put together an example of how to use option 3.
I've added comments to the JS (nested in /* comment */
) so that you could see/follow what's happening. If anything is unclear, let me know. Additionally, I added the JS separately as JS, without all of the ancillary things like "" & + that are necessary when embedding it in Python.
There are probably more comments in the JS than JS code.
There are 2 events in the JS: click and double-click. The click event has 3 conditions.
To use the click event you click on a plotted element. In the line chart, that means you would have to find a point that is plotted within the line, that's why I added a double-click event. That way you can click anywhere on the plot to return to the parent (the bar graph).
I tend to have problems triggering double-click events in Plotly without adding a delay. Therefore, in the call to show the plot, I use
config
to add a click delay to the double-click event.
There is a condition for each item in the legend, and one for the line graphs.
You have several categories in your chart, whereas I only have 2. If your real data has many categories, I can help you write this as a loop, instead of the lengthy process of writing if/else for each category. Let me know if this is the case.
In each condition, the visibility changes, the type changes (line or bar), and an annotation changes (annotation only appears when showing the line graph).
In the comments, you'll see UPDATE
where you may need to make changes. Where the condition is "a" == ty
, "b" == ty
, and within every condition, where you see visible
. a
, and b
are my legend entries. Visibility is in reference to the number of traces that are rendered (not the number of traces you code in Python). Each color is a separate trace. You need either true
or false
in these lists for each trace. Assume that the order in the legend is the order the traces are documented in Python. For example, if you have five traces, you need five boolean values in each list indicated with visible
.
The double-click event is strictly for returning to the parent (the bar graph). The click event also contains a condition for the line graph. This code is nearly identical. The only difference is that there are no conditions in the double-click event.
The imports, data I used, the Javascript, and the plot. I used Plotly Express. However, it doesn't matter if you use Plotly Graph Objects or Plotly Express.
As I mentioned earlier, in the JS there are a few things you will need to change. These are annotated with JS comments, these comments stick out further to the right than all others, and include the word UPDATE (in all caps).
import plotly.express as px
import plotly.io as io
import pandas as pd
import datetime as dt
df1 = pd.DataFrame({ # create data for example
'x': [dt.datetime.today() - dt.timedelta(days = x) for x in range(5)] +
[dt.datetime.today() - dt.timedelta(days = x) for x in range(5)],
'y': [10, 0, 20, 10, 5, 10, 25, 3, 14, 30],
'clr': ['a'] * 5 + ['b'] * 5
})
fig = px.bar(df1, x = "x", y = "y", color = "clr") # plot the bar chart
# note that the double click and single click event can return the plot to the original state
# in the single click event, the user would have to click on a literal data point within the line
ps = 'setTimeout(function() {\n' + \
' myplt = document.querySelector("#plt");\n' + \
' myplt.on("plotly_click", function(dt) { /* EVENT 1: CLICK */\n' + \
' nm = dt.points[0].data.name;\n' + \
' ty = dt.points[0].data.type;\n' + \
' lout = { /* used in multiple conditions */\n' + \
' annotations: [{\n' + \
' xref: "paper", yref: "paper", x: 1, y: 1, showarrow: false,\n' + \
' text: "Double click the chart to return to the bar chart" \n' + \
' }]\n' + \
' };\n' + \
' if(ty != "bar") { /* bar or line type? UPDATE HERE: add true for each color */\n' + \
' trc = {type: "bar", visible: [true, true]};\n' + \
' lout = {annotations: []}; /* remove click note */\n' + \
' Plotly.update("plt", trc, lout); /* return to original plot */\n' + \
' } else if(nm == "a") { /* UPDATE HERE: replace "a" with a category from your data */\n' + \
' /* make only one trace invisible, change the type to line graph */\n' + \
' trc = {type: "scatter", mode: "lines", visible: [true, false]}; /* UPDATE HERE: add booleans */\n' + \
' Plotly.update("plt", trc, lout);\n' + \
' } else if(nm == "b") { /* UPDATE HERE: replace "a" with a category from your data */\n' + \
' /* make only one trace invisible, change the type to line graph */\n' + \
' trc = {type: "scatter", mode: "lines", visible: [false, true]}; /* UPDATE HERE: add booleans */\n' + \
' Plotly.update("plt", trc, lout);\n' + \
' }\n' + \
' });\n' + \
' myplt.on("plotly_doubleclick", function() { /* EVENT 2: DOUBLECLICK */\n' + \
' /* UPDATE HERE: add true for each color */\n' + \
' trc = {type: "bar", visible: [true, true]};\n' + \
' lout = {annotations: []}; /* remove click note */\n' + \
' Plotly.update("plt", trc, lout); /* return to original plot */\n' + \
' });\n' + \
'}, 200);\n'
io.write_html( # untitled.html created, but plot opens in browser, as well
fig, file = "untitled.html", post_script = ps, auto_open = True,
config = [{'doubleClickDelay': 750}], div_id = "plt",
include_plotlyjs = 'cdn', include_mathjax = 'cdn')
...as it will appear behind the scenes in your browser when you render the plot.
setTimeout(function() {
myplt = document.querySelector("#plt");
myplt.on("plotly_click", function(dt) {
nm = dt.points[0].data.name;
ty = dt.points[0].data.type;
lout = { /* layout update; used in each line graph */
annotations: [{
xref: "paper", yref: "paper", x: 1, y: 1, showarrow: false,
text: "Double click the chart to return to the bar chart"
}]
};
if(ty != "bar") { /* is this a drilldown, a line graph? */
/* trace updates, add -true- for each legend item (each color) */
trc = {type: "bar", visible: [true, true]};
lout = {annotations: []}; /* remove annotation */
Plotly.update("plt", trc, lout); /* return to original plot */
} else if(nm == "a") { /* NOTE: replace "a" with your data (note a in legend) */
trc = {type: "scatter", mode: "lines", visible: [true, false]};
Plotly.update("plt", trc, lout); /* create single trace line graph of ONLY a */
} else if(nm == "b") {
trc = {type: "scatter", mode: "lines", visible: [false, true]};
Plotly.update("plt", trc, lout); /* create single trace line graph of ONLY b */
}
});
myplt.on("plotly_doubleclick", function() { /* alternate to finding a pt within line */
/* trace updates, add -true- for each legend item (each color) */
trc = {type: "bar", visible: [true, true]};
lout = {annotations: []}; /* remove annotation */
Plotly.update("plt", trc, lout); /* return to original plot */
});
}, 200);
Upvotes: 2