Callback functions are not properly triggered for "Plot 2" and "Plot 3" buttons,

Iā€™m trying to create an application where I need several buttons, each button triggering a specific function. In the attached code, Iā€™m sharing my attempt. However, I couldnā€™t understand why nothing happens when clicking on the buttons ā€œPlot 2ā€ or ā€œPlot 3ā€. Can anyone help me discover why the callback works only for ā€œPlot 1ā€ and the buttons related to it?


import dash
from dash import html, dcc
from dash.dependencies import Input, Output

app = dash.Dash(__name__, suppress_callback_exceptions=True)

app.layout = html.Center([
    html.Button('Plot 1', id='btn_plot1'),
    html.Button('Plot 2', id='btn_plot2'),
    html.Button('Plot 3', id='btn_plot3'),
    html.Div(id='additional_btn_container'),
    html.Div(id='plot_output')
])


# Callback to update additional buttons container
@app.callback(
    Output('additional_btn_container', 'children'),
    [Input('btn_plot1', 'n_clicks'),
     Input('btn_plot2', 'n_clicks'),
     Input('btn_plot3', 'n_clicks')]
)
def display_additional_buttons(btn1_clicks, btn2_clicks, btn3_clicks):
    ctx = dash.callback_context
    triggered_id = ctx.triggered[0]['prop_id'].split('.')[0]

    if triggered_id == 'btn_plot1':
        return html.Center([
            html.Button('Option 10', id='btn_option10'),
            html.Button('Option 20', id='btn_option11'),
            html.Button('Option 30', id='btn_option12')
        ])
    elif triggered_id == 'btn_plot2':
        return html.Center([
            html.Button('Option 20', id='btn_option20'),
            html.Button('Option 21', id='btn_option21'),
            html.Button('Option 22', id='btn_option22')
        ])
    elif triggered_id == 'btn_plot3':
        return html.Center([
            html.Button('Option 31', id='btn_option30'),
            html.Button('Option 32', id='btn_option31'),
            html.Button('Option 33', id='btn_option32')
        ])
    else:
        return ""


# Callback to display different plots based on button clicks
@app.callback(
    Output('plot_output', 'children'),
    [Input('btn_option10', 'n_clicks'),
     Input('btn_option11', 'n_clicks'),
     Input('btn_option12', 'n_clicks')]
)
def display_plot(btn10_clicks, btn11_clicks, btn12_clicks):
    ctx = dash.callback_context
    triggered_id = ctx.triggered[0]['prop_id'].split('.')[0]
    print(ctx.triggered)
    print(triggered_id)
    if triggered_id == "btn_option10":
        option_num = int(triggered_id.split('btn_option')[-1])
        return html.Div([
            dcc.Graph(
                id='example-graph',
                figure={
                    'data': [
                        {'x': [1, 2, 3], 'y': [option_num, option_num + 1, option_num + 2], 'type': 'bar', 'name': f'Option {option_num}'},
                    ],
                    'layout': {
                        'title': f'Option {option_num} Plot'
                    }
                }
            )
        ])
    else:
        return ""


if __name__ == '__main__':
    app.run_server(debug=True)


'

Hi @Safwan

When I run your code, the ā€œOptionsā€ buttons are updating correctly for each ā€œPlotā€ button. Itā€™s just not clearing the ā€œplot_outputā€ container

Thank you for your answer.

To follow what happens, I printed in the terminal:

print(ctx.triggered)
print(triggered_id)

That will show when clicking on Plot 1, Plot 10, Plot 11, and Plot 12. Nothing shows in the terminal when clicking on any other button. This means that the callback works only for ā€œPlot 1ā€ and the buttons related to it.

You only have a callback for Plot1 and options 10, 11, 12.

If you add callbacks for the other options it should work.

Thank you. I couldnā€™t succeed in adding other callbacks. Could you please guide me on how to add another callback for Plot 2, for example?

Hi @Safwan

Hereā€™s an example with another group added to the callback. Note you can simplify things by using ctx.triggered_id

This type of app lends itself well to pattern matching callbacks. See the second example below as well.


from dash import Dash, html, dcc, Input, Output, ctx

app = Dash(__name__, suppress_callback_exceptions=True)

