英文:
Fix plot position in a Gif - animate plot with dynamic axis labels
问题
我正在尝试制作一个 GIF(每帧中,我想在 y 轴上绘制变量,x 轴上绘制值),但在修复绘图位置方面遇到了一些困难,因为一旦 y 轴上的变量长度发生变化,绘图位置就会移动。
这里是一个可复现的示例:
packs <- c("data.table","tidyverse","scales","magick")
lapply(packs,require,character.only=T)
data <- data.table(variable=c(letters[1:6],"agdjfhgbbkf"),
values=c(1:7))
data$variable_levels <- fct_reorder(data$variable, data$values)
generate_frame <- function(data, i) {
# 创建绘图
p <- ggplot(data[1:i,], aes(x = values, y = variable_levels)) +
geom_col(fill = "#4285F4", position = position_dodge()) +
scale_x_continuous(limits = c(0, max(data$values)),labels = scales::comma) +
scale_y_discrete(limits = rev(levels(data$variable_levels)),
labels = function(x) {
wrap_format(10)(ifelse(x %in% data$variable_levels[1:i], x, ""))
}) +
# 设置 y 轴限制
theme_bw() +
theme(axis.text = element_text(family = "mono"),
panel.grid.major.y = element_blank(),
axis.line.y = element_blank(),
axis.text.y = element_text(hjust = 1, size = 10),
axis.ticks.y = element_blank(),
axis.title.y = element_blank(),
plot.margin = margin(t = 1, r = 1, b = 1, l = 5, unit = "cm"))+
labs(x="Value",y="",title="")
# 将绘图保存为 PNG 文件
ggsave(paste0("frame_", i, ".png"), p, width = 800, height = 400, dpi = 150,units='px')
}
# 生成所有帧
for (i in 1:nrow(data)) {
generate_frame(data, i)
}
# 创建一个空的图像列表
image_list <- image_blank(width = 800, height = 400)
# 读取每个帧并将其添加到图像列表
for (i in 1:nrow(data)) {
frame_file <- paste0("frame_", i, ".png")
frame_image <- image_read(frame_file)
image_list <- c(image_list, frame_image)
}
# 将所有帧合并为 GIF 并保存
image_write(image_list, "animation.gif", format = "gif")
我尝试使用 scale_y_discrete()
中的 expand()
,但它不起作用。
如果有人可以向我展示如何修复绘图位置并定义每帧之间的间隔,我将不胜感激。
附注:我也尝试使用 gganimate
,但不确定如何动态地使 y 轴上的变量发生变化。
英文:
I am trying to make a gif(for each frame I want to plot the variable in the y axis, and the value in x axis), but bit struggled to fix the plot position since it will move once the length of the variable in y axis changes.
Here is one reproducible example:
packs <- c("data.table","tidyverse","scales","magick")
lapply(packs,require,character.only=T)
data <- data.table(variable=c(letters[1:6],"agdjfhgbbkf"),
values=c(1:7))
data$variable_levels <- fct_reorder(data$variable, data$values)
generate_frame <- function(data, i) {
# create the plot
p <- ggplot(data[1:i,], aes(x = values, y = variable_levels)) +
geom_col(fill = "#4285F4", position = position_dodge()) +
scale_x_continuous(limits = c(0, max(data$values)),labels = scales::comma) +
scale_y_discrete(limits = rev(levels(data$variable_levels)),
labels = function(x) {
wrap_format(10)(ifelse(x %in% data$variable_levels[1:i], x, ""))
}) +
# set y-axis limits
theme_bw() +
theme(axis.text = element_text(family = "mono"),
panel.grid.major.y = element_blank(),
axis.line.y = element_blank(),
axis.text.y = element_text(hjust = 1, size = 10),
axis.ticks.y = element_blank(),
axis.title.y = element_blank(),
plot.margin = margin(t = 1, r = 1, b = 1, l = 5, unit = "cm"))+
labs(x="Value",y="",title="")
# save the plot as a png file
ggsave(paste0("frame_", i, ".png"), p, width = 800, height = 400, dpi = 150,units='px')
}
# generate all the frames
for (i in 1:nrow(data)) {
generate_frame(data, i)
}
# create an empty image list
image_list <- image_blank(width = 800, height = 400)
# read in each frame and add it to the image list
for (i in 1:nrow(data)) {
frame_file <- paste0("frame_", i, ".png")
frame_image <- image_read(frame_file)
image_list <- c(image_list, frame_image)
}
# combine all frames into a GIF and save it
image_write(image_list, "animation.gif", format = "gif")
I have tried to use expand()
in scale_y_discrete()
, but it does not work.
I would appreciate it if someone can show me how to fix the plot position and define the interval between each frame.
PS: also tried to use gganimate
, but not sure how to make the occurence of the variable in y axis dynamically
library(gganimate)
library(ggplot2)
ggplot(data, aes(x = variable, y = values)) +
geom_bar(stat = "identity", position = "dodge") +
scale_x_continuous(limits = c(0, max(data$values)),labels = scales::comma) +
scale_y_discrete(limits = rev(levels(data$variable_levels)),
labels = function(x) {
wrap_format(20)(ifelse(x %in% data$variable_levels, x, ""))
}
) +
# set y-axis limits
theme_bw() +
theme(axis.text = element_text(family = "mono"),
panel.grid.major.y = element_blank(),
axis.line.y = element_blank(),
axis.text.y = element_text(hjust = 1, size = 10),
axis.ticks.y = element_blank(),
axis.title.y = element_blank())+
labs(x="Value",y="")+
transition_states(variable_levels, wrap = F) +
shadow_mark()
答案1
得分: 1
这是使用gganimate可能的。如果您的目标是获得带有动画轴标签,我目前看不到绕过自定义注释的方法,例如使用geom_text创建轴标签和使用geom_segment创建刻度。这有点麻烦,因为您需要部分硬编码标签位置并为绘图添加边距。但是,您只需要执行这一步一次。
此外,您还需要使用shadow_mark
。
您可以使用anim_save
保存输出。要指定图像分辨率,请参考https://stackoverflow.com/questions/49058567/define-size-for-gif-created-by-gganimate-change-dimension-resolution/61763614#61763614
suppressMessages(library(tidyverse))
library(gganimate)
data <- data.frame(variable = c(letters[1:6], "agdjfhgbbkf"), values = c(1:7))
data$variable_levels <- fct_reorder(data$variable, data$values)
p <- ggplot(data, aes(x = values, y = variable_levels)) +
geom_col(fill = "#4285F4", position = position_dodge()) +
## size and x nudge are hard coded - might need adjustment.
geom_text(aes(x = 0, label = variable_levels), size = 10 * 5 / 14, hjust = 1, nudge_x = -.1) +
## now the ticks with geom_segment
geom_segment(aes(x = -.05, xend = 0, yend = variable_levels)) +
scale_x_continuous(expand = c(0, 0), labels = scales::comma) +
## need to turn off clipping
coord_cartesian(clip = "off", xlim = c(0, NA)) +
theme( ## remove y axis labels and ticks
axis.text.y = element_blank(),
axis.ticks.y = element_blank(),
## add a margin to y title and to the plot
plot.margin = margin(l = 60, r = 2)
) +
## use NULL to remove titles
labs(x = "Value", y = NULL)
p_anim <- p + transition_states(values) + shadow_mark()
animate(p_anim)
创建于2023-03-07,使用reprex v2.0.2
英文:
This is possible with gganimate. If your aim is to get animated axis labels, I don't currently see a way around custom annotation - e.g., axis labels with geom_text and ticks with geom_segment. This is a bit painful, as you need to partially hard code your label positions and also add a margin to your plot. However, you only need to do this once.
Additionally, you will need shadow_mark
.
You can save the output with anim_save
. To specify your image resolution, follow https://stackoverflow.com/questions/49058567/define-size-for-gif-created-by-gganimate-change-dimension-resolution/61763614#61763614
suppressMessages(library(tidyverse))
library(gganimate)
data <- data.frame(variable = c(letters[1:6], "agdjfhgbbkf"), values = c(1:7))
data$variable_levels <- fct_reorder(data$variable, data$values)
p <-
ggplot(data, aes(x = values, y = variable_levels)) +
geom_col(fill = "#4285F4", position = position_dodge()) +
## size and x nudge are hard coded - might need adjustment.
geom_text(aes(x = 0, label = variable_levels), size = 10 * 5 / 14, hjust = 1, nudge_x = -.1) +
## now the ticks with geom_segment
geom_segment(aes(x = -.05, xend = 0, yend = variable_levels)) +
scale_x_continuous(expand = c(0, 0), labels = scales::comma) +
## need to turn off clipping
coord_cartesian(clip = "off", xlim = c(0, NA)) +
theme( ## remove y axis labels and ticks
axis.text.y = element_blank(),
axis.ticks.y = element_blank(),
## add a margin to y title and to the plot
plot.margin = margin(l = 60, r = 2)
) +
## use NULL to remove titles
labs(x = "Value", y = NULL)
p_anim <- p + transition_states(values) + shadow_mark()
animate(p_anim)
<!-- -->
<sup>Created on 2023-03-07 with reprex v2.0.2</sup>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论