Facet plot paging or virtualization

Is there any way to get facet plot paging or virtualization in Dash? I have a very large dataset with thousands of facet levels, so it’s pretty much impossible to generate all of the plots all at once. I would like to be able to have each facet only be generated during scrolling or at least be on its own page (with forward/back buttons), similar to way virtualization works for DataTable.
Thanks!

are you using the px facet functions? if so, those will return a single figure that will be hard to break up into multiple dcc.Graph functions.

however, if you can write your own facetting that creates separate figures, then you could write your own paging with two html.Button components that update a html.Div with a list of dcc.Graph functions. determine which page you are on by subtracting the “next” n_clicks from the “previous” n_clicks. or, instead of buttons, provide a set of links (1, 2, 3, …) each with their own href that updates the url. then, display a unique set of graphs depending on the url by using the dcc.Location component.

Thanks for the fast response! The button method makes sense. I would like to use the derived data from filtering within a DataTable to get the unique values of the level that should be paged. I’m not sure I follow what you mean about the clicks. Is the number of clicks a property I can read or do you mean to update a variable that stores the number of clicks within the button callback?

yeah, it’s a property of the html.Button component that you can read from in your callbacks

Got it. I’ll play around with this and see if I can get something working. Thanks again!

cool! would love to see what you come up with (screenshots, etc!)

Hi @chriddyp, thanks again for your help on this. I ended up accomplishing what I wanted by making the current facet a data-* (data-panel) property of the div containing the graph and put in a callback that updates the data-panel property based on button clicks (right or left) or updates to the derived_virtual_row_ids of a DataTable. I then filter the current data on the backend based on the row ids, determine if the current panel is possible given the filtering/go to the previous or next available panel based on the click action. The graph then updates in response to the data-panel property. I can’t show a screenshot unfortunately, but here is the basic gist in code:

html.Div(children=[
        html.H1(children='Dashboard'),
        html.Div(children=[
            dash_table.DataTable(
                id='data',
                columns=[{"name": i, "id": i} for i in data.columns],
                data=make_data_dict_records(data),
                filter_action="native",
                sort_action="native",
                sort_mode="multi",
                style_table={
                    "overflowX": "scroll",
                    "height": "25rem",
                    "width": "100rem",
                },
                style_cell={
                    "minWidth": "10em"
                },
                virtualization=True,
                derived_virtual_row_ids=data.index,
                fixed_rows={'headers': True},
            )
        ]),
        html.Hr(),
        dcc.Tabs(
            id="graph-tabs",
            value="tab-1",
            children=[
                dcc.Tab(
                    label="Over time",
                    value="tab-1",
                    children=[
                        html.Div(id='graph-container', children=[
                            html.Button(
                                id='btn-prev',
                                type='button',
                                children=html.I(className='fas fa-chevron-left'),
                                style={'display': 'inline-block', 'width': '25%'},
                            ),
                            html.Button(
                                id='btn-next',
                                type='button',
                                children=html.I(className='fas fa-chevron-right'),
                                style={'display': 'inline-block', 'width': '25%'},
                            ),
                            html.Div(id='graph', children=[
                                dcc.Graph(
                                    id='bcp-scatter',
                                )
                            ])
                        ], **{'data-panel': None})
                    ]
                ),
                dcc.Tab(
                    label="Heatmap",
                    value="tab-2",
                    children=[
                        html.H3(
                            "Place holder"
                        )
                    ]
                )
            ]
        )
    ])

@_app.callback(
    Output("graph-container", "data-panel"),
    [Input("btn-next", "n_clicks"),
     Input("btn-prev", "n_clicks"),
     Input("data", "derived_virtual_row_ids")],
    [State("graph-container", "data-panel")]
)
def update_panel(prev_clicks, next_clicks, row_ids, curr_panel):
    if not callback_context.triggered or not row_ids:
        raise PreventUpdate

    global data
    df = data.iloc[row_ids, :]

    triggered = callback_context.triggered[0]['prop_id']

    if triggered == 'btn-prev.n_clicks':
        return get_prev_panel(curr_panel, df)

    if triggered == 'btn-next.n_clicks':
        return get_next_panel(curr_panel, df)

    if triggered == 'data.derived_virtual_row_ids':
        return get_new_panel(curr_panel, df)

@_app.callback(
    Output('bcp-scatter', 'figure'),
    [Input('graph-container', 'data-panel')],
    [State('data', 'derived_virtual_row_ids')],
)
def update_graph(panel, row_ids):
    global data
    df = data.iloc[row_ids, :].set_index('short_name').loc[(panel,), :]
    return dict(
        data=[
            make_scatter(df, dna)
            for dna in df['dna_id'].unique()
        ],
        layout={
            'title': {
                'text': panel,
                'xanchor': 'center',
            }
        }
    )

Now I just have to style everything and get it working seamlessly within a larger Pyramid app, which is annoying but works surprisingly well once you get all of the routes properly mapped… I also really need to move the data loading into its own component and possibly make it thread local, but this works for development purposes.

1 Like

nice, looks good! thanks for following up