Is it possible to return a different layout to the same Output in plotly

Im working on a project where I would like to return a different layout from the function to the decorator. I am trying to imitate a PowerPoint slide with Plotly.

2 Likes

Hello @RalphLawrence23 and welcome to the forum!

Of course it is possible :). For example if you include html.Div() component into your layout and you target its children property in Output you can return any layout you would like. If it will not be clear to you you will have to be a little bit more specific with what is unclear :slight_smile:

2 Likes

Hello Martin thanks for the response here’s what I have in mind. Here is what I have done so far:


from dash import Dash, dcc, html, Input, Output
import dash_bootstrap_components as dbc

app = Dash(__name__, external_stylesheets=[dbc.themes.SUPERHERO, dbc.icons.BOOTSTRAP])

interval = 10000

def first_card():
    return dbc.Card(
        html.Div('This is picture 1'),
        className="text-center text-nowrap my-2 p-2",
    )

def second_card():
    return dbc.Card(
        html.Div('This is picture 2'),
        className="text-center text-nowrap my-2 p-2",
    )

def third_card():
    return dbc.Card(
        html.Div('This is picture 3'),
        className="text-center text-nowrap my-2 p-2",
    )

interval = dcc.Interval(interval=interval)
cards = html.Div()
app.layout = dbc.Container([interval, cards], className="my-5")

@app.callback(
    Output(cards, "children"),
    Input(interval, "n_intervals"))
def update_cards(_):
    for i in range(1, 3):
        if i == 1:
            return first_card()
        elif i == 2:
            return second_card()
        elif i == 3:
            return third_card()

First of all, I’m kinda new to Python so I’m going to need some help. I want the topmost card to be shown first for about 10 seconds, then the second card for another 10, and the third card for another 10. I tried adding a time.sleep(10) but not sure where to place it.

1 Like

I think you can use dbc.Carousel for this purpose. Something as below:

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

carousel = dbc.Carousel(
    items=[
        {"key": "1", "src": "https://media.cnn.com/api/v1/images/stellar/prod/230621042149-01-cristiano-ronaldo-euro-200-apps-062023-restricted.jpg?c=16x9&q=h_540,w_960,c_fill/f_webp"},
        {"key": "2", "src": "https://www.altonherald.com/tindle-static/image/2022/10/05/17/FH%20Wayne%20Rooney.jpg?width=669&height=445&crop=669:445"},
        {"key": "3", "src": "https://i1-thethao.vnecdn.net/2017/12/18/Mu-9658-1513564506.jpg?w=680&h=0&q=100&dpr=1&fit=crop&s=TaSwhDcVZQ0ClUblWWtYXg"},
    ],
    controls=False,
    indicators=False,
    interval=2000,
    ride="carousel",
)

app = dash.Dash(__name__, title='Dashboard',external_stylesheets=[dbc.themes.BOOTSTRAP])

app.layout = html.Div(
    dbc.Row([
        dbc.Col([
            dbc.Card([
                dbc.CardBody([carousel])
            ])
        ], width={'size':4,"offset":0})
    ])
)

if __name__ == "__main__":
    app.run_server(debug=False,port=8055)

Recording 2023-07-07 082650 (1)

4 Likes

@hoatran Thanks for the response, I saw this but I was thinking, instead of a picture, can I use a layout instead? Sort of a hybrid carousel, only in this case, each slide has a different layout. I’m thinking along the lines of PowerPoint. The closes POC I can think of is using Tabs. But the headache is dynamically switching Tabs.

I believe you can use an Interval component to trigger the switch. Would that work for you? :blush:

1 Like

@Emil Thanks for the response. Did that already, problem that I’m finding is that intervals don’t pause decorators. I need to be able to return a different layout based on an if statement. The purpose of the project is mimic power point by displaying 3 different data visualization in the same layout. The problem is that I’m finding that this type of job request is leaning more and more toward a multipage app. I want the app to mimic a carousel, but the app.layout can be a fig, picture, table anything I chose.

I have a question that does it need to be updated by intervals or just something like Tabs, Radioitems?

You could use tabs if you like, but it is not necessary. Here is a small example using a simple Div as container,

from dash import Dash, dcc, html, Input, Output
import dash_bootstrap_components as dbc


# region Card definition

def first_card():
    return dbc.Card(
        html.Div('This is picture 1'),
        className="text-center text-nowrap my-2 p-2",
    )


def second_card():
    return dbc.Card(
        html.Div('This is picture 2'),
        className="text-center text-nowrap my-2 p-2",
    )


def third_card():
    return dbc.Card(
        html.Div('This is picture 3'),
        className="text-center text-nowrap my-2 p-2",
    )


cards = [first_card, second_card, third_card]

# endregion

app = Dash(__name__, external_stylesheets=[dbc.themes.SUPERHERO, dbc.icons.BOOTSTRAP])
interval = dcc.Interval()
container = html.Div(children=cards[0]())
app.layout = dbc.Container([interval, container])


