Interactive Image Callbacks

There have been a few questions about tying callbacks to static images, see https://github.com/plotly/dash/issues/178 and https://stackoverflow.com/questions/48586208/ for example. I thought I’d open up an official Dash community discussion on this topic.

Here is one approach: use the background image support in the dcc.Graph component and that component’s select tools. This example could be generalized a bit more to dynamically compute the dimensions of the image (right now I’m using square images).

If any body has a more concrete use case with real data, please share!

import dash
from dash.dependencies import Input, Output
import dash_core_components as dcc
import dash_html_components as html
import base64
import json

app = dash.Dash()
app.css.append_css({'external_url': 'https://codepen.io/chriddyp/pen/dZVMbK.css'})

RANGE = [0, 1]

def InteractiveImage(id, image_path):
    encoded_image = base64.b64encode(open(image_path, 'rb').read())
    return dcc.Graph(
        id=id,
        figure={
            'data': [],
            'layout': {
                'xaxis': {
                    'range': RANGE
                },
                'yaxis': {
                    'range': RANGE,
                    'scaleanchor': 'x',
                    'scaleratio': 1
                },
                'height': 600,
                'images': [{
                    'xref': 'x',
                    'yref': 'y',
                    'x': RANGE[0],
                    'y': RANGE[1],
                    'sizex': RANGE[1] - RANGE[0],
                    'sizey': RANGE[1] - RANGE[0],
                    'sizing': 'stretch',
                    'layer': 'below',
                    'source': 'data:image/png;base64,{}'.format(encoded_image)
                }],
                'dragmode': 'select'  # or 'lasso'
            }
        }
    )


app.layout = html.Div([
    html.Div(className='row', children=[
        html.Div(InteractiveImage('image', 'dash_app.png'), className='six columns'),
        html.Div(dcc.Graph(id='graph'), className='six columns')
    ]),
    html.Pre(id='console')
])


# display the event data for debugging
@app.callback(Output('console', 'children'), [Input('image', 'selectedData')])
def display_selected_data(selectedData):
    return json.dumps(selectedData, indent=2)


@app.callback(Output('graph', 'figure'), [Input('image', 'selectedData')])
def update_histogram(selectedData):
    x_range = selectedData['range']['x']
    x_range = selectedData['range']['y']
    # filter data based off of selection in here

    # for simple example purposes, we'll just display the selected RANGE
    return {
        'data': [{
            'x': x_range,
            'y': x_range,
            'mode': 'markers',
            'marker': {
                'size': 20
            }
        }],
        'layout': {
            'xaxis': {'range': RANGE},
            'yaxis': {'range': RANGE, 'scaleanchor': 'x', 'scaleratio': 1},
            'height': 600
        }
    }


if __name__ == '__main__':
    app.run_server(debug=True)
2 Likes

From the dcc.Graph/figure/‘images’ property
For this to work for me (Windows 10) I had to add .decode() after encoded_image
Should note that I replaced dash_app.png with a .jpg image.

2 Likes

I think there are a couple bugs in update_histogram:

x_range = selectedData[‘range’][‘y’] # should be:
y_range = selectedData[‘range’][‘y’]

and

‘y’: x_range, # should be:
‘y’: y_range,

Also, first time entering this function, selectedData is None, and this line will throw exception:
x_range = selectedData[‘range’][‘x’]

After fixing these issues, I can indeed select, and the histogram updates.

However, the background image does not show up. I verified that the image is correctly read into the variable encoded_image. Any idea why this could be?

It works correctly when I replace the source with this:
‘source’: “https://images.plot.ly/language-icons/api-home/python-logo.png

1 Like