ggplot2: изменение размера текста geom с помощью графика и текста force / fit в панели geom

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

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

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

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

но вот еще одна проблема, даже когда метка вписывается в панель, изменение размера участка все портит. Заглянув в него, я также обнаружил в документация ggplot это

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

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

и позвольте мне уточнить мой первый вопрос. Можно ли заставить 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))

этот код создает этот сюжет:

enter image description here

если я просто изменить сюжет, "метки остаются того же размера, но размер осей изменяется" таким образом, ярлыки вписываются в бары (теперь, возможно, ярлыки даже слишком малы).

enter image description here

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

Ok, но возвращаясь к тому, как соответствовать меткам в барах, самое простое решение-установить размер этикеток.

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)

опять же, это работает, как показано ниже, но он не ремонтопригоден /и не устойчив к изменениям в данных.

enter image description here

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

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)

enter image description here

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

2 ответов


одним из вариантов может быть запись geom, которая использует 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()

enter image description here


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

enter image description here

созданный этим кодом:

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()

трюк делается в три шага:

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