############################### ## Time Series Analysis - ui ## file: time_series_ui.R ############################### # --- 1. 定义下拉菜单和选项常量 --- ts_methods <- c("ma", "ets", "acf") names(ts_methods) <- c( i18n$t("Moving Average"), i18n$t("Exp. Smoothing"), i18n$t("Autocorrelation") ) # 绘图图层选项(统一用方法1) ts_plot_opts <- c("original", "ma", "ets", "acf") names(ts_plot_opts) <- c( i18n$t("Original Data"), i18n$t("Moving Average Line"), i18n$t("Exp. Smoothing Line"), i18n$t("ACF Plot") ) # 聚合粒度选项(统一用方法1) ts_agg_levels <- c("day", "week", "month", "quarter", "year") names(ts_agg_levels) <- c( i18n$t("Day"), i18n$t("Week"), i18n$t("Month"), i18n$t("Quarter"), i18n$t("Year") ) # 聚合函数选项(统一用方法1) ts_agg_funs <- c("mean", "sum", "count") names(ts_agg_funs) <- c( i18n$t("Mean (Average)"), i18n$t("Sum (Total)"), i18n$t("Count (Rows)") ) # --- 2. 初始化参数 --- # 读取后端 time_series 函数的默认参数 ts_args <- as.list(formals(time_series)) # --- 3. 打包用户输入 (ts_inputs) --- ts_inputs <- reactive({ # 处理数据过滤 ts_args$data_filter <- if (input$show_filter) input$data_filter else "" ts_args$dataset <- input$dataset ts_args$envir <- r_data # 自动循环匹配 input$ts_xxx 到参数 xxx for (i in r_drop(names(ts_args))) { ts_args[[i]] <- input[[paste0("ts_", i)]] } ts_args }) # --- 4. 动态 UI 组件定义 --- # Y轴变量 (数值型) output$ui_ts_var_num <- renderUI({ isNum <- .get_class() %in% c("integer", "numeric", "ts") vars <- c("None" = "", varnames()[isNum]) selectInput( inputId = "ts_var_num", label = i18n$t("Numeric variable:"), choices = vars, selected = state_single("ts_var_num", vars), multiple = FALSE ) }) # X轴变量 (日期/时间型/数值型) output$ui_ts_var_date <- renderUI({ all_vars <- varnames() # 自动检测日期类型的变量优先显示 isDate <- .get_class() %in% c("Date", "POSIXt", "POSIXct") # 如果有日期变量,默认选中第一个 def_sel <- if (any(isDate)) all_vars[isDate][1] else "" vars <- c("None" = "", all_vars) selectInput( inputId = "ts_var_date", label = i18n$t("Time variable:"), choices = vars, selected = state_single("ts_var_date", vars, def_sel), multiple = FALSE ) }) # --- 5. 侧边栏主面板 (UI Sidebar) --- output$ui_time_series <- renderUI({ req(input$dataset) tagList( wellPanel( # === 基础数据选择 === uiOutput("ui_ts_var_num"), uiOutput("ui_ts_var_date"), # 改为垂直排列,确保下拉菜单可以正常弹出并被选择 conditionalPanel( condition = "input.ts_var_date != ''", selectInput( inputId = "ts_agg_level", label = i18n$t("Interval:"), choices = ts_agg_levels, selected = state_single("ts_agg_level", ts_agg_levels, "month"), multiple = FALSE ), selectInput( inputId = "ts_agg_fun", label = i18n$t("Aggregation Method:"), choices = ts_agg_funs, selected = state_single("ts_agg_fun", ts_agg_funs, "mean"), multiple = FALSE ) ), conditionalPanel( condition = "input.tabs_time_series == i18n$t('Summary')", h6("计算方法:"), # 选择计算方法 checkboxGroupInput( inputId = "ts_methods", label = NULL, choices = ts_methods, selected = state_multiple("ts_methods", ts_methods, c("ma", "ets")) ), # MA 参数设置 conditionalPanel( condition = "input.ts_methods.includes('ma')", numericInput("ts_ma_n", i18n$t("MA Window Size:"), value = state_init("ts_ma_n", 3), min = 2, step = 1) ), # 预测参数设置 numericInput( inputId = "ts_fc_periods", label = i18n$t("Forecast Periods:"), value = state_init("ts_fc_periods", 0), min = 0, step = 1 ), ), # === 绘图图层配置 === conditionalPanel( condition = "input.tabs_time_series == i18n$t('Plot')", h6("图像:"), selectizeInput( inputId = "ts_plots", label = NULL, choices = ts_plot_opts, selected = state_multiple("ts_plots", ts_plot_opts, c("original", "ma", "ets")), multiple = TRUE, options = list(placeholder = "Select layers", plugins = list("remove_button", "drag_drop")) ) ) ), # 帮助按钮和报告生成按钮 help_and_report( modal_title = "Time Series Analysis", fun_name = "time_series", help_file = inclMD(file.path(getOption("radiant.path.basics"), "app/tools/help/time_series.md")) ) ) }) # --- 6. 定义图形高度 --- ts_plot_height <- function() { # 如果包含 ACF 图,增加高度 if ("acf" %in% input$ts_plots) 600 else 400 } # --- 7. 注册主界面输出 (Main Panel) --- output$time_series <- renderUI({ # 注册打印和绘图输出 register_print_output("summary_time_series", ".summary_time_series") register_plot_output("plot_time_series", ".plot_time_series", height_fun = "ts_plot_height") # 定义标签页 ts_output_panels <- tabsetPanel( id = "tabs_time_series", tabPanel(i18n$t("Summary"), verbatimTextOutput("summary_time_series")), tabPanel( i18n$t("Plot"), download_link("dlp_time_series"), plotOutput("plot_time_series", height = "100%") ) ) # 构建 Radiant 标准 Tab 面板 stat_tab_panel( menu = i18n$t("Basics > Time Series"), tool = i18n$t("Time Series Analysis"), tool_ui = "ui_time_series", output_panels = ts_output_panels ) }) # --- 8. 后端计算逻辑连接 --- # 执行计算 .time_series <- reactive({ tsi <- ts_inputs() tsi$envir <- r_data # 确保环境传递正确 req(tsi$var_num) # 必须选择了变量才执行 do.call(time_series, tsi) }) # 生成 Summary .summary_time_series <- reactive({ summary(.time_series()) }) # 生成 Plot .plot_time_series <- reactive({ validate( need(input$ts_plots, i18n$t("Please select at least one plot layer.")) ) withProgress(message = "Generating Plots...", value = 1, { plot(.time_series(), plots = input$ts_plots, shiny = TRUE) }) }) # --- 9. 报告生成 (Rmarkdown) --- time_series_report <- function() { if (is.empty(input$ts_var_num)) return(invisible()) # 判断是否生成图片代码 if (length(input$ts_plots) == 0) { figs <- FALSE outputs <- c("summary") inp_out <- list("", "") } else { figs <- TRUE outputs <- c("summary", "plot") inp_out <- list("", list(plots = input$ts_plots, custom = FALSE)) } update_report( inp_main = clean_args(ts_inputs(), ts_args), fun_name = "time_series", outputs = outputs, figs = figs, fig.width = 650, fig.height = ts_plot_height() ) } # --- 10. 下载处理器 --- download_handler( id = "dlp_time_series", fun = download_handler_plot, fn = function() paste0(input$dataset, "_time_series"), type = "png", caption = "Save time series plot", plot = .plot_time_series, width = 650, height = ts_plot_height ) # --- 11. 事件监听 --- observeEvent(input$time_series_report, { r_info[["latest_screenshot"]] <- NULL time_series_report() }) observeEvent(input$time_series_screenshot, { r_info[["latest_screenshot"]] <- NULL radiant_screenshot_modal("modal_time_series_screenshot") }) observeEvent(input$modal_time_series_screenshot, { time_series_report() removeModal() })