Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

improve colorbar errors for plots that don't use a colormap #3090

Merged
merged 30 commits into from
Sep 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
d66b8b0
improve colorbar errors for plots that don't use a colormap
SimonDanisch Jul 24, 2023
fc513b4
don't mess with existing is_atomic_plot
SimonDanisch Jul 24, 2023
e3f3ca5
improve errors
SimonDanisch Jul 25, 2023
1432fa9
add tests
SimonDanisch Jul 25, 2023
f778e42
fix error
SimonDanisch Jul 26, 2023
631669c
Merge branch 'master' into sd/improve-colorbar-error
SimonDanisch Jul 28, 2023
b3065cc
try a more generic approach
SimonDanisch Jul 28, 2023
31d0191
Merge branch 'master' into sd/improve-colorbar-error
SimonDanisch Aug 21, 2023
9e31dbf
Merge branch 'sd/improve-colorbar-error' of https://github.com/MakieO…
SimonDanisch Aug 23, 2023
e8bfb6b
Merge branch 'master' into sd/improve-colorbar-error
SimonDanisch Aug 23, 2023
0105441
recursively search for colormap
SimonDanisch Aug 23, 2023
b31aa88
implement categorical=true correctly
SimonDanisch Aug 25, 2023
ef7b503
correctly get cgrad values
SimonDanisch Aug 25, 2023
af4857d
always use ColorMap
SimonDanisch Sep 6, 2023
765d058
fix most cases?!
SimonDanisch Sep 7, 2023
5f13c6a
fiiiixies!?
SimonDanisch Sep 8, 2023
28a2c2c
merge master
SimonDanisch Sep 8, 2023
e76c9d1
improve ReversibleScale printing
SimonDanisch Sep 8, 2023
de507e2
one last bug?
SimonDanisch Sep 8, 2023
efc72cd
fix cairomakie + Makie unit tests
SimonDanisch Sep 8, 2023
b9d61fc
fix contourf
SimonDanisch Sep 8, 2023
9106ebb
allow type change for ticks
SimonDanisch Sep 8, 2023
aba34cb
clean up implementation
SimonDanisch Sep 12, 2023
ff7acf5
add reference test
SimonDanisch Sep 12, 2023
79f2b71
Merge branch 'master' into sd/improve-colorbar-error
SimonDanisch Sep 12, 2023
f67539e
add test
SimonDanisch Sep 12, 2023
a9aabbd
Merge branch 'sd/improve-colorbar-error' of https://github.com/MakieO…
SimonDanisch Sep 12, 2023
6ee1a44
update comment
SimonDanisch Sep 12, 2023
de83ce2
fix broadcast
SimonDanisch Sep 12, 2023
eb1e1a9
fix continous with values
SimonDanisch Sep 12, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions GLMakie/src/drawing_primitives.jl
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,9 @@ end

