| | |
| | library(dplyr) |
| | library(plotly) |
| | library(lubridate) |
| |
|
| | |
| | |
| | |
| | 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) |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | get_axis_label <- function(df, col_name, default_label = NULL) { |
| | units <- attr(df, "units") |
| |
|
| | |
| | if (is.null(default_label)) { |
| | base_label <- col_name %>% |
| | gsub("_", " ", .) %>% |
| | stringr::str_to_title() |
| | } else { |
| | base_label <- default_label |
| | } |
| |
|
| | |
| | if (!is.null(units) && !is.null(units[[col_name]])) { |
| | return(paste0(base_label, " [", units[[col_name]], "]")) |
| | } |
| |
|
| | return(base_label) |
| | } |
| |
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | 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_weather_data <- function(df) { |
| | if (is.null(df) || nrow(df) == 0) { |
| | return(df) |
| | } |
| |
|
| | |
| | weather_cols <- c("temp", "temp_min", "temp_max", "temp_min_abs", "temp_max_abs", "rh", "precip", "wind_speed", "wind_dir", "pressure", "station_pressure", "cloud_cover", "wind_gust_max", "solar_global", "sunshine_duration") |
| |
|
| | |
| | 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 ("temp_min" %in% names(df)) df$temp_min[df$temp_min < -90 | df$temp_min > 60] <- NA_real_ |
| | if ("temp_max" %in% names(df)) df$temp_max[df$temp_max < -90 | df$temp_max > 60] <- NA_real_ |
| | if ("temp_min_abs" %in% names(df)) df$temp_min_abs[df$temp_min_abs < -90 | df$temp_min_abs > 60] <- NA_real_ |
| | if ("temp_max_abs" %in% names(df)) df$temp_max_abs[df$temp_max_abs < -90 | df$temp_max_abs > 60] <- NA_real_ |
| | if ("temp_min_ground" %in% names(df)) df$temp_min_ground[df$temp_min_ground < -90 | df$temp_min_ground > 70] <- NA_real_ |
| | if ("temp_min_50cm" %in% names(df)) df$temp_min_50cm[df$temp_min_50cm < -90 | df$temp_min_50cm > 60] <- NA_real_ |
| |
|
| | if ("rh" %in% names(df)) df$rh[df$rh < 0 | df$rh > 100] <- NA_real_ |
| | if ("rh_min" %in% names(df)) df$rh_min[df$rh_min < 0 | df$rh_min > 100] <- NA_real_ |
| | if ("rh_max" %in% names(df)) df$rh_max[df$rh_max < 0 | df$rh_max > 100] <- NA_real_ |
| |
|
| | if ("precip" %in% names(df)) df$precip[df$precip < 0 | df$precip > 500] <- NA_real_ |
| | if ("precip_duration" %in% names(df)) df$precip_duration[df$precip_duration < 0 | df$precip_duration > 1440] <- 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 ("cloud_cover" %in% names(df)) df$cloud_cover[df$cloud_cover < 0 | df$cloud_cover > 9] <- NA_real_ |
| | if ("snow_depth" %in% names(df)) df$snow_depth[df$snow_depth < 0 | df$snow_depth > 1000] <- NA_real_ |
| | if ("snow_fresh" %in% names(df)) df$snow_fresh[df$snow_fresh < 0 | df$snow_fresh > 500] <- NA_real_ |
| | if ("etp" %in% names(df)) df$etp[df$etp < 0 | df$etp > 600] <- 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_weather_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) { |
| | if (is.null(df) || nrow(df) == 0) { |
| | return(create_empty_plot("No data available for the selected period")) |
| | } |
| |
|
| | |
| | df <- clean_weather_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_ |
| | } |
| | } |
| |
|
| | 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 %>% dplyr::filter(!is.na(temp)), |
| | x = ~datetime, |
| | y = ~temp, name = "Temperature", |
| | line = list(color = "#e53935", width = 2), |
| | hovertemplate = "Temp: %{y:.1f}°C<extra></extra>", |
| | type = "scatter", mode = "lines", |
| | showlegend = TRUE |
| | ) |
| | } |
| | if (has_dew) { |
| | p1 <- p1 %>% plotly::add_lines( |
| | data = df %>% dplyr::filter(!is.na(dew_point)), |
| | 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", |
| | 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) { |
| | p2 <- p2 %>% plotly::add_lines( |
| | data = df %>% dplyr::filter(!is.na(wind_speed)), |
| | x = ~datetime, |
| | y = ~wind_speed, name = "Wind Speed", fill = "tozeroy", |
| | line = list(color = "#43a047", width = 1), |
| | hovertemplate = "Wind: %{y:.1f} m/s<extra></extra>", |
| | type = "scatter", mode = "lines", |
| | showlegend = TRUE |
| | ) |
| | } |
| | 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 %>% dplyr::filter(!is.na(pressure)), |
| | 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", |
| | showlegend = TRUE |
| | ) |
| | } |
| | if (has_station_p) { |
| | p3 <- p3 %>% |
| | plotly::add_lines( |
| | data = df %>% dplyr::filter(!is.na(station_pressure)), |
| | 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", |
| | 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)) |
| | has_rh <- "rh" %in% names(df) && any(!is.na(df$rh)) |
| |
|
| | if (has_cloud || has_rh) { |
| | p4 <- plotly::plot_ly(type = "scatter", mode = "none") |
| | if (has_cloud) { |
| | |
| | p4 <- p4 %>% plotly::add_trace( |
| | data = df %>% dplyr::filter(!is.na(cloud_cover)), |
| | x = ~datetime, |
| | y = ~cloud_cover, name = "Cloud Cover", |
| | marker = list(color = "#757575", size = 4, symbol = "circle"), |
| | hovertemplate = "Cloud: %{y} /8 octas<extra></extra>", |
| | type = "scatter", mode = "markers" |
| | ) |
| | } |
| | if (has_rh) { |
| | p4 <- p4 %>% plotly::add_lines( |
| | data = df %>% dplyr::filter(!is.na(rh)), |
| | x = ~datetime, |
| | y = ~rh, name = "Humidity", |
| | line = list(color = "#00bcd4", width = 1.5, dash = "dot"), |
| | hovertemplate = "Humidity: %{y:.1f}%<extra></extra>", |
| | yaxis = "y2" |
| | ) |
| | } |
| | p4 <- p4 %>% plotly::layout( |
| | yaxis = list(title = "Cloud (octas)", range = c(0, 8.5)), |
| | yaxis2 = list(title = "Humidity (%)", overlaying = "y", side = "right", range = c(0, 105), showgrid = FALSE) |
| | ) |
| | } 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/Humidity 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.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 = "hourly") { |
| | if (is.null(df) || nrow(df) == 0) { |
| | return(create_empty_plot("No data available for the selected period")) |
| | } |
| |
|
| | |
| | df <- clean_weather_data(df) |
| |
|
| | date_range_str <- paste( |
| | format(min(df$datetime), "%d %b %Y"), "-", |
| | format(max(df$datetime), "%d %b %Y") |
| | ) |
| |
|
| | |
| | label_suffix <- if (resolution == "daily") "Daily" else "1h" |
| | precip_cols <- c( |
| | "precip" = paste0("Precip (", label_suffix, ")"), |
| | "snow_fresh" = "Fresh Snow (mm)", |
| | "snow_depth" = "Snow Depth (cm)" |
| | ) |
| |
|
| | 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[!is.na(df[[col_name]]), ] |
| |
|
| | 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_weather_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, 4, 6, 8, 10, 12, 14, 16, Inf), |
| | labels = c("0-2", "2-4", "4-6", "6-8", "8-10", "10-12", "12-14", "14-16", "16+") |
| | ) |
| | ) %>% |
| | 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("#a5d6a7", "#66bb6a", "#26a69a", "#0288d1", "#ffeb3b", "#ffc107", "#ff9800", "#f44336", "#b71c1c") |
| |
|
| | 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, |
| | tickfont = list(size = 12) |
| | ), |
| | radialaxis = list( |
| | ticksuffix = "%", |
| | tickfont = list(size = 10) |
| | ) |
| | ), |
| | showlegend = TRUE, |
| | hoverlabel = list(), |
| | legend = list( |
| | title = list(text = "Wind Speed (m/s)", side = "top"), |
| | orientation = "h", |
| | x = 0.5, |
| | xanchor = "center", |
| | y = -0.2 |
| | ), |
| | margin = list(t = 50, b = 60, l = 20, r = 20), |
| | modebar = list(orientation = "h"), |
| | annotations = list( |
| | list( |
| | x = 0, y = 1.15, 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", |
| | |
| | name = "info_annotation" |
| | ) |
| | ) |
| | ) %>% |
| | 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_weather_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_plot <- function(df) { |
| | if (is.null(df) || nrow(df) == 0) { |
| | return(create_empty_plot("No data available for the selected period")) |
| | } |
| |
|
| | |
| | df <- clean_weather_data(df) |
| |
|
| | |
| | is_hourly <- TRUE |
| | if (nrow(df) > 1) { |
| | |
| | head_df <- df[1:min(10, nrow(df)), ] |
| | dt <- mean(as.numeric(difftime(head_df$datetime[2:nrow(head_df)], head_df$datetime[1:(nrow(head_df) - 1)], units = "hours")), na.rm = TRUE) |
| | if (!is.na(dt) && dt > 20) is_hourly <- FALSE |
| | } else { |
| | |
| | |
| | |
| | |
| | } |
| |
|
| | date_range_str <- paste( |
| | format(min(df$datetime), "%d %b %Y"), "-", |
| | format(max(df$datetime), "%d %b %Y") |
| | ) |
| |
|
| | |
| | plot_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)) |
| | has_etp <- "etp" %in% names(df) && any(!is.na(df$etp)) |
| |
|
| | if (!has_global && !has_sun && !has_etp) { |
| | return(create_empty_plot("No solar/sun/etp data in selected time range")) |
| | } |
| |
|
| | |
| | if (has_global) { |
| | p1 <- plotly::plot_ly( |
| | data = plot_data, |
| | x = ~datetime, |
| | y = ~solar_global, |
| | name = if (is_hourly) "Global Radiation" else "Daily 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>" |
| | ) %>% |
| | plotly::layout(yaxis = list(title = "Rad (J/cm²)")) |
| | } else { |
| | p1 <- create_empty_plot("No Radiation Data") %>% |
| | plotly::layout(yaxis = list(title = "Rad")) |
| | } |
| |
|
| | |
| | if (has_sun) { |
| | if (!is_hourly) { |
| | |
| | plot_data <- plot_data %>% mutate(sun_display = sunshine_duration / 60) |
| | y_title <- "Sun (h)" |
| | hv_temp <- "Sunshine: %{y:.1f} h<extra></extra>" |
| | y_range <- NULL |
| | } else { |
| | plot_data <- plot_data %>% mutate(sun_display = sunshine_duration) |
| | y_title <- "Sun (min)" |
| | hv_temp <- "Sunshine: %{y:.0f} min<extra></extra>" |
| | y_range <- c(0, 65) |
| | } |
| |
|
| | p2 <- plotly::plot_ly( |
| | data = plot_data, |
| | x = ~datetime, |
| | y = ~sun_display, |
| | name = if (is_hourly) "Sunshine" else "Daily Sunshine", |
| | type = "bar", |
| | marker = list(color = "rgba(0, 0, 0, 0.3)", line = list(color = "#FFD700", width = 1.5)), |
| | hovertemplate = hv_temp |
| | ) %>% |
| | plotly::layout(yaxis = list(title = y_title, range = y_range)) |
| | } else { |
| | p2 <- create_empty_plot("No Sunshine Data") %>% |
| | plotly::layout(yaxis = list(title = "Sun")) |
| | } |
| |
|
| | |
| | p3 <- NULL |
| | if (has_etp) { |
| | p3 <- plotly::plot_ly( |
| | data = plot_data, |
| | x = ~datetime, |
| | y = ~etp, |
| | name = "ETP", |
| | type = "scatter", |
| | mode = "lines", |
| | line = list(color = "#795548", width = 1.5, dash = "dash"), |
| | hovertemplate = "ETP: %{y:.1f} mm<extra></extra>" |
| | ) %>% |
| | plotly::layout(yaxis = list(title = "ETP (mm)")) |
| | } |
| |
|
| | |
| | plot_list <- list(p1, p2) |
| | rows_cnt <- 2 |
| | if (!is.null(p3)) { |
| | plot_list[[3]] <- p3 |
| | rows_cnt <- 3 |
| | } |
| |
|
| | plotly::subplot(plot_list, nrows = rows_cnt, shareX = TRUE, titleY = TRUE, margin = 0.04) %>% |
| | plotly::layout( |
| | title = list(text = paste("Solar Radiation & Sunshine:", date_range_str), font = list(size = 14)), |
| | xaxis = list(title = "", type = "date"), |
| | hovermode = "x unified", |
| | hoverlabel = list(), |
| | legend = list(orientation = "h", x = 0.5, xanchor = "center", y = -0.25), |
| | margin = list(t = 60, b = 60, l = 60, r = 20), |
| | modebar = list(orientation = "h") |
| | ) %>% |
| | plotly::config(displaylogo = FALSE) |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | create_temperature_single_plot <- function(df) { |
| | if (is.null(df) || nrow(df) == 0) { |
| | return(create_empty_plot("No data available for Temperature")) |
| | } |
| |
|
| | df <- clean_weather_data(df) |
| |
|
| | 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_abs <- "temp_min_abs" %in% names(df) && any(!is.na(df$temp_min_abs)) |
| | has_tmax_abs <- "temp_max_abs" %in% names(df) && any(!is.na(df$temp_max_abs)) |
| |
|
| | 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_abs && has_tmax_abs) { |
| | |
| | |
| | p <- p %>% plotly::add_trace( |
| | data = df %>% dplyr::filter(!is.na(temp_min_abs)), |
| | x = ~datetime, |
| | y = ~temp_min_abs, |
| | 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_abs)), |
| | x = ~datetime, |
| | y = ~temp_max_abs, |
| | 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 |
| | ) |
| | } |
| |
|
| | |
| | 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 = "Min/Max Range", |
| | 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 = "Min/Max 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_tmin) { |
| | p <- p %>% plotly::add_lines( |
| | data = df %>% dplyr::filter(!is.na(temp_min)), |
| | x = ~datetime, |
| | y = ~temp_min, name = "Mean Min", |
| | line = list(color = "#1976d2", width = 2), |
| | hovertemplate = "Mean Min: %{y:.1f}°C<extra></extra>", |
| | showlegend = TRUE |
| | ) |
| | } |
| | if (has_tmax) { |
| | p <- p %>% plotly::add_lines( |
| | data = df %>% dplyr::filter(!is.na(temp_max)), |
| | x = ~datetime, |
| | y = ~temp_max, name = "Mean Max", |
| | line = list(color = "#d32f2f", width = 2), |
| | hovertemplate = "Mean Max: %{y:.1f}°C<extra></extra>", |
| | showlegend = TRUE |
| | ) |
| | } |
| | } |
| |
|
| | |
| | if (has_temp) { |
| | p <- p %>% plotly::add_lines( |
| | data = df %>% dplyr::filter(!is.na(temp)), |
| | x = ~datetime, |
| | y = ~temp, name = if (has_tmin || has_tmax) "Tmean" else "Temperature", |
| | line = list( |
| | color = "#b71c1c", |
| | width = 2, |
| | dash = if (has_tmin || has_tmax) "solid" else "solid" |
| | ), |
| | hovertemplate = paste0(if (has_tmin || has_tmax) "Tmean" else "Temp", ": %{y:.1f}°C<extra></extra>"), |
| | type = "scatter", mode = "lines", |
| | 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 = get_axis_label(df, "temp", "Temperature")), |
| | hovermode = "x unified", |
| | hoverlabel = list(), |
| | legend = list(orientation = "h", x = 0.5, xanchor = "center", y = -0.25), |
| | margin = list(t = 50, b = 80, l = 60, r = 20), |
| | modebar = list(orientation = "h") |
| | ) %>% |
| | plotly::config(displaylogo = FALSE) |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | create_humidity_single_plot <- function(df) { |
| | if (is.null(df) || nrow(df) == 0) { |
| | return(create_empty_plot("No data available for Humidity")) |
| | } |
| |
|
| | |
| | df <- clean_weather_data(df) |
| |
|
| | |
| | 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_ |
| | } |
| | } |
| |
|
| | 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 %>% dplyr::filter(!is.na(dew_point)), |
| | 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", |
| | showlegend = TRUE |
| | ) |
| | } |
| |
|
| | if (has_rh) { |
| | p <- p %>% plotly::add_lines( |
| | data = df %>% dplyr::filter(!is.na(rh)), |
| | 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", |
| | 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 = get_axis_label(df, "dew_point", "Dew Point")), |
| | hovermode = "x unified", |
| | hoverlabel = list(), |
| | legend = list(orientation = "h", x = 0.5, xanchor = "center", y = -0.25), |
| | margin = list(t = 50, b = 80, l = 60, r = 60), |
| | modebar = list(orientation = "h") |
| | ) |
| |
|
| | if (has_rh) { |
| | layout_args$yaxis2 <- list( |
| | title = get_axis_label(df, "rh", "Relative Humidity"), |
| | overlaying = "y", |
| | side = "right", |
| | range = c(0, 100) |
| | ) |
| | } |
| |
|
| | do.call(plotly::layout, c(list(p), layout_args)) %>% |
| | plotly::config(displaylogo = FALSE) |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | create_solar_radiation_plot <- function(df) { |
| | if (is.null(df) || nrow(df) == 0) { |
| | return(create_empty_plot("No data available")) |
| | } |
| |
|
| | df <- clean_weather_data(df) |
| |
|
| | has_global <- "solar_global" %in% names(df) && any(!is.na(df$solar_global)) |
| | if (!has_global) { |
| | return(create_empty_plot("No solar radiation data available")) |
| | } |
| |
|
| | |
| | is_hourly <- TRUE |
| | if (nrow(df) > 1) { |
| | head_df <- df[1:min(10, nrow(df)), ] |
| | dt <- mean(as.numeric(difftime(head_df$datetime[2:nrow(head_df)], head_df$datetime[1:(nrow(head_df) - 1)], units = "hours")), na.rm = TRUE) |
| | if (!is.na(dt) && dt > 20) is_hourly <- FALSE |
| | } |
| |
|
| | date_range_str <- paste( |
| | format(min(df$datetime), "%d %b %Y"), "-", |
| | format(max(df$datetime), "%d %b %Y") |
| | ) |
| |
|
| | plotly::plot_ly( |
| | data = df, |
| | x = ~datetime, |
| | y = ~solar_global, |
| | name = if (is_hourly) "Global Radiation" else "Daily 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>" |
| | ) %>% |
| | plotly::layout( |
| | title = list(text = paste("Solar Radiation:", date_range_str), font = list(size = 14)), |
| | xaxis = list(title = "", type = "date"), |
| | yaxis = list(title = get_axis_label(df, "solar_global", "Radiation")), |
| | hovermode = "x unified", |
| | hoverlabel = list(), |
| | legend = list(orientation = "h", x = 0.5, xanchor = "center", y = -0.25), |
| | margin = list(t = 50, b = 80, l = 60, r = 20), |
| | modebar = list(orientation = "h") |
| | ) %>% |
| | plotly::config(displaylogo = FALSE) |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | create_sunshine_plot <- function(df) { |
| | if (is.null(df) || nrow(df) == 0) { |
| | return(create_empty_plot("No data available")) |
| | } |
| |
|
| | df <- clean_weather_data(df) |
| |
|
| | has_sun <- "sunshine_duration" %in% names(df) && any(!is.na(df$sunshine_duration)) |
| | if (!has_sun) { |
| | return(create_empty_plot("No sunshine duration data available")) |
| | } |
| |
|
| | |
| | is_hourly <- TRUE |
| | if (nrow(df) > 1) { |
| | head_df <- df[1:min(10, nrow(df)), ] |
| | dt <- mean(as.numeric(difftime(head_df$datetime[2:nrow(head_df)], head_df$datetime[1:(nrow(head_df) - 1)], units = "hours")), na.rm = TRUE) |
| | if (!is.na(dt) && dt > 20) is_hourly <- FALSE |
| | } |
| |
|
| | date_range_str <- paste( |
| | format(min(df$datetime), "%d %b %Y"), "-", |
| | format(max(df$datetime), "%d %b %Y") |
| | ) |
| |
|
| | |
| | if (!is_hourly) { |
| | df <- df %>% mutate(sun_display = sunshine_duration / 60) |
| | y_title <- "Sunshine (h)" |
| | hv_temp <- "Sunshine: %{y:.1f} h<extra></extra>" |
| | y_range <- NULL |
| | } else { |
| | df <- df %>% mutate(sun_display = sunshine_duration) |
| | y_title <- "Sunshine (min)" |
| | hv_temp <- "Sunshine: %{y:.0f} min<extra></extra>" |
| | y_range <- c(0, 65) |
| | } |
| |
|
| | plotly::plot_ly( |
| | data = df, |
| | x = ~datetime, |
| | y = ~sun_display, |
| | name = if (is_hourly) "Sunshine" else "Daily Sunshine", |
| | type = "bar", |
| | marker = list(color = "rgba(0, 0, 0, 0.3)", line = list(color = "#FFD700", width = 1.5)), |
| | hovertemplate = hv_temp |
| | ) %>% |
| | plotly::layout( |
| | title = list(text = paste("Sunshine Duration:", date_range_str), font = list(size = 14)), |
| | xaxis = list(title = "", type = "date"), |
| | yaxis = list(title = y_title, range = y_range), |
| | hovermode = "x unified", |
| | hoverlabel = list(), |
| | legend = list(orientation = "h", x = 0.5, xanchor = "center", y = -0.25), |
| | margin = list(t = 50, b = 80, l = 60, r = 20), |
| | modebar = list(orientation = "h") |
| | ) %>% |
| | plotly::config(displaylogo = FALSE) |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | create_etp_plot <- function(df) { |
| | if (is.null(df) || nrow(df) == 0) { |
| | return(create_empty_plot("No data available")) |
| | } |
| |
|
| | df <- clean_weather_data(df) |
| |
|
| | has_etp <- "etp" %in% names(df) && any(!is.na(df$etp)) |
| | if (!has_etp) { |
| | return(create_empty_plot("No ETP data available")) |
| | } |
| |
|
| | date_range_str <- paste( |
| | format(min(df$datetime), "%d %b %Y"), "-", |
| | format(max(df$datetime), "%d %b %Y") |
| | ) |
| |
|
| | plotly::plot_ly( |
| | data = df, |
| | x = ~datetime, |
| | y = ~etp, |
| | name = "ETP", |
| | type = "scatter", |
| | mode = "lines", |
| | line = list(color = "#795548", width = 1.5), |
| | hovertemplate = "ETP: %{y:.1f} mm<extra></extra>" |
| | ) %>% |
| | plotly::layout( |
| | title = list(text = paste("Evapotranspiration:", date_range_str), font = list(size = 14)), |
| | xaxis = list(title = "", type = "date"), |
| | yaxis = list(title = get_axis_label(df, "etp", "ETP")), |
| | hovermode = "x unified", |
| | hoverlabel = list(), |
| | legend = list(orientation = "h", x = 0.5, xanchor = "center", y = -0.25), |
| | margin = list(t = 50, b = 80, l = 60, r = 20), |
| | modebar = list(orientation = "h") |
| | ) %>% |
| | plotly::config(displaylogo = FALSE) |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | create_precipitation_only_plot <- function(df, resolution = "hourly") { |
| | if (is.null(df) || nrow(df) == 0) { |
| | return(create_empty_plot("No data available")) |
| | } |
| |
|
| | df <- clean_weather_data(df) |
| |
|
| | has_precip <- "precip" %in% names(df) && any(!is.na(df$precip)) |
| | if (!has_precip) { |
| | return(create_empty_plot("No precipitation data available")) |
| | } |
| |
|
| | date_range_str <- paste( |
| | format(min(df$datetime), "%d %b %Y"), "-", |
| | format(max(df$datetime), "%d %b %Y") |
| | ) |
| |
|
| | label_suffix <- if (resolution == "daily") "Daily" else "1h" |
| | axis_title <- get_axis_label(df, "precip", paste0("Precipitation (", label_suffix, ")")) |
| |
|
| | if (resolution != "daily") { |
| | alias_map <- attr(df, "preferred_alias_map") |
| | precip_source <- NA_character_ |
| | if (!is.null(alias_map) && length(alias_map) > 0) { |
| | precip_alias <- alias_map["precipitation_amount"] |
| | rain_alias <- alias_map["rainfall_amount"] |
| | if (!is.null(precip_alias) && length(precip_alias) == 1 && !is.na(precip_alias)) { |
| | precip_source <- precip_alias |
| | } else if (!is.null(rain_alias) && length(rain_alias) == 1 && !is.na(rain_alias)) { |
| | precip_source <- rain_alias |
| | } |
| | } |
| |
|
| | is_1h_accum <- is.character(precip_source) && |
| | length(precip_source) == 1 && |
| | !is.na(precip_source) && |
| | grepl("^precip_1h($|_)", precip_source, ignore.case = TRUE) |
| |
|
| | |
| | |
| | if (!is_1h_accum && "precip" %in% names(df)) { |
| | one_h_cols <- names(df)[grepl( |
| | "^(precip_1h($|_)|rainfall_amount(_lvl[^_]+)?_1h$)", |
| | names(df), |
| | ignore.case = TRUE |
| | )] |
| | if (length(one_h_cols) > 0) { |
| | p_vals <- suppressWarnings(as.numeric(df$precip)) |
| | for (col_name in one_h_cols) { |
| | c_vals <- suppressWarnings(as.numeric(df[[col_name]])) |
| | ok <- is.finite(p_vals) & is.finite(c_vals) |
| | if (sum(ok) >= 3) { |
| | max_diff <- suppressWarnings(max(abs(p_vals[ok] - c_vals[ok]), na.rm = TRUE)) |
| | if (is.finite(max_diff) && max_diff < 1e-8) { |
| | is_1h_accum <- TRUE |
| | break |
| | } |
| | } |
| | } |
| | } |
| | } |
| |
|
| | dt_mins <- NA_real_ |
| | if ("datetime" %in% names(df) && nrow(df) > 1) { |
| | dt_series <- df$datetime[order(df$datetime)] |
| | dt_vals <- as.numeric(difftime(dt_series[2:nrow(df)], dt_series[1:(nrow(df) - 1)], units = "mins")) |
| | dt_vals <- dt_vals[is.finite(dt_vals) & dt_vals > 0] |
| | if (length(dt_vals) > 0) { |
| | dt_mins <- suppressWarnings(stats::median(dt_vals, na.rm = TRUE)) |
| | } |
| | } |
| | updated_every_10m <- is.finite(dt_mins) && dt_mins >= 9 && dt_mins <= 11 |
| |
|
| | if (is_1h_accum && updated_every_10m) { |
| | axis_title <- "Precipitation (1h accumulation,<br>updated every 10 min)" |
| | } |
| | } |
| |
|
| | plotly::plot_ly( |
| | data = df, |
| | x = ~datetime, |
| | y = ~precip, |
| | name = paste0("Precip (", label_suffix, ")"), |
| | type = "bar", |
| | marker = list(color = "#0277bd"), |
| | hovertemplate = paste0("Precip: %{y:.1f} mm<extra></extra>") |
| | ) %>% |
| | plotly::layout( |
| | title = list(text = paste("Precipitation:", date_range_str), font = list(size = 14)), |
| | xaxis = list(title = "", type = "date"), |
| | yaxis = list(title = axis_title), |
| | hovermode = "x unified", |
| | hoverlabel = list(), |
| | legend = list(orientation = "h", x = 0.5, xanchor = "center", y = -0.25), |
| | margin = list(t = 50, b = 80, l = 60, r = 20), |
| | modebar = list(orientation = "h") |
| | ) %>% |
| | plotly::config(displaylogo = FALSE) |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | create_snow_plot <- function(df) { |
| | if (is.null(df) || nrow(df) == 0) { |
| | return(create_empty_plot("No data available")) |
| | } |
| |
|
| | df <- clean_weather_data(df) |
| |
|
| | has_fresh <- "snow_fresh" %in% names(df) && any(!is.na(df$snow_fresh)) |
| | has_depth <- "snow_depth" %in% names(df) && any(!is.na(df$snow_depth)) |
| |
|
| | if (!has_fresh && !has_depth) { |
| | return(create_empty_plot("No snow data available")) |
| | } |
| |
|
| | date_range_str <- paste( |
| | format(min(df$datetime), "%d %b %Y"), "-", |
| | format(max(df$datetime), "%d %b %Y") |
| | ) |
| |
|
| | |
| | time_range <- c(min(df$datetime), max(df$datetime)) |
| |
|
| | |
| | p <- plotly::plot_ly(x = ~datetime) |
| |
|
| | |
| | if (has_fresh) { |
| | p <- p %>% plotly::add_bars( |
| | data = df %>% filter(!is.na(snow_fresh)), |
| | y = ~snow_fresh, |
| | name = "Fresh Snow", |
| | marker = list(color = "#B0BEC5"), |
| | hovertemplate = "Fresh: %{y:.1f} cm<extra></extra>", |
| | yaxis = "y" |
| | ) |
| | } |
| |
|
| | |
| | if (has_depth) { |
| | p <- p %>% plotly::add_bars( |
| | data = df %>% filter(!is.na(snow_depth)), |
| | y = ~snow_depth, |
| | name = "Snow Depth", |
| | marker = list(color = "#90CAF9", opacity = 0.7), |
| | hovertemplate = "Depth: %{y:.1f} cm<extra></extra>", |
| | yaxis = "y2" |
| | ) |
| | } |
| |
|
| | |
| | layout_args <- list( |
| | title = list(text = paste("Snow:", date_range_str), font = list(size = 14)), |
| | xaxis = list(title = "", type = "date", range = time_range), |
| | yaxis = list( |
| | title = get_axis_label(df, "snow_fresh", "Fresh Snow"), |
| | titlefont = list(color = "#000000"), |
| | tickfont = list(color = "#000000") |
| | ), |
| | |
| | yaxis2 = list( |
| | title = get_axis_label(df, "snow_depth", "Snow Depth"), |
| | titlefont = list(color = "#000000"), |
| | tickfont = list(color = "#000000"), |
| | overlaying = "y", |
| | side = "right" |
| | ), |
| | hovermode = "x unified", |
| | margin = list(t = 50, b = 80, l = 60, r = 60), |
| | legend = list(orientation = "h", x = 0.5, xanchor = "center", y = -0.25), |
| | barmode = "overlay" |
| | ) |
| |
|
| | |
| | if (!has_fresh) { |
| | layout_args$yaxis$title <- "" |
| | layout_args$yaxis$showticklabels <- FALSE |
| | layout_args$yaxis$range <- c(0, 0.1) |
| | } |
| | if (!has_depth) { |
| | layout_args$yaxis2$title <- "" |
| | layout_args$yaxis2$showticklabels <- FALSE |
| | layout_args$yaxis2$overlaying <- NULL |
| | } |
| |
|
| | p <- do.call(plotly::layout, c(list(p), layout_args)) |
| |
|
| | p %>% plotly::config(displaylogo = FALSE) |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | create_pressure_plot <- function(df) { |
| | if (is.null(df) || nrow(df) == 0) { |
| | return(create_empty_plot("No data available")) |
| | } |
| |
|
| | df <- clean_weather_data(df) |
| |
|
| | |
| | has_sea <- "pressure_sea" %in% names(df) && any(!is.na(df$pressure_sea)) |
| | has_stat <- "pressure_station" %in% names(df) && any(!is.na(df$pressure_station)) |
| |
|
| | |
| | |
| | if (!has_sea && !has_stat && "pressure" %in% names(df) && any(!is.na(df$pressure))) { |
| | has_sea <- TRUE |
| | df$pressure_sea <- df$pressure |
| | } |
| |
|
| | if (!has_sea && !has_stat) { |
| | return(create_empty_plot("No pressure data available")) |
| | } |
| |
|
| | date_range_str <- paste( |
| | format(min(df$datetime), "%d %b %Y"), "-", |
| | format(max(df$datetime), "%d %b %Y") |
| | ) |
| |
|
| | p <- plotly::plot_ly(type = "scatter", mode = "lines") |
| |
|
| | if (has_sea) { |
| | p <- p %>% plotly::add_lines( |
| | data = df %>% filter(!is.na(pressure_sea)), |
| | x = ~datetime, |
| | y = ~pressure_sea, |
| | name = "Mean Sea Level Pressure", |
| | line = list(color = "#AB47BC", width = 1.5), |
| | hovertemplate = "MSL Pressure: %{y:.1f} hPa<extra></extra>" |
| | ) |
| | } |
| |
|
| | if (has_stat) { |
| | p <- p %>% plotly::add_lines( |
| | data = df %>% filter(!is.na(pressure_station)), |
| | x = ~datetime, |
| | y = ~pressure_station, |
| | name = "Station Pressure", |
| | line = list(color = "#7B1FA2", width = 1.5, dash = "dot"), |
| | hovertemplate = "Station Pressure: %{y:.1f} hPa<extra></extra>" |
| | ) |
| | } |
| |
|
| | p %>% |
| | plotly::layout( |
| | title = list(text = paste("Atmospheric Pressure:", date_range_str), font = list(size = 14)), |
| | xaxis = list(title = "", type = "date"), |
| | yaxis = list(title = get_axis_label(df, "pressure_sea", "Pressure")), |
| | hovermode = "x unified", |
| | hoverlabel = list(), |
| | legend = list(orientation = "h", x = 0.5, xanchor = "center", y = -0.25), |
| | margin = list(t = 50, b = 80, l = 60, r = 20), |
| | modebar = list(orientation = "h") |
| | ) %>% |
| | plotly::config(displaylogo = FALSE) |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | create_wind_time_series_plot <- function(df) { |
| | if (is.null(df) || nrow(df) == 0) { |
| | return(create_empty_plot("No data available")) |
| | } |
| |
|
| | df <- clean_weather_data(df) |
| |
|
| | has_speed <- "wind_speed" %in% names(df) && any(!is.na(df$wind_speed)) |
| | has_gust <- "wind_gust" %in% names(df) && any(!is.na(df$wind_gust)) |
| |
|
| | if (!has_speed && !has_gust) { |
| | return(create_empty_plot("No wind data available")) |
| | } |
| |
|
| | date_range_str <- paste( |
| | format(min(df$datetime), "%d %b %Y"), "-", |
| | format(max(df$datetime), "%d %b %Y") |
| | ) |
| |
|
| | p <- plotly::plot_ly(type = "scatter", mode = "lines") |
| |
|
| | if (has_speed) { |
| | p <- p %>% plotly::add_lines( |
| | data = df %>% filter(!is.na(wind_speed)), |
| | x = ~datetime, |
| | y = ~wind_speed, |
| | name = "Wind Speed (Avg)", |
| | line = list(color = "#43a047", width = 1), |
| | fill = "tozeroy", |
| | hovertemplate = "Wind: %{y:.1f} m/s<extra></extra>" |
| | ) |
| | } |
| |
|
| | if (has_gust) { |
| | p <- p %>% plotly::add_markers( |
| | data = df %>% filter(!is.na(wind_gust)), |
| | x = ~datetime, |
| | y = ~wind_gust, |
| | name = "Gust (Max)", |
| | marker = list(color = "#2e7d32", size = 4), |
| | hovertemplate = "Gust: %{y:.1f} m/s<extra></extra>" |
| | ) |
| | } |
| |
|
| | p %>% |
| | plotly::layout( |
| | title = list(text = paste("Wind Speed & Gusts:", date_range_str), font = list(size = 14)), |
| | xaxis = list(title = "", type = "date"), |
| | yaxis = list(title = get_axis_label(df, "wind_speed", "Speed")), |
| | hovermode = "x unified", |
| | hoverlabel = list(), |
| | legend = list(orientation = "h", x = 0.5, xanchor = "center", y = -0.25), |
| | margin = list(t = 50, b = 80, l = 60, r = 20), |
| | modebar = list(orientation = "h") |
| | ) %>% |
| | plotly::config(displaylogo = FALSE) |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | create_cloud_cover_plot <- function(df) { |
| | if (is.null(df) || nrow(df) == 0) { |
| | return(create_empty_plot("No data available")) |
| | } |
| |
|
| | df <- clean_weather_data(df) |
| |
|
| | has_cloud <- "cloud_cover" %in% names(df) && any(!is.na(df$cloud_cover)) |
| |
|
| | if (!has_cloud) { |
| | return(create_empty_plot("No cloud cover data available")) |
| | } |
| |
|
| | |
| | |
| | daily_cloud <- df %>% |
| | filter(!is.na(cloud_cover)) %>% |
| | mutate(date = as.Date(datetime)) %>% |
| | group_by(date) %>% |
| | summarise( |
| | mean_cloud = mean(cloud_cover, na.rm = TRUE), |
| | n_obs = n(), |
| | .groups = "drop" |
| | ) |
| |
|
| | if (nrow(daily_cloud) == 0) { |
| | return(create_empty_plot("No valid cloud cover data found")) |
| | } |
| |
|
| | date_range_str <- paste( |
| | format(min(daily_cloud$date), "%d %b %Y"), "-", |
| | format(max(daily_cloud$date), "%d %b %Y") |
| | ) |
| |
|
| | plotly::plot_ly( |
| | data = daily_cloud, |
| | x = ~date, |
| | y = ~mean_cloud, |
| | name = "Daily Mean Cloud Cover", |
| | type = "bar", |
| | marker = list(color = "#78909C", line = list(color = "#546E7A", width = 1)), |
| | hovertemplate = "Date: %{x}<br>Mean Cloud: %{y:.1f}/8<br>Obs: %{customdata} hours<extra></extra>", |
| | customdata = ~n_obs |
| | ) %>% |
| | plotly::layout( |
| | title = list(text = paste("Daily Mean Cloud Cover:", date_range_str), font = list(size = 14)), |
| | xaxis = list(title = "", type = "date"), |
| | yaxis = list(title = "Mean Cloud Cover (0-8)", range = c(0, 8.5), tickvals = 0:8), |
| | hovermode = "x unified", |
| | hoverlabel = list(), |
| | legend = list(orientation = "h", x = 0.5, xanchor = "center", y = -0.25), |
| | margin = list(t = 50, b = 80, l = 60, r = 20), |
| | modebar = list(orientation = "h") |
| | ) %>% |
| | plotly::config(displaylogo = FALSE) |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | create_visibility_plot <- function(df) { |
| | if (is.null(df) || nrow(df) == 0) { |
| | return(create_empty_plot("No data available")) |
| | } |
| |
|
| | df <- clean_weather_data(df) |
| |
|
| | has_vis <- "visibility" %in% names(df) && any(!is.na(df$visibility)) |
| |
|
| | if (!has_vis) { |
| | return(create_empty_plot("No visibility data available")) |
| | } |
| |
|
| | date_range_str <- paste( |
| | format(min(df$datetime), "%d %b %Y"), "-", |
| | format(max(df$datetime), "%d %b %Y") |
| | ) |
| |
|
| | |
| | max_vis <- max(df$visibility, na.rm = TRUE) |
| | is_km <- max_vis > 2000 |
| |
|
| | if (is_km) { |
| | df <- df %>% mutate(vis_display = visibility / 1000) |
| | y_lab <- "Visibility (km)" |
| | h_temp <- "Vis: %{y:.1f} km<extra></extra>" |
| | } else { |
| | df <- df %>% mutate(vis_display = visibility) |
| | y_lab <- "Visibility (m)" |
| | h_temp <- "Vis: %{y:.0f} m<extra></extra>" |
| | } |
| |
|
| | plotly::plot_ly( |
| | data = df, |
| | x = ~datetime, |
| | y = ~vis_display, |
| | name = "Visibility", |
| | type = "scatter", |
| | mode = "lines", |
| | line = list(color = "#5D4037", width = 1.5), |
| | hovertemplate = h_temp |
| | ) %>% |
| | plotly::layout( |
| | title = list(text = paste("Visibility:", date_range_str), font = list(size = 14)), |
| | xaxis = list(title = "", type = "date"), |
| | yaxis = list(title = y_lab), |
| | hovermode = "x unified", |
| | hoverlabel = list(), |
| | legend = list(orientation = "h", x = 0.5, xanchor = "center", y = -0.25), |
| | margin = list(t = 50, b = 80, l = 60, r = 20), |
| | modebar = list(orientation = "h") |
| | ) %>% |
| | plotly::config(displaylogo = FALSE) |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | create_ground_temp_plot <- function(df) { |
| | if (is.null(df) || nrow(df) == 0) { |
| | return(create_empty_plot("No data available")) |
| | } |
| |
|
| | df <- clean_weather_data(df) |
| |
|
| | has_ground <- "temp_min_ground" %in% names(df) && any(!is.na(df$temp_min_ground)) |
| | has_50cm <- "temp_min_50cm" %in% names(df) && any(!is.na(df$temp_min_50cm)) |
| |
|
| | if (!has_ground && !has_50cm) { |
| | return(create_empty_plot("No ground temperature data available")) |
| | } |
| |
|
| | date_range_str <- paste( |
| | format(min(df$datetime), "%d %b %Y"), "-", |
| | format(max(df$datetime), "%d %b %Y") |
| | ) |
| |
|
| | p <- plotly::plot_ly(type = "scatter", mode = "lines") |
| |
|
| | if (has_ground) { |
| | p <- p %>% plotly::add_lines( |
| | data = df %>% filter(!is.na(temp_min_ground)), |
| | x = ~datetime, |
| | y = ~temp_min_ground, |
| | name = "Ground Surface Min (TNSOL)", |
| | line = list(color = "#388E3C", width = 1.5), |
| | hovertemplate = "Ground Min: %{y:.1f}°C<extra></extra>" |
| | ) |
| | } |
| |
|
| | if (has_50cm) { |
| | p <- p %>% plotly::add_lines( |
| | data = df %>% filter(!is.na(temp_min_50cm)), |
| | x = ~datetime, |
| | y = ~temp_min_50cm, |
| | name = "Soil Min (-50cm)", |
| | line = list(color = "#795548", width = 1.5, dash = "dot"), |
| | hovertemplate = "Soil (-50cm): %{y:.1f}°C<extra></extra>" |
| | ) |
| | } |
| |
|
| | |
| | p <- p %>% plotly::add_lines( |
| | x = c(min(df$datetime), max(df$datetime)), |
| | y = c(0, 0), |
| | name = "Freezing (0°C)", |
| | line = list(color = "rgba(100,100,200,0.5)", width = 1, dash = "dash"), |
| | showlegend = FALSE, hoverinfo = "skip" |
| | ) |
| |
|
| | p %>% |
| | plotly::layout( |
| | title = list(text = paste("Ground Temperatures:", date_range_str), font = list(size = 14)), |
| | xaxis = list(title = "", type = "date"), |
| | yaxis = list(title = get_axis_label(df, "temp_min_ground", "Temperature")), |
| | hovermode = "x unified", |
| | hoverlabel = list(), |
| | legend = list(orientation = "h", x = 0.5, xanchor = "center", y = -0.25), |
| | margin = list(t = 50, b = 80, l = 60, r = 20), |
| | modebar = list(orientation = "h") |
| | ) %>% |
| | plotly::config(displaylogo = FALSE) |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | create_soil_temp_plot <- function(df) { |
| | if (is.null(df) || nrow(df) == 0) { |
| | return(create_empty_plot("No data available")) |
| | } |
| |
|
| | df <- clean_weather_data(df) |
| |
|
| | |
| | vars <- c("soil_temp_10cm", "soil_temp_20cm", "soil_temp_50cm", "soil_temp_100cm") |
| | has_any <- any(sapply(vars, function(v) v %in% names(df) && any(!is.na(df[[v]])))) |
| |
|
| | if (!has_any) { |
| | return(create_empty_plot("No soil temperature data available")) |
| | } |
| |
|
| | date_range_str <- paste( |
| | format(min(df$datetime), "%d %b %Y"), "-", |
| | format(max(df$datetime), "%d %b %Y") |
| | ) |
| |
|
| | p <- plotly::plot_ly(type = "scatter", mode = "lines") |
| |
|
| | |
| | colors <- c( |
| | "soil_temp_10cm" = "#D7CCC8", "soil_temp_20cm" = "#A1887F", |
| | "soil_temp_50cm" = "#795548", "soil_temp_100cm" = "#3E2723" |
| | ) |
| |
|
| | names <- c( |
| | "soil_temp_10cm" = "Soil -10cm", "soil_temp_20cm" = "Soil -20cm", |
| | "soil_temp_50cm" = "Soil -50cm", "soil_temp_100cm" = "Soil -100cm" |
| | ) |
| |
|
| | for (v in vars) { |
| | if (v %in% names(df) && any(!is.na(df[[v]]))) { |
| | p <- p %>% plotly::add_lines( |
| | data = df %>% filter(!is.na(.data[[v]])), |
| | x = ~datetime, |
| | y = as.formula(paste0("~", v)), |
| | name = names[[v]], |
| | line = list(color = colors[[v]], width = 1.5), |
| | hovertemplate = paste0(names[[v]], ": %{y:.1f}°C<extra></extra>") |
| | ) |
| | } |
| | } |
| |
|
| | |
| | p <- p %>% plotly::add_lines( |
| | x = c(min(df$datetime), max(df$datetime)), |
| | y = c(0, 0), |
| | name = "Freezing (0°C)", |
| | line = list(color = "rgba(100,100,200,0.5)", width = 1, dash = "dash"), |
| | showlegend = FALSE, hoverinfo = "skip" |
| | ) |
| |
|
| | p %>% |
| | plotly::layout( |
| | title = list(text = paste("Soil Temperatures:", date_range_str), font = list(size = 14)), |
| | xaxis = list(title = "", type = "date"), |
| | yaxis = list(title = get_axis_label(df, "soil_temp_10cm", "Temperature")), |
| | hovermode = "x unified", |
| | margin = list(t = 50, b = 80, l = 60, r = 20), |
| | legend = list(orientation = "h", x = 0.5, xanchor = "center", y = -0.25), |
| | modebar = list(orientation = "h") |
| | ) %>% |
| | plotly::config(displaylogo = FALSE) |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | create_wind_2m_gust_plot <- function(df) { |
| | if (is.null(df) || nrow(df) == 0) { |
| | return(create_empty_plot("No data available")) |
| | } |
| |
|
| | df <- clean_weather_data(df) |
| |
|
| | has_FF2 <- "wind_speed_2m" %in% names(df) && any(!is.na(df$wind_speed_2m)) |
| | has_FXI <- "wind_gust_inst" %in% names(df) && any(!is.na(df$wind_gust_inst)) |
| |
|
| | if (!has_FF2 && !has_FXI) { |
| | return(create_empty_plot("No Wind 2m / Instant Gust data available")) |
| | } |
| |
|
| | date_range_str <- paste( |
| | format(min(df$datetime), "%d %b %Y"), "-", |
| | format(max(df$datetime), "%d %b %Y") |
| | ) |
| |
|
| | p <- plotly::plot_ly(type = "scatter", mode = "lines") |
| |
|
| | if (has_FF2) { |
| | p <- p %>% plotly::add_lines( |
| | data = df %>% filter(!is.na(wind_speed_2m)), |
| | x = ~datetime, |
| | y = ~wind_speed_2m, |
| | name = "Wind Speed (2m)", |
| | line = list(color = "#81C784", width = 1.5), |
| | hovertemplate = "Wind (2m): %{y:.1f} m/s<extra></extra>" |
| | ) |
| | } |
| |
|
| | if (has_FXI) { |
| | p <- p %>% plotly::add_markers( |
| | data = df %>% filter(!is.na(wind_gust_inst)), |
| | x = ~datetime, |
| | y = ~wind_gust_inst, |
| | name = "Instant Gust (max)", |
| | marker = list(color = "#D32F2F", size = 3, opacity = 0.6), |
| | hovertemplate = "Inst. Gust: %{y:.1f} m/s<extra></extra>" |
| | ) |
| | } |
| |
|
| | p %>% |
| | plotly::layout( |
| | title = list(text = paste("Wind (2m) & Instant Gusts:", date_range_str), font = list(size = 14)), |
| | xaxis = list(title = "", type = "date"), |
| | yaxis = list(title = get_axis_label(df, "wind_speed_2m", "Speed")), |
| | hovermode = "x unified", |
| | margin = list(t = 50, b = 80, l = 60, r = 20), |
| | legend = list(orientation = "h", x = 0.5, xanchor = "center", y = -0.25), |
| | modebar = list(orientation = "h") |
| | ) %>% |
| | plotly::config(displaylogo = FALSE) |
| | } |
| |
|