@app.callback(
    Output(container, "children"),
    Input(interval, "n_intervals"),
    prevent_initial_call=True)
def update_cards(n_intervals):
    i = n_intervals % len(cards)
    return cards[i]()


if __name__ == "__main__":
    app.run_server(port=7676)
5 Likes

HI @RalphLawrence23 here an MRE using dash pages, navigation via icons in a navbar

import dash
from dash import dcc, html, callback, Input, Output, ctx
import dash_bootstrap_components as dbc
from collections import deque


external_stylesheets = [
    dbc.themes.FLATLY,
    dbc.icons.BOOTSTRAP,
]

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

page_paths = deque([page['path'] for page in dash.page_registry.values() if page['name'] != 'Not found 404'])

# create a navbar
navbar = dbc.Navbar(
    id='navbar',
    children=[
        dbc.Nav(
            [
                html.Div(
                    id='bwd',
                    className='bi bi-arrow-left-square',
                    style={
                        'font-size': '25px',
                        'marginLeft': '5px',
                        'marginRight': '5px',
                        'padding-bottom': '5px',
                        'color': 'white'
                    },
                ),
                html.Div(
                    id='fwd',
                    className='bi bi-arrow-right-square',
                    style={
                        'font-size': '25px',
                        'marginLeft': '5px',
                        'marginRight': '5px',
                        'padding-bottom': '5px',
                        'color': 'white'
                    },
                )
            ],
            navbar=True,
        ),
    ],
    color='primary',
    className='mb-0, p-0',
    # ^^ no margin on bottom
    style={'height': '50px'}
)

app.layout = html.Div(
    [
        navbar,
        html.Div(
            dash.page_container,
            className='container'
        ),
        dcc.Location(id="url", refresh="callback-nav")
    ],
)


@callback(
    Output("url", "href"),
    Input("fwd", "n_clicks"),
    Input("bwd", "n_clicks"),
    prevent_initial_callback=True
)
def generate_chart(*clicks):
    if not any(clicks):
        return "/"

    trigger = ctx.triggered_id

    if trigger == 'fwd':
        page_paths.rotate(-1)
    else:
        page_paths.rotate(1)

    return page_paths[0]


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

custom.css:

.container {
    border: 1px solid;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    padding: 10px;
    background-color: gray;
    height: 80%;
    width: 90%;
}

ínitial_page.py

from dash import html
import dash

dash.register_page(__name__, path="/", order=0)

layout = html.H1("Start")

page_1.py, page_2.py

from dash import html
import dash

dash.register_page(__name__, path="/your_slide_1", order=1)

layout = html.Div("Your layout on page 1")

gif

4 Likes

I like this sneaky line :laughing:

2 Likes

@AIMPED you have come closes to what I was looking for. Thanks for the help. Im trying to integrate an interval, this is what I have so far but its not working.

app.layout = html.Div(
    [
        navbar,
        dcc.Interval(
            id='interval-component',
            interval=7000,
            n_intervals=0
        ),
        html.Div(
            dash.page_container,
            className='container'
        ),
        dcc.Location(id="url", refresh="callback-nav")
    ],
)
@callback(
    Output("url", "href"),
    Input("fwd", "n_clicks"),
    Input("bwd", "n_clicks"),
    Input("interval-component", "n_interval"),
    prevent_initial_callback=True
)
def generate_chart(*clicks, n_interval):
    if not any(*clicks):
        return "/"

    forward = 'fwd'
    trigger = ctx.triggered_id if ctx.triggered_id else forward

    if trigger == 'fwd':
        page_paths.rotate(-1)
    else:
        page_paths.rotate(1)

    return page_paths[0]

But I’m getting an error. Not sure what I’m doing wrong.

The interval will trigger a forward click. Not sure how close I am to getting this thing to work, but maybe someone could lead me in the right direction.

Did you try the code i posted? I believe it does exactly what you want. If not, please elaborate on what is missing :blush:

You have a typo here, it should be n_intervals.

@Emil, got distracted by @AIMPED giff but yeah, yours is actually what I was looking for. I like the idea of the multipage, better organization. Can you integrate yours into a multipage where I could use these callbacks:

    Output("url", "href"),
    Input("fwd", "n_clicks"),
    Input("bwd", "n_clicks"),
    Input("interval-component", "n_interval"),
    prevent_initial_callback=True

@Emil I like the idea of having both options.

Hello @RalphLawrence23,

Yes, you have tons of flexibility with how you want to achieve this, taking @Emil’s example and using pages similar to @AIMPED , you can do something like this:

import dash
from dash import Dash, dcc, html, Input, Output, State, no_update, ctx
import dash_bootstrap_components as dbc


# region Card definition

def first_card():
    return dbc.Card(
        html.Div('This is picture 1'),
        className="text-center text-nowrap my-2 p-2",
    )


