Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Provide suggestions for unknown kwargs passed to blocks #4392

Merged
merged 13 commits into from
Dec 5, 2024
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
- Fix NaN handling in WGLMakie [#4282](https://github.com/MakieOrg/Makie.jl/pull/4282).
- 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)
- Parsing `Q` and `q` commands in svg paths with `BezierPath` is now supported [#4413](https://github.com/MakieOrg/Makie.jl/pull/4413)

Expand Down
36 changes: 21 additions & 15 deletions MakieCore/src/recipes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -670,9 +670,13 @@ plottype(plot_args...) = Plot{plot} # default to dispatch to type recipes!
deprecated_attributes(_) = ()

struct InvalidAttributeError <: Exception
plottype::Type
type::Type
object_name::String # Generic name like plot, block
attributes::Set{Symbol}
end
function InvalidAttributeError(::Type{PT}, attributes::Set{Symbol}) where {PT <: Plot}
return InvalidAttributeError(PT, "plot", attributes)
end

function print_columns(io::IO, v::Vector{String}; gapsize = 2, rows_first = true, cols = displaysize(io)[2])

Expand Down Expand Up @@ -718,27 +722,29 @@ function print_columns(io::IO, v::Vector{String}; gapsize = 2, rows_first = true
return
end

function Base.showerror(io::IO, i::InvalidAttributeError)
n = length(i.attributes)
function Base.showerror(io::IO, err::InvalidAttributeError)
n = length(err.attributes)
print(io, "Invalid attribute$(n > 1 ? "s" : "") ")
for (j, att) in enumerate(i.attributes)
j > 1 && print(io, j == length(i.attributes) ? " and " : ", ")
for (j, att) in enumerate(err.attributes)
j > 1 && print(io, j == length(err.attributes) ? " and " : ", ")
printstyled(io, att; color = :red, bold = true)
end
print(io, " for plot type ")
printstyled(io, i.plottype; color = :blue, bold = true)
print(io, " for $(err.object_name) type ")
printstyled(io, err.type; color = :blue, bold = true)
println(io, ".")
nameset = sort(string.(collect(attribute_names(i.plottype))))
nameset = sort(string.(collect(attribute_names(err.type))))
println(io)
println(io, "The available plot attributes for $(i.plottype) are:")
println(io, "The available $(err.object_name) attributes for $(err.type) 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 err.type isa Plot
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

Expand Down
24 changes: 24 additions & 0 deletions src/makielayout/blocks.jl
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,29 @@
return attributes
end

function MakieCore.InvalidAttributeError(::Type{BT}, attributes::Set{Symbol}) where {BT <: Block}
return MakieCore.InvalidAttributeError(BT, "block", attributes)

Check warning on line 292 in src/makielayout/blocks.jl

View check run for this annotation

Codecov / codecov/patch

src/makielayout/blocks.jl#L291-L292

Added lines #L291 - L292 were not covered by tests
end

function MakieCore.attribute_names(::Type{T}) where {T <: Block}
attrs = _attribute_docs(T)

Check warning on line 296 in src/makielayout/blocks.jl

View check run for this annotation

Codecov / codecov/patch

src/makielayout/blocks.jl#L295-L296

Added lines #L295 - L296 were not covered by tests
# Some blocks have keyword arguments that are not attributes.
# TODO: Refactor intiailize_block! to just not use kwargs?
(T <: Axis || T <: PolarAxis) && (attrs[:palette] = "")
T <: Legend && (attrs[:entrygroups] = "")
T <: Menu && (attrs[:default] = "")
T <: LScene && (attrs[:scenekw] = "")
return keys(attrs)

Check warning on line 303 in src/makielayout/blocks.jl

View check run for this annotation

Codecov / codecov/patch

src/makielayout/blocks.jl#L299-L303

Added lines #L299 - L303 were not covered by tests
end

function _check_remaining_kwargs(T::Type{<:Block}, kwdict::Dict)
badnames = setdiff(keys(kwdict), MakieCore.attribute_names(T))
if !isempty(badnames)
throw(MakieCore.InvalidAttributeError(T, badnames))

Check warning on line 309 in src/makielayout/blocks.jl

View check run for this annotation

Codecov / codecov/patch

src/makielayout/blocks.jl#L306-L309

Added lines #L306 - L309 were not covered by tests
end
return

Check warning on line 311 in src/makielayout/blocks.jl

View check run for this annotation

Codecov / codecov/patch

src/makielayout/blocks.jl#L311

Added line #L311 was not covered by tests
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
Expand All @@ -301,6 +324,7 @@
end
# the non-attribute kwargs will be passed to the block later
non_attribute_kwargs = kwdict
_check_remaining_kwargs(T, non_attribute_kwargs)

Check warning on line 327 in src/makielayout/blocks.jl

View check run for this annotation

Codecov / codecov/patch

src/makielayout/blocks.jl#L327

Added line #L327 was not covered by tests

topscene = get_topscene(fig_or_scene)
# retrieve the default attributes for this block given the scene theme
Expand Down
41 changes: 40 additions & 1 deletion test/pipeline.jl
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,10 @@ end
@test all(x -> x isa Volume, plots)
end

import Makie.MakieCore: InvalidAttributeError
import Makie.MakieCore:
InvalidAttributeError,
attribute_names
import Makie: _attribute_docs

@testset "validated attributes" begin
@test_throws InvalidAttributeError heatmap(zeros(10, 10); does_not_exist = 123)
Expand Down Expand Up @@ -176,3 +179,39 @@ 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
err = InvalidAttributeError(Lines, Set{Symbol}())
@test err.object_name == "plot"

err = InvalidAttributeError(Axis, Set{Symbol}())
@test err.object_name == "block"
@test attribute_names(Axis3) == keys(_attribute_docs(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 attribute_names(Axis)
@test :default in attribute_names(Menu)
@test :entrygroups in attribute_names(Legend)
@test :palette in attribute_names(PolarAxis)
end
Loading