在R中使用Highcharts实现分组堆叠百分比柱状图及同比变化展示
在R中使用Highcharts创建分组堆叠柱状图的解决方案
问题背景
需要生成满足以下要求的柱状图:
- X轴为
Zone,每个Zone对应TTM2和TTM1两个并列柱状图组 - 每个柱状图组内按
Revenue Type堆叠 - 堆叠段显示该类型的占比,但柱高保留实际数值总和
- 展示同一Zone下TTM1与TTM2的百分比变化
样本数据表
| Zone | Revenue Type | TTM2 | TTM1 |
|---|---|---|---|
| Americas | SaaS | 901 | 1001 |
| Americas | Subscriptions | 902 | 1002 |
| Americas | Perpetual Maintenance | 903 | 1003 |
| Americas | Perpetual | 904 | 1004 |
| APAC | SaaS | 905 | 1005 |
| APAC | Subscriptions | 906 | 1006 |
| APAC | Perpetual Maintenance | 907 | 1007 |
| APAC | Perpetual | 908 | 1008 |
| EMEA | SaaS | 909 | 1009 |
| EMEA | Subscriptions | 910 | 1010 |
| EMEA | Perpetual Maintenance | 911 | 1011 |
| EMEA | Perpetual | 912 | 1012 |
当前代码问题
现有代码将TTM1和TTM2的柱子错误堆叠在一起,且无法实现"堆叠显示占比、柱高保留实际总和"的效果。
解决方案
1. 数据预处理
先计算每个分组的总和、占比,以及TTM间的百分比变化:
library(highcharter) library(data.table) # 原始数据 dt <- data.table::data.table( Zone = c("Americas", "Americas", "Americas", "Americas", "APAC", "APAC", "APAC", "APAC", "EMEA", "EMEA", "EMEA", "EMEA"), Revenue_Type = c("SaaS", "Subscriptions", "Perpetual Maintenance", "Perpetual", "SaaS", "Subscriptions", "Perpetual Maintenance", "Perpetual", "SaaS", "Subscriptions", "Perpetual Maintenance", "Perpetual"), TTM2 = c(901, 902, 903, 904, 905, 906, 907, 908, 909, 910, 911, 912), TTM1 = c(1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009, 1010, 1011, 1012) ) # 转长格式 dt_melt <- melt(dt, id.vars = c("Zone", "Revenue_Type"), measure.vars = c("TTM1", "TTM2"), variable.name = "TTM", value.name = "value") # 计算每个TTM+Zone的总和与占比 dt_melt[, total := sum(value), by = .(Zone, TTM)] dt_melt[, percentage := round(value / total * 100, 1)] # 计算每个Zone的TTM1与TTM2百分比变化 change_data <- dt_melt[, .(total = sum(value)), by = .(Zone, TTM)] %>% dcast(Zone ~ TTM, value.var = "total") %>% .[, change := round((TTM2 - TTM1)/TTM1 * 100, 1)]
2. 构建目标图表
通过指定堆叠组实现分组并列,自定义标签和tooltip显示占比:
# 获取所有收入类型 rev_types <- unique(dt_melt$Revenue_Type) # 初始化图表 hc <- highchart() %>% hc_xAxis(categories = unique(dt_melt$Zone), title = list(text = "Zone")) %>% hc_yAxis(title = list(text = "Total Revenue")) %>% hc_plotOptions( column = list( grouping = TRUE, # 同一Zone下TTM1/TTM2并列 stacking = "normal", # 同一TTM内按收入类型堆叠 dataLabels = list( enabled = TRUE, formatter = JS("function() { return this.point.percentage + '%'; }") ) ) ) %>% hc_tooltip( formatter = JS("function() { return '<b>' + this.x + ' - ' + this.series.name + '</b><br/>' + this.point.TTM + ': $' + this.point.value + '<br/>' + '占比: ' + this.point.percentage + '%'; }") ) %>% hc_legend(title = list(text = "Revenue Type")) # 为每个收入类型添加TTM1、TTM2两个系列 for (rt in rev_types) { # TTM1系列 hc <- hc %>% hc_add_series( data = dt_melt[Revenue_Type == rt & TTM == "TTM1"], type = "column", name = rt, hcaes(x = Zone, y = value, percentage = percentage, TTM = TTM), stack = "TTM1" # 指定堆叠组为TTM1 ) # TTM2系列 hc <- hc %>% hc_add_series( data = dt_melt[Revenue_Type == rt & TTM == "TTM2"], type = "column", name = rt, hcaes(x = Zone, y = value, percentage = percentage, TTM = TTM), stack = "TTM2" # 指定堆叠组为TTM2 ) } # 添加百分比变化标注(显示在柱子上方) hc <- hc %>% hc_add_series( data = change_data, type = "scatter", name = "同比变化", hcaes(x = Zone, y = pmax(TTM1, TTM2) * 1.1), # 定位到较高柱子上方10%位置 marker = list(enabled = FALSE), dataLabels = list( enabled = TRUE, formatter = JS("function() { return this.point.change >= 0 ? '+' + this.point.change + '%' : this.point.change + '%'; }"), style = list(fontWeight = "bold") ), tooltip = list(formatter = JS("function() { return '<b>' + this.x + '</b><br/>TTM2 vs TTM1: ' + this.point.change + '%'; }")) ) # 显示图表 hc
关键实现细节
- 分组堆叠分离:通过
stack参数将TTM1和TTM2分为独立堆叠组,实现同一Zone下两组柱子并列、组内堆叠 - 占比显示:利用自定义JS函数在数据标签中展示预计算的占比,柱高由实际数值决定
- 百分比变化:用散点系列将变化值标注在柱子上方,避免干扰堆叠结构
内容的提问来源于stack exchange,提问作者Shubham
相关产品推荐
相关产品推荐

