From d66b8b0e2d438f591a43f9b81bb2d4c1688cfb42 Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Mon, 24 Jul 2023 17:44:55 +0200 Subject: [PATCH 01/23] improve colorbar errors for plots that don't use a colormap --- src/interfaces.jl | 2 ++ src/makielayout/blocks/colorbar.jl | 9 ++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/interfaces.jl b/src/interfaces.jl index c040da6cdd3..363669a5817 100644 --- a/src/interfaces.jl +++ b/src/interfaces.jl @@ -87,6 +87,8 @@ const atomic_function_symbols = ( const atomic_functions = getfield.(Ref(Makie), atomic_function_symbols) const Atomic{Arg} = Union{map(x-> Combined{x, Arg}, atomic_functions)...} +is_atomic_plot(::Combined{F}) where {F} = F in atomic_functions + function (PT::Type{<: Combined})(parent, transformation, attributes, input_args, converted) PT(parent, transformation, attributes, input_args, converted, AbstractPlot[]) end diff --git a/src/makielayout/blocks/colorbar.jl b/src/makielayout/blocks/colorbar.jl index 6ccbc928238..29662dfc02a 100644 --- a/src/makielayout/blocks/colorbar.jl +++ b/src/makielayout/blocks/colorbar.jl @@ -27,11 +27,18 @@ end function Colorbar(fig_or_scene, plot::AbstractPlot; kwargs...) colorbar_check((:colormap, :limits, :highclip, :lowclip), keys(kwargs)) - if haskey(plot, :calculated_colors) && plot.calculated_colors[] isa ColorMap cmap = plot.calculated_colors[] scale = cmap.scale else + # Atomic plots should always have calculated_colors set, if they actually use a colormap + if is_atomic_plot(plot) + error("Plot `$(plotfunc(plot))` has no values that are mapped to colors, so it cannot be used to create a colorbar.") + end + # So this is for recipes + if !all(x-> haskey(plot, x), (:colormap, :colorrange, :highclip, :lowclip)) + error("Plot $(plotfunc(plot)) doesn't have all colormap attributes. Attributes needed: colormap, colorrange, highclip, lowclip") + end cmap = plot scale = plot.colorscale end From fc513b4832392c4cc3c9bbec55f7c7d65760ca7d Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Mon, 24 Jul 2023 18:19:26 +0200 Subject: [PATCH 02/23] don't mess with existing is_atomic_plot --- src/interfaces.jl | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/interfaces.jl b/src/interfaces.jl index 363669a5817..c040da6cdd3 100644 --- a/src/interfaces.jl +++ b/src/interfaces.jl @@ -87,8 +87,6 @@ const atomic_function_symbols = ( const atomic_functions = getfield.(Ref(Makie), atomic_function_symbols) const Atomic{Arg} = Union{map(x-> Combined{x, Arg}, atomic_functions)...} -is_atomic_plot(::Combined{F}) where {F} = F in atomic_functions - function (PT::Type{<: Combined})(parent, transformation, attributes, input_args, converted) PT(parent, transformation, attributes, input_args, converted, AbstractPlot[]) end From e3f3ca53b22e5d0d0e8c38757a9f3bd7ce39b046 Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Tue, 25 Jul 2023 19:26:48 +0200 Subject: [PATCH 03/23] improve errors --- src/makielayout/blocks/colorbar.jl | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/makielayout/blocks/colorbar.jl b/src/makielayout/blocks/colorbar.jl index 29662dfc02a..a59e6f94778 100644 --- a/src/makielayout/blocks/colorbar.jl +++ b/src/makielayout/blocks/colorbar.jl @@ -32,12 +32,22 @@ function Colorbar(fig_or_scene, plot::AbstractPlot; kwargs...) scale = cmap.scale else # Atomic plots should always have calculated_colors set, if they actually use a colormap - if is_atomic_plot(plot) - error("Plot `$(plotfunc(plot))` has no values that are mapped to colors, so it cannot be used to create a colorbar.") + func = plotfunc(plot) + if func in Makie.atomic_functions + error("Plot `$(func)` doesn't have a color attribute that uses a colormap to map to colors, so it cannot be used to create a colorbar. Found color attribute: $(typeof(plot.calculated_colors[]))). Needs to be a number of vector of numbers!") end # So this is for recipes if !all(x-> haskey(plot, x), (:colormap, :colorrange, :highclip, :lowclip)) - error("Plot $(plotfunc(plot)) doesn't have all colormap attributes. Attributes needed: colormap, colorrange, highclip, lowclip") + attributes = filter(x-> haskey(plot, x), [:colormap, :colorrange, :highclip, :lowclip]) + error("Plot $(func) doesn't have all colormap attributes. Attributes needed: colormap, colorrange, highclip, lowclip. Found: $(attributes)") + end + if to_value(plot.colorrange) isa Automatic || (plot.color[] isa Union{AbstractVector{<: Colorant}, Colorant}) + error(""" + Plot $(func) doesn't have a color attribute that uses a colormap to map to colors, so it cannot be used to create a colorbar. + Please create the colorbar manually e.g. via `Colorbar(f[1, 2], colorrange=the_range, colormap=the_colormap)`. + This could also mean, that you're using a recipe, which itself doesn't calculate the colorrange, and leaves it to its children. + E.g. `f, ax, pl = barplot(1:3; color=1:3)` will not work, but `Colorbar(f[1, 2], pl.plots[1].plots[1])` will. + """) end cmap = plot scale = plot.colorscale From 1432fa999eb3893b23ca2b13ebad045a026bff3a Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Tue, 25 Jul 2023 19:28:03 +0200 Subject: [PATCH 04/23] add tests --- test/makielayout.jl | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/makielayout.jl b/test/makielayout.jl index 8d0bd20d107..6f4de450337 100644 --- a/test/makielayout.jl +++ b/test/makielayout.jl @@ -168,6 +168,14 @@ end @test ticklabel_strings[1] == "0.0" @test ticklabel_strings[end] == "1.0" end + @testset "errors" begin + f, ax, pl1 = scatter(rand(10)) + pl2 = scatter!(ax, rand(10); color=rand(RGBf, 10)) + pl3 = barplot!(ax, 1:3; colorrange=(0, 1)) + @test_throws ErrorException Colorbar(f[1, 2], pl1) + @test_throws ErrorException Colorbar(f[1, 2], pl2) + @test_throws ErrorException Colorbar(f[1, 2], pl3) + end end @testset "cycling" begin From f778e423b19855d5a892418de2e7a76267074452 Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Wed, 26 Jul 2023 17:51:12 +0200 Subject: [PATCH 05/23] fix error --- src/makielayout/blocks/colorbar.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/makielayout/blocks/colorbar.jl b/src/makielayout/blocks/colorbar.jl index a59e6f94778..fbe1e3ff512 100644 --- a/src/makielayout/blocks/colorbar.jl +++ b/src/makielayout/blocks/colorbar.jl @@ -41,7 +41,7 @@ function Colorbar(fig_or_scene, plot::AbstractPlot; kwargs...) attributes = filter(x-> haskey(plot, x), [:colormap, :colorrange, :highclip, :lowclip]) error("Plot $(func) doesn't have all colormap attributes. Attributes needed: colormap, colorrange, highclip, lowclip. Found: $(attributes)") end - if to_value(plot.colorrange) isa Automatic || (plot.color[] isa Union{AbstractVector{<: Colorant}, Colorant}) + if to_value(plot.colorrange) isa Automatic || (to_value(get(plot, :color, nothing)) isa Union{AbstractVector{<: Colorant}, Colorant}) error(""" Plot $(func) doesn't have a color attribute that uses a colormap to map to colors, so it cannot be used to create a colorbar. Please create the colorbar manually e.g. via `Colorbar(f[1, 2], colorrange=the_range, colormap=the_colormap)`. From b3065cc11857579b2a188db2bb736915133e6cd5 Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Fri, 28 Jul 2023 16:36:39 +0200 Subject: [PATCH 06/23] try a more generic approach --- MakieCore/src/attributes.jl | 1 + src/makielayout/blocks/legend.jl | 15 +++++++--- src/scenes.jl | 13 --------- src/utilities/utilities.jl | 50 ++++++++++++++++++++++++++++++++ 4 files changed, 62 insertions(+), 17 deletions(-) diff --git a/MakieCore/src/attributes.jl b/MakieCore/src/attributes.jl index 30f8d2cf5ad..a86723d400c 100644 --- a/MakieCore/src/attributes.jl +++ b/MakieCore/src/attributes.jl @@ -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...) diff --git a/src/makielayout/blocks/legend.jl b/src/makielayout/blocks/legend.jl index d711ce48338..b32874043a0 100644 --- a/src/makielayout/blocks/legend.jl +++ b/src/makielayout/blocks/legend.jl @@ -257,10 +257,10 @@ function connect_block_layoutobservables!(leg::Legend, layout_width, layout_heig end + function legendelement_plots!(scene, element::MarkerElement, bbox::Observable{Rect2f}, defaultattrs::Attributes) merge!(element.attributes, defaultattrs) attrs = element.attributes - fracpoints = attrs.markerpoints points = lift((bb, fp) -> fractionpoint.(Ref(bb), fp), scene, bbox, fracpoints) scat = scatter!(scene, points, color = attrs.markercolor, marker = attrs.marker, @@ -399,9 +399,15 @@ function scalar_lift(plot, attr, default) return observable end +function extract_color(@nospecialize(plot), color_default) + color = extract_color_recursive(plot) + color isa Dict && return color_default + return Makie.is_scalar_attribute(color[]) ? color_default : color +end + function legendelements(plot::Union{Lines, LineSegments}, legend) LegendElement[LineElement( - color = scalar_lift(plot, plot.color, legend.linecolor), + color = extract_color(plot, legend.linecolor), linestyle = scalar_lift(plot, plot.linestyle, legend.linestyle), linewidth = scalar_lift(plot, plot.linewidth, legend.linewidth))] end @@ -409,7 +415,7 @@ end function legendelements(plot::Scatter, legend) LegendElement[MarkerElement( - color = scalar_lift(plot, plot.color, legend.markercolor), + color = extract_color(plot, legend.markercolor), marker = scalar_lift(plot, plot.marker, legend.marker), markersize = scalar_lift(plot, plot.markersize, legend.markersize), strokewidth = scalar_lift(plot, plot.strokewidth, legend.markerstrokewidth), @@ -418,8 +424,9 @@ function legendelements(plot::Scatter, legend) end function legendelements(plot::Union{Poly, Violin, BoxPlot, CrossBar, Density}, legend) + color = extract_color(plot, legend.polycolor) LegendElement[PolyElement( - color = scalar_lift(plot, plot.color, legend.polycolor), + color = color, strokecolor = scalar_lift(plot, plot.strokecolor, legend.polystrokecolor), strokewidth = scalar_lift(plot, plot.strokewidth, legend.polystrokewidth), )] diff --git a/src/scenes.jl b/src/scenes.jl index f3e5f45a195..b1bd92c94e7 100644 --- a/src/scenes.jl +++ b/src/scenes.jl @@ -552,19 +552,6 @@ function plots_from_camera(scene::Scene, camera::Camera, list=AbstractPlot[]) list end -""" -Flattens all the combined plots and returns a Vector of Atomic plots -""" -function flatten_combined(plots::Vector, flat=AbstractPlot[]) - for elem in plots - if (elem isa Combined) - flatten_combined(elem.plots, flat) - else - push!(flat, elem) - end - end - flat -end function insertplots!(screen::AbstractDisplay, scene::Scene) for elem in scene.plots diff --git a/src/utilities/utilities.jl b/src/utilities/utilities.jl index 564b1a53418..fa75c6c1c15 100644 --- a/src/utilities/utilities.jl +++ b/src/utilities/utilities.jl @@ -347,3 +347,53 @@ end # Scalar - Vector getindex sv_getindex(v::Vector, i::Integer) = v[i] sv_getindex(x, i::Integer) = x + + +function extract_color_recursive(@nospecialize(plot)) + color = extract_color(attributes(plot)) + if color === nothing && !is_atomic_plot(plot) + extracted_colors = unique(map(extract_color, collect_atomic_plots(plot))) + if length(extracted_colors) == 1 + return extracted_colors[1] + elseif !isempty(extracted_colors) + if any(x-> x isa Dict, extracted_colors) + filter!(x-> x isa Dict, extracted_colors) + return only(extracted_colors) + end + else + error("Multiple or no colors found: $(map(typeof, extracted_colors))") + end + end + return color +end + +function extract_color(attributes) + if haskey(attributes, :calculated_colors) + color = attributes[:calculated_colors] + if color[] isa ColorMap + result = Dict{Symbol, Any}() + result[:colorrange] = color[].colorrange + result[:colormap] = color[].colormap + result[:highclip] = color[].highclip + result[:lowclip] = color[].lowclip + return result + end + return color + else + if haskey(attributes, :colormap) && haskey(attributes, :colorrange) && !(attributes.colorrange[] isa Automatic) + if haskey(attributes, :color) && attributes[:color][] isa Union{Number,Cycled} + return nothing + end + result = Dict{Symbol, Any}() + result[:colorrange] = attributes[:colorrange] + result[:colormap] = attributes[:colormap] + haskey(attributes, :highclip) && (result[:highclip] = attributes[:highclip]) + haskey(attributes, :lowclip) && (result[:lowclip] = attributes[:lowclip]) + return result + end + end + if haskey(attributes, :color) && attributes[:color][] isa Union{Colorant, AbstractArray{<: Colorant}} + return attributes[:color] + end + return nothing +end From 0105441070d3a7b158177ff6f24b5974d184e620 Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Wed, 23 Aug 2023 18:05:09 +0200 Subject: [PATCH 07/23] recursively search for colormap --- src/makielayout/blocks/colorbar.jl | 51 ++++++++++++++++++++++++++---- test/makielayout.jl | 5 +++ 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/src/makielayout/blocks/colorbar.jl b/src/makielayout/blocks/colorbar.jl index 8969722ef13..8a72021e0c4 100644 --- a/src/makielayout/blocks/colorbar.jl +++ b/src/makielayout/blocks/colorbar.jl @@ -25,14 +25,48 @@ function colorbar_check(keys, kwargs_keys) end end +function extract_colormap(plot) + if haskey(plot, :calculated_colors) && plot.calculated_colors[] isa Makie.ColorMap + return plot.calculated_colors[] + elseif all(x -> haskey(plot, x), [:colormap, :colorrange]) && !(plot.colorrange[] isa Makie.Automatic) + result = Dict{Symbol,Any}() + for key in [:colormap, :colorrange, :highclip, :lowclip, :colorscale, :color] + if haskey(plot, key) + result[key] = plot[key] + end + end + return result + else + colormaps = [extract_colormap(child) for child in plot.plots] + if length(colormaps) == 1 + return colormaps[1] + else + idx = findfirst(x -> x isa Makie.ColorMap, colormaps) + !isnothing(idx) && return colormaps[idx] + idx = findfirst(x -> x isa Dict, colormaps) + !isnothing(idx) && return colormaps[idx] + return nothing + end + end +end + function Colorbar(fig_or_scene, plot::AbstractPlot; kwargs...) colorbar_check((:colormap, :limits, :highclip, :lowclip), keys(kwargs)) - if haskey(plot, :calculated_colors) && plot.calculated_colors[] isa ColorMap - cmap = plot.calculated_colors[] + cmap = extract_colormap(plot) + func = plotfunc(plot) + if cmap isa ColorMap scale = cmap.scale + elseif cmap isa Dict + cmap = ( + colormap = cmap[:colormap], + colorrange = cmap[:colorrange], + highclip = get(cmap, :highclip, automatic), + lowclip = get(cmap, :lowclip, automatic), + color = cmap[:color] + ) + scale = plot[:colorscale] else # Atomic plots should always have calculated_colors set, if they actually use a colormap - func = plotfunc(plot) if func in Makie.atomic_functions error("Plot `$(func)` doesn't have a color attribute that uses a colormap to map to colors, so it cannot be used to create a colorbar. Found color attribute: $(typeof(plot.calculated_colors[]))). Needs to be a number of vector of numbers!") end @@ -49,10 +83,15 @@ function Colorbar(fig_or_scene, plot::AbstractPlot; kwargs...) E.g. `f, ax, pl = barplot(1:3; color=1:3)` will not work, but `Colorbar(f[1, 2], pl.plots[1].plots[1])` will. """) end - cmap = plot - scale = plot.colorscale end - + if to_value(cmap.color) isa Union{AbstractVector{<: Colorant}, Colorant} + error(""" + Plot $(func) doesn't have a color attribute that uses a colormap to map to colors, so it cannot be used to create a colorbar. + Please create the colorbar manually e.g. via `Colorbar(f[1, 2], colorrange=the_range, colormap=the_colormap)`. + This could also mean, that you're using a recipe, which itself doesn't calculate the colorrange, and leaves it to its children. + E.g. `f, ax, pl = barplot(1:3; color=1:3)` will not work, but `Colorbar(f[1, 2], pl.plots[1].plots[1])` will. + """) + end Colorbar( fig_or_scene; colormap=cmap.colormap, diff --git a/test/makielayout.jl b/test/makielayout.jl index 6f4de450337..3d8b5ef309b 100644 --- a/test/makielayout.jl +++ b/test/makielayout.jl @@ -176,6 +176,11 @@ end @test_throws ErrorException Colorbar(f[1, 2], pl2) @test_throws ErrorException Colorbar(f[1, 2], pl3) end + @testset "Recipes" begin + f, ax, pl = barplot(1:3; color=1:3) + cbar = Colorbar(f[1, 2], pl) + @test cbar.limits[] == Vec(1.0, 3.0) + end end @testset "cycling" begin From b31aa88bb329fe89a7bcc67cdbcbb97f53f56566 Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Fri, 25 Aug 2023 14:58:34 +0200 Subject: [PATCH 08/23] implement categorical=true correctly --- src/colorsampler.jl | 5 + src/makielayout/blocks/colorbar.jl | 200 ++++++++++++++--------------- src/makielayout/blocks/legend.jl | 34 ++--- src/utilities/utilities.jl | 50 -------- 4 files changed, 118 insertions(+), 171 deletions(-) diff --git a/src/colorsampler.jl b/src/colorsampler.jl index 71cc0d275a0..2f8c14bccfe 100644 --- a/src/colorsampler.jl +++ b/src/colorsampler.jl @@ -258,6 +258,11 @@ function to_color(c::ColorMap) 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::ColorMap, 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 diff --git a/src/makielayout/blocks/colorbar.jl b/src/makielayout/blocks/colorbar.jl index 8a72021e0c4..eb5b8c8aa45 100644 --- a/src/makielayout/blocks/colorbar.jl +++ b/src/makielayout/blocks/colorbar.jl @@ -25,10 +25,11 @@ function colorbar_check(keys, kwargs_keys) end end -function extract_colormap(plot) +function extract_colormap(@nospecialize(plot::T)) where {T <: AbstractPlot} if haskey(plot, :calculated_colors) && plot.calculated_colors[] isa Makie.ColorMap return plot.calculated_colors[] - elseif all(x -> haskey(plot, x), [:colormap, :colorrange]) && !(plot.colorrange[] isa Makie.Automatic) + elseif all(x -> haskey(plot, x), [:colormap, :colorrange, :color]) && + !(plot.colorrange[] isa Makie.Automatic) result = Dict{Symbol,Any}() for key in [:colormap, :colorrange, :highclip, :lowclip, :colorscale, :color] if haskey(plot, key) @@ -40,12 +41,13 @@ function extract_colormap(plot) colormaps = [extract_colormap(child) for child in plot.plots] if length(colormaps) == 1 return colormaps[1] - else - idx = findfirst(x -> x isa Makie.ColorMap, colormaps) - !isnothing(idx) && return colormaps[idx] - idx = findfirst(x -> x isa Dict, colormaps) - !isnothing(idx) && return colormaps[idx] + elseif isempty(colormaps) return nothing + else + # Prefer ColorMap if in doubt! + cmaps = filter(x-> x isa ColorMap, colormaps) + length(cmaps) == 1 && return cmaps[1] + error("Multiple colormaps found for plot $(plot), please specify which one to use manually. Please overload `Makie.extract_colormap(::$(T))` to allow for the automatical creation of a Colorbar.") end end end @@ -54,11 +56,16 @@ function Colorbar(fig_or_scene, plot::AbstractPlot; kwargs...) colorbar_check((:colormap, :limits, :highclip, :lowclip), keys(kwargs)) cmap = extract_colormap(plot) func = plotfunc(plot) + if isnothing(cmap) + error("Neither $(func) nor any of its children use a colormap. Cannot create a Colorbar from this plot, please create it manually. + If this is a recipe, one needs to overload `Makie.extract_colormap(::$(Combined{func}))` to allow for the automatical creation of a Colorbar.") + end if cmap isa ColorMap scale = cmap.scale + colormap = cmap elseif cmap isa Dict + colormap = cmap[:colormap] cmap = ( - colormap = cmap[:colormap], colorrange = cmap[:colorrange], highclip = get(cmap, :highclip, automatic), lowclip = get(cmap, :lowclip, automatic), @@ -66,35 +73,18 @@ function Colorbar(fig_or_scene, plot::AbstractPlot; kwargs...) ) scale = plot[:colorscale] else - # Atomic plots should always have calculated_colors set, if they actually use a colormap - if func in Makie.atomic_functions - error("Plot `$(func)` doesn't have a color attribute that uses a colormap to map to colors, so it cannot be used to create a colorbar. Found color attribute: $(typeof(plot.calculated_colors[]))). Needs to be a number of vector of numbers!") - end - # So this is for recipes - if !all(x-> haskey(plot, x), (:colormap, :colorrange, :highclip, :lowclip)) - attributes = filter(x-> haskey(plot, x), [:colormap, :colorrange, :highclip, :lowclip]) - error("Plot $(func) doesn't have all colormap attributes. Attributes needed: colormap, colorrange, highclip, lowclip. Found: $(attributes)") - end - if to_value(plot.colorrange) isa Automatic || (to_value(get(plot, :color, nothing)) isa Union{AbstractVector{<: Colorant}, Colorant}) - error(""" - Plot $(func) doesn't have a color attribute that uses a colormap to map to colors, so it cannot be used to create a colorbar. - Please create the colorbar manually e.g. via `Colorbar(f[1, 2], colorrange=the_range, colormap=the_colormap)`. - This could also mean, that you're using a recipe, which itself doesn't calculate the colorrange, and leaves it to its children. - E.g. `f, ax, pl = barplot(1:3; color=1:3)` will not work, but `Colorbar(f[1, 2], pl.plots[1].plots[1])` will. - """) - end + error("extract_colormap(::$(Combined{func})) returned an invalid value: $cmap. Needs to return either a `Dict` or `Makie.ColorMap`.") end + if to_value(cmap.color) isa Union{AbstractVector{<: Colorant}, Colorant} - error(""" - Plot $(func) doesn't have a color attribute that uses a colormap to map to colors, so it cannot be used to create a colorbar. - Please create the colorbar manually e.g. via `Colorbar(f[1, 2], colorrange=the_range, colormap=the_colormap)`. - This could also mean, that you're using a recipe, which itself doesn't calculate the colorrange, and leaves it to its children. - E.g. `f, ax, pl = barplot(1:3; color=1:3)` will not work, but `Colorbar(f[1, 2], pl.plots[1].plots[1])` will. + error("""Plot $(func)'s color attribute uses colors directly, so it can't be used to create a Colorbar, since no numbers are mapped to a color via the colormap. + Please create the colorbar manually e.g. via `Colorbar(f[1, 2], colorrange=the_range, colormap=the_colormap)`.. """) end + Colorbar( fig_or_scene; - colormap=cmap.colormap, + colormap=colormap, limits=cmap.colorrange, scale=scale, highclip=cmap.highclip, @@ -153,12 +143,7 @@ end function initialize_block!(cb::Colorbar) blockscene = cb.blockscene - limits = lift(blockscene, cb.limits, cb.colorrange) do limits, colorrange - if all(!isnothing, (limits, colorrange)) - error("Both colorrange + limits are set, please only set one, they're aliases. colorrange: $(colorrange), limits: $(limits)") - end - return something(limits, colorrange, (0, 1)) - end + onany(blockscene, cb.size, cb.vertical) do sz, vertical if vertical @@ -170,30 +155,40 @@ function initialize_block!(cb::Colorbar) framebox = lift(round_to_IRect2D, blockscene, cb.layoutobservables.computedbbox) - cgradient = Observable{PlotUtils.ColorGradient}() - map!(blockscene, cgradient, cb.colormap) do cmap - if cmap isa PlotUtils.ColorGradient - # if we have a colorgradient directly, we want to keep it intact - # to enable correct categorical colormap behavior etc - return cmap - else - # this is a bit weird, first convert to a vector of colors, - # then use cgrad, but at least I can use `get` on that later - converted = Makie.to_colormap(cmap) - return cgrad(converted) + # TODO, always convert to ColorMap! + if cb.colormap[] isa ColorMap + cmap = cb.colormap[] + map_is_categorical = cmap.categorical + cgradient = lift(x-> cmap, cmap.colormap) + colormap = cmap.colormap + else + cgradient = Observable{Any}() + map_is_categorical = Observable(false) + colormap = cb.colormap + map!(blockscene, cgradient, cb.colormap) do cmap + if cmap isa PlotUtils.CategoricalColorGradient + map_is_categorical[] = true + end + if cmap isa PlotUtils.ColorGradient + map_is_categorical[] = false + return cmap + end + map_is_categorical[] = false + return cgrad(to_colormap(cmap)) end end - function isvisible(x, compare) + function isvisible(x, cmap, start_end) x isa Automatic && return false x isa Nothing && return false + compare = start_end ? cmap[begin] : cmap[end] c = to_color(x) alpha(c) <= 0.0 && return false return c != compare end - lowclip_tri_visible = lift(isvisible, blockscene, cb.lowclip, lift(x-> get(x, 0), blockscene, cgradient)) - highclip_tri_visible = lift(isvisible, blockscene, cb.highclip, lift(x-> get(x, 1), blockscene, cgradient)) + lowclip_tri_visible = lift(isvisible, blockscene, cb.lowclip, cgradient, true) + highclip_tri_visible = lift(isvisible, blockscene, cb.highclip, cgradient, false) tri_heights = lift(blockscene, highclip_tri_visible, lowclip_tri_visible, framebox) do hv, lv, box if cb.vertical[] @@ -221,16 +216,6 @@ function initialize_block!(cb::Colorbar) end - map_is_categorical = lift(x -> x isa PlotUtils.CategoricalColorGradient, blockscene, cgradient) - - steps = lift(blockscene, cgradient, cb.nsteps, cb.scale) do cgradient, n, scale - s = if cgradient isa PlotUtils.CategoricalColorGradient - cgradient.values - else - collect(colorbar_range(0, 1, n, scale)) - end::Vector{Float64} - end - # it's hard to draw categorical and continous colormaps well # with the same primitives @@ -239,36 +224,33 @@ function initialize_block!(cb::Colorbar) # at the same time, then we just set one of them invisible # depending on the type of colormap # this should solve most white-line issues - + colors = lift(blockscene, cgradient, cb.nsteps, cb.scale) do cgradient, n, scale + if cgradient isa ColorMap && cgradient.categorical[] + return sort!(unique(convert(Vector{Float64}, cgradient.color[]))) + else + return collect(Makie.colorbar_range(0, 1, n, scale)) + end::Vector{Float64} + end # for categorical colormaps we make a number of rectangle polys - rects_and_colors = lift(blockscene, barbox, cb.vertical, steps, cgradient, cb.scale, - limits) do bbox, v, steps, gradient, scale, lims - - # we need to convert the 0 to 1 steps into rescaled 0 to 1 steps given the - # colormap's `scale` attribute - - s_scaled = scaled_steps(steps, scale, lims) - + n_colors = lift(length, colors; ignore_equal_values=true) + categorical_rects = lift(blockscene, barbox, cb.vertical, n_colors) do bbox, v, n + r = range(0, length=n, step=1/n) xmin, ymin = minimum(bbox) xmax, ymax = maximum(bbox) - - rects = if v - yvals = s_scaled .* (ymax - ymin) .+ ymin - [BBox(xmin, xmax, b, t) - for (b, t) in zip(yvals[1:end-1], yvals[2:end])] + xwidth = (xmax - xmin) + ywidth = (ymax - ymin) + if v + return map(r) do s + return Rect2f(xmin, ymin + (s * ywidth), xmax - xmin, (1/n) * ywidth) + end else - xvals = s_scaled .* (xmax - xmin) .+ xmin - [BBox(l, r, ymin, ymax) - for (l, r) in zip(xvals[1:end-1], xvals[2:end])] + return map(r) do s + return Rect2f(xmin + (s * xwidth), ymin, (1 / n) * xwidth, ymax - ymin) + end end - - colors = get.(Ref(gradient), (steps[1:end-1] .+ steps[2:end]) ./2) - rects, colors end - colors = lift(x -> getindex(x, 2), blockscene, rects_and_colors) - rects = poly!(blockscene, - lift(x -> getindex(x, 1), blockscene, rects_and_colors); + poly!(blockscene, categorical_rects; color = colors, visible = map_is_categorical, inspectable = false @@ -277,24 +259,21 @@ function initialize_block!(cb::Colorbar) # for continous colormaps we sample a 1d image # to avoid white lines when rendering vector graphics - continous_pixels = lift(blockscene, cb.vertical, cb.nsteps, cgradient, limits, - cb.scale) do v, n, grad, lims, scale - - s_steps = scaled_steps(colorbar_range(0, 1, n, scale), scale, lims) - px = get.(Ref(grad), s_steps) - return v ? reshape(px, 1, n) : reshape(px, n, 1) + continous_pixels = lift(blockscene, cb.vertical, colors) do v, colors + n = length(colors) + return v ? reshape(colors, 1, n) : reshape(colors, n, 1) end - cont_image = image!(blockscene, + image!(blockscene, lift(bb -> range(left(bb), right(bb); length=2), blockscene, barbox), lift(bb -> range(bottom(bb), top(bb); length=2), blockscene, barbox), - continous_pixels, + lift((x, s)-> s.(x), continous_pixels, cb.scale), + colormap = colormap, visible=lift(!, blockscene, map_is_categorical), interpolate = true, inspectable = false ) - highclip_tri = lift(blockscene, barbox, cb.spinewidth) do box, spinewidth if cb.vertical[] lb, rb = topline(box) @@ -313,7 +292,7 @@ function initialize_block!(cb::Colorbar) to_color(hc isa Automatic || isnothing(hc) ? :transparent : hc) end - highclip_tri_poly = poly!(blockscene, highclip_tri, color = highclip_tri_color, + poly!(blockscene, highclip_tri, color = highclip_tri_color, strokecolor = :transparent, visible = highclip_tri_visible, inspectable = false) @@ -335,7 +314,7 @@ function initialize_block!(cb::Colorbar) to_color(lc isa Automatic || isnothing(lc) ? :transparent : lc) end - lowclip_tri_poly = poly!(blockscene, lowclip_tri, color = lowclip_tri_color, + poly!(blockscene, lowclip_tri, color = lowclip_tri_color, strokecolor = :transparent, visible = lowclip_tri_visible, inspectable = false) @@ -385,12 +364,34 @@ function initialize_block!(cb::Colorbar) end end - + ticks = lift(cb.ticks, colors, map_is_categorical) do ticks, colors, categorical + if categorical + if ticks isa Automatic + return (1:length(colors), string.(colors)) + else + return ticks + end + else + return ticks + end + end + limits = lift(blockscene, cb.limits, cb.colorrange, map_is_categorical, n_colors) do limits, colorrange, categorical, n + if all(!isnothing, (limits, colorrange)) + error("Both colorrange + limits are set, please only set one, they're aliases. colorrange: $(colorrange), limits: $(limits)") + end + lims = something(limits, colorrange, (0, 1)) + if categorical + return (0.5, n + 0.5) + else + return lims + end + end axis = LineAxis(blockscene, endpoints = axispoints, flipped = cb.flipaxis, - limits = limits, ticklabelalign = cb.ticklabelalign, label = cb.label, + limits=limits, ticklabelalign=cb.ticklabelalign, label=cb.label, labelpadding = cb.labelpadding, labelvisible = cb.labelvisible, labelsize = cb.labelsize, labelcolor = cb.labelcolor, labelrotation = cb.labelrotation, - labelfont = cb.labelfont, ticklabelfont = cb.ticklabelfont, ticks = cb.ticks, tickformat = cb.tickformat, + labelfont=cb.labelfont, ticklabelfont=cb.ticklabelfont, ticks=ticks, + tickformat=cb.tickformat, ticklabelsize = cb.ticklabelsize, ticklabelsvisible = cb.ticklabelsvisible, ticksize = cb.ticksize, ticksvisible = cb.ticksvisible, ticklabelpad = cb.ticklabelpad, tickalign = cb.tickalign, ticklabelrotation = cb.ticklabelrotation, @@ -407,7 +408,6 @@ function initialize_block!(cb::Colorbar) onany(blockscene, axis.protrusion, cb.vertical, cb.flipaxis) do axprotrusion, vertical, flipaxis - left, right, top, bottom = 0f0, 0f0, 0f0, 0f0 if vertical @@ -453,5 +453,5 @@ function scaled_steps(steps, scale, lims) # scale with scaling function s_limits_scaled = scale.(s_limits) # then rescale to 0 to 1 - s_scaled = (s_limits_scaled .- s_limits_scaled[1]) ./ (s_limits_scaled[end] - s_limits_scaled[1]) + return (s_limits_scaled .- s_limits_scaled[1]) ./ (s_limits_scaled[end] - s_limits_scaled[1]) end diff --git a/src/makielayout/blocks/legend.jl b/src/makielayout/blocks/legend.jl index b32874043a0..f35e6cfd628 100644 --- a/src/makielayout/blocks/legend.jl +++ b/src/makielayout/blocks/legend.jl @@ -390,36 +390,28 @@ function _rename_attributes!(T, a) a end - -function scalar_lift(plot, attr, default) - observable = Observable{Any}() - map!(plot, observable, attr, default) do at, def - Makie.is_scalar_attribute(at) ? at : def - end - return observable -end +choose_scalar(attr, default) = is_scalar_attribute(to_value(attr)) ? attr : default function extract_color(@nospecialize(plot), color_default) - color = extract_color_recursive(plot) - color isa Dict && return color_default - return Makie.is_scalar_attribute(color[]) ? color_default : color + color = haskey(plot, :calculated_color) ? plot.calculated_color : plot.color + color[] isa ColorMap && return color_default + return choose_scalar(color, color_default) end function legendelements(plot::Union{Lines, LineSegments}, legend) LegendElement[LineElement( color = extract_color(plot, legend.linecolor), - linestyle = scalar_lift(plot, plot.linestyle, legend.linestyle), - linewidth = scalar_lift(plot, plot.linewidth, legend.linewidth))] + linestyle = choose_scalar(plot.linestyle, legend.linestyle), + linewidth = choose_scalar(plot.linewidth, legend.linewidth))] end - function legendelements(plot::Scatter, legend) LegendElement[MarkerElement( color = extract_color(plot, legend.markercolor), - marker = scalar_lift(plot, plot.marker, legend.marker), - markersize = scalar_lift(plot, plot.markersize, legend.markersize), - strokewidth = scalar_lift(plot, plot.strokewidth, legend.markerstrokewidth), - strokecolor = scalar_lift(plot, plot.strokecolor, legend.markerstrokecolor), + marker = choose_scalar(plot.marker, legend.marker), + markersize = choose_scalar(plot.markersize, legend.markersize), + strokewidth = choose_scalar(plot.strokewidth, legend.markerstrokewidth), + strokecolor = choose_scalar(plot.strokecolor, legend.markerstrokecolor), )] end @@ -427,14 +419,14 @@ function legendelements(plot::Union{Poly, Violin, BoxPlot, CrossBar, Density}, l color = extract_color(plot, legend.polycolor) LegendElement[PolyElement( color = color, - strokecolor = scalar_lift(plot, plot.strokecolor, legend.polystrokecolor), - strokewidth = scalar_lift(plot, plot.strokewidth, legend.polystrokewidth), + strokecolor = choose_scalar(plot.strokecolor, legend.polystrokecolor), + strokewidth = choose_scalar(plot.strokewidth, legend.polystrokewidth), )] end function legendelements(plot::Band, legend) # there seems to be no stroke for Band, so we set it invisible - LegendElement[PolyElement(polycolor = scalar_lift(plot, plot.color, legend.polystrokecolor), polystrokecolor = :transparent, polystrokewidth = 0)] + LegendElement[PolyElement(polycolor = choose_scalar(plot.color, legend.polystrokecolor), polystrokecolor = :transparent, polystrokewidth = 0)] end # if there is no specific overload available, we go through the child plots and just stack diff --git a/src/utilities/utilities.jl b/src/utilities/utilities.jl index fa75c6c1c15..564b1a53418 100644 --- a/src/utilities/utilities.jl +++ b/src/utilities/utilities.jl @@ -347,53 +347,3 @@ end # Scalar - Vector getindex sv_getindex(v::Vector, i::Integer) = v[i] sv_getindex(x, i::Integer) = x - - -function extract_color_recursive(@nospecialize(plot)) - color = extract_color(attributes(plot)) - if color === nothing && !is_atomic_plot(plot) - extracted_colors = unique(map(extract_color, collect_atomic_plots(plot))) - if length(extracted_colors) == 1 - return extracted_colors[1] - elseif !isempty(extracted_colors) - if any(x-> x isa Dict, extracted_colors) - filter!(x-> x isa Dict, extracted_colors) - return only(extracted_colors) - end - else - error("Multiple or no colors found: $(map(typeof, extracted_colors))") - end - end - return color -end - -function extract_color(attributes) - if haskey(attributes, :calculated_colors) - color = attributes[:calculated_colors] - if color[] isa ColorMap - result = Dict{Symbol, Any}() - result[:colorrange] = color[].colorrange - result[:colormap] = color[].colormap - result[:highclip] = color[].highclip - result[:lowclip] = color[].lowclip - return result - end - return color - else - if haskey(attributes, :colormap) && haskey(attributes, :colorrange) && !(attributes.colorrange[] isa Automatic) - if haskey(attributes, :color) && attributes[:color][] isa Union{Number,Cycled} - return nothing - end - result = Dict{Symbol, Any}() - result[:colorrange] = attributes[:colorrange] - result[:colormap] = attributes[:colormap] - haskey(attributes, :highclip) && (result[:highclip] = attributes[:highclip]) - haskey(attributes, :lowclip) && (result[:lowclip] = attributes[:lowclip]) - return result - end - end - if haskey(attributes, :color) && attributes[:color][] isa Union{Colorant, AbstractArray{<: Colorant}} - return attributes[:color] - end - return nothing -end From ef7b50330a4aa8b12fd2e77d838261b953597894 Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Fri, 25 Aug 2023 16:14:10 +0200 Subject: [PATCH 09/23] correctly get cgrad values --- src/makielayout/blocks/colorbar.jl | 61 ++++++++++++++++++------------ 1 file changed, 37 insertions(+), 24 deletions(-) diff --git a/src/makielayout/blocks/colorbar.jl b/src/makielayout/blocks/colorbar.jl index eb5b8c8aa45..04ec1fad45e 100644 --- a/src/makielayout/blocks/colorbar.jl +++ b/src/makielayout/blocks/colorbar.jl @@ -144,7 +144,6 @@ end function initialize_block!(cb::Colorbar) blockscene = cb.blockscene - onany(blockscene, cb.size, cb.vertical) do sz, vertical if vertical cb.layoutobservables.autosize[] = (sz, nothing) @@ -154,7 +153,6 @@ function initialize_block!(cb::Colorbar) end framebox = lift(round_to_IRect2D, blockscene, cb.layoutobservables.computedbbox) - # TODO, always convert to ColorMap! if cb.colormap[] isa ColorMap cmap = cb.colormap[] @@ -164,13 +162,14 @@ function initialize_block!(cb::Colorbar) else cgradient = Observable{Any}() map_is_categorical = Observable(false) - colormap = cb.colormap + colormap = cgradient map!(blockscene, cgradient, cb.colormap) do cmap if cmap isa PlotUtils.CategoricalColorGradient map_is_categorical[] = true + else + map_is_categorical[] = false end if cmap isa PlotUtils.ColorGradient - map_is_categorical[] = false return cmap end map_is_categorical[] = false @@ -178,6 +177,13 @@ function initialize_block!(cb::Colorbar) end end + _limits = lift(blockscene, cb.limits, cb.colorrange; ignore_equal_values=true) do limits, colorrange + if all(!isnothing, (limits, colorrange)) + error("Both colorrange + limits are set, please only set one, they're aliases. colorrange: $(colorrange), limits: $(limits)") + end + return something(limits, colorrange, (0, 1)) + end + function isvisible(x, cmap, start_end) x isa Automatic && return false x isa Nothing && return false @@ -187,8 +193,8 @@ function initialize_block!(cb::Colorbar) return c != compare end - lowclip_tri_visible = lift(isvisible, blockscene, cb.lowclip, cgradient, true) - highclip_tri_visible = lift(isvisible, blockscene, cb.highclip, cgradient, false) + lowclip_tri_visible = lift(isvisible, blockscene, cb.lowclip, colormap, true) + highclip_tri_visible = lift(isvisible, blockscene, cb.highclip, colormap, false) tri_heights = lift(blockscene, highclip_tri_visible, lowclip_tri_visible, framebox) do hv, lv, box if cb.vertical[] @@ -224,15 +230,32 @@ function initialize_block!(cb::Colorbar) # at the same time, then we just set one of them invisible # depending on the type of colormap # this should solve most white-line issues - colors = lift(blockscene, cgradient, cb.nsteps, cb.scale) do cgradient, n, scale + colors = lift(blockscene, cgradient, cb.nsteps, cb.scale, _limits) do cgradient, n, scale, limits if cgradient isa ColorMap && cgradient.categorical[] return sort!(unique(convert(Vector{Float64}, cgradient.color[]))) + elseif cgradient isa PlotUtils.CategoricalColorGradient + mini, maxi = limits + return map(2:length(cgradient.values)) do i + prev = cgradient.values[i - 1] + v = cgradient.values[i] + return round(((prev + v) / 2) .* (maxi - mini) .+ mini; digits=3) + end else return collect(Makie.colorbar_range(0, 1, n, scale)) end::Vector{Float64} end # for categorical colormaps we make a number of rectangle polys n_colors = lift(length, colors; ignore_equal_values=true) + + + limits = lift(blockscene, _limits, map_is_categorical, n_colors) do limits, categorical, n + if categorical + return (0.5, n + 0.5) + else + return limits + end + end + categorical_rects = lift(blockscene, barbox, cb.vertical, n_colors) do bbox, v, n r = range(0, length=n, step=1/n) xmin, ymin = minimum(bbox) @@ -252,6 +275,7 @@ function initialize_block!(cb::Colorbar) poly!(blockscene, categorical_rects; color = colors, + colormap = lift(to_colormap, colormap), visible = map_is_categorical, inspectable = false ) @@ -263,7 +287,6 @@ function initialize_block!(cb::Colorbar) n = length(colors) return v ? reshape(colors, 1, n) : reshape(colors, n, 1) end - image!(blockscene, lift(bb -> range(left(bb), right(bb); length=2), blockscene, barbox), lift(bb -> range(bottom(bb), top(bb); length=2), blockscene, barbox), @@ -366,26 +389,16 @@ function initialize_block!(cb::Colorbar) end ticks = lift(cb.ticks, colors, map_is_categorical) do ticks, colors, categorical if categorical - if ticks isa Automatic - return (1:length(colors), string.(colors)) - else - return ticks - end + return (1:length(colors), string.(colors)) + # if ticks isa Automatic + # else + # return ticks + # end else return ticks end end - limits = lift(blockscene, cb.limits, cb.colorrange, map_is_categorical, n_colors) do limits, colorrange, categorical, n - if all(!isnothing, (limits, colorrange)) - error("Both colorrange + limits are set, please only set one, they're aliases. colorrange: $(colorrange), limits: $(limits)") - end - lims = something(limits, colorrange, (0, 1)) - if categorical - return (0.5, n + 0.5) - else - return lims - end - end + axis = LineAxis(blockscene, endpoints = axispoints, flipped = cb.flipaxis, limits=limits, ticklabelalign=cb.ticklabelalign, label=cb.label, labelpadding = cb.labelpadding, labelvisible = cb.labelvisible, labelsize = cb.labelsize, From af4857d9dcdd0efcdd2050cd5aedb725f8d92c01 Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Wed, 6 Sep 2023 10:56:40 +0200 Subject: [PATCH 10/23] always use ColorMap --- src/basic_recipes/contourf.jl | 15 +- src/colorsampler.jl | 83 ++++++----- src/makielayout/blocks/colorbar.jl | 224 +++++++++++------------------ 3 files changed, 136 insertions(+), 186 deletions(-) diff --git a/src/basic_recipes/contourf.jl b/src/basic_recipes/contourf.jl index 4da445fc29f..889c06a47d3 100644 --- a/src/basic_recipes/contourf.jl +++ b/src/basic_recipes/contourf.jl @@ -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] @@ -161,9 +158,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] diff --git a/src/colorsampler.jl b/src/colorsampler.jl index 2f8c14bccfe..c4505cd02ee 100644 --- a/src/colorsampler.jl +++ b/src/colorsampler.jl @@ -176,6 +176,8 @@ function numbers_to_colors(numbers::Union{AbstractArray{<:Number, N},Number}, end end +@enum ColorValuePosition color_edge color_center + struct ColorMap{N,T<:AbstractArray{<:Number,N},T2<:AbstractArray{<:Number,N}} color::Observable{T} colormap::Observable{Vector{RGBAf}} @@ -188,70 +190,87 @@ struct ColorMap{N,T<:AbstractArray{<:Number,N},T2<:AbstractArray{<:Number,N}} nan_color::Observable{RGBAf} categorical::Observable{Bool} + value_position::ColorValuePosition # 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) +_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 + + +function ColorMap(color::T, colors_obs, colormap, colorrange, colorscale, alpha, lowclip, + highclip, nan_color, + value_position=automatic) where {N, T<:AbstractArray{<:Number, N}} + + color_tight = convert(Observable{_array_value_type(color)}, colors_obs) + _colormap = Observable(RGBAf[]; ignore_equal_values=true) categorical = Observable(false) - colorscale = convert(Observable{Function}, plot.colorscale) - mapping = Observable{Union{Nothing, Vector{Float64}}}(nothing) + colorscale = convert(Observable{Function}, colorscale) + mapping = Observable{Union{Nothing,Vector{Float64}}}(nothing) + + if !(value_position isa ColorValuePosition) + value_position = colormap[] isa PlotUtils.CategoricalColorGradient ? color_edge : color_center + end function update_colors(cmap, a) colors = to_colormap(cmap) 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) end - colormap[] = colors + _colormap[] = colors categorical[] = cmap isa PlotUtils.CategoricalColorGradient - if colormap isa PlotUtils.ColorGradient + 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[])}( - color_tight, - colormap, - colorscale, - mapping, - colorrange, - lowclip, - highclip, - nan_color, - categorical, - colorrange_scaled, - color_scaled - ) + CT = ColorMap{N,T,typeof(color_scaled[])} + return CT(color_tight, + _colormap, + colorscale, + mapping, + colorrange, + _lowclip, + _highclip, + lift(to_color, nan_color), + categorical, + value_position, + colorrange_scaled, + color_scaled) +end + +function assemble_colors(c::AbstractArray{<:Number}, @nospecialize(color), @nospecialize(plot)) + return ColorMap(c, color, plot.colormap, plot.colorrange, plot.colorscale, plot.alpha, plot.lowclip, + plot.highclip, plot.nan_color) end function to_color(c::ColorMap) diff --git a/src/makielayout/blocks/colorbar.jl b/src/makielayout/blocks/colorbar.jl index 04ec1fad45e..5606ad3a74d 100644 --- a/src/makielayout/blocks/colorbar.jl +++ b/src/makielayout/blocks/colorbar.jl @@ -25,20 +25,30 @@ function colorbar_check(keys, kwargs_keys) end end -function extract_colormap(@nospecialize(plot::T)) where {T <: AbstractPlot} +function extract_colormap(@nospecialize(plot::AbstractPlot)) + has_colorrange = haskey(plot, :colorrange) && !(plot.colorrange[] isa Makie.Automatic) if haskey(plot, :calculated_colors) && plot.calculated_colors[] isa Makie.ColorMap return plot.calculated_colors[] - elseif all(x -> haskey(plot, x), [:colormap, :colorrange, :color]) && - !(plot.colorrange[] isa Makie.Automatic) - result = Dict{Symbol,Any}() - for key in [:colormap, :colorrange, :highclip, :lowclip, :colorscale, :color] - if haskey(plot, key) - result[key] = plot[key] - end - end - return result + elseif has_colorrange && all(x -> haskey(plot, x), [:colormap, :colorrange, :color]) + return ColorMap( + plot.color[], plot.color, plot.colormap, plot.colorrange, + get(plot, :colorscale, Observable(identity)), + get(plot, :alpha, Observable(1.0)), + get(plot, :highclip, Observable(automatic)), + get(plot, :lowclip, Observable(automatic)), + get(plot, :nan_color, Observable(RGBAf(0,0,0,0))), + ) + else + return nothing + end +end + +function extract_colormap_recursive(@nospecialize(plot::T)) where {T <: AbstractPlot} + cmap = extract_colormap(plot) + if !isnothing(cmap) + return cmap else - colormaps = [extract_colormap(child) for child in plot.plots] + colormaps = [extract_colormap_recursive(child) for child in plot.plots] if length(colormaps) == 1 return colormaps[1] elseif isempty(colormaps) @@ -54,26 +64,14 @@ end function Colorbar(fig_or_scene, plot::AbstractPlot; kwargs...) colorbar_check((:colormap, :limits, :highclip, :lowclip), keys(kwargs)) - cmap = extract_colormap(plot) + cmap = extract_colormap_recursive(plot) func = plotfunc(plot) if isnothing(cmap) error("Neither $(func) nor any of its children use a colormap. Cannot create a Colorbar from this plot, please create it manually. If this is a recipe, one needs to overload `Makie.extract_colormap(::$(Combined{func}))` to allow for the automatical creation of a Colorbar.") end - if cmap isa ColorMap - scale = cmap.scale - colormap = cmap - elseif cmap isa Dict - colormap = cmap[:colormap] - cmap = ( - colorrange = cmap[:colorrange], - highclip = get(cmap, :highclip, automatic), - lowclip = get(cmap, :lowclip, automatic), - color = cmap[:color] - ) - scale = plot[:colorscale] - else - error("extract_colormap(::$(Combined{func})) returned an invalid value: $cmap. Needs to return either a `Dict` or `Makie.ColorMap`.") + if !(cmap isa ColorMap) + error("extract_colormap(::$(Combined{func})) returned an invalid value: $cmap. Needs to return either a `Makie.ColorMap`.") end if to_value(cmap.color) isa Union{AbstractVector{<: Colorant}, Colorant} @@ -82,58 +80,19 @@ function Colorbar(fig_or_scene, plot::AbstractPlot; kwargs...) """) end - Colorbar( + return Colorbar( fig_or_scene; - colormap=colormap, - limits=cmap.colorrange, - scale=scale, - highclip=cmap.highclip, - lowclip=cmap.lowclip, + colormap=cmap, kwargs... ) end -function Colorbar(fig_or_scene, contourf::Union{Contourf, Tricontourf}; kwargs...) - colorbar_check((:colormap, :limits, :highclip, :lowclip), keys(kwargs)) - - steps = contourf._computed_levels - - limits = lift(steps) do steps - steps[1], steps[end] - end - - Colorbar( - fig_or_scene; - colormap = contourf._computed_colormap, - limits = limits, - lowclip = contourf._computed_extendlow, - highclip = contourf._computed_extendhigh, - scale = contourf.colorscale, - kwargs... - ) -end - -function Colorbar(fig_or_scene, voronoi::Voronoiplot; kwargs...) - colorbar_check((:colormap, :limits, :highclip, :lowclip), keys(kwargs)) - - # switch to lowered version - if voronoi.plots[1] isa Voronoiplot - voronoi = voronoi.plots[1] - end - - range = map(voronoi._calculated_colors, voronoi.colorrange) do cs, cr - cr === automatic ? extrema(cs) : cr - end - - Colorbar( - fig_or_scene; - colormap=voronoi.colormap, - limits=range, - scale=voronoi.colorscale, - highclip=voronoi.highclip, - lowclip=voronoi.lowclip, - kwargs... - ) +function extract_colormap(plot::Union{Contourf,Tricontourf}) + levels = plot._computed_levels + limits = lift(l-> (l[1], l[end]), levels) + return ColorMap(levels[], levels, plot._computed_colormap, limits, plot.colorscale, Observable(1.0), + plot._computed_extendlow, + plot._computed_extendhigh, plot.nan_color, color_edge) end colorbar_range(start, stop, length, _) = LinRange(start, stop, length) # noop @@ -156,72 +115,58 @@ function initialize_block!(cb::Colorbar) # TODO, always convert to ColorMap! if cb.colormap[] isa ColorMap cmap = cb.colormap[] - map_is_categorical = cmap.categorical - cgradient = lift(x-> cmap, cmap.colormap) - colormap = cmap.colormap else - cgradient = Observable{Any}() - map_is_categorical = Observable(false) - colormap = cgradient - map!(blockscene, cgradient, cb.colormap) do cmap - if cmap isa PlotUtils.CategoricalColorGradient - map_is_categorical[] = true - else - map_is_categorical[] = false + limits = lift(blockscene, cb.limits, cb.colorrange) do limits, colorrange + if all(!isnothing, (limits, colorrange)) + error("Both colorrange + limits are set, please only set one, they're aliases. colorrange: $(colorrange), limits: $(limits)") end - if cmap isa PlotUtils.ColorGradient - return cmap - end - map_is_categorical[] = false - return cgrad(to_colormap(cmap)) + return something(limits, colorrange, (0, 1)) end - end - - _limits = lift(blockscene, cb.limits, cb.colorrange; ignore_equal_values=true) do limits, colorrange - if all(!isnothing, (limits, colorrange)) - error("Both colorrange + limits are set, please only set one, they're aliases. colorrange: $(colorrange), limits: $(limits)") + c = cb.colormap + color = if c[] isa PlotUtils.ColorGradient + lift((c, l) -> l[1] .+ c.values .* (l[end] - l[1]), c, limits) + else + lift((n, s) -> collect(Makie.colorbar_range(0, 1, n, s)), cb.nsteps, cb.scale) end - return something(limits, colorrange, (0, 1)) - end - - function isvisible(x, cmap, start_end) - x isa Automatic && return false - x isa Nothing && return false - compare = start_end ? cmap[begin] : cmap[end] - c = to_color(x) - alpha(c) <= 0.0 && return false - return c != compare + cmap = ColorMap(color[], color, cb.colormap, limits, cb.scale, Observable(1.0), cb.lowclip, + cb.highclip, Observable(RGBAf(0,0,0,0))) end - lowclip_tri_visible = lift(isvisible, blockscene, cb.lowclip, colormap, true) - highclip_tri_visible = lift(isvisible, blockscene, cb.highclip, colormap, false) - tri_heights = lift(blockscene, highclip_tri_visible, lowclip_tri_visible, framebox) do hv, lv, box - if cb.vertical[] - (lv * width(box), hv * width(box)) + map_is_categorical = cmap.categorical + colormap = cmap.colormap + _limits = cmap.colorrange + colors = lift(blockscene, cmap.color, cmap.categorical, cb.nsteps, cb.scale) do values, categorical, n, scale + if categorical + if cmap.value_position == color_center + return sort!(unique(convert(Vector{Float64}, values))) + else + return convert(Vector{Float64}, values) + end else - (lv * height(box), hv * height(box)) - end .* sin(pi/3) + return collect(Makie.colorbar_range(0, 1, n, scale)) + end::Vector{Float64} end - barsize = lift(blockscene, tri_heights) do heights + lowclip_tri_visible = lift(x -> !(x isa Automatic), blockscene, cmap.lowclip; ignore_equal_values=true) + highclip_tri_visible = lift(x -> !(x isa Automatic), blockscene, cmap.highclip; ignore_equal_values=true) + + tri_heights = lift(blockscene, highclip_tri_visible, lowclip_tri_visible, framebox; ignore_equal_values=true) do hv, lv, box if cb.vertical[] - max(1, height(framebox[]) - sum(heights)) + return (lv * width(box), hv * width(box)) else - max(1, width(framebox[]) - sum(heights)) - end + return (lv * height(box), hv * height(box)) + end .* sin(pi / 3) end - barbox = lift(blockscene, barsize) do sz - fbox = framebox[] + barbox = lift(blockscene, framebox; ignore_equal_values=true) do fbox if cb.vertical[] - BBox(left(fbox), right(fbox), bottom(fbox) + tri_heights[][1], top(fbox) - tri_heights[][2]) + return BBox(left(fbox), right(fbox), bottom(fbox) + tri_heights[][1], top(fbox) - tri_heights[][2]) else - BBox(left(fbox) + tri_heights[][1], right(fbox) - tri_heights[][2], bottom(fbox), top(fbox)) + return BBox(left(fbox) + tri_heights[][1], right(fbox) - tri_heights[][2], bottom(fbox), top(fbox)) end end - # it's hard to draw categorical and continous colormaps well # with the same primitives @@ -230,27 +175,18 @@ function initialize_block!(cb::Colorbar) # at the same time, then we just set one of them invisible # depending on the type of colormap # this should solve most white-line issues - colors = lift(blockscene, cgradient, cb.nsteps, cb.scale, _limits) do cgradient, n, scale, limits - if cgradient isa ColorMap && cgradient.categorical[] - return sort!(unique(convert(Vector{Float64}, cgradient.color[]))) - elseif cgradient isa PlotUtils.CategoricalColorGradient - mini, maxi = limits - return map(2:length(cgradient.values)) do i - prev = cgradient.values[i - 1] - v = cgradient.values[i] - return round(((prev + v) / 2) .* (maxi - mini) .+ mini; digits=3) - end - else - return collect(Makie.colorbar_range(0, 1, n, scale)) - end::Vector{Float64} - end + + # for categorical colormaps we make a number of rectangle polys n_colors = lift(length, colors; ignore_equal_values=true) - limits = lift(blockscene, _limits, map_is_categorical, n_colors) do limits, categorical, n if categorical - return (0.5, n + 0.5) + if cmap.value_position == color_center + return (0.5, n + 0.5) + else + (0, n - 1) + end else return limits end @@ -262,20 +198,22 @@ function initialize_block!(cb::Colorbar) xmax, ymax = maximum(bbox) xwidth = (xmax - xmin) ywidth = (ymax - ymin) + yw_scaled = (1 / n) * ywidth + xw_scaled = (1 / n) * xwidth if v return map(r) do s - return Rect2f(xmin, ymin + (s * ywidth), xmax - xmin, (1/n) * ywidth) + return Rect2f(xmin, ymin + (s * ywidth), xwidth, yw_scaled) end else return map(r) do s - return Rect2f(xmin + (s * xwidth), ymin, (1 / n) * xwidth, ymax - ymin) + return Rect2f(xmin + (s * xwidth), ymin, xw_scaled, ywidth) end end end poly!(blockscene, categorical_rects; color = colors, - colormap = lift(to_colormap, colormap), + colormap = colormap, visible = map_is_categorical, inspectable = false ) @@ -287,12 +225,13 @@ function initialize_block!(cb::Colorbar) n = length(colors) return v ? reshape(colors, 1, n) : reshape(colors, n, 1) end + image!(blockscene, lift(bb -> range(left(bb), right(bb); length=2), blockscene, barbox), lift(bb -> range(bottom(bb), top(bb); length=2), blockscene, barbox), lift((x, s)-> s.(x), continous_pixels, cb.scale), colormap = colormap, - visible=lift(!, blockscene, map_is_categorical), + visible= true,#lift(!, blockscene, map_is_categorical), interpolate = true, inspectable = false ) @@ -390,10 +329,6 @@ function initialize_block!(cb::Colorbar) ticks = lift(cb.ticks, colors, map_is_categorical) do ticks, colors, categorical if categorical return (1:length(colors), string.(colors)) - # if ticks isa Automatic - # else - # return ticks - # end else return ticks end @@ -445,6 +380,7 @@ function initialize_block!(cb::Colorbar) # trigger bbox notify(cb.layoutobservables.suggestedbbox) + notify(barbox) return end From 765d0583031b34ec502bedb82168e6d3ba8123e8 Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Thu, 7 Sep 2023 18:20:11 +0200 Subject: [PATCH 11/23] fix most cases?! --- src/basic_recipes/contourf.jl | 1 - src/colorsampler.jl | 8 +-- src/makielayout/blocks/colorbar.jl | 109 ++++++++--------------------- 3 files changed, 34 insertions(+), 84 deletions(-) diff --git a/src/basic_recipes/contourf.jl b/src/basic_recipes/contourf.jl index 889c06a47d3..d2e0d7a8f55 100644 --- a/src/basic_recipes/contourf.jl +++ b/src/basic_recipes/contourf.jl @@ -90,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[]) diff --git a/src/colorsampler.jl b/src/colorsampler.jl index c4505cd02ee..ded923f64f1 100644 --- a/src/colorsampler.jl +++ b/src/colorsampler.jl @@ -201,11 +201,11 @@ _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 -function ColorMap(color::T, colors_obs, colormap, colorrange, colorscale, alpha, lowclip, +function ColorMap(color::AbstractArray{<:Number, N}, colors_obs, colormap, colorrange, colorscale, alpha, lowclip, highclip, nan_color, - value_position=automatic) where {N, T<:AbstractArray{<:Number, N}} - - color_tight = convert(Observable{_array_value_type(color)}, colors_obs) + value_position=automatic) where {N} + T = _array_value_type(color) + color_tight = convert(Observable{T}, colors_obs) _colormap = Observable(RGBAf[]; ignore_equal_values=true) categorical = Observable(false) colorscale = convert(Observable{Function}, colorscale) diff --git a/src/makielayout/blocks/colorbar.jl b/src/makielayout/blocks/colorbar.jl index 5606ad3a74d..5000ac3995b 100644 --- a/src/makielayout/blocks/colorbar.jl +++ b/src/makielayout/blocks/colorbar.jl @@ -91,8 +91,8 @@ function extract_colormap(plot::Union{Contourf,Tricontourf}) levels = plot._computed_levels limits = lift(l-> (l[1], l[end]), levels) return ColorMap(levels[], levels, plot._computed_colormap, limits, plot.colorscale, Observable(1.0), - plot._computed_extendlow, - plot._computed_extendhigh, plot.nan_color, color_edge) + plot.extendlow, + plot.extendhigh, plot.nan_color, color_edge) end colorbar_range(start, stop, length, _) = LinRange(start, stop, length) # noop @@ -100,7 +100,7 @@ function colorbar_range(start, stop, length, scale::REVERSIBLE_SCALES) inverse_transform(scale).(range(start, stop; length)) end -function initialize_block!(cb::Colorbar) +function initialize_block!(cb::Colorbar; categorical=false) blockscene = cb.blockscene onany(blockscene, cb.size, cb.vertical) do sz, vertical @@ -131,18 +131,17 @@ function initialize_block!(cb::Colorbar) cmap = ColorMap(color[], color, cb.colormap, limits, cb.scale, Observable(1.0), cb.lowclip, cb.highclip, Observable(RGBAf(0,0,0,0))) end - - map_is_categorical = cmap.categorical + is_real_cat = categorical || cmap.value_position == color_center colormap = cmap.colormap - _limits = cmap.colorrange - colors = lift(blockscene, cmap.color, cmap.categorical, cb.nsteps, cb.scale) do values, categorical, n, scale - if categorical - if cmap.value_position == color_center - return sort!(unique(convert(Vector{Float64}, values))) - else - return convert(Vector{Float64}, values) - end + limits = cmap.colorrange + + colors = lift(blockscene, cmap.color, cmap.categorical, cb.nsteps, + cmap.scale) do values, categorical, n, scale + if categorical && !is_real_cat + return convert(Vector{Float64}, unique(values)) + elseif categorical && is_real_cat + return 1:length(values) else return collect(Makie.colorbar_range(0, 1, n, scale)) end::Vector{Float64} @@ -167,72 +166,25 @@ function initialize_block!(cb::Colorbar) end end - # it's hard to draw categorical and continous colormaps well - # with the same primitives - - # therefore we make one interpolated image for continous - # colormaps and number of polys for categorical colormaps - # at the same time, then we just set one of them invisible - # depending on the type of colormap - # this should solve most white-line issues - - - # for categorical colormaps we make a number of rectangle polys - n_colors = lift(length, colors; ignore_equal_values=true) - - limits = lift(blockscene, _limits, map_is_categorical, n_colors) do limits, categorical, n - if categorical - if cmap.value_position == color_center - return (0.5, n + 0.5) - else - (0, n - 1) - end - else - return limits - end - end - - categorical_rects = lift(blockscene, barbox, cb.vertical, n_colors) do bbox, v, n - r = range(0, length=n, step=1/n) - xmin, ymin = minimum(bbox) - xmax, ymax = maximum(bbox) - xwidth = (xmax - xmin) - ywidth = (ymax - ymin) - yw_scaled = (1 / n) * ywidth - xw_scaled = (1 / n) * xwidth - if v - return map(r) do s - return Rect2f(xmin, ymin + (s * ywidth), xwidth, yw_scaled) - end - else - return map(r) do s - return Rect2f(xmin + (s * xwidth), ymin, xw_scaled, ywidth) - end - end - end - - poly!(blockscene, categorical_rects; - color = colors, - colormap = colormap, - visible = map_is_categorical, - inspectable = false - ) - # for continous colormaps we sample a 1d image # to avoid white lines when rendering vector graphics - continous_pixels = lift(blockscene, cb.vertical, colors) do v, colors + continous_pixels = lift(blockscene, cb.vertical, colors, map_is_categorical) do v, colors, iscat n = length(colors) + if iscat && !is_real_cat + colors = (colors[1:end-1] .+ colors[2:end]) ./2 + n = n - 1 + end return v ? reshape(colors, 1, n) : reshape(colors, n, 1) end image!(blockscene, lift(bb -> range(left(bb), right(bb); length=2), blockscene, barbox), lift(bb -> range(bottom(bb), top(bb); length=2), blockscene, barbox), - lift((x, s)-> s.(x), continous_pixels, cb.scale), + continous_pixels; colormap = colormap, - visible= true,#lift(!, blockscene, map_is_categorical), - interpolate = true, + colorscale = cmap.scale, + interpolate = lift(!, map_is_categorical), inspectable = false ) @@ -250,7 +202,7 @@ function initialize_block!(cb::Colorbar) end end - highclip_tri_color = lift(blockscene, cb.highclip) do hc + highclip_tri_color = lift(blockscene, cmap.highclip) do hc to_color(hc isa Automatic || isnothing(hc) ? :transparent : hc) end @@ -272,7 +224,7 @@ function initialize_block!(cb::Colorbar) end end - lowclip_tri_color = lift(blockscene, cb.lowclip) do lc + lowclip_tri_color = lift(blockscene, cmap.lowclip) do lc to_color(lc isa Automatic || isnothing(lc) ? :transparent : lc) end @@ -326,16 +278,16 @@ function initialize_block!(cb::Colorbar) end end - ticks = lift(cb.ticks, colors, map_is_categorical) do ticks, colors, categorical - if categorical - return (1:length(colors), string.(colors)) - else - return ticks - end + + ticks = lift(colors, map_is_categorical, cb.ticks) do cs, iscat, ticks + iscat && is_real_cat ? (1:length(cs), string.(cs)) : ticks + end + lims = lift(colors, map_is_categorical, limits) do cs, iscat, limits + return iscat && is_real_cat ? (0.5, length(cs) + 0.5) : limits end axis = LineAxis(blockscene, endpoints = axispoints, flipped = cb.flipaxis, - limits=limits, ticklabelalign=cb.ticklabelalign, label=cb.label, + limits=lims, ticklabelalign=cb.ticklabelalign, label=cb.label, labelpadding = cb.labelpadding, labelvisible = cb.labelvisible, labelsize = cb.labelsize, labelcolor = cb.labelcolor, labelrotation = cb.labelrotation, labelfont=cb.labelfont, ticklabelfont=cb.ticklabelfont, ticks=ticks, @@ -348,11 +300,10 @@ function initialize_block!(cb::Colorbar) spinecolor = :transparent, spinevisible = :false, flip_vertical_label = cb.flip_vertical_label, minorticksvisible = cb.minorticksvisible, minortickalign = cb.minortickalign, minorticksize = cb.minorticksize, minortickwidth = cb.minortickwidth, - minortickcolor = cb.minortickcolor, minorticks = cb.minorticks, scale = cb.scale) + minortickcolor = cb.minortickcolor, minorticks = cb.minorticks, scale = cmap.scale) cb.axis = axis - onany(blockscene, axis.protrusion, cb.vertical, cb.flipaxis) do axprotrusion, vertical, flipaxis From 5f13c6a25a048be51c0e5b5cc2a86ea0cbe1d38c Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Fri, 8 Sep 2023 18:43:26 +0200 Subject: [PATCH 12/23] fiiiixies!? --- docs/examples/blocks/colorbar.md | 3 +- src/colorsampler.jl | 8 +++++ src/makielayout/blocks/colorbar.jl | 50 +++++++++++++++++++++--------- 3 files changed, 45 insertions(+), 16 deletions(-) diff --git a/docs/examples/blocks/colorbar.md b/docs/examples/blocks/colorbar.md index b2fe0561463..c6a333be7b7 100644 --- a/docs/examples/blocks/colorbar.md +++ b/docs/examples/blocks/colorbar.md @@ -67,7 +67,6 @@ fig ``` \end{examplefigure} - ## Attributes -\attrdocs{Colorbar} \ No newline at end of file +\attrdocs{Colorbar} diff --git a/src/colorsampler.jl b/src/colorsampler.jl index ded923f64f1..738612d41fb 100644 --- a/src/colorsampler.jl +++ b/src/colorsampler.jl @@ -181,6 +181,7 @@ end struct ColorMap{N,T<:AbstractArray{<:Number,N},T2<:AbstractArray{<:Number,N}} color::Observable{T} colormap::Observable{Vector{RGBAf}} + raw_colormap::Observable{Vector{RGBAf}} # the above is scaled (when coming from cgrad), this is not scale::Observable{Function} mapping::Observable{Union{Nothing, Vector{Float64}}} colorrange::Observable{Vec{2,Float64}} @@ -200,6 +201,8 @@ end _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) function ColorMap(color::AbstractArray{<:Number, N}, colors_obs, colormap, colorrange, colorscale, alpha, lowclip, highclip, nan_color, @@ -207,6 +210,7 @@ function ColorMap(color::AbstractArray{<:Number, N}, colors_obs, colormap, color T = _array_value_type(color) color_tight = convert(Observable{T}, colors_obs) _colormap = Observable(RGBAf[]; ignore_equal_values=true) + raw_colormap = Observable(RGBAf[]; ignore_equal_values=true) categorical = Observable(false) colorscale = convert(Observable{Function}, colorscale) mapping = Observable{Union{Nothing,Vector{Float64}}}(nothing) @@ -217,10 +221,13 @@ function ColorMap(color::AbstractArray{<:Number, N}, colors_obs, colormap, color 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), Colors.alpha(c) * a), colors) + raw_colors = map(c -> RGBAf(Colors.color(c), Colors.alpha(c) * a), raw_colors) end _colormap[] = colors + raw_colormap[] = raw_colors categorical[] = cmap isa PlotUtils.CategoricalColorGradient if cmap isa PlotUtils.ColorGradient mapping[] = cmap.values @@ -256,6 +263,7 @@ function ColorMap(color::AbstractArray{<:Number, N}, colors_obs, colormap, color CT = ColorMap{N,T,typeof(color_scaled[])} return CT(color_tight, _colormap, + raw_colormap, colorscale, mapping, colorrange, diff --git a/src/makielayout/blocks/colorbar.jl b/src/makielayout/blocks/colorbar.jl index 5000ac3995b..d5a074a73bf 100644 --- a/src/makielayout/blocks/colorbar.jl +++ b/src/makielayout/blocks/colorbar.jl @@ -87,6 +87,10 @@ function Colorbar(fig_or_scene, plot::AbstractPlot; kwargs...) ) end +function extract_colormap(plot::StreamPlot) + return extract_colormap(plot.plots[1]) +end + function extract_colormap(plot::Union{Contourf,Tricontourf}) levels = plot._computed_levels limits = lift(l-> (l[1], l[end]), levels) @@ -133,17 +137,16 @@ function initialize_block!(cb::Colorbar; categorical=false) end map_is_categorical = cmap.categorical is_real_cat = categorical || cmap.value_position == color_center - colormap = cmap.colormap + colormap = cmap.raw_colormap limits = cmap.colorrange - colors = lift(blockscene, cmap.color, cmap.categorical, cb.nsteps, - cmap.scale) do values, categorical, n, scale + colors = lift(blockscene, cmap.color, cmap.categorical, cb.nsteps, limits) do values, categorical, n, lims if categorical && !is_real_cat return convert(Vector{Float64}, unique(values)) elseif categorical && is_real_cat return 1:length(values) else - return collect(Makie.colorbar_range(0, 1, n, scale)) + return collect(LinRange(lims..., n)) end::Vector{Float64} end @@ -165,23 +168,42 @@ function initialize_block!(cb::Colorbar; categorical=false) return BBox(left(fbox) + tri_heights[][1], right(fbox) - tri_heights[][2], bottom(fbox), top(fbox)) end end + xrange = Observable(Float32[]; ignore_equal_values=true) + yrange = Observable(Float32[]; ignore_equal_values=true) + function update_xyrange(bb, v, colors, scale) + xmin, ymin = minimum(bb) + xmax, ymax = maximum(bb) + if is_real_cat + colors = edges(colors) + end + s_scaled = scale.(colors) + mini, maxi = extrema(s_scaled) + s_scaled = (s_scaled .- mini) ./ (maxi - mini) + if v + xrange[] = LinRange(xmin, xmax, 2) + yrange[] = s_scaled .* (ymax - ymin) .+ ymin + else + xrange[] = s_scaled .* (xmax - xmin) .+ xmin + yrange[] = LinRange(ymin, ymax, 2) + end + return + end + + update_xyrange(barbox[], cb.vertical[], colors[], cmap.scale[]) + onany(update_xyrange, blockscene, barbox, cb.vertical, colors, cmap.scale) # for continous colormaps we sample a 1d image # to avoid white lines when rendering vector graphics - continous_pixels = lift(blockscene, cb.vertical, colors, map_is_categorical) do v, colors, iscat - n = length(colors) - if iscat && !is_real_cat + continous_pixels = lift(blockscene, cb.vertical, colors) do v, colors + if !is_real_cat colors = (colors[1:end-1] .+ colors[2:end]) ./2 - n = n - 1 end - return v ? reshape(colors, 1, n) : reshape(colors, n, 1) + n = length(colors) + return v ? reshape((colors), 1, n) : reshape((colors), n, 1) end - - image!(blockscene, - lift(bb -> range(left(bb), right(bb); length=2), blockscene, barbox), - lift(bb -> range(bottom(bb), top(bb); length=2), blockscene, barbox), - continous_pixels; + heatmap!(blockscene, + xrange, yrange, continous_pixels; colormap = colormap, colorscale = cmap.scale, interpolate = lift(!, map_is_categorical), From e76c9d1940c5f56e454b3d4aef5f53b113dc1b4d Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Fri, 8 Sep 2023 19:21:32 +0200 Subject: [PATCH 13/23] improve ReversibleScale printing --- src/layouting/transformation.jl | 5 +++-- src/types.jl | 11 ++++++----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/layouting/transformation.jl b/src/layouting/transformation.jl index 897c49e5576..bf8ed3c03e1 100644 --- a/src/layouting/transformation.jl +++ b/src/layouting/transformation.jl @@ -362,7 +362,8 @@ apply_transform(f::NTuple{3, typeof(identity)}, r::Rect) = r const pseudolog10 = ReversibleScale( x -> sign(x) * log10(abs(x) + 1), x -> sign(x) * (exp10(abs(x)) - 1); - limits=(0f0, 3f0) + limits=(0f0, 3f0), + name=:pseudolog10 ) Symlog10(hi) = Symlog10(-hi, hi) @@ -383,7 +384,7 @@ function Symlog10(lo, hi) else x end - ReversibleScale(forward, inverse; limits=(0f0, 3f0)) + return ReversibleScale(forward, inverse; limits=(0.0f0, 3.0f0), name=:Symlog10) end inverse_transform(::typeof(identity)) = identity diff --git a/src/types.jl b/src/types.jl index 9a4908eac24..b91dd552cce 100644 --- a/src/types.jl +++ b/src/types.jl @@ -409,7 +409,8 @@ struct ReversibleScale{F <: Function, I <: Function, T <: AbstractInterval} <: F valid limits interval (optional) """ interval::T - function ReversibleScale(forward, inverse = Automatic(); limits = (0f0, 10f0), interval = (-Inf32, Inf32)) + name::Symbol + function ReversibleScale(forward, inverse = Automatic(); limits = (0f0, 10f0), interval = (-Inf32, Inf32), name=Symbol(forward)) inverse isa Automatic && (inverse = inverse_transform(forward)) isnothing(inverse) && throw(ArgumentError( "Cannot determine inverse transform: you can use `ReversibleScale($(forward), inverse($(forward)))` instead." @@ -422,10 +423,10 @@ struct ReversibleScale{F <: Function, I <: Function, T <: AbstractInterval} <: F lft ≈ Id(lft) || throw(ArgumentError("Invalid inverse transform: $lft !≈ $(Id(lft))")) rgt ≈ Id(rgt) || throw(ArgumentError("Invalid inverse transform: $rgt !≈ $(Id(rgt))")) - new{typeof(forward),typeof(inverse),typeof(interval)}(forward, inverse, limits, interval) + return new{typeof(forward),typeof(inverse),typeof(interval)}(forward, inverse, limits, interval, name) end end -function (s::ReversibleScale)(args...) # functor - s.forward(args...) -end +(s::ReversibleScale)(args...) = s.forward(args...) # functor +Base.show(io::IO, s::ReversibleScale) = print(io, "ReversibleScale($(s.name))") +Base.show(io::IO, ::MIME"text/plain", s::ReversibleScale) = print(io, "ReversibleScale($(s.name))") From de507e2df4447ba1c149d024161c538f579bf35b Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Fri, 8 Sep 2023 20:06:01 +0200 Subject: [PATCH 14/23] one last bug? --- GLMakie/src/drawing_primitives.jl | 2 +- src/makielayout/blocks/colorbar.jl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/GLMakie/src/drawing_primitives.jl b/GLMakie/src/drawing_primitives.jl index 3dd5476484a..e9282d6a345 100644 --- a/GLMakie/src/drawing_primitives.jl +++ b/GLMakie/src/drawing_primitives.jl @@ -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 diff --git a/src/makielayout/blocks/colorbar.jl b/src/makielayout/blocks/colorbar.jl index 88054d62bb4..7dfbc87b79c 100644 --- a/src/makielayout/blocks/colorbar.jl +++ b/src/makielayout/blocks/colorbar.jl @@ -142,7 +142,7 @@ function initialize_block!(cb::Colorbar; categorical=false) cb.highclip, Observable(RGBAf(0,0,0,0))) end map_is_categorical = cmap.categorical - is_real_cat = categorical || cmap.value_position == color_center + is_real_cat = categorical || (cmap.value_position == color_center && map_is_categorical[]) colormap = cmap.raw_colormap limits = cmap.colorrange From efc72cd7adb5c11e248bc4eec510e0128b89312c Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Fri, 8 Sep 2023 21:34:58 +0200 Subject: [PATCH 15/23] fix cairomakie + Makie unit tests --- src/makielayout/blocks/colorbar.jl | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/makielayout/blocks/colorbar.jl b/src/makielayout/blocks/colorbar.jl index 7dfbc87b79c..19211f99067 100644 --- a/src/makielayout/blocks/colorbar.jl +++ b/src/makielayout/blocks/colorbar.jl @@ -208,11 +208,20 @@ function initialize_block!(cb::Colorbar; categorical=false) n = length(colors) return v ? reshape((colors), 1, n) : reshape((colors), n, 1) end + # TODO, implement interpolate = true for irregular grics in CairoMakie + # Then, we can just use heatmap! and don't need the image plot! heatmap!(blockscene, - xrange, yrange, continous_pixels; + xrange, yrange, continous_pixels; + colormap=colormap, + colorscale=cmap.scale, + isible=map_is_categorical, + inspectable=false) + + image!(blockscene, + lift(x-> LinRange(extrema(x)..., 2), xrange), lift(y-> LinRange(extrema(y)..., 2), yrange), continous_pixels; colormap = colormap, colorscale = cmap.scale, - interpolate = lift(!, map_is_categorical), + visible = lift(!, map_is_categorical), inspectable = false ) @@ -356,7 +365,12 @@ function initialize_block!(cb::Colorbar; categorical=false) # trigger protrusions with one of the attributes notify(cb.vertical) - + # We set everything via the ColorMap now. To be backwards compatible, we always set those fields: + setfield!(cb, :limits, convert(Observable{Any}, limits)) + setfield!(cb, :colormap, convert(Observable{Any}, cmap.colormap)) + setfield!(cb, :highclip, convert(Observable{Any}, cmap.highclip)) + setfield!(cb, :lowclip, convert(Observable{Any}, cmap.lowclip)) + setfield!(cb, :scale, convert(Observable{Any}, cmap.scale)) # trigger bbox notify(cb.layoutobservables.suggestedbbox) notify(barbox) From b9d61fc3c3a940301c4ae3bc4578fb5fea4e2d1d Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Fri, 8 Sep 2023 22:26:18 +0200 Subject: [PATCH 16/23] fix contourf --- src/makielayout/blocks/colorbar.jl | 32 ++++++++++++++++++------------ 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/src/makielayout/blocks/colorbar.jl b/src/makielayout/blocks/colorbar.jl index 19211f99067..4b13c5fc776 100644 --- a/src/makielayout/blocks/colorbar.jl +++ b/src/makielayout/blocks/colorbar.jl @@ -43,6 +43,25 @@ function extract_colormap(@nospecialize(plot::AbstractPlot)) end end +function extract_colormap(plot::StreamPlot) + return extract_colormap(plot.plots[1]) +end + +function extract_colormap(plot::Union{Contourf,Tricontourf}) + levels = plot._computed_levels + limits = lift(l -> (l[1], l[end]), levels) + function extend_color(color, computed) + color === nothing && return automatic + color == :auto || color == automatic && return computed + return computed + end + elow = lift(extend_color, plot.extendlow, plot._computed_extendlow) + ehigh = lift(extend_color, plot.extendhigh, plot._computed_extendhigh) + return ColorMap(levels[], levels, plot._computed_colormap, limits, plot.colorscale, Observable(1.0), + elow, ehigh, plot.nan_color, color_edge) +end + + function extract_colormap_recursive(@nospecialize(plot::T)) where {T <: AbstractPlot} cmap = extract_colormap(plot) if !isnothing(cmap) @@ -87,17 +106,6 @@ function Colorbar(fig_or_scene, plot::AbstractPlot; kwargs...) ) end -function extract_colormap(plot::StreamPlot) - return extract_colormap(plot.plots[1]) -end - -function extract_colormap(plot::Union{Contourf,Tricontourf}) - levels = plot._computed_levels - limits = lift(l-> (l[1], l[end]), levels) - return ColorMap(levels[], levels, plot._computed_colormap, limits, plot.colorscale, Observable(1.0), - plot.extendlow, - plot.extendhigh, plot.nan_color, color_edge) -end function colorbar_range(start, stop, length, colorscale) colorscale === identity && return LinRange(start, stop, length) @@ -155,10 +163,8 @@ function initialize_block!(cb::Colorbar; categorical=false) return collect(LinRange(lims..., n)) end::Vector{Float64} end - lowclip_tri_visible = lift(x -> !(x isa Automatic), blockscene, cmap.lowclip; ignore_equal_values=true) highclip_tri_visible = lift(x -> !(x isa Automatic), blockscene, cmap.highclip; ignore_equal_values=true) - tri_heights = lift(blockscene, highclip_tri_visible, lowclip_tri_visible, framebox; ignore_equal_values=true) do hv, lv, box if cb.vertical[] return (lv * width(box), hv * width(box)) From 9106ebb7b695e14559c5961167ff8b41c9f2d719 Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Fri, 8 Sep 2023 23:06:47 +0200 Subject: [PATCH 17/23] allow type change for ticks --- src/makielayout/blocks/colorbar.jl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/makielayout/blocks/colorbar.jl b/src/makielayout/blocks/colorbar.jl index 4b13c5fc776..68aaf9f77cd 100644 --- a/src/makielayout/blocks/colorbar.jl +++ b/src/makielayout/blocks/colorbar.jl @@ -322,9 +322,11 @@ function initialize_block!(cb::Colorbar; categorical=false) end - ticks = lift(colors, map_is_categorical, cb.ticks) do cs, iscat, ticks + ticks = Observable{Any}() + map!(ticks, colors, map_is_categorical, cb.ticks) do cs, iscat, ticks iscat && is_real_cat ? (1:length(cs), string.(cs)) : ticks end + lims = lift(colors, map_is_categorical, limits) do cs, iscat, limits return iscat && is_real_cat ? (0.5, length(cs) + 0.5) : limits end From aba34cbffa1f973ccfc047813f18668e1744f839 Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Tue, 12 Sep 2023 13:51:57 +0200 Subject: [PATCH 18/23] clean up implementation --- GLMakie/src/drawing_primitives.jl | 4 +- WGLMakie/src/lines.jl | 4 +- WGLMakie/src/meshes.jl | 2 +- docs/reference/blocks/colorbar.md | 2 - src/colorsampler.jl | 93 +++++++++++++++--------- src/makielayout/blocks/colorbar.jl | 112 +++++++++++++++-------------- src/makielayout/blocks/legend.jl | 2 +- test/makielayout.jl | 8 +-- 8 files changed, 126 insertions(+), 101 deletions(-) diff --git a/GLMakie/src/drawing_primitives.jl b/GLMakie/src/drawing_primitives.jl index e9282d6a345..5e048f45c48 100644 --- a/GLMakie/src/drawing_primitives.jl +++ b/GLMakie/src/drawing_primitives.jl @@ -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 diff --git a/WGLMakie/src/lines.jl b/WGLMakie/src/lines.jl index f8fce2b5f68..bfdcfc6f158 100644 --- a/WGLMakie/src/lines.jl +++ b/WGLMakie/src/lines.jl @@ -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 @@ -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, diff --git a/WGLMakie/src/meshes.jl b/WGLMakie/src/meshes.jl index 83e314c1e33..55744ec005a 100644 --- a/WGLMakie/src/meshes.jl +++ b/WGLMakie/src/meshes.jl @@ -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 diff --git a/docs/reference/blocks/colorbar.md b/docs/reference/blocks/colorbar.md index c6a333be7b7..568d5eaa905 100644 --- a/docs/reference/blocks/colorbar.md +++ b/docs/reference/blocks/colorbar.md @@ -1,5 +1,3 @@ - - # Colorbar A Colorbar needs a colormap and a tuple of low/high limits. diff --git a/src/colorsampler.jl b/src/colorsampler.jl index 738612d41fb..92e1c7d2eba 100644 --- a/src/colorsampler.jl +++ b/src/colorsampler.jl @@ -176,13 +176,27 @@ function numbers_to_colors(numbers::Union{AbstractArray{<:Number, N},Number}, end end -@enum ColorValuePosition color_edge color_center +""" + 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 ColorMap{N,T<:AbstractArray{<:Number,N},T2<:AbstractArray{<:Number,N}} +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}} 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}} @@ -190,34 +204,43 @@ struct ColorMap{N,T<:AbstractArray{<:Number,N},T2<:AbstractArray{<:Number,N}} highclip::Observable{Union{Automatic, RGBAf}} # Defaults to last color in colormap nan_color::Observable{RGBAf} - categorical::Observable{Bool} - value_position::ColorValuePosition + color_mapping_type::Observable{ColorMappingType} # scaled attributes colorrange_scaled::Observable{Vec2f} color_scaled::Observable{T2} end +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) -function ColorMap(color::AbstractArray{<:Number, N}, colors_obs, colormap, colorrange, colorscale, alpha, lowclip, - highclip, nan_color, - value_position=automatic) where {N} + +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) - _colormap = Observable(RGBAf[]; ignore_equal_values=true) + map_colors = Observable(RGBAf[]; ignore_equal_values=true) raw_colormap = Observable(RGBAf[]; ignore_equal_values=true) - categorical = Observable(false) + mapping = Observable{Union{Nothing,Vector{Float64}}}(nothing; ignore_equal_values=true) colorscale = convert(Observable{Function}, colorscale) - mapping = Observable{Union{Nothing,Vector{Float64}}}(nothing) - - if !(value_position isa ColorValuePosition) - value_position = colormap[] isa PlotUtils.CategoricalColorGradient ? color_edge : color_center - end function update_colors(cmap, a) colors = to_colormap(cmap) @@ -226,9 +249,8 @@ function ColorMap(color::AbstractArray{<:Number, N}, colors_obs, colormap, color 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 + map_colors[] = colors raw_colormap[] = raw_colors - categorical[] = cmap isa PlotUtils.CategoricalColorGradient if cmap isa PlotUtils.ColorGradient mapping[] = cmap.values end @@ -260,32 +282,33 @@ function ColorMap(color::AbstractArray{<:Number, N}, colors_obs, colormap, color color_scaled = lift(color_tight, colorscale) do color, scale return el32convert(apply_scale(scale, color)) end - CT = ColorMap{N,T,typeof(color_scaled[])} - return CT(color_tight, - _colormap, - raw_colormap, - colorscale, - mapping, - colorrange, - _lowclip, - _highclip, - lift(to_color, nan_color), - categorical, - value_position, - colorrange_scaled, - color_scaled) + CT = ColorMapping{N,T,typeof(color_scaled[])} + + return CT( + color_tight, + map_colors, + raw_colormap, + colorscale, + mapping, + colorrange, + _lowclip, + _highclip, + lift(to_color, nan_color), + color_mapping_type, + colorrange_scaled, + color_scaled) end function assemble_colors(c::AbstractArray{<:Number}, @nospecialize(color), @nospecialize(plot)) - return ColorMap(c, color, plot.colormap, plot.colorrange, plot.colorscale, plot.alpha, plot.lowclip, + 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::ColorMap, value::Number) +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 @@ -308,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) diff --git a/src/makielayout/blocks/colorbar.jl b/src/makielayout/blocks/colorbar.jl index 68aaf9f77cd..d251f079778 100644 --- a/src/makielayout/blocks/colorbar.jl +++ b/src/makielayout/blocks/colorbar.jl @@ -27,10 +27,10 @@ end function extract_colormap(@nospecialize(plot::AbstractPlot)) has_colorrange = haskey(plot, :colorrange) && !(plot.colorrange[] isa Makie.Automatic) - if haskey(plot, :calculated_colors) && plot.calculated_colors[] isa Makie.ColorMap + if haskey(plot, :calculated_colors) && plot.calculated_colors[] isa Makie.ColorMapping return plot.calculated_colors[] elseif has_colorrange && all(x -> haskey(plot, x), [:colormap, :colorrange, :color]) && plot.color[] isa AbstractVector{<:Colorant} - return ColorMap( + return ColorMapping( plot.color[], plot.color, plot.colormap, plot.colorrange, get(plot, :colorscale, Observable(identity)), get(plot, :alpha, Observable(1.0)), @@ -57,8 +57,8 @@ 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 ColorMap(levels[], levels, plot._computed_colormap, limits, plot.colorscale, Observable(1.0), - elow, ehigh, plot.nan_color, color_edge) + return ColorMapping(levels[], levels, plot._computed_colormap, limits, plot.colorscale, Observable(1.0), + elow, ehigh, plot.nan_color) end @@ -73,8 +73,8 @@ function extract_colormap_recursive(@nospecialize(plot::T)) where {T <: Abstract elseif isempty(colormaps) return nothing else - # Prefer ColorMap if in doubt! - cmaps = filter(x-> x isa ColorMap, colormaps) + # Prefer ColorMapping if in doubt! + cmaps = filter(x-> x isa ColorMapping, colormaps) length(cmaps) == 1 && return cmaps[1] error("Multiple colormaps found for plot $(plot), please specify which one to use manually. Please overload `Makie.extract_colormap(::$(T))` to allow for the automatical creation of a Colorbar.") end @@ -89,8 +89,8 @@ function Colorbar(fig_or_scene, plot::AbstractPlot; kwargs...) error("Neither $(func) nor any of its children use a colormap. Cannot create a Colorbar from this plot, please create it manually. If this is a recipe, one needs to overload `Makie.extract_colormap(::$(Combined{func}))` to allow for the automatical creation of a Colorbar.") end - if !(cmap isa ColorMap) - error("extract_colormap(::$(Combined{func})) returned an invalid value: $cmap. Needs to return either a `Makie.ColorMap`.") + if !(cmap isa ColorMapping) + error("extract_colormap(::$(Combined{func})) returned an invalid value: $cmap. Needs to return either a `Makie.ColorMapping`.") end if to_value(cmap.color) isa Union{AbstractVector{<: Colorant}, Colorant} @@ -106,19 +106,7 @@ function Colorbar(fig_or_scene, plot::AbstractPlot; kwargs...) ) end - -function colorbar_range(start, stop, length, colorscale) - colorscale === identity && return LinRange(start, stop, length) - - inverse = inverse_transform(colorscale) - isnothing(inverse) && throw(ArgumentError( - "Cannot determine inverse transform: you can use `ReversibleScale($(colorscale), inverse($(colorscale)))` instead." - )) - - inverse.(range(start, stop; length)) -end - -function initialize_block!(cb::Colorbar; categorical=false) +function initialize_block!(cb::Colorbar) blockscene = cb.blockscene onany(blockscene, cb.size, cb.vertical) do sz, vertical @@ -130,8 +118,9 @@ function initialize_block!(cb::Colorbar; categorical=false) end framebox = lift(round_to_IRect2D, blockscene, cb.layoutobservables.computedbbox) - # TODO, always convert to ColorMap! - if cb.colormap[] isa ColorMap + + # TODO, always convert to ColorMapping! + if cb.colormap[] isa ColorMapping cmap = cb.colormap[] else limits = lift(blockscene, cb.limits, cb.colorrange) do limits, colorrange @@ -140,29 +129,29 @@ function initialize_block!(cb::Colorbar; categorical=false) end return something(limits, colorrange, (0, 1)) end - c = cb.colormap - color = if c[] isa PlotUtils.ColorGradient - lift((c, l) -> l[1] .+ c.values .* (l[end] - l[1]), c, limits) - else - lift((n, s) -> collect(Makie.colorbar_range(0, 1, n, s)), cb.nsteps, cb.scale) - end - cmap = ColorMap(color[], color, cb.colormap, limits, cb.scale, Observable(1.0), cb.lowclip, - cb.highclip, Observable(RGBAf(0,0,0,0))) + alpha = Observable(1.0) # dont have these as fields in Colorbar + nan_color = Observable(RGBAf(0, 0, 0, 0)) + cmap = ColorMapping( + Float64[], Observable(Float64[]), cb.colormap, limits, + cb.scale, alpha, cb.lowclip, cb.highclip, nan_color) end - map_is_categorical = cmap.categorical - is_real_cat = categorical || (cmap.value_position == color_center && map_is_categorical[]) + colormap = cmap.raw_colormap limits = cmap.colorrange - - colors = lift(blockscene, cmap.color, cmap.categorical, cb.nsteps, limits) do values, categorical, n, lims - if categorical && !is_real_cat - return convert(Vector{Float64}, unique(values)) - elseif categorical && is_real_cat - return 1:length(values) + colors = lift(blockscene, cmap.mapping, cmap.color_mapping_type, cmap.color, limits, cb.nsteps; ignore_equal_values=true) do mapping, mapping_type, values, limits, n + if mapping === nothing + if mapping_type === Makie.banded + return sort!(convert(Vector{Float64}, mapping)) + elseif mapping_type === Makie.categorical + return convert(Vector{Float64},1:length(unique(values))) + else + return convert(Vector{Float64}, LinRange(limits[1], limits[2], n)) + end else - return collect(LinRange(lims..., n)) - end::Vector{Float64} + return mapping + end end + lowclip_tri_visible = lift(x -> !(x isa Automatic), blockscene, cmap.lowclip; ignore_equal_values=true) highclip_tri_visible = lift(x -> !(x isa Automatic), blockscene, cmap.highclip; ignore_equal_values=true) tri_heights = lift(blockscene, highclip_tri_visible, lowclip_tri_visible, framebox; ignore_equal_values=true) do hv, lv, box @@ -180,12 +169,14 @@ function initialize_block!(cb::Colorbar; categorical=false) return BBox(left(fbox) + tri_heights[][1], right(fbox) - tri_heights[][2], bottom(fbox), top(fbox)) end end + xrange = Observable(Float32[]; ignore_equal_values=true) yrange = Observable(Float32[]; ignore_equal_values=true) - function update_xyrange(bb, v, colors, scale) + + function update_xyrange(bb, v, colors, scale, mapping_type) xmin, ymin = minimum(bb) xmax, ymax = maximum(bb) - if is_real_cat + if mapping_type == Makie.categorical colors = edges(colors) end s_scaled = scale.(colors) @@ -201,14 +192,15 @@ function initialize_block!(cb::Colorbar; categorical=false) return end - update_xyrange(barbox[], cb.vertical[], colors[], cmap.scale[]) - onany(update_xyrange, blockscene, barbox, cb.vertical, colors, cmap.scale) + update_xyrange(barbox[], cb.vertical[], colors[], cmap.scale[], cmap.color_mapping_type[]) + onany(update_xyrange, blockscene, barbox, cb.vertical, colors, cmap.scale, cmap.color_mapping_type) # for continous colormaps we sample a 1d image # to avoid white lines when rendering vector graphics - continous_pixels = lift(blockscene, cb.vertical, colors) do v, colors - if !is_real_cat + continous_pixels = lift(blockscene, cb.vertical, colors, + cmap.color_mapping_type) do v, colors, mapping_type + if mapping_type !== Makie.categorical colors = (colors[1:end-1] .+ colors[2:end]) ./2 end n = length(colors) @@ -216,18 +208,30 @@ function initialize_block!(cb::Colorbar; categorical=false) end # TODO, implement interpolate = true for irregular grics in CairoMakie # Then, we can just use heatmap! and don't need the image plot! + + show_cats = Observable(false; ignore_equal_values=true) + show_continous = Observable(false; ignore_equal_values=true) + on(blockscene, cmap.color_mapping_type; update=true) do type + if type === continuous + show_continous[] = true + show_cats[] = false + else + show_continous[] = false + show_cats[] = true + end + end heatmap!(blockscene, xrange, yrange, continous_pixels; colormap=colormap, colorscale=cmap.scale, - isible=map_is_categorical, + visible=show_cats, inspectable=false) image!(blockscene, lift(x-> LinRange(extrema(x)..., 2), xrange), lift(y-> LinRange(extrema(y)..., 2), yrange), continous_pixels; colormap = colormap, colorscale = cmap.scale, - visible = lift(!, map_is_categorical), + visible=show_continous, inspectable = false ) @@ -323,12 +327,12 @@ function initialize_block!(cb::Colorbar; categorical=false) end ticks = Observable{Any}() - map!(ticks, colors, map_is_categorical, cb.ticks) do cs, iscat, ticks - iscat && is_real_cat ? (1:length(cs), string.(cs)) : ticks + map!(ticks, colors, cmap.color_mapping_type, cb.ticks) do cs, type, ticks + type === Makie.categorical ? (1:length(cs), string.(cs)) : ticks end - lims = lift(colors, map_is_categorical, limits) do cs, iscat, limits - return iscat && is_real_cat ? (0.5, length(cs) + 0.5) : limits + lims = lift(colors, cmap.color_mapping_type, limits) do cs, type, limits + return type === Makie.categorical ? (0.5, length(cs) + 0.5) : limits end axis = LineAxis(blockscene, endpoints = axispoints, flipped = cb.flipaxis, @@ -373,7 +377,7 @@ function initialize_block!(cb::Colorbar; categorical=false) # trigger protrusions with one of the attributes notify(cb.vertical) - # We set everything via the ColorMap now. To be backwards compatible, we always set those fields: + # We set everything via the ColorMapping now. To be backwards compatible, we always set those fields: setfield!(cb, :limits, convert(Observable{Any}, limits)) setfield!(cb, :colormap, convert(Observable{Any}, cmap.colormap)) setfield!(cb, :highclip, convert(Observable{Any}, cmap.highclip)) diff --git a/src/makielayout/blocks/legend.jl b/src/makielayout/blocks/legend.jl index f35e6cfd628..8d995e774f6 100644 --- a/src/makielayout/blocks/legend.jl +++ b/src/makielayout/blocks/legend.jl @@ -394,7 +394,7 @@ choose_scalar(attr, default) = is_scalar_attribute(to_value(attr)) ? attr : defa function extract_color(@nospecialize(plot), color_default) color = haskey(plot, :calculated_color) ? plot.calculated_color : plot.color - color[] isa ColorMap && return color_default + color[] isa ColorMapping && return color_default return choose_scalar(color, color_default) end diff --git a/test/makielayout.jl b/test/makielayout.jl index 59d970af71e..f2ae4ca3835 100644 --- a/test/makielayout.jl +++ b/test/makielayout.jl @@ -397,10 +397,10 @@ end @test_throws ArgumentError ReversibleScale(sqrt, exp10) # incorrect inverse scale end -@testset "Invalid inverse transform" begin - f = Figure() - @test_throws ArgumentError Colorbar(f[1, 1], limits = (1, 100), scale = x -> log10(x)) -end +# @testset "Invalid inverse transform" begin +# f = Figure() +# @test_throws ArgumentError Colorbar(f[1, 1], limits = (1, 100), scale = x -> log10(x)) +# end @testset "Colorscales" begin x = 10.0.^(1:0.1:4) From ff7acf523188135364a567dd4f484e3e48eb7ea8 Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Tue, 12 Sep 2023 14:25:09 +0200 Subject: [PATCH 19/23] add reference test --- .../src/tests/figures_and_makielayout.jl | 41 ++++++++++++++++--- src/makielayout/blocks/colorbar.jl | 7 ++-- 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/ReferenceTests/src/tests/figures_and_makielayout.jl b/ReferenceTests/src/tests/figures_and_makielayout.jl index 9d3d24d5cee..ea48e27f7d0 100644 --- a/ReferenceTests/src/tests/figures_and_makielayout.jl +++ b/ReferenceTests/src/tests/figures_and_makielayout.jl @@ -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 @@ -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, @@ -179,7 +179,7 @@ end thetaticklabelsize = 18, thetaticklabelcolor = :blue, thetaticklabelstrokewidth = 1, thetaticklabelstrokecolor = :white, ) - + f end @@ -192,3 +192,34 @@ end end f end + +@reference_test "Colorbar for recipes" begin + fig, ax, pl = barplot(1:3; color=1:3, colormap=Makie.Categorical(:viridis)) + 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) + + 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 diff --git a/src/makielayout/blocks/colorbar.jl b/src/makielayout/blocks/colorbar.jl index d251f079778..7ef9bc73e85 100644 --- a/src/makielayout/blocks/colorbar.jl +++ b/src/makielayout/blocks/colorbar.jl @@ -123,6 +123,7 @@ function initialize_block!(cb::Colorbar) if cb.colormap[] isa ColorMapping cmap = cb.colormap[] else + # Old way without Colormapping. We keep it, to be able to create a colormap directly limits = lift(blockscene, cb.limits, cb.colorrange) do limits, colorrange if all(!isnothing, (limits, colorrange)) error("Both colorrange + limits are set, please only set one, they're aliases. colorrange: $(colorrange), limits: $(limits)") @@ -141,7 +142,7 @@ function initialize_block!(cb::Colorbar) colors = lift(blockscene, cmap.mapping, cmap.color_mapping_type, cmap.color, limits, cb.nsteps; ignore_equal_values=true) do mapping, mapping_type, values, limits, n if mapping === nothing if mapping_type === Makie.banded - return sort!(convert(Vector{Float64}, mapping)) + error("Banded without a mapping is invalid. Please use colormap=cgrad(...; categorical=true)") elseif mapping_type === Makie.categorical return convert(Vector{Float64},1:length(unique(values))) else @@ -197,7 +198,6 @@ function initialize_block!(cb::Colorbar) # for continous colormaps we sample a 1d image # to avoid white lines when rendering vector graphics - continous_pixels = lift(blockscene, cb.vertical, colors, cmap.color_mapping_type) do v, colors, mapping_type if mapping_type !== Makie.categorical @@ -206,9 +206,9 @@ function initialize_block!(cb::Colorbar) n = length(colors) return v ? reshape((colors), 1, n) : reshape((colors), n, 1) end + # TODO, implement interpolate = true for irregular grics in CairoMakie # Then, we can just use heatmap! and don't need the image plot! - show_cats = Observable(false; ignore_equal_values=true) show_continous = Observable(false; ignore_equal_values=true) on(blockscene, cmap.color_mapping_type; update=true) do type @@ -328,6 +328,7 @@ function initialize_block!(cb::Colorbar) ticks = Observable{Any}() map!(ticks, colors, cmap.color_mapping_type, cb.ticks) do cs, type, ticks + # For categorical we just enumerate type === Makie.categorical ? (1:length(cs), string.(cs)) : ticks end From f67539e1cdfa8cb0f4cc1a98297ef3fb935a12d8 Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Tue, 12 Sep 2023 15:45:59 +0200 Subject: [PATCH 20/23] add test --- ReferenceTests/src/tests/figures_and_makielayout.jl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ReferenceTests/src/tests/figures_and_makielayout.jl b/ReferenceTests/src/tests/figures_and_makielayout.jl index ea48e27f7d0..575123c6f10 100644 --- a/ReferenceTests/src/tests/figures_and_makielayout.jl +++ b/ReferenceTests/src/tests/figures_and_makielayout.jl @@ -212,7 +212,9 @@ end 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) From 6ee1a44c8ccd357fb91105e2c90731f8536de4a3 Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Tue, 12 Sep 2023 17:01:23 +0200 Subject: [PATCH 21/23] update comment --- src/makielayout/blocks/colorbar.jl | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/makielayout/blocks/colorbar.jl b/src/makielayout/blocks/colorbar.jl index 7ef9bc73e85..278ab58fc62 100644 --- a/src/makielayout/blocks/colorbar.jl +++ b/src/makielayout/blocks/colorbar.jl @@ -137,7 +137,15 @@ function initialize_block!(cb::Colorbar) cb.scale, alpha, cb.lowclip, cb.highclip, nan_color) end - colormap = cmap.raw_colormap + colormap = lift(cmap.raw_colormap, cmap.colormap, cmap.mapping) do rcm, cm, mapping + if isnothing(mapping) + return rcm + else + # if there is a mapping, we want to apply it to the colormap, which is already done for cmap.colormap (by calling to_colormap(cgrad(...))) + # In the future, we may want to use cmap.mapping to do this ourselves + return cm + end + end limits = cmap.colorrange colors = lift(blockscene, cmap.mapping, cmap.color_mapping_type, cmap.color, limits, cb.nsteps; ignore_equal_values=true) do mapping, mapping_type, values, limits, n if mapping === nothing @@ -149,7 +157,8 @@ function initialize_block!(cb::Colorbar) return convert(Vector{Float64}, LinRange(limits[1], limits[2], n)) end else - return mapping + # Mapping is always 0..1, but color should be scaled + return limits[1] + (mapping .* (limits[2] - limits[1])) end end @@ -206,7 +215,6 @@ function initialize_block!(cb::Colorbar) n = length(colors) return v ? reshape((colors), 1, n) : reshape((colors), n, 1) end - # TODO, implement interpolate = true for irregular grics in CairoMakie # Then, we can just use heatmap! and don't need the image plot! show_cats = Observable(false; ignore_equal_values=true) @@ -220,18 +228,18 @@ function initialize_block!(cb::Colorbar) show_cats[] = true end end + heatmap!(blockscene, - xrange, yrange, continous_pixels; - colormap=colormap, - colorscale=cmap.scale, - visible=show_cats, - inspectable=false) + xrange, yrange, continous_pixels; + colormap=colormap, + visible=show_cats, + inspectable=false + ) image!(blockscene, lift(x-> LinRange(extrema(x)..., 2), xrange), lift(y-> LinRange(extrema(y)..., 2), yrange), continous_pixels; colormap = colormap, - colorscale = cmap.scale, - visible=show_continous, + visible = show_continous, inspectable = false ) From de83ce2d5ba65ddb309b10ebb570589ad7d2301d Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Tue, 12 Sep 2023 17:15:22 +0200 Subject: [PATCH 22/23] fix broadcast --- src/makielayout/blocks/colorbar.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/makielayout/blocks/colorbar.jl b/src/makielayout/blocks/colorbar.jl index 278ab58fc62..af613fa0e42 100644 --- a/src/makielayout/blocks/colorbar.jl +++ b/src/makielayout/blocks/colorbar.jl @@ -158,7 +158,7 @@ function initialize_block!(cb::Colorbar) end else # Mapping is always 0..1, but color should be scaled - return limits[1] + (mapping .* (limits[2] - limits[1])) + return limits[1] .+ (mapping .* (limits[2] - limits[1])) end end From eb1e1a9bbbe34734f10c3d7afdb2808ef792158a Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Tue, 12 Sep 2023 18:46:36 +0200 Subject: [PATCH 23/23] fix continous with values --- .../src/tests/figures_and_makielayout.jl | 3 +-- src/makielayout/blocks/colorbar.jl | 20 ++++++++++++++----- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/ReferenceTests/src/tests/figures_and_makielayout.jl b/ReferenceTests/src/tests/figures_and_makielayout.jl index 575123c6f10..98557769540 100644 --- a/ReferenceTests/src/tests/figures_and_makielayout.jl +++ b/ReferenceTests/src/tests/figures_and_makielayout.jl @@ -179,7 +179,6 @@ end thetaticklabelsize = 18, thetaticklabelcolor = :blue, thetaticklabelstrokewidth = 1, thetaticklabelstrokecolor = :white, ) - f end @@ -194,7 +193,7 @@ end end @reference_test "Colorbar for recipes" begin - fig, ax, pl = barplot(1:3; color=1:3, colormap=Makie.Categorical(:viridis)) + 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) diff --git a/src/makielayout/blocks/colorbar.jl b/src/makielayout/blocks/colorbar.jl index af613fa0e42..75b1a34a617 100644 --- a/src/makielayout/blocks/colorbar.jl +++ b/src/makielayout/blocks/colorbar.jl @@ -147,18 +147,29 @@ function initialize_block!(cb::Colorbar) end end limits = cmap.colorrange - colors = lift(blockscene, cmap.mapping, cmap.color_mapping_type, cmap.color, limits, cb.nsteps; ignore_equal_values=true) do mapping, mapping_type, values, limits, n + colors = lift(blockscene, cmap.mapping, cmap.color_mapping_type, cmap.color, cb.nsteps, limits; + ignore_equal_values=true) do mapping, mapping_type, values, n, limits if mapping === nothing if mapping_type === Makie.banded error("Banded without a mapping is invalid. Please use colormap=cgrad(...; categorical=true)") elseif mapping_type === Makie.categorical return convert(Vector{Float64},1:length(unique(values))) else - return convert(Vector{Float64}, LinRange(limits[1], limits[2], n)) + return convert(Vector{Float64}, LinRange(limits..., n)) end else - # Mapping is always 0..1, but color should be scaled - return limits[1] .+ (mapping .* (limits[2] - limits[1])) + if mapping_type === Makie.categorical + # This is because cmap.mapping comes from cgrad.values, which doesn't encode categorical colormapping correctly + error("Mapping should not be used for categorical colormaps") + end + if mapping_type === Makie.continuous + # we need at least nsteps, to correctly sample from the colormap (which has the mapping applied already) + return convert(Vector{Float64}, LinRange(limits..., n)) + else + # Mapping is always 0..1, but color should be scaled + return limits[1] .+ (mapping .* (limits[2] - limits[1])) + end + return end end @@ -235,7 +246,6 @@ function initialize_block!(cb::Colorbar) visible=show_cats, inspectable=false ) - image!(blockscene, lift(x-> LinRange(extrema(x)..., 2), xrange), lift(y-> LinRange(extrema(y)..., 2), yrange), continous_pixels; colormap = colormap,