I have packages A
, B
, C
, where both B
and C
depend on A
. In order to make some function f
available to both B
and C
at the same time, I first define it in A
and then overload it in B
and C
with some local methods, e.g.
module A
function f end
export f
end
module B
using A
...
A.f(x::SomeTypeInB) = ...
end
module C
using A
...
A.f(x::SomeTypeInC) = ...
end
So that I can then write using A, B, C
and use f
, dispatching without issues.
Can I use a similar approach to define recipe functions (using RecipesBase) common to both B
and C
? I’ve tried without success.
Additional info
For reference, atm I define recipes via
@userplot COOLPLOT
@recipe function f(h::COOLPLOT)
if h.args[1] isa SomeTypeInPackage
...
else
error("Got $(typeof(h.args[1])) instead of SomeTypeInPackage.")
end
end
which exports coolplot
and coolplot!
automatically.
You can just define a user recipe
Recipes · Plots on the Union of your types (or their abstract supertype if they have one).
Although I want these recipes to share the same name, the actual codes for plotting SomeTypeInB
and SomeTypeInC
are quite different. I need to be able to differentiate between these types inside B
and C
respectively.
Do they need to have a particular name? Maybe can you be a litte more concrete about how the plots should look and what the types are?
I’d like to define a common recipe convergence
(and convergence!
) that shows the standard converge plot (error vs iteration) of a fixed-point problem using some custom algorithms defined in B
and C
.
In other words, I have SolutionUsingAlgorithmB
and SolutionUsingAlgorithmC
(which in practice are two different struct
s of common supertype <: AbstractSolution
, where AbstractSolution
is defined in A
), defined in each respective package.
I would like to define convergence(solution::SolutionUsingAlgorithmB)
and convergence(solution::SolutionUsingAlgorithmC)
using RecipesBase
alone. In practice, each method of convergence
must be different (because SolutionUsingAlgorithmB
and SolutionUsingAlgorithmC
are very different), so I cannot simply make use of convergence(solution::AbstractSolution)
.
Ah, I see. I think. It’s a little tricky because Plots already uses dispatch to have the recipe names work.
The easiest is probably to have a function inside the recipe that generates the data to be plotted from your solution, and have that function dispatch on the type of solution. So that the recipe itself only sets up what is visible to the user plotting (which should be the same).
2 Likes
Yes, that would do it! However, using this approach, how do I go about passing different Plots’ stylistic arguments, e.g. markersize --> 4
, from such a function that lives inside the recipe to the recipe itself?
EDIT: Looking at this beautiful daschw’s post, I realised that it is simply a matter of push
ing to plotattributes
as follows:
module A
...
function _convergence end
@userplot CONVERGENCE
@recipe f(h::CONVERGENCE) = _convergence(h.args[1], plotattributes)
end
module B
using A
...
function A._convergence(solution::SolutionUsingAlgorithmB, plotattributes)
push!(plotattributes, :framestyle => :box, :gridalpha => 0.2, ...)
...
return solution.plottablefield
end
and similarly for C
.
For future reference, based on @mkborregaard’s comment, I found a better solution that involves wrapping SolutionUsingAlgorithmB
and SolutionUsingAlgorithmC
inside a constructor _Convergence
defined in A
:
module A
...
struct _Convergence{solution_T}
solution::solution_T
end
@userplot CONVERGENCE
@recipe function f(h::CONVERGENCE)
return _Convergence(h.args[1])
end
end
module B
using A
...
@recipe function f(wrappedobject::NSDEBase._Convergence{<:SolutionUsingAlgorithmB})
solution = wrappedobject.solution
...
end
end
and similarly for C
. This solution has the non-trivial advantage of allowing for the usage of the usual RecipesBase
tricks (for loops of @series
, etc) inside the relevant recipes defined in B
and C
, which otherwise couldn’t be used (that easily) with the previous approach based on _convergence
.
My idea was just something along the lines of
using RecipesBase
create_xy(x::A_type) = ...; x, y
create_xy(x::B_type) = ...; x, y
@userplot CoolPlot
@recipe function f(h::CoolPlot)
markersize --> 4
seriestype := :scatter
create_xy(h.args[1])
end
1 Like