簡體   English   中英

ggplot2:geom_text 在 geom_bar 中使用繪圖和強制/適合文本調整大小

[英]ggplot2: geom_text resize with the plot and force/fit text within geom_bar

這實際上是兩個問題合二為一(不確定是否違反 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))

此代碼生成此圖:

在此處輸入圖片說明

如果我只是調整繪圖的大小,“標簽保持相同的大小,但軸的大小會改變”,從而使標簽適合條形(現在標簽甚至可能太小了)。

在此處輸入圖片說明

所以,這是我的第二個問題。 標簽也可以調整大小並保持與條的縱橫比,這將是很好的。 任何想法如何實現這一點,或者是否有可能?

好的,但是回到如何將標簽放入條中,最簡單的解決方案是設置標簽的大小。

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)

在此處輸入圖片說明

我可以繼續舉例,將標簽的大小設置為 x 軸上類別數量的函數等等。 但是你明白了,也許你們中的ggplot2專家可以給我一些想法。

一種選擇可能是編寫一個 geom,它使用帶有自定義 drawDetails 方法的 textGrob 以適應分配的空間,由條寬設置。

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

在此處輸入圖片說明

如果水平條形圖沒問題,那么問題不在於標簽的大小,而在於位置。 我的解決方案是

在此處輸入圖片說明

由此代碼創建:

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的映射也根據 y 的值使用hjust 如果條形短於 y 范圍的一半,則文本將打印在條形之外(y 值的右側)。 如果條形長度超過 y 范圍的一半,則文本將打印在條形內(y 值的左側)。 這確保文本始終打印在繪圖區域內(如果不是太長的話)。
  3. 我在欄和文本之間添加了一些額外的空間。 如果您希望文本直接在 y 值處開始或結束,您可以使用hjust = ifelse(y < ymid, 0, 1))

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM