This is actually two questions in one (not sure if goes against SO rules, but anyway).
First question is how can I force a geom_text
to fit within a
If horizontal bar charts are OK, then the issue is not the size of the labels but the placement. My solution would be
created by this code:
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()
The trick is done in three steps:
coord_flip
makes a horizontal bar chart.geom_text
uses also hjust
depending on the value of y. If the bar is shorter than half of the range of y, the text is printed outside of the bar (right to the y value). If the bar is longer than half of the range of y, the text is printed inside the bar (left to the y value). This makes sure that the text is always printed inside the plot area (if not too long at all).hjust = ifelse(y < ymid, 0, 1))
.one option might be to write a geom that uses a textGrob with a custom drawDetails method to fit within the allocated space, set by the bar width.
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()