Files
shiny-app/IT Shiny App.R
2025-12-14 14:03:45 +01:00

314 lines
10 KiB
R

library(rjson)
library(shiny)
library(bslib)
library(leaflet)
library(sf)
library(htmltools)
library(dplyr)
library(purrr)
library(ggplot2)
# Json of Crime Reports
crime_json <- fromJSON(file="data.json")
get_bezirk_by_stadtteil <- function(name) {
parents <- names(crime_json)[sapply(crime_json, function(item) name %in% names(item))]
if (length(parents) == 0) return(NULL)
parents
}
map_data_to_table <- function(bezirk, stadtteil, year) {
year <- as.character(trimws(year))
map_df(names(crime_json[[bezirk]][[stadtteil]]), function(crime) {
row <- crime_json[[bezirk]][[stadtteil]][[crime]][[year]]
tibble(
Name = crime,
`Erfasste Fälle` = as.integer(row[["Erfasste Fälle"]]),
`Aufgeklärte Fälle` = as.integer(row[["Aufgeklärte Fälle"]]),
`Aufklärung relativ` = paste(row[["Aufklärung relativ"]], "%", sep=""),
)
})
}
map_data_to_top3_plot <- function(bezirk, stadtteil, year) {
year <- as.character(trimws(year))
req(bezirk)
req(stadtteil)
req(year)
komplettes_tibble <- map_df(names(crime_json[[bezirk]][[stadtteil]]), function(crime) {
row <- crime_json[[bezirk]][[stadtteil]][[crime]][[year]]
tibble(
Name = crime,
Erfasst = as.integer(row[["Erfasste Fälle"]]),
)
})
# Sortieren und Beschränken auf die Ränge 2, 3 und 4
top_3_tibble <- komplettes_tibble %>%
# Sortieren: Absteigend nach "Erfasste Fälle". Der höchste Wert ist nun in Zeile 1.
arrange(desc(Erfasst)) %>%
# Beschränken: Wählt die Zeilen 2, 3 und 4 aus.
# Dies sind die Ränge 2, 3 und 4.
slice(2:4)
return(top_3_tibble)
}
#GeoJson for Bezirke
geo_bezirke <- st_read("geobezirke-parsed.json")
geo_bezirke <- st_transform(geo_bezirke, crs = 4326)
geo_bezirke$leaflet_id <- paste("bez_", geo_bezirke$bezirk, sep="")
#GeoJson for Stadtteile
geo_stadtteile <- st_read("geostadtteile-parsed.json")
geo_stadtteile <- st_transform(geo_stadtteile, crs = 4326)
geo_stadtteile$leaflet_id <- paste("std_", geo_stadtteile$stadtteil, sep="")
bezirke <- sort(names(crime_json))
auswahlmöglichkeiten <- crime_json %>%
# 1. map(names) wendet names() auf jedes Element der ersten Ebene, wie Bezirke ("A", "B", "C") an.
# Ergebnis: Eine Liste von Vektoren (z.B. list(c("aa1", "aa2"), c("bb1", "bb2"), c("cc1"))), hier: Stadtteile
map(names) %>%
# 2. unlist() vereint alle diese Vektoren zu einem einzigen Vektor.
# Ergebnis: c("aa1", "aa2", "bb1", "bb2", "cc1")
unlist() %>%
# 3. unique(): auf Nummer sicher gehen, dass die Stadtteile alle eindeutig sind.
unique() %>%
sort()
#User interface definitions
ui <- function() {
page_fillable(
h1("Kriminalstatistik Hamburg",
style = "color: #006400;",
class = "ms-4 display-4"),# ms-4 sorgt für den Abstand links
navset_card_tab(
nav_panel("Karte",
page_sidebar(
layout_columns(
leafletOutput("hhmap"),
card(
div(
# h3 oder div dient als Block-Element und richtet seinen Inhalt (das span) rechtsbündig aus
style = "text-align: right; width: 100%; ",
# Der Text wird in ein span verpackt und erhält die Border.
# Ein span nimmt nur den Platz ein, den der Inhalt benötigt (inline).
tags$span(
"2024",
style = "
border: 1px solid rgba(40,70,94,0.1);
border-radius: 5px;
padding: 5px 10px 5px 10px;
background-color: #eeeeee;
"
# padding ist der abstand an leerer Fläche in der reihenfolge top right bottom left
# farbe rgba ist die exakte Farbe der Trennlinien und Card-Umrandungen aus der App
)
),
div(
class = "d-flex align-items-end gap-2",
h5(strong("Bezirk:"), style = "margin-bottom: 0;"),
textOutput("txt_map_selection_bezirk"),
),
div(
class = "d-flex align-items-end gap-2",
h5(strong("Stadtteil:"), style = "margin-bottom: 0;"),
textOutput("txt_map_selection_stadtteil"),
),
plotOutput("grph_top3"),
#tableOutput("tbl_2024"),
),
col_widths = c(8, 4),
),
sidebar = sidebar(
radioButtons(
inputId = "rd_maptype",
label = "Kartentyp",
choices = c("Bezirke", "Stadtteile"),
selected = "Bezirke"
),
###selectizeInput ist was wir wollen, wahrscheinlich
selectizeInput(
inputId = "search",
label = "Wählen Sie ein Feld (Schlüssel) aus:",
choices = NULL,
selected = NULL,
multiple = FALSE, # Hier wahrscheinlich nur Einzelauswahl gewünscht
options = list(
placeholder = "Start typing..."
)
)
),
)
),
nav_panel("Statistik", "Statistik_inhalt"),
nav_panel("Wiki",
accordion(
accordion_panel(
title = "Diebstahl",
#icon =
"Unter Diebstahl sind alle Diebsthal-Delikte nach §239."
),
accordion_panel(
title = "Gewaltkriminalität",
#icon =
"Gewaltkriminalität ist doof."
),
accordion_panel(
title = "Was ganz langes.",
#icon
"Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet."
)
)
),
id = "tab"
)
)
}
get_map_layer_prefix <- function(text){
return(substring(text, 0, 4))
}
get_map_layer_name <- function(text) {
return(substring(text, 5))
}
#Server handling user input and processing of data
server <- function(input, output, session) {
currently_selected_bezirk <- reactiveVal("")
currently_selected_stadtteil <- reactiveVal("")
# 1. Aktualisieren der Auswahlmöglichkeiten mit den extrahierten Schlüsselnamen
updateSelectizeInput(
session = session,
inputId = "search",
choices = auswahlmöglichkeiten,
selected = ""
)
observeEvent(input$search, {
req(input$search)
currently_selected_bezirk(get_bezirk_by_stadtteil(input$search))
currently_selected_stadtteil(input$search)
})
observeEvent(input$rd_maptype, {
maptype <- input$rd_maptype
mapproxy <- leafletProxy("hhmap")
if (maptype == "Bezirke"){
hideGroup(mapproxy, "layer_stadtteile")
showGroup(mapproxy, "layer_bezirke")
}
else {
hideGroup(mapproxy, "layer_bezirke")
showGroup(mapproxy, "layer_stadtteile")
}
})
observeEvent(input$hhmap_shape_click, {
click_event <- input$hhmap_shape_click
# Check if an ID was returned (meaning a polygon was clicked)
if (!is.null(click_event$id)) {
# The ID of the clicked polygon
clicked_polygon_id <- click_event$id
prefix <- get_map_layer_prefix(clicked_polygon_id)
rest_of_name <- get_map_layer_name(clicked_polygon_id)
if (prefix == "bez_") {
currently_selected_bezirk(rest_of_name)
currently_selected_stadtteil(paste("Bezirk", rest_of_name))
}
if(prefix == "std_") {
currently_selected_bezirk(get_bezirk_by_stadtteil(rest_of_name))
currently_selected_stadtteil(rest_of_name)
}
#neues Polygon über die anderen legen, wenn ein bezirk angeklickt wurde
leafletProxy("hhmap") %>%
clearGroup("selected") %>%
addPolygons(
data = geo_bezirke[geo_bezirke[["leaflet_id"]] == click_event$id, ],
layerId = id,
fillColor = "#51968b",
color = "#7bb5ab",
fillOpacity = 0.1,
weight = 4,
group = "selected"
)
}
})
output$hhmap <- renderLeaflet({
leaflet() %>%
addProviderTiles(providers$CartoDB.Positron) %>%
addPolygons(
data = geo_bezirke,
layerId = ~leaflet_id,
group = "layer_bezirke",
color = "#7bb5ab",
fillOpacity = 0.4, # Polygon fill transparency
highlightOptions = highlightOptions(
color = "#103b57",
weight = 4,
bringToFront = TRUE
),
) %>%
addPolygons(
data = geo_stadtteile,
group = "layer_stadtteile",
layerId = ~leaflet_id,
color = "#7bb5ab",
fillColor = "#bdf0e7",
options = pathOptions(pane = "overlayPane"), # Use a leaflet option to ensure it's hidden
weight = 4,
fillOpacity = 0.4, # Polygon fill transparency
highlightOptions = highlightOptions(
color = "#103b57",
weight = 4,
bringToFront = TRUE
),
) %>%
setView(
lng = 9.98716634776887,
lat = 53.5488439196432,
zoom = 11
)
})
output$txt_map_selection_bezirk <- renderText({
currently_selected_bezirk()
})
output$txt_map_selection_stadtteil <- renderText({
currently_selected_stadtteil()
})
output$grph_top3 <- renderPlot({
data_tibble <- map_data_to_top3_plot(
bezirk = currently_selected_bezirk(),
stadtteil = currently_selected_stadtteil(),
year = "2024"
)
req(nrow(data_tibble) > 0)
ggplot(data_tibble, aes(x = Name, y = Erfasst, fill = Name)) +
geom_col()
})
output$tbl_2024 <- renderTable(
map_data_to_table(
bezirk = currently_selected_bezirk(),
stadtteil = currently_selected_stadtteil(),
year = "2024"
),
striped = TRUE
)
}
options(shiny.host = '0.0.0.0')
options(shiny.port = 8888)
shinyApp(ui = ui, server = server)