Black Lives Matter. Please consider donating to Black Girls Code today.

Trigger an event by changing tab

Hii,

I want to ask if someone has a clue how to trigger an Event (@app.callback…) by changing a tab.
So it’s similar like a button, only that a butten has one trigger, but a tab with 3 slots have 3 triggers.

It should be similar with how you trigger a callback for dropdown.

1 Like

Hi, my problem is I have ah tab with an graph who gets live updated by an interval, but if I change the tab, the graph waits for the interval and is first visible after the interval.
So I have to wait until the interval is done

Then you use tab to trigger interval and then use interval to trigger the figure. It seems to me that tab behaves almost exactly like drop-down. It’s just in a different form and you could see all the options.

Yes of course, but my problem is, if I have an interval of 10 sec. and change my tab, I have to wait 10 sec before my graph appears.

I don’t understand. You have to wait 10 secs right? Because that’s the time it takes to generate the figure. How do you want it to behave?

Would something like this work?

@app.callback(Output('container', 'children'), [Input('tab', 'value')]):
def display_content(selected_tab):
    return html.Div([
          html.H1(selected_tab),
          dcc.Graph(id='graph', figure=generate_figure(selected_tab))
    ])


def generate_figure(selected_tab):
    return {'data': [{'x': [1, 2, 3]}], 'layout': {...} # fill in with your figure

app.callback(
    Output('graph', 'figure'), 
    events=[Event('my-interval', 'interval')],
    state=[State('tab', 'value')
)(generate_figure)

This is the way I have build it now, the problem is if you make a interval of 60 sec, will have to wait after tab changing for the figure until its recreated…

Hm, @Gustiii could you share your solution then? The sketch that I posted in Trigger an event by changing tab works for me. It generates a dynamic graph on tab click (inside the display_content function) and again after interval seconds from the second callback around generate_figure.

Here’s a GIF if this solution in action. Note how clicking on the tab updates the graph’s title immediately and notice how the title updates again after 3 seconds on that current tab.

Here’s the full code example:

import dash
from dash.dependencies import Input, Output, Event, State
import dash_core_components as dcc
import dash_html_components as html

import datetime

app = dash.Dash()
app.scripts.config.serve_locally = True
app.config['suppress_callback_exceptions'] = True

app.layout = html.Div([
    dcc.Tabs(
        tabs=[
            {'label': 'Tab {}'.format(i), 'value': i} for i in range(1, 5)
        ],
        value=3,
        id='tab'
    ),
    html.Div(id='container'),
    dcc.Interval(id='my-interval', interval=3*1000)
])


def generate_figure(selected_tab):
    now = datetime.datetime.now()
    return {
        'data': [{'y': [1, 5, 3]}],
        'layout': {
            'title': 'Tab {} at {}:{}:{}'.format(
                selected_tab, now.hour, now.minute, now.second
            )
        }
    }


@app.callback(Output('container', 'children'), [Input('tab', 'value')])
def display_content(selected_tab):
    return html.Div([
          html.H1(selected_tab),
          dcc.Graph(id='graph', figure=generate_figure(selected_tab))
    ])


app.callback(
    Output('graph', 'figure'),
    events=[Event('my-interval', 'interval')],
    state=[State('tab', 'value')])(generate_figure)


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

Hey @chriddyp thank your for your work.
I give you a simplified sample of my code.
If you run it, you will see it takes 5 second after tab changing until the random number appears.

import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, Event
import plotly
import numpy as np
from random import randint

app = dash.Dash()

app.css.config.serve_locally = True
app.scripts.config.serve_locally = True
app.config['suppress_callback_exceptions']=True


app.layout = html.Div([
                #TABS
                dcc.Tabs(
                    tabs=[
                        {'label': 'Tab1', 'value': 1},
                        {'label': 'Tab2', 'value': 2}],
                    value=1,
                    id='tabs',
                    vertical=False),
                html.Div(id='tab-output'),
                dcc.Interval(id='interval-component5000',interval=1*5000),

            ],
            style={
                'width': '80%',
                'fontFamily': 'Sans-Serif',
                'margin-left': 'auto',
                'margin-right': 'auto'}
            )

@app.callback(Output('live1', 'children'),
              events=[Event('interval-component5000', 'interval')])
def rand_num1():
    
    return (html.H4(str(randint(0,9))))

@app.callback(Output('live2', 'children'),
              events=[Event('interval-component5000', 'interval')])
def rand_num2():
    
    return (html.H4(str(randint(0,9))))



@app.callback(Output('tab-output', 'children'),
              [Input('tabs', 'value')])
def display_tab(value):
    
    if value == 1:
        return (html.Div(id='live1'))
            
    if value == 2:
        return (html.Div(id='live2'))


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

@Gustiii - You’ll need to modify your example so that your display_tab function calls your rand_num2 or rand_num1 functions to get the most recent data. I’d recommend taking a closer look at the example that I posted in Trigger an event by changing tab - clicking on tab calls generate_figure to get the most recent figure and the interval callback calls generate_figure after N seconds.

@Gustiii - And here is your example updated with my suggestions above:

import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, Event
import plotly
import numpy as np
from random import randint

app = dash.Dash()

app.css.config.serve_locally = True
app.scripts.config.serve_locally = True
app.config['suppress_callback_exceptions']=True


app.layout = html.Div([
                #TABS
                dcc.Tabs(
                    tabs=[
                        {'label': 'Tab1', 'value': 1},
                        {'label': 'Tab2', 'value': 2}],
                    value=1,
                    id='tabs',
                    vertical=False),
                html.Div(id='tab-output'),
                dcc.Interval(id='interval-component5000',interval=1*5000),

            ],
            style={
                'width': '80%',
                'fontFamily': 'Sans-Serif',
                'margin-left': 'auto',
                'margin-right': 'auto'}
            )

def rand_num1():
    return (html.H4(str(randint(0,9))))

app.callback(
    Output('live1', 'children'),
    events=[Event('interval-component5000', 'interval')])(rand_num1)

def rand_num2():    
    return (html.H4(str(randint(0,9))))

app.callback(
    Output('live2', 'children'),
    events=[Event('interval-component5000', 'interval')])(rand_num2)


@app.callback(Output('tab-output', 'children'),
              [Input('tabs', 'value')])
def display_tab(value):
    
    if value == 1:
        return (html.Div(id='live1', children=rand_num1()))
            
    if value == 2:
        return (html.Div(id='live2', children=rand_num2()))


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

I recommend reading through https://stackoverflow.com/questions/739654/how-to-make-a-chain-of-function-decorators/1594484#1594484 to get a better sense of how these decorators work and how you can refactor your code around them. :beers:

2 Likes