Подтвердить что ты не робот

Ggplot2: geom_text изменяет размер с помощью графика и текста силы/подгонки в пределах geom_bar

На самом деле это два вопроса в одном (не уверен, идет ли речь о правилах SO, но в любом случае).

Первый вопрос: как я могу заставить geom_text вписаться в geom_bar? (динамически в соответствии со значениями)

Оглядываясь, решения, которые я нашел, меняли размер метки. Это, безусловно, работает, но не для каждого случая. Вы можете изменить размер для конкретного графика, чтобы текст был помещен в панель, но при изменении данных вам может потребоваться вручную изменить размер текста снова. Моя реальная проблема заключается в том, что мне нужно создать один и тот же сюжет для постоянного изменения данных (ежедневно), поэтому я не могу вручную настроить размер для каждого сюжета.

Я попытался установить размер метки как функцию данных. Это работает, но не идеально, но работает во многих случаях.

Но вот еще одна проблема, даже когда ярлык вписывается в панель, изменение размера заговора все испортит. Взглянув в нее, я также нашел в ggplot documentation, что

метки имеют высоту и ширину, но они являются физическими единицами, а не данными единицы. Количество места, которое они занимают на этом участке, не является постоянным в Единицы данных: при изменении размера графика метки остаются одинаковыми, но размер осей изменяется.

Который берет меня на мой второй вопрос: возможно ли изменить это поведение по умолчанию и позволить/сделать этикетки изменяться с помощью графика?

И позвольте мне уточнить мой первый вопрос. Можно ли принудительно установить geom_text в пределах geom_bar, динамически определяя размер текста, используя умную связь между физическими единицами и единицами данных?

Итак, чтобы следовать хорошей практике, вот мой воспроизводимый пример:

set.seed(1234567)
data_gd <- data.frame(x = letters[1:5], 
                      y = runif(5, 100, 99999))

ggplot(data = data_gd,
       mapping = aes(x = x, y = y, fill = x)) +
    geom_bar(stat = "identity") +
    geom_text(mapping = aes(label = y, y = y/2))

Этот код создает этот график:

введите описание изображения здесь

Если я просто изменяю размер сюжета, метки введите описание изображения здесь

Итак, это мой второй вопрос. Было бы неплохо, чтобы этикетки также изменялись и сохраняли пропорции по отношению к барам. Любые идеи, как это сделать или если это возможно вообще?

Хорошо, но, вернувшись к тому, как установить метки внутри столбцов, самым простым решением является установка размера меток.

ggplot(data = data_gd,
       mapping = aes(x = x, y = y, fill = x)) +
    geom_bar(stat = "identity") +
    geom_text(mapping = aes(label = y, y = y/2), size = 3)

Опять же, это работает, как показано ниже, но оно не поддерживает и не поддерживает изменения в данных.

введите описание изображения здесь

Например, тот же самый код для генерации графика с разными данными дает катастрофические результаты.

data_gd <- data.frame(x = letters[1:30], 
                      y = runif(30, 100, 99999))
ggplot(data = data_gd,
       mapping = aes(x = x, y = y, fill = x)) +
    geom_bar(stat = "identity") +
    geom_text(mapping = aes(label = y, y = y/2), size = 3)

введите описание изображения здесь

И я могу продолжить примеры, установив размер меток как функцию количества категорий по оси х и т.д. Но вы понимаете, и, возможно, один из вас может предложить мне теги ggplot2.

4b9b3361

Ответ 1

одним из вариантов может быть запись геометрии, которая использует textGrob с помощью специального метода drawDetails для размещения в выделенном пространстве, заданного шириной полосы.

library(grid)
library(ggplot2)

fitGrob <- function(label, x=0.5, y=0.5, width=1){
  grob(x=x, y=y, width=width, label=label, cl = "fit")
}
drawDetails.fit <- function(x, recording=FALSE){
  tw <- sapply(x$label, function(l) convertWidth(grobWidth(textGrob(l)), "native", valueOnly = TRUE))
  cex <- x$width / tw
  grid.text(x$label, x$x, x$y, gp=gpar(cex=cex), default.units = "native")
}


`%||%` <- ggplot2:::`%||%`

GeomFit <- ggproto("GeomFit", GeomRect,
                   required_aes = c("x", "label"),

                   setup_data = function(data, params) {
                     data$width <- data$width %||%
                       params$width %||% (resolution(data$x, FALSE) * 0.9)
                     transform(data,
                               ymin = pmin(y, 0), ymax = pmax(y, 0),
                               xmin = x - width / 2, xmax = x + width / 2, width = NULL
                     )
                   },
                   draw_panel = function(self, data, panel_scales, coord, width = NULL) {
                     bars <- ggproto_parent(GeomRect, self)$draw_panel(data, panel_scales, coord)
                     coords <- coord$transform(data, panel_scales)    
                     width <- abs(coords$xmax - coords$xmin)
                     tg <- fitGrob(label=coords$label, y = coords$y/2, x = coords$x, width = width)

                     grobTree(bars, tg)
                   }
)

geom_fit <- function(mapping = NULL, data = NULL,
                     stat = "count", position = "stack",
                     ...,
                     width = NULL,
                     binwidth = NULL,
                     na.rm = FALSE,
                     show.legend = NA,
                     inherit.aes = TRUE) {

  layer(
    data = data,
    mapping = mapping,
    stat = stat,
    geom = GeomFit,
    position = position,
    show.legend = show.legend,
    inherit.aes = inherit.aes,
    params = list(
      width = width,
      na.rm = na.rm,
      ...
    )
  )
}


set.seed(1234567)
data_gd <- data.frame(x = letters[1:5], 
                      y = runif(5, 100, 99999))

ggplot(data = data_gd,
       mapping = aes(x = x, y = y, fill = x, label=round(y))) +
  geom_fit(stat = "identity") +
  theme()

введите описание изображения здесь

Ответ 2

Если горизонтальные гистограммы в порядке, то проблема заключается не в размере ярлыков, а в размещении. Мое решение было бы

введите описание изображения здесь

созданный с помощью этого кода:

library(ggplot2)
data_gd <- data.frame(x = letters[1:26], 
                      y = runif(26, 100, 99999))
ymid <- mean(range(data_gd$y))
ggplot(data = data_gd,
       mapping = aes(x = x, y = y, fill = x)) +
  geom_bar(stat = "identity") +
  geom_text(mapping = aes(label = y, y = y, 
            hjust = ifelse(y < ymid, -0.1, 1.1)), size = 3) +
  coord_flip()

Трюк выполняется в три этапа:

  • coord_flip делает горизонтальную гистограмму.
  • Отображение в geom_text также используется в зависимости от значения y. Если полоса короче половины диапазона y, текст печатается за пределами полосы (справа от значения y). Если полоса длиннее половины диапазона y, текст печатается внутри панели (слева до значения y). Это гарантирует, что текст всегда печатается внутри области графика (если не слишком длинный).
  • Я добавил дополнительное пространство между строкой и текстом. Если вы хотите, чтобы текст начинался или заканчивался непосредственно по значению y, вы можете использовать hjust = ifelse(y < ymid, 0, 1)).