R 数据可视化 —— gtable 绘制多个 Y 轴(补充)

本文介绍了如何在R语言的ggplot2库中,通过gtable和grid功能,实现将Y轴添加到图形的左侧,并处理轴标签、刻度线等元素,同时展示了一个可扩展的函数`plot_multi_yaxis`,用于合并多个带有不同Y轴的图形。
摘要由CSDN通过智能技术生成

前言

上一节所介绍的绘制多个 Y 轴,只能在图形的右侧依次添加 Y 轴。

Y 轴数量过多的情况下(当然,轴不应该太多),将轴平均地放置在左右两侧会更美观些。

因此,这节主要介绍如何在图形的左侧添加 Y

添加 Y 轴

总的来说,将 Y 轴添加到左侧会更简单,不需要对坐标轴、刻度标签及轴标签进行转换。主要获取到轴对象及轴标签对象,将其添加到左侧即可。

对于下面两张图

colors <- c('#5470C6', '#91CC75', '#EE6666', '#ff7f00')
data <- data.frame(
  category = factor(substr(month.name, 1, 3), levels = substr(month.name, 1, 3)),
  Evaporation = c(2.0, 4.9, 7.0, 23.2, 25.6, 76.7, 135.6, 162.2, 32.6, 20.0, 6.4, 3.3),
  Precipitation = c(2.6, 5.9, 9.0, 26.4, 28.7, 70.7, 175.6, 182.2, 48.7, 18.8, 6.0, 2.3),
  Temperature = c(2.0, 2.2, 3.3, 4.5, 6.3, 10.2, 20.3, 23.4, 23.0, 16.5, 12.0, 6.2)
)

p1 <- ggplot(data, aes(category, Evaporation)) + 
  geom_col(fill = colors[1], width = 0.3, position = position_nudge(x = -0.2)) + 
  labs(x = "month", y = "Evaporation(ml)") +
  scale_y_continuous(limits = c(0, 250), expand = c(0,0)) +
  theme(
        axis.text.y = element_text(color = colors[1]), 
        axis.ticks.y = element_line(color = colors[1]), 
        axis.title.y = element_text(color = colors[1]), 
        axis.line.y = element_line(color = colors[1]), 
        axis.line.x = element_line(color = "black"),
        axis.text.x = element_text(angle = 45, hjust = 1, vjust = 1)
  )
p1

p2 <- ggplot(data, aes(category, Precipitation)) + 
  geom_col(fill = colors[2], width = 0.3, position = position_nudge(x = 0.2)) + 
  labs(x = "month", y = "Precipitation(ml)") +
  scale_y_continuous(limits = c(0, 250), expand = c(0,0))  +
  theme( 
        axis.text.y = element_text(color = colors[2]), 
        axis.ticks.y = element_line(color = colors[2]), 
        axis.title.y = element_text(color = colors[2]), 
        axis.line.y = element_line(color = colors[2]), 
        axis.text.x = element_text(angle = 45, hjust = 1, vjust = 1)
  )
p2

获取 gtable 对象

my_theme <- theme(panel.grid = element_blank(), panel.background = element_rect(fill = NA))

g1 <- ggplotGrob(p1 + my_theme)
g2 <- ggplotGrob(p2 + my_theme)

合并主绘图区域的代码是一样的

pos <- c(subset(g1$layout, name == "panel", select = t:r))

g1 <- gtable_add_grob(g1, g2$grobs[[which(g2$layout$name == "panel")]], 
                      pos$t, pos$l, pos$b, pos$l)
plot(g1)

获取 Y 轴及 Y 轴标签的位置信息

index <- which(g2$layout$name == "axis-l")
yaxis <- g2$grobs[[index]]

pos <- c(subset(g1$layout, name == "ylab-l", select = t:r))