app.layout = html.Center([
    html.Button('Plot 1', id='btn_plot1'),
    html.Button('Plot 2', id='btn_plot2'),
    html.Button('Plot 3', id='btn_plot3'),
    html.Div(id='additional_btn_container'),
    html.Div(id='plot_output'),
])


# Callback to update additional buttons container
@app.callback(
    Output('additional_btn_container', 'children'),
    [Input('btn_plot1', 'n_clicks'),
     Input('btn_plot2', 'n_clicks'),
     Input('btn_plot3', 'n_clicks')]
)
def display_additional_buttons(btn1_clicks, btn2_clicks, btn3_clicks):

    if ctx.triggered_id == 'btn_plot1':
        return html.Center([
            html.Button('Option 10', id='btn_option10'),
            html.Button('Option 20', id='btn_option11'),
            html.Button('Option 30', id='btn_option12')
        ])
    elif ctx.triggered_id == 'btn_plot2':
        return html.Center([
            html.Button('Option 20', id='btn_option20'),
            html.Button('Option 21', id='btn_option21'),
            html.Button('Option 22', id='btn_option22')
        ])
    elif ctx.triggered_id == 'btn_plot3':
        return html.Center([
            html.Button('Option 31', id='btn_option30'),
            html.Button('Option 32', id='btn_option31'),
            html.Button('Option 33', id='btn_option32')
        ])
    else:
        return ""


# Callback to display different plots based on button clicks
@app.callback(
    Output('plot_output', 'children'),
    Input('btn_option10', 'n_clicks'),
    Input('btn_option11', 'n_clicks'),
    Input('btn_option12', 'n_clicks'),
    prevent_initial_call=True
)
def display_plot(*n):

    print(ctx.triggered)
    print(ctx.triggered_id)
    if ctx.triggered_id == "btn_option10":
        option_num = int(ctx.triggered_id.split('btn_option')[-1])
        return html.Div([
            dcc.Graph(
                id='example-graph',
                figure={
                    'data': [
                        {'x': [1, 2, 3], 'y': [option_num, option_num + 1, option_num + 2], 'type': 'bar', 'name': f'Option {option_num}'},
                    ],
                    'layout': {
                        'title': f'Option {option_num} Plot'
                    }
                }
            )
        ])
    else:
        return ""




# Callback to display different plots based on button clicks
@app.callback(
    Output('plot_output', 'children', allow_duplicate=True),
    Input('btn_option20', 'n_clicks'),
    Input('btn_option21', 'n_clicks'),
    Input('btn_option22', 'n_clicks'),
    prevent_initial_call=True
)
def display_plot(*n):
    print(ctx.triggered)
    print(ctx.triggered_id)
    if ctx.triggered_id == "btn_option20":
        return "btn 20"
    return "other btn"



if __name__ == '__main__':
    app.run_server(debug=True)


Hereā€™s a better way to do this using Pattern Matching callbacks

import dash
from dash import Dash, dcc, html, Input, Output, ALL, callback, ctx

app = Dash(__name__)

app.layout = html.Center([
    html.Button('Plot 1', id={"type": 'btn_plot', "index": 1}),
    html.Button('Plot 2', id={"type": 'btn_plot', "index": 2}),
    html.Button('Plot 3', id={"type": 'btn_plot', "index": 3}),
    html.Div(id='additional_btn_container'),
    html.Div(id='plot_output'),
])



@callback(
    Output("additional_btn_container", "children"), Input({"type": "btn_plot", "index": ALL}, "n_clicks")
)
def add_buttons(_):
    print(ctx.triggered_id)
    if ctx.triggered_id:
        btn_index = ctx.triggered_id.index * 10
        return html.Center([
            html.Button(f'Option {btn_index}', id={"type": 'btn_option', "index": btn_index}),
            html.Button(f'Option {btn_index + 1}', id={"type": 'btn_option', "index": btn_index + 1}),
            html.Button(f'Option {btn_index + 2}', id={"type": 'btn_option', "index": btn_index + 2}),
        ])
    return dash.no_update


@callback(
    Output("plot_output", "children"), Input({"type": "btn_option", "index": ALL}, "n_clicks")
)
def display_plots(_):
    print(ctx.triggered_id)
    if ctx.triggered_id:
        return f"Button Option: {ctx.triggered_id.index}"
    return ""

if __name__ == "__main__":
    app.run(debug=True)

