Избегание столкновения ярлыка осевой оси в гранях ggplots

В многопанельных/граненых графиках мне нравится, когда возможно скручивать панели вместе без пробелов между ними, например. используя theme(panel.spacing=grid::unit(0,"lines")) (Эдвард Туфте говорит, что это хорошо, потому что межпанельные пространства создают отвлекающие визуальные эффекты, а также тратят пространство данных, и я выпил этот конкретный бит Kool-Aid.)

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

dd <- data.frame(x=rep(1:3,3),
                 y=c(0.1,0.2,0.3,
                     0.1,0.4,0.6,
                     1,2,3),
                 f=factor(rep(letters[1:3],each=3)))
library(ggplot2)
ggplot(dd,aes(x,y))+
   facet_grid(f~.,scale="free")+
   geom_point()+
   theme_bw(base_size=24)+
   theme(panel.spacing=grid::unit(0,"lines"))
ggsave("tmp1.png",width=4,height=6)

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

Я хочу создать общее, удобное решение этой проблемы - расширение пределов каждой грани на соответствующую сумму (она будет различной для каждой грани, поскольку диапазоны являются гетерогенными), но подавляя (по крайней мере) метки и ( возможно) отметки для экстремальных значений. Я сделал это в прошлом в супер-хакерском режиме, установив специальную функцию breaks в scale_y_continuous. Возможно, я подумал о некоторых других способах сделать это (и опубликую их как ответ, если я смогу заставить их работать), но я ищу (а) достаточно надежную общую функцию для указания labels и/или breaks.

Это не то же самое, что Автоматизировать галочку max и min в гранжевом ggplot, который просто хочет получить значения max/min по граням.

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

Ответ 1

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

Какая разница между этим и использованием expand внутри scale_y_continuous (кроме того, что можно выбрать панели)? Эта функция оставляет перерывы (и метки и линии сетки), как они были в оригинале. Просто они занимают меньше места. expand, однако, сжимает масштаб, но ggplot добавит новые метки, линии сетки и отметки, если это возможно.

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

dd <- data.frame(x=rep(1:3,3),
                 y=c(0.1,0.2,0.3,
                     0.1,0.4,0.6,
                     1,2,3),
                 f=factor(rep(letters[1:3],each=3)))
library(ggplot2)


p = ggplot(dd,aes(x,y))+
   facet_grid(f~.,scale="free")+
   geom_point()+
   theme_bw(base_size=24)+
   theme(panel.spacing=grid::unit(0,"lines"))


expand = function(plot, mult = 0.1, applyTo) {
# Get the build data
gd = ggplot_build(plot)

# Get the original limits,
# and calculate the new limits,
# expanded according to the multiplicative factor
limits <- sapply(gd$layout$panel_ranges, "[[", "y.range")    # Original limits
fac = mult*(limits[2,] - limits[1, ])  
newlimits = rbind(limits[1, ] - fac, limits[2, ] + fac)

# The range for the new limits
range = newlimits[2, ] - newlimits[1, ]

# Calculate the new y.major and y.minor relative positions
# and put them along with the new limits back into the build data

N = dim(gd$layout$panel_layout)[1]  # Number of panels

for(i in applyTo) {
y.major = (gd$layout$panel_ranges[[i]]$y.major_source - newlimits[1, i]) / range[i]
y.minor = (gd$layout$panel_ranges[[i]]$y.minor_source - newlimits[1, i]) / range[i]

gd$layout$panel_ranges[[i]]$y.range = newlimits[, i]

gd$layout$panel_ranges[[i]]$y.major = y.major
gd$layout$panel_ranges[[i]]$y.minor = y.minor
}

# Get the gtable
ggplot_gtable(gd)
}


# Plot it
grid::grid.draw(expand(p, mult = 0.1, applyTo = 1:2))

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

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