Если это ваш реальный вариант использования (а не упрощенный пример), то я бы сказал, что основная часть того, что вы ищете, это пользовательский Стат , а не пользовательский Geom .Вычисления / манипуляции с данными должны происходить в первом.
(Для справки я обычно смотрю на код в GeomBoxplot
/ StatBoxplot
, чтобы выяснить, гдевещи должны произойти, так как этот вариант использования включает в себя кучу вычислений для квантилей / выбросов, а также комбинацию различных элементов grob, которые принимают различные эстетические отображения.)
Данные со случайным начальным числом для воспроизводимости:
set.seed(123)
pts <- matrix(runif(2*Npts), ncol = 2) %>%
st_multipoint() %>%
st_sfc() %>%
st_cast("POINT") %>%
st_sf()
Базовая демонстрация
Следующая StatEnvelope
будет принимать набор данных, переданный в соответствующий слой geom, и преобразовывать коллекцию значений геометрии в каждой группе (если эстетика группировки не указана, всянабор данных будет рассматриваться как одна группа) в выпуклый корпус:
StatEnvelope <- ggproto(
"StatEnvelope", Stat,
required_aes = "geometry",
compute_group = function(data, scales) {
if(nrow(data) <= 2) return (NULL)
data %>%
group_by_at(vars(-geometry)) %>%
summarise(geometry = sf::st_convex_hull(sf::st_combine(geometry))) %>%
ungroup()
}
)
ggplot(pts) +
geom_sf() +
geom_sf(stat = StatEnvelope,
alpha = 0.5, color = "grey20", fill = "white", size = 0.5) +
theme_bw()
Обновление
Приведенный выше подход с использованиемсуществующий geom_sf
, отлично справляется с работой при создании конверта.Если мы хотим указать некоторые эстетические параметры по умолчанию, а не повторять их в каждом экземпляре geom_sf
, нам все же не нужно определять новый Geom.Хорошо подойдет функция, которая модифицирует существующий geom_sf
.
geom_envelope <- function(...){
suppressWarnings(geom_sf(stat = StatEnvelope,
..., # any aesthetic argument specified in the function
# will take precedence over the default arguments
# below, with suppressWarning to mute warnings on
# any duplicated aesthetics
alpha = 0.5, color = "grey20", fill = "white", size = 0.5))
}
# outputs same plot as before
ggplot(pts) +
geom_sf() +
geom_envelope() +
theme_bw()
# with different aesthetic specifications for demonstration
ggplot(pts) +
geom_sf() +
geom_envelope(alpha = 0.1, colour = "brown", fill = "yellow", size = 3) +
theme_bw()
Объяснение того, что происходит с кодом, размещенным ввопрос
Когда я возлюсь с настроенными объектами ggproto, мне нравится использовать один полезный прием - вставлять операторы печати в каждую изменяемую мной функцию, например, "setting up parameters"
или "drawing panel, step 3"
и т. д. Это позволяет мнеиметь хорошее представление о том, что происходит под капотом, и отслеживать, где что-то пошло не так, когда функция (неизбежно) возвращает ошибку с 1-й / 2-й / ... / n-й попыткой.
В этом случаеесли мы вставим print("draw group")
в начале функции GeomEnvelope
draw_group
перед запуском ggplot(pts) + geom_sf() + geom_envelope() + theme_bw()
, мы увидим отсутствие какого-либо печатного сообщения в консоли.Другими словами, функция draw_group
никогда не вызывалась , поэтому любые манипуляции с данными, определенные в ней, не влияют на вывод.
В Geom*
есть несколько draw_*
функций,что может сбивать с толку, когда мы хотим внести изменения.Из кода для Geom
мы можем видеть иерархию следующим образом:
draw_layer
(включая строку do.call(self$draw_panel, args)
) draw_panel
(включая строку self$draw_group(group, panel_params, coord, ...)
) draw_group
(что не реализовано для Geom
).
Итак draw_layer
триггеры draw_panel
,и draw_panel
запускает draw_group
.(Отражая это, в Stat
, compute_layer
запускаются compute_panel
и compute_panel
запускаются compute_group
.)
GeomSf
, который наследуется от Geom
(код здесь ), переопределяет функцию Geom
draw_panel
фрагментом кода, который возвращает sf_grob(...)
и НЕ триггер draw_group
.
Следовательно, когда GeomEnvelope
наследует функцию GeomSf
draw_panel
, ничто в ее функции draw_group
не имеет значения.То, что нарисовано на графике, зависит от draw_panel
, и слой geom_envelope
в вопросе выполняет по существу ту же задачу, что и geom_sf
, составляя каждую отдельную точку отдельно.Если вы удалите / закомментируете слой geom_sf
, вы увидите те же точки;только с color = "grey20", alpha = 0.5 и т. д., как указано в GeomSf
s default_aes
.
(Примечание: fill = "white"
не используется, потому что geom_sf
по умолчанию использует GeomPoint
эстетику по умолчанию для точечных данных, что означает, что он наследует GeomPoint
pch = 19
для своей точечной формы и строит сплошной круг, не подверженный влиянию любого значения заливки.)