Thank you very much, Ann. The pattern matching callbacks work perfectly. However, I have another problem. I wonder if you can guide me to solve it. I defined some buttons: btn1, btn2, and btn3, ā€¦ Each button contains several options: option1, option2, option3, ā€¦ When clicking on each option, there is a different plot to show that works perfectly. I want to create a callback function that prints the coordinates of a mouse click on a bar plot that exists on btn3/option2. How can I do that? Here is my code:

import dash
import json
import pyarrow.parquet as pq
from src.plot_mapas_indiretos_entrada import plot_mapas_indiretos_entrada
from src.mapas_indiretos_entrada import mapas_indiretos_entrada
from dash import Dash, dcc, html, Input, Output, State, ALL, callback, ctx

parquet_file_path = 'Data/EFDICMSIPI-Entrada.parquet'
data = pq.read_table(parquet_file_path).to_pandas()
json_mapas_indiretos_entrada = mapas_indiretos_entrada(all_data=data, filtered_data=data)

file_path = 'dashboard_input.json'
with open(file_path, "r") as json_file:
    dashboard = json.load(json_file)

app = Dash(__name__)
server = app.server

n_buttons = 6
name_buttons = [f'Plot {i+1}' for i in range(n_buttons)]
n_options = [0 for _ in range(n_buttons)]
actual_btn = 0
actual_name_button = ''
for key in dashboard:
    name_buttons[dashboard[key]["n_dashboard"]] = key
    n_options[dashboard[key]["n_dashboard"]] = len(dashboard[key]["options"])

buttons = [html.Button(name_buttons[i], id={"type": 'btn_plot', "index": i+1}) for i in range(n_buttons)]
app.layout = html.Div([
    html.Img(src=r'assets/Logo-MakeTheWay.ico', style={'height': '50px', 'width': '50px', 'float': 'left'}),
    html.Img(src=r'assets/logo_maketheway_vetorizado.png', style={'height': '50px', 'width': '100px', 'float': 'left'}),
    html.Div([
        html.Center([
            *buttons,
            html.Div(id='additional_btn_container'),
            html.Div(id='plot_output')
        ]),
    ])
])


# Callback to change button colors when clicked
@app.callback(
    Output({'type': 'btn_plot', 'index': ALL}, 'style'),
    [Input({'type': 'btn_plot', 'index': ALL}, 'n_clicks')],
    prevent_initial_call=True
)
def change_button_color(n_clicks_list):
    styles = []
    for i, _ in enumerate(n_clicks_list):
        if i+1 == ctx.triggered_id.index:
            styles.append({'background-color': '#00FF00'})  # Change color of clicked button
        else:
            styles.append({'background-color': '#CCCCCC'})  # Reset color of other buttons
    return styles


# ================================
@callback(
    Output("additional_btn_container", "children"),
    Input({"type": "btn_plot", "index": ALL}, "n_clicks")
)
def add_buttons(_):
    if ctx.triggered_id:
        global actual_btn, actual_name_button
        actual_btn = ctx.triggered_id.index
        name = name_buttons[ctx.triggered_id['index']-1]
        actual_name_button = name
        if name in dashboard.keys():
            btn_index = ctx.triggered_id.index * 10
            n_option = n_options[ctx.triggered_id['index']-1]
            option = sorted(dashboard[name]['options'], key=lambda x: x['board'], reverse=False)
            if n_option:
                buttons_option = [html.Button(option[i]['name'], id={"type": 'btn_option', "index": btn_index + i + 1})
                                  for i in range(n_option)]
                return html.Center(buttons_option)
            else:
                return html.Center([])
        else:
            return html.Center([])
    return dash.no_update


# Callback to change button colors when clicked
@app.callback(
    Output({'type': 'btn_option', 'index': ALL}, 'style'),
    [Input({'type': 'btn_option', 'index': ALL}, 'n_clicks')],
    prevent_initial_call=True
)
def change_button_color(n_clicks_list):
    styles = []

    for i, _ in enumerate(n_clicks_list):
        index_btn = actual_btn * 10 + i + 1
        if index_btn == ctx.triggered_id.index:
            styles.append({'background-color': '#00FF00'})  # Change color of clicked button
        else:
            styles.append({'background-color': '#CCCCCC'})  # Reset color of other buttons
    return styles
# ================================


