From f0fa921c9309bb716ef6f8bcd35a66dede73a224 Mon Sep 17 00:00:00 2001 From: t-bltg Date: Mon, 25 Dec 2023 18:35:05 +0100 Subject: [PATCH 1/4] fix colorbar corner cases (contourf, tricontourf) --- src/basic_recipes/contourf.jl | 6 ++-- src/basic_recipes/tricontourf.jl | 11 ++++--- src/colorsampler.jl | 9 ++++-- src/layouting/data_limits.jl | 3 +- src/makielayout/blocks/colorbar.jl | 4 +-- src/makielayout/ticklocators/wilkinson.jl | 5 ++- test/makielayout.jl | 39 +++++++++++++++++++---- 7 files changed, 54 insertions(+), 23 deletions(-) diff --git a/src/basic_recipes/contourf.jl b/src/basic_recipes/contourf.jl index 89ff2ae4da3..08523303137 100644 --- a/src/basic_recipes/contourf.jl +++ b/src/basic_recipes/contourf.jl @@ -74,9 +74,8 @@ function Makie.plot!(c::Contourf{<:Tuple{<:AbstractVector{<:Real}, <:AbstractVec _get_isoband_levels(Val(mode), levels, vec(zs)) end - colorrange = lift(c, c._computed_levels) do levels - minimum(levels), maximum(levels) - end + colorrange = lift(distinct_extrema_nan, c._computed_levels) + computed_colormap = lift(compute_contourf_colormap, c, c._computed_levels, c.colormap, c.extendlow, c.extendhigh) c.attributes[:_computed_colormap] = computed_colormap @@ -171,7 +170,6 @@ function _group_polys(points, ids) for p1 in polys_lastdouble, p2 in polys_lastdouble] unclassified_polyindices = collect(1:size(containment_matrix, 1)) - # @show unclassified_polyindices # each group has first an outer polygon, and then its holes # TODO: don't specifically type this 2f0? diff --git a/src/basic_recipes/tricontourf.jl b/src/basic_recipes/tricontourf.jl index e711345735d..4a37c82791d 100644 --- a/src/basic_recipes/tricontourf.jl +++ b/src/basic_recipes/tricontourf.jl @@ -75,11 +75,14 @@ function Makie.convert_arguments(::Type{<:Tricontourf}, x::AbstractVector{<:Real end function compute_contourf_colormap(levels, cmap, elow, ehigh) - levels_scaled = (levels .- minimum(levels)) ./ (maximum(levels) - minimum(levels)) - n = length(levels_scaled) - _cmap = to_colormap(cmap) + lo, hi = extrema(levels) + lo == hi && return cgrad(_cmap, levels; categorical=true) + + levels_scaled = (levels .- lo) ./ (hi - lo) + n = length(levels_scaled) + if elow === :auto && ehigh !== :auto cm_base = cgrad(_cmap, n + 1; categorical=true)[2:end] cm = cgrad(cm_base, levels_scaled; categorical=true) @@ -122,7 +125,7 @@ function Makie.plot!(c::Tricontourf{<:Tuple{<:DelTri.Triangulation, <:AbstractVe return _get_isoband_levels(Val(mode), levels, vec(zs)) end - colorrange = lift(extrema_nan, c, c._computed_levels) + colorrange = lift(distinct_extrema_nan, c, c._computed_levels) computed_colormap = lift(compute_contourf_colormap, c, c._computed_levels, c.colormap, c.extendlow, c.extendhigh) c.attributes[:_computed_colormap] = computed_colormap diff --git a/src/colorsampler.jl b/src/colorsampler.jl index a8800b46b69..b5cc898a9de 100644 --- a/src/colorsampler.jl +++ b/src/colorsampler.jl @@ -292,11 +292,14 @@ function _colormapping( end 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) + if !(auto = crange isa Automatic) + crange[1] == crange[2] && error("Cannot use a colorrange with a single value ($crange): provide distinct values instead.") + end + return auto ? 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)) + colorrange_scaled = lift(colorrange, colorscale; ignore_equal_values=true) do crange, cscale + return Vec2f(apply_scale(cscale, crange)) end color_scaled = lift(color_tight, colorscale) do color, scale diff --git a/src/layouting/data_limits.jl b/src/layouting/data_limits.jl index 92d4e94a38b..2948c1cf231 100644 --- a/src/layouting/data_limits.jl +++ b/src/layouting/data_limits.jl @@ -32,7 +32,8 @@ end function distinct_extrema_nan(x) lo, hi = extrema_nan(x) - lo == hi ? (lo - 0.5f0, hi + 0.5f0) : (lo, hi) + δ = lo == hi ? eps(Float32) : 0 + (lo - δ, hi + δ) end function point_iterator(plot::Union{Scatter, MeshScatter, Lines, LineSegments}) diff --git a/src/makielayout/blocks/colorbar.jl b/src/makielayout/blocks/colorbar.jl index 9032a15a888..980d63811cd 100644 --- a/src/makielayout/blocks/colorbar.jl +++ b/src/makielayout/blocks/colorbar.jl @@ -63,7 +63,7 @@ end function extract_colormap(plot::Union{Contourf,Tricontourf}) levels = plot._computed_levels - limits = lift(l -> (l[1], l[end]), levels) + colorrange = lift(distinct_extrema_nan, levels) function extend_color(color, computed) color === nothing && return automatic color == :auto || color == automatic && return computed @@ -71,7 +71,7 @@ function extract_colormap(plot::Union{Contourf,Tricontourf}) end elow = lift(extend_color, plot.extendlow, plot._computed_extendlow) ehigh = lift(extend_color, plot.extendhigh, plot._computed_extendhigh) - return ColorMapping(levels[], levels, plot._computed_colormap, limits, plot.colorscale, Observable(1.0), + return ColorMapping(levels[], levels, plot._computed_colormap, colorrange, plot.colorscale, Observable(1.0), elow, ehigh, plot.nan_color) end diff --git a/src/makielayout/ticklocators/wilkinson.jl b/src/makielayout/ticklocators/wilkinson.jl index 2e5922db448..86a86c31cf5 100644 --- a/src/makielayout/ticklocators/wilkinson.jl +++ b/src/makielayout/ticklocators/wilkinson.jl @@ -7,7 +7,7 @@ function WilkinsonTicks(k_ideal::Int; k_min = 2, k_max = 10, min_px_dist = 50.0) if !(0 < k_min <= k_ideal <= k_max) - error("Invalid tick number specifications k_ideal $k_ideal, k_min $k_min, k_max $k_max") + error("Invalid tick number specifications k_ideal=$k_ideal, k_min=$k_min, k_max=$k_max") end WilkinsonTicks(k_ideal, k_min, k_max, Q, granularity_weight, simplicity_weight, @@ -17,9 +17,8 @@ end get_tickvalues(ticks::WilkinsonTicks, vmin, vmax) = get_tickvalues(ticks, Float64(vmin), Float64(vmax)) function get_tickvalues(ticks::WilkinsonTicks, vmin::Float64, vmax::Float64) - tickvalues, _ = PlotUtils.optimize_ticks(Float64(vmin), Float64(vmax); - extend_ticks = false, strict_span=true, span_buffer = nothing, + extend_ticks = false, span_buffer = nothing, strict_span = vmin ≉ vmax, k_min = ticks.k_min, k_max = ticks.k_max, k_ideal = ticks.k_ideal, diff --git a/test/makielayout.jl b/test/makielayout.jl index 33611f38f2a..f9becaa067c 100644 --- a/test/makielayout.jl +++ b/test/makielayout.jl @@ -42,15 +42,42 @@ end _, hm = heatmap(fig[1, 1], xs, ys, zs) cb = Colorbar(fig[1, 2], hm) - @test hm.calculated_colors[].colorrange[] == Vec(-0.5, 0.5) - @test cb.limits[] == Vec(-.5, .5) - + let (lo, hi) = hm.calculated_colors[].colorrange[] + @test lo ≉ hi + end + let (lo, hi) = cb.limits[] + @test lo ≉ hi + end hm.colorrange = Float32.((-1, 1)) @test cb.limits[] == Vec(-1, 1) +end + +@testset "zero contourf" begin + x = [0, 1] + y = [0, 2] + z = zeros(2, 2) + + fig = Figure() + _, ct = contourf(fig[1, 1], x, y, z) + cb = Colorbar(fig[1, 2], ct) - # TODO: This doesn't work anymore because colorbar doesn't use the same observable - # cb.limits[] = Float32.((-2, 2)) - # @test hm.attributes[:colorrange][] == (-2, 2) + let (lo, hi) = cb.limits[] + @test lo ≉ hi + end +end + +@testset "zero tricontourf" begin + x = randn(10) + y = randn(10) + z = @. -sqrt(x^2 + y^2) + 0.1 * randn() + + fig = Figure() + _, ct = tricontourf(fig[1, 1], x, y, z) + cb = Colorbar(fig[1, 2], ct) + + let (lo, hi) = cb.limits[] + @test lo ≉ hi + end end @testset "Axis limits basics" begin From 77ab3af91c3fd2980268ad60621517b3eb3b76e8 Mon Sep 17 00:00:00 2001 From: t-bltg Date: Mon, 25 Dec 2023 18:49:23 +0100 Subject: [PATCH 2/4] rework tests --- test/makielayout.jl | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/test/makielayout.jl b/test/makielayout.jl index f9becaa067c..2b736d172a9 100644 --- a/test/makielayout.jl +++ b/test/makielayout.jl @@ -33,7 +33,7 @@ end @test isempty(ax.scene.plots) end -@testset "zero heatmap" begin +@testset "Colorbar: zero heatmap" begin xs = LinRange(0, 20, 10) ys = LinRange(0, 15, 10) zs = zeros(length(xs), length(ys)) @@ -52,7 +52,13 @@ end @test cb.limits[] == Vec(-1, 1) end -@testset "zero contourf" begin +@testset "Colorbar: invalid limits" begin + fig = Figure() + Axis(fig[1, 1]) + @test_throws ErrorException Colorbar(fig[1, 2], limits = (1, 1)) +end + +@testset "Colorbar: zero contourf" begin x = [0, 1] y = [0, 2] z = zeros(2, 2) @@ -66,7 +72,7 @@ end end end -@testset "zero tricontourf" begin +@testset "Colorbar: zero tricontourf" begin x = randn(10) y = randn(10) z = @. -sqrt(x^2 + y^2) + 0.1 * randn() From 8fa2ade89fdf9c8c9e9c361007f9e3a293c3fb4c Mon Sep 17 00:00:00 2001 From: t-bltg Date: Tue, 26 Dec 2023 12:27:41 +0100 Subject: [PATCH 3/4] change zero type --- src/layouting/data_limits.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/layouting/data_limits.jl b/src/layouting/data_limits.jl index 2948c1cf231..fe2b8e88bdd 100644 --- a/src/layouting/data_limits.jl +++ b/src/layouting/data_limits.jl @@ -32,7 +32,7 @@ end function distinct_extrema_nan(x) lo, hi = extrema_nan(x) - δ = lo == hi ? eps(Float32) : 0 + δ = lo == hi ? eps(Float32) : zero(lo) (lo - δ, hi + δ) end From 1ef457c3b6e395976f8729c2417ebca5316041c0 Mon Sep 17 00:00:00 2001 From: t-bltg Date: Tue, 26 Dec 2023 16:39:39 +0100 Subject: [PATCH 4/4] alternative --- src/layouting/data_limits.jl | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/layouting/data_limits.jl b/src/layouting/data_limits.jl index fe2b8e88bdd..837257615a8 100644 --- a/src/layouting/data_limits.jl +++ b/src/layouting/data_limits.jl @@ -32,8 +32,17 @@ end function distinct_extrema_nan(x) lo, hi = extrema_nan(x) - δ = lo == hi ? eps(Float32) : zero(lo) - (lo - δ, hi + δ) + δ⁻ = if lo == hi + iszero(lo) ? zero(lo) : one(lo) + else + zero(lo) + end + δ⁺ = if lo == hi + iszero(hi) ? one(hi) : zero(hi) + else + zero(hi) + end + (lo - δ⁻, hi + δ⁺) end function point_iterator(plot::Union{Scatter, MeshScatter, Lines, LineSegments})