我有一个闪亮的应用程序示例,使用 echarts4r 按类别和年份可视化销售数据。
我正在尝试向该图中添加两个功能:
图表顶部显示的动态总计,根据图例选择进行更新。我知道如何创建一个,但我不知道当图例中的某些内容被取消选择时如何更改它。 我想我需要某种监听的反应函数 再次选择并计算总数?
条形右侧的数字显示类别的总和,该数字也会随着图例选择而更新。
这是我当前的代码:
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)
编辑: 我需要什么:
我想澄清的是,这个方法在 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)
正如我最初提到的,这段代码是您如何在没有 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) }) }
)
}"
)