20

这实际上是两个问题合二为一(不确定是否违反 SO 规则,但无论如何)。

第一个问题是如何强制 ageom_text适应 a geom_bar?(根据绘制的值动态)

环顾四周,我发现的解决方案是改变标签的大小。这当然有效,但并非适用于所有情况。您可以更改特定绘图的大小以使文本适合条形图,但是当数据更改时,您可能需要再次手动更改文本的大小。我现实生活中的问题是我需要为不断变化的数据(每天)生成相同的图,所以我不能真正手动调整每个图的大小。

我尝试将标签的大小设置为数据的函数。它有点工作,不是完美的,但适用于许多情况。

但这是另一个问题,即使标签适合条形,调整情节的大小也会使一切变得混乱。调查它,我还在ggplot 文档中发现

标签确实有高度和宽度,但它们是物理单位,而不是数据单位。它们在该图上占据的空间量在数据单元中不是恒定的:当您调整图的大小时,标签保持相同的大小,但轴的大小会发生变化。

这让我想到了第二个问题:是否可以更改此默认行为并让/使标签随情节调整大小?

也让我完善我的第一个问题。是否可以强制 ageom_text适应 a 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专家可以给我一些想法。

4

2 回答 2

15

一种选择可能是编写一个几何图形,它使用带有自定义 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()

在此处输入图像描述

于 2016-03-31T08:22:59.520 回答
8

如果水平条形图没问题,那么问题不是标签的大小而是位置。我的解决方案是

在此处输入图像描述

由此代码创建:

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_texthjust取决于 y 的值。如果条形短于 y 范围的一半,则文本将打印在条形之外(y 值的右侧)。如果条长于 y 范围的一半,则文本将打印在条内(y 值的左侧)。这确保文本总是打印在绘图区域内(如果不是太长的话)。
  3. 我在栏和文本之间添加了一些额外的空间。如果您希望文本直接在 y 值处开始或结束,您可以使用hjust = ifelse(y < ymid, 0, 1)).
于 2016-03-30T23:04:53.343 回答