Makie.jl: colormap without coarse graining

Motivation I have a function f on a 2d domain, which approximatively has a minimal negative value -1 and a maximal positive value of \approx 1/4 (not really important), which I want to visualise in a way, such that f \approx 0 is drawn as white (the function decays exponentially fast, so the background should essentially be white. As an example consider

xs = -3:0.03:3
ys = xs

f(r) = (sqrt(2)*r^2 - 1) * exp(-r^2)
data = [f(√(x^2 + y^2)) for x ∈ xs, y ∈ ys]

The Problem is, that Makie.jl seems to coarse-grain any colormap, probably doing a cubic interpolation or something. Now, since white is a corner point in RGB space, this makes it impossible to to get true white as an intermediate point.

Consider following example:

function make_test_fig(cmaps)
  fig = Figure(resolution = (300 * length(cmaps), 300))

  for i ∈ 1:length(cmaps)
    heatmap(fig[1, i], xs, ys, data, colorrange = (-1.0, 0.25), colormap = cmaps[i])
    Colorbar(fig[1, 2i],
      limits = (-1.0, 0.25), ticks = -1.0:0.25:0.25, colormap = cmaps[i]

With this I get these result

  1. Left most image is created with
  [range(colorant"orange", colorant"white", length = 80)...,
  range(colorant"white", colorant"blue", length = 20)...]

The idea simply was that the interval [-1,0] takes 80% of the total weight of the interval [-1,1/4]. But clearly f \approx 0 has a blue tint.

  1. The second idea was then to shift blue a little bit upward
  [range(colorant"orange", colorant"white", length = 82)...,
  range(colorant"white", colorant"blue", length = 18)...]

Still, this doesn’t seem to work perfectly, is hacky, doesn’t seem to generalise, and most of all: It requires some magic numbers, which are basically guesses

  1. Okay, if we cannot bring the prophet to the mountain, we bring the mountain to the prophet. I.e., let’s add manually a large white section in our color bar.
  [range(colorant"orange", colorant"white", length = 78)...,
  fill(colorant"white", 4)...,
  range(colorant"white", colorant"blue", length = 18)...]

Instead of fill(colorant"white", 4) I used fill(colorant"red", 4) above to demonstrate whats happening next.

  1. So solution 3 seems to technically work, but the white area is quite large (speaking of relative weight in the [-1,1/4] domain) and this is a little bit of a dishonest representation. So let’s at least reduce the weight:
  [range(colorant"orange", colorant"white", length = 79)...,
  fill(colorant"white", 2)...,
  range(colorant"white", colorant"blue", length = 19)...]

Again, I used red above, instead of white above for demonstration purposes. Aaaaand we end up with the last figure… So red lies a little bit below the wanted value f \approx 0, and it is clearly washed out. This then motivated my hypothesis: Makie.jl does some cubic interpolation for the colours.

So how can I fix the color value of my colormap at specific values and have nice interpolation between these values. Sadly, Makie’s documentation is no help at all :smiling_face_with_tear:

In the example I used the packages CairoMakie, Colors, and ColorSchemes

Addendum I tried fill(colorant"red", 1) to fix to find out how fix the index for the f \approx 0 and I got a figure without any red in it :sweat_smile:. So apparantly, Makie probably does not even sample the entire provided colormap but instead just picks some values? Why is this no where documented? :sob:

I think makie samples with linear interpolation from a vector of colors with equal distance between the endpoints of the color range. So if your colormap doesn’t have enough entries and the entry values don’t correspond to specific values you want to see you will get interpolation. You could try increasing the number of colors in the colormap so much that you don’t see interpolation effects anymore.

See the addumdum: Your suggestions was one of the things I tried, but sometimes Makie seems just to drop some values and that seems to become worse when you actually increase the number of colors. :smiling_face_with_tear: Can provide an example if needed

Ok, that sounds like a bug…Out of curiosity, have you tried GLMakie as well?
Just to pin down if this is a backend bug :wink:

@sdanisch GLMakie seems to behave similarily.

This figure is with regard to @jules reply. I generated the left most figure with

[range(colorant"orange", colorant"white", length = 19)...,
fill(colorant"red", 2)...,
range(colorant"white", colorant"blue", length = 4)...]

and than subsequently just doubled all numerical values. In the color bars specifically you can see that the red center gets: shifted, narrowed and also “interpolated” or what ever is going on. I produced this figure with GLMakie and CairoMakie