Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bokeh: How to add a legend and custom color boundaries to an image plot?

Tags:

python

bokeh

I have a two-dimensional array that I want to plot using bokeh's bokeh.plotting.figure.Figure.image. It works wonderful.

Now, I want to add a legend using the colors used for the image. I don't find any example for my case. The legend that I'd like to achieve is similar to the picture.

example plot

from bokeh.models import LinearColorMapper, ColorBar
from bokeh.plotting import figure, show

plot = figure(x_range=(0,1), y_range=(0,1), toolbar_location="right")
color_mapper =  LinearColorMapper(palette="YlGn9", low=-1, high=1, nan_color="white")
plot.image(image=[ndvi], color_mapper=color_mapper,dh=[1.0], dw=[1.0], x=[0], y=[0])

color_bar = ColorBar(color_mapper=color_mapper,label_standoff=12, border_line_color=None, location=(0,0))

plot.add_layout(color_bar, 'right')

Additionally, I'd like to have some custom color boundaries, with non-fixed intervals. Here is an example how it would be done with matplotlib:

cmap = colors.ListedColormap(['#27821f', '#3fa336', '#6ce362','#ffffff','#e063a8' ,'#cc3b8b','#9e008c','#59044f'])
bounds = [-1000, -500, -100, 0, 50, 100, 300, 500, 10000000]
norm = colors.BoundaryNorm(bounds, cmap.N)
fig, ax = plt.subplots()
ax.imshow(data, cmap=cmap, norm=norm)
like image 924
ISTI Vita Avatar asked Nov 26 '25 03:11

ISTI Vita


1 Answers

You can choose the red-yellow-green palette. In bokeh the name is 'RdYlGn5', where the digit at the end tells how many colors are needed. To use it in a legend, you'ld need to import RdYlGn5 from bokeh.palettes.

For creating the legend, I only know of employing some dummy glyphs as in the code below.

I updated my example with the new requirements of setting custom bounds with non-fixed intervals. This post offers some guidance. Basically, the idea is to use a larger colormap with repeated colors. Such a format doesn't fit for general types of boundaries, but it fits yours, at least when the lowest and highest bound are interpreted to be infinite.

I also tried to layout the legend with some custom spaces to get all labels aligned. A background color is chosen to contrast with the legend entries.

There is a colorbar to verify how the colormap bounds work internally. After verification, you may leave it out. The example image has values from -1000 to 1000 to show how the values outside the strict colormap limits are handled.

Here is an example with dummy data:

from bokeh.models import LinearColorMapper, Legend, LegendItem, ColorBar, SingleIntervalTicker
from bokeh.plotting import figure, show
import numpy as np

x, y = np.meshgrid(np.linspace(0, 10, 1000), np.linspace(0, 10, 1000))
z = 1000*np.sin(x + np.cos(y))

plot = figure(x_range=(0, 1), y_range=(0, 1), toolbar_location="right")
base_colors = ['#27821f', '#3fa336', '#6ce362','#ffffff','#e063a8' ,'#cc3b8b','#9e008c','#59044f']
bounds = [-1000, -500, -100, 0, 50, 100, 300, 500, 10000000]
low = -600
high = 600
bound_colors = []
j = 0
for i in range(low, high, 50):
    if i >= bounds[j+1]:
        j += 1
    bound_colors.append(base_colors[j])
color_mapper = LinearColorMapper(palette=bound_colors, low=low, high=high, nan_color="white")

plot.image(image=[z], color_mapper=color_mapper, dh=[1.0], dw=[1.0], x=[0], y=[0])

# these are a dummy glyphs to help draw the legend
dummy_for_legend = [plot.line(x=[1, 1], y=[1, 1], line_width=15, color=c, name='dummy_for_legend')
                    for c in base_colors]
legend_labels = [f'     < {bounds[1]}'] + \
                [('' if l < 0 else '     ' if l < 10 else '   ' if l < 100 else ' ')
                 + f'{l} ‒ {h}' for l, h in zip(bounds[1:], bounds[2:-1])] + \
                [f'     > {bounds[-2]}']

legend1 = Legend(title="NDVI", background_fill_color='gold',
                 items=[LegendItem(label=lab, renderers=[gly]) for lab, gly in zip(legend_labels, dummy_for_legend) ])
plot.add_layout(legend1)

color_bar = ColorBar(color_mapper=color_mapper, label_standoff=12, border_line_color=None, location=(0, 0),
                     ticker=SingleIntervalTicker(interval=50))
plot.add_layout(color_bar)

show(plot)

example plot

like image 151
JohanC Avatar answered Nov 28 '25 17:11

JohanC



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!