dash-core-components icon indicating copy to clipboard operation
dash-core-components copied to clipboard

Dash objects reset their height to 450px after changing tabs

Open gtg489p opened this issue 4 years ago • 20 comments

This problem started after I upgraded to dash 1.19.0 (possibly a dash-core-components 1.15.0 issue??)

I have an app with multiple tabs, with dash components on them like charts and indicators. I set the component heights using update_layout. When I first load the page, everything looks fine. But when I click on a different tab, and then go back to the previous tab, all the dash charts and indicators change their height to 450px. The indicators are set in my code to be 200px, and when they snap to 450px after changing tabs, the indicators components themselves stretch outside of their containing DIVs.

Can confirm this was not an issue in Dash 1.16 and was recently introduced.

As well as getting this on a backlog for a fix in future release, I'm also interested if anyone has a clever workaround to prevent this right now while using the current dash version.

Thanks, Nathan

gtg489p avatar Jan 26 '21 20:01 gtg489p

Thanks for the report @gtg489p - can you make a self-contained example app that shows the problem? There are a number of ways to set sizes so it's important that we're talking about the same thing.

alexcjohnson avatar Jan 26 '21 22:01 alexcjohnson

I can try for a full fledged example when time allows, but for the time being - to address your specific concern, here is how I set the height of the figures in my python code

fig.update_layout(height=200)

It is set once at initialization, and no callbacks ever touch it again.

Thanks, Nathan

gtg489p avatar Jan 27 '21 02:01 gtg489p

Here is a small app that shows the problem. It's a new issue in 1.19.0. It works fine in 1.18.1

import dash
import dash_html_components as html
import dash_core_components as dcc
import plotly.express as px
import pandas as pd


external_stylesheets = ["https://codepen.io/chriddyp/pen/bWLwgP.css"]

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

df = pd.DataFrame(
    {
        "Fruit": ["Apples", "Oranges", "Bananas", "Apples", "Oranges", "Bananas"],
        "Amount": [4, 1, 2, 2, 4, 5],
        "City": ["SF", "SF", "SF", "Montreal", "Montreal", "Montreal"],
    }
)

fig = px.bar(df, x="Fruit", y="Amount", color="City", barmode="group")
fig.update_layout(height=200)


tab1 = html.Div([html.H3("Tab content 1"), dcc.Graph(id="example-graph1", figure=fig)])
tab2 = html.Div([html.H3("Tab content 2"), dcc.Graph(id="example-graph2", figure=fig)])

app.layout = html.Div(
    [
        dcc.Tabs(
            id="tabs",
            value="tab-1",
            children=[
                dcc.Tab(label="Tab one", value="tab-1", children=tab1),
                dcc.Tab(label="Tab two", value="tab-2", children=tab2),
            ],
        ),
    ]
)


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

tabs_graph_height_issue

AnnMarieW avatar Jan 27 '21 03:01 AnnMarieW

Thanks @gtg489p and @AnnMarieW - moved to dcc as that's ultimately where the problem is. This is probably a result of https://github.com/plotly/dash-core-components/pull/905

alexcjohnson avatar Jan 27 '21 13:01 alexcjohnson

Hey @gtg489p

A workaround is to add the height in the style parameter of dcc.Graph: This works with 1.19.0

tab1 = html.Div(
    [
        html.H3("Tab content 1"),
        dcc.Graph(id="example-graph1", figure=fig, style={"height": 200})
    ]
)

AnnMarieW avatar Jan 27 '21 14:01 AnnMarieW

Thanks @AnnMarieW , I'll see to using that in the mean time (changed 200 to "200px")

Off topic, but how did you create that gif illustrating the bug? I know there are gif maker programs out there but just wondering if there's a commonly accepted way. Thanks

gtg489p avatar Jan 28 '21 00:01 gtg489p

@gtg489p Happy to help :-)

I'm not sure about the commonly accepted way to make a gif, but on Linux I use Peek and on Windows I used ScreenToGif. Both are pretty simple, which makes them easy to use.

AnnMarieW avatar Jan 28 '21 00:01 AnnMarieW

Any other workaround on this? Setting the height in the dcc.Graph does not work for me as I want to have different dcc.Graph and figure heights (and using 'overflowY': 'scroll').

ppfreitas avatar May 28 '21 19:05 ppfreitas

@ann-marie-ward thanks for this workaround. It works for me but is clunky to have to specify the height by style when my dcc.Graph objects (a plotly Table and a line_mapbox) already specify height via their height parameters.

