The limits of the colorbar are manually set to the minimum and maximum of the values plotted. This is bound to give an incorrect mapping if one of the heatmaps only “uses” e.g. half of the range of the limits, as the heatmap uses the full color range on whatever is plotted.
How do I make a colorbar that represents all heatmaps?
Precision: limits have to be set for both the heatmaps and the colorbar.
Here is a more detailed example:
using CairoMakie
# define the maps
n_rows = 2
n_cols = 5
maps = [rand(5,5) .* (i_row * 10 + i_col) for i_row in 1:n_rows, i_col in 1:n_cols]
# get global extrema
extremas = map(extrema, maps)
global_min = minimum(t->first(t), extremas)
global_max = maximum(t->last(t), extremas)
# these limits have to be shared by the maps and the colorbar
clims = (global_min, global_max)
let
fig = Figure()
for i_row in 1:n_rows, i_col in 1:n_cols
ax = Axis(fig[i_row, i_col])
heatmap!(ax, maps[i_row, i_col]; colorrange=clims)
end
cb = Colorbar(fig[:, n_cols + 1]; limits=clims)
fig
end
Would it be possible to pass a figure, and have the colorbar fit all plot objects on all axes of the figure? This would alleviate the need to define variables for each plot-objects.
pass a figure, and have the colorbar fit all plot objects on all axes of the figure
That would be nice if all plots are to share the same colorbar indeed.
But maybe it could be possible to do the reverse:
define the cb=Colorbar(...) first
and pass a colorbar=cb argument to all heatmaps that should share this colorbar ?
That would suppress the need to define a reference for each heatmap,
while allowing to have separate groups of heatmaps with their own colorbar if needed.
Slightly related, this issue discusses adding linkcolors!, analogous to linkaxes!, to link color ranges across plots. (Mentioning it here because I imagine something like that would be needed to implement the “shared colorbar for many plots” functionality.)
It turns out that you can pass any of the heatmaps (since they have the same colorranges and colormaps, if you have done anything right) to the Colorbar command. I found this simpler than adding a kwarg to the call to Colorbar with explicit limits.
A pretty cool convenience would be a sync_colorranges! function, which would take a variable number of plotobject’s, check that they have colors (so heatmap and contour, right?), and set the colorrange of them all to the extrema of all z-values in all the plots.
sync_colorranges! could perhaps also take a figure, and automatically call itself on the axes within the figure.
If implemented, sync_colorranges! would make explicit limit-setting (no fun) redundant, and replace it with a simple-to-understand and easy-to-remember function.
Finally (and possibly too far down the rabbit-hole of convenience), one could implement a method Colorbar(fig::Figure, kwargs...) that would call sync_colorranges! on the figure, and then add a colorbar. I am thinking that kwargs could specify vertical (default) or horizontal, and location (:right (default), :left, :above, :below).
"""
sync_colorranges!(plotobjects::MakieCore.ScenePlot...)
Set the colorrange of all `plotobjects` to the same value,
namely the extrema of all z-values of all plotobjects in `plotobjects`.
"""
function sync_colorranges!(plotobjects::MakieCore.ScenePlot...)
for plt in plotobjects
haskey(plt.attributes.attributes, :colorrange) || error("This function syncronizes the color range of the given plotobjects. One of the plotobjects passed has no color range.")
end
possible_extremas = [extrema(to_value(plt[3])) for plt in plotobjects]
global_extremas = extrema(vcat(collect.(possible_extremas)...))
for plt in plotobjects
plt.attributes.colorrange[] = global_extremas
end
return nothing
end