The second trace goes into the data
array as well. The thing to note is that indexing matters : the trace at index 1 is drawn above the trace at index 0, and so on.
For the slider configuration, it should be the same as in python : each step change triggers the same 'restyle' method with the same arguments, ie. Plotly.restyle(graphDiv, ...args)
, that is, with args
such that the method call matches the signature :
Plotly.restyle(graphDiv, update [, traceIndices])
Now, the most important thing is which trace (traceIndices) the slider should target, that is, which index or which name for explicitly named traces (default is all if I'm not wrong), but again here it doesn't change between Python and Javascript.
Here is a full example (play around with it on codepen.io) :
// Random z data
const w = {length: 600};
const h = {length: 400};
const z0 = Array.from(h, () => Array.from(w, () => Math.floor(Math.random() * 100)));
const z1 = Array.from(h, () => Array.from(w, () => Math.floor(Math.random() * 100)));
// Initial opacity for the trace 'above'
const op_init = 0.5;
const data = [
// Nb. Trace 1 drawn on top of trace 0
{type: 'heatmap', z: z0, colorscale: 'Greys'}, // trace 0
{type: 'heatmap', z: z1, colorscale: 'Cividis', opacity: op_init} // trace 1
];
// Steps for the opacity slider
const steps = [];
const n_steps = 100; // number of steps above step 0
for (let i = 0; i <= n_steps; i++) {
steps.push({
label: i + '%',
execute: true,
method: 'restyle',
args: [{
opacity: i/n_steps
}, [1]] // <- Nb. this applies only to trace 1
});
}
const layout = {
width: 600,
sliders: [{
steps: steps,
active: Math.round(op_init * n_steps), // slider default matches op_init
pad: {t: 30},
currentvalue: {prefix: 'opacity: '}
}]
};
Plotly.newPlot('plot', data, layout);
Image vs Heatmap
A Heatmap works only with single channel data (individual value-to-color mappings according to a given colorscale).
When working with rgb (or rgba, rgba256, hsl, hsla), one has to use the image
type. The difference is that z
must be a 2-dimensional array in which each element is an array of 3 or 4 numbers representing a color (the colormodel
should be set accordingly).
For example, setting an rgb image made of noise as the background layer :
const z0 = Array.from(h, () => Array.from(w, () => ['r', 'g', 'b'].map(() => Math.floor(Math.random() * 255)) ));
// ...
const data = [
{type: 'image', z: z0, colormodel: 'rgb'}, // trace 0
{type: 'heatmap', z: z1, colorscale: 'Cividis', opacity: op_init} // trace 1
];
Here a second example where we have an rgb[a] image (DOM object img
) and its pixel data represented as a 1-dimensional Uint8Array (uint8Arr
), which need to be converted in 2d :
const z0 = [];
const nChannels = uint8Arr.length / img.width / img.height;
const chunkSize = uint8Arr.length / img.height;
const z0_model = nChannels === 4 ? 'rgba' : 'rgb';
for (let i = 0; i < uint8Arr.length; i += chunkSize) {
const chunk = uint8Arr.slice(i, i + chunkSize);
const row = [];
for (let j = 0; j < chunk.length; j += nChannels)
row.push(chunk.slice(j, j + nChannels));
z0.push(row);
}
// ...
const data = [
{type: 'image', z: z0, colormodel: z0_model}, // trace 0
{type: 'heatmap', z: z1, colorscale: 'Cividis', opacity: op_init} // trace 1
];
Nb. When you plot an image, the yaxis is automatically reversed (unless specified otherwise, which would display the image upside down). This affects the orientation of the heatmap y-labels, as they're on the same plot, but only the labels not the data.
Here is the layout settings ensuring that both traces share the same aspect ratio and that the image is oriented correctly :
const layout = {
// ...
xaxis: {anchor: 'y', scaleanchor: 'y', constrain: 'domain'},
yaxis: {anchor: 'x', autorange: 'reversed', constrain: 'domain'},
};