# ================================
# plot the dashboard
@callback(
    Output("plot_output", "children"),
    Input({"type": "btn_option", "index": ALL}, "n_clicks")
)
def display_plots(_):
    if ctx.triggered_id:
        # ====== Mapas Indiretos - Entradas - EFD ICMS IPI ======
        if actual_name_button == 'Mapas Indiretos':
            index_option = ctx.triggered_id.index - actual_btn * 10
            option = sorted(dashboard[actual_name_button]['options'], key=lambda x: x['board'], reverse=False)
            if option[index_option-1]['name'] == 'Entradas - EFD ICMS IPI':
                print(option[index_option-1]['name'])
                initial_figure, table = plot_mapas_indiretos_entrada(json_mapas_indiretos_entrada)

                return [
                    dcc.Graph(id='bar-plot', figure=initial_figure,
                              style={'width': '90%', 'height': '80vh', 'position': 'absolute', 'top': '170px',
                                     'left': '50px', 'z-index': '1'}
                              ),
                    html.Div(table, style={'display': 'flex', 'justify-content': 'center', 'align-items': 'center',
                                           'height': '20vh', 'z-index': '2'})
                ]
        return f"Button Option: {ctx.triggered_id.index}"
    return


if __name__ == "__main__":
    app.run_server(debug=True, port=5001)

Hi @Safwan

Glad the pattern matching callbacks are working for you. :tada:

Could you make a minimal app with some sample data that just has the figure? You mention a bar plot but in your code it looks like it might be a map? Itā€™s good to start with a simple app and regular callback first, before trying to make it work with pattern matching callbacks.

Note that you should avoid using global variables to share data between callbacks.

global actual_btn, actual_name_button

For more info see:

Thank you for your answer. The global variables actual_btn and actual_name_button were defined to apply a condition to the buttons in order to call a different function for each button. Is there a more intelligent way to do that?

Here, Iā€™m including a simplified version of the code with the data for the plot. My objective is to print in the terminal the coordinates of the mouse click on the bar plot. These coordinates are shown when I pass the mouse over any bar. The difficulty I have is that the bar plot is defined when clicking on a specific button. Thank you again for your help in using this incredible tool (Plotly).

import dash
import json
import pyarrow.parquet as pq
from dash import Dash, dcc, html, Input, Output, State, ALL, callback, ctx
import plotly.graph_objs as go
from plotly.subplots import make_subplots


def plot_mapas_indiretos_entrada(data):

    fig = make_subplots(rows=2, cols=2, shared_xaxes=False, shared_yaxes=False,
                        horizontal_spacing=0.05,  # Increase space between columns
                        vertical_spacing=0.3,  # Increase space between rows
                        subplot_titles=('Totais Documentos / MĆŖs',
                                        'Totais Tributos / MĆŖs',
                                        'Totais Entrada / CFOP',
                                        'Totais Entrada / Fornecedor (TOP 10 FORNECEDORES)'
                                        )
                        )

    # Add traces for each subplot
    x_values, y_values = list(data['line_total_doc'].keys()), list(data['line_total_doc'].values())
    fig.add_trace(go.Scatter(x=x_values, y=y_values, mode='lines+markers',
                             marker=dict(color='blue', size=6),  # Adjust marker size
                             line=dict(color='blue', width=2),  # Adjust line width
                             name='vlmerc (sum)'),  # Change the label of the trace
                  row=1, col=1)

    cores = {'VlPis': 'blue', 'VlCofins': 'green', 'VlIcms': 'orange', 'VlIpi': 'purple'}
    for i, col in enumerate(['VlPis', 'VlCofins', 'VlIcms', 'VlIpi'], start=2):
        x_values, y_values = list(data['line_tributos'][col].keys()), list(data['line_tributos'][col].values())
        fig.add_trace(go.Scatter(x=x_values, y=y_values, mode='lines+markers',
                                 marker=dict(color=cores[col], size=6),  # Adjust marker size
                                 line=dict(color=cores[col], width=2),  # Adjust line width
                                 name=col),  # Change the label of the trace
                      row=1, col=2)

    x_values, y_values = list(data['Bar'].keys()), list(data['Bar'].values())
    fig.add_trace(go.Bar(x=x_values, y=y_values, marker_color='blue', name='vlitem (sum)'), row=2, col=2)

    fig.update_layout(
        font=dict(family="Arial", size=12, color="black"),
        plot_bgcolor="lightgrey",
        legend=dict(
            title="Legend Title",
            x=1.03,
            y=0.99,
            bgcolor="white",
            bordercolor="black",
            borderwidth=1
        )
    )

    return fig