def second_card():
    return dbc.Card(
        html.Div('This is picture 2'),
        className="text-center text-nowrap my-2 p-2",
    )


def third_card():
    return dbc.Card(
        html.Div('This is picture 3'),
        className="text-center text-nowrap my-2 p-2",
    )


cards = ['/', '/2', '/3']

# endregion

app = Dash(__name__, external_stylesheets=[dbc.themes.SUPERHERO, dbc.icons.BOOTSTRAP],
           pages_folder='', use_pages=True)
interval = dcc.Interval(interval=5000)
buttons = html.Div([html.Button(id='prev', className='bi bi-arrow-left-square'),
                    html.Button(id='pause', className='bi bi-pause-btn'),
                    html.Button(id='play', className='bi bi-play-btn', disabled=True),
                    html.Button(id='next', className='bi bi-arrow-right-square')])
app.layout = dbc.Container([interval, buttons, dash.page_container])

dash.register_page('1', path='/', layout=first_card)
dash.register_page('2', path='/2', layout=second_card)
dash.register_page('3', path='/3', layout=third_card)


@app.callback(
    Output('_pages_location', 'href'),
    Input(interval, "n_intervals"),
    State('play', 'disabled'),
    State('_pages_location', 'pathname'),
    prevent_initial_call=True)
def update_cards(n_intervals, v, pathname):
    if not v:
        return no_update
    i = pathname.replace('/', '')
    # sets i to current page location
    if not i:
        i = 0
    else:
        i = int(i) - 1
    i += 1
    i = i % len(cards)
    return cards[i]

@app.callback(
    Output('_pages_location', 'href', allow_duplicate=True),
    Input('prev', "n_clicks"),
    Input('next', "n_clicks"),
    State('_pages_location', 'pathname'),
    prevent_initial_call=True)
def update_cards_buttons(n1, n2, pathname):
    i = pathname.replace('/', '')
    # sets i to current page location
    if not i:
        i = 0
    else:
        i = int(i) - 1
    if ctx.triggered_id == 'prev':
        ## set that page back, -1 = last in the page list
        i -= 1
    else:
        i += 1
        # loops 3 => 0
        i = i % len(cards)
    return cards[i]

@app.callback(
    Output('pause', 'disabled'),
    Output('play', 'disabled'),
    Input('pause', 'n_clicks'),
    prevent_initial_call=True
)
def pause(n1):
    return True, False

@app.callback(
    Output('pause', 'disabled', allow_duplicate=True),
    Output('play', 'disabled', allow_duplicate=True),
    Input('play', 'n_clicks'),
    prevent_initial_call=True
)
def play(n1):
    return False, True


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

This is just an example of how you’d do it. The main thing is figuring out the order of the cards and where you are in the list. :slight_smile:

@jinnyzor you took this to a whole different level, going to implement it now. Question is there some kind of book that I can read to learn about doing it this way, it looks pretty advance if you ask me. A link or some thing to start with:

@app.callback(
    Output('_pages_location', 'href'),
    Input(interval, "n_intervals"),
    State('play', 'disabled'),
    State('_pages_location', 'pathname'),
    prevent_initial_call=True)
def update_cards(n_intervals, v, pathname):
    if not v:
        return no_update
    i = pathname.replace('/', '')
    # sets i to current page location
    if not i:
        i = 0
    else:
        i = int(i) - 1
    i += 1
    i = i % len(cards)
    return cards[i]

I don’t believe you will find this in any book :). @jinnyzor just cleverly used his very good overview in components properties.

Instead of using standard n_clicks property of the button (which increments by one each time you click it) he is using disabled property of the button (which is True or False - True means it is not clickable and False means that it can be clicked)

Here is my analysis of the callback you quoted:

So firstly the callback is triggered on each interval tick.

Since there are “play” and “pause” buttons implemented you have to check if you are in “play” state. If the disabled property of play button is False (so you are paused because play button is clickable) you won’t move to next layout. Returning no_update means that the callback will not update targeted Output (in this case we have only one Output so the callback won’t do anyting)

Here you extract current page from pathname (i.e. you extract “2” from http://127.0.0.1:7676/2)

When you initialize the page there is no number behind / so you have to set something to i variable.

When there is something in i variable it is a string since you extracted it from pathanme string. Here you convert it to number and subtract 1 from it (subtraction here is because lists are indexed from 0 - so page 1 is stored on index 0, page 2 is stored at index 1 etc)

Here you move to the next page.

If you moved to the page 4 if you have only 3 pages here you will come back to the begining (% is modulo operation)

And now you just return the page you need :slight_smile:

By the way, don’t be worried about complexity of this code. There are always numerous number of great implementation and you will get grip of it when you get to know the component libraries better. For example this code can get little bit more simple when you replace play and pause button with something like dmc.Switch. But you will lose some really fancy disabled property utilization :)).

1 Like