How to get click event from donut pie when the hole in the middle is clicked?

I created a pie chart along with a datatable. when the pie chart is clicked the datatable will display the data from the clicked pie by using callback. that works perfectly. However In some cases I also want to display all the data in the datatable not just the part from the clicked pie. I found the donut chart. It has a hole in the middle. I thought it would be perfect for my purpose. when the hole is click, I can display all the data in the datatable. To my surprise, no click event was generated when the hole is clicked. therefore I am still looking for a way to display all data. Does anybody have any suggestions in terms of what is the best way to accomplish what I need to do? Thanks

Hi @xiali_zheng,

Welcome to the community! :slight_smile:

I don’t think you can get a click event if you are not interacting with a trace (please anyone correct me if I am wrong). Therefore one approach could be to add a dummy trace, like one point in the donut center, and then assign the click event showing the data you want.

If you provide us a reproducible example of what you did so far, I would be happy to help with a more precise suggestion.

Hope this helps! :slight_smile:

Hi jlfsjunior:

Thank you very much for your response. I am not sure how the dummy trace works so I created a simple sample to show what I am trying to do. Here is the output of my code:

As you can see if I click anyway in the pie chart other than the middle part, the data displayed in the DataTable below is the data with that specific status. what I want is when I click the middle cricle in white the the DataTable will display all the data with all status. Below is my code:

from dash import Dash, html, dcc, dash_table
import plotly.express as px
import pandas as pd
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output, State

external_stylesheets = [dbc.themes.BOOTSTRAP]
app = Dash(__name__, external_stylesheets=external_stylesheets)
# assume you have a "long-form" data frame
# see https://plotly.com/python/px-arguments/ for more options
df = pd.DataFrame([
                  {"key":"CDC-1", "created":"09-02-2021", "status":"Closed", "closed":"10-23-2021"},
                  {"key":"CDC-2", "created":"09-04-2021", "status":"Processing", "closed":""},
                  {"key":"CDC-3", "created":"09-04-2021", "status":"Closed", "closed":"05-22-2021"},
                  {"key":"CDC-3", "created":"10-15-2021", "status":"Closed", "closed":"12-22-2021"},
                  {"key":"CDC-4", "created":"02-04-2022", "status":"Processing", "closed":""},
                  {"key":"CDC-5", "created":"04-04-2022", "status":"Open", "closed":""},
                  {"key":"CDC-6", "created":"04-05-2022", "status":"Open", "closed":""},
                  {"key":"CDC-7", "created":"04-25-2022", "status":"Open", "closed":""},
                  {"key":"CDC-8", "created":"04-27-2022", "status":"Open", "closed":""},
                 ])
df_count = df.groupby("status").size().reset_index(name="counts")
figure = px.pie(df_count, names="status", values="counts", title = None, hole=.2)
figure.update_traces(textposition="inside", textinfo='percent+label+value', hoverinfo="skip", hovertemplate=None)
columns =[{'name':'Key', 'id':'key'},
          {'name':'Created Date', 'id': 'created'},
          {'name':'Status', 'id': 'status'},
          {'name':'Closed Date', 'id': 'closed'},
         ]
table = dash_table.DataTable(id="data-table", columns=columns, data=df.to_dict('records'),
                             style_table={'width':'400px'})

@app.callback(
   Output("data-table", "data"),
   Input('donut-pie-chart', 'clickData'),
   )
def donut_pie_chart_callback(clickData):
    status = None
    if not clickData:
       status = "Open"
    else:
       status = clickData["points"][0]["label"]
    return df.loc[df["status"] == status].to_dict("records")

app.layout = html.Div([
         dbc.Row([dbc.Col(dcc.Graph(id='donut-pie-chart', figure=figure),width=4)],justify='center'),
         dbc.Row([dbc.Col(table,width=2)], justify='center')
         ])
if __name__ == '__main__':
   app.run_server(port=8050, debug=True)

Thank you very much for your help!

Xiali

Hi,

Thank you for the example, this is very useful. What I mean by dummy trace is the following (you should add it after the fig.update_traces() statement):

figure.add_trace(go.Scatter(x=[0], y=[0], marker={'size': 40, 'color': "white"), showlegend=False))

figure.update_layout(
   xaxis={
      'showgrid': False, # thin lines in the background
      'zeroline': False, # thick line at x=0
      'visible': False,  # numbers below
   },
   yaxis={
      'showgrid': False, 
      'zeroline': False,
      'visible': False,  
   },
   plot_bgcolor='rgba(0,0,0,0)'
)

This will create a scatter plot with a single white point at the center of the donut. You might have to tweak a bit its size to fit the entire hole… Then this scatter plot will certainly add the grid and axis in the background, what you certainly don’t want, so the update in layout takes care of removing it.

So now if you hover the center of the donut, you can actually see that there is a trace with coord (0, 0) there. If you click it, you will see a very similiar data in clickData, except that the curveNumber will be 1 (this is the second trace of the graph). You can use this logic to establish if you have clicked in the donut or in the scatter plot. The code is more or less like this:

@app.callback(
   Output("data-table", "data"),
   Input('donut-pie-chart', 'clickData'),
   )
def donut_pie_chart_callback(clickData):
   status = None
   if (not clickData) or (clickData['points'][0]['curveNumber'] == 1):
      return df.to_dict("records")
   else:
      status = clickData["points"][0]["label"]
      return df.loc[df["status"] == status].to_dict("records")

Note that here I have joined the logic for a click in the hole and no click (all rows in df are shown).

You can also add a text label on top of the point and remove/edit the hover text to something more informative. It should be straightforward to find this information on the documentation, otherwise please let us know and we can help you further.

Hope this helps! ::slight_smile:

Hi jlfsjunior:

Thank you so much! It works perfectly!

However I have a question for you. I tried to change the donut pie chart to a regular pie chart. I thought the second trace will overlap the first one. with the regular pie chart I should still see the white circle from the second trace, but it did not happen as I expected and I don’t get click event by clicking the middle part. again with the donut pie chart, it works perfectly so I am good, but I am just wondering why the regular pie chart will not work.

Again, thank you very much for your help.

Xiali

Hi Xiali,

I think the reason is that the trace order is not preserved in this case due to the pie chart. In other words, just because you added the pie chart trace first and the point later, it doesn’t actually mean that one will be on top of the other.

In most cases it will though and your intuition is right.