parquet_file_path = 'Data/EFDICMSIPI-Entrada.parquet'
data = pq.read_table(parquet_file_path).to_pandas()
json_mapas_indiretos_entrada = {
    "Bar": {
        "46242558000184": 114834911.26,
        "04626152000155": 75191209.77,
        "04626152000589": 57807574.57,
        "04626152000236": 14394927.23,
        "13480185000120": 9273451.45,
        "04626152000740": 6972599.2,
        "04626152000660": 6211330.86,
        "76104397000123": 1432445.4,
        "04626152000821": 1135383.55
    },
    "line_total_doc": {
        "2018-08-01T00:00:00": 25091993.65,
        "2018-09-01T00:00:00": 24824468.95,
        "2018-10-01T00:00:00": 30867122.27,
        "2018-11-01T00:00:00": 18662724.79,
        "2018-12-01T00:00:00": 25790976.32,
        "2019-01-01T00:00:00": 45915510.78,
        "2019-02-01T00:00:00": 219332207.14,
        "2019-03-01T00:00:00": 521535869.79,
        "2019-04-01T00:00:00": 298304478.08,
        "2019-05-01T00:00:00": 286302709.15
    },
    "line_tributos": {
        "VlPis": {
            "2018-08-01": 66825.34,
            "2018-09-01": 61348.68,
            "2018-10-01": 79969.39,
            "2018-11-01": 76221.52,
            "2018-12-01": 27777.04,
            "2019-01-01": 64381.92,
            "2019-02-01": 101723.6,
            "2019-03-01": 130789.09,
            "2019-04-01": 132020.68,
            "2019-05-01": 151876.72
        },
        "VlCofins": {
            "2018-08-01": 307801.79,
            "2018-09-01": 282575.81,
            "2018-10-01": 368343.83,
            "2018-11-01": 351079.99,
            "2018-12-01": 127942.21,
            "2019-01-01": 296547.08,
            "2019-02-01": 468331.07,
            "2019-03-01": 602052.05,
            "2019-04-01": 607417.3,
            "2019-05-01": 698913.18
        },
        "VlIcms": {
            "2018-08-01": 579616.07,
            "2018-09-01": 603905.48,
            "2018-10-01": 658627.97,
            "2018-11-01": 675782.1,
            "2018-12-01": 187434.27,
            "2019-01-01": 587657.91,
            "2019-02-01": 1010860.64,
            "2019-03-01": 1216810.4,
            "2019-04-01": 1068793.41,
            "2019-05-01": 1205247.28
        },
        "VlIpi": {
            "2018-08-01": 50606.06,
            "2018-09-01": 62963.93,
            "2018-10-01": 74667.45,
            "2018-11-01": 68879.68,
            "2018-12-01": 16503.87,
            "2019-01-01": 80179.81,
            "2019-02-01": 779303.66,
            "2019-03-01": 958982.24,
            "2019-04-01": 971285.35,
            "2019-05-01": 1289644.83
        }
    }
}

file_path = 'dashboard_input.json'
with open(file_path, "r") as json_file:
    dashboard = json.load(json_file)

app = Dash(__name__)
server = app.server

actual_btn = 0
actual_name_button = ''
actual_name_options = []

buttons = [html.Button('btn1', id={"type": 'btn_plot', "index": 1}),
           html.Button('Mapas Indiretos', id={"type": 'btn_plot', "index": 2})]

app.layout = html.Div([
    html.Div([
        html.Center([
            *buttons,
            html.Div(id='additional_btn_container'),
            html.Div(id='plot_output')
        ]),
    ])
])


# Callback to change button colors when clicked
@app.callback(
    Output({'type': 'btn_plot', 'index': ALL}, 'style'),
    [Input({'type': 'btn_plot', 'index': ALL}, 'n_clicks')],
    prevent_initial_call=True
)
def change_button_color(n_clicks_list):
    styles = []
    for i, _ in enumerate(n_clicks_list):
        if i+1 == ctx.triggered_id.index:
            styles.append({'background-color': '#00FF00'})  # Change color of clicked button
        else:
            styles.append({'background-color': '#CCCCCC'})  # Reset color of other buttons
    return styles