In my case, my two tabs initially render correctly, click to the second one, good. Click back to the first, and the height changes to 450px. This is using 1.21. If I add your style parameter to both dcc.Graph objects, it works correctly.

nhoover avatar Sep 11 '21 18:09 nhoover

Experiencing the same problem as ppfreitas. Having another workaround/fix would be nice!

Admolly avatar Oct 07 '21 13:10 Admolly

Same problem here! The workaround works well though, thanks @AnnMarieW . But a fix would be good since now I am styling both the figure and graph in two places, when one should be enough

mcsewhoy avatar Nov 17 '21 13:11 mcsewhoy

Is this issue solved in Dash 2.x?

jo47011 avatar Jan 18 '22 18:01 jo47011

No, it's still not solved

daviddavo avatar May 22 '22 16:05 daviddavo

This issue happened when I changed one tab (showing a heatmap with default aspect ratio) to another tab (showing a bar chart). I tried the style parameter workaround as suggested:

Hey @gtg489p

A workaround is to add the height in the style parameter of dcc.Graph: This works with 1.19.0

tab1 = html.Div(
    [
        html.H3("Tab content 1"),
        dcc.Graph(id="example-graph1", figure=fig, style={"height": 200})
    ]
)

but still to no avail. I noticed that this issue only happens when I changed tabs from the heatmap tab, and the tab with issue is the subsequent tab from heatmap tab (where I am using px.imshow.

So I added the aspect='auto' parameter in px.imshow so that it follows the size of plot area. Now it works fine.

I am using:

  • python=3.10.5
  • dash=2.6.1
  • dash-bootstrap-components=1.2.1

yuenherny avatar Aug 20 '22 09:08 yuenherny

Experiencing same bug. Switching tabs resets height of Dropdown options container, previously set by maxHeight argument. image

lnkov avatar Sep 02 '22 11:09 lnkov

I'm still having this issue as my charts are dynamic heights based on internal variables.

Lxstr avatar Sep 06 '22 11:09 Lxstr

Here's a way to solve it. I used @AnnMarieW 's code for this example. Just added a Callback that uses the dcc.Tabs value as input to set the size of the graph everytime you switch tabs. It uses the current state of both graphs to transform it into a go.Figure (from plotly.graph_objects) and sets the layout's height you need.

It just causes a little blink in the chart and you can see how it changes sizes, but is only just a microsecond, so, if it's not a major problem for you, it works :)

https://user-images.githubusercontent.com/37990054/202556541-b132e93d-29d7-419b-8eff-2d950d8226b2.mp4


import dash
import dash_html_components as html
import dash_core_components as dcc
import plotly.express as px
import pandas as pd
from dash import Input, Output, State, ctx
from dash.exceptions import PreventUpdate
import plotly.graph_objects as go

external_stylesheets = ["https://codepen.io/chriddyp/pen/bWLwgP.css"]

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

df = pd.DataFrame(
    {
        "Fruit": ["Apples", "Oranges", "Bananas", "Apples", "Oranges", "Bananas"],
        "Amount": [4, 1, 2, 2, 4, 5],
        "City": ["SF", "SF", "SF", "Montreal", "Montreal", "Montreal"],
    }
)

fig = px.bar(df, x="Fruit", y="Amount", color="City", barmode="group")
fig.update_layout(height=200)


tab1 = html.Div([html.H3("Tab content 1"), dcc.Graph(id="example-graph1", figure=fig)])
tab2 = html.Div([html.H3("Tab content 2"), dcc.Graph(id="example-graph2", figure=fig)])

app.layout = html.Div(
    [
        dcc.Tabs(
            id="tabs",
            value="tab-1",
            children=[
                dcc.Tab(label="Tab one", value="tab-1", children=tab1),
                dcc.Tab(label="Tab two", value="tab-2", children=tab2),
            ],
        ),
    ]
)

@app.callback(
    [Output('example-graph1','figure'),
    Output('example-graph2','figure')],
    Input('tabs','value'),
    State('example-graph1','figure'),
    State('example-graph2','figure')
)

def controlGraphSizes(tabValue, figure1,figure2):

    if tabValue == 'tab-1':
        print(type(figure1))
        newFigure1 = go.Figure(figure1)
        newFigure1.update_layout(height=200)
        return [newFigure1,dash.no_update]
    
    elif tabValue == 'tab-2':
        print(type(figure2))
        newFigure2 = go.Figure(figure1)
        newFigure2.update_layout(height=200)
        return [dash.no_update,newFigure2]

    else:
        raise PreventUpdate

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

IsraelCea avatar Nov 17 '22 20:11 IsraelCea

Here's an example of a clientside callback for faster updating if your graph takes a while to create and specifying the height in style isn't a solution for you. Note that also specifying the height in the dcc.Graph style will likely overwrite the callback output.

The dcc.Tabs components has id='tabs', the dcc.Graph has id='plot' and the dcc.Graph is inside tab 1 with default value='tab-1'. The updated graph will have a height of 450px.

from dash import clientside_callback, Input, Output, State

clientside_callback(
    """
    function adjust_height(tab_value, plot) {
        if (tab_value === 'tab-1') {
            var plot = Object.assign({}, plot);
            plot.layout.height = 450;
            return plot;
        }
        return plot;
    }
    """,
    Output('plot', 'figure', allow_duplicate=True),
    Input('tabs', 'value'),
    State('plot', 'figure'),
    prevent_initial_call=True
)

Use allow_duplicate=True in the Output if the dcc.Graph is created through another callback. Otherwise you should be okay to remove it.

MitchMedeiros avatar May 27 '23 21:05 MitchMedeiros

The graphs on each of my tabs have a variable height that is calculated based on the len of the df in the callback. The height varies widely depending on the value selected from the dropdown. In this case, setting a fixed height is not an option. I don't think any of the proposed workarounds will work for my case.

Any ideas in this case?

chivonblanton avatar Sep 07 '23 22:09 chivonblanton

Here's a way to solve it. I used @AnnMarieW 's code for this example. Just added a Callback that uses the dcc.Tabs value as input to set the size of the graph everytime you switch tabs. It uses the current state of both graphs to transform it into a go.Figure (from plotly.graph_objects) and sets the layout's height you need.

It just causes a little blink in the chart and you can see how it changes sizes, but is only just a microsecond, so, if it's not a major problem for you, it works :)

WhatsApp.Video.2022-11-17.at.17.37.49.4.mp4


import dash
import dash_html_components as html
import dash_core_components as dcc
import plotly.express as px
import pandas as pd
from dash import Input, Output, State, ctx
from dash.exceptions import PreventUpdate
import plotly.graph_objects as go

external_stylesheets = ["https://codepen.io/chriddyp/pen/bWLwgP.css"]

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

df = pd.DataFrame(
    {
        "Fruit": ["Apples", "Oranges", "Bananas", "Apples", "Oranges", "Bananas"],
        "Amount": [4, 1, 2, 2, 4, 5],
        "City": ["SF", "SF", "SF", "Montreal", "Montreal", "Montreal"],
    }
)

fig = px.bar(df, x="Fruit", y="Amount", color="City", barmode="group")
fig.update_layout(height=200)


tab1 = html.Div([html.H3("Tab content 1"), dcc.Graph(id="example-graph1", figure=fig)])
tab2 = html.Div([html.H3("Tab content 2"), dcc.Graph(id="example-graph2", figure=fig)])

app.layout = html.Div(
    [
        dcc.Tabs(
            id="tabs",
            value="tab-1",
            children=[
                dcc.Tab(label="Tab one", value="tab-1", children=tab1),
                dcc.Tab(label="Tab two", value="tab-2", children=tab2),
            ],
        ),
    ]
)

@app.callback(
    [Output('example-graph1','figure'),
    Output('example-graph2','figure')],
    Input('tabs','value'),
    State('example-graph1','figure'),
    State('example-graph2','figure')
)

def controlGraphSizes(tabValue, figure1,figure2):

    if tabValue == 'tab-1':
        print(type(figure1))
        newFigure1 = go.Figure(figure1)
        newFigure1.update_layout(height=200)
        return [newFigure1,dash.no_update]
    
    elif tabValue == 'tab-2':
        print(type(figure2))
        newFigure2 = go.Figure(figure1)
        newFigure2.update_layout(height=200)
        return [dash.no_update,newFigure2]

    else:
        raise PreventUpdate

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

In case anyone else is facing the same issue, and to complete a bit this answer, I managed to avoid the blink by having the tab content outside the Tab component, as in this example: https://dash-bootstrap-components.opensource.faculty.ai/examples/graphs-in-tabs/

I have only tried it with dbc.Tabs and not with dcc.Tabs, so I don't know if it'd solve the issue. In any case I think it can be useful.

Cheers

cscadenas avatar Apr 08 '24 23:04 cscadenas