Simple gallery with previous and next buttons help

Hi Dash Community! This is my first post, so please let me know if its in the wrong category. :grin:

I am trying to create a simple gallery to display images the user can upload. I want to have previous and next buttons to click backward and forward through the array of uploaded images. So far, I am using bootstrap buttons which have n_click attribute. Iโ€™m not sure how to prevent indices that are negative or go beyond the length of the list. This is what I have so far:

@app.callback(Output("displayProcessedImage", "children"),
              [Input("uploadImage", "contents"),
              Input("prevImage", "n_clicks"),
              Input("nextImage", "n_clicks")],
              prevent_initial_call=True)
def displayImagesPrevious(contents, prevClicks, nextClicks):
    if not contents:
        return html.Div()

    # get the index of image to view
    lenContents = len(contents)
    if nextClicks is None:
        index = 0
    elif prevClicks is not None:
        dif = nextClicks - prevClicks
        if dif < 0:
            index = 0
        else:
            index = min(dif, lenContents - 1)
    else:
        index = min(nextClicks, lenContents - 1)
    print("prev: ", prevClicks, " next: ", nextClicks, " index: ", index)

    return dcc.Graph(figure=runModel(contents[index]))

The logic I am using to get the index does not work correctly when you spam a button then try to go in the other direction. Any help is appreciated!

Hi @Angop and welcome to the Dash community :slightly_smiling_face:

I think the new Carousel component in the latest release of Dash Bootstrap Components might work for you. See more information in the announcement here Dash Bootstrap Components v0.13.0 New Carousel Component, R and Julia examples

Thank you @AnnMarieW , this is almost exactly what I was looking for! The only difference is the carousel only seems to accept images, whereas I was trying to display the image as a Plotly figure so the user can zoom in and possibly annotate it. (I should have mentioned that in my original post!)

This will work perfectly as a temporary fix for now though! :tada:

Correct - the Carousel only displays images, so you were on the right track originally.

Hmm, on second thought, you might be able to do a hybrid. For example you could use the Carousel to scroll through the images, then if you want to zoom in and annotate, there could be a button to open that image as a Plotly figure for annotating etc. .

I figured out another way! Instead of literally trying to get the index from the n_clicks of next and previous buttons, I have a temporary index variable (using dcc.store) that is updated when the buttons are clicked IF it wonโ€™t cause the index to be undefined.

Here is the code if anyone is trying to do something similar:

@cfg.app.callback(Output("displayProcessedImage", "children"),
              [Input("uploadImage", "contents"),
              Input("imageIndex", "data")],
              prevent_initial_call=True)
def displayImages(contents, data):
    """
    Displays the labeled image given the array of uploaded images and the index
    """
    if not contents:
        return html.Div()

    # get the index of image to view
    data = data or {'index': 0} # default index to 0 if not already set
    index = data['index']

    return dcc.Graph(figure=runModel(contents[index]))

@cfg.app.callback(Output("imageIndex", "data"),
              [Input("prevImage", "n_clicks"),
              Input("nextImage", "n_clicks")],
              [State("imageIndex", "data"),
              State("uploadImage", "contents")])
def updateIndex(prev, next, data, contents):
    """
    When the next or previous buttons are clicked, update the image index
    ensuring it stays within 0 <= index < len(contents)
    """
    if not contents:
        return data

    # set default index if there is none
    data = data or {'index': 0}
    index = data["index"]

    # get which button click triggered callback
    trig = dash.callback_context.triggered[0]['prop_id']

    # update data if necessary and return it
    if trig == "prevImage.n_clicks" and index > 0:
        data['index'] = index - 1
    elif trig == "nextImage.n_clicks" and index < len(contents) - 1:
        data['index'] = index + 1
    else:
        # index should not change, it would cause undefined index
        raise PreventUpdate
    return data
1 Like