@clairew As I understood you need a discrete colorscale derived from a continuous one.
Unlike the matplotlib, a plotly continuous colorscale is defined by a number of colors much smaller than 256 (the usual number of colors in a matplotlib colormap).
To get a binned colorscale from a plotly continuous colorscale we have to find the colors of the new
discrete colorscale from those of the continuous one, through linear interpolation, using the function np.interp
.
We can get the colors of a continuous colorscale by calling:
px.colors.sequential.ColorscaleName
or px.colors.diverging.ColorscaleName
.
As we can notice below these colors are either hex or rgb colors, and their number differ from colorscale to colorscale.
I define below a function that display the swatches corresponding to colors returned by px.colors…
from plotly.colors import unlabel_rgb, hex_to_rgb
import plotly.graph_objects as go
import plotly.express as px
import numpy as np
def colorscale_swatches(seq):
data = [go.Bar(x=[k], y=[1], marker_color=c, hovertext=seq[k]) for k, c in enumerate(seq)]
fig = go.Figure(data=data)
fig.update_layout(bargap=0.02, template="none",
showlegend=False,
xaxis=dict(showticklabels=False, showgrid=False),
yaxis_showticklabels=False,
height=250)
return fig
Let us plot the swatches corresponding to Viridis:
seq1 = px.colors.sequential.Viridis
seq1
['#440154',
'#482878',
'#3e4989',
'#31688e',
'#26828e',
'#1f9e89',
'#35b779',
'#6ece58',
'#b5de2b',
'#fde725']
fig1 = colorscale_swatches(seq1)
Notice that we have 10 swatches, and their color is given in hex.
Now take the colors for the colormap cmocean.matter
seq2 = px.colors.sequential.matter
seq2
['rgb(253, 237, 176)',
'rgb(250, 205, 145)',
'rgb(246, 173, 119)',
'rgb(240, 142, 98)',
'rgb(231, 109, 84)',
'rgb(216, 80, 83)',
'rgb(195, 56, 90)',
'rgb(168, 40, 96)',
'rgb(138, 29, 99)',
'rgb(107, 24, 93)',
'rgb(76, 21, 80)',
'rgb(47, 15, 61)']
In this case there are 12 rgb colors, and their swatches are:
fig2 = colorscale_swatches(seq2)
But in our projects we need a discrete colorscale derived from a continuous one containing different number of colors.
The following function does the job, i.e. from a given sequence of colors derived from a continuous colorscale it returns a discrete colorscale of nr_swatches
colors.
def binned_colorscale(seq, nr_swatches=5):
"""
seq: a sequence of hex colors or rgb colors returned by px.colors.sequential.ColorscaleName
or px.colors.diverging.ColorscaleName
nr_swatches: the number of swatches for the discrete colorscale, derived from ColorscaleName
"""
if seq[0][0] == '#':
arr_colors=np.array([hex_to_rgb(s) for s in seq])/255
elif seq[0][0:3] == 'rgb':
arr_colors = np.array([unlabel_rgb(s) for s in seq])/255
else:
raise ValueError("a plotly colorscale is given either with hex colors or as rgb colors")
n = len(seq)
svals = [k/(n-1) for k in range(n)] #the scale values corresponding to the colors in seq
grid = [k/(nr_swatches-1) for k in range(nr_swatches)]# define the scale values corresponding nr_swatches
r, g, b = [np.interp(grid, svals, arr_colors[:, k]) for k in range(3)] #np.interp interpolates linearly
cmap_arr = np.clip(np.vstack((r, g, b)).T, 0, 1)
new_colors = np.array(cmap_arr*255, dtype=int)
discrete_colorscale = []
N = len(new_colors+1)
for k in range(N):
discrete_colorscale.extend([[k/N, f'rgb{tuple(new_colors[k])}'],
[(k+1)/N, f'rgb{tuple(new_colors[k])}']])
return discrete_colorscale
example:
dcolorsc = binned_colorscale(seq2, nr_swatches=7)
[[0.0, 'rgb(253, 237, 176)'],
[0.14285714285714285, 'rgb(253, 237, 176)'],
[0.14285714285714285, 'rgb(246, 178, 123)'],
[0.2857142857142857, 'rgb(246, 178, 123)'],
[0.2857142857142857, 'rgb(234, 120, 88)'],
[0.42857142857142855, 'rgb(234, 120, 88)'],
[0.42857142857142855, 'rgb(205, 67, 86)'],
[0.5714285714285714, 'rgb(205, 67, 86)'],
[0.5714285714285714, 'rgb(158, 36, 97)'],
[0.7142857142857143, 'rgb(158, 36, 97)'],
[0.7142857142857143, 'rgb(101, 23, 90)'],
[0.8571428571428571, 'rgb(101, 23, 90)'],
[0.8571428571428571, 'rgb(47, 15, 61)'],
[1.0, 'rgb(47, 15, 61)']]
I hope that this is what you required. The colorbar associated to such a colorscale is also binned. Now to use such a discrete colorscale of `nr_swatches’ colors your data values should be binned as follows:
boundary_vals=[minval+k*(maxval-minval)/nr_swatches for k in range(nr_swatches+1]