Внутреннее устройство

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 copies plotattributes from @recipe, applies the replacements defined in its code block and returns corresponding new RecipeData object.

[NOTE]
====
[`@series`](@ref) должны быть определены как блоки кода с операторами `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