function handle_intensities!(attributes, plot)
color = plot.calculated_colors
if color[] isa Makie.ColorMap
if color[] isa Makie.ColorMapping
attributes[:intensity] = color[].color_scaled
interp = color[].categorical[] ? :nearest : :linear
interp = color[].color_mapping_type[] === Makie.continuous ? :linear : :nearest
attributes[:color_map] = Texture(color[].colormap; minfilter=interp)
attributes[:color_norm] = color[].colorrange_scaled
attributes[:nan_color] = color[].nan_color
Expand Down Expand Up @@ -417,7 +417,7 @@ function draw_atomic(screen::Screen, scene::Scene, heatmap::Heatmap)
t = Makie.transform_func_obs(heatmap)
mat = heatmap[3]
space = heatmap.space # needs to happen before connect_camera! call
xypos = map(t, heatmap[1], heatmap[2], space) do t, x, y, space
xypos = lift(t, heatmap[1], heatmap[2], space) do t, x, y, space
x1d = xy_convert(x, size(mat[], 1))
y1d = xy_convert(y, size(mat[], 2))
# Only if transform doesn't do anything, we can stay linear in 1/2D
Expand Down
1 change: 1 addition & 0 deletions MakieCore/src/attributes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Attributes(pairs::AbstractVector) = Attributes(Dict{Symbol, Observable}(node_pai
Attributes(pairs::Iterators.Pairs) = Attributes(collect(pairs))
Attributes(nt::NamedTuple) = Attributes(; nt...)
attributes(x::Attributes) = getfield(x, :attributes)
attributes(x::AbstractPlot) = getfield(x, :attributes)
Base.keys(x::Attributes) = keys(x.attributes)
Base.values(x::Attributes) = values(x.attributes)
function Base.iterate(x::Attributes, state...)
Expand Down
42 changes: 37 additions & 5 deletions ReferenceTests/src/tests/figures_and_makielayout.jl
Original file line number Diff line number Diff line change
Expand Up @@ -151,13 +151,13 @@ end
f = Figure(resolution = (800, 400))
ax1 = PolarAxis(f[1, 1], title = "No spine", spinevisible = false)
scatterlines!(ax1, range(0, 1, length=100), range(0, 10pi, length=100), color = 1:100)

ax2 = PolarAxis(f[1, 2], title = "Modified spine")
ax2.spinecolor[] = :red
ax2.spinestyle[] = :dash
ax2.spinewidth[] = 5
scatterlines!(ax2, range(0, 1, length=100), range(0, 10pi, length=100), color = 1:100)

f
end

Expand All @@ -166,9 +166,9 @@ end
@reference_test "PolarAxis decorations" begin
f = Figure(resolution = (400, 400), backgroundcolor = :black)
ax = PolarAxis(
f[1, 1],
f[1, 1],
backgroundcolor = :black,
rminorgridvisible = true, rminorgridcolor = :red,
rminorgridvisible = true, rminorgridcolor = :red,
rminorgridwidth = 1.0, rminorgridstyle = :dash,
thetaminorgridvisible = true, thetaminorgridcolor = :blue,
thetaminorgridwidth = 1.0, thetaminorgridstyle = :dash,
Expand All @@ -179,7 +179,6 @@ end
thetaticklabelsize = 18, thetaticklabelcolor = :blue,
thetaticklabelstrokewidth = 1, thetaticklabelstrokecolor = :white,
)

f
end

Expand All @@ -192,3 +191,36 @@ end
end
f
end

@reference_test "Colorbar for recipes" begin
fig, ax, pl = barplot(1:3; color=1:3, colormap=Makie.Categorical(:viridis), figure=(;resolution=(800, 800)))
Colorbar(fig[1, 2], pl; size=100)
x = LinRange(-1, 1, 20)
y = LinRange(-1, 1, 20)
z = LinRange(-1, 1, 20)
values = [sin(x[i]) * cos(y[j]) * sin(z[k]) for i in 1:20, j in 1:20, k in 1:20]

# TO not make this fail in CairoMakie, we dont actually plot the volume
_f, ax, cp = contour(x, y, z, values; levels=10, colormap=:viridis)
Colorbar(fig[2, :], cp; size=300)

# horizontal colorbars
Colorbar(fig[1, 3][2, 1]; limits=(0, 10), colormap=:viridis,
vertical=false)
Colorbar(fig[1, 3][3, 1]; limits=(0, 5), size=25,
colormap=cgrad(:Spectral, 5; categorical=true), vertical=false)
Colorbar(fig[1, 3][4, 1]; limits=(-1, 1), colormap=:heat, vertical=false, flipaxis=false,
highclip=:cyan, lowclip=:red)
xs = LinRange(0, 20, 50)
ys = LinRange(0, 15, 50)
zs = [cos(x) * sin(y) for x in xs, y in ys]
ax, hm = contourf(fig[2, 3][1, 1], xs, ys, zs;
colormap=:Spectral, levels=[-1, -0.5, -0.25, 0, 0.25, 0.5, 1])
Colorbar(fig[2, 3][1, 2], hm; ticks=-1:0.25:1)

ax, hm = contourf(fig[3, :][1, 1], xs, ys, zs;
colormap=:Spectral, colorscale=sqrt, levels=[ 0, 0.25, 0.5, 1])
Colorbar(fig[3, :][1, 2], hm; width=200)

fig
end
4 changes: 2 additions & 2 deletions WGLMakie/src/lines.jl
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ function create_shader(scene::Scene, plot::Union{Lines,LineSegments})
linewidth = converted_attribute(plot, :linewidth)
cmap = plot.calculated_colors[]

color = cmap isa Makie.ColorMap ? cmap.color_scaled : plot.calculated_colors
color = cmap isa Makie.ColorMapping ? cmap.color_scaled : plot.calculated_colors

for (k, attribute) in [:linewidth => linewidth, :color => color]
attribute = lift(attribute) do x
Expand All @@ -52,7 +52,7 @@ function create_shader(scene::Scene, plot::Union{Lines,LineSegments})
uniforms[Symbol("$(k)_end")] = attribute
else
if attribute[] isa AbstractVector{<:Number} && k == :color
@assert cmap isa Makie.ColorMap
@assert cmap isa Makie.ColorMapping
attribute = lift(Makie.numbers_to_colors, attribute, cmap.colormap, identity,
cmap.colorrange_scaled, cmap.lowclip,
cmap.highclip,
Expand Down
2 changes: 1 addition & 1 deletion WGLMakie/src/meshes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ function handle_color!(plot, uniforms, buffers, uniform_color_name = :uniform_co
uniforms[uniform_color_name] = Sampler(convert_text(color); minfilter=minfilter)
elseif color[] isa AbstractMatrix
uniforms[uniform_color_name] = Sampler(convert_text(color); minfilter=minfilter)
elseif color[] isa Makie.ColorMap
elseif color[] isa Makie.ColorMapping
if color[].color_scaled[] isa AbstractVector
buffers[:color] = Buffer(color[].color_scaled)
else
Expand Down
5 changes: 1 addition & 4 deletions docs/reference/blocks/colorbar.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@


# Colorbar

A Colorbar needs a colormap and a tuple of low/high limits.
Expand Down Expand Up @@ -67,7 +65,6 @@ fig
```
\end{examplefigure}


## Attributes

\attrdocs{Colorbar}
\attrdocs{Colorbar}
1 change: 0 additions & 1 deletion docs/reference/plots/hexbin.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,6 @@ tightlimits!(ax)

Colorbar(f[1, 2], hb,
label = "Number of airports",
ticks = (0:3, ["0", "10", "100", "1000"]),
height = Relative(0.5)
)
f
Expand Down
16 changes: 5 additions & 11 deletions src/basic_recipes/contourf.jl
Original file line number Diff line number Diff line change
Expand Up @@ -48,27 +48,24 @@ end
# _computed_extendlow
# _computed_extendhigh

function _get_isoband_levels(levels::Int, mi, ma)
edges = Float32.(LinRange(mi, ma, levels+1))
end
_get_isoband_levels(levels::Int, mi, ma) = Float32.(LinRange(mi, ma, levels+1))

function _get_isoband_levels(levels::AbstractVector{<:Real}, mi, ma)
edges = Float32.(levels)
@assert issorted(edges)
edges
end

conversion_trait(::Type{<:Contourf}) = ContinuousSurface()

function _get_isoband_levels(::Val{:normal}, levels, values)
_get_isoband_levels(levels, extrema_nan(values)...)
return _get_isoband_levels(levels, extrema_nan(values)...)
end

function _get_isoband_levels(::Val{:relative}, levels::AbstractVector, values)
mi, ma = extrema_nan(values)
Float32.(levels .* (ma - mi) .+ mi)
return Float32.(levels .* (ma - mi) .+ mi)
end

conversion_trait(::Type{<:Contourf}) = ContinuousSurface()


function Makie.plot!(c::Contourf{<:Tuple{<:AbstractVector{<:Real}, <:AbstractVector{<:Real}, <:AbstractMatrix{<:Real}}})
xs, ys, zs = c[1:3]
Expand All @@ -93,7 +90,6 @@ function Makie.plot!(c::Contourf{<:Tuple{<:AbstractVector{<:Real}, <:AbstractVec
map!(compute_highcolor, c, highcolor, c.extendhigh, c.colormap)
c.attributes[:_computed_extendhigh] = highcolor
is_extended_high = lift(!isnothing, c, highcolor)

PolyType = typeof(Polygon(Point2f[], [Point2f[]]))

polys = Observable(PolyType[])
Expand Down Expand Up @@ -161,9 +157,7 @@ inner polygons which are holes in the outer polygon. It is possible that one
group has multiple outer polygons with multiple holes each.
"""
function _group_polys(points, ids)

polys = [points[ids .== i] for i in unique(ids)]
npolys = length(polys)

polys_lastdouble = [push!(p, first(p)) for p in polys]

Expand Down
125 changes: 90 additions & 35 deletions src/colorsampler.jl
Original file line number Diff line number Diff line change
Expand Up @@ -176,88 +176,143 @@ function numbers_to_colors(numbers::Union{AbstractArray{<:Number, N},Number},
end
end

struct ColorMap{N,T<:AbstractArray{<:Number,N},T2<:AbstractArray{<:Number,N}}
"""
ColorMappingType

* categorical: there are n categories, and n colors are assigned to each category
* banded: there are ranges edge_start..edge_end, inside which values are mapped to one color
* continous: colors are mapped continuously to values
"""
@enum ColorMappingType categorical banded continuous


struct ColorMapping{N,T<:AbstractArray{<:Number,N},T2<:AbstractArray{<:Number,N}}
# The pure color values from the plot this colormapping is associated to
# Will be always an array of numbers
color::Observable{T}
colormap::Observable{Vector{RGBAf}}
scale::Observable{Union{ReversibleScale, Function}}
raw_colormap::Observable{Vector{RGBAf}} # the above is scaled (when coming from cgrad), this is not

# Scaling function that gets applied to color
scale::Observable{Function}

# The 0-1 scaled values from crange, which describe the colormapping
mapping::Observable{Union{Nothing, Vector{Float64}}}
colorrange::Observable{Vec{2,Float64}}

lowclip::Observable{Union{Automatic, RGBAf}} # Defaults to first color in colormap
highclip::Observable{Union{Automatic, RGBAf}} # Defaults to last color in colormap
nan_color::Observable{RGBAf}

categorical::Observable{Bool}
color_mapping_type::Observable{ColorMappingType}

# scaled attributes
colorrange_scaled::Observable{Vec2f}
color_scaled::Observable{T2}
end

function assemble_colors(::T, @nospecialize(color), @nospecialize(plot)) where {N, T<:AbstractArray{<:Number, N}}
color_tight = convert(Observable{T}, color)
colormap = Observable(RGBAf[]; ignore_equal_values=true)
categorical = Observable(false)
colorscale = convert(Observable{Union{ReversibleScale, Function}}, plot.colorscale)
mapping = Observable{Union{Nothing, Vector{Float64}}}(nothing)
struct Categorical{T} <: AbstractVector{RGBAf}
values::Vector{T}
end
Categorical(values) = Categorical(to_colormap(values))
Base.getindex(c::Categorical, i) = c.values[i]
Base.size(c::Categorical) = size(c.values)

_array_value_type(::Categorical{T}) where T = Vector{T}
_array_value_type(A::AbstractArray{<:Number}) = typeof(A)
_array_value_type(r::AbstractRange) = Vector{eltype(r)} # use vector instead, to have a few less types to worry about

_to_colormap(x::PlotUtils.ColorGradient) = to_colormap(x.colors)
_to_colormap(x) = to_colormap(x)


colormapping_type(@nospecialize(colormap)) = continuous
colormapping_type(::PlotUtils.CategoricalColorGradient) = banded
colormapping_type(::Categorical) = categorical

function ColorMapping(
color::AbstractArray{<:Number, N}, colors_obs, colormap, colorrange,
colorscale, alpha, lowclip, highclip, nan_color,
color_mapping_type=lift(colormapping_type, colormap; ignore_equal_values=true)) where {N}

T = _array_value_type(color)
color_tight = convert(Observable{T}, colors_obs)
map_colors = Observable(RGBAf[]; ignore_equal_values=true)
raw_colormap = Observable(RGBAf[]; ignore_equal_values=true)
mapping = Observable{Union{Nothing,Vector{Float64}}}(nothing; ignore_equal_values=true)
colorscale = convert(Observable{Function}, colorscale)

function update_colors(cmap, a)
colors = to_colormap(cmap)
raw_colors = _to_colormap(cmap) # dont do the scaling from cgrad
if a < 1.0
colors = map(c -> RGBAf(Colors.color(c), alpha(c) * a), colors)
colors = map(c -> RGBAf(Colors.color(c), Colors.alpha(c) * a), colors)
raw_colors = map(c -> RGBAf(Colors.color(c), Colors.alpha(c) * a), raw_colors)
end
colormap[] = colors
categorical[] = cmap isa PlotUtils.CategoricalColorGradient
if colormap isa PlotUtils.ColorGradient
map_colors[] = colors
raw_colormap[] = raw_colors
if cmap isa PlotUtils.ColorGradient
mapping[] = cmap.values
end
return
end
onany(update_colors, plot, plot.colormap, plot.alpha)
update_colors(plot.colormap[], plot.alpha[])

lowclip = Observable{Union{Automatic,RGBAf}}(automatic; ignore_equal_values=true)
on(plot, plot.lowclip; update=true) do lc
lowclip[] = lc isa Automatic ? lc : to_color(lc)
onany(update_colors, colormap, alpha)
update_colors(colormap[], alpha[])

_lowclip = Observable{Union{Automatic,RGBAf}}(automatic; ignore_equal_values=true)
on(lowclip; update=true) do lc
_lowclip[] = lc isa Union{Nothing, Automatic} ? automatic : to_color(lc)
return
end
highclip = Observable{Union{Automatic,RGBAf}}(automatic; ignore_equal_values=true)
on(plot, plot.highclip; update=true) do hc
highclip[] = hc isa Automatic ? hc : to_color(hc)
_highclip = Observable{Union{Automatic,RGBAf}}(automatic; ignore_equal_values=true)
on(highclip; update=true) do hc
_highclip[] = hc isa Union{Nothing,Automatic} ? automatic : to_color(hc)
return
end
nan_color = lift(to_color, plot.nan_color)
colorrange = Observable(Vec{2, Float64}(0); ignore_equal_values=true)

colorrange = lift(color_tight, plot.colorrange; ignore_equal_values=true) do color, crange
colorrange = lift(color_tight, colorrange; ignore_equal_values=true) do color, crange
return crange isa Automatic ? Vec2{Float64}(distinct_extrema_nan(color)) : Vec2{Float64}(crange)
end

colorrange_scaled = lift(colorrange, colorscale; ignore_equal_values=true) do range, scale
return Vec2f(apply_scale(scale, range))
end

color_scaled = lift(color_tight, colorscale) do color, scale
return el32convert(apply_scale(scale, color))
end
return ColorMap{N, T, typeof(color_scaled[])}(
CT = ColorMapping{N,T,typeof(color_scaled[])}

return CT(
color_tight,
colormap,
map_colors,
raw_colormap,
colorscale,
mapping,
colorrange,
lowclip,
highclip,
nan_color,
categorical,
_lowclip,
_highclip,
lift(to_color, nan_color),
color_mapping_type,
colorrange_scaled,
color_scaled
)
color_scaled)
end

function assemble_colors(c::AbstractArray{<:Number}, @nospecialize(color), @nospecialize(plot))
return ColorMapping(c, color, plot.colormap, plot.colorrange, plot.colorscale, plot.alpha, plot.lowclip,
plot.highclip, plot.nan_color)
end

function to_color(c::ColorMap)
function to_color(c::ColorMapping)
return numbers_to_colors(c.color_scaled[], c.colormap[], identity, c.colorrange_scaled[], lowclip(c)[], highclip(c)[], c.nan_color[])
end

function Base.get(c::ColorMapping, value::Number)
return numbers_to_colors([value], c.colormap[], c.scale[], c.colorrange_scaled[], lowclip(c)[],
highclip(c)[], c.nan_color[])[1]
end

function assemble_colors(colortype, color, plot)
return lift(plot, color, plot.alpha) do color, a
if a < 1.0
Expand All @@ -276,5 +331,5 @@ function assemble_colors(::Number, color, plot)
cm.nan_color)
end

highclip(cmap::ColorMap) = lift((cm, hc) -> hc isa Automatic ? last(cm) : hc, cmap.colormap, cmap.highclip)
lowclip(cmap::ColorMap) = lift((cm, hc) -> hc isa Automatic ? first(cm) : hc, cmap.colormap, cmap.lowclip)
highclip(cmap::ColorMapping) = lift((cm, hc) -> hc isa Automatic ? last(cm) : hc, cmap.colormap, cmap.highclip)
lowclip(cmap::ColorMapping) = lift((cm, hc) -> hc isa Automatic ? first(cm) : hc, cmap.colormap, cmap.lowclip)
Loading