Skip to content

Commit

Permalink
Allow for user defined recipes to be used in SpecApi (#4655)
Browse files Browse the repository at this point in the history
* Allow for user defined recipes to be used in SpecApi

* Update CHANGELOG.md

* allow external blocks as well

* add basic tests for specapi with external recipes

* fix test

---------

Co-authored-by: Anshul Singhvi <[email protected]>
  • Loading branch information
SimonDanisch and asinghvi17 authored Dec 12, 2024
1 parent 71cd207 commit 78f5597
Show file tree
Hide file tree
Showing 5 changed files with 39 additions and 18 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## [Unreleased]

- Allow for user defined recipes to be used in SpecApi [#4655](https://github.com/MakieOrg/Makie.jl/pull/4655).

## [0.21.17] - 2024-12-05

- Added `backend` and `update` kwargs to `show` [#4558](https://github.com/MakieOrg/Makie.jl/pull/4558)
Expand Down
9 changes: 9 additions & 0 deletions MakieCore/src/recipes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ function plotfunc(f::Function)
end
end

symbol_to_plot(x::Symbol) = symbol_to_plot(Val(x))
function symbol_to_plot(::Val{Sym}) where {Sym}
return nothing
end


function plotfunc!(x)
F = plotfunc(x)::Function
name = Symbol(nameof(F), :!)
Expand Down Expand Up @@ -188,6 +194,7 @@ macro recipe(theme_func, Tsym::Symbol, args::Symbol...)
Core.@__doc__ ($funcname)(args...; kw...) = _create_plot($funcname, Dict{Symbol, Any}(kw), args...)
($funcname!)(args...; kw...) = _create_plot!($funcname, Dict{Symbol, Any}(kw), args...)
$(MakieCore).default_theme(scene, ::Type{<:$PlotType}) = $(esc(theme_func))(scene)
$(MakieCore).symbol_to_plot(::Val{$(QuoteNode(Tsym))}) = $PlotType
export $PlotType, $funcname, $funcname!
end
if !isempty(args)
Expand Down Expand Up @@ -496,6 +503,8 @@ function create_recipe_expr(Tsym, args, attrblock)
$(MakieCore).documented_attributes(::Type{<:$(PlotType)}) = $attr_placeholder

$(MakieCore).plotsym(::Type{<:$(PlotType)}) = $(QuoteNode(Tsym))
$(MakieCore).symbol_to_plot(::Val{$(QuoteNode(Tsym))}) = $PlotType

function ($funcname)(args...; kw...)
kwdict = Dict{Symbol, Any}(kw)
_create_plot($funcname, kwdict, args...)
Expand Down
17 changes: 10 additions & 7 deletions src/makielayout/blocks.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ function attribute_default_expressions end
function _attribute_docs end
function has_forwarded_layout end

symbol_to_block(symbol::Symbol) = symbol_to_block(Val(symbol))
symbol_to_block(::Val) = nothing

macro Block(_name::Union{Expr, Symbol}, body::Expr = Expr(:block))

body.head === :block || error("A Block needs to be defined within a `begin end` block")
Expand Down Expand Up @@ -78,18 +81,18 @@ macro Block(_name::Union{Expr, Symbol}, body::Expr = Expr(:block))
$structdef

export $name

function Makie.is_attribute(::Type{$(name)}, sym::Symbol)
$(Makie).symbol_to_block(::Val{$(QuoteNode(name))}) = $name
function $(Makie).is_attribute(::Type{$(name)}, sym::Symbol)
sym in ($((attrs !== nothing ? [QuoteNode(a.symbol) for a in attrs] : [])...),)
end

function Makie.default_attribute_values(::Type{$(name)}, scene::Union{Scene, Nothing})
function $(Makie).default_attribute_values(::Type{$(name)}, scene::Union{Scene, Nothing})
sceneattrs = scene === nothing ? Attributes() : theme(scene)
curdeftheme = Makie.fast_deepcopy($(Makie).CURRENT_DEFAULT_THEME)
curdeftheme = $(Makie).fast_deepcopy($(Makie).CURRENT_DEFAULT_THEME)
$(make_attr_dict_expr(attrs, :sceneattrs, :curdeftheme))
end

function Makie.attribute_default_expressions(::Type{$name})
function $(Makie).attribute_default_expressions(::Type{$name})
$(
if attrs === nothing
Dict{Symbol, String}()
Expand All @@ -99,7 +102,7 @@ macro Block(_name::Union{Expr, Symbol}, body::Expr = Expr(:block))
)
end

function Makie._attribute_docs(::Type{$(name)})
function $(Makie)._attribute_docs(::Type{$(name)})
Dict(
$(
(attrs !== nothing ?
Expand All @@ -109,7 +112,7 @@ macro Block(_name::Union{Expr, Symbol}, body::Expr = Expr(:block))
)
end

Makie.has_forwarded_layout(::Type{$name}) = $has_forwarded_layout
$(Makie).has_forwarded_layout(::Type{$name}) = $has_forwarded_layout

docstring_modified = make_block_docstring($name, user_docstring)
@doc docstring_modified $name
Expand Down
18 changes: 8 additions & 10 deletions src/specapi.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,10 @@ using GridLayoutBase: GridLayoutBase

import GridLayoutBase: GridPosition, Side, ContentSize, GapSize, AlignMode, Inner, GridLayout, GridSubposition

function get_recipe_function(name::Symbol)
if hasproperty(Makie, name)
return getfield(Makie, name)
else
return nothing
end
function symbol_to_specable(sym::Symbol)
block = symbol_to_block(sym)
isnothing(block) || return block
return MakieCore.symbol_to_plot(sym)
end

"""
Expand All @@ -27,7 +25,7 @@ struct PlotSpec
error("PlotSpec objects are supposed to be used without !, unless when using `S.$(type)(axis::P.Axis, args...; kwargs...)`")
end
if !isuppercase(type_str[1])
func = get_recipe_function(type)
func = hasproperty(Makie, type) ? getproperty(Makie, type) : nothing
func === nothing && error("PlotSpec need to be existing recipes or Makie plot objects. Found: $(type_str)")
plot_type = Plot{func}
type = plotsym(plot_type)
Expand Down Expand Up @@ -169,7 +167,7 @@ function to_plotspec(::Type{P}, p::PlotSpec; kwargs...) where {P}
return PlotSpec(plotsym(plottype(P, S)), p.args...; p.kwargs..., kwargs...)
end

plottype(p::PlotSpec) = getfield(Makie, p.type)
plottype(p::PlotSpec) = MakieCore.symbol_to_plot(p.type)

function Base.show(io::IO, ::MIME"text/plain", spec::PlotSpec)
args = join(map(x -> string("::", typeof(x)), spec.args), ", ")
Expand Down Expand Up @@ -403,7 +401,7 @@ function Base.getproperty(::_SpecApi, field::Symbol)
# Since precompilation will cache only MakieCore's state
# And once everything is compiled, and MakieCore is loaded into a package
# The names are loaded from cache and dont contain anything after MakieCore.
func = get_recipe_function(field)
func = symbol_to_specable(field)
if isnothing(func)
error("$(field) neither a recipe, Makie plotting object or a Block (like Axis, Legend, etc).")
elseif func isa Function
Expand Down Expand Up @@ -744,7 +742,7 @@ function extract_colorbar_kw(legend::BlockSpec, scene::Scene)
end

function to_layoutable(parent, position::GridLayoutPosition, spec::BlockSpec)
BType = getfield(Makie, spec.type)
BType = symbol_to_block(spec.type)
fig = get_top_parent(parent)

block = if spec.type === :Colorbar
Expand Down
11 changes: 10 additions & 1 deletion test/specapi.jl
Original file line number Diff line number Diff line change
Expand Up @@ -181,9 +181,18 @@ end
# This is too internal and fragile, so we won't actually test this
# @test leg.scene.plots[2].marker[] == :circle
# @test leg.scene.plots[3].marker[] == :rect

# Test that the legend has the correct labels.
# Again, I consider this too fragile to work with!
# @test contents(contents(leg.grid)[1])[2].text[] == "A"
# @test contents(contents(leg.grid)[2])[4].text[] == "B"
end

@recipe(TestRecipeForSpecApi) do scene
return Attributes()
end

@testset "External Recipe compatibility (#4295)" begin
@test_nowarn S.TestRecipeForSpecApi
@test_nowarn S.TestRecipeForSpecApi(1, 2, 3; a = 4, b = 5)
end

0 comments on commit 78f5597

Please sign in to comment.