From 3a219d38c62c7521530e4bf037ca8f3244c604d8 Mon Sep 17 00:00:00 2001 From: schlenther Date: Thu, 5 Sep 2024 16:15:47 +0200 Subject: [PATCH 1/4] further improvements in R summary plotting scripts --- src/main/R/drtAnalysis/plotRunSummaries.R | 67 ++++++++++--- .../drtAnalysis/readValuesFromRunSummaries.R | 20 +++- ...sFromRunSummaries_v3.0.1-fareExperiments.R | 98 ++++++++++++++----- 3 files changed, 144 insertions(+), 41 deletions(-) diff --git a/src/main/R/drtAnalysis/plotRunSummaries.R b/src/main/R/drtAnalysis/plotRunSummaries.R index ef53500..485050d 100644 --- a/src/main/R/drtAnalysis/plotRunSummaries.R +++ b/src/main/R/drtAnalysis/plotRunSummaries.R @@ -4,18 +4,33 @@ library(tidyr) mainDir <- "E:/matsim-kelheim/v3.1.1/output-KEXI-2.45-AV--0.0/" -transposed_result <- read.csv(paste(mainDir, "results-deutsch.csv", sep=""), check.names = FALSE) +transposed_result <- read.csv(paste(mainDir, "results-deutsch.csv", sep=""), check.names = FALSE, sep =";") #names(transposed_result) <- make.names(names(transposed_result), unique = TRUE, allow_ = FALSE) # Betriebszeiten umschreiben -transposed_result$Betriebszeiten <- ifelse(transposed_result$Betriebszeiten == TRUE, "0h - 24h", "9h - 16h") +transposed_result$Betriebszeiten <- factor(ifelse(transposed_result$Betriebszeiten == TRUE, "0h - 24h", "9h - 16h"), + levels = c("9h - 16h", "0h - 24h")) + + +### +#in Realität haben wir eine avg gruppengr0eße von 1.7 gemessen, diese aber nicht simuliert. +# wir rechnen die jetzt im nachhinein wieder drauf. +## --> machen wir jetzt im skript, dass die daten rausschreibt (readValuesFromRunSammaries.R) + +#transposed_result <- transposed_result %>% +# mutate(`Anzahl Passagiere` = `Bediente Anfragen` * 1.7, +# `Gesamt Passagierkilometer [km]` = `Gesamt Passagierkilometer [km]` * 1.7) %>% +# mutate(`Passagiere pro Fahrzeug` = `Anzahl Passagiere` / Fahrzeuge, +# `Passagiere pro Fahrzeugkilometer` = `Anzahl Passagiere` / `Summe Fahrzeugkilometer [km]`, +# `Passagiere pro Fahrzeugstunde` = `Anzahl Passagiere` / `Summe Fzg.-Betriebsstunden`, +# `Besetzungsgrad [pax-km/v-km]` = `Gesamt Passagierkilometer [km]` / `Summe Fahrzeugkilometer [km]`)# results <- transposed_result %>% gather(key = "parameter", value = "mean", -Geschwindigkeit, -Bediengebiet, -Flottengroeße, -Intermodal, -Betriebszeiten) -plotByConfiguration <- function(parameterStr){ +plotByConfiguration <- function(parameterStr, scales = "free"){ # Filtern der Daten für die gewünschten Parameter plot_data <- results %>% @@ -24,7 +39,7 @@ plotByConfiguration <- function(parameterStr){ # Funktion zum Anpassen der Facet-Labels label_function <- function(value) { - paste(value, "m/s") + paste(round(as.numeric(value) * 3.6, 0), "km/h") } # Erstellen des Facet-Plots @@ -35,9 +50,9 @@ plotByConfiguration <- function(parameterStr){ ) + facet_wrap(~ Geschwindigkeit, labeller = labeller(Geschwindigkeit = label_function) - ,scales = "free" + ,scales = scales ) + - labs(title = paste(parameterStr, "nach Geschwindigkeit, Flottengröße, Bediengebiet und Betriebszeiten"), + labs(title = paste(parameterStr, "nach Geschwindigkeit, Flottengröße,\nBediengebiet und Betriebszeiten"), x = "Flottengröße", y = parameterStr, color = "Bediengebiet", @@ -57,11 +72,39 @@ plotByConfiguration <- function(parameterStr){ } +save <- function(fileName){ + ggsave(filename = paste(mainDir, "plots/", fileName, ".png", sep = ""), + dpi = 600, width = 32, height = 18, units = "cm") +} + unique(results$parameter) + +###nachfrage plotByConfiguration("Bediente Anfragen") -plotByConfiguration("Mittl. Wartezeit") -plotByConfiguration("Avg. ride distance [km]") -plotByConfiguration("Empty ratio") -plotByConfiguration("Total vehicle mileage [km]") -plotByConfiguration("Avg. fare [MoneyUnit]" ) -plotByConfiguration("Pax per veh-km") \ No newline at end of file +save("bedienteAnfragen") +plotByConfiguration("Anzahl Passagiere", "fixed") +save("passagiere") +plotByConfiguration("Mittl. Wartezeit [s]", "fixed") +save("wartezeit") +plotByConfiguration("Umwegfaktor", "fixed") +plotByConfiguration("Mittl. Reiseweite [km]", "fixed") +save("reiseweite") +plotByConfiguration("Anteil Leerkilometer", "fixed") +save("leerkilometer") + + +###betrieb +plotByConfiguration("Summe Fahrzeugkilometer [km]") +save("fahrzeugkilometer") +plotByConfiguration("Besetzungsgrad [pax-km/v-km]") +save("besetzungsgrad") +plotByConfiguration("Passagiere pro Fahrzeugkilometer", "fixed") +save("paxPerKM") +plotByConfiguration("Passagiere pro Fahrzeugstunde") +plotByConfiguration("Passagiere pro Fahrzeugstunde", "fixed") +x^xsave("paxPerVehHour") + + +plotByConfiguration("Gesamt Passagierkilometer [km]") +plotByConfiguration("Gesamt Passagierkilometer [km]", "fixed") + diff --git a/src/main/R/drtAnalysis/readValuesFromRunSummaries.R b/src/main/R/drtAnalysis/readValuesFromRunSummaries.R index 778a570..348f80a 100644 --- a/src/main/R/drtAnalysis/readValuesFromRunSummaries.R +++ b/src/main/R/drtAnalysis/readValuesFromRunSummaries.R @@ -24,6 +24,7 @@ extract_parameters <- function(folder_name, speed) { # Funktion zum Einlesen der CSV-Datei und Extrahieren der "mean"-Werte read_stats <- function(folder_path, file_name) { + print(paste("reading", folder_path)) csv_path <- file.path(folder_path, "analysis/drt-drt-av", file_name) if (file.exists(csv_path)) { @@ -99,12 +100,26 @@ for (speed in speeds) { results <- bind_rows(results) - +##### # Transponiere die Tabelle, um Parameter als Spalten zu setzen transposed_result <- results %>% select(speed, area, fleetSize, intermodal, allDay, parameter, mean) %>% spread(key = parameter, value = mean) +### +#in Realität haben wir eine avg gruppengr0eße von 1.7 gemessen, diese aber nicht simuliert. +# wir rechnen die jetzt im nachhinein wieder drauf. +transposed_result <- transposed_result %>% + mutate(`Passengers (Pax)` = `Handled Requests` * 1.7, + `Total pax distance [km]` = `Total pax distance [km]` * 1.7) %>% + mutate(`Pax per veh` = `Passengers (Pax)` / Vehicles, + `Pax per veh-km` = `Passengers (Pax)` / `Total vehicle mileage [km]`, + `Pax per veh-h` = `Passengers (Pax)` / `Total service hours`, + `Occupancy rate [pax-km/v-km]` = `Total pax distance [km]` / `Total vehicle mileage [km]`) + +#transponiere zurück +results <- transposed_result %>% + gather(key = "parameter", value = "mean", -speed, -area, -fleetSize, -intermodal, -allDay) # Ergebnisse ausgeben print(results) @@ -112,7 +127,6 @@ print(transposed_result) write_csv(transposed_result, paste(mainDir, "results.csv", sep="")) - ##################################################################### ######PLOTS#### @@ -138,6 +152,7 @@ plotByConfiguration <- function(parameterStr){ labeller = labeller(speed = label_function) ,scales = "free" ) + + geom_text(aes(label = fleetSize), vjust = -1, hjust = 0.5, size = 3, color = "black") + labs(title = paste(parameterStr, "by Fleet Size, Speed, Area and Service Hours"), x = "Fleet Size", y = parameterStr, @@ -213,4 +228,5 @@ plotByConfiguration("Pax per veh-km") # Plot anzeigen print(facet_plot) + \ No newline at end of file diff --git a/src/main/R/drtAnalysis/readValuesFromRunSummaries_v3.0.1-fareExperiments.R b/src/main/R/drtAnalysis/readValuesFromRunSummaries_v3.0.1-fareExperiments.R index acc5a56..46902b2 100644 --- a/src/main/R/drtAnalysis/readValuesFromRunSummaries_v3.0.1-fareExperiments.R +++ b/src/main/R/drtAnalysis/readValuesFromRunSummaries_v3.0.1-fareExperiments.R @@ -67,6 +67,7 @@ process_folders <- function(main_folder) { ############# mainDir <- "D:/Projekte/KelRide/runs/v3.0.1-fare-experiments/output-KEXI-kexi" +mainDir <- "E:/matsim-kelheim/v3.0.1-fare-experiments/output-KEXI-kexi/" #speeds <- list(3.3, 5, 8.3) #results <- list() @@ -95,6 +96,11 @@ write_csv(transposed_result, paste(mainDir, "results.csv", sep="")) +save <- function(fileName){ + ggsave(filename = paste(mainDir, "plots/", fileName, ".png", sep = ""), + dpi = 600, width = 32, height = 18, units = "cm") +} + ########################### plotByConfiguration <- function(parameterStr){ @@ -119,27 +125,26 @@ plotByConfiguration <- function(parameterStr){ #linetype = "All Day" #,shape = "Intermodal" ) + - theme_dark() + + #theme_dark() + theme( - plot.title = element_text(size = 16, face = "bold"), # Titelgröße anpassen - axis.title.x = element_text(size = 14), # X-Achsentitelgröße anpassen - axis.title.y = element_text(size = 14), # Y-Achsentitelgröße anpassen - axis.text = element_text(size = 12), # Achsentextgröße anpassen - legend.title = element_text(size = 14), # Legendentitelgröße anpassen - legend.text = element_text(size = 12), # Legendtextgröße anpassen - strip.text = element_text(size = 12) # Facet-Textgröße anpassen + plot.title = element_text(size = 20, face = "bold"), # Titelgröße anpassen + axis.title.x = element_text(size = 18), # X-Achsentitelgröße anpassen + axis.title.y = element_text(size = 18), # Y-Achsentitelgröße anpassen + axis.text = element_text(size = 14), # Achsentextgröße anpassen + legend.title = element_text(size = 18), # Legendentitelgröße anpassen + legend.text = element_text(size = 14), # Legendtextgröße anpassen + strip.text = element_text(size = 18, face = "bold") # Facet-Textgröße anpassen ) - } unique(results$parameter) plotByConfiguration("Rides") plotByConfiguration("Avg. wait time") -plotByConfiguration("Avg. ride distance [km]") -plotByConfiguration("Empty ratio") -plotByConfiguration("Total vehicle mileage [km]") -plotByConfiguration("Avg. fare [MoneyUnit]" ) -plotByConfiguration("Pax per veh-km") +#plotByConfiguration("Avg. ride distance [km]") +#plotByConfiguration("Empty ratio") +#plotByConfiguration("Total vehicle mileage [km]") +#plotByConfiguration("Avg. fare [MoneyUnit]" ) +#plotByConfiguration("Pax per veh-km") ##################### ##Zusammenhang wait time und Nachfrage @@ -155,7 +160,7 @@ avg_wait_time_data <- results %>% rename(avg_wait_time = mean) # Zusammenführen der Daten -plot_data <- left_join(handled_requests_data, avg_wait_time_data, by = c("fares")) + plot_data <- left_join(handled_requests_data, avg_wait_time_data, by = c("fares")) # Erstellen des Facet-Plots facet_plot <- ggplot(plot_data, aes(x = avg_wait_time, y = handled_requests)) + @@ -163,26 +168,65 @@ facet_plot <- ggplot(plot_data, aes(x = avg_wait_time, y = handled_requests)) + geom_point(size = 3 #,aes(shape = as.factor(intermodal)) ) + - geom_text(aes(label = fares), vjust = -1, hjust = 0.5, size = 3, color = "white") + + geom_text(aes(label = fares), vjust = -1, hjust = 0.5, size = 6, color = "black") + #facet_wrap(~ speed, scales = "free") + - labs(title = "Handled Requests by Avg. Wait Time and Fare System (conv. KEXI)", - x = "Avg. Wait Time", - y = "Handled Requests", + labs(title = "Anzahl Passagiere nach durchschn. Wartezeit und Preisschema", + x = "Durchschn. Wartezeit [s]", + y = "# Passagiere", #color = "Area", #linetype = "All Day" #,shape = "Intermodal" ) + - theme_dark() + + #theme_dark() + theme( - plot.title = element_text(size = 16, face = "bold"), # Titelgröße anpassen - axis.title.x = element_text(size = 14), # X-Achsentitelgröße anpassen - axis.title.y = element_text(size = 14), # Y-Achsentitelgröße anpassen - axis.text = element_text(size = 12), # Achsentextgröße anpassen - legend.title = element_text(size = 14), # Legendentitelgröße anpassen - legend.text = element_text(size = 12), # Legendtextgröße anpassen - strip.text = element_text(size = 12) # Facet-Textgröße anpassen + plot.title = element_text(size = 20, face = "bold"), # Titelgröße anpassen + axis.title.x = element_text(size = 18), # X-Achsentitelgröße anpassen + axis.title.y = element_text(size = 18), # Y-Achsentitelgröße anpassen + axis.text = element_text(size = 14), # Achsentextgröße anpassen + legend.title = element_text(size = 18), # Legendentitelgröße anpassen + legend.text = element_text(size = 14), # Legendtextgröße anpassen + strip.text = element_text(size = 18, face = "bold") # Facet-Textgröße anpassen ) # Plot anzeigen print(facet_plot) +save("pax-over-avg-wait-time") + +##################### +##Zusammenhang Durchschnittspreis und Nachfrage + +# Filter für die beiden relevanten Parameter +rides_data <- results %>% + filter(parameter == "Rides") %>% + select(fares, mean) %>% + rename(rides = mean) + +avg_fare_data <- results %>% + filter(parameter == "Avg. fare [MoneyUnit]") %>% + select(fares, mean) %>% + rename(avg_fare = mean) + +# Zusammenführen der Daten +plot_data <- left_join(rides_data, avg_fare_data, by = c("fares")) + +# Erstellen des Plots +fare_vs_rides_plot <- ggplot(plot_data, aes(x = avg_fare, y = rides)) + + geom_point(size = 3) + + geom_text(aes(label = fares), vjust = -1, hjust = 0.5, size = 6, color = "black") + + labs(title = "Anzahl Passagiere nach Durchschnittspreis", + x = "Durchschnittlicher Preis pro Fahrt [€]", + y = "# Passagiere") + + theme( + plot.title = element_text(size = 20, face = "bold"), + axis.title.x = element_text(size = 18), + axis.title.y = element_text(size = 18), + axis.text = element_text(size = 14), + legend.title = element_text(size = 18), + legend.text = element_text(size = 14), + strip.text = element_text(size = 18, face = "bold") + ) + +# Plot anzeigen +print(fare_vs_rides_plot) +save("pax-over-avg-fare") From a4b50cbb1b732d033d1c624087d7252be1fd10fa Mon Sep 17 00:00:00 2001 From: schlenther Date: Thu, 5 Sep 2024 17:13:54 +0200 Subject: [PATCH 2/4] readable boxplots for project report (german) --- .../KEXI-analysis-for-calibration-VIA.R | 35 ++++++++++++------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/src/main/R/drtDemandAnalysis/VIA-data/KEXI-analysis-for-calibration-VIA.R b/src/main/R/drtDemandAnalysis/VIA-data/KEXI-analysis-for-calibration-VIA.R index 529000d..6c9e103 100644 --- a/src/main/R/drtDemandAnalysis/VIA-data/KEXI-analysis-for-calibration-VIA.R +++ b/src/main/R/drtDemandAnalysis/VIA-data/KEXI-analysis-for-calibration-VIA.R @@ -16,7 +16,7 @@ Sys.setlocale("LC_TIME", "en_US.UTF-8") ### INPUT DEFINITIONS ### # set working directory -setwd("D:/Module/vsp/shared-svn/") +setwd("D:/shared-svn/projects/KelRide/data/KEXI") #setwd("C:/Users/Simon/Documents/shared-svn/projects/KelRide/data/KEXI/") # read data @@ -201,17 +201,20 @@ for(dataset in datasets) { boxplot_TravelTime_s <- ggplot(j, aes(y=travelTime_s)) + - stat_boxplot(geom="errorbar", width=3) + + stat_boxplot(geom="errorbar", width=5) + geom_boxplot(width=5) + scale_y_continuous(n.breaks = 10) + scale_x_discrete() + stat_summary(fun=mean, geom="errorbar",aes(ymax=..y.., ymin=..y.., x=0), - width=5, colour="red") + + width=5, colour="red", linewidth =1.5) + labs(x="", y="travel time [s]", title=paste("Boxplot KEXI Travel Time for dataset", names[i])) + + #labs(x="", y="Reisezeit [s]", title=paste("Verteilung der KEXI Reisezeit\n ab 2022")) + # labs(x="", y="travel time [s]") + #for paper only - theme(plot.title = element_text(hjust=0.5, size=10, face="bold"), axis.text.y = element_text(size=8), - axis.title.y = element_text(size=15, face="bold")) + theme(plot.title = element_text(hjust=0.5, size=28, face="bold"), + axis.text.y = element_text(size=22), + axis.title.y = element_text(size=26, face="bold")) + boxplot_TravelTime_s plotFile = paste0("plots/",names[i],"/boxplot_KEXI_travel_time_s.png") paste0("printing plot to ", plotFile) ggsave(plotFile, limitsize = FALSE) @@ -246,12 +249,15 @@ for(dataset in datasets) { scale_y_continuous(n.breaks = 10) + scale_x_discrete() + stat_summary(fun=mean, geom="errorbar",aes(ymax=..y.., ymin=..y.., x=0), - width=5, colour="red") + - labs(x="", y="travel distance [m]", title=paste("Boxplot KEXI Travel Distance for dataset", names[i])) + + width=5, colour="red", linewidth =1.5) + + #labs(x="", y="travel distance [m]", title=paste("Boxplot KEXI Travel Distance for dataset", names[i])) + + labs(x="", y="Haltestellendistanz [m]", title=paste("Verteilung der Haltestellendistanz\n ab 2022")) + # labs(x="", y="travel distance [m]") + #for paper only - theme(plot.title = element_text(hjust=0.5, size=10, face="bold"), axis.text.y = element_text(size=8), - axis.title.y = element_text(size=15, face="bold")) + theme(plot.title = element_text(hjust=0.5, size=28, face="bold"), + axis.text.y = element_text(size=22), + axis.title.y = element_text(size=26, face="bold")) + boxplot_distance_m plotFile = paste0("plots/",names[i],"/boxplot_KEXI_travel_distance_m.png") paste0("printing plot to ", plotFile) ggsave(plotFile, limitsize = FALSE) @@ -304,12 +310,15 @@ for(dataset in datasets) { scale_y_continuous(n.breaks = 8) + scale_x_discrete() + stat_summary(fun=mean, geom="errorbar",aes(ymax=..y.., ymin=..y.., x=0), - width=5, colour="red") + + width=5, colour="red", linewidth =1.5) + labs(x="", y="bookings", title=paste("Boxplot KEXI bookings per day for dataset", names[i])) + + #labs(x="", y="# Buchungen", title=paste("Verteilung der Anzahl beförderter\n Buchungen im konv. KEXI ab 2022")) + # labs(x="", y="travel distance [m]") + #for paper only - theme(plot.title = element_text(hjust=0.5, size=10, face="bold"), axis.text.y = element_text(size=8), - axis.title.y = element_text(size=15, face="bold")) - + theme(plot.title = element_text(hjust=1, size=28, face="bold"), + axis.text.y = element_text(size=22), + axis.title.y = element_text(size=26, face="bold")) + + boxplot_daily_bookings plotFile = paste0("plots/",names[i],"/boxplot_KEXI_daily_bookings.png") paste0("printing plot to ", plotFile) ggsave(plotFile, limitsize = FALSE) From 8f69c0f50ddcbb7a4e3a00fac8ae3187c4afb31c Mon Sep 17 00:00:00 2001 From: simei94 <67737999+simei94@users.noreply.github.com> Date: Mon, 9 Sep 2024 12:13:55 +0200 Subject: [PATCH 3/4] enhance ag noise analysis + dashboard + unit test such that every element of the enhanced noise single run dashboard is displayed (#86) --- .../postAnalysis/NoiseAverageAnalysis.java | 74 +++++++++--- .../AverageKelheimEmissionsDashboard.java | 2 +- .../AverageKelheimNoiseDashboard.java | 74 ++++++++---- .../dashboard/CreateAverageDashboards.java | 10 +- .../matsim/run/MeanNoiseDashboardTest.java | 112 ++++++++++++------ 5 files changed, 197 insertions(+), 75 deletions(-) diff --git a/src/main/java/org/matsim/analysis/postAnalysis/NoiseAverageAnalysis.java b/src/main/java/org/matsim/analysis/postAnalysis/NoiseAverageAnalysis.java index 33974c2..5c853dc 100644 --- a/src/main/java/org/matsim/analysis/postAnalysis/NoiseAverageAnalysis.java +++ b/src/main/java/org/matsim/analysis/postAnalysis/NoiseAverageAnalysis.java @@ -40,7 +40,8 @@ @CommandLine.Command(name = "average-noise", description = "Calculates average noise stats based on several sim runs with different random seeds.") @CommandSpec( requires = {"runs"}, - produces = {"mean_emission_per_day.csv", "mean_immission_per_day.avro", "mean_immission_per_hour.avro"} + produces = {"mean_emission_per_day.csv", "mean_immission_per_day.avro", "mean_immission_per_hour.avro", "mean_noise_stats.csv", + "mean_damages_receiverPoint_per_day.avro", "mean_damages_receiverPoint_per_hour.avro"} ) public class NoiseAverageAnalysis implements MATSimAppCommand { private final Logger log = LogManager.getLogger(NoiseAverageAnalysis.class); @@ -58,6 +59,10 @@ public class NoiseAverageAnalysis implements MATSimAppCommand { private List imissionsPerHour = new ArrayList<>(); private Map> emissionsPerDay = new HashMap<>(); private Map meanEmissionsPerDay = new HashMap<>(); + private Map> totalStats = new HashMap<>(); + private Map meanTotalStatsPerDay = new HashMap<>(); + private List damagesPerDay = new ArrayList<>(); + private List damagesPerHour = new ArrayList<>(); public static void main(String[] args) { @@ -81,6 +86,9 @@ public Integer call() throws Exception { String emissionsCsv = globFile(analysisDir, "*emission_per_day.csv*").toString(); String imissionsPerDayAvro = globFile(analysisDir, "*immission_per_day.avro*").toString(); String imissionsPerHourAvro = globFile(analysisDir, "*immission_per_hour.avro*").toString(); + String totalStatsCsv = globFile(analysisDir, "*noise_stats.csv*").toString(); + String damagesPerDayAvro = globFile(analysisDir, "*damages_receiverPoint_per_day.avro*").toString(); + String damagesPerHourAvro = globFile(analysisDir, "*damages_receiverPoint_per_hour.avro*").toString(); // read Table emissions = Table.read().csv(CsvReadOptions.builder(IOUtils.getBufferedReader(emissionsCsv)) @@ -88,9 +96,17 @@ public Integer call() throws Exception { .sample(false) .separator(CsvOptions.detectDelimiter(emissionsCsv)).build()); -// read avro file + Table totalStatsTable = Table.read().csv(CsvReadOptions.builder(IOUtils.getBufferedReader(totalStatsCsv)) + .columnTypes(new ColumnType[]{ColumnType.STRING, ColumnType.DOUBLE}) + .header(false) + .sample(false) + .separator(CsvOptions.detectDelimiter(totalStatsCsv)).build()); + +// read avro files readAvroFile(imissionsPerDayAvro, imissionsPerDay); readAvroFile(imissionsPerHourAvro, imissionsPerHour); + readAvroFile(damagesPerDayAvro, damagesPerDay); + readAvroFile(damagesPerHourAvro, damagesPerHour); // get all emission stats for (int i = 0; i < emissions.rowCount(); i++) { @@ -101,20 +117,28 @@ public Integer call() throws Exception { } emissionsPerDay.get(row.getString(LINK_ID)).add(row.getDouble(VALUE)); } - } -// calc emission means and write to mean map - for (Map.Entry> e : emissionsPerDay.entrySet()) { - AtomicReference sum = new AtomicReference<>(0.); - e.getValue().forEach(d -> sum.set(sum.get() + d)); +// get all total stats + for (int i = 0; i < totalStatsTable.rowCount(); i++) { + Row row = totalStatsTable.row(i); - meanEmissionsPerDay.put(e.getKey(), sum.get() / e.getValue().size()); + if (!totalStats.containsKey(row.getString(0))) { + totalStats.put(row.getString(0), new ArrayList<>()); + } + totalStats.get(row.getString(0)).add(row.getDouble(1)); + } } -// calc avro means - XYTData imissionsPerDayMean = calcAvroMeans(imissionsPerDay); - XYTData imissionsPerHourMean = calcAvroMeans(imissionsPerHour); +// calc emission means and write to mean map + calcCsvMeans(emissionsPerDay, meanEmissionsPerDay); +// calc mean total stats and write to mean map + calcCsvMeans(totalStats, meanTotalStatsPerDay); +// calc avro means + XYTData imissionsPerDayMean = calcAvroMeans(imissionsPerDay, "imissions"); + XYTData imissionsPerHourMean = calcAvroMeans(imissionsPerHour, "imissions"); + XYTData damagesPerDayMean = calcAvroMeans(damagesPerDay, "damages_receiverPoint"); + XYTData damagesPerHourMean = calcAvroMeans(damagesPerHour, "damages_receiverPoint"); // write emission mean stats try (CSVPrinter printer = new CSVPrinter(Files.newBufferedWriter(output.getPath("mean_emission_per_day.csv")), CSVFormat.DEFAULT)) { @@ -125,15 +149,33 @@ public Integer call() throws Exception { } } +// write total mean stats + try (CSVPrinter printer = new CSVPrinter(Files.newBufferedWriter(output.getPath("mean_noise_stats.csv")), CSVFormat.DEFAULT)) { + for (Map.Entry e : meanTotalStatsPerDay.entrySet()) { + printer.printRecord(e.getKey(), e.getValue()); + } + } + // write avro mean files writeAvro(imissionsPerDayMean, new File(output.getPath("mean_immission_per_day.avro").toString())); writeAvro(imissionsPerHourMean, new File(output.getPath("mean_immission_per_hour.avro").toString())); + writeAvro(damagesPerDayMean, new File(output.getPath("mean_damages_receiverPoint_per_day.avro").toString())); + writeAvro(damagesPerHourMean, new File(output.getPath("mean_damages_receiverPoint_per_hour.avro").toString())); return 0; } + private void calcCsvMeans(Map> dataMap, Map meanMap) { + for (Map.Entry> e : dataMap.entrySet()) { + AtomicReference sum = new AtomicReference<>(0.); + e.getValue().forEach(d -> sum.set(sum.get() + d)); + + meanMap.put(e.getKey(), sum.get() / e.getValue().size()); + } + } + /** - * write an .avro file containing immission data. + * write an .avro file containing immission / damage data. */ public void writeAvro(XYTData xytData, File outputFile) { DatumWriter datumWriter = new SpecificDatumWriter<>(XYTData.class); @@ -146,7 +188,7 @@ public void writeAvro(XYTData xytData, File outputFile) { } } - private XYTData calcAvroMeans(List recordList) { + private XYTData calcAvroMeans(List recordList, String dataFieldName) { String crs = null; List xCoords = new ArrayList<>(); List yCoords = new ArrayList<>(); @@ -189,7 +231,7 @@ private XYTData calcAvroMeans(List recordList) { List values = new ArrayList<>(); for (Map.Entry entry : ((HashMap) object4).entrySet()) { - if (entry.getKey() instanceof Utf8 && entry.getKey().toString().equals("imissions") && entry.getValue() instanceof GenericData.Array) { + if (entry.getKey() instanceof Utf8 && entry.getKey().toString().equals(dataFieldName) && entry.getValue() instanceof GenericData.Array) { values.addAll((GenericData.Array) entry.getValue()); String entryString = ((Utf8) entry.getKey()).toString(); @@ -211,7 +253,7 @@ private XYTData calcAvroMeans(List recordList) { // calc mean values for each datapoint out of sums and number of records (1 record = 1 run seed) data.entrySet() .stream() - .filter(entry -> entry.getKey().equals("imissions")) + .filter(entry -> entry.getKey().equals(dataFieldName)) .forEach(entry -> entry.getValue() .forEach(value -> entry.getValue().set(entry.getValue().indexOf(value), value / recordList.size()))); @@ -234,7 +276,7 @@ private void getCoordData(Object object, List target) { } /** - * read an .avro file containing immissions. + * read an .avro file containing immissions / damages. */ public void readAvroFile(String input, List target) { try { diff --git a/src/main/java/org/matsim/dashboard/AverageKelheimEmissionsDashboard.java b/src/main/java/org/matsim/dashboard/AverageKelheimEmissionsDashboard.java index 526e24b..ba6201d 100644 --- a/src/main/java/org/matsim/dashboard/AverageKelheimEmissionsDashboard.java +++ b/src/main/java/org/matsim/dashboard/AverageKelheimEmissionsDashboard.java @@ -92,7 +92,7 @@ public void configure(Header header, Layout layout) { viz.height = 12.0; viz.datasets.csvFile = postProcess(data, "mean_emissions_per_link_per_m.csv"); viz.datasets.csvBase = Path.of(this.dirs.get(0)).getParent().relativize(Path.of(pathToCsvBase)).toString(); - viz.network = new CreateAverageDashboards().copyGeoJsonNetwork(dirs); + viz.network = new CreateAverageDashboards().copyVizNetwork(dirs, ".avro"); viz.display.color.columnName = "CO2_TOTAL [g/m]"; viz.display.color.dataset = "csvFile"; viz.display.width.scaleFactor = 100; diff --git a/src/main/java/org/matsim/dashboard/AverageKelheimNoiseDashboard.java b/src/main/java/org/matsim/dashboard/AverageKelheimNoiseDashboard.java index cb83ae7..333dc57 100644 --- a/src/main/java/org/matsim/dashboard/AverageKelheimNoiseDashboard.java +++ b/src/main/java/org/matsim/dashboard/AverageKelheimNoiseDashboard.java @@ -8,6 +8,7 @@ import org.matsim.simwrapper.viz.ColorScheme; import org.matsim.simwrapper.viz.GridMap; import org.matsim.simwrapper.viz.MapPlot; +import org.matsim.simwrapper.viz.Tile; import java.util.ArrayList; import java.util.List; @@ -26,6 +27,8 @@ public class AverageKelheimNoiseDashboard implements Dashboard { private static final String LIGHT_BLUE = "#95c7df"; private static final String ORANGE = "#f4a986"; private static final String RED = "#cc0c27"; + private static final String SAND = "#dfb095"; + private static final String YELLOW = "#dfdb95"; public AverageKelheimNoiseDashboard(List dirs, Integer noRuns) { this.dirs = dirs; @@ -45,51 +48,82 @@ public void configure(Header header, Layout layout) { header.title = "Average Noise"; header.description = "Shows the average noise footprint and spatial distribution for several simulation runs."; - layout.row("aggregate noise") - .el(GridMap.class, (viz, data) -> { - viz.title = "Noise Immissions (Grid)"; - viz.description = "Aggregate Noise Immissions per day"; - viz.height = 12.0; - viz.cellSize = 250; - viz.opacity = 0.2; - viz.maxHeight = 20; - viz.center = data.context().getCenter(); - viz.zoom = data.context().mapZoomLevel; - viz.setColorRamp(new double[]{40, 50, 60}, new String[]{DARK_BLUE, LIGHT_BLUE, ORANGE, RED}); - viz.file = postProcess(data, "mean_immission_per_day.avro"); - }) + layout.row("stats") + .el(Tile.class, (viz, data) -> { + viz.dataset = postProcess(data, "mean_noise_stats.csv"); + viz.height = 0.1; + }); + + layout.row("emissions") .el(MapPlot.class, (viz, data) -> { viz.title = "Noise Emissions (Link)"; - viz.description = "Aggregate Noise Emissions per day"; + viz.description = "Maximum Noise Level per day [dB]"; viz.height = 12.0; viz.center = data.context().getCenter(); viz.zoom = data.context().mapZoomLevel; viz.minValue = minDb; viz.maxValue = maxDb; - viz.setShape(new CreateAverageDashboards().copyGeoJsonNetwork(dirs)); + viz.setShape(new CreateAverageDashboards().copyVizNetwork(dirs, ".avro")); viz.addDataset(NOISE, postProcess(data, "mean_emission_per_day.csv")); viz.display.lineColor.dataset = NOISE; viz.display.lineColor.columnName = "value"; viz.display.lineColor.join = "Link Id"; viz.display.lineColor.fixedColors = new String[]{DARK_BLUE, LIGHT_BLUE, ORANGE, RED}; - viz.display.lineColor.setColorRamp(ColorScheme.RdYlBu, 4, true, "45, 55, 65"); + viz.display.lineColor.setColorRamp(ColorScheme.Oranges, 8, false, "35, 45, 55, 65, 75, 85, 95"); viz.display.lineWidth.dataset = NOISE; viz.display.lineWidth.columnName = "value"; viz.display.lineWidth.scaleFactor = 8d; viz.display.lineWidth.join = "Link Id"; }); - layout.row("hourly noise") + layout.row("immissions") .el(GridMap.class, (viz, data) -> { - viz.title = "Hourly Noise Immissions (Grid)"; - viz.description = "Noise Immissions per hour"; + viz.title = "Noise Immissions (Grid)"; + viz.description = "Total Noise Immissions per day"; viz.height = 12.0; viz.cellSize = 250; viz.opacity = 0.2; viz.maxHeight = 20; viz.center = data.context().getCenter(); viz.zoom = data.context().mapZoomLevel; - viz.setColorRamp(new double[]{40, 50, 60}, new String[]{DARK_BLUE, LIGHT_BLUE, ORANGE, RED}); + viz.setColorRamp(new double[]{30, 40, 50, 60, 70}, new String[]{DARK_BLUE, LIGHT_BLUE, YELLOW, SAND, ORANGE, RED}); + viz.file = postProcess(data, "mean_immission_per_day.avro"); + }) + .el(GridMap.class, (viz, data) -> { + viz.title = "Hourly Noise Immissions (Grid)"; + viz.description = "Noise Immissions per hour"; + viz.height = 12.0; + viz.cellSize = 250; + viz.opacity = 0.1; + viz.maxHeight = 40; + viz.center = data.context().getCenter(); + viz.zoom = data.context().mapZoomLevel; + viz.setColorRamp(new double[]{30, 40, 50, 60, 70}, new String[]{DARK_BLUE, LIGHT_BLUE, YELLOW, SAND, ORANGE, RED}); viz.file = postProcess(data, "mean_immission_per_hour.avro"); }); + layout.row("damages") + .el(GridMap.class, (viz, data) -> { + viz.title = "Daily Noise Damages (Grid)"; + viz.description = "Total Noise Damages per day [€]"; + viz.height = 12.0; + viz.cellSize = 250; + viz.opacity = 0.1; + viz.maxHeight = 40; + viz.center = data.context().getCenter(); + viz.zoom = data.context().mapZoomLevel; + viz.setColorRamp(new double[]{30, 40, 50, 60, 70}, new String[]{DARK_BLUE, LIGHT_BLUE, YELLOW, SAND, ORANGE, RED}); + viz.file = postProcess(data, "mean_damages_receiverPoint_per_day.avro"); + }) + .el(GridMap.class, (viz, data) -> { + viz.title = "Hourly Noise Damages (Grid)"; + viz.description = "Noise Damages per hour [€]"; + viz.height = 12.0; + viz.cellSize = 250; + viz.opacity = 0.2; + viz.maxHeight = 40; + viz.center = data.context().getCenter(); + viz.zoom = data.context().mapZoomLevel; + viz.setColorRamp(new double[]{30, 40, 50, 60, 70}, new String[]{DARK_BLUE, LIGHT_BLUE, YELLOW, SAND, ORANGE, RED}); + viz.file = postProcess(data, "mean_damages_receiverPoint_per_hour.avro"); + }); } } diff --git a/src/main/java/org/matsim/dashboard/CreateAverageDashboards.java b/src/main/java/org/matsim/dashboard/CreateAverageDashboards.java index cc0dd7f..b6dffb3 100644 --- a/src/main/java/org/matsim/dashboard/CreateAverageDashboards.java +++ b/src/main/java/org/matsim/dashboard/CreateAverageDashboards.java @@ -82,23 +82,23 @@ public Integer call() throws Exception { } /** - * A helper method to copy an already existing Geojson network rather than creating it all over again. + * A helper method to copy an already existing Geojson / avro network rather than creating it all over again. */ - String copyGeoJsonNetwork(List dirs) { + String copyVizNetwork(List dirs, String fileType) { for (String dir : dirs) { - File networkFile = new File(dir + "/analysis/network/network.geojson"); + File networkFile = new File(dir + "/analysis/network/network" + fileType); Path target = Path.of(Path.of(dir).getParent() + "/analysis/network"); if (Files.notExists(target) && networkFile.exists() && networkFile.isFile()) { try { Files.createDirectories(target); - Files.copy(networkFile.toPath(), Path.of(target + "/network.geojson")); + Files.copy(networkFile.toPath(), Path.of(target + "/network" + fileType)); } catch (IOException e) { throw new UncheckedIOException(e); } } } - return "analysis/network/network.geojson"; + return "analysis/network/network" + fileType; } } diff --git a/src/test/java/org/matsim/run/MeanNoiseDashboardTest.java b/src/test/java/org/matsim/run/MeanNoiseDashboardTest.java index e53ed82..e3e0c50 100644 --- a/src/test/java/org/matsim/run/MeanNoiseDashboardTest.java +++ b/src/test/java/org/matsim/run/MeanNoiseDashboardTest.java @@ -11,7 +11,7 @@ import org.matsim.analysis.postAnalysis.NoiseAverageAnalysis; import org.matsim.application.avro.XYTData; import org.matsim.application.options.CsvOptions; -import org.matsim.application.prepare.network.CreateGeoJsonNetwork; +import org.matsim.application.prepare.network.CreateAvroNetwork; import org.matsim.core.utils.io.IOUtils; import org.matsim.dashboard.AverageKelheimNoiseDashboard; import org.matsim.simwrapper.Dashboard; @@ -35,6 +35,8 @@ class MeanNoiseDashboardTest { @RegisterExtension public final MatsimTestUtils utils = new MatsimTestUtils(); + private final NoiseAverageAnalysis analysis = new NoiseAverageAnalysis(); + /** * Test for building means over noise emission and immission values of several runs. The display of the values has to be done manually by looking at the resulting mean noise dashbaord. */ @@ -44,11 +46,10 @@ void runMeanNoiseDashboardTest() throws IOException { String networkPath = "https://svn.vsp.tu-berlin.de/repos/public-svn/matsim/scenarios/countries/de/kelheim/kelheim-v3.0/input/kelheim-v3.0-network.xml.gz"; String crs = "EPSG:25832"; String path = utils.getInputDirectory(); - NoiseAverageAnalysis analysis = new NoiseAverageAnalysis(); List foldersSeeded = new ArrayList<>(); - new CreateGeoJsonNetwork().execute(List.of("--network", networkPath, "--with-properties", "--shp", "./input/shp/dilutionArea.shp", "--output-network", path + "1seed/analysis/network/network.geojson", + new CreateAvroNetwork().execute(List.of("--network", networkPath, "--with-properties", "--shp", "./input/shp/dilutionArea.shp", "--output-network", path + "1seed/analysis/network/network.avro", "--input-crs", "EPSG:25832").toArray(new String[0])); // write dummy data @@ -56,7 +57,8 @@ void runMeanNoiseDashboardTest() throws IOException { List xCoords = new ArrayList<>(); List yCoords = new ArrayList<>(); List timeStamps = new ArrayList<>(); - Map> data = new HashMap<>(); + Map> immissionData = new HashMap<>(); + Map> damageData = new HashMap<>(); xCoords.add(710419.08F); xCoords.add(710424.82F); @@ -65,21 +67,31 @@ void runMeanNoiseDashboardTest() throws IOException { timeStamps.add(28800); - data.put("imissions", List.of((float) i)); + immissionData.put("imissions", List.of((float) i)); + damageData.put("damages_receiverPoint", List.of((float) i)); String seedDir = path + i + "seed/"; foldersSeeded.add(seedDir); // write avro dummy files Files.createDirectories(Path.of(seedDir + "analysis/noise/")); - analysis.writeAvro(new XYTData(crs, xCoords, yCoords, List.of(0), data), new File(seedDir + "analysis/noise/immission_per_day.avro")); - analysis.writeAvro(new XYTData(crs, xCoords, yCoords, timeStamps, data), new File(seedDir + "analysis/noise/immission_per_hour.avro")); + analysis.writeAvro(new XYTData(crs, xCoords, yCoords, List.of(0), immissionData), new File(seedDir + "analysis/noise/immission_per_day.avro")); + analysis.writeAvro(new XYTData(crs, xCoords, yCoords, timeStamps, immissionData), new File(seedDir + "analysis/noise/immission_per_hour.avro")); + analysis.writeAvro(new XYTData(crs, xCoords, yCoords, List.of(0), damageData), new File(seedDir + "analysis/noise/damages_receiverPoint_per_day.avro")); + analysis.writeAvro(new XYTData(crs, xCoords, yCoords, timeStamps, damageData), new File(seedDir + "analysis/noise/damages_receiverPoint_per_hour.avro")); // write emissions csv dummy file try (CSVPrinter printer = new CSVPrinter(Files.newBufferedWriter(Path.of(seedDir + "analysis/noise/emission_per_day.csv")), CSVFormat.DEFAULT)) { printer.printRecord("Link Id", "value"); printer.printRecord("-27443742#0", i); } + +// write total stats csv dummy file + try (CSVPrinter printer = new CSVPrinter(Files.newBufferedWriter(Path.of(seedDir + "analysis/noise/noise_stats.csv")), CSVFormat.DEFAULT)) { + printer.printRecord("Annual cost rate per pop. unit [€]:", i); + printer.printRecord("Total damages at receiver points", i); + printer.printRecord("Total immission at receiver points", i); + } } SimWrapper sw = SimWrapper.create(); @@ -92,32 +104,25 @@ void runMeanNoiseDashboardTest() throws IOException { } sw.run(Path.of(path)); -// assert that: a) mean immission is 2.0 on daily and hourly data b) hourly data has timestamp 28800 c) mean emission is 2.0 - List daily = new ArrayList<>(); - List hourly = new ArrayList<>(); - analysis.readAvroFile(path + "analysis/postAnalysis-noise/mean_immission_per_day.avro", daily); - analysis.readAvroFile(path + "analysis/postAnalysis-noise/mean_immission_per_hour.avro", hourly); - - if (daily.getFirst().get(4) instanceof HashMap) { - Map.Entry entry = ((HashMap) daily.getFirst().get(4)).entrySet().stream().toList().getFirst(); - if (entry.getKey() instanceof Utf8 && entry.getValue() instanceof GenericData.Array) { - float dailyImmission = ((GenericData.Array) entry.getValue()).getFirst(); - Assertions.assertEquals(2.0, dailyImmission); - } - } - - if (hourly.getFirst().get(4) instanceof HashMap) { - Map.Entry entry = ((HashMap) hourly.getFirst().get(4)).entrySet().stream().toList().getFirst(); - if (entry.getKey() instanceof Utf8 && entry.getValue() instanceof GenericData.Array) { - float hourlyImmission = ((GenericData.Array) entry.getValue()).getFirst(); - Assertions.assertEquals(2.0, hourlyImmission); - } - } - - if (hourly.getFirst().get(3) instanceof GenericData.Array) { - int timeStamp = ((GenericData.Array) hourly.getFirst().get(3)).getFirst(); - Assertions.assertEquals(28800, timeStamp); - } +// assert that: +// a) mean immission is 2.0 on daily and hourly data +// b) hourly immission data has timestamp 28800 +// c) mean emission is 2.0 +// d) all mean total stats are 2.0 +// e) mean damage is 2.0 on daily and hourly data +// f) hourly damage data has timestamp + List dailyImmission = new ArrayList<>(); + List hourlyImmission = new ArrayList<>(); + List dailyDamage = new ArrayList<>(); + List hourlyDamage = new ArrayList<>(); + analysis.readAvroFile(path + "analysis/postAnalysis-noise/mean_immission_per_day.avro", dailyImmission); + analysis.readAvroFile(path + "analysis/postAnalysis-noise/mean_immission_per_hour.avro", hourlyImmission); + analysis.readAvroFile(path + "analysis/postAnalysis-noise/mean_damages_receiverPoint_per_day.avro", dailyDamage); + analysis.readAvroFile(path + "analysis/postAnalysis-noise/mean_damages_receiverPoint_per_hour.avro", hourlyDamage); + + assertValue(dailyImmission); + assertValue(hourlyImmission); + assertTimeStamp(hourlyImmission); Table emissions = Table.read().csv(CsvReadOptions.builder(IOUtils.getBufferedReader(path + "analysis/postAnalysis-noise/mean_emission_per_day.csv")) .columnTypesPartial(Map.of("Link Id", ColumnType.STRING, "value", ColumnType.DOUBLE)) @@ -129,5 +134,46 @@ void runMeanNoiseDashboardTest() throws IOException { Assertions.assertEquals("-27443742#0", linkId); Assertions.assertEquals(2.0, emission); + + Table totalStats = Table.read().csv(CsvReadOptions.builder(IOUtils.getBufferedReader(path + "analysis/postAnalysis-noise/mean_noise_stats.csv")) + .columnTypes(new ColumnType[]{ColumnType.STRING, ColumnType.DOUBLE}) + .header(false) + .sample(false) + .separator(CsvOptions.detectDelimiter(path + "analysis/postAnalysis-noise/mean_noise_stats.csv")).build()); + + String row1ParamName = (String) totalStats.get(0, 0); + String row2ParamName = (String) totalStats.get(1, 0); + String row3ParamName = (String) totalStats.get(2, 0); + double row1Value = (Double) totalStats.get(0, 1); + double row2Value = (Double) totalStats.get(1, 1); + double row3Value = (Double) totalStats.get(2, 1); + + Assertions.assertEquals("Annual cost rate per pop. unit [€]:", row1ParamName); + Assertions.assertEquals("Total damages at receiver points", row2ParamName); + Assertions.assertEquals("Total immission at receiver points", row3ParamName); + Assertions.assertEquals(2.0, row1Value); + Assertions.assertEquals(2.0, row2Value); + Assertions.assertEquals(2.0, row3Value); + + assertValue(dailyDamage); + assertValue(hourlyDamage); + assertTimeStamp(hourlyDamage); + } + + private static void assertTimeStamp(List records) { + if (records.getFirst().get(3) instanceof GenericData.Array) { + int timeStamp = ((GenericData.Array) records.getFirst().get(3)).getFirst(); + Assertions.assertEquals(28800, timeStamp); + } + } + + private static void assertValue(List records) { + if (records.getFirst().get(4) instanceof HashMap) { + Map.Entry entry = ((HashMap) records.getFirst().get(4)).entrySet().stream().toList().getFirst(); + if (entry.getKey() instanceof Utf8 && entry.getValue() instanceof GenericData.Array) { + float value = ((GenericData.Array) entry.getValue()).getFirst(); + Assertions.assertEquals(2.0, value); + } + } } } From 2c69476eb5a6c2db5500cfdbdf21839e1e528f0b Mon Sep 17 00:00:00 2001 From: schlenther Date: Mon, 9 Sep 2024 12:17:38 +0200 Subject: [PATCH 4/4] minor changes for readability --- .../VIA-data/KEXI-shiny-dashboard.R | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/main/R/drtDemandAnalysis/VIA-data/KEXI-shiny-dashboard.R b/src/main/R/drtDemandAnalysis/VIA-data/KEXI-shiny-dashboard.R index 0ef7e48..75f6ff3 100644 --- a/src/main/R/drtDemandAnalysis/VIA-data/KEXI-shiny-dashboard.R +++ b/src/main/R/drtDemandAnalysis/VIA-data/KEXI-shiny-dashboard.R @@ -722,12 +722,18 @@ server <- function(input, output) { layout( title = list( text = "Mittlere Anzahl verfügbarer Fahrzeuge pro Tag", - font = list(size = 14, color = "black", family = "Arial", weight = "bold"), + font = list(size = 18, color = "black", family = "Arial", weight = "bold"), x = 0.5 # Zentriert den Titel ), - xaxis = list(title = "Datum"), - yaxis = list(title = "Mittlere Fahrzeugverfuegbarkeit pro Tag", side = "left"), + xaxis = list(title = "Datum", + font = list(size = 16, family = "Arial", color = "black", weight = "bold") # Größe und Stil der X-Achsenbeschriftung + ), + yaxis = list(title = "Mittlere Fahrzeugverfuegbarkeit pro Tag", + font = list(size = 16, family = "Arial", color = "black", weight = "bold"), # Größe und Stil der Y-Achsenbeschriftung + + side = "left"), yaxis2 = list(title = "Passagiere pro Tag", + font = list(size = 16, family = "Arial", color = "red", weight = "bold"), # Größe und Stil der zweiten Y-Achsenbeschriftung overlaying = "y", side = "right", color = "red")#, @@ -739,6 +745,7 @@ server <- function(input, output) { ) fig + })