You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Echarts4r:基于图例选择动态更新累计求和结果

动态更新堆叠柱状图的累计求和折线(Echarts4r + Shiny)

核心问题分析

当前代码中cum_sum_total是基于所有program的总和提前计算的,当图例隐藏部分系列时,累计值无法自动适配剩余系列的总和。解决方案是通过ECharts的事件监听机制,在图例选中状态变化时实时计算累计值。

实现思路

  • 移除提前计算的合计、百分比及累计值,改用JS逻辑动态计算
  • 绑定legendselectchanged事件,监听图例选中状态变化
  • 在事件回调中:
    1. 提取当前选中的series
    2. 计算选中series的每个zipcode合计值
    3. 基于选中系列的总合计,计算百分比和累计百分比
    4. 更新折线图数据

修改后的完整代码

# 数据准备:仅保留排序逻辑,移除提前计算的静态统计值
data2 <- data.frame(
  zipcode= as.factor(round(rnorm(10, 87965, 50),0)), 
  program_1 = rnorm(10, 200, 20), 
  program_2 = rnorm(10, 700, 100), 
  program_3 = rnorm(10, 500, 50)
)

data <- data2 %>%
  mutate(zipcode = fct_reorder(zipcode, desc(rowSums(across(where(is.numeric)))))) %>%
  arrange(desc(rowSums(across(where(is.numeric)))))

# 图表渲染+动态累计逻辑
data %>% 
  e_charts(zipcode) %>% 
  e_bar(program_1, stack = "g", bind=round(100*program_1/rowSums(across(program_1:program_3)),1), name="A", y_index = 0, emphasis = list(focus = "series")) %>% 
  e_bar(program_2, stack = "g", bind=round(100*program_2/rowSums(across(program_1:program_3)),1), name="B", y_index = 0, emphasis = list(focus = "series")) %>% 
  e_bar(program_3, stack = "g", bind=round(100*program_3/rowSums(across(program_1:program_3)),1), name="C", y_index = 0, emphasis = list(focus = "series")) %>%
  # 初始化折线图,后续通过JS动态更新数据
  e_line(
    legend=T, 
    data = rep(0, nrow(data)), 
    y_index = 1, 
    name = "Cumulative %", 
    emphasis = list(focus = "series"),
    tooltip=list(show=F),
    label = list(
      show = TRUE, 
      formatter = htmlwidgets::JS("function(params) { return params.value + '%' }"),
      rotate = 0,
      textStyle = list(fontFamily = "Arial", fontSize = 10)
    )
  ) %>% 
  e_x_axis(name = "Zip Code", show=TRUE,
           nameLocation = "middle", nameGap=50,
           nameTextStyle = list(color = "#87786B", fontSize = 15), 
           axisLabel = list(fontFamily="Arial", interval = 0, rotate = 30, fontSize=12, color="#87786B")) %>%
  e_y_axis(name="n° of visits", index = 0, show = TRUE, 
           nameLocation ="middle", nameGap=70, align="right", 
           nameTextStyle = list(color = "#87786B", fontSize = 12),
           axisLabel = list(fontSize=12, color="#87786B")) %>%
  # 新增右侧Y轴用于显示百分比
  e_y_axis(index = 1, show = TRUE, 
           name="Cumulative %", nameLocation ="middle", nameGap=70, 
           nameTextStyle = list(color = "#87786B", fontSize = 12),
           axisLabel = list(formatter = "{value}%", fontSize=12, color="#87786B")) %>%
  e_legend(itemWidth=30, top = 30, right=10, 
           type="scroll", padding=0,
           selected = list('A' = TRUE, 'B' = TRUE, 'C' = TRUE, 'Cumulative %' = FALSE),
           textStyle=list(fontFamily="arial", fontSize=12, color="#87786B")) %>% 
  e_tooltip(
    formatter = htmlwidgets::JS("function(params){
      if(params.seriesName === 'Cumulative %') return '';
      return(params.marker + params.seriesName +
             '<br>' +
             'Total: ' + echarts.format.addCommas(params.value[1]) +  ' (' +
             params.data + '%)');
    }"),
    textStyle=list(fontFamily="Arial", fontSize=12)
  ) %>%
  e_animation(duration=1500) %>%
  e_toolbox(show =T, top=0, right= 10, itemSize=10, emphasis=list(iconStyle=list(textFill="#6C2C4E"))) %>%
  e_toolbox_feature(feature = c("saveAsImage", "restore")) %>% 
  e_theme_custom('{"color":["#F8AD3B", "#D15425", "#9CAC3B", "#6C2C4E"]}') %>%
  # 监听图例选择事件,动态更新累计折线
  e_on(
    event = "legendselectchanged",
    handler = htmlwidgets::JS("function(params) {
      var chart = this;
      var selectedSeries = [];
      // 筛选当前选中的业务系列(排除累计折线)
      for(var key in params.selected) {
        if(key !== 'Cumulative %' && params.selected[key]) {
          selectedSeries.push(key);
        }
      }
      
      // 映射系列名称到数据列
      var seriesMap = {'A': 'program_1', 'B': 'program_2', 'C': 'program_3'};
      var selectedCols = selectedSeries.map(s => seriesMap[s]);
      
      // 收集选中系列的原始数据
      var seriesData = {};
      chart.getOption().series.forEach(s => {
        if(selectedSeries.includes(s.name)) {
          seriesData[seriesMap[s.name]] = s.data.map(d => d[1]);
        }
      });
      
      // 计算每个zipcode的合计值
      var totals = [];
      for(let i=0; i<chart.getOption().xAxis[0].data.length; i++) {
        let sum = 0;
        selectedCols.forEach(col => sum += seriesData[col][i]);
        totals.push(sum);
      }
      
      // 计算累计百分比
      var grandTotal = totals.reduce((a,b) => a+b, 0);
      var perTotals = totals.map(t => (t/grandTotal)*100);
      var cumSum = [];
      let currentSum = 0;
      perTotals.forEach(p => {
        currentSum += p;
        cumSum.push(currentSum.toFixed(1));
      });
      
      // 更新折线图数据
      chart.setOption({
        series: [{
          name: 'Cumulative %',
          data: cumSum
        }]
      });
    }")
  ) %>%
  // 页面加载完成后初始化累计折线数据
  e_on(
    event = "finished",
    handler = htmlwidgets::JS("function() {
      this.dispatchAction({
        type: 'legendselectchanged',
        selected: this.getOption().legend[0].selected
      });
    }")
  )

关键修改说明

  1. 数据层简化:仅保留zipcode排序逻辑,所有统计值改为动态计算
  2. 双Y轴配置:新增右侧Y轴专门展示百分比,避免数值范围冲突
  3. 事件监听:通过legendselectchanged实时响应图例变化,finished事件初始化折线数据
  4. JS计算逻辑:在回调中完成选中系列的合计、百分比、累计值计算,并更新折线图

内容的提问来源于stack exchange,提问作者ddl

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 15:24:16