Reputation: 51
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.
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)
Upvotes: 3
Views: 2839
Reputation: 80339
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)
Upvotes: 2