Nama: Fatih Zahrani
NPM: 140610230014
Dosen Pengampu: I Gede Nyoman Mindra Jaya, M.Si., Ph.D.

S1 Statistika FMIPA UNPAD


Petunjuk

Download folder Dashboard dan jalankan syntax app.R dibawah ini


Link file:

https://drive.google.com/drive/folders/1oV10YffnE1jPM6ByesNhxubHrZA-fA05?usp=sharing


Syntax Dashboard

# ============================================================
# app.R Dashboard Analisis DBD Jawa Barat 2024
# Version: 1.0
# ============================================================

# ----------------------------
# Load libraries
# ----------------------------
library(shiny)
library(shinydashboard)
library(shinyWidgets)
library(dplyr)
library(plotly)
library(leaflet)
library(DT)
library(spdep)
library(sf)
library(sp)
library(openxlsx)
library(tidyr)
library(ggplot2)
library(DiagrammeR)
library(viridis)
library(scales)

# ============================================================
# 1. GLOBAL SECTION (Data & Model)
# ============================================================

# --- Load data shapefile dan Excel ---
# CATATAN: Sesuaikan path dengan lokasi file Anda
Indo <- st_read("D:/Kuliah/Sem 5/Epidem/Dashboard/shapefile")
data <- read.xlsx("D:/Kuliah/Sem 5/Epidem/Dashboard/data/DATA EPIDEMIOLOGI.xlsx")

# --- Merge shapefile dan data ---
jabar_merged <- Indo %>%
  left_join(data, by = c("WADMKK" = "kab_kota"))

# --- Hitung Prevalensi dan CFR ---
jabar_merged <- jabar_merged %>%
  mutate(
    Prevalensi = (JK / JP) * 100,
    CFR = (KDBD / JK) * 100
  )

# --- Total untuk Jawa Barat ---
total_JK <- sum(jabar_merged$JK, na.rm = TRUE)
total_JP <- sum(jabar_merged$JP, na.rm = TRUE)
total_KDBD <- sum(jabar_merged$KDBD, na.rm = TRUE)
prevalensi_jabar <- (total_JK / total_JP) * 100
cfr_jabar <- (total_KDBD / total_JK) * 100

# --- Konversi ke Spatial untuk analisis spasial ---
jabar_sp <- as_Spatial(jabar_merged)
row.names(jabar_sp) <- jabar_sp$WADMKK

# --- Spatial weight matrix ---
W <- poly2nb(jabar_sp, row.names = row.names(jabar_sp), queen = TRUE)
WL <- nb2listw(W, style = "W", zero.policy = TRUE)

cat("??? Semua data berhasil dimuat!\n")

# ============================================================
# 2. UI SECTION
# ============================================================

