| |
| |
| |
|
|
| |
| |
| |
| 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, scrollZoom = FALSE) |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| insert_gap_breaks <- function(df, value_cols, gap_threshold_multiplier = 2, min_gap_threshold = NULL) { |
| if (is.null(df) || nrow(df) < 2) { |
| return(df) |
| } |
|
|
| |
| df <- df %>% dplyr::arrange(datetime) |
|
|
| |
| time_diffs <- diff(as.numeric(df$datetime)) |
|
|
| if (length(time_diffs) == 0 || all(is.na(time_diffs))) { |
| return(df) |
| } |
|
|
| |
| median_interval <- stats::median(time_diffs, na.rm = TRUE) |
|
|
| |
| gap_threshold <- 0 |
| if (!is.null(min_gap_threshold)) { |
| gap_threshold <- min_gap_threshold |
| } else { |
| if (is.na(median_interval) || median_interval <= 0) { |
| return(df) |
| } |
| gap_threshold <- median_interval * gap_threshold_multiplier |
| } |
| gap_indices <- which(time_diffs > gap_threshold) |
|
|
| if (length(gap_indices) == 0) { |
| return(df) |
| } |
|
|
| |
| |
| gap_rows <- lapply(gap_indices, function(i) { |
| row <- df[i, , drop = FALSE] |
| |
| row$datetime <- df$datetime[i] + (df$datetime[i + 1] - df$datetime[i]) / 2 |
| |
| for (col in value_cols) { |
| if (col %in% names(row)) { |
| row[[col]] <- NA |
| } |
| } |
| row |
| }) |
|
|
| |
| gap_df <- dplyr::bind_rows(gap_rows) |
| result <- dplyr::bind_rows(df, gap_df) %>% |
| dplyr::arrange(datetime) |
|
|
| return(result) |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| split_into_chunks <- function(df, value_col, gap_threshold_multiplier = 2, min_gap_threshold = NULL) { |
| if (is.null(df) || nrow(df) < 2) { |
| return(list(df)) |
| } |
|
|
| |
| df <- df %>% |
| dplyr::filter(!is.na(!!rlang::sym(value_col))) %>% |
| dplyr::arrange(datetime) |
|
|
| if (nrow(df) < 2) { |
| return(list(df)) |
| } |
|
|
| |
| time_diffs <- diff(as.numeric(df$datetime)) |
|
|
| if (length(time_diffs) == 0 || all(is.na(time_diffs))) { |
| return(list(df)) |
| } |
|
|
| |
| median_interval <- stats::median(time_diffs, na.rm = TRUE) |
|
|
| |
| gap_threshold <- 0 |
| if (!is.null(min_gap_threshold)) { |
| gap_threshold <- min_gap_threshold |
| } else { |
| if (is.na(median_interval) || median_interval <= 0) { |
| return(list(df)) |
| } |
| gap_threshold <- median_interval * gap_threshold_multiplier |
| } |
| gap_indices <- which(time_diffs > gap_threshold) |
|
|
| if (length(gap_indices) == 0) { |
| return(list(df)) |
| } |
|
|
| |
| chunks <- list() |
| start_idx <- 1 |
|
|
| for (gap_idx in gap_indices) { |
| chunks[[length(chunks) + 1]] <- df[start_idx:gap_idx, , drop = FALSE] |
| start_idx <- gap_idx + 1 |
| } |
|
|
| |
| if (start_idx <= nrow(df)) { |
| chunks[[length(chunks) + 1]] <- df[start_idx:nrow(df), , drop = FALSE] |
| } |
|
|
| return(chunks) |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| create_temperature_plot <- function(df) { |
| if (is.null(df) || nrow(df) == 0) { |
| return(create_empty_plot("No data available for Temperature")) |
| } |
|
|
| 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)) |
|
|
| 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 && has_tmax) { |
| df_ribbon <- df %>% dplyr::filter(!is.na(temp_min) & !is.na(temp_max)) |
| chunks <- split_into_chunks(df_ribbon, "temp_min", min_gap_threshold = 86400) |
| 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) { |
| df_tmax <- insert_gap_breaks(df, value_cols = c("temp_max"), min_gap_threshold = 86400) |
| p <- p %>% plotly::add_lines( |
| data = df_tmax, |
| x = ~datetime, |
| y = ~temp_max, name = "Tmax", |
| line = list(color = "#d32f2f", width = 2), |
| hovertemplate = "Tmax: %{y:.1f}°C<extra></extra>", |
| connectgaps = FALSE, |
| showlegend = TRUE |
| ) |
| } |
|
|
| if (has_tmin) { |
| df_tmin <- insert_gap_breaks(df, value_cols = c("temp_min"), min_gap_threshold = 86400) |
| p <- p %>% plotly::add_lines( |
| data = df_tmin, |
| x = ~datetime, |
| y = ~temp_min, name = "Tmin", |
| line = list(color = "#1976d2", width = 2), |
| hovertemplate = "Tmin: %{y:.1f}°C<extra></extra>", |
| connectgaps = FALSE, |
| showlegend = TRUE |
| ) |
| } |
| } |
|
|
| if (has_temp) { |
| df_temp <- insert_gap_breaks(df, value_cols = c("temp"), min_gap_threshold = 86400) |
| p <- p %>% plotly::add_lines( |
| data = df_temp, |
| x = ~datetime, |
| y = ~temp, name = if (has_tmin || has_tmax) "Tmean" else "Temperature", |
| line = list(color = if (has_tmin || has_tmax) "#b71c1c" else "#e53935", width = 2), |
| connectgaps = FALSE, |
| hovertemplate = paste0(if (has_tmin || has_tmax) "Tmean" else "Temp", ": %{y:.1f}°C<extra></extra>"), |
| showlegend = if (has_tmin || has_tmax) TRUE else FALSE |
| ) |
| } |
|
|
| p %>% |
| plotly::layout( |
| title = list(text = paste("Temperature:", date_range_str), font = list(size = 14)), |
| yaxis = list(title = "Temperature (°C)"), |
| xaxis = list(title = "", type = "date"), |
| hovermode = "x unified", |
| legend = list(orientation = "h", x = 0.5, xanchor = "center", y = -0.1), |
| margin = list(t = 50, b = 50, l = 50, r = 20), |
| modebar = list(orientation = "h") |
| ) %>% |
| plotly::config(displaylogo = FALSE, scrollZoom = FALSE) |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| create_humidity_plot <- function(df) { |
| if (is.null(df) || nrow(df) == 0) { |
| return(create_empty_plot("No data available for Humidity")) |
| } |
|
|
| date_range_str <- paste( |
| format(min(df$datetime), "%d %b %Y"), "-", |
| format(max(df$datetime), "%d %b %Y") |
| ) |
|
|
| has_rh <- "rh" %in% names(df) && any(!is.na(df$rh)) |
| has_dew <- "dew_point" %in% names(df) && any(!is.na(df$dew_point)) |
|
|
| if (!has_rh && !has_dew) { |
| return(create_empty_plot("No data available for Humidity")) |
| } |
|
|
| |
| value_cols <- c() |
| if (has_dew) value_cols <- c(value_cols, "dew_point") |
| if (has_rh) value_cols <- c(value_cols, "rh") |
| df_plot <- insert_gap_breaks(df, value_cols = value_cols, min_gap_threshold = 86400) |
|
|
| p <- plotly::plot_ly(df_plot, x = ~datetime) |
|
|
| if (has_dew) { |
| p <- p %>% plotly::add_lines( |
| y = ~dew_point, name = "Dew Point", |
| line = list(color = "#1e88e5", width = 1.5), |
| connectgaps = FALSE, |
| hovertemplate = "Dew Pt: %{y:.1f}°C<extra></extra>" |
| ) |
| } |
|
|
| if (has_rh) { |
| p <- p %>% plotly::add_lines( |
| y = ~rh, name = "RH%", |
| line = list(color = "#43a047", width = 1.5, dash = "dot"), |
| yaxis = "y2", |
| connectgaps = FALSE, |
| hovertemplate = "RH: %{y:.0f}%<extra></extra>" |
| ) |
| } |
|
|
| 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", |
| legend = list(orientation = "h", x = 0.5, xanchor = "center", y = -0.1), |
| margin = list(t = 50, b = 50, 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, scrollZoom = FALSE) |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| create_wind_overview_plot <- function(df) { |
| if (is.null(df) || nrow(df) == 0) { |
| return(create_empty_plot("No data available for Wind")) |
| } |
|
|
| 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" %in% names(df) && any(!is.na(df$wind_gust)) |
|
|
| if (!has_wind && !has_gust) { |
| return(create_empty_plot("No data available for Wind")) |
| } |
|
|
| p <- plotly::plot_ly() |
|
|
| |
| |
| if (has_wind) { |
| wind_chunks <- split_into_chunks(df, value_col = "wind_speed", min_gap_threshold = 86400) |
|
|
| for (i in seq_along(wind_chunks)) { |
| chunk <- wind_chunks[[i]] |
| if (nrow(chunk) > 0) { |
| p <- p %>% plotly::add_lines( |
| data = chunk, |
| x = ~datetime, |
| y = ~wind_speed, |
| name = if (i == 1) "Wind Speed" else NULL, |
| legendgroup = "wind", |
| showlegend = (i == 1), |
| fill = "tozeroy", |
| fillcolor = "rgba(67, 160, 71, 0.3)", |
| line = list(color = "#43a047", width = 1), |
| connectgaps = FALSE, |
| hovertemplate = "Wind: %{y:.1f} m/s<extra></extra>" |
| ) |
| } |
| } |
| } |
|
|
| |
| if (has_gust) { |
| gust_data <- df %>% dplyr::filter(!is.na(wind_gust)) |
| if (nrow(gust_data) > 0) { |
| p <- p %>% plotly::add_markers( |
| data = gust_data, |
| x = ~datetime, |
| y = ~wind_gust, |
| name = "Gust", |
| marker = list(color = "#2e7d32", size = 4), |
| hovertemplate = "Gust: %{y:.1f} m/s<extra></extra>" |
| ) |
| } |
| } |
|
|
| p %>% |
| plotly::layout( |
| title = list(text = paste("Wind:", date_range_str), font = list(size = 14)), |
| yaxis = list(title = "Wind (m/s)"), |
| xaxis = list(title = "", type = "date"), |
| hovermode = "x unified", |
| legend = list(orientation = "h", x = 0.5, xanchor = "center", y = -0.1), |
| margin = list(t = 50, b = 50, l = 50, r = 20), |
| modebar = list(orientation = "h") |
| ) %>% |
| plotly::config(displaylogo = FALSE, scrollZoom = FALSE) |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| create_pressure_plot <- function(df) { |
| if (is.null(df) || nrow(df) == 0) { |
| return(create_empty_plot("No data available for Pressure")) |
| } |
|
|
| date_range_str <- paste( |
| format(min(df$datetime), "%d %b %Y"), "-", |
| format(max(df$datetime), "%d %b %Y") |
| ) |
|
|
| has_msl <- "pressure" %in% names(df) && any(!is.na(df$pressure)) |
| has_stn <- "station_pressure" %in% names(df) && any(!is.na(df$station_pressure)) |
|
|
| if (!has_msl && !has_stn) { |
| return(create_empty_plot("No data available for Pressure")) |
| } |
|
|
| |
| value_cols <- c() |
| if (has_msl) value_cols <- c(value_cols, "pressure") |
| if (has_stn) value_cols <- c(value_cols, "station_pressure") |
| df_plot <- insert_gap_breaks(df, value_cols = value_cols, min_gap_threshold = 86400) |
|
|
| p <- plotly::plot_ly(df_plot, x = ~datetime) |
|
|
| if (has_msl) { |
| p <- p %>% plotly::add_lines( |
| y = ~pressure, name = "MSL Pressure", |
| line = list(color = "#7b1fa2", width = 1.5), |
| connectgaps = FALSE, |
| hovertemplate = "MSL: %{y:.1f} hPa<extra></extra>" |
| ) |
| } |
|
|
| if (has_stn) { |
| p <- p %>% plotly::add_lines( |
| y = ~station_pressure, name = "Stn Pressure", |
| line = list(color = "#ef6c00", width = 1.2, dash = "dot"), |
| connectgaps = FALSE, |
| hovertemplate = "Stn: %{y:.1f} hPa<extra></extra>" |
| ) |
| } |
|
|
| p %>% |
| plotly::add_segments( |
| x = min(df$datetime), xend = max(df$datetime), |
| y = 1013.25, yend = 1013.25, |
| name = "Standard (1013.25)", |
| line = list(color = "rgba(100, 100, 100, 0.5)", width = 1, dash = "dash"), |
| showlegend = TRUE |
| ) %>% |
| plotly::layout( |
| title = list(text = paste("Pressure:", date_range_str), font = list(size = 14)), |
| yaxis = list(title = "Pressure (hPa)"), |
| xaxis = list(title = "", type = "date"), |
| hovermode = "x unified", |
| legend = list(orientation = "h", x = 0.5, xanchor = "center", y = -0.1), |
| margin = list(t = 50, b = 50, l = 50, r = 20), |
| modebar = list(orientation = "h") |
| ) %>% |
| plotly::config(displaylogo = FALSE, scrollZoom = FALSE) |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| create_visibility_plot <- function(df) { |
| if (is.null(df) || nrow(df) == 0) { |
| return(create_empty_plot("No data available for Visibility")) |
| } |
|
|
| date_range_str <- paste( |
| format(min(df$datetime), "%d %b %Y"), "-", |
| format(max(df$datetime), "%d %b %Y") |
| ) |
|
|
| if (!"vis" %in% names(df) || all(is.na(df$vis))) { |
| return(create_empty_plot("No data available for Visibility")) |
| } |
|
|
| |
| df_plot <- insert_gap_breaks(df, value_cols = c("vis"), min_gap_threshold = 86400) |
|
|
| plotly::plot_ly(df_plot, x = ~datetime) %>% |
| plotly::add_lines( |
| y = ~vis, name = "Visibility", |
| line = list(color = "#607d8b", width = 1.5), |
| connectgaps = FALSE, |
| hovertemplate = "Vis: %{y:.1f} km<extra></extra>" |
| ) %>% |
| plotly::layout( |
| title = list(text = paste("Visibility:", date_range_str), font = list(size = 14)), |
| yaxis = list(title = "Vis (km)"), |
| xaxis = list(title = "", type = "date"), |
| hovermode = "x unified", |
| margin = list(t = 50, b = 50, l = 50, r = 20), |
| modebar = list(orientation = "h") |
| ) %>% |
| plotly::config(displaylogo = FALSE, scrollZoom = FALSE) |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| create_precipitation_plot <- function(df) { |
| if (is.null(df) || nrow(df) == 0) { |
| 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") |
| ) |
|
|
| precip_cols <- c( |
| "precip" = "1h", |
| "precip_3h" = "3h", |
| "precip_6h" = "6h", |
| "precip_9h" = "9h", |
| "precip_12h" = "12h", |
| "precip_15h" = "15h", |
| "precip_18h" = "18h", |
| "precip_21h" = "21h", |
| "precip_24h" = "24h" |
| ) |
|
|
| 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 %>% |
| dplyr::filter(!is.na(!!rlang::sym(col_name))) %>% |
| dplyr::select(datetime, val = !!rlang::sym(col_name)) |
|
|
| if (nrow(p_data) > 0) { |
| p <- plotly::plot_ly(p_data, x = ~datetime) %>% |
| plotly::add_bars( |
| y = ~val, |
| name = paste("Precip", label), |
| marker = list(color = "#0277bd"), |
| hovertemplate = paste0(label, ": %{y:.1f} mm<extra></extra>"), |
| showlegend = FALSE |
| ) %>% |
| 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", |
| margin = list(t = 50, b = 80, l = 60, r = 20), |
| modebar = list(orientation = "h") |
| ) %>% |
| plotly::config(displaylogo = FALSE, scrollZoom = FALSE) |
| } |
|
|
|
|
| |
| |
| |
| |
| |
| |
| |
| create_wind_rose_plot <- function(df) { |
| if (is.null(df) || nrow(df) == 0) { |
| 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") |
| ) |
|
|
| if (!all(c("wind_speed", "wind_dir") %in% names(df))) { |
| return(create_empty_plot("No wind data available")) |
| } |
|
|
| wind_df <- df %>% |
| dplyr::filter(!is.na(!!rlang::sym("wind_speed")), !is.na(!!rlang::sym("wind_dir"))) |
|
|
| if (nrow(wind_df) == 0) { |
| return(create_empty_plot("No wind data available")) |
| } |
|
|
| wind_df <- wind_df %>% |
| dplyr::mutate( |
| dir_bin = round(!!rlang::sym("wind_dir") / 22.5) * 22.5, |
| dir_bin = ifelse(dir_bin == 360, 0, dir_bin), |
| speed_cat = cut( |
| !!rlang::sym("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(!!rlang::sym("dir_bin"), !!rlang::sym("speed_cat")) %>% |
| dplyr::summarise(count = dplyr::n(), .groups = "drop") |
|
|
| total_obs <- sum(wind_df$count) |
| wind_df <- wind_df %>% |
| dplyr::mutate(percentage = !!rlang::sym("count") / total_obs * 100) |
|
|
| 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() |
| 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(!!rlang::sym("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 = 10) |
| ), |
| 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.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", |
| name = "info_annotation" |
| ) |
| ) |
| ) %>% |
| plotly::config(displaylogo = FALSE, scrollZoom = FALSE) |
| } |
|
|
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| create_weathergami_plot <- function(df) { |
| if (is.null(df) || nrow(df) == 0) { |
| return(create_empty_plot("No temperature data available")) |
| } |
|
|
| |
| daily_summary <- df |
|
|
| if (is.null(daily_summary) || nrow(daily_summary) == 0) { |
| return(create_empty_plot("No temperature data available")) |
| } |
|
|
| |
| daily_temps <- daily_summary %>% |
| dplyr::filter(!is.na(!!rlang::sym("temp_max")), !is.na(!!rlang::sym("temp_min"))) |
|
|
| if (nrow(daily_temps) == 0) { |
| return(create_empty_plot("No temperature data available")) |
| } |
|
|
| date_range_str <- paste( |
| format(min(df$datetime), "%d %b %Y"), "-", |
| format(max(df$datetime), "%d %b %Y") |
| ) |
|
|
| |
| daily_temps <- daily_temps %>% |
| dplyr::mutate( |
| tmax_bin = round(!!rlang::sym("temp_max")), |
| tmin_bin = round(!!rlang::sym("temp_min")) |
| ) |
|
|
| |
| freq_table <- daily_temps %>% |
| dplyr::group_by(!!rlang::sym("tmax_bin"), !!rlang::sym("tmin_bin")) %>% |
| dplyr::summarise(count = dplyr::n(), .groups = "drop") |
|
|
| tmin_range <- range(freq_table$tmin_bin) |
| tmax_range <- range(freq_table$tmax_bin) |
|
|
| p <- plotly::plot_ly() %>% |
| 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>" |
| ) |
| ) |
|
|
| diag_range <- seq( |
| min(tmin_range[1], tmax_range[1]) - 2, |
| max(tmin_range[2], tmax_range[2]) + 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 |
| ), |
| showlegend = TRUE, |
| legend = list(orientation = "h", x = 0.5, xanchor = "center", y = -0.15), |
| margin = list(t = 50, b = 100, l = 60, r = 20), |
| modebar = list(orientation = "h"), |
| annotations = list( |
| list( |
| x = 0, y = 1.12, xref = "paper", yref = "paper", |
| text = "ⓘ", |
| showarrow = FALSE, |
| font = list(size = 18, color = "#666"), |
| xanchor = "left", |
| hovertext = paste( |
| "Weathergami shows how often each combination of", |
| "daily max and min temperatures occurs.<br>", |
| "Darker colors indicate more frequent combinations." |
| ), |
| hoverinfo = "text", |
| name = "info_annotation" |
| ) |
| ) |
| ) %>% |
| plotly::config(displaylogo = FALSE, scrollZoom = FALSE) |
| } |
|
|
|
|
| |
| |
| |
| |
| |
| |
| |
| create_diurnal_plot <- function(df, offset_hours = 0) { |
| if (is.null(df) || nrow(df) == 0) { |
| return(create_empty_plot("No temperature data available")) |
| } |
|
|
| df_diurnal <- df %>% |
| dplyr::filter(!is.na(!!rlang::sym("temp"))) %>% |
| dplyr::mutate( |
| local_time = !!rlang::sym("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")) |
| } |
|
|
| median_cycle <- df_diurnal %>% |
| dplyr::group_by(!!rlang::sym("hour_val")) %>% |
| dplyr::summarise(median_temp = median(!!rlang::sym("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), by = 5)] |
| df_diurnal_sampled <- df_diurnal %>% |
| dplyr::filter(!!rlang::sym("date") %in% sampled_dates) |
|
|
| tz_sign <- ifelse(offset_hours >= 0, "+", "") |
| tz_str <- paste0("UTC", tz_sign, round(offset_hours, 1)) |
|
|
| plotly::plot_ly() %>% |
| plotly::add_lines( |
| data = df_diurnal_sampled, |
| x = ~hour_val, |
| y = ~temp, |
| split = ~date, |
| line = list(color = "rgba(150, 150, 150, 0.3)", width = 0.5), |
| connectgaps = FALSE, |
| hoverinfo = "none", |
| showlegend = FALSE, |
| name = "Daily Cycles" |
| ) %>% |
| plotly::add_lines( |
| data = median_cycle %>% dplyr::filter(!is.na(!!rlang::sym("median_temp"))), |
| x = ~hour_val, |
| y = ~median_temp, |
| name = "Median Cycle", |
| line = list(color = "#d32f2f", width = 4), |
| connectgaps = FALSE, |
| hovertemplate = "Hour: %{x}:00<br>Median Temp: %{y:.1f}°C<extra></extra>" |
| ) %>% |
| plotly::layout( |
| title = list(text = paste("Diurnal Temperature Cycle:", date_range_str), font = list(size = 14)), |
| xaxis = list(title = paste0("Hour of Day (Local Mean Time, ", tz_str, ")"), tickvals = seq(0, 23, by = 3)), |
| yaxis = list(title = "Temperature (°C)"), |
| showlegend = TRUE, |
| 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"), |
| annotations = list( |
| list( |
| x = 0, y = 1.12, xref = "paper", yref = "paper", |
| text = "ⓘ", |
| showarrow = FALSE, |
| font = list(size = 18, color = "#666"), |
| xanchor = "left", |
| hovertext = paste( |
| "<b>Diurnal Temperature Cycle</b><br>", |
| "Shows daily temperature patterns.<br><br>", |
| "<b>Grey lines:</b> Individual days (sampled)<br>", |
| "<b>Red line:</b> Median hourly temperature<br><br>", |
| "<b>Time Zone:</b> Local Mean Time (LMT)<br>", |
| "Approximated from longitude:<br>", |
| "UTC + (Longitude / 15)" |
| ), |
| hoverinfo = "text", |
| name = "info_annotation" |
| ) |
| ) |
| ) %>% |
| plotly::config(displaylogo = FALSE, scrollZoom = FALSE) |
| } |
|
|