RecipesBase
The @recipe macro defines a new method for RecipesBase.apply_recipe.
@recipe function f(args...; kwargs...)
defines
RecipesBase.apply_recipe(plotattributes, args...; kwargs...)
returning a Vector{RecipeData} where RecipeData holds the plotattributes Dict and the arguments returned in @recipe or in @series.
struct RecipeData
plotattributes::AbstractDict{Symbol,Any}
args::Tuple
end
This function sets and overwrites entries in plotattributes and possibly adds new series.
-
attr --> valtranslates tohaskey(plotattributes, :attr) || plotattributes[:attr] = val -
attr := valsetsplotattributes[:attr] = val. -
@seriesallows to add new series within@recipe. It copiesplotattributesfrom@recipe, applies the replacements defined in its code block and returns corresponding newRecipeDataobject. !!! info@serieshave to be defined as a code block withbeginandendstatements.julia @series begin ... end
So RecipesBase.apply_recipe(plotattributes, args...; kwargs...) returns a Vector{RecipeData}. Plots can then recursively apply it again on the plotattributes and args of the elements of this vector, dispatching on a different signature.
Plots
The standard plotting commands
plot(args...; plotattributes...)
plot!(args...; plotattributes...)
and shorthands like scatter or bar call the core internal plotting function Plots._plot!.
Plots._plot!(plt::Plot, plotattributes::AbstractDict{Symbol, Any}, args::Tuple)
In the following we will go through the major steps of the preprocessing pipeline implemented in Plots._plot!.
Preprocess plotattributes
Before Plots._plot! is called and after each recipe is applied, preprocessArgs! preprocesses the plotattributes Dict. It replaces aliases, expands magic arguments, and converts some attribute types.
-
lc = nothingis replaced bylinecolor = RGBA(0, 0, 0, 0). -
marker = (:red, :circle, 8)expands tomarkercolor = :red,markershape = :circleandmarkersize = 8.
Process User Recipes
In the first step, _process_userrecipe is called.
kw_list = _process_userrecipes(plt, plotattributes, args)
It converts the user-provided plotattributes to a vector of RecipeData. It recursively applies RecipesBase.apply_recipe on the fields of the first element of the RecipeData vector and prepends the resulting RecipeData vector to it. If the args of an element are empty, it extracts plotattributes and adds it to a Vector of Dicts kw_list. When all RecipeData elements are fully processed, kw_list is returned.
Process Type Recipes
After user recipes are processed, at some point in the recursion above args is of the form (y, ), (x, y) or (x, y, z). Plots defines recipes for these signatures. The two argument version, for example, looks like this.
@recipe function f(x, y)
did_replace = false
newx = _apply_type_recipe(plotattributes, x)
x === newx || (did_replace = true)
newy = _apply_type_recipe(plotattributes, y)
y === newy || (did_replace = true)
if did_replace
newx, newy
else
SliceIt, x, y, nothing
end
end
It recursively calls _apply_type_recipe on each argument until none of the arguments is replaced. _apply_type_recipe applies the type recipe with the corresponding signature and for vectors it tries to apply the recipe element-wise. When no argument is changed by _apply_type_recipe, the fallback SliceIt recipe is applied, which adds the data to plotattributes and returns RecipeData with empty args.
Process Plot Recipes
At this stage all arguments have been processed to something Plots supports. In _plot! we have a Vector{Dict} kw_list with an entry for each series and already populated :x, :y and :z keys. Now _process_plotrecipe is called until all plot recipes are processed.
still_to_process = kw_list
kw_list = KW[]
while !isempty(still_to_process)
next_kw = popfirst!(still_to_process)
_process_plotrecipe(plt, next_kw, kw_list, still_to_process)
end
If no series type is set in the Dict, _process_plotrecipe pushes it to kw_list and returns. Otherwise it tries to call RecipesBase.apply_recipe with the plot recipe signature. If there is a method for this signature and the seriestype has changed by applying the recipe, the new plotattributes are appended to still_to_process. If there is no method for the current plot recipe signature, we append the current Dict to kw_list and rely on series recipe processing.
After all plot recipes have been applied, the plot and subplots are set-up.
_plot_setup(plt, plotattributes, kw_list)
_subplot_setup(plt, plotattributes, kw_list)
Process Series Recipes
We are almost finished. Now the series defaults are populated and _process_seriesrecipe is called for each series .
for kw in kw_list
# merge defaults
series_attr = Attr(kw, _series_defaults)
_process_seriesrecipe(plt, series_attr)
end
If the series type is natively supported by the backend, we finalize processing and pass the series along to the backend. Otherwise, the series recipe for the current series type is applied and _process_seriesrecipe is called again for the plotattributes in each returned RecipeData object. Here we have to check again that the series type changed. Due to this recursive processing, complex series types can be built up by simple blocks. For example if we add an @show st in _process_seriesrecipe and plot a histogram, we go through the following series types:
plot(histogram(randn(1000)))
st = :histogram
st = :barhist
st = :barbins
st = :bar
st = :shape