Using html.Img as filter in callback

Hi community!

I’ve been scratching my head on this for ages - I am looking to allow users to click a team logo to filter a table that shows players in that team.

This is my app.layout:

def update_layout():
    data = getShots(None)

    return html.Div(children=[
        html.Div(
            html.Div(
                [html.A(
                    html.Img(src=teams.loc[teams['TeamID'] == i, 'TeamLogo'].iloc[0], style={'height': '92px'}, className='team-overlay', id='team-logo-'+str(i)))
                    for i in teams['TeamID'].values if i is not None],
            ), className='team-container'),

        html.Div(id="placeholder"),

        html.Div(
            get_data_object(teamdf), id='tableContainer')

    ])

app.layout = update_layout()

And this is my callback:

@app.callback(
    Output('tableContainer', 'children'),
    [
       # there are 30 team images 
        Input('team-logo-1610612737', 'n_clicks'), Input('team-logo-1610612737', 'src'),
        Input('team-logo-1610612738', 'n_clicks'), Input('team-logo-1610612738', 'src'),
        ...
        ...
       ...
        # Input('team-logo-1610612766', 'n_clicks'), Input('team-logo-1610612766', 'src'),
    ])

def update_graph(n_clicks1, src1, n_clicks2, src2...  n_clicksN, scrN):
    if n_clicks1:
        teamId = teams.loc[teams['TeamLogo'] == src1, 'TeamID'].iloc[0]

    if n_clicks2:
        teamId = teams.loc[teams['TeamLogo'] == src2, 'TeamID'].iloc[0]

    else:
        teamId = None

    teamdf = parseTeams(rosters, teamId)

    return get_data_object(teamdf)

This is sort of something but I don’t think the solution is 30 separate n_clicks and src Inputs in the callback function.

Is there away for me to use the Outer Div element and then use the html.Img that I click and then pass the src to get the TeamId.

Any help would be appreciated!
Pratik

This is currently (to the best of my knowledge) one of those Dash limitations that means what you are trying to achieve will be a bit cumbersome. There are some features in the pipeline that would make this sort of thing easy: https://github.com/plotly/dash/issues/475

One workaround that I have used in the past is to use the dcc.Location and dcc.Link component’s pathname or search parameter as input to the callback instead. This can be used to set the teamId.

See https://dash.plot.ly/urls for working examples

For illustrative purposes only:

def update_layout():
    data = getShots(None)

    return html.Div(children=[
        html.Div(
            html.Div(
                # use Dash's Link component
                [dcc.Link(
                    html.Img(src=teams.loc[teams['TeamID'] == i, 'TeamLogo'].iloc[0], style={'height': '92px'},
                    className='team-overlay', id='team-logo-'+str(i)), href='/' + str(i)) #set link's href to include teamid
                    for i in teams['TeamID'].values if i is not None],
            ), className='team-container'),

        html.Div(id="placeholder"),

        html.Div(
            get_data_object(teamdf), id='tableContainer'),

       dcc.Location(id='url') # include location component

    ])

app.layout = update_layout()

#use pathname as input to filter table
@app.callback(
    Output('tableContainer', 'children'),
    [
        Input('url', 'pathname')
    ])

def update_graph(pathname):

    teamId = int(pathname.split('/')[-1])
    teamdf = parseTeams(rosters, teamId)

    return get_data_object(teamdf)

Thanks for getting back to me @mikesmith1611 - that’s an interesting way to go. So i played around with your suggestion;

My question is, how does dcc.Location then tie into this?

I looked to use this page (https://dash.plot.ly/urls) as you suggested and pass url into the @callback.

def update_layout():
    data = getShots(None)

    return html.Div(children=[
        html.Div(
            # dcc.Location(id='url', refresh=False),
            [dcc.Link(
                    html.Img(src=teams.loc[teams['TeamID'] == i, 'TeamLogo'].iloc[0], style={'height': '92px'},
                             className='team-overlay', id='team-logo-'+str(i)), href='/' + str(i))
             for i in teams['TeamID'].values if i is not None]),

This is the @callback;

@app.callback(
    Output('tableContainer', 'children'),
    [Input('url', 'pathname')]
)
def update_graph(pathname):
    teamId = int(pathname.split('/')[-1])
    teamdf = parseTeams(rosters, teamId)

    return get_data_object(teamdf)

Do you have any other thoughts?

Without your full code, it is hard to see what you’re actually doing here but going by the snippet you sent:

  • You do not have anything in your layout with id=‘tableContainer’ but are trying to output its children

  • You have commented out the dcc.Location object which has to be in your layout to work as an input to your callback

  • You should end up with a bunch of images which you can click on to update the pathname in the url. This will then trigger the callback

That’s my mistake for not adding all detail, to answer your quesries;

-The full return statement looks like this:

def update_layout():
    data = getShots(None)

    return html.Div(children=[
        html.Div(
            dcc.Location(id='url', refresh=False),
            [dcc.Link(
                    html.Img(src=teams.loc[teams['TeamID'] == i, 'TeamLogo'].iloc[0], style={'height': '92px'},
                             className='team-overlay', id='team-logo-'+str(i)), href='/' + str(i))
             for i in teams['TeamID'].values if i is not None]),

        html.Div(
            get_data_object(teamdf), id='tableContainer')
    ])

What’s interesting is that even without the dcc.Location uncommented when I click between images I see the URL change

When dcc.Location is included, i get this error and the layout doesn’t load:

TypeError: unhashable type: 'list'

Scrap that, i used dcc.Location outside the div element with the image and that’s done it (and it’s pretty quick!)

def update_layout():
    data = getShots(None)

    return html.Div(children=[
        dcc.Location(id='url', refresh=False),
        html.Div(
            [dcc.Link(
                html.Img(src=teams.loc[teams['TeamID'] == i, 'TeamLogo'].iloc[0], style={'height': '92px'},
                         className='team-overlay', id='team-logo-'+str(i)), href='/' + str(i))
             for i in teams['TeamID'].values if i is not None]),

        html.Div(
            get_data_object(teamdf), id='tableContainer')
     ])

Thanks for your help on this @mikesmith1611

All the work is here if you were curious (https://github.com/pratikthanki/statflows-nba) - cheers!

1 Like