As pointed out by the OP, since opacity
is a style attribute applied by the client regardless of the trace (data) which is associated to a given image, there is no need to precompute one trace for each image variation, nor to redraw anything when the slider moves.
Using simple slider controls, we should be able to apply such (minor) changes in real-time.
Option 1 :
The first option is to let Plotly.js handle the changes by specifying the proper method
and args
in the slider' steps
configuration (ie. using the restyle method, Plotly is smart enough to not redraw the whole plot) :
from PIL import Image
import plotly.graph_objects as go
import numpy as np
import scipy.misc
imgA = scipy.misc.face()
imgB = Image.fromarray(np.random.random(imgA.shape[:2])*255).convert('RGB')
fig = go.Figure([
go.Image(name='raccoon', z=imgA, opacity=1), # trace 0
go.Image(name='noise', z=imgB, opacity=0.5) # trace 1
])
slider = {
'active': 50,
'currentvalue': {'prefix': 'Noise: '},
'steps': [{
'value': step/100,
'label': f'{step}%',
'visible': True,
'execute': True,
'method': 'restyle',
'args': [{'opacity': step/100}, [1]] # apply to trace [1] only
} for step in range(101)]
}
fig.update_layout(sliders=[slider])
fig.show(renderer='browser')
Option 2 :
The second option demonstrates a more general case where one wants to bypass Plotly API on slider events and trigger his own code instead. For example, what if the restyle method were not efficient enough for this task ?
In this situation, one can hook into the plotly_sliderchange event and apply the changes manually in order to obtain smooth transitions.
The browser (default) renderer makes it possible by using the post_script
parameter, which you can pass to the show()
method. It allows to add javascript snippets which are executed just after plot creation. Once one knows that (documentation should emphasize this part!), it is just a matter of binding the appropriate handler. For example (JS syntax highlighting) :
// {plot_id} is a placeholder for the graphDiv id.
const gd = document.getElementById('{plot_id}');
// Retrieve the image (easier with d3, cf. 1st revision, see comments)
const trace = gd.calcdata.find(data => data[0].trace.name === 'noise')[0];
const group = Object.keys(trace).find(prop => (/node[0-4]/).test(prop));
const img = trace[group][0][0].firstChild;
// Listen for slider events and apply changes on the fly
gd.on('plotly_sliderchange', event => img.style.opacity = event.step.value);
Now for the slider configuration, the important thing is to set execute=False
and method='skip'
to bypass API calls and prevent redrawing the plot when the slider changes :
slider = {
'active': 50,
'currentvalue': {'prefix': 'Noise: '},
'steps': [{
'value': step/100,
'label': f'{step}%',
'visible': True,
'execute': False,
'method': 'skip',
} for step in range(101)]
}
js = '''
const gd = document.getElementById('{plot_id}');
const trace = gd.calcdata.find(data => data[0].trace.name === 'noise')[0];
const group = Object.keys(trace).find(prop => (/node[0-4]/).test(prop));
const img = trace[group][0][0].firstChild;
gd.on('plotly_sliderchange', event => img.style.opacity = event.step.value);
'''
fig.update_layout(sliders=[slider])
fig.show(renderer='browser', post_script=[js])