# ================================
@callback(
    Output("additional_btn_container", "children"),
    Input({"type": "btn_plot", "index": ALL}, "n_clicks")
)
def add_buttons(_):
    print(ctx.triggered_id)
    if ctx.triggered_id:
        global actual_btn, actual_name_button, actual_name_options
        actual_btn = ctx.triggered_id.index

        btn_index = ctx.triggered_id.index * 10
        if ctx.triggered_id.index == 2:
            actual_name_button = 'Mapas Indiretos'
            actual_name_options = ['Option 1', 'Entradas - EFD ICMS IPI']
            additional_btns = [
                html.Button(f'Option {btn_index}', id={"type": 'btn_option', "index": btn_index}),
                html.Button(f'Entradas - EFD ICMS IPI', id={"type": 'btn_option', "index": btn_index + 1}),
            ]
        else:
            additional_btns = [
                html.Button(f'Option {btn_index}', id={"type": 'btn_option', "index": btn_index}),
                html.Button(f'Option {btn_index + 1}', id={"type": 'btn_option', "index": btn_index + 1}),
                html.Button(f'Option {btn_index + 2}', id={"type": 'btn_option', "index": btn_index + 2}),
            ]
        return html.Center(additional_btns)
    return dash.no_update


# Callback to change button colors when clicked
@app.callback(
    Output({'type': 'btn_option', 'index': ALL}, 'style'),
    [Input({'type': 'btn_option', 'index': ALL}, 'n_clicks')],
    prevent_initial_call=True
)
def change_button_color(n_clicks_list):
    styles = []

    for i, _ in enumerate(n_clicks_list):
        index_btn = actual_btn * 10 + i
        if index_btn == ctx.triggered_id.index:
            styles.append({'background-color': '#00FF00'})  # Change color of clicked button
        else:
            styles.append({'background-color': '#CCCCCC'})  # Reset color of other buttons
    return styles
# ================================


# ================================
# plot the dashboard
@callback(
    Output("plot_output", "children"),
    Input({"type": "btn_option", "index": ALL}, "n_clicks")
)
def display_plots(_):
    if ctx.triggered_id:
        # ====== Mapas Indiretos - Entradas - EFD ICMS IPI ======
        if actual_name_button == 'Mapas Indiretos':
            index_option = ctx.triggered_id.index - actual_btn * 10
            if actual_name_options[index_option] == 'Entradas - EFD ICMS IPI':
                initial_figure = plot_mapas_indiretos_entrada(json_mapas_indiretos_entrada)

                return [
                    dcc.Graph(id='bar-plot', figure=initial_figure,
                              style={'width': '90%', 'height': '80vh', 'position': 'absolute', 'top': '170px',
                                     'left': '50px', 'z-index': '1'}
                              ),
                ]
        return f"Button Option: {ctx.triggered_id.index}"
    return


if __name__ == "__main__":
    app.run_server(debug=True, port=5010)

Can you please make a version that I can copy and run locally?

import dash
import json
import pyarrow.parquet as pq
from dash import Dash, dcc, html, Input, Output, State, ALL, callback, ctx
import plotly.graph_objs as go
from plotly.subplots import make_subplots