首先,添加一个 3mm 的空白间距。注意是在轴标签位置的左侧添加是(pos$l - 1

g <- gtable_add_cols(g1, unit(3, "mm"), pos$l - 1)

然后将 Y 轴添加到一个新的列

g <- gtable_add_cols(g, g2$widths[g2$layout[index, ]$l], pos$l - 1)
g <- gtable_add_grob(g, yaxis, pos$t, pos$l, pos$b, pos$l, clip = "off")
plot(g)

添加轴标签也是类似的

index <- which(g2$layout$name == "ylab-l")
ylab <- g2$grobs[[index]]
g <- gtable_add_cols(g, g2$widths[g2$layout[index, ]$l], pos$l - 1)
g <- gtable_add_grob(g, ylab, pos$t, pos$l, pos$b, pos$l, clip = "off")

这样就可以啦。

我们可以将上次的代码改写,使其可以根据传入图形的数量来决定轴的添加位置。改写的代码如下

library(ggplot2)
library(gtable)
library(grid)


# 反转标题 grobs 的对齐方式和位置
hinvert_title_grob <- function(grob){
    # 交换宽度
    widths <- grob$widths
    grob$widths[1] <- widths[3]
    grob$widths[3] <- widths[1]
    # 在一些版本的ggplot2中,grob 对象可能不包含 vp 或 layout 组件
    if (!is.null(grob$vp) && !is.null(grob$vp[[1]]$layout)) {
        grob$vp[[1]]$layout$widths[1] <- widths[3]
        grob$vp[[1]]$layout$widths[3] <- widths[1]
    }
    # 修改对齐方式
    grob$children[[1]] <- editGrob(
        grob$children[[1]], 
        hjust = 1 - grob$children[[1]]$hjust, 
        vjust = 1 - grob$children[[1]]$vjust, 
        x = unit(1, "npc") - grob$children[[1]]$x
    )
    grob
}
# 左侧添加Y轴
add_yaxis_left <- function(g1, g2) {
    # 获取左侧Y轴标签的位置
    pos <- c(subset(g1$layout, name == "ylab-l", select = t:r))
    index <- which(g2$layout$name == "axis-l")
    yaxis <- g2$grobs[[index]]
    # 添加列并插入Y轴
    g <- gtable_add_cols(g1, unit(3, "mm"), pos$l - 1)
    g <- gtable_add_cols(g, g2$widths[g2$layout[index, ]$l], pos$l - 1)
    g <- gtable_add_grob(g, yaxis, pos$t, pos$l, pos$b, pos$l, clip = "off")
    # 获取左侧Y轴标签
    index <- which(g2$layout$name == "ylab-l")
    ylab <- g2$grobs[[index]]
    # 插入Y轴标签
    g <- gtable_add_cols(g, g2$widths[g2$layout[index, ]$l], pos$l - 1)
    g <- gtable_add_grob(g, ylab, pos$t, pos$l, pos$b, pos$l, clip = "off")
    g
}
# 右侧添加Y轴
add_yaxis_right <- function(g1, g2, pos) {
    # 获取右侧Y轴标签
    index <- which(g2$layout$name == "ylab-l")
    ylab <- g2$grobs[[index]]
    ylab <- hinvert_title_grob(ylab)
    # 添加列并插入Y轴标签
    g <- gtable_add_cols(g1, g2$widths[g2$layout[index, ]$l], pos$r)
    g <- gtable_add_grob(g, ylab, pos$t, pos$r + 1, pos$b, pos$r + 1, clip = "off", name = "ylab-r")
    # 获取右侧Y轴
    index <- which(g2$layout$name == "axis-l")
    yaxis <- g2$grobs[[index]]
    # 调整Y轴线位置
    yaxis$children[[1]]$x <- unit.c(unit(0, "npc"), unit(0, "npc"))
    # 获取刻度
    ticks <- yaxis$children[[2]]
    # 调整刻度线位置
    for (i in seq_along(ticks$grobs)) {
        # 刻度标签,3.5 和 3.4 中 ticks$grobs 对象的属性数量不一样,所以根据类型来判断
        if (inherits(ticks$grobs[[i]], "titleGrob")) {
            ticks$grobs[[i]] <- hinvert_title_grob(ticks$grobs[[i]])
        # 刻度线
        } else if (inherits(ticks$grobs[[i]], "polyline")) {
            ticks$grobs[[i]]$x <- ticks$grobs[[i]]$x - unit(1, "npc") + unit(3, "pt")
        }
    }
    # 反转刻度线和刻度标签
    ticks$widths <- rev(ticks$widths)
    ticks$grobs <- rev(ticks$grobs)
    
    yaxis$children[[2]] <- ticks
    # 添加列并插入Y轴
    g <- gtable_add_cols(g, g2$widths[g2$layout[index, ]$l] + unit(3, "mm"), pos$r)
    g <- gtable_add_grob(g, yaxis, pos$t, pos$r + 1, pos$b, pos$r + 1, clip = "off", name = "axis-r")
    g
}
# 添加Y轴函数,根据偏移量判断添加在左侧或右侧
add_yaxis <- function(g1, g2, offset = 0) {
    pos <- c(subset(g1$layout, name == "panel", select = t:r))
    # 添加主绘图区域
    g1 <- gtable_add_grob(g1, g2$grobs[[which(g2$layout$name == "panel")]], 
                          pos$t, pos$l, pos$b * ((offset - 2) * 0.00001 + 1), pos$l)
    # 根据偏移量判断添加在左侧或右侧
    if (offset > 3 && offset %% 2 == 0) {
        g1 <- add_yaxis_left(g1, g2)
    } else {
        g1 <- add_yaxis_right(g1, g2, pos)
    }
    g1
}
# 绘制多Y轴图形的函数
plot_multi_yaxis <- function(..., right_label_reverse = TRUE) {
    args <- list(...)
    my_theme <- theme(panel.grid = element_blank(), panel.background = element_rect(fill = NA))
    len <- length(args)
    args[[1]] <- args[[1]] + my_theme
    g <- ggplotGrob(args[[1]])
    for (i in len:2) { 
        if (i < 4 || i %% 2 && right_label_reverse) {
            # 旋转轴标签
            args[[i]] <- args[[i]] + 
                theme(axis.title.y = element_text(angle = 270))
        }
        args[[i]] <- args[[i]] + my_theme
        g2 <- ggplotGrob(args[[i]])
        g <- add_yaxis(g, g2, offset = i)
    }
    # 绘制图形
    grid.newpage()
    grid.draw(g)
}

GitHub 代码也更新为该版本:
https://github.com/dxsbiocc/learn/blob/main/R/plot/plot_multi_yaxis.R

测试效果

先添加第三张图

p3 <- ggplot(data, aes(category, Temperature, group = 1)) + 
  geom_line(colour = colors[3]) + 
  geom_point(aes(colour = colors[3]), fill = "white", shape = 21, show.legend = FALSE) +
  scale_y_continuous(limits = c(0, 25), expand = c(0,0)) +
  labs(x = "month", y = expression(paste("Temperature (", degree, " C)"))) +
  theme(
        axis.text.y = element_text(color = colors[3]), 
        axis.ticks.y = element_line(color = colors[3]), 
        axis.title.y = element_text(color = colors[3]), 
        axis.line.y = element_line(color = colors[3]), 
        axis.text.x = element_text(angle = 45, hjust = 1, vjust = 1)
  )

合并三张图

plot_multi_yaxis(p1, p2, p3)

再添加第四张图

library(dplyr)

set.seed(100)

p4 <- mutate(data, Temperature = rev(Temperature) + rnorm(12)) %>%
  ggplot(aes(category, Temperature, group = 1)) + 
  geom_line(colour = colors[4]) + 
  geom_point(aes(colour = colors[4]), fill = "white", shape = 21, show.legend = FALSE) +
  scale_y_continuous(limits = c(0, 25), expand = c(0,0)) +
  labs(x = "month", y = expression(paste("Temperature (", degree, " C)"))) +
  theme(
    axis.text.y = element_text(color = colors[4]), 
    axis.ticks.y = element_line(color = colors[4]), 
    axis.title.y = element_text(color = colors[4]), 
    axis.line.y = element_line(color = colors[4]), 
    axis.text.x = element_text(angle = 45, hjust = 1, vjust = 1)
  )

合并四张图

plot_multi_yaxis(p1, p2, p3, p4)

再添加两张,当然这样做是没什么道理的。只是为了说明函数依然能够完美工作

plot_multi_yaxis(p1, p2, p3, p4, p1, p2)

  • 6
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

名本无名

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值