| | |
| |
|
| |
|
| | |
| | |
| | |
| | create_empty_plot <- function(message = "Data not available for this parameter") { |
| | plotly::plot_ly() %>% |
| | plotly::add_trace(type = "scatter", mode = "markers", marker = list(opacity = 0), showlegend = FALSE) %>% |
| | plotly::add_annotations( |
| | text = message, |
| | showarrow = FALSE, |
| | xref = "paper", yref = "paper", |
| | x = 0.5, y = 0.5, |
| | font = list(size = 16, color = "#666") |
| | ) %>% |
| | plotly::layout( |
| | xaxis = list(visible = FALSE), |
| | yaxis = list(visible = FALSE), |
| | margin = list(t = 50, b = 50, l = 50, r = 50) |
| | ) %>% |
| | plotly::config(displaylogo = FALSE) |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | ensure_temporal_continuity <- function(df, resolution = NULL) { |
| | if (is.null(df) || nrow(df) < 2) { |
| | return(df) |
| | } |
| |
|
| | seq_by <- NULL |
| | if (!is.null(resolution)) { |
| | seq_by <- switch(tolower(resolution), |
| | "hourly" = "hour", |
| | "daily" = "day", |
| | "monthly" = "month", |
| | NULL |
| | ) |
| | } |
| |
|
| | if (is.null(seq_by)) { |
| | |
| | |
| | n_check <- min(nrow(df), 200) |
| | dt_vals <- as.numeric(difftime(df$datetime[2:n_check], df$datetime[1:(n_check - 1)], units = "hours")) |
| | |
| | dt_vals <- dt_vals[!is.na(dt_vals) & dt_vals > 0] |
| |
|
| | if (length(dt_vals) == 0) { |
| | return(df) |
| | } |
| |
|
| | dt <- min(dt_vals) |
| |
|
| | seq_by <- "hour" |
| | if (dt >= 600) { |
| | seq_by <- "month" |
| | } else if (dt >= 23) { |
| | seq_by <- "day" |
| | } |
| | } |
| |
|
| | |
| | full_dates <- data.frame( |
| | datetime = seq( |
| | from = min(df$datetime), |
| | to = max(df$datetime), |
| | by = seq_by |
| | ) |
| | ) |
| |
|
| | |
| | |
| | df_complete <- dplyr::full_join(full_dates, df, by = "datetime") %>% |
| | dplyr::arrange(datetime) |
| |
|
| | return(df_complete) |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | split_into_chunks <- function(df, value_col) { |
| | |
| | if (nrow(df) == 0) { |
| | return(list()) |
| | } |
| |
|
| | |
| | |
| | |
| | is_na_vec <- is.na(df[[value_col]]) |
| |
|
| | |
| | if (all(is_na_vec)) { |
| | return(list()) |
| | } |
| |
|
| | rle_res <- rle(is_na_vec) |
| |
|
| | |
| | |
| | |
| | |
| |
|
| |
|
| | |
| | |
| | |
| | |
| | |
| |
|
| | |
| | chunk_ids <- rep(seq_along(rle_res$values), rle_res$lengths) |
| |
|
| | df$chunk_id <- chunk_ids |
| |
|
| | |
| | valid_chunks <- df[!is_na_vec, ] |
| |
|
| | if (nrow(valid_chunks) == 0) { |
| | return(list()) |
| | } |
| |
|
| | |
| | split(valid_chunks, valid_chunks$chunk_id) |
| | } |
| |
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | clean_dwd_data <- function(df) { |
| | if (is.null(df) || nrow(df) == 0) { |
| | return(df) |
| | } |
| |
|
| | |
| | weather_cols <- c( |
| | "temp", "dew_point", "rh", "abs_humidity", "vapor_pressure", "wet_bulb_temp", |
| | "precip", "wind_speed", "wind_dir", "pressure", "station_pressure", "cloud_cover", "wind_gust_max", "solar_global", "sunshine_duration", |
| | "visibility", |
| | "soil_temp_2cm", "soil_temp_5cm", "soil_temp_10cm", "soil_temp_20cm", "soil_temp_50cm", "soil_temp_100cm", |
| | "snow_depth", "snow_water_equiv", "snow_fresh_sum", "snow_depth_sum", |
| | "temp_max", "temp_min", "temp_max_avg", "temp_min_avg" |
| | ) |
| |
|
| | |
| | for (col in weather_cols) { |
| | if (col %in% names(df)) { |
| | |
| | if (!is.numeric(df[[col]])) { |
| | df[[col]] <- suppressWarnings(as.numeric(as.character(df[[col]]))) |
| | } |
| | |
| | bad_idx <- is.na(df[[col]]) | df[[col]] >= 7999 | df[[col]] <= -998 |
| | df[[col]][bad_idx] <- NA_real_ |
| | } |
| | } |
| |
|
| | |
| | if ("temp" %in% names(df)) df$temp[df$temp < -90 | df$temp > 60] <- NA_real_ |
| | if ("dew_point" %in% names(df)) df$dew_point[df$dew_point < -90 | df$dew_point > 60] <- NA_real_ |
| | if ("wet_bulb_temp" %in% names(df)) df$wet_bulb_temp[df$wet_bulb_temp < -90 | df$wet_bulb_temp > 60] <- NA_real_ |
| | if ("rh" %in% names(df)) df$rh[df$rh < 0 | df$rh > 100] <- NA_real_ |
| | if ("abs_humidity" %in% names(df)) df$abs_humidity[df$abs_humidity < 0] <- NA_real_ |
| | if ("vapor_pressure" %in% names(df)) df$vapor_pressure[df$vapor_pressure < 0] <- NA_real_ |
| | if ("precip" %in% names(df)) df$precip[df$precip < 0 | df$precip > 3000] <- NA_real_ |
| | if ("pressure" %in% names(df)) df$pressure[df$pressure < 800 | df$pressure > 1100] <- NA_real_ |
| | if ("station_pressure" %in% names(df)) df$station_pressure[df$station_pressure < 500 | df$station_pressure > 1100] <- NA_real_ |
| | if ("solar_global" %in% names(df)) df$solar_global[df$solar_global < 0 | df$solar_global > 3500] <- NA_real_ |
| | if ("visibility" %in% names(df)) df$visibility[df$visibility < 0] <- NA_real_ |
| | if ("cloud_cover" %in% names(df)) df$cloud_cover[df$cloud_cover < 0 | df$cloud_cover > 9] <- NA_real_ |
| |
|
| | return(df) |
| | } |
| |
|
| | |
| | |
| | calculate_dew_point <- function(temp, rh) { |
| | if (is.null(temp) | is.null(rh)) { |
| | return(NA) |
| | } |
| | |
| | a <- 17.625 |
| | b <- 243.04 |
| | alpha <- log(rh / 100) + (a * temp) / (b + temp) |
| | dp <- (b * alpha) / (a - alpha) |
| | return(dp) |
| | } |
| |
|
| | |
| | create_daily_summary <- function(df) { |
| | if (is.null(df) || nrow(df) == 0) { |
| | return(NULL) |
| | } |
| | |
| | df <- clean_dwd_data(df) |
| |
|
| | |
| | has_daily_minmax <- "temp_min" %in% names(df) && "temp_max" %in% names(df) |
| |
|
| | if (has_daily_minmax) { |
| | |
| | df %>% |
| | mutate(date = as.Date(datetime)) %>% |
| | group_by(date) %>% |
| | summarise( |
| | Tmax = if (all(is.na(temp_max))) NA else max(temp_max, na.rm = TRUE), |
| | Tmin = if (all(is.na(temp_min))) NA else min(temp_min, na.rm = TRUE), |
| | .groups = "drop" |
| | ) |
| | } else { |
| | |
| | df %>% |
| | mutate(date = as.Date(datetime)) %>% |
| | group_by(date) %>% |
| | summarise( |
| | Tmax = if (all(is.na(temp))) NA else max(temp, na.rm = TRUE), |
| | Tmin = if (all(is.na(temp))) NA else min(temp, na.rm = TRUE), |
| | .groups = "drop" |
| | ) |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | create_weather_trends_plot <- function(df, resolution = NULL) { |
| | if (is.null(df) || nrow(df) == 0) { |
| | return(create_empty_plot("No data available for the selected period")) |
| | } |
| |
|
| | |
| | df <- clean_dwd_data(df) |
| |
|
| |
|
| | |
| | if (!"dew_point" %in% names(df) && "rh" %in% names(df)) { |
| | df$dew_point <- calculate_dew_point(df$temp, df$rh) |
| | |
| | if (!is.null(df$dew_point)) { |
| | df$dew_point[is.na(df$dew_point) | df$dew_point < -90 | df$dew_point > 60] <- NA_real_ |
| | } |
| | } |
| |
|
| |
|
| | |
| | df <- ensure_temporal_continuity(df, resolution = resolution) |
| |
|
| | date_range_str <- paste( |
| | format(min(df$datetime), "%d %b %Y"), "-", |
| | format(max(df$datetime), "%d %b %Y") |
| | ) |
| |
|
| | date_range_vals <- c(min(df$datetime), max(df$datetime)) |
| |
|
| | |
| | has_temp <- "temp" %in% names(df) && any(!is.na(df$temp)) |
| | has_dew <- "dew_point" %in% names(df) && any(!is.na(df$dew_point)) |
| |
|
| | if (has_temp || has_dew) { |
| | p1 <- plotly::plot_ly(type = "scatter", mode = "none") |
| | if (has_temp) { |
| | p1 <- p1 %>% plotly::add_lines( |
| | data = df, |
| | x = ~datetime, |
| | y = ~temp, name = "Temperature", |
| | line = list(color = "#e53935", width = 2), |
| | hovertemplate = "Temp: %{y:.1f}°C<extra></extra>", |
| | type = "scatter", mode = "lines", |
| | connectgaps = FALSE, |
| | showlegend = TRUE |
| | ) |
| | } |
| | if (has_dew) { |
| | p1 <- p1 %>% plotly::add_lines( |
| | data = df, |
| | x = ~datetime, |
| | y = ~dew_point, name = "Dew Point", |
| | line = list(color = "#1e88e5", width = 1.5, dash = "dot"), |
| | hovertemplate = "Dew Pt: %{y:.1f}°C<extra></extra>", |
| | type = "scatter", mode = "lines", |
| | connectgaps = FALSE, |
| | showlegend = TRUE |
| | ) |
| | } |
| | p1 <- p1 %>% plotly::layout(yaxis = list(title = "Temp/Dew (°C)")) |
| | } else { |
| | p1 <- plotly::plot_ly(type = "scatter", mode = "none") %>% |
| | plotly::add_trace(type = "scatter", mode = "markers", marker = list(opacity = 0), name = "N/A", showlegend = FALSE) %>% |
| | plotly::add_annotations(text = "Temperature Not Available", showarrow = FALSE, font = list(color = "#999")) %>% |
| | plotly::layout(xaxis = list(title = "", type = "date"), yaxis = list(visible = FALSE, title = "Temp")) |
| | } |
| |
|
| | |
| | has_wind <- "wind_speed" %in% names(df) && any(!is.na(df$wind_speed)) |
| | has_gust <- "wind_gust_max" %in% names(df) && any(!is.na(df$wind_gust_max)) |
| |
|
| | if (has_wind || has_gust) { |
| | p2 <- plotly::plot_ly(type = "scatter", mode = "none") |
| | if (has_wind) { |
| | chunks <- split_into_chunks(df, "wind_speed") |
| | for (i in seq_along(chunks)) { |
| | chunk <- chunks[[i]] |
| | if (nrow(chunk) < 1) next |
| |
|
| | p2 <- p2 %>% plotly::add_lines( |
| | data = chunk, |
| | x = ~datetime, |
| | y = ~wind_speed, name = "Wind Speed", fill = "tozeroy", |
| | line = list(color = "#43a047", width = 1), |
| | fillcolor = "rgba(67, 160, 71, 0.5)", |
| | hovertemplate = "Wind: %{y:.1f} m/s<extra></extra>", |
| | type = "scatter", mode = "lines", |
| | connectgaps = FALSE, |
| | showlegend = (i == 1), |
| | legendgroup = "wind_trend" |
| | ) |
| | } |
| | } |
| | if (has_gust) { |
| | p2 <- p2 %>% plotly::add_markers( |
| | data = df %>% dplyr::filter(!is.na(wind_gust_max)), |
| | x = ~datetime, |
| | y = ~wind_gust_max, name = "Gust (Max)", |
| | marker = list(color = "#2e7d32", size = 4), |
| | hovertemplate = "Gust: %{y:.1f} m/s<extra></extra>", |
| | type = "scatter", mode = "markers", |
| | showlegend = TRUE |
| | ) |
| | } |
| | p2 <- p2 %>% plotly::layout(yaxis = list(title = "Wind (m/s)")) |
| | } else { |
| | p2 <- plotly::plot_ly(type = "scatter", mode = "none") %>% |
| | plotly::add_trace(type = "scatter", mode = "markers", marker = list(opacity = 0), name = "N/A", showlegend = FALSE) %>% |
| | plotly::add_annotations(text = "Wind Not Available", showarrow = FALSE, font = list(color = "#999")) %>% |
| | plotly::layout(xaxis = list(title = "", type = "date"), yaxis = list(visible = FALSE, title = "Wind")) |
| | } |
| |
|
| | |
| | has_msl <- "pressure" %in% names(df) && any(!is.na(df$pressure)) |
| | has_station_p <- "station_pressure" %in% names(df) && any(!is.na(df$station_pressure)) |
| |
|
| | if (has_msl || has_station_p) { |
| | p3 <- plotly::plot_ly(type = "scatter", mode = "none") |
| | if (has_msl) { |
| | p3 <- p3 %>% |
| | plotly::add_lines( |
| | data = df, |
| | x = ~datetime, |
| | y = ~pressure, name = "Sea Level", |
| | line = list(color = "#7b1fa2", width = 1.5), |
| | hovertemplate = "MSL: %{y:.1f} hPa<extra></extra>", |
| | type = "scatter", mode = "lines", |
| | connectgaps = FALSE, |
| | showlegend = TRUE |
| | ) |
| | } |
| | if (has_station_p) { |
| | p3 <- p3 %>% |
| | plotly::add_lines( |
| | data = df, |
| | x = ~datetime, |
| | y = ~station_pressure, name = "Pressure (Station)", |
| | line = list(color = "#AB47BC", width = 1.5, dash = "dot"), |
| | hovertemplate = "Stn: %{y:.1f} hPa<extra></extra>", |
| | type = "scatter", mode = "lines", |
| | connectgaps = FALSE, |
| | showlegend = TRUE |
| | ) |
| | } |
| | p3 <- p3 %>% |
| | plotly::add_lines( |
| | x = date_range_vals, |
| | y = c(1013.25, 1013.25), |
| | name = "Standard (1013.25)", |
| | line = list(color = "rgba(100, 100, 100, 0.5)", width = 1, dash = "dash"), |
| | showlegend = TRUE, |
| | hoverinfo = "skip", |
| | inherit = FALSE, |
| | type = "scatter", mode = "lines" |
| | ) %>% |
| | plotly::layout(yaxis = list(title = "Pressure (hPa)")) |
| | } else { |
| | p3 <- plotly::plot_ly(type = "scatter", mode = "none") %>% |
| | plotly::add_trace(type = "scatter", mode = "markers", marker = list(opacity = 0), name = "N/A", showlegend = FALSE) %>% |
| | plotly::add_annotations(text = "Pressure Not Available", showarrow = FALSE, font = list(color = "#999")) %>% |
| | plotly::layout(xaxis = list(title = "", type = "date"), yaxis = list(visible = FALSE, title = "Pressure")) |
| | } |
| |
|
| | |
| | has_cloud <- "cloud_cover" %in% names(df) && any(!is.na(df$cloud_cover)) |
| | if (has_cloud) { |
| | p4 <- plotly::plot_ly(type = "scatter", mode = "none") %>% |
| | plotly::add_lines( |
| | data = df, |
| | x = ~datetime, |
| | y = ~cloud_cover, name = "Cloud Cover", |
| | line = list(color = "#757575", width = 1.5), |
| | hovertemplate = "Cloud: %{y} /8 octas<extra></extra>", |
| | type = "scatter", mode = "lines", |
| | connectgaps = FALSE, |
| | showlegend = TRUE |
| | ) %>% |
| | plotly::layout(yaxis = list(title = "Cloud (octas)", range = c(0, 8))) |
| | } else { |
| | p4 <- plotly::plot_ly(type = "scatter", mode = "none") %>% |
| | plotly::add_trace(type = "scatter", mode = "markers", marker = list(opacity = 0), name = "N/A", showlegend = FALSE) %>% |
| | plotly::add_annotations(text = "Cloud Cover Not Available", showarrow = FALSE, font = list(color = "#999")) %>% |
| | plotly::layout(xaxis = list(title = "", type = "date"), yaxis = list(visible = FALSE, title = "Cloud")) |
| | } |
| |
|
| | |
| | plotly::subplot(p1, p2, p3, p4, nrows = 4, shareX = TRUE, titleY = TRUE, margin = 0.04) %>% |
| | plotly::layout( |
| | title = list(text = paste("Weather Overview:", date_range_str), font = list(size = 14)), |
| | xaxis = list(title = "", type = "date", range = date_range_vals), |
| | hovermode = "x unified", |
| | hoverlabel = list(), |
| | legend = list(orientation = "h", x = 0.5, xanchor = "center", y = -0.05), |
| | margin = list(t = 50, b = 80, l = 60, r = 20), |
| | modebar = list(orientation = "h") |
| | ) %>% |
| | plotly::config(displaylogo = FALSE) |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | create_temperature_plot <- function(df, resolution = NULL) { |
| | if (is.null(df) || nrow(df) == 0) { |
| | return(create_empty_plot("No data available for Temperature")) |
| | } |
| |
|
| | |
| | df <- clean_dwd_data(df) |
| |
|
| | |
| | df <- ensure_temporal_continuity(df, resolution = resolution) |
| |
|
| | date_range_str <- paste( |
| | format(min(df$datetime), "%d %b %Y"), "-", |
| | format(max(df$datetime), "%d %b %Y") |
| | ) |
| |
|
| | |
| | has_temp <- "temp" %in% names(df) && any(!is.na(df$temp)) |
| | has_tmin <- "temp_min" %in% names(df) && any(!is.na(df$temp_min)) |
| | has_tmax <- "temp_max" %in% names(df) && any(!is.na(df$temp_max)) |
| | has_tmin_avg <- "temp_min_avg" %in% names(df) && any(!is.na(df$temp_min_avg)) |
| | has_tmax_avg <- "temp_max_avg" %in% names(df) && any(!is.na(df$temp_max_avg)) |
| |
|
| | if (!has_temp && !has_tmin && !has_tmax) { |
| | return(create_empty_plot("No data available for Temperature")) |
| | } |
| |
|
| | p <- plotly::plot_ly(type = "scatter", mode = "lines") |
| |
|
| | |
| | if (has_tmin_avg && has_tmax_avg) { |
| | |
| | if (has_tmin && has_tmax) { |
| | |
| | p <- p %>% plotly::add_trace( |
| | data = df %>% dplyr::filter(!is.na(temp_min)), |
| | x = ~datetime, |
| | y = ~temp_min, |
| | type = "scatter", mode = "lines", |
| | name = "Abs Min", |
| | line = list(color = "rgba(211, 47, 47, 0.3)", width = 1), |
| | hovertemplate = "Abs Min: %{y:.1f}°C<extra></extra>", |
| | showlegend = FALSE |
| | ) |
| | |
| | p <- p %>% plotly::add_trace( |
| | data = df %>% dplyr::filter(!is.na(temp_max)), |
| | x = ~datetime, |
| | y = ~temp_max, |
| | type = "scatter", mode = "lines", |
| | fill = "tonexty", |
| | name = "Absolute Range", |
| | line = list(color = "rgba(211, 47, 47, 0.3)", width = 1), |
| | fillcolor = "rgba(211, 47, 47, 0.1)", |
| | hovertemplate = "Abs Max: %{y:.1f}°C<extra></extra>", |
| | showlegend = TRUE |
| | ) |
| | } |
| |
|
| | |
| | |
| | p <- p %>% plotly::add_trace( |
| | data = df %>% dplyr::filter(!is.na(temp_min_avg)), |
| | x = ~datetime, |
| | y = ~temp_min_avg, |
| | type = "scatter", mode = "lines", |
| | name = "Avg Min", |
| | line = list(color = "rgba(211, 47, 47, 0.5)", width = 1), |
| | hovertemplate = "Avg Daily Min: %{y:.1f}°C<extra></extra>", |
| | showlegend = FALSE |
| | ) |
| | |
| | p <- p %>% plotly::add_trace( |
| | data = df %>% dplyr::filter(!is.na(temp_max_avg)), |
| | x = ~datetime, |
| | y = ~temp_max_avg, |
| | type = "scatter", mode = "lines", |
| | fill = "tonexty", |
| | name = "Avg Daily Range", |
| | line = list(color = "rgba(211, 47, 47, 0.5)", width = 1), |
| | fillcolor = "rgba(211, 47, 47, 0.25)", |
| | hovertemplate = "Avg Daily Max: %{y:.1f}°C<extra></extra>", |
| | showlegend = TRUE |
| | ) |
| | } else { |
| | |
| |
|
| | |
| | |
| | if (has_tmin && has_tmax) { |
| | |
| | |
| |
|
| | |
| | chunks <- split_into_chunks(df, "temp_min") |
| |
|
| | for (i in seq_along(chunks)) { |
| | chunk <- chunks[[i]] |
| | if (nrow(chunk) < 1) next |
| |
|
| | |
| | show_leg <- (i == 1) |
| |
|
| | |
| | p <- p %>% plotly::add_trace( |
| | data = chunk, |
| | x = ~datetime, |
| | y = ~temp_min, |
| | type = "scatter", mode = "lines", |
| | name = "Daily Min", |
| | line = list(color = "rgba(211, 47, 47, 0.4)", width = 1), |
| | hovertemplate = "Tmin: %{y:.1f}°C<extra></extra>", |
| | showlegend = FALSE, |
| | legendgroup = "daily_range" |
| | ) |
| |
|
| | |
| | p <- p %>% plotly::add_trace( |
| | data = chunk, |
| | x = ~datetime, |
| | y = ~temp_max, |
| | type = "scatter", mode = "lines", |
| | fill = "tonexty", |
| | name = "Daily Range", |
| | line = list(color = "rgba(211, 47, 47, 0.4)", width = 1), |
| | fillcolor = "rgba(211, 47, 47, 0.2)", |
| | hovertemplate = "Tmax: %{y:.1f}°C<extra></extra>", |
| | showlegend = show_leg, |
| | legendgroup = "daily_range" |
| | ) |
| | } |
| | } else { |
| | |
| | if (has_tmax) { |
| | p <- p %>% plotly::add_lines( |
| | data = df, |
| | x = ~datetime, |
| | y = ~temp_max, name = "Tmax", |
| | line = list(color = "#d32f2f", width = 2), |
| | hovertemplate = "Tmax: %{y:.1f}°C<extra></extra>", |
| | type = "scatter", mode = "lines", |
| | connectgaps = FALSE, |
| | showlegend = TRUE |
| | ) |
| | } |
| |
|
| | if (has_tmin) { |
| | p <- p %>% plotly::add_lines( |
| | data = df, |
| | x = ~datetime, |
| | y = ~temp_min, name = "Tmin", |
| | line = list(color = "#1976d2", width = 2), |
| | hovertemplate = "Tmin: %{y:.1f}°C<extra></extra>", |
| | type = "scatter", mode = "lines", |
| | connectgaps = FALSE, |
| | showlegend = TRUE |
| | ) |
| | } |
| | } |
| | } |
| |
|
| | |
| | if (has_temp) { |
| | p <- p %>% plotly::add_lines( |
| | data = df, |
| | x = ~datetime, |
| | y = ~temp, name = if (has_tmin || has_tmax) "Tmean" else "Temperature", |
| | line = list( |
| | color = "#b71c1c", |
| | width = 2, |
| | dash = "solid" |
| | ), |
| | hovertemplate = paste0(if (has_tmin || has_tmax) "Tmean" else "Temp", ": %{y:.1f}°C<extra></extra>"), |
| | type = "scatter", mode = "lines", |
| | connectgaps = FALSE, |
| | showlegend = TRUE |
| | ) |
| | } |
| |
|
| | p %>% |
| | plotly::layout( |
| | title = list(text = paste("Temperature:", date_range_str), font = list(size = 14)), |
| | xaxis = list(title = "", type = "date"), |
| | yaxis = list(title = "Temperature (°C)"), |
| | hovermode = "x unified", |
| | hoverlabel = list(), |
| | legend = list(orientation = "h", x = 0.5, xanchor = "center", y = -0.1), |
| | margin = list(t = 50, b = 80, l = 60, r = 20), |
| | modebar = list(orientation = "h") |
| | ) %>% |
| | plotly::config(displaylogo = FALSE) |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | create_humidity_plot <- function(df, resolution = NULL) { |
| | if (is.null(df) || nrow(df) == 0) { |
| | return(create_empty_plot("No data available for Humidity")) |
| | } |
| |
|
| | |
| | df <- clean_dwd_data(df) |
| |
|
| | |
| | df <- ensure_temporal_continuity(df, resolution = resolution) |
| |
|
| | |
| | if (!"dew_point" %in% names(df) && "rh" %in% names(df) && "temp" %in% names(df)) { |
| | df$dew_point <- calculate_dew_point(df$temp, df$rh) |
| | if (!is.null(df$dew_point)) { |
| | df$dew_point[is.na(df$dew_point) | df$dew_point < -90 | df$dew_point > 60] <- NA_real_ |
| | } |
| | } |
| |
|
| | df <- ensure_temporal_continuity(df) |
| |
|
| | date_range_str <- paste( |
| | format(min(df$datetime), "%d %b %Y"), "-", |
| | format(max(df$datetime), "%d %b %Y") |
| | ) |
| |
|
| | has_dew <- "dew_point" %in% names(df) && any(!is.na(df$dew_point)) |
| | has_rh <- "rh" %in% names(df) && any(!is.na(df$rh)) |
| |
|
| | if (!has_dew && !has_rh) { |
| | return(create_empty_plot("No data available for Humidity")) |
| | } |
| |
|
| | p <- plotly::plot_ly(type = "scatter", mode = "none") |
| |
|
| | if (has_dew) { |
| | p <- p %>% plotly::add_lines( |
| | data = df, |
| | x = ~datetime, |
| | y = ~dew_point, name = "Dew Point", |
| | line = list(color = "#1e88e5", width = 2), |
| | hovertemplate = "Dew Pt: %{y:.1f}°C<extra></extra>", |
| | type = "scatter", mode = "lines", |
| | connectgaps = FALSE, |
| | showlegend = TRUE |
| | ) |
| | } |
| |
|
| | if (has_rh) { |
| | p <- p %>% plotly::add_lines( |
| | data = df, |
| | x = ~datetime, |
| | y = ~rh, name = "Relative Humidity", |
| | line = list(color = "#43a047", width = 1.5, dash = "dot"), |
| | hovertemplate = "RH: %{y:.0f}%<extra></extra>", |
| | type = "scatter", mode = "lines", |
| | yaxis = "y2", |
| | connectgaps = FALSE, |
| | showlegend = TRUE |
| | ) |
| | } |
| |
|
| | |
| | layout_args <- list( |
| | title = list(text = paste("Humidity:", date_range_str), font = list(size = 14)), |
| | xaxis = list(title = "", type = "date"), |
| | yaxis = list(title = "Dew Point (°C)"), |
| | hovermode = "x unified", |
| | hoverlabel = list(), |
| | legend = list(orientation = "h", x = 0.5, xanchor = "center", y = -0.1), |
| | margin = list(t = 50, b = 80, l = 60, r = 60), |
| | modebar = list(orientation = "h") |
| | ) |
| |
|
| | if (has_rh) { |
| | layout_args$yaxis2 <- list( |
| | title = "Relative Humidity (%)", |
| | overlaying = "y", |
| | side = "right", |
| | range = c(0, 100) |
| | ) |
| | } |
| |
|
| | do.call(plotly::layout, c(list(p), layout_args)) %>% |
| | plotly::config(displaylogo = FALSE) |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | create_wind_overview_plot <- function(df, resolution = NULL) { |
| | if (is.null(df) || nrow(df) == 0) { |
| | return(create_empty_plot("No data available for Wind")) |
| | } |
| |
|
| | |
| | df <- clean_dwd_data(df) |
| |
|
| | df <- ensure_temporal_continuity(df, resolution = resolution) |
| |
|
| | date_range_str <- paste( |
| | format(min(df$datetime), "%d %b %Y"), "-", |
| | format(max(df$datetime), "%d %b %Y") |
| | ) |
| |
|
| | has_wind <- "wind_speed" %in% names(df) && any(!is.na(df$wind_speed)) |
| | has_gust <- "wind_gust_max" %in% names(df) && any(!is.na(df$wind_gust_max)) |
| |
|
| | if (!has_wind && !has_gust) { |
| | return(create_empty_plot("No data available for Wind")) |
| | } |
| |
|
| | p <- plotly::plot_ly(type = "scatter", mode = "none") |
| |
|
| | if (has_wind) { |
| | chunks <- split_into_chunks(df, "wind_speed") |
| | for (i in seq_along(chunks)) { |
| | chunk <- chunks[[i]] |
| | if (nrow(chunk) < 1) next |
| |
|
| | p <- p %>% plotly::add_lines( |
| | data = chunk, |
| | x = ~datetime, |
| | y = ~wind_speed, name = "Wind Speed", fill = "tozeroy", |
| | line = list(color = "#43a047", width = 1), |
| | fillcolor = "rgba(67, 160, 71, 0.5)", |
| | hovertemplate = "Wind: %{y:.1f} m/s<extra></extra>", |
| | type = "scatter", mode = "lines", |
| | connectgaps = FALSE, |
| | showlegend = (i == 1), |
| | legendgroup = "wind_overview" |
| | ) |
| | } |
| | } |
| | if (has_gust) { |
| | p <- p %>% plotly::add_markers( |
| | data = df %>% dplyr::filter(!is.na(wind_gust_max)), |
| | x = ~datetime, |
| | y = ~wind_gust_max, name = "Gust (Max)", |
| | marker = list(color = "#2e7d32", size = 4), |
| | hovertemplate = "Gust: %{y:.1f} m/s<extra></extra>", |
| | type = "scatter", mode = "markers", |
| | showlegend = TRUE |
| | ) |
| | } |
| |
|
| | p %>% |
| | plotly::layout( |
| | title = list(text = paste("Wind:", date_range_str), font = list(size = 14)), |
| | xaxis = list(title = "", type = "date"), |
| | yaxis = list(title = "Wind Speed (m/s)"), |
| | hovermode = "x unified", |
| | hoverlabel = list(), |
| | legend = list(orientation = "h", x = 0.5, xanchor = "center", y = -0.1), |
| | margin = list(t = 50, b = 80, l = 60, r = 20), |
| | modebar = list(orientation = "h") |
| | ) %>% |
| | plotly::config(displaylogo = FALSE) |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | create_pressure_plot <- function(df, resolution = NULL) { |
| | if (is.null(df) || nrow(df) == 0) { |
| | return(create_empty_plot("No data available for Pressure")) |
| | } |
| |
|
| | |
| | df <- clean_dwd_data(df) |
| |
|
| | df <- ensure_temporal_continuity(df, resolution = resolution) |
| |
|
| | date_range_str <- paste( |
| | format(min(df$datetime), "%d %b %Y"), "-", |
| | format(max(df$datetime), "%d %b %Y") |
| | ) |
| |
|
| | date_range_vals <- c(min(df$datetime), max(df$datetime)) |
| |
|
| | has_msl <- "pressure" %in% names(df) && any(!is.na(df$pressure)) |
| | has_station_p <- "station_pressure" %in% names(df) && any(!is.na(df$station_pressure)) |
| |
|
| | if (!has_msl && !has_station_p) { |
| | return(create_empty_plot("No data available for Pressure")) |
| | } |
| |
|
| | p <- plotly::plot_ly(type = "scatter", mode = "none") |
| |
|
| | if (has_msl) { |
| | p <- p %>% |
| | plotly::add_lines( |
| | data = df, |
| | x = ~datetime, |
| | y = ~pressure, name = "Sea Level", |
| | line = list(color = "#7b1fa2", width = 1.5), |
| | hovertemplate = "MSL: %{y:.1f} hPa<extra></extra>", |
| | type = "scatter", mode = "lines", |
| | connectgaps = FALSE, |
| | showlegend = TRUE |
| | ) |
| | } |
| | if (has_station_p) { |
| | p <- p %>% |
| | plotly::add_lines( |
| | data = df, |
| | x = ~datetime, |
| | y = ~station_pressure, name = "Pressure (Station)", |
| | line = list(color = "#AB47BC", width = 1.5, dash = "dot"), |
| | hovertemplate = "Stn: %{y:.1f} hPa<extra></extra>", |
| | type = "scatter", mode = "lines", |
| | connectgaps = FALSE, |
| | showlegend = TRUE |
| | ) |
| | } |
| |
|
| | |
| | p <- p %>% |
| | plotly::add_lines( |
| | x = date_range_vals, |
| | y = c(1013.25, 1013.25), |
| | name = "Standard (1013.25)", |
| | line = list(color = "rgba(100, 100, 100, 0.5)", width = 1, dash = "dash"), |
| | showlegend = TRUE, |
| | hoverinfo = "skip", |
| | inherit = FALSE, |
| | type = "scatter", mode = "lines" |
| | ) |
| |
|
| | p %>% |
| | plotly::layout( |
| | title = list(text = paste("Pressure:", date_range_str), font = list(size = 14)), |
| | xaxis = list(title = "", type = "date"), |
| | yaxis = list(title = "Pressure (hPa)"), |
| | hovermode = "x unified", |
| | hoverlabel = list(), |
| | legend = list(orientation = "h", x = 0.5, xanchor = "center", y = -0.1), |
| | margin = list(t = 50, b = 80, l = 60, r = 20), |
| | modebar = list(orientation = "h") |
| | ) %>% |
| | plotly::config(displaylogo = FALSE) |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | create_cloud_cover_plot <- function(df, resolution = NULL) { |
| | if (is.null(df) || nrow(df) == 0) { |
| | return(create_empty_plot("No data available for Cloud Cover")) |
| | } |
| |
|
| | |
| | df <- clean_dwd_data(df) |
| |
|
| | df <- ensure_temporal_continuity(df, resolution = resolution) |
| |
|
| | date_range_str <- paste( |
| | format(min(df$datetime), "%d %b %Y"), "-", |
| | format(max(df$datetime), "%d %b %Y") |
| | ) |
| |
|
| | has_cloud <- "cloud_cover" %in% names(df) && any(!is.na(df$cloud_cover)) |
| |
|
| | if (!has_cloud) { |
| | return(create_empty_plot("No data available for Cloud Cover")) |
| | } |
| |
|
| | plotly::plot_ly(type = "scatter", mode = "none") %>% |
| | plotly::add_lines( |
| | data = df, |
| | x = ~datetime, |
| | y = ~cloud_cover, name = "Cloud Cover", |
| | line = list(color = "#757575", width = 1.5), |
| | hovertemplate = "Cloud: %{y} /8 octas<extra></extra>", |
| | type = "scatter", mode = "lines", |
| | connectgaps = FALSE, |
| | showlegend = TRUE |
| | ) %>% |
| | plotly::layout( |
| | title = list(text = paste("Cloud Cover:", date_range_str), font = list(size = 14)), |
| | xaxis = list(title = "", type = "date"), |
| | yaxis = list(title = "Cloud Cover (octas)", range = c(0, 8)), |
| | hovermode = "x unified", |
| | hoverlabel = list(), |
| | legend = list(orientation = "h", x = 0.5, xanchor = "center", y = -0.1), |
| | margin = list(t = 50, b = 80, l = 60, r = 20), |
| | modebar = list(orientation = "h") |
| | ) %>% |
| | plotly::config(displaylogo = FALSE) |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | create_precipitation_plot <- function(df, resolution = NULL) { |
| | if (is.null(df) || nrow(df) == 0) { |
| | return(create_empty_plot("No data available for the selected period")) |
| | } |
| |
|
| | |
| | df <- clean_dwd_data(df) |
| |
|
| | df <- ensure_temporal_continuity(df, resolution = resolution) |
| |
|
| | date_range_str <- paste( |
| | format(min(df$datetime), "%d %b %Y"), "-", |
| | format(max(df$datetime), "%d %b %Y") |
| | ) |
| |
|
| | |
| | |
| | resolution_label <- "1h" |
| | if (nrow(df) > 1) { |
| | dt <- as.numeric(difftime(df$datetime[2], df$datetime[1], units = "hours")) |
| | if (!is.na(dt)) { |
| | if (dt >= 600) { |
| | resolution_label <- "Monthly" |
| | } else if (dt >= 23) { |
| | resolution_label <- "Daily" |
| | } else if (dt < 0.9) { |
| | resolution_label <- "10 min" |
| | } |
| | } |
| | } |
| |
|
| | |
| | precip_cols <- c( |
| | "precip" = resolution_label |
| | ) |
| |
|
| | plot_list <- list() |
| |
|
| | |
| | for (col_name in names(precip_cols)) { |
| | if (col_name %in% names(df) && any(!is.na(df[[col_name]]))) { |
| | label <- precip_cols[[col_name]] |
| |
|
| | |
| | p_data <- df |
| |
|
| | if (nrow(p_data) > 0) { |
| | |
| | |
| |
|
| | |
| | |
| |
|
| | |
| | p <- plotly::plot_ly(data = p_data, x = ~datetime) %>% |
| | plotly::add_bars( |
| | y = as.formula(paste0("~", col_name)), |
| | name = paste("Precip", label), |
| | marker = list(color = "#0277bd"), |
| | hovertemplate = paste0(label, ": %{y:.1f} mm<extra></extra>"), |
| | showlegend = TRUE |
| | ) %>% |
| | plotly::layout( |
| | yaxis = list(title = paste0(label, " (mm)")) |
| | ) |
| |
|
| | plot_list[[length(plot_list) + 1]] <- p |
| | } |
| | } |
| | } |
| |
|
| | if (length(plot_list) == 0) { |
| | return(create_empty_plot("No precipitation data available")) |
| | } |
| |
|
| | |
| | plotly::subplot(plot_list, nrows = length(plot_list), shareX = TRUE, titleY = TRUE, margin = 0.04) %>% |
| | plotly::layout( |
| | title = list(text = paste("Precipitation:", date_range_str), font = list(size = 14)), |
| | xaxis = list(title = "", type = "date"), |
| | hovermode = "x unified", |
| | hoverlabel = list(), |
| | margin = list(t = 50, b = 80, l = 60, r = 20), |
| | modebar = list(orientation = "h") |
| | ) %>% |
| | plotly::config(displaylogo = FALSE) |
| | } |
| |
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | create_wind_rose_plot <- function(df) { |
| | if (is.null(df) || nrow(df) == 0) { |
| | return(create_empty_plot("No data available for the selected period")) |
| | } |
| |
|
| | |
| | df <- clean_dwd_data(df) |
| |
|
| | if (!all(c("wind_speed", "wind_dir") %in% names(df))) { |
| | return(create_empty_plot("Wind data not available for this station")) |
| | } |
| |
|
| | date_range_str <- paste( |
| | format(min(df$datetime), "%d %b %Y"), "-", |
| | format(max(df$datetime), "%d %b %Y") |
| | ) |
| |
|
| | wind_df <- df %>% |
| | dplyr::filter(!is.na(wind_speed), !is.na(wind_dir)) %>% |
| | dplyr::mutate( |
| | dir_bin = round(wind_dir / 22.5) * 22.5, |
| | dir_bin = ifelse(dir_bin == 360, 0, dir_bin), |
| | speed_cat = cut( |
| | wind_speed, |
| | breaks = c(-0.1, 2, 5, 10, 20, Inf), |
| | labels = c("0-2", "2-5", "5-10", "10-20", "20+") |
| | ) |
| | ) %>% |
| | dplyr::group_by(dir_bin, speed_cat) %>% |
| | dplyr::summarise(count = dplyr::n(), .groups = "drop") |
| |
|
| | total_obs <- sum(wind_df$count) |
| | wind_df <- wind_df %>% |
| | dplyr::mutate(percentage = count / total_obs * 100) |
| |
|
| | if (nrow(wind_df) == 0) { |
| | return(create_empty_plot("No wind data available for the selected period")) |
| | } |
| |
|
| | compass <- data.frame( |
| | dir_bin = seq(0, 337.5, by = 22.5), |
| | label = c( |
| | "N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", |
| | "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW" |
| | ) |
| | ) |
| |
|
| | p <- plotly::plot_ly(type = "barpolar") |
| | speed_levels <- levels(wind_df$speed_cat) |
| | colors <- c("#2196F3", "#4CAF50", "#FFC107", "#FF9800", "#F44336") |
| |
|
| | for (i in seq_along(speed_levels)) { |
| | lvl <- speed_levels[i] |
| | lvl_df <- wind_df %>% dplyr::filter(speed_cat == lvl) |
| | if (nrow(lvl_df) > 0) { |
| | p <- p %>% |
| | plotly::add_trace( |
| | data = lvl_df, type = "barpolar", |
| | r = ~percentage, theta = ~dir_bin, |
| | name = lvl, marker = list(color = colors[i]) |
| | ) |
| | } |
| | } |
| |
|
| | p %>% |
| | plotly::layout( |
| | title = list(text = paste("Wind Rose:", date_range_str), font = list(size = 14)), |
| | polar = list( |
| | angularaxis = list( |
| | rotation = 90, direction = "clockwise", |
| | tickmode = "array", tickvals = compass$dir_bin, ticktext = compass$label |
| | ), |
| | radialaxis = list(ticksuffix = "%") |
| | ), |
| | showlegend = TRUE, |
| | hoverlabel = list(), |
| | legend = list(title = list(text = "Wind Speed (m/s)"), orientation = "v"), |
| | margin = list(t = 80, b = 80, l = 40, r = 40), |
| | modebar = list(orientation = "h"), |
| | annotations = list( |
| | list( |
| | x = 0, y = 1.1, xref = "paper", yref = "paper", |
| | text = "ⓘ", |
| | showarrow = FALSE, |
| | font = list(size = 18, color = "#666"), |
| | xanchor = "left", |
| | hovertext = "Wind rose shows the frequency distribution of wind direction and speed", |
| | hoverinfo = "text" |
| | ) |
| | ) |
| | ) %>% |
| | plotly::config(displaylogo = FALSE) |
| | } |
| |
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | create_weathergami_plot <- function(df) { |
| | if (is.null(df) || nrow(df) == 0) { |
| | return(create_empty_plot("No data available for the selected period")) |
| | } |
| |
|
| | |
| | df <- clean_dwd_data(df) |
| |
|
| | |
| | has_daily_minmax <- "temp_min" %in% names(df) && "temp_max" %in% names(df) |
| |
|
| | if (!has_daily_minmax) { |
| | return(create_empty_plot("Weathergami requires daily data with Tmin/Tmax.<br>Please select daily resolution.")) |
| | } |
| |
|
| | |
| | |
| | daily_temps <- df %>% |
| | filter(!is.na(temp_min), !is.na(temp_max)) %>% |
| | mutate( |
| | date = as.Date(datetime), |
| | tmax_bin = round(temp_max), |
| | tmin_bin = round(temp_min) |
| | ) |
| |
|
| | if (nrow(daily_temps) == 0) { |
| | return(create_empty_plot("No valid temperature min/max data available")) |
| | } |
| |
|
| | date_range_str <- paste( |
| | format(min(daily_temps$datetime), "%d %b %Y"), "-", |
| | format(max(daily_temps$datetime), "%d %b %Y") |
| | ) |
| |
|
| | |
| | freq_table <- daily_temps %>% |
| | dplyr::group_by(tmax_bin, tmin_bin) %>% |
| | dplyr::summarise(count = dplyr::n(), .groups = "drop") |
| |
|
| | |
| | p <- plotly::plot_ly(type = "heatmap") %>% |
| | plotly::add_heatmap( |
| | data = freq_table, |
| | x = ~tmax_bin, |
| | y = ~tmin_bin, |
| | z = ~count, |
| | colors = c("#f7fbff", "#c6dbef", "#6baed6", "#2171b5", "#08519c", "#08306b"), |
| | colorbar = list(title = "Days"), |
| | hovertemplate = paste0( |
| | "Tmax: %{x}°C<br>", |
| | "Tmin: %{y}°C<br>", |
| | "Days: %{z}<extra></extra>" |
| | ) |
| | ) |
| |
|
| | |
| | tmin_r <- range(freq_table$tmin_bin) |
| | tmax_r <- range(freq_table$tmax_bin) |
| | diag_range <- seq(min(tmin_r, tmax_r) - 2, max(tmin_r, tmax_r) + 2) |
| |
|
| | p <- p %>% |
| | plotly::add_lines( |
| | x = diag_range, |
| | y = diag_range, |
| | line = list(color = "rgba(150, 150, 150, 0.5)", width = 1, dash = "dash"), |
| | name = "Tmax = Tmin", |
| | showlegend = TRUE, |
| | hoverinfo = "skip", |
| | inherit = FALSE |
| | ) |
| |
|
| | p %>% |
| | plotly::layout( |
| | title = list( |
| | text = paste("Weathergami:", date_range_str), |
| | font = list(size = 14) |
| | ), |
| | xaxis = list( |
| | title = "Daily Maximum Temperature (°C)", |
| | zeroline = FALSE |
| | ), |
| | yaxis = list( |
| | title = "Daily Minimum Temperature (°C)", |
| | zeroline = FALSE, |
| | scaleanchor = "x", |
| | scaleratio = 1 |
| | ), |
| | hovermode = "closest", |
| | hoverlabel = list() |
| | ) %>% |
| | plotly::config(displaylogo = FALSE) |
| | } |
| |
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | create_diurnal_plot <- function(df, offset_hours = 0) { |
| | if (is.null(df) || nrow(df) == 0) { |
| | return(create_empty_plot("No data available for the selected period")) |
| | } |
| |
|
| | |
| | df_diurnal <- df %>% |
| | dplyr::filter(!is.na(temp)) %>% |
| | dplyr::mutate( |
| | local_time = datetime + (offset_hours * 3600), |
| | date = as.Date(local_time), |
| | hour_val = lubridate::hour(local_time) |
| | ) |
| |
|
| | if (nrow(df_diurnal) == 0) { |
| | return(create_empty_plot("No temperature data available for diurnal plot")) |
| | } |
| |
|
| | median_cycle <- df_diurnal %>% |
| | dplyr::group_by(hour_val) %>% |
| | dplyr::summarise(median_temp = median(temp, na.rm = TRUE), .groups = "drop") |
| |
|
| | date_range_str <- paste( |
| | format(min(df$datetime), "%d %b %Y"), "-", |
| | format(max(df$datetime), "%d %b %Y") |
| | ) |
| |
|
| | |
| | unique_dates <- sort(unique(df_diurnal$date)) |
| | sampled_dates <- unique_dates[seq(1, length(unique_dates), length.out = min(length(unique_dates), 100))] |
| |
|
| | df_bg <- df_diurnal %>% filter(date %in% sampled_dates) |
| |
|
| | plotly::plot_ly(type = "scatter", mode = "none", showlegend = FALSE, hoverinfo = "none") %>% |
| | plotly::add_lines( |
| | data = df_bg, |
| | x = ~hour_val, |
| | y = ~temp, |
| | split = ~date, |
| | line = list(color = "rgba(150, 150, 150, 0.2)", width = 0.5), |
| | hoverinfo = "none", |
| | showlegend = FALSE, |
| | type = "scatter", mode = "lines" |
| | ) %>% |
| | plotly::add_lines( |
| | data = median_cycle, |
| | x = ~hour_val, |
| | y = ~median_temp, |
| | name = "Median Cycle", |
| | line = list(color = "#d32f2f", width = 4), |
| | hovertemplate = "Hour: %{x}:00<br>Median: %{y:.1f}°C<extra></extra>", |
| | type = "scatter", mode = "lines" |
| | ) %>% |
| | plotly::layout( |
| | title = list(text = paste("Diurnal Cycle:", date_range_str), font = list(size = 14)), |
| | xaxis = list(title = "Hour (UTC+Offset)"), |
| | yaxis = list(title = "Temperature (°C)"), |
| | showlegend = TRUE, |
| | hoverlabel = list() |
| | ) %>% |
| | plotly::config(displaylogo = FALSE) |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | create_solar_radiation_plot <- function(df, resolution = NULL) { |
| | if (is.null(df) || nrow(df) == 0) { |
| | return(create_empty_plot("No data available for Solar Radiation")) |
| | } |
| |
|
| | |
| | df <- clean_dwd_data(df) |
| |
|
| | df <- ensure_temporal_continuity(df, resolution = resolution) |
| |
|
| | has_global <- "solar_global" %in% names(df) && any(!is.na(df$solar_global)) |
| |
|
| | if (!has_global) { |
| | return(create_empty_plot("No data available for Solar Radiation")) |
| | } |
| |
|
| | date_range_str <- paste( |
| | format(min(df$datetime), "%d %b %Y"), "-", |
| | format(max(df$datetime), "%d %b %Y") |
| | ) |
| |
|
| | |
| | resolution_label <- "Hourly" |
| | if (nrow(df) > 1) { |
| | dt <- as.numeric(difftime(df$datetime[2], df$datetime[1], units = "hours")) |
| | if (!is.na(dt)) { |
| | if (dt >= 23) { |
| | resolution_label <- "Daily" |
| | } else if (dt < 0.9) { |
| | resolution_label <- "10 min" |
| | } |
| | } |
| | } |
| |
|
| | p <- plotly::plot_ly() |
| |
|
| | chunks <- split_into_chunks(df, "solar_global") |
| | for (i in seq_along(chunks)) { |
| | chunk <- chunks[[i]] |
| | if (nrow(chunk) < 1) next |
| |
|
| | p <- p %>% plotly::add_trace( |
| | data = chunk, |
| | x = ~datetime, |
| | y = ~solar_global, |
| | name = "Global Radiation", |
| | type = "scatter", |
| | mode = "lines", |
| | fill = "tozeroy", |
| | line = list(color = "rgba(255, 179, 0, 0.9)", width = 1), |
| | fillcolor = "rgba(255, 179, 0, 0.3)", |
| | hovertemplate = "Solar: %{y:,.0f} J/cm²<extra></extra>", |
| | connectgaps = FALSE, |
| | showlegend = (i == 1), |
| | legendgroup = "solar_rad" |
| | ) |
| | } |
| |
|
| | p %>% |
| | plotly::layout( |
| | title = list(text = paste(resolution_label, "Solar Radiation:", date_range_str), font = list(size = 14)), |
| | xaxis = list(title = "", type = "date"), |
| | yaxis = list(title = "Radiation (J/cm²)"), |
| | hovermode = "x unified", |
| | hoverlabel = list(), |
| | legend = list(orientation = "h", x = 0.5, xanchor = "center", y = -0.1), |
| | margin = list(t = 50, b = 80, l = 60, r = 20), |
| | modebar = list(orientation = "h") |
| | ) %>% |
| | plotly::config(displaylogo = FALSE) |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | create_sunshine_duration_plot <- function(df, resolution = NULL) { |
| | if (is.null(df) || nrow(df) == 0) { |
| | return(create_empty_plot("No data available for Sunshine Duration")) |
| | } |
| |
|
| | |
| | df <- clean_dwd_data(df) |
| |
|
| | df <- ensure_temporal_continuity(df, resolution = resolution) |
| |
|
| | has_sun <- "sunshine_duration" %in% names(df) && any(!is.na(df$sunshine_duration)) |
| |
|
| | if (!has_sun) { |
| | return(create_empty_plot("No data available for Sunshine Duration")) |
| | } |
| |
|
| | date_range_str <- paste( |
| | format(min(df$datetime), "%d %b %Y"), "-", |
| | format(max(df$datetime), "%d %b %Y") |
| | ) |
| |
|
| | |
| | date_range_vals <- c(min(df$datetime), max(df$datetime)) |
| |
|
| | |
| | resolution_label <- "Hourly" |
| | y_range <- c(0, 65) |
| | y_title <- "Sunshine (min)" |
| | if (nrow(df) > 1) { |
| | dt <- as.numeric(difftime(df$datetime[2], df$datetime[1], units = "hours")) |
| | if (!is.na(dt)) { |
| | if (dt >= 600) { |
| | resolution_label <- "Monthly" |
| | y_range <- NULL |
| | y_title <- "Sunshine (hours)" |
| | } else if (dt >= 23) { |
| | resolution_label <- "Daily" |
| | y_range <- c(0, 24) |
| | y_title <- "Sunshine (hours)" |
| | } else if (dt < 0.9) { |
| | resolution_label <- "10 min" |
| | y_range <- NULL |
| | y_title <- "Sunshine (min)" |
| | } |
| | } |
| | } |
| |
|
| | plotly::plot_ly( |
| | data = df, |
| | x = ~datetime, |
| | y = ~sunshine_duration, |
| | name = "Sunshine Duration", |
| | type = "bar", |
| | marker = list(color = "rgba(0, 0, 0, 0.3)", line = list(color = "#FFD700", width = 1.5)), |
| | hovertemplate = paste0("Sunshine: %{y:.1f} ", if (resolution_label %in% c("Daily", "Monthly")) "h" else "min", "<extra></extra>") |
| | ) %>% |
| | plotly::layout( |
| | title = list(text = paste(resolution_label, "Sunshine Duration:", date_range_str), font = list(size = 14)), |
| | xaxis = list(title = "", type = "date", range = date_range_vals), |
| | yaxis = list(title = y_title, range = y_range), |
| | hovermode = "x unified", |
| | hoverlabel = list(), |
| | legend = list(orientation = "h", x = 0.5, xanchor = "center", y = -0.1), |
| | margin = list(t = 50, b = 80, l = 60, r = 20), |
| | modebar = list(orientation = "h") |
| | ) %>% |
| | plotly::config(displaylogo = FALSE) |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | create_solar_plot_hourly <- function(df, resolution = NULL) { |
| | |
| |
|
| | has_global <- "solar_global" %in% names(df) && any(!is.na(df$solar_global)) |
| | has_sun <- "sunshine_duration" %in% names(df) && any(!is.na(df$sunshine_duration)) |
| |
|
| | df <- ensure_temporal_continuity(df, resolution = resolution) |
| |
|
| | date_range_str <- paste( |
| | format(min(df$datetime), "%d %b %Y"), "-", |
| | format(max(df$datetime), "%d %b %Y") |
| | ) |
| | date_range_vals <- c(min(df$datetime), max(df$datetime)) |
| |
|
| | |
| | if (has_global) { |
| | |
| | p1 <- plotly::plot_ly() |
| |
|
| | chunks <- split_into_chunks(df, "solar_global") |
| | for (i in seq_along(chunks)) { |
| | chunk <- chunks[[i]] |
| | if (nrow(chunk) < 1) next |
| |
|
| | p1 <- p1 %>% plotly::add_trace( |
| | data = chunk, |
| | x = ~datetime, |
| | y = ~solar_global, |
| | name = "Global Radiation", |
| | type = "scatter", |
| | mode = "lines", |
| | fill = "tozeroy", |
| | line = list(color = "rgba(255, 179, 0, 0.9)", width = 1), |
| | fillcolor = "rgba(255, 179, 0, 0.3)", |
| | hovertemplate = "Solar: %{y:,.0f} J/cm²<extra></extra>", |
| | connectgaps = FALSE, |
| | showlegend = (i == 1), |
| | legendgroup = "solar_hourly" |
| | ) |
| | } |
| | p1 <- p1 %>% plotly::layout(yaxis = list(title = "Radiation (J/cm²)")) |
| | } else { |
| | p1 <- create_empty_plot("No Radiation Data") %>% |
| | plotly::layout(yaxis = list(title = "Radiation")) |
| | } |
| |
|
| | |
| | if (has_sun) { |
| | |
| | p2 <- plotly::plot_ly( |
| | data = df, |
| | x = ~datetime, |
| | y = ~sunshine_duration, |
| | name = "Sunshine Duration", |
| | type = "bar", |
| | marker = list(color = "rgba(0, 0, 0, 0.3)", line = list(color = "#FFD700", width = 1.5)), |
| | hovertemplate = "Sunshine: %{y:.0f} min<extra></extra>" |
| | ) %>% |
| | plotly::layout(yaxis = list(title = "Sunshine (min)", range = c(0, 65))) |
| | } else { |
| | p2 <- create_empty_plot("No Sunshine Data") %>% |
| | plotly::layout(yaxis = list(title = "Sunshine")) |
| | } |
| |
|
| | |
| | plotly::subplot(p1, p2, nrows = 2, shareX = TRUE, titleY = TRUE, margin = 0.04) %>% |
| | plotly::layout( |
| | title = list(text = paste("Hourly Solar & Sunshine:", date_range_str), font = list(size = 14)), |
| | xaxis = list(title = "", type = "date", range = date_range_vals), |
| | hovermode = "x unified", |
| | hoverlabel = list(), |
| | legend = list(orientation = "h", x = 0.5, xanchor = "center", y = -0.1), |
| | margin = list(t = 60, b = 60, l = 60, r = 20), |
| | modebar = list(orientation = "h") |
| | ) %>% |
| | plotly::config(displaylogo = FALSE) |
| | } |
| |
|
| | |
| | |
| | |
| | create_solar_plot_daily <- function(df, resolution = NULL) { |
| | |
| |
|
| | has_global <- "solar_global" %in% names(df) && any(!is.na(df$solar_global)) |
| | has_sun <- "sunshine_duration" %in% names(df) && any(!is.na(df$sunshine_duration)) |
| |
|
| | |
| | df_daily <- df %>% |
| | dplyr::mutate(date = as.Date(datetime)) %>% |
| | dplyr::group_by(date) %>% |
| | dplyr::summarise(.groups = "drop") |
| |
|
| | if (has_global) { |
| | solar_agg <- df %>% |
| | dplyr::mutate(date = as.Date(datetime)) %>% |
| | dplyr::group_by(date) %>% |
| | dplyr::summarise( |
| | daily_solar = sum(solar_global, na.rm = TRUE), |
| | .groups = "drop" |
| | ) |
| | df_daily <- dplyr::left_join(df_daily, solar_agg, by = "date") |
| | } |
| |
|
| | if (has_sun) { |
| | sun_agg <- df %>% |
| | dplyr::mutate(date = as.Date(datetime)) %>% |
| | dplyr::group_by(date) %>% |
| | dplyr::summarise( |
| | daily_sun_hours = sum(sunshine_duration, na.rm = TRUE), |
| | .groups = "drop" |
| | ) |
| | df_daily <- dplyr::left_join(df_daily, sun_agg, by = "date") |
| | } |
| |
|
| | |
| | |
| | |
| | df_daily <- df_daily %>% dplyr::mutate(datetime = as.POSIXct(date)) |
| | df_daily <- ensure_temporal_continuity(df_daily, resolution = "daily") |
| | df_daily$date <- as.Date(df_daily$datetime) |
| |
|
| | date_range_str <- paste( |
| | format(min(df_daily$date), "%d %b %Y"), "-", |
| | format(max(df_daily$date), "%d %b %Y") |
| | ) |
| | date_range_vals <- c(min(df_daily$date), max(df_daily$date)) |
| |
|
| | |
| | if (has_global) { |
| | p1 <- plotly::plot_ly() |
| | chunks <- split_into_chunks(df_daily, "daily_solar") |
| | for (i in seq_along(chunks)) { |
| | chunk <- chunks[[i]] |
| | if (nrow(chunk) < 1) next |
| |
|
| | p1 <- p1 %>% plotly::add_trace( |
| | data = chunk, |
| | x = ~date, |
| | y = ~daily_solar, |
| | name = "Daily Global Radiation", |
| | type = "scatter", |
| | mode = "lines", |
| | fill = "tozeroy", |
| | line = list(color = "rgba(255, 179, 0, 0.9)", width = 1), |
| | fillcolor = "rgba(255, 179, 0, 0.3)", |
| | hovertemplate = "Daily Rad: %{y:,.0f} J/cm²<extra></extra>", |
| | connectgaps = FALSE, |
| | showlegend = (i == 1), |
| | legendgroup = "daily_solar" |
| | ) |
| | } |
| | p1 <- p1 %>% plotly::layout(yaxis = list(title = "Radiation (J/cm²)")) |
| | } else { |
| | p1 <- create_empty_plot("No Radiation Data") %>% |
| | plotly::layout(yaxis = list(title = "Radiation")) |
| | } |
| |
|
| | |
| | if (has_sun) { |
| | p2 <- plotly::plot_ly( |
| | data = df_daily, |
| | x = ~date, |
| | y = ~daily_sun_hours, |
| | name = "Daily Sunshine", |
| | type = "bar", |
| | marker = list(color = "rgba(0, 0, 0, 0.3)", line = list(color = "#FFD700", width = 1.5)), |
| | hovertemplate = "Sunshine: %{y:.1f} h<extra></extra>" |
| | ) %>% |
| | plotly::layout(yaxis = list(title = "Sunshine (Hours)", range = if (any(df_daily$daily_sun_hours > 24, na.rm = TRUE)) NULL else c(0, 24))) |
| | } else { |
| | p2 <- create_empty_plot("No Sunshine Data") %>% |
| | plotly::layout(yaxis = list(title = "Sunshine")) |
| | } |
| |
|
| | |
| | plotly::subplot(p1, p2, nrows = 2, shareX = TRUE, titleY = TRUE, margin = 0.04) %>% |
| | plotly::layout( |
| | title = list(text = paste("Daily Solar Energy & Sunshine:", date_range_str), font = list(size = 14)), |
| | xaxis = list(title = "", type = "date", range = date_range_vals), |
| | hovermode = "x unified", |
| | hoverlabel = list(), |
| | legend = list(orientation = "h", x = 0.5, xanchor = "center", y = -0.1), |
| | margin = list(t = 60, b = 60, l = 60, r = 20), |
| | modebar = list(orientation = "h") |
| | ) %>% |
| | plotly::config(displaylogo = FALSE) |
| | } |
| |
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | create_solar_plot <- function(df, resolution = NULL) { |
| | if (is.null(df) || nrow(df) == 0) { |
| | return(create_empty_plot("No data available for the selected period")) |
| | } |
| |
|
| | |
| | df <- clean_dwd_data(df) |
| |
|
| | has_global <- "solar_global" %in% names(df) && any(!is.na(df$solar_global)) |
| | has_sun <- "sunshine_duration" %in% names(df) && any(!is.na(df$sunshine_duration)) |
| |
|
| | if (!has_global && !has_sun) { |
| | return(create_empty_plot("No solar/sunshine data in selected time range")) |
| | } |
| |
|
| | |
| | is_daily <- FALSE |
| | if (!is.null(resolution)) { |
| | if (tolower(resolution) == "daily") is_daily <- TRUE |
| | } else { |
| | |
| | if (nrow(df) > 1) { |
| | dt <- as.numeric(difftime(df$datetime[2], df$datetime[1], units = "hours")) |
| | if (!is.na(dt) && dt >= 23) { |
| | is_daily <- TRUE |
| | } |
| | } |
| | } |
| |
|
| | if (is_daily) { |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | create_solar_plot_daily(df, resolution = resolution) |
| | } else { |
| | create_solar_plot_hourly(df, resolution = resolution) |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | create_snow_plot <- function(df, resolution = NULL) { |
| | if (is.null(df) || nrow(df) == 0) { |
| | return(create_empty_plot("No data available for the selected period")) |
| | } |
| |
|
| | |
| | df <- clean_dwd_data(df) |
| |
|
| | has_depth <- "snow_depth" %in% names(df) && any(!is.na(df$snow_depth)) |
| | has_swe <- "snow_water_equiv" %in% names(df) && any(!is.na(df$snow_water_equiv)) |
| | has_fresh_sum <- "snow_fresh_sum" %in% names(df) && any(!is.na(df$snow_fresh_sum)) |
| | has_depth_sum <- "snow_depth_sum" %in% names(df) && any(!is.na(df$snow_depth_sum)) |
| |
|
| | if (!has_depth && !has_swe && !has_fresh_sum && !has_depth_sum) { |
| | return(create_empty_plot("No Snow data in selected time range")) |
| | } |
| |
|
| | df <- ensure_temporal_continuity(df, resolution = resolution) |
| |
|
| | date_range_str <- paste( |
| | format(min(df$datetime), "%d %b %Y"), "-", |
| | format(max(df$datetime), "%d %b %Y") |
| | ) |
| |
|
| | p <- plotly::plot_ly() |
| |
|
| | |
| | if (has_fresh_sum || has_depth_sum) { |
| | |
| | if (has_depth_sum) { |
| | p <- p %>% plotly::add_bars( |
| | data = df %>% dplyr::filter(!is.na(snow_depth_sum)), |
| | x = ~datetime, |
| | y = ~snow_depth_sum, |
| | name = "Snow Depth Sum", |
| | marker = list(color = "#1565c0"), |
| | hovertemplate = "Depth Sum: %{y:.1f} cm<extra></extra>" |
| | ) |
| | } |
| |
|
| | |
| |
|
| | layout_args <- list( |
| | title = list(text = paste("Monthly Snow Summary:", date_range_str), font = list(size = 14)), |
| | xaxis = list(title = list(text = ""), type = "date"), |
| | yaxis = list(title = "Snow Depth Sum (cm)", min = 0), |
| | hovermode = "x unified", |
| | legend = list(orientation = "h", x = 0.5, xanchor = "center", y = -0.1), |
| | margin = list(t = 50, b = 80, l = 60, r = 20) |
| | ) |
| |
|
| | p <- do.call(plotly::layout, c(list(p), layout_args)) |
| |
|
| | return(p %>% plotly::config(displaylogo = FALSE)) |
| | } |
| |
|
| | |
| | |
| | if (has_depth) { |
| | p <- p %>% plotly::add_trace( |
| | data = df, |
| | x = ~datetime, |
| | y = ~snow_depth, |
| | name = "Snow Depth", |
| | type = "scatter", |
| | mode = "lines", |
| | fill = "tozeroy", |
| | line = list(color = "rgba(144, 202, 249, 1)", width = 1), |
| | fillcolor = "rgba(144, 202, 249, 0.4)", |
| | hovertemplate = "Depth: %{y:.1f} cm<extra></extra>", |
| | connectgaps = FALSE |
| | ) |
| | } |
| |
|
| | |
| | if (has_swe) { |
| | p <- p %>% plotly::add_lines( |
| | data = df, |
| | x = ~datetime, |
| | y = ~snow_water_equiv, |
| | name = "Water Equiv.", |
| | line = list(color = "#1565c0", width = 2), |
| | hovertemplate = "SWE: %{y:.1f} mm<extra></extra>", |
| | connectgaps = FALSE |
| | ) |
| | } |
| |
|
| | p %>% |
| | plotly::layout( |
| | title = list(text = paste("Snow Depth:", date_range_str), font = list(size = 14)), |
| | xaxis = list(title = "", type = "date"), |
| | yaxis = list(title = "Snow Depth (cm) / SWE (mm)", min = 0), |
| | hovermode = "x unified", |
| | hoverlabel = list(), |
| | legend = list(orientation = "h", x = 0.5, xanchor = "center", y = -0.1), |
| | margin = list(t = 50, b = 80, l = 60, r = 20), |
| | modebar = list(orientation = "h") |
| | ) %>% |
| | plotly::config(displaylogo = FALSE) |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | create_soil_temp_plot <- function(df, resolution = NULL) { |
| | if (is.null(df) || nrow(df) == 0) { |
| | return(create_empty_plot("No data available for the selected period")) |
| | } |
| |
|
| | |
| | df <- clean_dwd_data(df) |
| |
|
| | df <- ensure_temporal_continuity(df, resolution = resolution) |
| |
|
| | date_range_str <- paste( |
| | format(min(df$datetime), "%d %b %Y"), "-", |
| | format(max(df$datetime), "%d %b %Y") |
| | ) |
| |
|
| | |
| | soil_cols <- list( |
| | soil_temp_2cm = list(name = "2 cm", color = "#d32f2f"), |
| | soil_temp_5cm = list(name = "5 cm", color = "#f57c00"), |
| | soil_temp_10cm = list(name = "10 cm", color = "#fbc02d"), |
| | soil_temp_20cm = list(name = "20 cm", color = "#388e3c"), |
| | soil_temp_50cm = list(name = "50 cm", color = "#1976d2"), |
| | soil_temp_100cm = list(name = "100 cm", color = "#6a1b9a") |
| | ) |
| |
|
| | |
| | available_cols <- names(soil_cols)[sapply(names(soil_cols), function(col) { |
| | col %in% names(df) && any(!is.na(df[[col]])) |
| | })] |
| |
|
| | if (length(available_cols) == 0) { |
| | return(create_empty_plot("No soil temperature data available for this station")) |
| | } |
| |
|
| | p <- plotly::plot_ly(type = "scatter", mode = "none") |
| |
|
| | for (col in available_cols) { |
| | col_info <- soil_cols[[col]] |
| | |
| | p <- p %>% |
| | plotly::add_trace( |
| | data = df, |
| | x = ~datetime, |
| | y = as.formula(paste0("~", col)), |
| | name = col_info$name, |
| | type = "scatter", |
| | mode = "lines", |
| | line = list(color = col_info$color, width = 1.5), |
| | hovertemplate = paste0(col_info$name, ": %{y:.1f}°C<extra></extra>"), |
| | connectgaps = FALSE, |
| | showlegend = TRUE |
| | ) |
| | } |
| |
|
| | |
| | date_range_vals <- c(min(df$datetime), max(df$datetime)) |
| | p <- p %>% |
| | plotly::add_lines( |
| | x = date_range_vals, |
| | y = c(0, 0), |
| | name = "Freezing", |
| | line = list(color = "rgba(100, 100, 100, 0.5)", width = 1, dash = "dash"), |
| | showlegend = TRUE, |
| | hoverinfo = "skip", |
| | inherit = FALSE, |
| | type = "scatter", mode = "lines" |
| | ) |
| |
|
| | p %>% |
| | plotly::layout( |
| | title = list(text = paste("Soil Temperature Profile:", date_range_str), font = list(size = 14)), |
| | xaxis = list(title = "", type = "date"), |
| | yaxis = list(title = "Soil Temperature (°C)"), |
| | hovermode = "x unified", |
| | hoverlabel = list(), |
| | legend = list(orientation = "h", x = 0.5, xanchor = "center", y = -0.1), |
| | margin = list(t = 50, b = 80, l = 60, r = 20), |
| | modebar = list(orientation = "h") |
| | ) %>% |
| | plotly::config(displaylogo = FALSE) |
| | } |
| |
|