Change properties of all other objects when one object is clicked

Hello everyone, I’m very new to dash and plotly and I’m trying to find a more elegant solution to this problem. I have many images (3x3 array, but in the future I’d like to increase it). I’d like for only one image to be “active” at a given time. Active means that I’m able to perform some actions with it, such as highlight it, rotate it etc. I would like that when I click on one given image, it becomes the “active” one and this action deactivates the previously active image.

I have managed to achieve this for the simple case of dynamically changing the opacity of just two images which are given by

img11 = html.Img(id={‘type’: ‘tile-image’, ‘index’: ‘tile11’}, src=’/assets/pngs/test11.png’, style={‘opacity’: 0.5}, alt=“Tile 11”, n_clicks_timestamp=0)
img12 = html.Img(id={ ‘type’: ‘tile-image’, ‘index’: ‘tile12’}, src=’/assets/pngs/test12.png’, style={‘opacity’: 1.0}, alt=“Tile 12”, n_clicks_timestamp=0)

with the following callback:

[Output({‘type’: ‘tile-image’, ‘index’: ‘tile11’ }, ‘style’),
Output({‘type’: ‘tile-image’, ‘index’: ‘tile12’ }, ‘style’),
[Input({‘type’: ‘tile-image’, ‘index’: ‘tile11’ }, ‘n_clicks_timestamp’),
Input({‘type’: ‘tile-image’, ‘index’: ‘tile11’ }, ‘style’),
Input({‘type’: ‘tile-image’, ‘index’: ‘tile12’ }, ‘n_clicks_timestamp’),
Input({‘type’: ‘tile-image’, ‘index’: ‘tile12’ }, ‘style’),
def change_img(click1, style1, click2, style2):

if not click1: raise PreventUpdate
if not click2: raise PreventUpdate

if click1:
    if style1['opacity']==1.0:
elif click2:
    if style2['opacity']==1.0:

return style1, style2

As you can see this is pretty clunky. I have to give many inputs and outputs and the callback has a lot if statements. It is hardly scalable to many images. Is there a way to make this more efficient? Ideally, I would like to have a variable ‘active’ for every image in the ID dictionary that changes to True whenever we click on it, and at the same time this action should turn active=False for all the other images. Is that feasible? Can I change IDs dynamically like that?

Thanks a lot for your help!

Hi @pmolignini,

Welcome to the community! :slightly_smiling_face:

I think you can simplify quite a lot your callback using pattern-matching callbacks (with ALL) and the callback_context. You are already using ids with type and index, which makes it easier to modify.

Here’s how it would look like:

# from dash import State, ALL
    Output({"type": "tile-image", "index": ALL}, "style"),
    Input({"type": "tile-image", "index": ALL}, "n_clicks"),
    State({"type": "tile-image", "index": ALL}, "id"),
    State({"type": "tile-image", "index": ALL}, "style"),
def update_img(n_clicks, ids, styles):
     ctx = dash.callback_context
     triggered_str = ctx.triggered[0]['prop_id'].split('.')[0]
     # this is a json str, needs to be parsed...
     triggered_id = json.loads(triggered_str)["index"]

     for id, style in zip(ids, styles):
          if id["index"] == triggered_id: 
               style["opacity"] = 0.5
               style["opacity"] = 1.0
     return styles

I haven’t tested it, but I suppose it works or at least it gives you a head start. Please refer to the two links I mentioned on the top with more information about the functionality I have used in this callback, they are very handy!

Hope this helps!

EDIT: swapping opacity values for completeness… my bad.

1 Like

Wow I wasn’t expecting an answer so quickly! Thank you so much, it worked brilliantly (the only change necessary was to swap the value of the opacity for the two cases). I had tried with ALL before but I didn’t manage to find a solution. I was probably missing the callback context part. This looks very neat, I will look into it more. Thanks again :smile: !

1 Like