I'm plotting a quite large heatmap using plotly. I would like to have access to the color of each pixel in the hoverlabel to color a symbol accordingly. Here is what I managed to do so far:
import numpy as np
import plotly.graph_objects as go
import plotly.colors as pc
# generate image
size = 5
img = np.random.random((size, size+2))
# manually generate colors
min_value, max_value = img.min(), img.max()
cmap = pc.sequential.Jet
colors = pc.sample_colorscale(cmap, (img.ravel() - min_value) / (max_value - min_value), colortype="tuple")
# convert to HTML hex color codes and reshape to image shape
colors = np.reshape([f'#{int(c[0]*255):02x}{int(c[1]*255):02x}{int(c[2]*255):02x}' for c in colors], img.shape)
# plot heatmap
fig = go.Figure(
go.Heatmap(
z=img,
colorscale=cmap,
customdata=colors,
hovertemplate='Value %{z} with color <span style="color:%{customdata};">⬤</span>',
name=''
)
)
fig.show('browser')
However, this method uses customdata
to pass the array of colors, which is not very convenient as it doubles the final size of the resulting HTML (that can be over 100 or 200 MB).
We know that at some point, the colors are computed by plotly. I would like to know if there is a way to access this in the hoverlabel without having to manually pass them as customdata
as this is redundant (something like %{color}
for example but this does not exist).
It's pretty difficult to see with the darker colors. However, if you only wanted to change the ⬤, this will work.
Instead of relayout
, this is a restyle
. (It changes a trace, not the layout.)
fig = go.Figure(go.Heatmap(z = img, hovertemplate = 'Value %{z} with color <span style="color:red;">⬤</span>'))
pio.write_html(fig, 'index.html', auto_open = True, include_mathjax = 'cdn', include_plotlyjs = 'cdn',
full_html = True, div_id = 'myDiv', # this id is used in the JS
post_script = "setTimeout(function() {" +
"el = document.getElementById('myDiv');" +
"el.on('plotly_hover', function(d) {" +
"cols = el.calcdata[0][0].trace.colorscale; /* get colors by value */" +
"vals = Object.values(cols);" +
"z = d.points[0].z; /* collect current hover point data */" +
"i = []; /* reset i in each event */" +
"vals.forEach((n, index) => {" +
"if(n[0] > z){" +
"i.push(index); /* find the right color */" +
"} /* end if */" +
"}); /* end forEach */" +
"/* 2 values for each key, the lower range of z and color */" +
"newC = Object.values(cols)[i[0]][1]; /* extract hex value for color */" +
"labf = d.points[0].fullData.hovertemplate; /* get object to update */" +
"old = labf.match(/(?<=(color:)(.*)(?=;))/)[2]; /* capture color to replace next */"
"labf = labf.replace(old, newC);" +
"Plotly.restyle(el, {'hovertemplate': labf}, d.curveNumber); /* make the change */" +
"});" +
"});")
The JS without the quotes & '+':
setTimeout(function() {
el = document.getElementById('myDiv');
cols = el.calcdata[0][0].trace.colorscale; /* get colors by value */
vals = Object.values(cols);
el.on('plotly_hover', function(d) {
z = d.points[0].z; /* collect current hover point data */
i = []; /* reset i in each event */
vals.forEach((n, index) => {
if(n[0] > z){
i.push(index); /* find the right color */
} /* end if */
}) /* end forEach */
/* 2 values for each key, the lower range of z and color */
newC = Object.values(cols)[i[0]][1]; /* extract hex value for color */
labf = d.points[0].fullData.hovertemplate; /* get object to update */
old = labf.match(/(?<=(color:)(.*)(?=;))/)[2]; /* capture color to replace next */
labf = labf.replace(old, newC);
Plotly.restyle(el, {'hovertemplate': labf}, d.curveNumber) /* make the change */
});
});
###---- original answer -----
I think this is what you're looking for.
You mentioned this as an eternal HTML file, and in order to do this, you'll need to at least create the external file.
I used plotly.io
's write_html
so that I could append Javascript with the argument post_script
. The file size is 10kb. I don't know what you're using in your file (i.e., data, options, all that), but you may want to look at the arguments I've used in pio.write_html()
.
You also mentioned that you did not want to designate colors. I did not designate any specific colors when creating the plot.
I used np.random.seed(0)
so that you could reproduce my example, as well.
I used comments in my Javascript so that if you wanted to understand what was happening, you could. If you have any questions, let me know.
import plotly.graph_objects as go
import plotly.io as pio
import numpy as np
# generate image
size = 5
np.random.seed(0) # for consistency
img = np.random.random((size, size+2))
# manually generate colors
min_value, max_value = img.min(), img.max()
fig = go.Figure(go.Heatmap(z = img, hovertemplate = 'Value %{z} with color ⬤'))
# fig.show()
pio.write_html(fig, 'index.html', auto_open = True, include_mathjax = 'cdn', include_plotlyjs = 'cdn',
full_html = True, div_id = 'myDiv', # this id is used in the JS
post_script = "setTimeout(function() {" +
"el = document.getElementById('myDiv');" +
"el.on('plotly_hover', function(d) {" +
"cols = el.calcdata[0][0].trace.colorscale; /* get colors by value */" +
"vals = Object.values(cols);" +
"z = d.points[0].z; /* collect current hover point data */" +
"i = []; /* reset i in each event */" +
"vals.forEach((n, index) => {" +
"if(n[0] > z){" +
"i.push(index); /* find the right color */" +
"} /* end if */" +
"}); /* end forEach */" +
"/* 2 values for each key, the lower range of z and color */" +
"newC = Object.values(cols)[i[0]][1]; /* extract hex value for color */" +
"labf = d.points[0].fullData.hoverlabel; /* get object to update */" +
"labf.font.color = newC; /* set font color */" +
"if(z < .5) { /* find contrasting background color */" +
"bgc = Object.values(cols)[7][1] /* orangish yellow background for dark colors */" +
"} else {" +
"bgc = Object.values(cols)[1][1] /* dark blueish-purple background for light colors */" +
"}" +
"labf.bgcolor = bgc; /* set bgcolor based on conditions */" +
"Plotly.relayout(el, {'hoverlabel': labf}, d.curveNumber) /* make the change */" +
"});" +
"});")
The JS without the quotes & '+':
setTimeout(function() {
el = document.getElementById('myDiv');
cols = el.calcdata[0][0].trace.colorscale; /* get colors by value */
vals = Object.values(cols);
el.on('plotly_hover', function(d) {
z = d.points[0].z; /* collect current hover point data */
i = []; /* reset i in each event */
vals.forEach((n, index) => {
if(n[0] > z){
i.push(index); /* find the right color */
} /* end if */
}); /* end forEach */
/* 2 values for each key, the lower range of z and color */
newC = Object.values(cols)[i[0]][1]; /* extract hex value for color */
labf = d.points[0].fullData.hoverlabel; /* get object to update */
labf.font.color = newC; /* set font color */
if(z < .5) { /* find contrasting background color */
bgc = Object.values(cols)[7][1]; /* orangish yellow background for dark colors */
} else {
bgc = Object.values(cols)[1][1]; /* dark blueish-purple background for light colors */
}
labf.bgcolor = bgc; /* set bgcolor based on conditions */
Plotly.relayout(el, {'hoverlabel': labf}, d.curveNumber) /* make the change */
});
});