diff --git a/CHANGELOG.md b/CHANGELOG.md index 45f82637f21..71b6a893cdd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - Show DataInspector tooltip on NaN values if `nan_color` has been set to other than `:transparent` [#4310](https://github.com/MakieOrg/Makie.jl/pull/4310) - Fix `linestyle` not being used in `triplot` [#4332](https://github.com/MakieOrg/Makie.jl/pull/4332) +- Invalid keyword arguments for `Block`s (e.g. `Axis` and `Colorbar`) now throw errors and show suggestions rather than simply throwing [#4392](https://github.com/MakieOrg/Makie.jl/pull/4392) - Fix voxel clipping not being based on voxel centers [#4397](https://github.com/MakieOrg/Makie.jl/pull/4397) ## [0.21.11] - 2024-09-13 diff --git a/MakieCore/src/recipes.jl b/MakieCore/src/recipes.jl index 4b1d9aaff97..eb64bd0dbc2 100644 --- a/MakieCore/src/recipes.jl +++ b/MakieCore/src/recipes.jl @@ -706,6 +706,10 @@ function print_columns(io::IO, v::Vector{String}; gapsize = 2, rows_first = true return end +__obj_name(_) = "plot" +__valid_attributes(p) = attribute_names(p) +__has_generic_attributes(_) = true + function Base.showerror(io::IO, i::InvalidAttributeError) n = length(i.attributes) print(io, "Invalid attribute$(n > 1 ? "s" : "") ") @@ -713,20 +717,22 @@ function Base.showerror(io::IO, i::InvalidAttributeError) j > 1 && print(io, j == length(i.attributes) ? " and " : ", ") printstyled(io, att; color = :red, bold = true) end - print(io, " for plot type ") + print(io, " for $(__obj_name(i.plottype)) type ") printstyled(io, i.plottype; color = :blue, bold = true) println(io, ".") - nameset = sort(string.(collect(attribute_names(i.plottype)))) + nameset = sort(string.(collect(__valid_attributes(i.plottype)))) println(io) - println(io, "The available plot attributes for $(i.plottype) are:") + println(io, "The available $(__obj_name(i.plottype)) attributes for $(i.plottype) are:") println(io) print_columns(io, nameset; cols = displaysize(stderr)[2], rows_first = true) - allowlist = attribute_name_allowlist() - println(io) - println(io) - println(io, "Generic attributes are:") - println(io) - print_columns(io, sort([string(a) for a in allowlist]); cols = displaysize(stderr)[2], rows_first = true) + if __has_generic_attributes(i.plottype) + allowlist = attribute_name_allowlist() + println(io) + println(io) + println(io, "Generic attributes are:") + println(io) + print_columns(io, sort([string(a) for a in allowlist]); cols = displaysize(stderr)[2], rows_first = true) + end println(io) end diff --git a/src/makielayout/blocks.jl b/src/makielayout/blocks.jl index 6ec49ad154f..2be828e0a16 100644 --- a/src/makielayout/blocks.jl +++ b/src/makielayout/blocks.jl @@ -288,6 +288,27 @@ function block_defaults(::Type{B}, attribute_kwargs::Dict, scene::Union{Nothing, return attributes end +MakieCore.__obj_name(::Type{<:Block}) = "block" +function MakieCore.__valid_attributes(T::Type{S}) where {S<:Block} + attrs = _attribute_docs(T) + # Some blocks have keyword arguments that are not attributes. + # TODO: Refactor intiailize_block! to just not use kwargs? + (S <: Axis || S <: PolarAxis) && (attrs[:palette] = "") + S <: Legend && (attrs[:entrygroups] = "") + S <: Menu && (attrs[:default] = "") + S <: LScene && (attrs[:scenekw] = "") + return keys(attrs) +end +MakieCore.__has_generic_attributes(::Type{<:Block}) = false + +function _check_remaining_kwargs(T::Type{<:Block}, kwdict::Dict) + badnames = setdiff(keys(kwdict), MakieCore.__valid_attributes(T)) + if !isempty(badnames) + throw(MakieCore.InvalidAttributeError(T, badnames)) + end + return +end + function _block(T::Type{<:Block}, fig_or_scene::Union{Figure,Scene}, args, kwdict::Dict, bbox; kwdict_complete=false) # first sort out all user kwargs that correspond to block attributes @@ -301,6 +322,7 @@ function _block(T::Type{<:Block}, fig_or_scene::Union{Figure,Scene}, args, kwdic end # the non-attribute kwargs will be passed to the block later non_attribute_kwargs = kwdict + _check_remaining_kwargs(T, non_attribute_kwargs) topscene = get_topscene(fig_or_scene) # retrieve the default attributes for this block given the scene theme diff --git a/test/pipeline.jl b/test/pipeline.jl index 931c9ff2775..b13cea906d7 100644 --- a/test/pipeline.jl +++ b/test/pipeline.jl @@ -148,7 +148,13 @@ end @test all(x -> x isa Volume, plots) end -import Makie.MakieCore: InvalidAttributeError +import Makie.MakieCore: + __obj_name, + __valid_attributes, + __has_generic_attributes, + InvalidAttributeError, + attribute_names +import Makie: _attribute_docs @testset "validated attributes" begin @test_throws InvalidAttributeError heatmap(zeros(10, 10); does_not_exist = 123) @@ -176,3 +182,40 @@ end @test_throws InvalidAttributeError testrecipe(1:4, 1:4, colour=:red) @test testrecipe(1:4, 1:4, color=:red) isa Makie.FigureAxisPlot end + +@testset "validated attributes for blocks" begin + @test __obj_name(Lines) == "plot" + @test __valid_attributes(Lines) == attribute_names(Lines) + @test __has_generic_attributes(Lines) + + @test __obj_name(Axis) == "block" + @test __valid_attributes(Axis3) == keys(_attribute_docs(Axis3)) + @test !__has_generic_attributes(Axis3) + + fig = Figure() + @test_throws InvalidAttributeError Axis(fig[1, 1], does_not_exist = 123) + @test_throws InvalidAttributeError Axis3(fig[1, 1], does_not_exist = 123, does_not_exist2 = 123) + @test_throws InvalidAttributeError lines(1:10, axis = (does_not_exist = 123,)) + @test_throws InvalidAttributeError Colorbar(fig[1, 1], does_not_exist = 123) + @test_throws InvalidAttributeError Label(fig[1, 1], does_not_exist = 123) + @test_throws InvalidAttributeError Box(fig[1, 1], does_not_exist = 123) + @test_throws InvalidAttributeError Slider(fig[1, 1], does_not_exist = 123) + @test_throws InvalidAttributeError SliderGrid(fig[1, 1], does_not_exist = 123) + @test_throws InvalidAttributeError IntervalSlider(fig[1, 1], does_not_exist = 123) + @test_throws InvalidAttributeError Button(fig[1, 1], does_not_exist = 123) + @test_throws InvalidAttributeError Toggle(fig[1, 1], does_not_exist = 123) + @test_throws InvalidAttributeError Menu(fig[1, 1], does_not_exist = 123) + @test_throws InvalidAttributeError Legend(fig[1, 1], does_not_exist = 123) + @test_throws InvalidAttributeError LScene(fig[1, 1], does_not_exist = 123) + @test_throws InvalidAttributeError Textbox(fig[1, 1], does_not_exist = 123) + @test_throws InvalidAttributeError PolarAxis(fig[1, 1], does_not_exist = 123) + + @test Axis(fig[1, 1], palette = nothing) isa Axis # just checking that it doesn't error + @test Menu(fig[1, 2], default = nothing) isa Menu + @test Legend(fig[1, 3], entrygroups = []) isa Legend + @test PolarAxis(fig[1, 4], palette = nothing) isa PolarAxis + @test :palette in __valid_attributes(Axis) + @test :default in __valid_attributes(Menu) + @test :entrygroups in __valid_attributes(Legend) + @test :palette in __valid_attributes(PolarAxis) +end \ No newline at end of file