ui <- dashboardPage(
  skin = "blue",
  dashboardHeader(title = "Dashboard DBD Jawa Barat 2024", titleWidth = 350),
  
  dashboardSidebar(
    width = 270,
    sidebarMenu(
      menuItem("About Dashboard", tabName = "about", icon = icon("info-circle")),
      menuItem("Import Data", tabName = "import", icon = icon("upload")),
      menuItem("Data Overview", tabName = "data_overview", icon = icon("table")),
      menuItem("Agent-Host-Environment", tabName = "ahe", icon = icon("project-diagram")),
      menuItem("Statistika Deskriptif", tabName = "descriptive", icon = icon("chart-bar")),
      menuItem("Sebaran DBD", tabName = "spatial", icon = icon("map-marked-alt")),
      menuItem("Ukuran Epidemiologi", tabName = "epidemiology", icon = icon("calculator"))
    )
  ),
  
  dashboardBody(
    tags$head(
      tags$link(rel = "stylesheet", type = "text/css", href = "custom.css")
    ),
    tabItems(
      # =====================================================
      # 1. TENTANG DASHBOARD
      # =====================================================
      tabItem(tabName = "about",
              fluidRow(
                box(width = 12, title = "Selamat Datang di Dashboard DBD Jawa Barat 2024", 
                    status = "primary", solidHeader = TRUE,
                    h4("???? Tentang Dashboard"),
                    p("Dashboard ini menyajikan analisis komprehensif mengenai Demam Berdarah Dengue (DBD) di Provinsi Jawa Barat Tahun 2024. 
                      Dashboard dirancang untuk memudahkan pemahaman pola sebaran, faktor risiko, dan ukuran epidemiologi DBD."),
                    hr(),
                    h4("Urgensi"),
                    tags$ul(
                      tags$li("Jawa Barat merupakan salah satu provinsi dengan jumlah penduduk terbesar di Indonesia, 
                              sehingga potensi penyebaran penyakit menular seperti DBD sangat tinggi."),
                      tags$li("Berdasarkan data dari Kemenkes RI, Jawa Barat termasuk dalam provinsi dengan insidensi 
                              dan jumlah kasus DBD tertinggi setiap tahunnya.")
                    ),
                    hr(),
                    h4(" Sumber Data"),
                    p(strong("Data Kasus DBD:"), " BPS Provinsi Jawa Barat"),
                    p(strong("Shapefile:"), " RBI 50K Jawa Barat"),
                    p(strong("Tahun:"), " 2024"),
                    p(strong("Jumlah Kabupaten/Kota:"), nrow(data)),
                    hr(),
                    h4("???? Tim Penyusun"),
                    tags$ul(
                      tags$li("Fatih Zahrani (140610230014)"),
                      tags$li("Rahma Aulia Putri (140610230037)"),
                      tags$li("Nafalla Afftanur Rismawanti (140610230044)")
                    ),
                    p(em("Dashboard Version 1.0 - 2025"))
                )
              )
      ),
      # =====================================================
      # 2. IMPORT DATA
      # =====================================================
      tabItem(tabName = "import",
              fluidRow(
                box(width = 12, title = "Upload Data DBD", status = "warning", solidHeader = TRUE,
                    p("Upload file Excel (.xlsx) dengan format yang sesuai. File harus mengandung kolom: 
                      kab_kota, JK (Jumlah Kasus), JP (Jumlah Penduduk), KDBD (Kematian DBD)."),
                    fileInput("upload_data", "Pilih File Excel", accept = c(".xlsx")),
                    actionButton("load_data", "Load Data", class = "btn-primary", icon = icon("upload")),
                    hr(),
                    h4("Preview Data yang Diupload:"),
                    DTOutput("preview_upload")
                )
              ),
              fluidRow(
                box(width = 12, title = "Template Download", status = "info", solidHeader = TRUE,
                    p("Jika Anda belum memiliki format data yang sesuai, silakan download template berikut:"),
                    downloadButton("download_template", "Download Template Excel", class = "btn-success")
                )
              )
      ),
      # =====================================================
      # 3. DATA OVERVIEW
      # =====================================================
      tabItem(tabName = "data_overview",
              fluidRow(
                # Menampilkan ringkasan data
                valueBoxOutput("total_cases", width = 4),       # Total kasus DBD
                valueBoxOutput("total_deaths", width = 4),      # Total kematian DBD
                valueBoxOutput("num_districts", width = 4)      # Jumlah kabupaten/kota
              ),
              fluidRow(
                box(width = 12, title = "Tabel Data DBD", status = "info", solidHeader = TRUE,
                    sliderInput("n_rows", "Jumlah Baris:", min = 5, max = 50, value = 10),
                    downloadButton("download_data", "Download Data (Excel)", class = "btn-primary"),
                    hr(),
                    DTOutput("data_table"))
              )
      ),
      # =====================================================
      # 4. AGENT-HOST-ENVIRONMENT
      # =====================================================
      tabItem(tabName = "ahe",
              fluidRow(
                box(width = 12, title = "Faktor Risiko DBD: Agent-Host-Environment", 
                    status = "primary", solidHeader = TRUE,
                    h4("Diagram Faktor Risiko DBD"),
                    p("Klik pada kotak faktor untuk melihat penjelasan detail hubungan dengan risiko DBD."),
                    grVizOutput("ahe_diagram", height = "900px", width = "100%")
                    
                )
              ),
              fluidRow(
                box(width = 12, title = "Penjelasan Faktor Risiko", status = "info", solidHeader = TRUE,
                    tabsetPanel(
                      tabPanel("???? Agent",
                               br(),
                               h4("Virus Dengue"),
                               p(strong("Klasifikasi:"), " Flavivirus, famili Flaviviridae"),
                               p(strong("Serotipe:"), " DENV-1, DENV-2, DENV-3, DENV-4"),
                               p("Virus dengue ditularkan ke manusia melalui gigitan nyamuk Aedes betina yang terinfeksi, terutama Aedes aegypti dan Aedes albopictus."),
                               hr(),
                               p(strong("Mekanisme:"), " Virus masuk ke tubuh manusia melalui gigitan nyamuk ??? 
                                 replikasi virus dalam sel ??? viremia ??? manifestasi klinis DBD")
                      ),
                      tabPanel("???? Host",
                               br(),
                               h4("Faktor Host yang Meningkatkan Kerentanan"),
                               tags$ul(
                                 tags$li(strong("Usia 5-14 tahun:"), " Kelompok usia ini memiliki aktivitas tinggi dan 
                                         sistem imun yang masih berkembang, sehingga lebih rentan terinfeksi."),
                                 tags$li(strong("Status Gizi Buruk:"), " Malnutrisi menurunkan sistem imun tubuh, 
                                         membuat individu lebih mudah terinfeksi dan mengalami komplikasi."),
                                 tags$li(strong("Golongan Darah AB:"), " Penelitian menunjukkan golongan darah AB 
                                         memiliki risiko lebih tinggi terkena DBD berat."),
                                 tags$li(strong("Laki-laki:"), " Secara statistik, laki-laki memiliki risiko sedikit 
                                         lebih tinggi, kemungkinan karena faktor aktivitas dan paparan."),
                                 tags$li(strong("Riwayat Infeksi Sebelumnya:"), " Infeksi dengue sebelumnya dengan 
                                         serotipe berbeda meningkatkan risiko DBD berat (DHF) melalui antibody-dependent enhancement (ADE).")
                               )
                      ),
                      tabPanel(" Environment",
                               br(),
                               h4("Faktor Lingkungan yang Meningkatkan Risiko"),
                               tags$ul(
                                 tags$li(strong("Curah Hujan Tinggi:"), " Meningkatkan habitat jentik Aedes aegypti 
                                         karena banyak genangan air ??? populasi nyamuk meningkat ??? risiko penularan DBD naik."),
                                 tags$li(strong("Kenaikan Suhu Global:"), " Suhu 25-30??C optimal untuk perkembangan nyamuk. 
                                         Suhu tinggi mempercepat siklus hidup nyamuk dan replikasi virus dalam tubuh nyamuk ??? transmisi lebih cepat."),
                                 tags$li(strong("Rendahnya Pelaksanaan 3M Plus:"), " Tidak menguras tempat penampungan air, 
                                         tidak menutup rapat, dan tidak mengubur barang bekas ??? habitat jentik nyamuk tetap ada ??? risiko DBD tinggi."),
                                 tags$li(strong("Kepadatan Penduduk Tinggi:"), " Wilayah padat penduduk meningkatkan kontak antarmanusia 
                                         dan nyamuk ??? penyebaran virus lebih cepat ??? wabah lebih mudah terjadi.")
                               ),
                               hr(),
                               h4("???? Upaya Pencegahan"),
                               tags$ul(
                                 tags$li("Gerakan 3M Plus (Menguras, Menutup, Mengubur + Plus)"),
                                 tags$li("Fogging/penyemprotan di area fokus wabah"),
                                 tags$li("Edukasi masyarakat tentang bahaya DBD"),
                                 tags$li("Surveilans dan monitoring kasus secara aktif")
                               )
                      )
                    )
                )
              )
      ),
      
      
      # =====================================================
      # 5. STATISTIKA DESKRIPTIF
      # =====================================================
      tabItem(tabName = "descriptive",
              fluidRow(
                box(width = 12, title = "Pengaturan Analisis", status = "primary", solidHeader = TRUE,
                    fluidRow(
                      column(6, 
                             pickerInput("desc_vars", "Pilih Variabel:", 
                                         choices = c(
                                           "Jumlah DBD" = "Jumlah DBD",
                                           "Jumlah Penduduk" = "Jumlah Penduduk",
                                           "Jumlah Kematian DBD" = "Jumlah Kematian DBD",
                                           "Prevalensi" = "Prevalensi", 
                                           "CFR" = "CFR"
                                         ),
                                         selected = "Jumlah DBD", multiple = TRUE,
                                         options = list(actionsBox = TRUE, liveSearch = TRUE))
                      )
                    ),
                    downloadButton("download_desc", "Download Hasil Analisis", class = "btn-success")
                )
              ),
              fluidRow(
                box(width = 6, title = "Histogram", status = "info", solidHeader = TRUE,
                    plotlyOutput("desc_histogram", height = "400px")),
                box(width = 6, title = "Boxplot", status = "warning", solidHeader = TRUE,
                    plotlyOutput("desc_boxplot", height = "400px"))
              ),
              fluidRow(
                box(width = 12, title = "Statistik Deskriptif", status = "success", solidHeader = TRUE,
                    DTOutput("desc_summary"))
              )
      ),
      
      # =====================================================
      # 6. SEBARAN DBD
      # =====================================================
      tabItem(tabName = "spatial",
              fluidRow(
                valueBoxOutput("total_kasus", width = 4),
                valueBoxOutput("total_kematian", width = 4),
                valueBoxOutput("total_wilayah", width = 4)
              ),
              fluidRow(
                box(width = 12, title = "Peta Sebaran Kasus DBD", status = "danger", solidHeader = TRUE,
                    leafletOutput("map_kasus", height = "500px"))
              ),
              fluidRow(
                box(width = 12, title = "Analisis Autokorelasi Spasial", status = "primary", solidHeader = TRUE,
                    tabsetPanel(
                      tabPanel("Moran's I & Geary's C",
                               br(),
                               fluidRow(
                                 valueBoxOutput("moranI_box", width = 6),
                                 valueBoxOutput("gearyC_box", width = 6)
                               ),
                               plotlyOutput("moran_plot", height = "500px")
                      ),
                      tabPanel("LISA Cluster Map",
                               br(),
                               p("Local Indicators of Spatial Association (LISA) menunjukkan cluster spasial lokal."),
                               leafletOutput("lisa_map", height = "600px")
                      ),
                      tabPanel("Getis-Ord Gi*",
                               br(),
                               p("Identifikasi hot spots dan cold spots berdasarkan z-score."),
                               leafletOutput("getisord_map", height = "600px")
                      )
                    )
                )
              )
      ),
      
      # =====================================================
      # 7. UKURAN EPIDEMIOLOGI
      # =====================================================
      tabItem(tabName = "epidemiology",
              fluidRow(
                valueBoxOutput("prev_jabar_box", width = 6),
                valueBoxOutput("cfr_jabar_box", width = 6)
              ),
              
              # PREVALENSI
              fluidRow(
                box(width = 12, title = "Analisis Prevalensi", status = "info", solidHeader = TRUE, 
                    collapsible = TRUE,
                    tabsetPanel(
                      tabPanel("Peta Prevalensi",
                               br(),
                               leafletOutput("map_prevalensi", height = "500px")
                      ),
                      tabPanel("Prevalensi per Wilayah",
                               br(),
                               pickerInput("prev_cities", "Pilih Kabupaten/Kota:", 
                                           choices = NULL, multiple = TRUE,
                                           options = list(actionsBox = TRUE, liveSearch = TRUE)),
                               fluidRow(
                                 column(6, DTOutput("prev_table")),
                                 column(6, plotlyOutput("prev_bar", height = "400px"))
                               )
                      )
                    )
                )
              ),
              
              # CFR
              fluidRow(
                box(width = 12, title = "Analisis Case Fatality Rate (CFR)", status = "warning", solidHeader = TRUE,
                    collapsible = TRUE,
                    tabsetPanel(
                      tabPanel("Peta CFR",
                               br(),
                               leafletOutput("map_cfr", height = "500px")
                      ),
                      tabPanel("CFR per Wilayah",
                               br(),
                               pickerInput("cfr_cities", "Pilih Kabupaten/Kota:", 
                                           choices = NULL, multiple = TRUE,
                                           options = list(actionsBox = TRUE, liveSearch = TRUE)),
                               fluidRow(
                                 column(6, DTOutput("cfr_table")),
                                 column(6, plotlyOutput("cfr_bar", height = "400px"))
                               )
                      )
                    )
                )
              )
      )
    )
  )
)

# ============================================================
# 3. SERVER SECTION
# ============================================================

server <- function(input, output, session) {
  
  # ============================================================
  # REACTIVE VALUES
  # ============================================================
  
  # Update choices untuk picker inputs
  observe({
    city_choices <- data$kab_kota
    
    updatePickerInput(session, "desc_cities", choices = city_choices)
    updatePickerInput(session, "prev_cities", choices = city_choices)
    updatePickerInput(session, "cfr_cities", choices = city_choices)
  })
  
  # ============================================================
  # 2. AGENT-HOST-ENVIRONMENT
  # ============================================================
  
  output$ahe_diagram <- renderGrViz({
    grViz("
    digraph AHE {
      graph [rankdir = TB, bgcolor = transparent, splines = ortho, nodesep = 0.8, ranksep = 1.2]
      node [shape = box, style = 'filled,rounded', fontname = 'Arial', fontsize = 11, width = 2.5, height = 0.8]
      edge [color = '#555555', penwidth = 2, fontname = 'Arial', fontsize = 10]

      # Subgraph Environment
      subgraph cluster_env {
        label = 'ENVIRONMENT'
        style = filled
        color = '#E8F5E9'
        fontsize = 14
        fontname = 'Arial Bold'
        
        ENV1 [label = 'Kepadatan\\nPenduduk\\nTinggi', fillcolor = '#81C784', fontcolor = white]
        ENV2 [label = 'Curah Hujan\\nTinggi', fillcolor = '#81C784', fontcolor = white]
        ENV3 [label = 'Kenaikan Suhu\\nGlobal', fillcolor = '#81C784', fontcolor = white]
        ENV4 [label = 'Rendahnya\\n3M Plus', fillcolor = '#81C784', fontcolor = white]
      }

      # Nodes lain
      GENANGAN [label = 'Genangan Air', fillcolor = '#64B5F6', fontcolor = white]
      NYAMUK [label = 'Populasi Nyamuk\\nAedes Meningkat', fillcolor = '#FF7043', fontcolor = white]
      VIRUS [label = 'Virus Dengue', fillcolor = '#E53935', fontcolor = white, shape = ellipse]
      MANUSIA [label = 'Manusia', fillcolor = '#FFA726', fontcolor = white, shape = ellipse]
      DBD [label = 'KASUS DBD', fillcolor = '#D32F2F', fontcolor = white, style = 'filled,rounded,bold', penwidth = 3]

      # Subgraph Host Factors
      subgraph cluster_host {
        label = 'HOST FACTORS\\n(Kerentanan)'
        style = filled
        color = '#FFF3E0'
        fontsize = 14
        fontname = 'Arial Bold'
        
        HOST1 [label = 'Status Gizi\\nBuruk', fillcolor = '#FFB74D', fontcolor = white]
        HOST2 [label = 'Usia\\n5-14 Tahun', fillcolor = '#FFB74D', fontcolor = white]
        HOST3 [label = 'Riwayat Infeksi\\nSebelumnya', fillcolor = '#FFB74D', fontcolor = white]
        HOST4 [label = 'Laki-laki', fillcolor = '#FFB74D', fontcolor = white]
        HOST5 [label = 'Golongan\\nDarah AB', fillcolor = '#FFB74D', fontcolor = white]
      }

      # Edges Environment
      ENV1 -> NYAMUK [label = 'meningkatkan\\npopulasi', fontsize = 9]
      ENV1 -> MANUSIA [label = 'meningkatkan\\npenyebaran', fontsize = 9]
      ENV2 -> GENANGAN [label = 'menciptakan', fontsize = 9]
      ENV3 -> NYAMUK [label = 'mempercepat\\nsiklus hidup', fontsize = 9]
      ENV4 -> GENANGAN [label = 'tidak\\nmembersihkan', fontsize = 9]
      GENANGAN -> NYAMUK [label = 'tempat\\nperindukan', fontsize = 9]

      # Edges Agent-Vector-Host
      NYAMUK -> VIRUS [label = 'membawa', fontsize = 9]
      VIRUS -> MANUSIA [label = 'transmisi melalui\\ngigitan nyamuk', fontsize = 9]
      MANUSIA -> DBD [label = 'infeksi', fontsize = 9]

      # Edges Host Factors
      HOST1 -> DBD [label = 'meningkatkan\\nkerentanan', fontsize = 9, color = '#F57C00']
      HOST2 -> DBD [label = 'meningkatkan\\nkerentanan', fontsize = 9, color = '#F57C00']
      HOST3 -> DBD [label = 'meningkatkan\\nkerentanan', fontsize = 9, color = '#F57C00']
      HOST4 -> DBD [label = 'meningkatkan\\nkerentanan', fontsize = 9, color = '#F57C00']
      HOST5 -> DBD [label = 'meningkatkan\\nkerentanan', fontsize = 9, color = '#F57C00']
    }
    ")
  })
  # ============================================================
  # 3. DATA OVERVIEW
  # ============================================================
  # ValueBoxes
  output$total_cases <- renderValueBox({
    valueBox(sum(data$JK), "Total Kasus DBD", icon = icon("virus"), color = "red")
  })
  
  output$total_deaths <- renderValueBox({
    valueBox(sum(data$KDBD), "Total Kematian DBD", icon = icon("skull-crossbones"), color = "black")
  })
  
  output$num_districts <- renderValueBox({
    valueBox(length(unique(data$kab_kota)), "Jumlah Kabupaten/Kota", icon = icon("map-marker-alt"), color = "blue")
  })
  
  # Data Table & Download
  output$data_table <- renderDT({
    # Buat salinan data supaya kolom asli tetap
    data_display <- data
    
    # Ganti nama kolom sesuai mapping
    colnames(data_display)[colnames(data_display) == "no"]   <- "No"
    colnames(data_display)[colnames(data_display) == "kab_kota"] <- "Kabupaten/Kota"
    colnames(data_display)[colnames(data_display) == "JK"]   <- "Jumlah DBD"
    colnames(data_display)[colnames(data_display) == "JP"]   <- "Jumlah Penduduk"
    colnames(data_display)[colnames(data_display) == "KDBD"] <- "Jumlah Kematian DBD"
    
    datatable(
      head(data_display, input$n_rows),
      options = list(scrollX = TRUE, pageLength = 10),
      rownames = FALSE
    )
  })
  output$download_data <- downloadHandler(
    filename = function() {
      paste0("Data_DBD_Jabar_", Sys.Date(), ".xlsx")
    },
    content = function(file) {
      write.xlsx(data, file)
    }
  )
  
  
  # ============================================================
  # 4. IMPORT DATA
  # ============================================================
  
  output$preview_upload <- renderDT({
    req(input$upload_data)
    
    df <- read.xlsx(input$upload_data$datapath)
    datatable(df, options = list(pageLength = 10, scrollX = TRUE))
  })
  
  output$download_template <- downloadHandler(
    filename = function() {
      "Template_Data_DBD.xlsx"
    },
    content = function(file) {
      template <- data.frame(
        'Kabupaten/Kota' = c("Kabupaten A", "Kabupaten B"),
        'Jumlah Kasus DBD' = c(100, 150),
        'Jumlah Penduduk' = c(50000, 60000),
        'Jumlah Kematian DBD' = c(2, 3)
      )
      write.xlsx(template, file)
    }
  )
  
  # ============================================================
  # 4. STATISTIKA DESKRIPTIF
  # ============================================================
  
  desc_data <- reactive({
    data   # langsung pakai seluruh data tanpa filtering
  })
  
  desc_data_renamed <- reactive({
    df <- desc_data()
    df <- df %>% rename(
      "No" = no,
      "Kabupaten/Kota" = kab_kota,
      "Jumlah DBD" = JK,
      "Jumlah Penduduk" = JP,
      "Jumlah Kematian DBD" = KDBD
    )
    df
  })
  
  output$desc_histogram <- renderPlotly({
    req(input$desc_vars)
    
    df <- desc_data_renamed()
    
    plots <- lapply(input$desc_vars, function(var) {
      plot_ly(df, x = ~get(var), type = "histogram", name = var,
              marker = list(line = list(color = 'white', width = 1))) %>%
        layout(xaxis = list(title = var), yaxis = list(title = "Frekuensi"))
    })
    
    subplot(plots, nrows = ceiling(length(input$desc_vars)/2), shareX = FALSE, shareY = FALSE, titleX = TRUE, titleY = TRUE)
  })
  
  output$desc_boxplot <- renderPlotly({
    req(input$desc_vars)
    
    df <- desc_data_renamed()
    
    plots <- lapply(input$desc_vars, function(var) {
      plot_ly(df, y = ~get(var), type = "box", name = var, boxmean = TRUE,
              marker = list(color = 'blue')) %>%
        layout(yaxis = list(title = var))
    })
    
    subplot(plots, nrows = ceiling(length(input$desc_vars)/2), shareX = FALSE, shareY = FALSE)
  })
  
  output$desc_summary <- renderDT({
    req(input$desc_vars)
    
    df <- desc_data_renamed()
    
    summary_df <- df %>%
      select(all_of(input$desc_vars)) %>%
      summarise(across(everything(), list(
        Min = ~min(., na.rm = TRUE),
        Q1 = ~quantile(., 0.25, na.rm = TRUE),
        Median = ~median(., na.rm = TRUE),
        Mean = ~mean(., na.rm = TRUE),
        Q3 = ~quantile(., 0.75, na.rm = TRUE),
        Max = ~max(., na.rm = TRUE),
        SD = ~sd(., na.rm = TRUE)
      ))) %>%
      pivot_longer(everything(), names_to = "Variable_Stat", values_to = "Value") %>%
      separate(Variable_Stat, into = c("Variable", "Statistic"), sep = "_") %>%
      pivot_wider(names_from = Statistic, values_from = Value)
    
    datatable(summary_df, options = list(dom = 't', paging = FALSE, ordering = FALSE)) %>%
      formatRound(columns = 2:8, digits = 2)
  })
  
  output$download_desc <- downloadHandler(
    filename = function() {
      paste0("Statistik_Deskriptif_", Sys.Date(), ".xlsx")
    },
    content = function(file) {
      write.xlsx(desc_data_renamed(), file)
    }
  )
  
  
  # ============================================================
  # 5. SEBARAN DBD
  # ============================================================
  
  output$total_kasus <- renderValueBox({
    valueBox(
      value = format(total_JK, big.mark = ","),
      subtitle = "Total Kasus DBD",
      icon = icon("procedures"),
      color = "red"
    )
  })
  
  output$total_kematian <- renderValueBox({
    valueBox(
      value = format(total_KDBD, big.mark = ","),
      subtitle = "Total Kematian DBD",
      icon = icon("heart-broken"),
      color = "maroon"
    )
  })
  
  output$total_wilayah <- renderValueBox({
    valueBox(
      value = nrow(data),
      subtitle = "Kabupaten/Kota",
      icon = icon("map"),
      color = "orange"
    )
  })
  
  output$map_kasus <- renderLeaflet({
    pal <- colorNumeric("YlOrRd", domain = jabar_merged$JK)
    
    leaflet(jabar_merged) %>%
      addTiles() %>%
      addPolygons(
        fillColor = ~pal(JK),
        weight = 1,
        opacity = 1,
        color = "white",
        fillOpacity = 0.7,
        label = ~paste0(WADMKK, ": ", format(JK, big.mark = ","), " kasus"),
        highlightOptions = highlightOptions(weight = 3, color = "#666", fillOpacity = 0.9)
      ) %>%
      addLegend(pal = pal, values = ~JK,
                title = "Jumlah Kasus", position = "bottomright")
  })
  
  # Moran's I
  moran_result <- reactive({
    x <- jabar_merged$JK
    names(x) <- rownames(jabar_merged)
    moran.test(x, WL, zero.policy = TRUE)
  })
  
  # Geary's C
  geary_result <- reactive({
    x <- jabar_merged$JK
    names(x) <- rownames(jabar_merged)
    geary.test(x, WL, zero.policy = TRUE)
  })
  
  output$moranI_box <- renderValueBox({
    m <- moran_result()
    valueBox(
      value = round(m$estimate[1], 4),
      subtitle = paste0("Moran's I (p: ", round(m$p.value, 4), ")"),
      icon = icon("project-diagram"),
      color = if(m$p.value < 0.05) "green" else "red"
    )
  })
  
  output$gearyC_box <- renderValueBox({
    g <- geary_result()
    valueBox(
      value = round(g$estimate[1], 4),
      subtitle = paste0("Geary's C (p: ", round(g$p.value, 4), ")"),
      icon = icon("chart-area"),
      color = if(g$p.value < 0.05) "green" else "red"
    )
  })
  
  output$moran_plot <- renderPlotly({
    x <- jabar_merged$JK
    names(x) <- rownames(jabar_merged)
    wx <- lag.listw(WL, x, zero.policy = TRUE)
    
    x_std <- scale(x)[,1]
    wx_std <- scale(wx)[,1]
    
    region_names <- data$kab_kota
    
    quadrant <- case_when(
      x_std > 0 & wx_std > 0 ~ "Q1: High-High",
      x_std < 0 & wx_std > 0 ~ "Q2: Low-High",
      x_std < 0 & wx_std < 0 ~ "Q3: Low-Low",
      x_std > 0 & wx_std < 0 ~ "Q4: High-Low"
    )
    
    df_moran <- data.frame(
      x = x_std, 
      wx = wx_std,
      region = region_names,
      quadrant = quadrant,
      x_original = x,
      wx_original = wx
    )
    
    colors <- c("Q1: High-High" = "#d62728",
                "Q2: Low-High" = "#ff7f0e",
                "Q3: Low-Low" = "#1f77b4",
                "Q4: High-Low" = "#2ca02c")
    
    plot_ly(df_moran, 
            x = ~x, 
            y = ~wx,
            color = ~quadrant,
            colors = colors,
            type = 'scatter',
            mode = 'markers',
            marker = list(size = 10, line = list(color = 'white', width = 1)),
            text = ~paste0("<b>", region, "</b><br>",
                           "Kasus: ", format(x_original, big.mark = ","), "<br>",
                           "Spatial Lag: ", round(wx_original, 2), "<br>",
                           "Kuadran: ", quadrant),
            hoverinfo = 'text') %>%
      add_lines(x = ~x, y = fitted(lm(wx ~ x, data = df_moran)),
                name = "Trend Line",
                line = list(color = 'black', width = 2, dash = 'dash'),
                hoverinfo = 'skip',
                showlegend = TRUE) %>%
      layout(
        title = list(text = "<b>Moran's I Scatterplot - Kasus DBD</b>", font = list(size = 16)),
        xaxis = list(title = "Standardized Kasus DBD", zeroline = TRUE),
        yaxis = list(title = "Spatial Lag", zeroline = TRUE),
        hovermode = 'closest',
        legend = list(x = 0.02, y = 0.98)
      )
  })
  
  # LISA Map
  output$lisa_map <- renderLeaflet({
    x <- jabar_merged$JK
    names(x) <- rownames(jabar_merged)
    lisa <- localmoran(x, WL, zero.policy = TRUE)
    
    x_std <- scale(x)
    x_lag_std <- scale(lag.listw(WL, x, zero.policy = TRUE))
    
    lisa_class <- rep("Not Significant", length(x))
    sig <- lisa[, 5] < 0.05
    
    lisa_class[sig & x_std > 0 & x_lag_std > 0] <- "High-High"
    lisa_class[sig & x_std < 0 & x_lag_std < 0] <- "Low-Low"
    lisa_class[sig & x_std > 0 & x_lag_std < 0] <- "High-Low"
    lisa_class[sig & x_std < 0 & x_lag_std > 0] <- "Low-High"
    
    jabar_merged$lisa_class <- lisa_class
    
    pal <- colorFactor(c("red", "blue", "pink", "lightblue", "white"),
                       domain = c("High-High", "Low-Low", "High-Low", "Low-High", "Not Significant"))
    
    leaflet(jabar_merged) %>%
      addTiles() %>%
      addPolygons(
        fillColor = ~pal(lisa_class),
        weight = 1,
        opacity = 1,
        color = "black",
        fillOpacity = 0.7,
        label = ~paste0(WADMKK, ": ", lisa_class, " (", format(JK, big.mark = ","), " kasus)"),
        highlightOptions = highlightOptions(weight = 3, fillOpacity = 0.9)
      ) %>%
      addLegend(pal = pal, values = ~lisa_class,
                title = "LISA Cluster", position = "bottomright")
  })
  
  # Getis-Ord Gi*
  output$getisord_map <- renderLeaflet({
    x <- jabar_merged$JK
    names(x) <- rownames(jabar_merged)
    
    gi_z <- as.numeric(localG(x, WL, zero.policy = TRUE))
    jabar_merged$z_Gistar <- gi_z
    
    jabar_merged$hotcold <- case_when(
      gi_z >=  1.96 ~ "Hot spot (p???0.05)",
      gi_z <= -1.96 ~ "Cold spot (p???0.05)",
      TRUE          ~ "Not significant"
    )
    
    pal <- colorFactor(
      palette = c("Hot spot (p???0.05)" = "#b2182b",
                  "Cold spot (p???0.05)" = "#2166ac",
                  "Not significant"      = "grey85"),
      domain = jabar_merged$hotcold
    )
    
    leaflet(jabar_merged) %>%
      addTiles() %>%
      addPolygons(
        fillColor = ~pal(hotcold),
        weight = 1,
        opacity = 1,
        color = "black",
        fillOpacity = 0.7,
        label = ~paste0(WADMKK, ": ", hotcold, 
                        " (z = ", round(z_Gistar, 2), ", Kasus: ", format(JK, big.mark = ","), ")"),
        highlightOptions = highlightOptions(weight = 3, fillOpacity = 0.9)
      ) %>%
      addLegend(pal = pal, values = ~hotcold,
                title = "Getis-Ord Gi*", position = "bottomright")
  })
  
  # ============================================================
  # 6. UKURAN EPIDEMIOLOGI
  # ============================================================
  
  output$prev_jabar_box <- renderValueBox({
    valueBox(
      value = paste0(round(prevalensi_jabar, 2), "%"),
      subtitle = "Prevalensi DBD Jawa Barat",
      icon = icon("percentage"),
      color = "light-blue"
    )
  })
  
  output$cfr_jabar_box <- renderValueBox({
    valueBox(
      value = paste0(round(cfr_jabar, 2), "%"),
      subtitle = "CFR DBD Jawa Barat",
      icon = icon("heartbeat"),
      color = "red"
    )
  })
  
  # PETA PREVALENSI
  output$map_prevalensi <- renderLeaflet({
    breaks_prev <- c(0, 0.09, 0.13, 0.20, Inf)
    labels_prev <- c("???0.09%", "0.10-0.13%", "0.14-0.20%", ">0.20%")
    
    jabar_merged$Kategori_Prev <- cut(
      jabar_merged$Prevalensi,
      breaks = breaks_prev,
      labels = labels_prev,
      right = TRUE,
      include.lowest = TRUE
    )
    
    pal <- colorFactor(
      palette = c("???0.09%" = "#ffffb2",
                  "0.10-0.13%" = "#fecc5c",
                  "0.14-0.20%" = "#fd8d3c",
                  ">0.20%" = "#e31a1c"),
      domain = jabar_merged$Kategori_Prev
    )
    
    leaflet(jabar_merged) %>%
      addTiles() %>%
      addPolygons(
        fillColor = ~pal(Kategori_Prev),
        weight = 1,
        opacity = 1,
        color = "black",
        fillOpacity = 0.7,
        label = ~paste0(WADMKK, ": ", round(Prevalensi, 2), "%"),
        highlightOptions = highlightOptions(weight = 3, fillOpacity = 0.9)
      ) %>%
      addLegend(pal = pal, values = ~Kategori_Prev,
                title = "Prevalensi (%)", position = "bottomright")
  })
  
  # PREVALENSI PER WILAYAH
  prev_wilayah <- reactive({
    req(input$prev_cities)
    
    jabar_merged %>%
      filter(WADMKK %in% input$prev_cities) %>%
      select(WADMKK, JK, JP, Prevalensi) %>%
      st_drop_geometry() %>%
      arrange(desc(Prevalensi))
  })
  
  output$prev_table <- renderDT({
    req(input$prev_cities)
    
    df <- prev_wilayah()
    
    datatable(
      df,
      colnames = c("Kabupaten/Kota", "Jumlah Kasus", "Populasi", "Prevalensi (%)"),
      options = list(dom = 't', paging = FALSE, ordering = FALSE)
    ) %>%
      formatRound(columns = "Prevalensi", digits = 2) %>%
      formatCurrency(columns = c("JK", "JP"), currency = "", digits = 0)
  })
  
  output$prev_bar <- renderPlotly({
    req(input$prev_cities)
    
    df <- prev_wilayah()
    
    plot_ly(df, x = ~Prevalensi, y = ~reorder(WADMKK, Prevalensi),
            type = 'bar', orientation = 'h',
            marker = list(color = '#3498db',
                          line = list(color = 'white', width = 1)),
            text = ~paste0(round(Prevalensi, 2), "%"),
            textposition = 'outside',
            hoverinfo = 'text',
            hovertext = ~paste0("<b>", WADMKK, "</b><br>",
                                "Prevalensi: ", round(Prevalensi, 2), "%<br>",
                                "Kasus: ", format(JK, big.mark = ","), "<br>",
                                "Populasi: ", format(JP, big.mark = ","))) %>%
      layout(
        title = "Prevalensi DBD per Kabupaten/Kota",
        xaxis = list(title = "Prevalensi (%)"),
        yaxis = list(title = ""),
        margin = list(l = 150)
      )
  })
  
  # PETA CFR
  output$map_cfr <- renderLeaflet({
    breaks_cfr <- c(0, 0.35, 0.51, 0.74, Inf)
    labels_cfr <- c("???0.35%", "0.36-0.51%", "0.52-0.74%", ">0.74%")
    
    jabar_merged$Kategori_CFR <- cut(
      jabar_merged$CFR,
      breaks = breaks_cfr,
      labels = labels_cfr,
      right = TRUE,
      include.lowest = TRUE
    )
    
    pal <- colorFactor(
      palette = c("???0.35%" = "#ffffb2",
                  "0.36-0.51%" = "#fecc5c",
                  "0.52-0.74%" = "#fd8d3c",
                  ">0.74%" = "#e31a1c"),
      domain = jabar_merged$Kategori_CFR
    )
    
    leaflet(jabar_merged) %>%
      addTiles() %>%
      addPolygons(
        fillColor = ~pal(Kategori_CFR),
        weight = 1,
        opacity = 1,
        color = "black",
        fillOpacity = 0.7,
        label = ~paste0(WADMKK, ": ", round(CFR, 2), "%"),
        highlightOptions = highlightOptions(weight = 3, fillOpacity = 0.9)
      ) %>%
      addLegend(pal = pal, values = ~Kategori_CFR,
                title = "CFR (%)", position = "bottomright")
  })
  
  # CFR PER WILAYAH
  cfr_wilayah <- reactive({
    req(input$cfr_cities)
    
    jabar_merged %>%
      filter(WADMKK %in% input$cfr_cities) %>%
      select(WADMKK, JK, KDBD, CFR) %>%
      st_drop_geometry() %>%
      arrange(desc(CFR))
  })
  
  output$cfr_table <- renderDT({
    req(input$cfr_cities)
    
    df <- cfr_wilayah()
    
    datatable(
      df,
      colnames = c("Kabupaten/Kota", "Jumlah Kasus", "Kematian", "CFR (%)"),
      options = list(dom = 't', paging = FALSE, ordering = FALSE)
    ) %>%
      formatRound(columns = "CFR", digits = 2) %>%
      formatCurrency(columns = c("JK", "KDBD"), currency = "", digits = 0)
  })
  
  output$cfr_bar <- renderPlotly({
    req(input$cfr_cities)
    
    df <- cfr_wilayah()
    
    plot_ly(df, x = ~CFR, y = ~reorder(WADMKK, CFR),
            type = 'bar', orientation = 'h',
            marker = list(color = '#e74c3c',
                          line = list(color = 'white', width = 1)),
            text = ~paste0(round(CFR, 2), "%"),
            textposition = 'outside',
            hoverinfo = 'text',
            hovertext = ~paste0("<b>", WADMKK, "</b><br>",
                                "CFR: ", round(CFR, 2), "%<br>",
                                "Kasus: ", format(JK, big.mark = ","), "<br>",
                                "Kematian: ", KDBD)) %>%
      layout(
        title = "Case Fatality Rate per Kabupaten/Kota",
        xaxis = list(title = "CFR (%)"),
        yaxis = list(title = ""),
        margin = list(l = 150)
      )
  })
  
}

# ============================================================
# 4. RUN APP
# ============================================================

shinyApp(ui = ui, server = server)