条形图中的动态总计

问题描述 投票:0回答:1

我有一个闪亮的应用程序示例,使用 echarts4r 按类别和年份可视化销售数据。

我正在尝试向该图中添加两个功能:

  1. 图表顶部显示的动态总计,根据图例选择进行更新。我知道如何创建一个,但我不知道当图例中的某些内容被取消选择时如何更改它。 我想我需要某种监听的反应函数 再次选择并计算总数?

  2. 条形右侧的数字显示类别的总和,该数字也会随着图例选择而更新。

这是我当前的代码:

library(shiny)
library(echarts4r)
library(dplyr)

# Sample data
data <- data.frame(
  Year = rep(c("2020", "2021", "2022"), each = 4),
  Category = rep(c("Electronics", "Clothing", "Books", "Home"), 3),
  Sales = c(300, 200, 150, 250,
            350, 220, 180, 280,
            400, 250, 200, 320)
)

ui <- fluidPage(
  titlePanel("Sales by Category and Year"),
  mainPanel(
    echarts4rOutput("plot", height = "500px")
  )
)

server <- function(input, output, session) {
  output$plot <- renderEcharts4r({
    data %>%
      group_by(Year) %>%
      e_charts(Category) %>%
      e_bar(Sales, stack = "stack") %>%
      e_x_axis(axisLabel = list(interval = 0, rotate = 0)) %>%
      e_y_axis(splitLine = list(show = FALSE)) %>%
      e_labels(position = "inside") %>%
      e_tooltip(trigger = "axis") %>%
      e_legend(orient = "horizontal", bottom = "bottom", type = "scroll") %>%
      e_flip_coords()
  })
}

shinyApp(ui, server)

编辑: 我需要什么:

enter image description here

r shiny echarts4r
1个回答
0
投票

我想澄清的是,这个方法在 Shiny 中有效,但必须修改才能在 Shiny 之外工作。我在我的答案中添加了 Shiny 的进出,因为我想即使你不能使用,也许其他人可以。

我无法让标签

formatter
配合,所以我使用
ui
script
标签添加到Shiny应用程序中。

在脚本中,有注释,以便您可以了解发生了什么。在 UI 中,您将绘图渲染器命名为“plot”。这将成为应用程序后台的关键词,并在 Javascript 脚本元素中使用两次。如果更改此渲染器的名称,则也必须在脚本中更改它。

在该代码中,有 3 个主要函数,一个用于按条计算总计,一个用于计算绘图总计,一个用于识别图例事件后哪些元素可见。

server
中的绘图代码中,我添加了一些元素来为绘图总计和条形总计创建主页。

绘图总计是一个图形文本元素。条形总计以散点图形式输入。根据您绘制的数据,我将条形总数放在 x 轴上的 1200 处。当然,您可以根据需要进行调整。

如果您有任何疑问,请告诉我。

闪亮代码:

library(tidyverse)
library(echarts4r)
library(shiny)

ui <- fluidPage(
  tags$head(
    tags$script(HTML(
      "setTimeout(function() {           /* give page time to load */
        ms = [];             /* array to collect data to cycle through */
                                        /* 'plot' matches the name of the call in UI... */
        document.querySelector('#plot').htmlwidget_data_init_result.getOpts().series.forEach((it, ix) => {
          ms.push({name: it.name, type: it.type, data: it.data}) 
        });
                                /* function for handling filtering by legend */
        legendHandle = (event, series) => {  /* update series when legend clicked */
                                                /* collect names visible from legend */
          nms = Object.keys(event.selected).filter(key => event.selected[key] === true);
          nms.push('b');                        /* add text trace (not in legend) */
          visSeries = series.filter(serie => nms.includes(serie.name));
          return seriesHandler(visSeries);      /* calculate total of visible elements */
        }
                               /* function for handling total by column */
        seriesHandler = series => {                /* find total by category of visible series */
          return series.map((serie, index) => {
            if (index === series.length - 1) {  /* only show labels in right-most group */
              return {...serie, label: {  normal: {       
                show: true, position: 'right',
                formatter: params => {  /* calculate the total for the bar */
                  let total = 0
                  series.forEach((s, i) => { 
                  if(i !== series.length - 1) {   /* don't add scatter value */
                    total += Number(s.data[params.dataIndex].value[0])
                  }
                })
                return total
              }
            }}}
            } else {
              return serie
            }
          })
        }
                               /* function for handling plot total */
        function totalHandler(event, series) {
          series2 = legendHandle(event, series);
          let tTotal = 0;
          series2.forEach((s, i) => {    /* cycle through visible series - other than scatter */
            if(i !== series2.length - 1)
            s.data.forEach(s2 => {       /* cycle through array of arrays of data */
              tTotal += Number(s2.value[0])      /* calculate running total */
            });
          });
                                /* get graphics text element as originally coded */
          eg = document.querySelector('#plot').htmlwidget_data_init_result.getOpts().graphic;
          eg.style.text = 'Total Sales: ' + tTotal;      /* modify graphic text element */
          return eg
        } 
                               /* capture current echart */
        e = echarts.getInstanceById( plot.getAttribute('_echarts_instance_') );  /* 'plot' matches the name of the call in UI... */
                               /* deploy label change and legend event */
        e.setOption({series: seriesHandler(ms) });
        e.on('legendselectchanged',   /* update both the series' totals and plot total when legend clicked */
            event => { e.setOption({series: legendHandle(event, ms),
                                    graphic: totalHandler(event, ms)  }) }
        )
      }, 300) "))
  ),
  titlePanel("Sales by Category and Year"),
  mainPanel(
    echarts4rOutput("plot", height = "500px") # <---- this name 'plot' is used in the JS script; if it changes it has to change in the JS, as well
  )
)
  
