| |
| library(plotly) |
| library(dplyr) |
| 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) |
| } |
|
|
| |
| |
| ensure_temporal_continuity <- function(df, resolution = NULL) { |
| if (is.null(df) || nrow(df) < 2) { |
| return(df) |
| } |
|
|
| |
| if (!"DateTime" %in% names(df)) { |
| return(df) |
| } |
|
|
| seq_by <- NULL |
| if (!is.null(resolution)) { |
| seq_by <- switch(resolution, |
| "Hourly" = "hour", |
| "Daily" = "day", |
| "Monthly" = "month", |
| "10 Minutes" = "10 min", |
| NULL |
| ) |
| } |
|
|
| |
| valid_dt <- df$DateTime[!is.na(df$DateTime)] |
| if (length(valid_dt) < 2) return(df) |
|
|
| if (is.null(seq_by)) { |
| |
| dt_vals <- as.numeric(difftime(valid_dt[2:min(length(valid_dt), 200)], valid_dt[1:(min(length(valid_dt), 200) - 1)], units = "mins")) |
| dt_vals <- dt_vals[!is.na(dt_vals) & dt_vals > 0] |
| if (length(dt_vals) > 0) { |
| dt <- min(dt_vals) |
| if (dt >= 40000) seq_by <- "month" |
| else if (dt >= 1400) seq_by <- "day" |
| else if (dt >= 50) seq_by <- "hour" |
| else seq_by <- "10 min" |
| } else { |
| return(df) |
| } |
| } |
|
|
| |
| full_dates <- data.frame( |
| DateTime = seq( |
| from = min(valid_dt), |
| to = max(valid_dt), |
| by = seq_by |
| ) |
| ) |
|
|
| |
| |
| df_complete <- full_join(full_dates, df, by = "DateTime") %>% |
| 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_jma_data <- function(df) { |
| if (is.null(df) || nrow(df) == 0) return(df) |
| |
| |
| |
| cols_to_check <- c( |
| "Temperature", "Temp_Mean", "Temp_Max", "Temp_Min", |
| "Precipitation", "Wind_Speed", "Humidity", "Pressure", |
| "Sunshine_Hours", "Sunshine_Minutes", |
| "Solar_Radiation", |
| "Snow_Depth", "Snowfall" |
| ) |
|
|
| for (col in cols_to_check) { |
| if (col %in% names(df)) { |
| |
| if (!is.numeric(df[[col]])) { |
| df[[col]] <- suppressWarnings(as.numeric(as.character(df[[col]]))) |
| } |
| } |
| } |
| 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_temperature_plot_jma <- function(df, resolution = NULL) { |
| df <- clean_jma_data(df) |
| df <- ensure_temporal_continuity(df, resolution) |
|
|
| if (is.null(df) || nrow(df) == 0) return(create_empty_plot("No Temperature Data")) |
|
|
| |
| has_temp <- "Temperature" %in% names(df) && any(!is.na(df$Temperature)) |
| has_temp_mean <- "Temp_Mean" %in% names(df) && any(!is.na(df$Temp_Mean)) |
| has_tmax <- "Temp_Max" %in% names(df) && any(!is.na(df$Temp_Max)) |
| has_tmin <- "Temp_Min" %in% names(df) && any(!is.na(df$Temp_Min)) |
|
|
| |
| |
| main_col <- if (has_temp) "Temperature" else if (has_temp_mean) "Temp_Mean" else NULL |
| |
| if (is.null(main_col) && !has_tmax && !has_tmin) { |
| return(create_empty_plot("No Temperature Data")) |
| } |
|
|
| |
| 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_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 = "Min: %{y:.1f}°C<extra></extra>", |
| showlegend = FALSE, |
| legendgroup = "daily_range" |
| ) %>% 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 = "Max: %{y:.1f}°C<extra></extra>", |
| showlegend = show_leg, |
| legendgroup = "daily_range" |
| ) |
| } |
| } |
|
|
| |
| if (!is.null(main_col)) { |
| p <- p %>% plotly::add_lines( |
| data = df, x = ~DateTime, y = as.formula(paste0("~", main_col)), |
| name = "Temperature", |
| line = list(color = "#b71c1c", width = 2), |
| hovertemplate = "Temp: %{y:.1f}°C<extra></extra>", |
| connectgaps = FALSE |
| ) |
| } |
|
|
| 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", |
| legend = list(orientation = "h", x = 0.5, xanchor = "center", y = -0.1), |
| margin = list(t = 50, b = 50, l = 50, r = 20) |
| ) %>% plotly::config(displaylogo = FALSE) |
| } |
|
|
| |
| |
| create_precipitation_plot_jma <- function(df, resolution = NULL) { |
| df <- clean_jma_data(df) |
| df <- ensure_temporal_continuity(df, resolution) |
|
|
| if (is.null(df) || nrow(df) == 0) return(create_empty_plot("No Precipitation Data")) |
| if (!"Precipitation" %in% names(df)) return(create_empty_plot("No Precipitation Data")) |
|
|
| |
| if (all(is.na(df$Precipitation))) return(create_empty_plot("No Precipitation Data")) |
|
|
| 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 = ~Precipitation, type = "bar", |
| name = "Precipitation", |
| marker = list(color = "#0277bd"), |
| hovertemplate = "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 = "Precipitation (mm)"), |
| hovermode = "x unified", |
| margin = list(t = 50, b = 50, l = 50, r = 20) |
| ) %>% plotly::config(displaylogo = FALSE) |
| } |
|
|
| |
| |
| create_wind_plot_jma <- function(df, resolution = NULL) { |
| df <- clean_jma_data(df) |
| df <- ensure_temporal_continuity(df, resolution) |
|
|
| if (is.null(df) || nrow(df) == 0) return(create_empty_plot("No Wind Data")) |
| if (!"Wind_Speed" %in% names(df)) return(create_empty_plot("No Wind Data")) |
|
|
| 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 = "none") |
|
|
| |
| 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", |
| line = list(color = "#43a047", width = 1), |
| fill = "tozeroy", |
| fillcolor = "rgba(67, 160, 71, 0.5)", |
| hovertemplate = "Speed: %{y:.1f} m/s<extra></extra>", |
| showlegend = (i == 1), |
| legendgroup = "wind_speed" |
| ) |
| } |
|
|
| |
| if ("Wind_Max_Speed" %in% names(df) && any(!is.na(df$Wind_Max_Speed))) { |
| df_max <- df %>% filter(!is.na(Wind_Max_Speed)) |
| |
| p <- p %>% plotly::add_markers( |
| data = df_max, x = ~DateTime, y = ~Wind_Max_Speed, |
| name = "Max Speed", |
| marker = list(color = "#2e7d32", size = 5, symbol = "circle"), |
| hovertemplate = "Max: %{y:.1f} m/s<extra></extra>", |
| legendgroup = "wind_max" |
| ) |
| } |
|
|
| |
| |
| if ("Wind_Direction_Deg" %in% names(df) && any(!is.na(df$Wind_Direction_Deg))) { |
| |
| df_dir <- df %>% filter(!is.na(Wind_Direction_Deg)) |
| |
| |
| |
| |
| |
| |
| p <- p %>% plotly::add_markers( |
| data = df_dir, x = ~DateTime, y = ~Wind_Direction_Deg, |
| yaxis = "y2", |
| name = "Direction", |
| marker = list(color = "purple", size = 3, opacity = 0.5), |
| hovertemplate = "Dir: %{y:.0f}°<extra></extra>" |
| ) |
| } |
|
|
| layout_args <- list( |
| title = list(text = paste("Wind:", date_range_str), font = list(size = 14)), |
| xaxis = list(title = "", type = "date"), |
| yaxis = list(title = "Wind Speed (m/s)"), |
| margin = list(t = 50, b = 50, l = 50, r = 50), |
| hovermode = "x unified", |
| legend = list(orientation = "h", x = 0.5, xanchor = "center", y = -0.1) |
| ) |
| |
| if ("Wind_Direction_Deg" %in% names(df) && any(!is.na(df$Wind_Direction_Deg))) { |
| layout_args$yaxis2 <- list( |
| title = "Direction (°)", |
| overlaying = "y", |
| side = "right", |
| range = c(0, 360), |
| tickmode = "array", |
| tickvals = seq(0, 360, 45), |
| ticktext = c("N", "NE", "E", "SE", "S", "SW", "W", "NW", "N") |
| ) |
| } |
|
|
| do.call(plotly::layout, c(list(p), layout_args)) %>% plotly::config(displaylogo = FALSE) |
| } |
|
|
| |
| |
| create_humidity_plot_jma <- function(df, resolution = NULL) { |
| df <- clean_jma_data(df) |
| df <- ensure_temporal_continuity(df, resolution) |
|
|
| if (is.null(df) || nrow(df) == 0) return(create_empty_plot("No Humidity Data")) |
| if (!"Humidity" %in% names(df)) return(create_empty_plot("No Humidity Data")) |
|
|
| |
| |
| has_temp <- "Temperature" %in% names(df) || "Temp_Mean" %in% names(df) |
| temp_col <- if ("Temperature" %in% names(df)) "Temperature" else "Temp_Mean" |
| |
| if (has_temp) { |
| df$Dew_Point <- calculate_dew_point(df[[temp_col]], df$Humidity) |
| } |
|
|
| 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 = "none") |
|
|
| |
| if ("Dew_Point" %in% names(df) && any(!is.na(df$Dew_Point))) { |
| 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>", |
| connectgaps = FALSE |
| ) |
| } |
|
|
| |
| p <- p %>% plotly::add_lines( |
| data = df, x = ~DateTime, y = ~Humidity, |
| name = "Humidity", |
| line = list(color = "#43a047", width = 1.5, dash = "dot"), |
| hovertemplate = "RH: %{y:.0f}%<extra></extra>", |
| yaxis = "y2", |
| connectgaps = FALSE |
| ) |
|
|
| p %>% plotly::layout( |
| title = list(text = paste("Humidity:", date_range_str), font = list(size = 14)), |
| xaxis = list(title = "", type = "date"), |
| yaxis = list(title = "Dew Point (°C)"), |
| yaxis2 = list(title = "Relative Humidity (%)", overlaying = "y", side = "right", range = c(0, 100)), |
| margin = list(t = 50, b = 50, l = 50, r = 50), |
| hovermode = "x unified", |
| legend = list(orientation = "h", x = 0.5, xanchor = "center", y = -0.1) |
| ) %>% plotly::config(displaylogo = FALSE) |
| } |
|
|
| |
| |
| create_sunshine_plot_jma <- function(df, resolution = NULL) { |
| df <- clean_jma_data(df) |
| df <- ensure_temporal_continuity(df, resolution) |
|
|
| col <- if ("Sunshine_Hours" %in% names(df)) "Sunshine_Hours" else if ("Sunshine_Minutes" %in% names(df)) "Sunshine_Minutes" else NULL |
| if (is.null(col) || all(is.na(df[[col]]))) return(create_empty_plot("No Sunshine Data")) |
|
|
| date_range_str <- paste(format(min(df$DateTime), "%d %b %Y"), "-", format(max(df$DateTime), "%d %b %Y")) |
| unit <- if (col == "Sunshine_Hours") "hours" else "min" |
| |
|
|
|
|
| plotly::plot_ly(data = df, x = ~DateTime, y = as.formula(paste0("~", col)), type = "bar", |
| name = "Sunshine", |
| marker = list(color = "rgba(255, 215, 0, 0.6)", line = list(color = "#FFD700", width = 1.5)), |
| hovertemplate = paste0("Sunshine: %{y:.1f} ", unit, "<extra></extra>") |
| ) %>% plotly::layout( |
| title = list(text = paste("Sunshine Duration:", date_range_str), font = list(size = 14)), |
| xaxis = list(title = "", type = "date"), |
| yaxis = list(title = paste0("Duration (", unit, ")")), |
| hovermode = "x unified", |
| margin = list(t = 50, b = 50, l = 50, r = 20) |
| ) %>% plotly::config(displaylogo = FALSE) |
| } |
|
|
| |
| |
| create_pressure_plot_jma <- function(df, resolution = NULL) { |
| df <- clean_jma_data(df) |
| df <- ensure_temporal_continuity(df, resolution) |
| |
| has_sta <- "Pressure" %in% names(df) && any(!is.na(df$Pressure)) |
| has_sea <- "Pressure_Sea_Level" %in% names(df) && any(!is.na(df$Pressure_Sea_Level)) |
| |
| if (is.null(df) || nrow(df) == 0 || (!has_sta && !has_sea)) { |
| return(create_empty_plot("No Pressure Data")) |
| } |
|
|
| 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_sta) { |
| p <- p %>% plotly::add_lines( |
| data = df, x = ~DateTime, y = ~Pressure, |
| name = "Station Pressure", |
| line = list(color = "#7b1fa2", width = 1.5), |
| hovertemplate = "Station: %{y:.1f} hPa<extra></extra>", |
| connectgaps = FALSE |
| ) |
| } |
|
|
| if (has_sea) { |
| p <- p %>% plotly::add_lines( |
| data = df, x = ~DateTime, y = ~Pressure_Sea_Level, |
| name = "Sea Level Pressure", |
| line = list(color = "#AB47BC", width = 1.5, dash = "dot"), |
| hovertemplate = "Sea Level: %{y:.1f} hPa<extra></extra>", |
| connectgaps = FALSE |
| ) |
| } |
|
|
| p %>% plotly::add_lines( |
| x = c(min(df$DateTime), max(df$DateTime)), |
| 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" |
| ) %>% 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", |
| margin = list(t = 50, b = 50, l = 50, r = 20), |
| legend = list(orientation = "h", x = 0.5, xanchor = "center", y = -0.1) |
| ) %>% plotly::config(displaylogo = FALSE) |
| } |
|
|
| |
| |
| create_solar_plot_jma <- function(df, resolution = NULL) { |
| df <- clean_jma_data(df) |
| df <- ensure_temporal_continuity(df, resolution) |
|
|
| if (is.null(df) || nrow(df) == 0) return(create_empty_plot("No Solar Data")) |
| |
| |
| if (!"Solar_Radiation" %in% names(df) || all(is.na(df$Solar_Radiation))) return(create_empty_plot("No Solar Data")) |
|
|
| 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 = "none") |
|
|
| chunks <- split_into_chunks(df, "Solar_Radiation") |
| 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_Radiation, |
| 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:.2f} MJ/m²<extra></extra>", |
| connectgaps = FALSE, |
| showlegend = (i == 1), |
| legendgroup = "solar_rad" |
| ) |
| } |
|
|
| p %>% plotly::layout( |
| title = list(text = paste("Solar Radiation:", date_range_str), font = list(size = 14)), |
| xaxis = list(title = "", type = "date"), |
| yaxis = list(title = "Radiation (MJ/m²)"), |
| hovermode = "x unified", |
| margin = list(t = 50, b = 50, l = 50, r = 20), |
| legend = list(orientation = "h", x = 0.5, xanchor = "center", y = -0.1) |
| ) %>% plotly::config(displaylogo = FALSE) |
| } |
|
|
| |
| |
| create_snow_plot_jma <- function(df, resolution = NULL) { |
| df <- clean_jma_data(df) |
| df <- ensure_temporal_continuity(df, resolution) |
|
|
| has_depth <- "Snow_Depth" %in% names(df) && any(!is.na(df$Snow_Depth)) |
| has_fall <- "Snowfall" %in% names(df) && any(!is.na(df$Snowfall)) |
|
|
| if (!has_depth && !has_fall) return(create_empty_plot("No Snow Data")) |
|
|
| date_range_str <- paste(format(min(df$DateTime), "%d %b %Y"), "-", format(max(df$DateTime), "%d %b %Y")) |
|
|
| p <- plotly::plot_ly() |
|
|
| if (has_depth) { |
| chunks <- split_into_chunks(df, "Snow_Depth") |
| for (i in seq_along(chunks)) { |
| chunk <- chunks[[i]] |
| if (nrow(chunk) < 1) next |
| |
| p <- p %>% plotly::add_lines( |
| data = chunk, x = ~DateTime, y = ~Snow_Depth, |
| name = "Snow Depth", |
| line = list(color = "#90caf9", width = 1), |
| fill = "tozeroy", |
| fillcolor = "rgba(144, 202, 249, 0.5)", |
| hovertemplate = "Depth: %{y:.0f} cm<extra></extra>", |
| showlegend = (i == 1), |
| legendgroup = "snow_depth" |
| ) |
| } |
| } |
|
|
| if (has_fall) { |
| p <- p %>% plotly::add_bars( |
| data = df, x = ~DateTime, y = ~Snowfall, |
| name = "Snowfall", |
| yaxis = "y2", |
| marker = list(color = "#1565c0"), |
| hovertemplate = "Snowfall: %{y:.0f} cm<extra></extra>" |
| ) |
| } |
|
|
| layout_args <- list( |
| title = list(text = paste("Snow:", date_range_str), font = list(size = 14)), |
| xaxis = list(title = "", type = "date"), |
| yaxis = list(title = "Snow Depth (cm)"), |
| margin = list(t = 50, b = 50, l = 50, r = 50), |
| hovermode = "x unified", |
| legend = list(orientation = "h", x = 0.5, xanchor = "center", y = -0.1) |
| ) |
| |
| if (has_fall) { |
| layout_args$yaxis2 <- list( |
| title = "Snowfall (cm)", |
| overlaying = "y", |
| side = "right", |
| rangemode = "tozero" |
| ) |
| } |
|
|
| do.call(plotly::layout, c(list(p), layout_args)) %>% plotly::config(displaylogo = FALSE) |
| } |
|
|
| |
| |
| |
| |
| |
| create_wind_rose_jma <- function(df, resolution = "Hourly") { |
| if (is.null(df) || nrow(df) == 0) { |
| return(plotly::plot_ly() %>% plotly::layout(title = "No data")) |
| } |
|
|
| |
| if (!all(c("Wind_Speed", "Wind_Direction_Deg") %in% names(df))) { |
| return(plotly::plot_ly() %>% plotly::layout(title = "Wind data not available (Speed/Dir missing)")) |
| } |
| |
| |
| wind_df <- df %>% |
| dplyr::filter(!is.na(Wind_Speed), !is.na(Wind_Direction_Deg)) |
| |
| if (nrow(wind_df) == 0) { |
| return(plotly::plot_ly() %>% plotly::layout(title = "No valid wind data (N/A)")) |
| } |
|
|
| date_range_str <- paste( |
| format(min(df$DateTime), "%Y-%m-%d"), "-", |
| format(max(df$DateTime), "%Y-%m-%d") |
| ) |
|
|
| |
| wind_df <- wind_df %>% |
| dplyr::mutate( |
| |
| dir_bin = round(Wind_Direction_Deg / 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) |
| |
| |
| 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]), |
| hovertemplate = paste0("Speed: ", lvl, " m/s<br>Freq: %{r:.1f}%<extra></extra>") |
| ) |
| } |
| } |
|
|
| p %>% |
| plotly::layout( |
| title = list(text = paste("Wind Rose:", date_range_str), font = list(size = 14)), |
| polar = list( |
| radialaxis = list(ticksuffix = "%", angle = 45), |
| angularaxis = list( |
| rotation = 90, direction = "clockwise", |
| tickmode = "array", tickvals = compass$dir_bin, ticktext = compass$label |
| ) |
| ), |
| showlegend = TRUE, |
| legend = list(title = list(text = "Wind Speed (m/s)"), orientation = "v"), |
| margin = list(t = 50, b = 50, l = 50, r = 50) |
| ) |
| } |
|
|