R语言镜像条形图优化:差异标签居中与%H:%M格式显示
镜像条形图优化方案
一、核心优化方向及实现
1. 大幅简化代码
通过统一数据预处理逻辑,避免重复编写分类型的绘图代码,同时优化排序逻辑:
- 给
tv类型数值添加负号,实现上下镜像布局,无需分别绘制正负轴图层 - 将月份转换为反转因子,直接实现最早月份置顶,移除冗余的
rev()调用 - 转长格式数据,用单个geom层绘制所有用户条形
2. 差异标签精准居中
重新计算标签位置,确保标签位于两个条形的水平中点:
- 标签位置公式:
(user1_scaled + user2_scaled)/2/60,其中user1_scaled和user2_scaled已处理正负值 - 移除手动调整
vjust和position_dodge的逻辑,直接用计算好的位置实现居中
3. 分钟转%H:%M格式
自定义转换函数,统一处理轴刻度和差异标签的格式,支持超过24小时的时长显示:
min_to_hhmm <- function(minutes) { hours <- floor(minutes / 60) mins <- minutes %% 60 sprintf("%02d:%02d", hours, mins) }
二、完整优化代码
set.seed(123) library(tidyverse) library(ggplot2) library(zoo) library(ggnewscale) # 自定义分钟转HH:MM格式函数 min_to_hhmm <- function(minutes) { hours <- floor(minutes / 60) mins <- minutes %% 60 sprintf("%02d:%02d", hours, mins) } # 原始数据 dat <- tibble( user = rep(c("user1", "user2"), each = 6), type = rep(c("tv", "movie"), each = 6), yearmonth = as.yearmon(rep(seq.Date(as.Date("2024-12-01"), as.Date("2025-02-01"), "month"), 4)), cumulative_minutes = sample(200:900, 12, replace = TRUE) ) # 统一数据预处理 processed_dat <- dat %>% pivot_wider(names_from = user, values_from = cumulative_minutes) %>% mutate( # 给tv类型数值加负号,实现镜像 value_multiplier = ifelse(type == "tv", -1, 1), user1_scaled = user1 * value_multiplier, user2_scaled = user2 * value_multiplier, diff = user1 - user2, # 计算标签居中位置 label_pos = (user1_scaled + user2_scaled) / 2 / 60, # 月份反转因子,实现最早月份置顶 yearmonth_fct = fct_rev(factor(yearmonth)) ) %>% # 转长格式,方便统一绘图 pivot_longer(cols = c(user1_scaled, user2_scaled), names_to = "user", values_to = "scaled_minutes") %>% mutate(user = str_remove(user, "_scaled")) # 绘图 ggplot(processed_dat, aes(x = yearmonth_fct)) + # 差异背景条 geom_col( data = processed_dat %>% distinct(type, yearmonth_fct, user1, user2, value_multiplier), aes(y = max(user1, user2) * value_multiplier / 60, fill = ifelse(user1 > user2, "#C7E9C0", "#FCBBA1")), alpha = 0.7 ) + # 用户条形 geom_col(aes(y = scaled_minutes / 60, fill = user), position = position_dodge(width = 0.8), width = 0.7) + # 差异标签(HH:MM格式) geom_text( data = processed_dat %>% distinct(type, yearmonth_fct, diff, label_pos), aes(y = label_pos, label = min_to_hhmm(abs(diff))), size = 3, color = "black" ) + # Movies填充色设置 scale_fill_manual( values = c("user1" = "#1B9E77", "user2" = "#D95F02", "#C7E9C0" = "#C7E9C0", "#FCBBA1" = "#FCBBA1"), breaks = c("user1", "user2"), guide = guide_legend("Movies") ) + new_scale_fill() + # TV填充色设置 scale_fill_manual( values = c("user1" = "#E41A1C", "user2" = "#377EB8", "#C7E9C0" = "#C7E9C0", "#FCBBA1" = "#FCBBA1"), breaks = c("user1", "user2"), guide = guide_legend("TV") ) + # Y轴刻度转为HH:MM格式 scale_y_continuous( labels = function(y) min_to_hhmm(abs(y)*60), name = "时长" ) + # X轴标签格式化 scale_x_discrete(labels = function(x) as.yearmon(x)) + labs(x = "月份") + theme_light() + coord_flip()
内容的提问来源于stack exchange,提问作者devster
相关产品推荐
相关产品推荐

