Внутреннее устройство
RecipesBase
Макрос @recipe
определяет новый метод для RecipesBase.apply_recipe
.
@recipe function f(args...; kwargs...)
определяет
RecipesBase.apply_recipe(plotattributes, args...; kwargs...)
возвращая Vector{RecipeData}
, где RecipeData
содержит словарь plotattributes
и аргументы, возвращаемые в @recipe
или в @series
.
struct RecipeData
plotattributes::AbstractDict{Symbol,Any}
args::Tuple
end
Эта функция задает и перезаписывает записи в plotattributes
и, возможно, добавляет новые ряды.
-
attr --> val
преобразует вhaskey(plotattributes, :attr) || plotattributes[:attr] = val
. -
attr := val
задаетplotattributes[:attr] = val
. -
@series
allows to add new series within@recipe
. It copiesplotattributes
from@recipe
, applies the replacements defined in its code block and returns corresponding newRecipeData
object.
[NOTE] ==== `@series` должны быть определены как блоки кода с операторами `begin` и `end`. ==== ```julia @series begin ... end ```
Таким образом, RecipesBase.apply_recipe(plotattributes, args...; kwargs...)
возвращает Vector{RecipeData}
. Затем Plots может рекурсивно применить его снова к атрибутам (plotattributes
) и аргументам (args
) элементов этого вектора, диспетчеризируя по другой сигнатуре.
Plots
Стандартные команды построения графиков
plot(args...; plotattributes...)
plot!(args...; plotattributes...)
и такие сокращения, как scatter
или bar
, вызывают основную внутреннюю функцию построения графиков Plots._plot!
.
Plots._plot!(plt::Plot, plotattributes::AbstractDict{Symbol, Any}, args::Tuple)
Далее мы рассмотрим основные этапы конвейера предварительной обработки, реализованного в Plots._plot!
.
Предварительная обработка plotattributes
Перед вызовом Plots._plot!
и после применения каждого шаблона preprocessArgs!
выполняет предварительную обработку словаря plotattributes
. Эта функция заменяет псевдонимы, расширяет особые аргументы и преобразует некоторые типы атрибутов.
-
lc = nothing
заменяется наlinecolor = RGBA(0, 0, 0, 0)
. -
marker = (:red, :circle, 8)
расширяется доmarkercolor = :red
,markershape = :circle
иmarkersize = 8
.
Обработка пользовательских шаблонов
На первом шаге вызывается функция _process_userrecipe
.
kw_list = _process_userrecipes(plt, plotattributes, args)
Она преобразует указанные пользователем атрибуты (plotattributes
) в вектор RecipeData
. Она рекурсивно применяет RecipesBase.apply_recipe
к полям первого элемента вектора RecipeData
и добавляет в его начало полученный вектор RecipeData
. Если аргументы (args
) элемента пусты, она извлекает атрибуты (plotattributes
) и добавляет их к вектору словарей kw_list
. После обработки всех элементов RecipeData
возвращается kw_list
.
Обработка шаблонов типов
После обработки пользовательских шаблонов на определенном этапе рекурсии вышеуказанные аргументы имеют вид (y, )
, (x, y)
или (x, y, z)
. Plots определяет шаблон для этих сигнатур. Двухаргументная версия, например, выглядит следующим образом.
@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
Она рекурсивно вызывает _apply_type_recipe
для каждого аргумента до тех пор, пока не будет заменен ни один из аргументов. _apply_type_recipe
применяет шаблон типа с соответствующей сигнатурой, а для векторов пытается применить шаблон поэлементно. Если ни один аргумент не изменен с помощью _apply_type_recipe
, применяется резервный шаблон SliceIt
, который добавляет данные в атрибуты (plotattributes
) и возвращает RecipeData
с пустыми аргументами.
Обработка шаблонов графиков
На этом этапе все аргументы были обработаны до уровня, поддерживаемого Plots. В _plot!
существует Vector{Dict}
kw_list
с записью для каждого ряда и уже заполненными ключами :x
, :y
и :z
. Теперь _process_plotrecipe
вызывается до тех пор, пока не будут обработаны все шаблоны графиков.
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
Если в словаре не задан тип ряда, _process_plotrecipe
отправляет его в kw_list
и возвращается. В противном случае предпринимается попытка вызова RecipesBase.apply_recipe
с сигнатурой шаблона графика. Если для данной сигнатуры существует метод, и тип ряда изменился в результате применения шаблона, новые атрибуты (plotattributes
) добавляются к still_to_process
. Если для текущей сигнатуры шаблона графика отсутствует метод, мы добавляем текущий словарь к kw_list
и используем обработку шаблона ряда.
После применения всех шаблонов графиков производится настройка графика и подграфиков.
_plot_setup(plt, plotattributes, kw_list)
_subplot_setup(plt, plotattributes, kw_list)
Обработка шаблонов рядов
Мы почти закончили. Теперь заполняются значения по умолчанию для рядов, и для каждого ряда вызывается _process_seriesrecipe
.
for kw in kw_list
# объединяет значения по умолчанию
series_attr = Attr(kw, _series_defaults)
_process_seriesrecipe(plt, series_attr)
end
Если бэкенд поддерживает тип ряда, мы завершаем обработку и передаем ряд бэкенду. В противном случае применяется шаблон ряда для текущего типа ряда, и _process_seriesrecipe
вызывается снова для plotattributes
в каждом возвращаемом объекте RecipeData
. Здесь необходимо еще раз проверить, что тип ряда изменился. Благодаря такой рекурсивной обработке можно строить сложные типы рядов из простых блоков. Например, если мы добавляем @show st
в _process_seriesrecipe
и строим гистограмму, мы проходим через следующие типы рядов:
plot(histogram(randn(1000)))
st = :histogram
st = :barhist
st = :barbins
st = :bar
st = :shape