server <- function(input, output, session) {
  output$plot <- renderEcharts4r({
    data %>%
      group_by(Year) %>%
      e_charts(Category) %>%
      e_bar(Sales, stack = "stack") %>%
      e_x_axis(index = 0,        # <--------------------------- I'm new!
               axisLabel = list(interval = 0, rotate = 0)) %>%
      e_y_axis(splitLine = list(show = FALSE)) %>%
      e_tooltip(trigger = "axis") %>%
      e_labels(show = T, position = 'inside') %>% 
      e_legend(orient = "horizontal", bottom = "bottom", type = "scroll") %>%
          # --------- new from here until e_flip_coords() ----------
      e_data(data.frame(a = unique(data$Category), b = 1200), x = a) %>%  # 1200 chosen arbitrarily based on data in plot
      e_scatter(b, legend = F, tooltip = list(show = F)) %>%              # home for the totals by bar
      e_x_axis(index = 1, show = F) %>%                                   # don't show another x axis
      e_text_g(top = 40, right = 10, style = list(
        text = paste0('Total Sales: ', sum(data$Sales))                   # home for plot total
      )) %>% 
      e_flip_coords()
  })
}

shinyApp(ui, server)

filter1 plotfilter2 plot

无光泽

正如我最初提到的,这段代码是您如何在没有 Shiny 的情况下完成相同任务的方法。

此方法在 JS 中使用

script
,而不是
htmlwidgets::onRender
标签。因为没有绘图渲染器,所以在绘图中调用参数
elementId
。在此代码中,该参数设置为
"myChart"
。在 JS 中,您会发现它被替换为
"plot"
,而不是
"myChart"
(在 Shiny 版本中)。

data %>% group_by(Year) %>% 
  e_charts(Category, elementId = "myChart") %>%  # <------- I'm new (elementId)
  e_bar(Sales, stack = "stack") %>%
  e_x_axis(index = 0,        # <--------------------------- I'm new!
           axisLabel = list(interval = 0, rotate = 0)) %>%
  e_y_axis(splitLine = list(show = FALSE)) %>%
  e_tooltip(trigger = "axis") %>%
  e_labels(show = T, position = 'inside') %>% 
  e_legend(orient = "horizontal", bottom = "bottom", type = "scroll") %>%
        # --------- new from here until e_flip_coords ---------------
  e_data(data.frame(a = unique(data$Category), b = 1200), x = a) %>%  # 1200 chosen arbitrarily based on data in plot
  e_scatter(b, legend = F, tooltip = list(show = F)) %>%              # home for the totals by bar
  e_x_axis(index = 1, show = F) %>%                                   # don't show another x axis
  e_text_g(top = 40, right = 10, style = list(
    text = paste0('Total Sales: ', sum(data$Sales))                   # home for plot total
  )) %>% 
  e_flip_coords() %>% 
  htmlwidgets::onRender(
    "function(el, x) {
      ms = [];           /* data to cycle through whenever there's a change */
      document.querySelector('#myChart').htmlwidget_data_init_result.getOpts().series.forEach((it, ix) => {
        ms.push({name: it.name, type: it.type, data: it.data}) 
      });
      legendHandle = (event, series) => {  /* update series when legend clicked */
                                              /* collect names visible from legend */
        nms = Object.keys(event.selected).filter(key => event.selected[key] === true);
        nms.push('b');                        /* add text trace (not in legend) */
        visSeries = series.filter(serie => nms.includes(serie.name));
        return seriesHandler(visSeries);      /* calculate total of visible elements */
      }
      seriesHandler = series => {                /* find total by category of visible series */
        return series.map((serie, index) => {
          if (index === series.length - 1) {  /* only show labels in right-most group */
            return {...serie, label: {  normal: {       
              show: true, position: 'right',
              formatter: params => {  /* calculate the total for the bar */
                let total = 0
                series.forEach((s, i) => { 
                if(i !== series.length - 1) {   /* don't add scatter value */
                  total += Number(s.data[params.dataIndex].value[0])
                }
              })
              return total
            }
          }}}
          } else {
            return serie
          }
        })
      }
      function totalHandler(event, series) {
        series2 = legendHandle(event, series);
        let tTotal = 0;
        series2.forEach((s, i) => {    /* cycle through visible series - other than scatter */
          if(i !== series2.length - 1)
          s.data.forEach(s2 => {       /* cycle through array of arrays of data */
            tTotal += Number(s2.value[0])      /* calculate running total */
          });
        });
                      /* get graphics text element as originally coded */
        eg = document.querySelector('#myChart').htmlwidget_data_init_result.getOpts().graphic;
        eg.style.text = 'Total Sales: ' + tTotal;      /* modify graphic text element */
        return eg
      } 
                      /* get chart */
      e = echarts.getInstanceById( myChart.getAttribute('_echarts_instance_') );
                      /* update chart */
      e.setOption({series: seriesHandler(ms) });
      /* e.on('legendselectchanged', 
            event => { totalHandler(event, ms)}); */
      e.on('legendselectchanged',   /* update both the series' totals and plot total when legend clicked */
            event => { e.setOption({series: legendHandle(event, ms),
                                    graphic: totalHandler(event, ms)  }) }
      )
    }"
  )
© www.soinside.com 2019 - 2024. All rights reserved.