Color Range: pick color in range base on values in a vector

I want to show the wealth of 6 people by showing a brighter yellow color if the person is closer to the wealthiest in the group, and deeper red if the person is closer to the poorest in the group.

I know the following will generate a range of colors between red and yellow, but here the “slicing” is uniform. How can I take the wealth-numbers from my vector and assign the appropriate colors:

wealth = [1, 12, 45, 2, 129, 10]
range(colorant"yellow", stop=colorant"red", length=length(wealth)).

Note: I want to assign the colors to nodes in a GraphRecipe plot, but unless the method is very different, I suppose showing the solution using marker dots in an x-y scatter plot will do.

Is this what you are looking for?

range(colorant"yellow", stop=colorant"red", length=maximum(wealth))[wealth]
1 Like

If you wrap your color range into a ColorScheme object, then the linear interpolation that you need to convert between wealth and color is already implemented:

julia> using Colors, ColorSchemes

julia> cs = ColorScheme([colorant"yellow", colorant"red"]);

julia> wealth = [1, 12, 45, 2, 129, 10];

julia> colors = get.(Ref(cs), (wealth .- minimum(wealth)) ./ maximum(wealth))
6-element Array{RGB{N0f8},1} with eltype RGB{FixedPointNumbers.N0f8}:

Note that linear interpolation will yield a colormap that is not perceptually uniform, so it will tend to distort data (see e.g. this nice video on the design of matplotlib’s color schemes). You can use the PerceptualColormaps.jl package to correct this, for example:

using Colors, ColorSchemes, PerceptualColourMaps

cs = ColorScheme([colorant"yellow", colorant"red"])
origcolors = get(cs, 0:0.01:1) # linear interpolation, not perceptually uniform

# generate corrected colormap:
rgbdata = equalisecolourmap("RGB", RGBA{Float64}.(origcolors), "CIEDE2000", [1,0,0])
newcolors = [RGB(rgb...) for rgb in eachrow(rgb)]

Compare the original linear colormap origcolors (top) with the corrected colormap newcolors (bottom):

You can see that the original linearly interpolated scheme washes out some of the visual contrast, especially on the red (right) side of the scale.

Then you can create a ColorScheme object and do interpolation on it:

newcs = ColorScheme(newcolors)

wealth = [1, 12, 45, 2, 129, 10]
colors = get(newcs, wealth, :extrema)

Note that there is no need to do broadcasting: you can do get(newcs, somearray, :extrema) rather than get.(Ref(newcs), normalized_somearray), because the ColorSchemes.jl package implements a vectorized get method, and by passing :extrema as the third argument it will automatically normalize the data (and you can also pass a different normalization).


However, in general I would recommend not designing your own colormaps, because high-quality colormaps are carefully designed to be perceptually uniform, accessible to people with color-vision deficiencies, etc.

For example you might use one of the “scientific” colormaps provided here in ColorSchemes.jl, or one of Matplotlib’s perceptually uniform sequential colormaps available here in ColorSchemes.jl.

1 Like

By the way, if you run this code in a multimedia-capable environment such as a Jupyter notebook, it displays the colors visually for you: