From 5b282c9491c90edd34b6d5a14ee44c1da205a3b7 Mon Sep 17 00:00:00 2001 From: Mirko Calcaterra Date: Wed, 30 Oct 2024 21:24:22 +0100 Subject: [PATCH] New refresher --- All Statistics/.Rhistory | 894 +++++++++--------- Scoreboard/VolleyballScoreboardServer.py | 7 +- ...VolleyballScoreboardServer.cpython-312.pyc | Bin 0 -> 7119 bytes .../__pycache__/refresh_app.cpython-312.pyc | Bin 0 -> 2573 bytes Scoreboard/main_app.py | 17 + Scoreboard/refresh_app.py | 46 + Scoreboard/templates/scoreboard.html | 107 ++- 7 files changed, 593 insertions(+), 478 deletions(-) create mode 100644 Scoreboard/__pycache__/VolleyballScoreboardServer.cpython-312.pyc create mode 100644 Scoreboard/__pycache__/refresh_app.cpython-312.pyc create mode 100644 Scoreboard/main_app.py create mode 100644 Scoreboard/refresh_app.py diff --git a/All Statistics/.Rhistory b/All Statistics/.Rhistory index 8e76cee..07464eb 100644 --- a/All Statistics/.Rhistory +++ b/All Statistics/.Rhistory @@ -1,268 +1,166 @@ -# Fill missing values for `n_receptions`, `positivity`, and `rate` with zeros (or other defaults) -reception_rate <- reception_rate %>% -mutate( -n_receptions = replace_na(n_receptions, 0), -positivity = replace_na(positivity, 0), -rate = replace_na(rate, 0) +summarize( +Att_Tot = n(), +Att_Perfetto = sum(evaluation_code == '#') / n(), +Att_Efficienza = (sum(evaluation_code == '#') - sum(evaluation_code == '=') - + sum(evaluation_code == '/') ) / n() ) -# Plot the heatmap for reception frequency with positivity values -ggplot(reception_rate, aes(x, y, fill = rate)) + -geom_tile() + -geom_text(aes(label = scales::percent(positivity)), color = "white", size = 4) + # Add positivity text -ggcourt("lower", labels = NULL) + -scale_fill_gradient2(name = "Rate: reception\nend location") + -ggtitle(paste("Reception Analysis for", ifelse(input$player == "All Players", input$team, input$player))) -} else if (input$skill == "Attack") { -# Attack analysis -# Attack analysis: Generate heatmap kernel density estimate for attack end coordinates -attack_data <- px %>% -filter(skill == "Attack", team == input$team) -if (input$player != "All Players") { -attack_data <- attack_data %>% -filter(player_name == input$player) -} -if (input$attack_description != "All Descriptions") { -attack_data <- attack_data %>% -filter(attack_description == input$attack_description) -} -# Generate KDE heatmap using attack end coordinates -hx <- ov_heatmap_kde(attack_data %>% dplyr::select(end_coordinate_x, end_coordinate_y), -resolution = "coordinates", court = "upper") -# Plot the heatmap for attack end locations -ggplot(hx, aes(x, y, fill = density)) + -scale_fill_distiller(palette = "Spectral", guide = "none") + -geom_raster() + -ggcourt(labels = NULL, court = "upper") + # Plot court over the heatmap -ggtitle(paste("Attack Heatmap for", ifelse(input$player == "All Players", input$team, input$player))) -} else if (input$skill == "Block") { -# Block analysis: Distribution of setters' positions or actions -block_data <- px %>% -filter(skill == "Block", team == input$team) -if (input$player != "All Players") { -block_data <- block_data %>% -filter(player_name == input$player) -} -# Plot setter distribution using discrete color scale -ggplot(block_data, aes(x = start_coordinate_x, y = start_coordinate_y, color = player_name)) + -geom_point(alpha = 0.7) + -scale_color_viridis_d() + # Use discrete color scale -ggcourt() + -ggtitle(paste("Block Distribution for", ifelse(input$player == "All Players", input$team, input$player))) +muro_table <- px %>% +filter(skill == "Block") %>% +group_by(player_name) %>% +summarize( +Muro_tot = n(), +Muro_positivo = sum(evaluation_code == '#' | evaluation_code == '+' | evaluation_code == '!') / n(), +Muro_negativo = sum(evaluation_code == '-' | evaluation_code == '=' | evaluation_code == '/') / n(), +) +punti_totali_table <- px %>% +filter(skill %in% c("Block", "Attack", "Serve")) %>% +group_by(player_name) %>% +summarize(Punti_tot = sum(evaluation_code == '#')) +errori_table <- px %>% +filter(skill %in% c("Block", "Attack", "Serve", "Reception", "Set")) %>% +group_by(player_name) %>% +summarize(Err = sum(evaluation_code == '=')) +efficiency_table <- punti_totali_table %>% +left_join(errori_table, by = "player_name") +efficiency_table <- efficiency_table %>% +mutate(Eff = Punti_tot - Err) +# Read and process SQ files for roles +sq_files <- list.files("Elenco Giocatori Squadra", pattern = "\\.sq$", full.names = TRUE) +#sq_files <- list.files("B1_SQ", pattern = "\\.sq$", full.names = TRUE) +# Initialize an empty list to store the dataframes +df_list <- list() +# Loop through each .sq file +for (file in sq_files) { +file_content <- readLines(file) +team_line <- strsplit(file_content[2], "\t")[[1]] #Team :-) +team_name <- team_line[2] +data_lines <- file_content[-c(1, 2)] +split_data <- strsplit(data_lines, "\t") +df <- do.call(rbind, lapply(split_data, function(x) c(x[3], x[9], x[10]))) +df <- as.data.frame(df, stringsAsFactors = FALSE) +colnames(df) <- c("Cognome", "Nome", "Ruolo") +df$player_name <- paste(df$Nome, df$Cognome) +df <- df %>% +mutate(role = case_when( +Ruolo == "1" ~ "Libero", +Ruolo == "2" ~ "Schiacciatore", +Ruolo == "3" ~ "Opposto", +Ruolo == "4" ~ "Centrale", +Ruolo == "5" ~ "Palleggiatore", +TRUE ~ "Unknown" +)) +# Add the team name column to each row +df$team <- team_name +# Add the processed dataframe to the list +df_list[[file]] <- df } +# Combine all dataframes into one +combined_role_df <- do.call(rbind, df_list) +# Select only the relevant columns +role <- combined_role_df %>% +select(player_name, role, team) +# Get all unique player names from all tables +all_players <- unique(c( +battuta_table$player_name, +ricezione_table$player_name, +attack_table$player_name, +muro_table$player_name, +efficiency_table$player_name +)) +# Create a base dataframe with all player names +final_table <- data.frame(player_name = all_players) +# Join all tables +final_table <- final_table %>% +left_join(battuta_table, by = "player_name") %>% +left_join(ricezione_table, by = "player_name") %>% +left_join(attack_table, by = "player_name") %>% +left_join(muro_table, by = "player_name") %>% +left_join(efficiency_table, by = "player_name") +#%>% left_join(role, by = "player_name") +final_table[is.na(final_table)] <- 0 +final_table <- final_table%>% left_join(role, by = "player_name") +# Identifying percentage columns based on naming patterns +percentage_columns <- grep( +pattern = "Positiv|Perfett|Efficienza|positivo|negativo|Ace", +x = names(final_table), +value = TRUE +) +# Convert the columns to numeric and then to percentages +final_table[percentage_columns] <- lapply(final_table[percentage_columns], function(x) { +x <- as.numeric(as.character(x)) # Convert to numeric +round(x * 100, 2) # Multiply by 100 and round to 2 decimal places }) -# Player stats table -output$player_stats <- renderTable({ -req(filtered_data()) -filtered_data() -}) +return(final_table) } -shinyApp(ui, server) -View(reception_rate4) -View(reception_rate4) +# Run the data processing +final_table <- process_data() +# Save the processed data +saveRDS(final_table, "final_table_ItalyA1.rds") +library(shiny) +library(DT) +# Load the pre-processed data +final_table <- readRDS("final_table_ItalyA1.rds") +# UI definition +ui <- fluidPage( +titlePanel("Player Statistics"), +selectInput("role_filter", "Filter by Role:", +choices = c("All", unique(final_table$role)), +selected = "All"), +DTOutput("table") +) +# Server definition server <- function(input, output, session) { -# Initial message when no team is selected -output$message <- renderText({ -if (is.null(input$team)|| length(input$team) == 0) { -return("Select a team to see statistics.") -} else { -return(NULL) +filtered_table <- reactive({ +filtered_data <- final_table +if (input$role_filter != "All") { +filtered_data <- filtered_data[filtered_data$role == input$role_filter, ] } +filtered_data <- filtered_data[complete.cases(filtered_data), ] +return(filtered_data) }) -# Dynamic player selection based on team -output$player_select <- renderUI({ -req(input$team, cancelOutput = TRUE) # Ensure team is selected -players <- final_table %>% -filter(team == input$team) %>% -pull(player_name) %>% -unique() -selectInput("player", "Select Player", choices = c("All Players", players)) -}) -# Dynamic attack description selection based on the selected team and skill -output$attack_description_select <- renderUI({ -req(input$team, input$skill) # Ensure a team and skill are selected -if (input$skill == "Attack") { -descriptions <- px %>% -filter(skill == "Attack", team == input$team, player_name==input$player) %>% -pull(attack_description) %>% -unique() -selectInput("attack_description", "Select Attack Description", choices = c("All Descriptions", descriptions)) -} else { -return(NULL) # Return NULL if skill is not Attack -} -}) -# Reactive data filtering based on selected team and player -filtered_data <- reactive({ -req(input$team) # Ensure the input exists -if (is.null(input$team) || length(input$team) == 0) return(NULL) # Check for NULL or zero-length input -data <- final_table %>% -filter(team == input$team) -if (input$player != "All Players" && !is.null(input$player) && input$player != "") { -data <- data %>% filter(player_name == input$player) -} -if (nrow(data) == 0) return(NULL) # Check for empty filtered data -data +output$table <- renderDT({ +datatable(filtered_table(), options = list(pageLength = 10, autoWidth = TRUE)) }) -# Plot generation -output$skill_plot <- renderPlot({ -req(input$skill, input$team, input$player, filtered_data()) -if(input$skill == "Serve") { -# Filter serves by the selected team and player -filtered_serves <- px %>% -filter(skill == "Serve", -team == input$team, -player_name == input$player) -filtered_serves <- filtered_serves %>% -filter(!is.na(start_zone), !is.na(end_zone)) -# Convert zones to coordinates (since we are using zones instead of coordinates) -filtered_serves <- convert_zones_to_coordinates(filtered_serves) -# Ensure that 'evaluation' contains only Ace, Error, or Other -filtered_serves <- filtered_serves %>% -mutate(evaluation = ifelse(evaluation %in% c("Ace", "Error"), evaluation, "Other")) -# Plot the serves with arrows and customized colors for Ace, Error, and Other -ggplot(filtered_serves, aes(x = start_coordinate_x, y = start_coordinate_y, -xend = end_coordinate_x, yend = end_coordinate_y, colour = evaluation)) + -geom_segment(arrow = arrow(length = unit(2, "mm"), type = "closed", angle = 20)) + -scale_colour_manual(values = c(Ace = "limegreen", Error = "firebrick", Other = "dodgerblue"), -name = "Evaluation") + -ggcourt(labels = c("Serving team", "Receiving team")) + -ggtitle(paste("Serve Analysis for", input$player, "from", input$team)) -} else if (input$skill == "Reception") { -# Reception analysis: Calculate positivity rate for each player in each zone -reception_rate <- px %>% -filter(skill == "Reception", team == input$team, !is.na(end_zone)) -if (input$player != "All Players") { -reception_rate <- reception_rate %>% -filter(player_name == input$player) } -# Calculate positivity and reception rate by zone -reception_rate <- reception_rate %>% -group_by(end_zone) %>% -summarize( -n_receptions = n(), -positivity = sum(evaluation_code %in% c('#', '+')) / n_receptions # Correct positivity calculation -) %>% -mutate(rate = n_receptions / sum(n_receptions)) %>% -ungroup() -all_zones <- tibble( -end_zone = 1:9, -x = c(3, 3, 2, 1, 1, 2, 1, 2, 3), # Predefined x-coordinates for each zone -y = c(1, 3, 3, 3, 1, 1, 2, 2, 2) # Predefined y-coordinates for each zone -) -# Add coordinates for the reception zones -#reception_rate <- cbind(reception_rate, dv_xy(reception_rate$end_zone, end = "lower")) -# Left join to ensure all zones 1-9 are present in the dataset -reception_rate <- left_join(all_zones, reception_rate, by = "end_zone") -# Fill missing values for `n_receptions`, `positivity`, and `rate` with zeros (or other defaults) -reception_rate <- reception_rate %>% -mutate( -n_receptions = replace_na(n_receptions, 0), -positivity = replace_na(positivity, 0), -rate = replace_na(rate, 0) +# Run the app +shinyApp(ui = ui, server = server) +# Load the pre-processed data +final_table <- readRDS("final_table_ItalyA1.rds") +# UI definition +ui <- fluidPage( +titlePanel("Player Statistics"), +selectInput("role_filter", "Filter by Role:", +choices = c("All", unique(final_table$role)), +selected = "All"), +DTOutput("table") ) -# Plot the heatmap for reception frequency with positivity values -ggplot(reception_rate, aes(x, y, fill = rate)) + -geom_tile() + -geom_text(aes(label = scales::percent(positivity)), color = "white", size = 4) + # Add positivity text -ggcourt("lower", labels = NULL) + -scale_fill_gradient2(name = "Rate: reception\nend location") + -ggtitle(paste("Reception Analysis for", ifelse(input$player == "All Players", input$team, input$player))) -} else if (input$skill == "Attack") { -# Attack analysis -# Attack analysis: Generate heatmap kernel density estimate for attack end coordinates -attack_data <- px %>% -filter(skill == "Attack", team == input$team) -if (input$player != "All Players") { -attack_data <- attack_data %>% -filter(player_name == input$player) -} -if (input$attack_description != "All Descriptions") { -attack_data <- attack_data %>% -filter(attack_description == input$attack_description) -} -# Generate KDE heatmap using attack end coordinates -hx <- ov_heatmap_kde(attack_data %>% dplyr::select(end_coordinate_x, end_coordinate_y), -resolution = "coordinates", court = "upper") -# Plot the heatmap for attack end locations -ggplot(hx, aes(x, y, fill = density)) + -scale_fill_distiller(palette = "Spectral", guide = "none") + -geom_raster() + -ggcourt(labels = NULL, court = "upper") + # Plot court over the heatmap -ggtitle(paste("Attack Heatmap for", ifelse(input$player == "All Players", input$team, input$player))) -} else if (input$skill == "Block") { -# Block analysis: Distribution of setters' positions or actions -block_data <- px %>% -filter(skill == "Block", team == input$team) -if (input$player != "All Players") { -block_data <- block_data %>% -filter(player_name == input$player) -} -# Plot setter distribution using discrete color scale -ggplot(block_data, aes(x = start_coordinate_x, y = start_coordinate_y, color = player_name)) + -geom_point(alpha = 0.7) + -scale_color_viridis_d() + # Use discrete color scale -ggcourt() + -ggtitle(paste("Block Distribution for", ifelse(input$player == "All Players", input$team, input$player))) +# Server definition +server <- function(input, output, session) { +filtered_table <- reactive({ +filtered_data <- final_table +if (input$role_filter != "All") { +filtered_data <- filtered_data[filtered_data$role == input$role_filter, ] } +filtered_data <- filtered_data[complete.cases(filtered_data), ] +return(filtered_data) }) -# Player stats table -output$player_stats <- renderTable({ -req(filtered_data()) -filtered_data() +output$table <- renderDT({ +datatable(filtered_table(), options = list(pageLength = 10, autoWidth = TRUE)) }) } -shinyApp(ui, server) -px$team.unique() -px$team -a<- unique(px$team) -a -FOCOL <- final_table %>% -filter(team == 'GS FO.CO.L. VOLLEY LEGNANO') -View(FOCOL) -shinyApp(ui, server) -FOCOL <- px %>% -filter(team == 'GS FO.CO.L. VOLLEY LEGNANO') -View(FOCOL) -shinyApp(ui, server) -reception_rate5 <- FOCOL %>% -filter(skill == "Reception", !is.na(end_zone)) -reception_rate6 <- reception_rate5 %>% -group_by(end_zone) %>% -summarize( -n_receptions = n(), -positivity = sum(evaluation_code %in% c('#', '+')) / n_receptions # Correct positivity calculation -) %>% -mutate(rate = n_receptions / sum(n_receptions)) %>% -ungroup() -all_zones <- tibble( -end_zone = 1:9, -x = c(3, 3, 2, 1, 1, 2, 1, 2, 3), # Predefined x-coordinates for each zone -y = c(1, 3, 3, 3, 1, 1, 2, 2, 2) # Predefined y-coordinates for each zone -) -reception_rate6 <- left_join(all_zones, reception_rate6, by = "end_zone") -reception_rate6 <- reception_rate6 %>% -mutate( -n_receptions = replace_na(n_receptions, 0), -positivity = replace_na(positivity, 0), -rate = replace_na(rate, 0) -) -View(reception_rate6) -View(reception_rate4) -ggplot(reception_rate6, aes(x, y, fill = rate)) + -geom_tile() + -geom_text(aes(label = scales::percent(positivity)), color = "white", size = 4) + # Add positivity text -ggcourt("lower", labels = NULL) + -scale_fill_gradient2(name = "Rate: reception\nend location") -library(shiny) -library(shinydashboard) -library(ggplot2) -library(dplyr) +# Run the app +shinyApp(ui = ui, server = server) +runApp() +# File: preprocess_data.R library(datavolley) -library(ovlytics) -library(DT) +library(dplyr) library(tidyr) -final_table <- readRDS("final_table_ItalyB1.rds") -#d <- list.files("Legnano/Scout/", pattern = "dvw$", full.names = TRUE) -d <- list.files("B1_Scout/", pattern = "dvw$", full.names = TRUE) +#TODO TO be changed with path you are using in your own PC! +setwd("C:/Users/mirko/Documents/GitHub/CuneoWebsite.io/All Statistics") +# Data processing function +process_data <- function() { +# Read and process DVW files +d <- list.files("Scout/", pattern = "dvw$", full.names = TRUE) +#d <- list.files("B1_Scout/", pattern = "dvw$", full.names = TRUE) lx <- list() # Read each file with error handling for (fi in seq_along(d)) { @@ -289,224 +187,326 @@ file.remove(temp_file) px <- list() for (fp in seq_along(lx)) px[[fp]] <- plays(lx[[fp]]) px <- do.call(rbind, px) -# Function to convert zones to coordinates -convert_zones_to_coordinates <- function(data) { -# Define fixed start coordinates based on start_zone -data <- data %>% -mutate( -start_coordinate_x = case_when( -start_zone == 5 ~ 1.08125, -start_zone == 6 ~ 2.10125, -start_zone == 1 ~ 3.18125, -TRUE ~ NA_real_ # Default to NA if no match -), -start_coordinate_y = case_when( -start_zone == 5 ~ 0.462963, -start_zone == 6 ~ 0.462963, -start_zone == 1 ~ 0.462963, -# Add other start zones here if needed -TRUE ~ NA_real_ -), -# Generate random end coordinates based on end_zone -end_coordinate_x = case_when( -end_zone == 1 ~ runif(n(), 0.7, 1.2), -end_zone == 6 ~ runif(n(), 1.5, 2.2), -end_zone == 5 ~ runif(n(), 2.3, 3), -TRUE ~ NA_real_ -), -end_coordinate_y = case_when( -end_zone %in% c(1, 6, 5) ~ runif(n(), 4.8, 5.8), -TRUE ~ NA_real_ +unique_player_names <- unique(px$player_name) +unique_player_names <- unique_player_names[!is.na(unique_player_names)] +battuta_table <- px %>% +filter(skill == "Serve") %>% +group_by(player_name) %>% +summarize( +Bat_Tot = n(), +Bat_Positiva = sum(evaluation_code == '#' | evaluation_code == '+' | evaluation_code == '!') / n(), +Bat_Perfetta = sum(((evaluation_code == '#')) / n()), ) +ricezione_table <- px %>% +filter(skill == "Reception") %>% +group_by(player_name) %>% +summarize( +Rice_Tot = n(), +Rice_Positiva = sum(evaluation_code == '#' | evaluation_code == '+') / n(), +Ace = sum(evaluation_code == '=') / n(), +Rice_Efficienza = (sum(evaluation_code == '#') + sum(evaluation_code == '+') - sum(evaluation_code == '=')-sum(evaluation_code == '/')) / n() ) -return(data) -} -# Check and prepare the data -if(!"team" %in% names(px)) { -stop("The 'team' column is missing from the data.") -} -if(!"player_name" %in% names(px)) { -if("player" %in% names(px)) { -final_table <- final_table %>% rename(player_name = player) -} else { -stop("Neither 'player_name' nor 'player' column found in the data.") -} -} -# Start Dashboard -ui <- dashboardPage( -dashboardHeader(title = "Volleyball Statistics"), -dashboardSidebar( -selectInput("team", "Select Team", choices = unique(final_table$team)), -selectInput("skill", "Select Skill", choices = c("Serve", "Reception", "Attack", "Block")), -uiOutput("player_select"), -conditionalPanel( -condition = "input.skill == 'Attack'", -uiOutput("attack_description_select") # Show only if skill is Attack +attack_table <- px %>% +filter(skill == "Attack") %>% +group_by(player_name) %>% +summarize( +Att_Tot = n(), +Att_Perfetto = sum(evaluation_code == '#') / n(), +Att_Efficienza = (sum(evaluation_code == '#') - sum(evaluation_code == '=') - + sum(evaluation_code == '/') ) / n() ) -), -dashboardBody( -#fluidRow( -# box(textOutput("message"))## Message to display when no team is selected -#), -fluidRow( -box(plotOutput("skill_plot"), width = 12) -), -fluidRow( -box(tableOutput("player_stats"), width = 12) +muro_table <- px %>% +filter(skill == "Block") %>% +group_by(player_name) %>% +summarize( +Muro_tot = n(), +Muro_positivo = sum(evaluation_code == '#' | evaluation_code == '+' | evaluation_code == '!') / n(), +Muro_negativo = sum(evaluation_code == '-' | evaluation_code == '=' | evaluation_code == '/') / n(), ) +punti_totali_table <- px %>% +filter(skill %in% c("Block", "Attack", "Serve")) %>% +group_by(player_name) %>% +summarize(Punti_tot = sum(evaluation_code == '#')) +errori_table <- px %>% +filter(skill %in% c("Block", "Attack", "Serve", "Reception", "Set")) %>% +group_by(player_name) %>% +summarize(Err = sum(evaluation_code == '=')) +efficiency_table <- punti_totali_table %>% +left_join(errori_table, by = "player_name") +efficiency_table <- efficiency_table %>% +mutate(Eff = Punti_tot - Err) +# Read and process SQ files for roles +sq_files <- list.files("Elenco Giocatori Squadra", pattern = "\\.sq$", full.names = TRUE) +#sq_files <- list.files("B1_SQ", pattern = "\\.sq$", full.names = TRUE) +# Initialize an empty list to store the dataframes +df_list <- list() +# Loop through each .sq file +for (file in sq_files) { +file_content <- readLines(file) +team_line <- strsplit(file_content[2], "\t")[[1]] #Team :-) +team_name <- team_line[2] +data_lines <- file_content[-c(1, 2)] +split_data <- strsplit(data_lines, "\t") +df <- do.call(rbind, lapply(split_data, function(x) c(x[3], x[9], x[10]))) +df <- as.data.frame(df, stringsAsFactors = FALSE) +colnames(df) <- c("Cognome", "Nome", "Ruolo") +df$player_name <- paste(df$Nome, df$Cognome) +df <- df %>% +mutate(role = case_when( +Ruolo == "1" ~ "Libero", +Ruolo == "2" ~ "Schiacciatore", +Ruolo == "3" ~ "Opposto", +Ruolo == "4" ~ "Centrale", +Ruolo == "5" ~ "Palleggiatore", +TRUE ~ "Unknown" +)) +# Add the team name column to each row +df$team <- team_name +# Add the processed dataframe to the list +df_list[[file]] <- df +} +# Combine all dataframes into one +combined_role_df <- do.call(rbind, df_list) +# Select only the relevant columns +role <- combined_role_df %>% +select(player_name, role, team) +# Get all unique player names from all tables +all_players <- unique(c( +battuta_table$player_name, +ricezione_table$player_name, +attack_table$player_name, +muro_table$player_name, +efficiency_table$player_name +)) +# Create a base dataframe with all player names +final_table <- data.frame(player_name = all_players) +# Join all tables +final_table <- final_table %>% +left_join(battuta_table, by = "player_name") %>% +left_join(ricezione_table, by = "player_name") %>% +left_join(attack_table, by = "player_name") %>% +left_join(muro_table, by = "player_name") %>% +left_join(efficiency_table, by = "player_name") +#%>% left_join(role, by = "player_name") +final_table[is.na(final_table)] <- 0 +final_table <- final_table%>% left_join(role, by = "player_name") +# Identifying percentage columns based on naming patterns +percentage_columns <- grep( +pattern = "Positiv|Perfett|Efficienza|positivo|negativo|Ace", +x = names(final_table), +value = TRUE ) +# Convert the columns to numeric and then to percentages +final_table[percentage_columns] <- lapply(final_table[percentage_columns], function(x) { +x <- as.numeric(as.character(x)) # Convert to numeric +round(x * 100, 2) # Multiply by 100 and round to 2 decimal places +}) +return(final_table) +} +# Run the data processing +final_table <- process_data() +# Save the processed data +saveRDS(final_table, "final_table_Germany.rds") +# Load the pre-processed data +final_table <- readRDS("final_table_Germany.rds") +# UI definition +ui <- fluidPage( +titlePanel("Player Statistics"), +selectInput("role_filter", "Filter by Role:", +choices = c("All", unique(final_table$role)), +selected = "All"), +DTOutput("table") ) +# Server definition server <- function(input, output, session) { -# Initial message when no team is selected -output$message <- renderText({ -if (is.null(input$team)|| length(input$team) == 0) { -return("Select a team to see statistics.") -} else { -return(NULL) +filtered_table <- reactive({ +filtered_data <- final_table +if (input$role_filter != "All") { +filtered_data <- filtered_data[filtered_data$role == input$role_filter, ] } +filtered_data <- filtered_data[complete.cases(filtered_data), ] +return(filtered_data) }) -# Dynamic player selection based on team -output$player_select <- renderUI({ -req(input$team, cancelOutput = TRUE) # Ensure team is selected -players <- final_table %>% -filter(team == input$team) %>% -pull(player_name) %>% -unique() -selectInput("player", "Select Player", choices = c("All Players", players)) +output$table <- renderDT({ +datatable(filtered_table(), options = list(pageLength = 10, autoWidth = TRUE)) }) -# Dynamic attack description selection based on the selected team and skill -output$attack_description_select <- renderUI({ -req(input$team, input$skill) # Ensure a team and skill are selected -if (input$skill == "Attack") { -descriptions <- px %>% -filter(skill == "Attack", team == input$team, player_name==input$player) %>% -pull(attack_description) %>% -unique() -selectInput("attack_description", "Select Attack Description", choices = c("All Descriptions", descriptions)) -} else { -return(NULL) # Return NULL if skill is not Attack } -}) -# Reactive data filtering based on selected team and player -filtered_data <- reactive({ -req(input$team) # Ensure the input exists -if (is.null(input$team) || length(input$team) == 0) return(NULL) # Check for NULL or zero-length input -data <- final_table %>% -filter(team == input$team) -if (input$player != "All Players" && !is.null(input$player) && input$player != "") { -data <- data %>% filter(player_name == input$player) +# Run the app +shinyApp(ui = ui, server = server) +#TODO TO be changed with path you are using in your own PC! +setwd("C:/Users/mirko/Documents/GitHub/CuneoWebsite.io/All Statistics") +# Data processing function +process_data <- function() { +# Read and process DVW files +d <- list.files("Scout/", pattern = "dvw$", full.names = TRUE) +#d <- list.files("B1_Scout/", pattern = "dvw$", full.names = TRUE) +lx <- list() +# Read each file with error handling +for (fi in seq_along(d)) { +tryCatch({ +lx[[fi]] <- dv_read(d[fi], insert_technical_timeouts = FALSE) +}, error = function(e) { +message("Error reading file ", d[fi], ": ", e$message) +# If the error is specifically about the [3SCOUT] section, try to read without it +if (grepl("\\[3SCOUT\\]", e$message)) { +message("Attempting to read file without [3SCOUT] section") +file_content <- readLines(d[fi]) +scout_index <- grep("\\[3SCOUT\\]", file_content) +if (length(scout_index) > 0) { +file_content <- file_content[1:(scout_index-1)] +temp_file <- tempfile(fileext = ".dvw") +writeLines(file_content, temp_file) +lx[[fi]] <- dv_read(temp_file, insert_technical_timeouts = FALSE) +file.remove(temp_file) +} } -if (nrow(data) == 0) return(NULL) # Check for empty filtered data -data }) -# Plot generation -output$skill_plot <- renderPlot({ -req(input$skill, input$team, input$player, filtered_data()) -if(input$skill == "Serve") { -# Filter serves by the selected team and player -filtered_serves <- px %>% -filter(skill == "Serve", -team == input$team, -player_name == input$player) -filtered_serves <- filtered_serves %>% -filter(!is.na(start_zone), !is.na(end_zone)) -# Convert zones to coordinates (since we are using zones instead of coordinates) -filtered_serves <- convert_zones_to_coordinates(filtered_serves) -# Ensure that 'evaluation' contains only Ace, Error, or Other -filtered_serves <- filtered_serves %>% -mutate(evaluation = ifelse(evaluation %in% c("Ace", "Error"), evaluation, "Other")) -# Plot the serves with arrows and customized colors for Ace, Error, and Other -ggplot(filtered_serves, aes(x = start_coordinate_x, y = start_coordinate_y, -xend = end_coordinate_x, yend = end_coordinate_y, colour = evaluation)) + -geom_segment(arrow = arrow(length = unit(2, "mm"), type = "closed", angle = 20)) + -scale_colour_manual(values = c(Ace = "limegreen", Error = "firebrick", Other = "dodgerblue"), -name = "Evaluation") + -ggcourt(labels = c("Serving team", "Receiving team")) + -ggtitle(paste("Serve Analysis for", input$player, "from", input$team)) -} else if (input$skill == "Reception") { -# Reception analysis: Calculate positivity rate for each player in each zone -reception_rate <- px %>% -filter(skill == "Reception", team == input$team, !is.na(end_zone)) -if (input$player != "All Players") { -reception_rate <- reception_rate %>% -filter(player_name == input$player) } -# Calculate positivity and reception rate by zone -reception_rate <- reception_rate %>% -group_by(end_zone) %>% +## now extract the play-by-play component from each and bind them together +px <- list() +for (fp in seq_along(lx)) px[[fp]] <- plays(lx[[fp]]) +px <- do.call(rbind, px) +unique_player_names <- unique(px$player_name) +unique_player_names <- unique_player_names[!is.na(unique_player_names)] +battuta_table <- px %>% +filter(skill == "Serve") %>% +group_by(player_name) %>% +summarize( +Bat_Tot = n(), +Bat_Positiva = sum(evaluation_code == '#' | evaluation_code == '+' | evaluation_code == '!') / n(), +Bat_Perfetta = sum(((evaluation_code == '#')) / n()), +) +ricezione_table <- px %>% +filter(skill == "Reception") %>% +group_by(player_name) %>% summarize( -n_receptions = n(), -positivity = sum(evaluation_code %in% c('#', '+')) / n_receptions # Correct positivity calculation -) %>% -mutate(rate = n_receptions / sum(n_receptions)) %>% -ungroup() -all_zones <- tibble( -end_zone = 1:9, -x = c(3, 3, 2, 1, 1, 2, 1, 2, 3), # Predefined x-coordinates for each zone -y = c(1, 3, 3, 3, 1, 1, 2, 2, 2) # Predefined y-coordinates for each zone +Rice_Tot = n(), +Rice_Positiva = sum(evaluation_code == '#' | evaluation_code == '+') / n(), +Ace = sum(evaluation_code == '=') / n(), +Rice_Efficienza = (sum(evaluation_code == '#') + sum(evaluation_code == '+') - sum(evaluation_code == '=')-sum(evaluation_code == '/')) / n() ) -# Add coordinates for the reception zones -#reception_rate <- cbind(reception_rate, dv_xy(reception_rate$end_zone, end = "lower")) -# Left join to ensure all zones 1-9 are present in the dataset -reception_rate <- left_join(all_zones, reception_rate, by = "end_zone") -# Fill missing values for `n_receptions`, `positivity`, and `rate` with zeros (or other defaults) -reception_rate <- reception_rate %>% -mutate( -n_receptions = replace_na(n_receptions, 0), -positivity = replace_na(positivity, 0), -rate = replace_na(rate, 0) +attack_table <- px %>% +filter(skill == "Attack") %>% +group_by(player_name) %>% +summarize( +Att_Tot = n(), +Att_Perfetto = sum(evaluation_code == '#') / n(), +Att_Efficienza = (sum(evaluation_code == '#') - sum(evaluation_code == '=') - + sum(evaluation_code == '/') ) / n() ) -# Plot the heatmap for reception frequency with positivity values -ggplot(reception_rate, aes(x, y, fill = rate)) + -geom_tile() + -geom_text(aes(label = scales::percent(positivity)), color = "white", size = 4) + # Add positivity text -ggcourt("lower", labels = NULL) + -scale_fill_gradient2(name = "Rate: reception\nend location") + -ggtitle(paste("Reception Analysis for", ifelse(input$player == "All Players", input$team, input$player))) -} else if (input$skill == "Attack") { -# Attack analysis -# Attack analysis: Generate heatmap kernel density estimate for attack end coordinates -attack_data <- px %>% -filter(skill == "Attack", team == input$team) -if (input$player != "All Players") { -attack_data <- attack_data %>% -filter(player_name == input$player) -} -if (input$attack_description != "All Descriptions") { -attack_data <- attack_data %>% -filter(attack_description == input$attack_description) +muro_table <- px %>% +filter(skill == "Block") %>% +group_by(player_name) %>% +summarize( +Muro_tot = n(), +Muro_positivo = sum(evaluation_code == '#' | evaluation_code == '+' | evaluation_code == '!') / n(), +Muro_negativo = sum(evaluation_code == '-' | evaluation_code == '=' | evaluation_code == '/') / n(), +) +punti_totali_table <- px %>% +filter(skill %in% c("Block", "Attack", "Serve")) %>% +group_by(player_name) %>% +summarize(Punti_tot = sum(evaluation_code == '#')) +errori_table <- px %>% +filter(skill %in% c("Block", "Attack", "Serve", "Reception", "Set")) %>% +group_by(player_name) %>% +summarize(Err = sum(evaluation_code == '=')) +efficiency_table <- punti_totali_table %>% +left_join(errori_table, by = "player_name") +efficiency_table <- efficiency_table %>% +mutate(Eff = Punti_tot - Err) +# Read and process SQ files for roles +sq_files <- list.files("Elenco Giocatori Squadra", pattern = "\\.sq$", full.names = TRUE) +#sq_files <- list.files("B1_SQ", pattern = "\\.sq$", full.names = TRUE) +# Initialize an empty list to store the dataframes +df_list <- list() +# Loop through each .sq file +for (file in sq_files) { +file_content <- readLines(file) +team_line <- strsplit(file_content[2], "\t")[[1]] #Team :-) +team_name <- team_line[2] +data_lines <- file_content[-c(1, 2)] +split_data <- strsplit(data_lines, "\t") +df <- do.call(rbind, lapply(split_data, function(x) c(x[3], x[9], x[10]))) +df <- as.data.frame(df, stringsAsFactors = FALSE) +colnames(df) <- c("Cognome", "Nome", "Ruolo") +df$player_name <- paste(df$Nome, df$Cognome) +df <- df %>% +mutate(role = case_when( +Ruolo == "1" ~ "Libero", +Ruolo == "2" ~ "Schiacciatore", +Ruolo == "3" ~ "Opposto", +Ruolo == "4" ~ "Centrale", +Ruolo == "5" ~ "Palleggiatore", +TRUE ~ "Unknown" +)) +# Add the team name column to each row +df$team <- team_name +# Add the processed dataframe to the list +df_list[[file]] <- df } -# Generate KDE heatmap using attack end coordinates -hx <- ov_heatmap_kde(attack_data %>% dplyr::select(end_coordinate_x, end_coordinate_y), -resolution = "coordinates", court = "upper") -# Plot the heatmap for attack end locations -ggplot(hx, aes(x, y, fill = density)) + -scale_fill_distiller(palette = "Spectral", guide = "none") + -geom_raster() + -ggcourt(labels = NULL, court = "upper") + # Plot court over the heatmap -ggtitle(paste("Attack Heatmap for", ifelse(input$player == "All Players", input$team, input$player))) -} else if (input$skill == "Block") { -# Block analysis: Distribution of setters' positions or actions -block_data <- px %>% -filter(skill == "Block", team == input$team) -if (input$player != "All Players") { -block_data <- block_data %>% -filter(player_name == input$player) +# Combine all dataframes into one +combined_role_df <- do.call(rbind, df_list) +# Select only the relevant columns +role <- combined_role_df %>% +select(player_name, role, team) +# Get all unique player names from all tables +all_players <- unique(c( +battuta_table$player_name, +ricezione_table$player_name, +attack_table$player_name, +muro_table$player_name, +efficiency_table$player_name +)) +# Create a base dataframe with all player names +final_table <- data.frame(player_name = all_players) +# Join all tables +final_table <- final_table %>% +left_join(battuta_table, by = "player_name") %>% +left_join(ricezione_table, by = "player_name") %>% +left_join(attack_table, by = "player_name") %>% +left_join(muro_table, by = "player_name") %>% +left_join(efficiency_table, by = "player_name") +#%>% left_join(role, by = "player_name") +final_table[is.na(final_table)] <- 0 +final_table <- final_table%>% left_join(role, by = "player_name") +# Identifying percentage columns based on naming patterns +percentage_columns <- grep( +pattern = "Positiv|Perfett|Efficienza|positivo|negativo|Ace", +x = names(final_table), +value = TRUE +) +# Convert the columns to numeric and then to percentages +final_table[percentage_columns] <- lapply(final_table[percentage_columns], function(x) { +x <- as.numeric(as.character(x)) # Convert to numeric +round(x * 100, 2) # Multiply by 100 and round to 2 decimal places +}) +return(final_table) } -# Plot setter distribution using discrete color scale -ggplot(block_data, aes(x = start_coordinate_x, y = start_coordinate_y, color = player_name)) + -geom_point(alpha = 0.7) + -scale_color_viridis_d() + # Use discrete color scale -ggcourt() + -ggtitle(paste("Block Distribution for", ifelse(input$player == "All Players", input$team, input$player))) +# Run the data processing +final_table <- process_data() +# Save the processed data +saveRDS(final_table, "final_table_France.rds") +# Load the pre-processed data +final_table <- readRDS("final_table_France.rds") +# UI definition +ui <- fluidPage( +titlePanel("Player Statistics"), +selectInput("role_filter", "Filter by Role:", +choices = c("All", unique(final_table$role)), +selected = "All"), +DTOutput("table") +) +# Server definition +server <- function(input, output, session) { +filtered_table <- reactive({ +filtered_data <- final_table +if (input$role_filter != "All") { +filtered_data <- filtered_data[filtered_data$role == input$role_filter, ] } +filtered_data <- filtered_data[complete.cases(filtered_data), ] +return(filtered_data) }) -# Player stats table -output$player_stats <- renderTable({ -req(filtered_data()) -filtered_data() +output$table <- renderDT({ +datatable(filtered_table(), options = list(pageLength = 10, autoWidth = TRUE)) }) } -shinyApp(ui, server) -shinyApp(ui, server) -shinyApp(ui, server) +# Run the app +shinyApp(ui = ui, server = server) diff --git a/Scoreboard/VolleyballScoreboardServer.py b/Scoreboard/VolleyballScoreboardServer.py index 36b3958..ff962e1 100644 --- a/Scoreboard/VolleyballScoreboardServer.py +++ b/Scoreboard/VolleyballScoreboardServer.py @@ -134,10 +134,11 @@ def get_team_summary(team): # Include current set, past set scores, and stats for the dashboard summary_data.append({ 'current_set': current_set, - 'team_set_wins': f"{home_team}: {home_set_wins} - {away_set_wins} {away_team}", # Set wins string + #'team_set_wins': f"{home_team}: {home_set_wins} - {away_set_wins} {away_team}", # Set wins string + 'team_set_wins': f"{home_set_wins} - {away_set_wins}", # Set wins string 'timeouts': current_timeouts, - 'substitutions': current_substitutions, - 'video_checks': current_video_checks, + #'substitutions': current_substitutions, + #'video_checks': current_video_checks, 'past_set_scores': set_scores, 'set_score': f"{final_home_score} - {final_away_score}" }) diff --git a/Scoreboard/__pycache__/VolleyballScoreboardServer.cpython-312.pyc b/Scoreboard/__pycache__/VolleyballScoreboardServer.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9c0165531411fa2203ffd461a692c42e17abe16f GIT binary patch literal 7119 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 H$Uyo(ee}(~ literal 0 HcmV?d00001 diff --git a/Scoreboard/__pycache__/refresh_app.cpython-312.pyc b/Scoreboard/__pycache__/refresh_app.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e93e5566991d1144047886ebeb012fb9baf6c2a0 GIT binary patch literal 2573 zcmZuzTTdHD6rS~t&4O=)*j#d}1LWeU#ko{fgQ}=4X~;!5l~7v6LRy*K0oGmbIRTQXB~t%F)7QR$>J+pEDN@siyrB?PQJ#8c)@y^yL7thNb7tnu zcfNCm-=ons1Tu8Lck-hkLVxoQU*KEFrU~5xq$6E2QBlfC5+4W5KvB-gVl11u7|aF5 z7@MJ72=qa-rKsc-m<#1v^_E;)01YEuSwMO#k)O!k*153Wwt#XGJq&GBk3bun#p&oP zu1-s?a(Ub#bed2Ycb+p$GCJ!pQv70?SnPb>(oLah`I6ciahxnz$@x-gZg6NYa|ISU znW90zu`_3Ftz3j9PG;C(=gZ@np|VBnugSP$Ffw4+nNiKAWZceEeGU)k(=(dqp4SFy zADjI!xCKZMl#_In3*1H%k}fSMIav>Y=lC|dF6DxwC@s6up{cwzNoqc7{uX>Ees%ua zJPyr|(gVbx_hd0JkG==kpVYo3G%w8~wZ3{5Fv-I1L8n|I)Kyf~uneZEZaXCo%TuOK zlr4=JwgpDmA?5^)z*cUns%mE5aa7geC#O=CcNc~R#{@)U0-~`xL}MOAV@(i^H6R+J zWCG5cQUU6L(kuFL0A4DR7G>VSJ^KCz99GG;66%0d;rY=_mAaz_$mqQK8je(dZk|m_-nQ zKz1&1mN23*qNg+w=9FO#rqV%Iaj2%2@@&dgh^0F-28?18KwFhuXT)+KFmA9!4U37m zqysbx$GAbl`Rwp8r&2V|r^2?S7H!>_Fo-_avmM(lDsJlxLQ_Gj;r6>{7SF7NdzZt# z56@J?C)XmomJ&xFC4V{a_`uT9kC!@5S0jU~k@l6y!R5%o=aIgRAc~x#?XWx@;$*V& zMFM1MQ~A+l@LF{vs(P)QH$9DrH0|R4MQe=Gh-s>7TB0%T2?zdDaRI8pP&B;0IQ*J# z7totPAb8?mMGp3hQ-xjwRb6;oMf7=YKxWh;iBO@Q19e-7>C-K-gUHC)b5H?-IvFod z0=6MAb!A$%9wYBKfovO0s9sT_DX>Vu&*9z!L2=8i;hV$v4*iL{#fqJMdj!T?vz8uh zWbjVRZ187`onKsGFJHFU)p^S}jzXSFK2P&oDn@}PSd%pN5o`rWLQoqf34M4EzQ-pxM?7N2{7O@U@Cax)Bb-yvRM0;y$u*^O zMM*6ysjAW|#(S5Qy;WuZT5{jRi`8VNl3l|cE4X_ZcR$B_{*N>FKycuFh(3RI;J*A@ zNaJp-jv%XZ!$;@_YGl5@;s&n(J7o7fV&Vzp+Y3h3&f>MVzOcZ(B}{!F89oNyIccm`dUuc8_5J;%988nzH?4EztcSQMaE zO0+U{jA`@Py?~V?H%Gp` z?C_{Oi+6o?7(F}O^Lar2O_HEZ$K4JAif&I1`2IRn;O3#l6<+D47`AZZ#j?qaT51{A zq?b$sT(##b+cW{bylFNb(oylRji^)f1F)jJ7J1Q0@sS7?2dFs-l~23^eTJR}EiaPd z5rBJ1(mFb_j!v$lzQ0g-9UXhok*&m6cXd@_Z?GcuFC~t>LD0WWNK()1P)ORl*&de? HA`AZmH5@Cd literal 0 HcmV?d00001 diff --git a/Scoreboard/main_app.py b/Scoreboard/main_app.py new file mode 100644 index 0000000..127d8b1 --- /dev/null +++ b/Scoreboard/main_app.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +""" +Created on Wed Oct 30 21:11:36 2024 + +@author: mirko +""" + +import multiprocessing +from refresh_app import start_observer + +# Set start method as early as possible +multiprocessing.set_start_method('spawn', force=True) + + + +if __name__ == '__main__': + start_observer() diff --git a/Scoreboard/refresh_app.py b/Scoreboard/refresh_app.py new file mode 100644 index 0000000..064d823 --- /dev/null +++ b/Scoreboard/refresh_app.py @@ -0,0 +1,46 @@ +from watchdog.observers import Observer +from watchdog.events import FileSystemEventHandler +from multiprocessing import Process +import time +from VolleyballScoreboardServer import app + +watch_directory = "C:/Users/mirko/Documents/GitHub/CuneoWebsite.io/Scoreboard" +file_extension = ".dvw" + +class ChangeHandler(FileSystemEventHandler): + def __init__(self, restart_function): + super().__init__() + self.restart_function = restart_function + + def on_modified(self, event): + if event.src_path.endswith(file_extension): + print(f"Detected change in: {event.src_path}") + self.restart_function() + +def run_server(): + app.run(debug=False) + +def restart_server(): + global server_process + if server_process: + server_process.terminate() + server_process.join() + server_process = Process(target=run_server) + server_process.start() + +def start_observer(): + global server_process + server_process = None + restart_server() + + event_handler = ChangeHandler(restart_server) + observer = Observer() + observer.schedule(event_handler, path=watch_directory, recursive=False) + observer.start() + + try: + while True: + time.sleep(2) + except KeyboardInterrupt: + observer.stop() + observer.join() diff --git a/Scoreboard/templates/scoreboard.html b/Scoreboard/templates/scoreboard.html index b6edcee..7fb13e1 100644 --- a/Scoreboard/templates/scoreboard.html +++ b/Scoreboard/templates/scoreboard.html @@ -23,52 +23,68 @@ } .team-panel { - padding: 2rem; + padding: 1rem; display: flex; flex-direction: column; - gap: 1rem; - background-color: rgba(0, 0, 0, 0.3); + gap: 0.5rem; + background-color: rgba(0, 0, 0, 0.3); } .center-panel { display: flex; flex-direction: column; align-items: center; - justify-content: center; - padding: 2rem; + padding: 1rem; text-align: center; + gap: 1rem; } - .set-score { - font-size: 5rem; + .set-count { + font-size: 5rem; /* Very Big font */ font-weight: bold; - margin-bottom: 2rem; } - - .set-info { - font-size: 2rem; - margin-bottom: 2rem; + .team-boxes { + display: flex; + justify-content: center; + gap: 1.5rem; /* Space between the two boxes */ + margin-bottom: 0.75rem; } - .past-sets { - display: flex; - flex-direction: column; - gap: 1rem; + .team-box { + background-color: rgba(0, 0, 0, 0.4); + padding: 0.5rem 1rem; + border-radius: 15px; + width: 300px; + text-align: center; } + .team-names { + font-size: 2rem; /* Big font */ + font-weight: bold; + margin-bottom: 1rem; + } + .current-score { + font-size: 5rem; /* Big font */ + font-weight: bold; + } + .current-set { + font-size: 2rem; /* Normal font */ + } + .past-sets-title { + font-size: 3rem; /* Big font */ + } .past-set-result { - font-size: 1.5rem; - padding: 0.5rem 2rem; - background-color: rgba(255, 255, 255, 0.1); - border-radius: 8px; + font-size: 3.5rem; /* Big font */ } .player { - background-color: rgba(255, 255, 255, 0.1); + background-color: rgba(0, 0, 0, 0.3); padding: 1rem; margin: 0.5rem 0; border-radius: 8px; - font-size: 1.1rem; + font-size: 1.25rem; + border: 1px solid rgba(255, 255, 255, 0.1); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); } .team-name { @@ -76,6 +92,9 @@ font-weight: bold; margin-bottom: 1rem; text-align: center; + background-color: rgba(0, 0, 0, 0.4); + padding: 0.5rem; + border-radius: 8px; } .total { @@ -83,9 +102,16 @@ margin-top: 1rem; text-align: center; padding: 1rem; - background-color: rgba(255, 255, 255, 0.2); + background-color: rgba(0, 0, 0, 0.4); border-radius: 8px; } + + .past-sets { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + } @@ -107,15 +133,40 @@
-
+ +
+ {{ summary_data[-1].team_set_wins }} +
+ +
+ +
+
+ {{ summary_data[0].team_name }} +
+
+ + +
+
+ {{ summary_data[1].team_name }} +
+
+
+ + +
{{ summary_data[-1].set_score }}
-
- Set {{ summary_data[-1].current_set }}
- {{ summary_data[-1].team_set_wins }} + + +
+ Set {{ summary_data[-1].current_set }}
+ +
-

Previous Sets

+
Set precedenti
{% for score in summary_data[-1].past_set_scores %}
{{ score }}
{% endfor %}