How to implement Value Suppressing Colormaps in Makie

I would like to implement Value Suppressing Colormap described in this paper https://dl.acm.org/doi/abs/10.1145/3173574.3174216


Which requires:

  1. triangular colorbar
  2. bivariate colormap, where I can manipulate color hue be changing saturation and lightness

At least how I understand it :slight_smile:
Any ideas? Is it even possible in Makie?

2 Likes

Very interesting. Thanks for sharing the triangular map.

We implemented the bivariate map in our GeoStats.viewer with Makie.jl, and it would be awesome to replace it with the triangular one.

If you can work on a package that takes a vector of Distributions.jl objects and uses this triangular map to show location and spread statistics, that would be neat.

1 Like

To be more precise, we managed to abstract this concept in the Colorfy.jl package, which takes a vector of objects and returns a vector of colors for plotting. The exact code that handles Distributions.jl objects is stored in an extension:

1 Like

@juliohm great tool! Thank you!
But is it possible to send there a vector of different transparency values for each heatmap value to represent uncertainty variations?

You can pass a vector of alphas to the colorfy function and it is demonstrated in the README. If you can submit a PR to improve our Distributions.jl extension with the triangular map above, we can happily review and merge. That way users could simply colorfy(distributions) to get the final colors for plotting with Makie.jl

I shared this recipe some time ago, maybe this capability should be added to Makie.jl directly?..
Basically, image() but alpha can be an array of values:

@recipe ImageAlpha (
		x::Makie.EndPoints,
		y::Makie.EndPoints,
		image::AbstractMatrix{<:Number}) begin
	Makie.MakieCore.documented_attributes(Makie.Image)...
	alpha = nothing
	alpharange = 0..1
end

function Makie.plot!(p::ImageAlpha)
	alpha = p.alpha[]
	p.alpha = 1
	Makie.color_and_colormap!(p, p.image)
	imcolors = @lift Makie.to_color($(p.calculated_colors))
	alphas_interval = @lift @something(extrema($(p.alpharange)), extrema(alpha))
	alpha = @lift clamp.((alpha .- $alphas_interval[1]) ./ ($alphas_interval[2] - $alphas_interval[1]), 0, 1)
	imcolors_a = @lift Makie.coloralpha.($imcolors, $alpha)
	image!(p, Makie.shared_attributes(p, Image), imcolors_a)
	return p
end

Makie.convert_arguments(::Type{<:ImageAlpha}, args...) = Makie.convert_arguments(ImageLike(), args...)

Colorfy.jl is a lightweight dependency that handles missing values, unitful values, etc. through package extensions. We created it to facilitate the definition of colorful representations for any Julia type. The API is quite stable at this point. Makie.jl could leverage this API to display Julia objects as colors in various plots.

There are a lot of reasonable useful ways to turn a Julia object (even just a Normal distribution) to a color + alpha, meaning that there isn’t any “fundamentally correct” way to do that.
That’s why it would make sense to have a simpler straightforward API in a plotting package itself. Plotting “image with alpha” is well-defined, there’s just one way to do that.

Do we need these other useful ways in practice though? Or just “the most useful” way that is the result of research like the one linked by the OP?

Your request to add support in Makie.jl for “image + alpha” is an (important) orthogonal request. I believe the OP and I are seeking the most useful colorful representation for a vector of objects, which happens to be a vector of Distributions.jl (or uncertainties).

Thank you very much! I tried this solution with

using TopoPlots
TopoPlots.eeg_topoplot(
    rand(64);
    positions = positions,
    label_text = true,
    plotfnc! = (plot, x, y, image; kwargs...) -> begin
        imgval = replace(image[], NaN => 0.0)  # unwrap the Observable
        imagealpha!(
            plot,
            Makie.EndPoints(first(x[]), last(x[])),
            Makie.EndPoints(first(y[]), last(y[])),
            imgval;
            alpha = imgval,
           # alpharange = 0.0 .. maximum(imgval),
            colormap = :viridis,
            interpolate = false,
        )
    end,
)

It kinda works, but as you my notice, the axis here is just a tiny spot on the left bottom angle. There is a mismatch of scales. Instead of 600x600 it should be just 1x1. How can I control the scale of the interpolated image? Do you have any ideas?

upd
even with simple example of

imagealpha(
    Makie.EndPoints((0.0, 1.0)),
    Makie.EndPoints((0.0, 1.0)),
    rand(60, 60);
    alpha =rand(60, 60,
    colormap = :viridis,
    interpolate = false,
)

.. limits are equal to the size of data and couldn’t be changed. How can I change them?

Yes I guess it should explicitly pass all arguments to image(), which it doesn’t do…
Feel free to suggest a fix :slight_smile: Alternatively, just use an array type that carries both data and arbitrary axis ranges in one object:

using AxisKeysExtra

A = KeyedArray(rand(60, 60), x=range(0..1, length=60), y=range(0..1, length=60))
imagealpha(
	A;
	alpha=A .^ 1.5,
    colormap = :viridis,
    interpolate = false,
)


I personally didn’t even notice this limitation of imagealpha, simply because I tend to use KeyedArrays whenever makes sense.

1 Like