def plot_mapas_indiretos_entrada(data):

    fig = make_subplots(rows=2, cols=2, shared_xaxes=False, shared_yaxes=False,
                        horizontal_spacing=0.05,  # Increase space between columns
                        vertical_spacing=0.3,  # Increase space between rows
                        subplot_titles=('Totais Documentos / MĆŖs',
                                        'Totais Tributos / MĆŖs',
                                        'Totais Entrada / CFOP',
                                        'Totais Entrada / Fornecedor (TOP 10 FORNECEDORES)'
                                        )
                        )

    # Add traces for each subplot
    x_values, y_values = list(data['line_total_doc'].keys()), list(data['line_total_doc'].values())
    fig.add_trace(go.Scatter(x=x_values, y=y_values, mode='lines+markers',
                             marker=dict(color='blue', size=6),  # Adjust marker size
                             line=dict(color='blue', width=2),  # Adjust line width
                             name='vlmerc (sum)'),  # Change the label of the trace
                  row=1, col=1)

    cores = {'VlPis': 'blue', 'VlCofins': 'green', 'VlIcms': 'orange', 'VlIpi': 'purple'}
    for i, col in enumerate(['VlPis', 'VlCofins', 'VlIcms', 'VlIpi'], start=2):
        x_values, y_values = list(data['line_tributos'][col].keys()), list(data['line_tributos'][col].values())
        fig.add_trace(go.Scatter(x=x_values, y=y_values, mode='lines+markers',
                                 marker=dict(color=cores[col], size=6),  # Adjust marker size
                                 line=dict(color=cores[col], width=2),  # Adjust line width
                                 name=col),  # Change the label of the trace
                      row=1, col=2)

    x_values, y_values = list(data['Bar'].keys()), list(data['Bar'].values())
    fig.add_trace(go.Bar(x=x_values, y=y_values, marker_color='blue', name='vlitem (sum)'), row=2, col=2)

    fig.update_layout(
        font=dict(family="Arial", size=12, color="black"),
        plot_bgcolor="lightgrey",
        legend=dict(
            title="Legend Title",
            x=1.03,
            y=0.99,
            bgcolor="white",
            bordercolor="black",
            borderwidth=1
        )
    )

    return fig

json_mapas_indiretos_entrada = {
    "Bar": {
        "46242558000184": 114834911.26,
        "04626152000155": 75191209.77,
        "04626152000589": 57807574.57,
        "04626152000236": 14394927.23,
        "13480185000120": 9273451.45,
        "04626152000740": 6972599.2,
        "04626152000660": 6211330.86,
        "76104397000123": 1432445.4,
        "04626152000821": 1135383.55
    },
    "line_total_doc": {
        "2018-08-01T00:00:00": 25091993.65,
        "2018-09-01T00:00:00": 24824468.95,
        "2018-10-01T00:00:00": 30867122.27,
        "2018-11-01T00:00:00": 18662724.79,
        "2018-12-01T00:00:00": 25790976.32,
        "2019-01-01T00:00:00": 45915510.78,
        "2019-02-01T00:00:00": 219332207.14,
        "2019-03-01T00:00:00": 521535869.79,
        "2019-04-01T00:00:00": 298304478.08,
        "2019-05-01T00:00:00": 286302709.15
    },
    "line_tributos": {
        "VlPis": {
            "2018-08-01": 66825.34,
            "2018-09-01": 61348.68,
            "2018-10-01": 79969.39,
            "2018-11-01": 76221.52,
            "2018-12-01": 27777.04,
            "2019-01-01": 64381.92,
            "2019-02-01": 101723.6,
            "2019-03-01": 130789.09,
            "2019-04-01": 132020.68,
            "2019-05-01": 151876.72
        },
        "VlCofins": {
            "2018-08-01": 307801.79,
            "2018-09-01": 282575.81,
            "2018-10-01": 368343.83,
            "2018-11-01": 351079.99,
            "2018-12-01": 127942.21,
            "2019-01-01": 296547.08,
            "2019-02-01": 468331.07,
            "2019-03-01": 602052.05,
            "2019-04-01": 607417.3,
            "2019-05-01": 698913.18
        },
        "VlIcms": {
            "2018-08-01": 579616.07,
            "2018-09-01": 603905.48,
            "2018-10-01": 658627.97,
            "2018-11-01": 675782.1,
            "2018-12-01": 187434.27,
            "2019-01-01": 587657.91,
            "2019-02-01": 1010860.64,
            "2019-03-01": 1216810.4,
            "2019-04-01": 1068793.41,
            "2019-05-01": 1205247.28
        },
        "VlIpi": {
            "2018-08-01": 50606.06,
            "2018-09-01": 62963.93,
            "2018-10-01": 74667.45,
            "2018-11-01": 68879.68,
            "2018-12-01": 16503.87,
            "2019-01-01": 80179.81,
            "2019-02-01": 779303.66,
            "2019-03-01": 958982.24,
            "2019-04-01": 971285.35,
            "2019-05-01": 1289644.83
        }
    }
}

app = Dash(__name__)
server = app.server

actual_btn = 0
actual_name_button = ''
actual_name_options = []

buttons = [html.Button('btn1', id={"type": 'btn_plot', "index": 1}),
           html.Button('Mapas Indiretos', id={"type": 'btn_plot', "index": 2})]

