Scaling color gradient with custom function

I have a contourf plot which uses a color gradient but I want the values’ color to have a specific meaning.

Specifically, I am using the :jet color gradient and I want any value in [0,3] to be blue to green, any value in [3,5] to be green to yellow and then any value greater than 5 to map to the orange/red region.

This is a non-linear rescale of the gradient map and i’m not sure how I can implement this. I tried using the scale parameter of cgrad but couldn’t get it working for an easier example where I have a parameter (m would be max of z so i can scale from [0,1] to original scale and back) I can pass in and get a simple scale function.

function g(m)
    return x -> x/2

contourf(x,y,z, color=cgrad(:jet, scale=g(maximum(z))))

All I can get to work is setting scale=exp, log, exp10 etc but can’t get a custom function working. If I could I think it should be easy enough to rescale them as I want in a function. Please let me know if this can be done much easier though.

Any pointers as to how to get this to work or alternative approaches that may work well would be appreciated.

Note: The specific mapping I want to implement is that 0 to 4 interpolates between 0 and 0.6 on jet color scale, 3 to 5 interpolates between 0.6 and 0.7 and the 5 to max interpolates between 0.7 and 1. I have implemented this as:

function imp_scale_closure(m)
    return x -> begin
        imp = x*m
        if imp <= 3
            return imp*0.6/3
        elseif imp <= 5
            return 0.6 + (imp-3)*0.1/2
            return 0.7 + (imp-5)*0.3/(m-5)

I wonder if ColorSchemeTools.jl might be able to help?

Making a color scheme seems promising but I’m not sure how I would base it off the jet color scheme.

Would you like something like the following?

It implements the non-linear mapping and saturates in red all data greater than a fixed value (set to 10 in this case).

If I understand it correct, this is clipping colors above 10? This isn’t quite what I’m looking for unfortunately but thanks!

Ten is an example, it could be any number. The need to fix it to something is to be able to align the value 3 with green and the value 5 with yellow.

I’m not sure if this is perhaps it’s own question but I tried to use non-uniform levels and I ended up with the color bar not lining up with the values on the graph. In the example below, you can see the white contour at value 3 doesn’t line up with the color bar but this isn’t a problem if the levels are uniform. Is this intentional behaviour?

levels = [0:0.25:3..., 5:2:17..., 20:10:40...]

contourf(x, y, z, color=:turbo, levels=levels, clabels=true, linewidth=0)
contour!(x, y, z, levels=[3], color=:white, linewidth=2)

If this wasn’t a problem then I would have a nice solution using non-uniform levels and an ansatz scaling function of exp(4x).

Yes I understand, my use case could have plots with much larger values in some instances and I want the flexibility to have some detail in the high points.

Not a problem, we just need to define a color palette function of the data, that scales properly function of maximum value, and call:
heatmap(M, color=mypalette(M))

I still get the problem that too much of the color gradient is given to high values. The solution below (using an ansatz scale function and non-uniform levels) gives me the color bar I want and the plot looks fine, but adding a contour at 5 suggests the colors don’t match up with the color bar.

contourf(x, y, z, color=cgrad(:turbo, scale=(x -> exp(4x))),
         linewidth=0, levels=[0:0.25:3..., 5:2:17..., 20:10:40...])

contour!(x, y, z, levels=[5], linewidth=1.5, color=:white)