app.layout = html.Div([
    html.Div([
        html.Center([
            *buttons,
            html.Div(id='additional_btn_container'),
            html.Div(id='plot_output')
        ]),
    ])
])


# Callback to change button colors when clicked
@app.callback(
    Output({'type': 'btn_plot', 'index': ALL}, 'style'),
    [Input({'type': 'btn_plot', 'index': ALL}, 'n_clicks')],
    prevent_initial_call=True
)
def change_button_color(n_clicks_list):
    styles = []
    for i, _ in enumerate(n_clicks_list):
        if i+1 == ctx.triggered_id.index:
            styles.append({'background-color': '#00FF00'})  # Change color of clicked button
        else:
            styles.append({'background-color': '#CCCCCC'})  # Reset color of other buttons
    return styles


# ================================
@callback(
    Output("additional_btn_container", "children"),
    Input({"type": "btn_plot", "index": ALL}, "n_clicks")
)
def add_buttons(_):
    print(ctx.triggered_id)
    if ctx.triggered_id:
        global actual_btn, actual_name_button, actual_name_options
        actual_btn = ctx.triggered_id.index

        btn_index = ctx.triggered_id.index * 10
        if ctx.triggered_id.index == 2:
            actual_name_button = 'Mapas Indiretos'
            actual_name_options = ['Option 1', 'Entradas - EFD ICMS IPI']
            additional_btns = [
                html.Button(f'Option {btn_index}', id={"type": 'btn_option', "index": btn_index}),
                html.Button(f'Entradas - EFD ICMS IPI', id={"type": 'btn_option', "index": btn_index + 1}),
            ]
        else:
            additional_btns = [
                html.Button(f'Option {btn_index}', id={"type": 'btn_option', "index": btn_index}),
                html.Button(f'Option {btn_index + 1}', id={"type": 'btn_option', "index": btn_index + 1}),
                html.Button(f'Option {btn_index + 2}', id={"type": 'btn_option', "index": btn_index + 2}),
            ]
        return html.Center(additional_btns)
    return dash.no_update


# Callback to change button colors when clicked
@app.callback(
    Output({'type': 'btn_option', 'index': ALL}, 'style'),
    [Input({'type': 'btn_option', 'index': ALL}, 'n_clicks')],
    prevent_initial_call=True
)
def change_button_color(n_clicks_list):
    styles = []

    for i, _ in enumerate(n_clicks_list):
        index_btn = actual_btn * 10 + i
        if index_btn == ctx.triggered_id.index:
            styles.append({'background-color': '#00FF00'})  # Change color of clicked button
        else:
            styles.append({'background-color': '#CCCCCC'})  # Reset color of other buttons
    return styles
# ================================


# ================================
# plot the dashboard
@callback(
    Output("plot_output", "children"),
    Input({"type": "btn_option", "index": ALL}, "n_clicks")
)
def display_plots(_):
    if ctx.triggered_id:
        # ====== Mapas Indiretos - Entradas - EFD ICMS IPI ======
        if actual_name_button == 'Mapas Indiretos':
            index_option = ctx.triggered_id.index - actual_btn * 10
            if actual_name_options[index_option] == 'Entradas - EFD ICMS IPI':
                initial_figure = plot_mapas_indiretos_entrada(json_mapas_indiretos_entrada)

                return [
                    dcc.Graph(id='bar-plot', figure=initial_figure,
                              style={'width': '90%', 'height': '80vh', 'position': 'absolute', 'top': '170px',
                                     'left': '50px', 'z-index': '1'}
                              ),
                ]
        return f"Button Option: {ctx.triggered_id.index}"
    return


if __name__ == "__main__":
    app.run_server(debug=True, port=5010)

Hi @Safwan

You can use the clickData prop to get the info from the figure:


@callback(
    Output("click-container", "children"),
    Input("bar-plot", "clickData")
)
def update(clickData):    
    return str(clickData)


Note - also include

app = Dash(__name__, suppress_callback_exceptions=True)

You can use a dcc.Store as described in the link I shared rather than using global variables. Otherwise it will fail in multi-user environment.

Thank you for your reply. That does not work. Where did you define the component_id, ā€˜click-containerā€™?

Yes that works perfectly, Thank you very much

1 Like