Compare commits

76 Commits
lappi ... main

Author SHA1 Message Date
5c0bfc24db Update headline 2026-02-07 22:53:29 +01:00
0860828339 Change pill color 2026-02-07 22:40:20 +01:00
11228e8941 Remove scaling old stuff 2026-02-07 22:34:36 +01:00
3f72ba1679 Add wiki css 2026-02-07 22:34:36 +01:00
7da43631ff Fix formatting 2026-02-07 22:34:36 +01:00
9ebc754c44 Fix layout margins and paddings 2026-02-07 22:34:36 +01:00
2fd7e150f3 Initial improved accordion design 2026-02-07 22:34:36 +01:00
09ef5ecff0 Update and style second page 2026-02-07 22:34:36 +01:00
a7f7aafd1f Add style to collapsed button 2026-02-07 22:34:36 +01:00
5700c61421 Move map zoom controls to top right 2026-02-07 22:34:36 +01:00
08e883533f Rework and fix css and style 2026-02-07 22:34:36 +01:00
cee19cefe0 Update plot 2026-02-07 22:34:36 +01:00
688573fe02 Add comments 2026-02-07 22:33:25 +01:00
2af26d5f54 Change bar colors for more contrast 2026-02-07 22:32:28 +01:00
f2fe7d105c Add comments 2026-02-07 19:25:58 +01:00
86d88b94fb Correct crime name 2026-02-07 19:21:43 +01:00
4fc9efcf64 Shorten crime name 2026-02-07 12:29:29 +01:00
6ee2b5a1d2 Change design of main headline 2026-02-07 12:29:29 +01:00
6db3a49f6f Fix accordion order 2026-02-07 12:29:29 +01:00
077f2576e2 Change viewport to 1000 2026-02-07 12:02:39 +01:00
d0f36ce7c5 Add legend to comparison 2026-02-07 12:01:21 +01:00
93123d0ff6 Replace interactive comparison with plotly 2026-02-07 11:31:55 +01:00
fdf2cf8331 Replace top3 plot with plotly 2026-02-07 11:31:14 +01:00
10d0a9ad05 Make whitespace changes and a small change in vjust 2026-02-06 15:54:21 +01:00
3c74b98827 Change font size and style 2026-02-06 15:50:30 +01:00
bc7e9ad756 Add navbar styling 2026-02-06 15:43:43 +01:00
5a6882b500 Add scroll bar to accordion tab 2026-02-06 15:43:07 +01:00
1cd6fda01b Remove whitespace 2026-02-06 15:42:47 +01:00
b389ff1338 Add title to plot back 2026-02-06 15:42:12 +01:00
2094db061d Fix name and style of former heatmap 2026-02-06 15:41:25 +01:00
5eefe3fe02 Fix paranthesis and clean up 2026-02-06 15:40:25 +01:00
414cb97670 Add more info on accordion tab 2026-02-06 15:39:23 +01:00
c0018c6886 Add more accordion to info tab 2026-02-05 16:24:04 +01:00
dc790f554d Use plotly for better graphs 2026-02-05 16:23:45 +01:00
d31650131b Use fixed aspect ratio scaling 2026-02-05 16:21:14 +01:00
b651e71276 Sort top 3 x axis by value instead of name 2026-01-21 19:06:33 +01:00
7a7b7f9a8a Filter key "Alle" 2026-01-21 18:59:46 +01:00
b67758b11b Fix quering Bezirk substring matching 2026-01-21 18:53:09 +01:00
b3c0f7a6b8 Add reactiveVal to compared year 2026-01-21 10:28:55 +01:00
a7d3106a6b Change legend 2026-01-21 10:28:07 +01:00
6ef042d072 Change legend appearance 2026-01-21 10:27:21 +01:00
12502c4208 Add font styling to card content 2026-01-21 10:26:22 +01:00
78508ab9e7 Add changes to top3 graph 2026-01-21 10:25:15 +01:00
44ed1e71f0 Add styling to comparison graph 2026-01-21 10:24:18 +01:00
36446df4ae Make year selectable for comparison 2026-01-21 10:23:38 +01:00
49a39e8cf0 Add padding to selection text 2026-01-21 10:22:15 +01:00
c043c0120a Move unused css 2026-01-21 10:16:44 +01:00
18030bfae6 Add padding to year display 2026-01-21 10:09:17 +01:00
8ec5060277 Change accordion styles 2026-01-21 10:08:33 +01:00
e55117d944 Add specific breakpoint 2026-01-21 10:05:41 +01:00
7156e7a7d7 Remove seperater 2026-01-21 10:04:00 +01:00
f6bec4a271 change navpanel title 2026-01-21 10:02:58 +01:00
467b963cf5 Add new styles to elements 2026-01-21 10:00:25 +01:00
9637836c90 Change Color of header 2026-01-21 09:59:53 +01:00
ab8d5abd42 Fix file indentation 2026-01-08 21:47:47 +01:00
3664acde0f Add border to dropdown content 2026-01-08 19:19:19 +01:00
7f51f9fd42 Change top bar style 2026-01-08 19:18:33 +01:00
b0018a1615 Add Graph display on comparison page 2026-01-07 22:09:11 +01:00
f5c0c94e77 Add data comparison mapping 2026-01-07 22:08:50 +01:00
edb880b9b8 Add comparison selection 2026-01-07 22:08:35 +01:00
b8c0bf80ab Add hover style to heatmap mode 2026-01-07 20:20:24 +01:00
59e28905ad Unselect map area by clicking it again 2026-01-07 20:20:07 +01:00
85a40b26ca Allow map selection in heatmap mode 2026-01-07 20:19:43 +01:00
afe5f52bcc Fix typo 2026-01-07 19:43:41 +01:00
4619970bd2 Add Legend to map 2026-01-07 19:43:14 +01:00
81d63ae727 Add heatmap headline 2025-12-16 19:17:58 +01:00
3cab59b60f Remove logging 2025-12-16 19:17:43 +01:00
eade9edb17 Replace combined stadtteile with other json data 2025-12-16 19:17:16 +01:00
af7112ad87 Replace combined stadtteile with other json data 2025-12-16 19:12:59 +01:00
425f199983 Replace combined stadtteile with other json data 2025-12-16 19:08:56 +01:00
97fb3dccf1 Fix naming of Hamburg-Altstadt 2025-12-16 19:08:26 +01:00
fd903abad1 Add missing dependencies 2025-12-15 17:21:04 +01:00
494517b05a Add wiki content 2025-12-15 17:18:16 +01:00
8a4d36c790 Fix removal of polygons 2025-12-15 17:09:54 +01:00
d48f8cfb25 Rename main file to app 2025-12-15 16:44:51 +01:00
7ba1b9cd4c Move project into different files 2025-12-15 16:44:25 +01:00
7 changed files with 2948 additions and 10637 deletions

View File

@@ -1,424 +0,0 @@
library(rjson)
library(shiny)
library(bslib)
library(bsicons)
library(leaflet)
library(sf)
library(htmltools)
library(dplyr)
library(purrr)
library(ggplot2)
library(ggthemes)
library(stringr)
# 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 = str_wrap(crime, width = 25),
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: #003063; font-weight: bold;",
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(
inputId = "search",
label = tags$span(icon("search"),"Suche"),
choices = NULL,
selected = NULL,
multiple = FALSE, # Hier wahrscheinlich nur Einzelauswahl gewünscht
options = list(
placeholder = "Anfangen zu tippen...",
openOnFocus = FALSE,
allowEmptyOption = TRUE,
selectOnTab = FALSE
)
)
),
)
),
nav_panel("Vergleich",
layout_sidebar(
sidebar = sidebar(
title = "Vergleichs-Optionen",
radioButtons(
"vergleichs_modus",
"Wählen Sie den Vergleichstyp:",
choices = c(
"Straftat vs. Orte" = "ort_vergleich",
"Ort vs. Straftaten" = "straftat_vergleich"
),
selected = "ort_vergleich"
),
tags$hr(),
# 2. Dynamische Input-Felder für die Orte und Straftaten
uiOutput("vergleichs_inputs"),
# 3. Gemeinsamer Input: Das Jahr
selectizeInput(
"vergleichs_jahr",
"Jahr wählen:",
choices = c(2024, 2023),
selected = 2024
),
),
card(
card_header(uiOutput("vergleichs_titel")), # Dynamischer Titel
plotOutput("vergleichs_plot")
)
)
),
nav_panel("Wiki",
accordion(
accordion_panel(
title = "Raub, räuberische Erpressung, räuberischer Angriff auf Kraftfahrer",
card(
card_header(
h4(bs_icon("cash-stack"),
"Raub nach § 249 StGB")
),
p("Raubdelikte nach § 249 des deutschen Strafgesetzbuch sind Straftaten, bei denen jemand eine fremde bewegliche Sache wegnimmt, indem er Gewalt anwendet oder mit Gewalt droht, um sie sich oder einem Dritten rechtswidrig zuzueignen."),
tags$blockquote(
class = "blockquote",
"(1) Wer mit Gewalt gegen eine Person oder unter Anwendung von Drohungen mit gegenwärtiger Gefahr für Leib oder Leben eine fremde bewegliche Sache einem anderen in der Absicht wegnimmt, die Sache sich oder einem Dritten rechtswidrig zuzueignen, wird mit Freiheitsstrafe nicht unter einem Jahr bestraft.",
tags$br(), #Zeilenumbruch
"(2) In minder schweren Fällen ist die Strafe Freiheitsstrafe von sechs Monaten bis zu fünf Jahren."
)
),
card(
card_header(
h4(icon("money-bill-transfer"),
"Räuberische Erpressung nach § 255 StGB")
),
p("Räuberische Erpressung (§ 255 StGB) ist eine
schwere Form der Erpressung, bei der Gewalt gegen eine Person oder die Drohung mit gegenwärtiger Lebens- oder Gesundheitsgefahr eingesetzt wird, um eine Vermögensverfügung zu erzwingen, wodurch der Täter wie ein Räuber (§ 249 StGB) bestraft wird, also mit mindestens einem Jahr Freiheitsstrafe."),
tags$blockquote(
class = "blockquote",
"Wird die Erpressung durch Gewalt gegen eine Person oder unter Anwendung von Drohungen mit gegenwärtiger Gefahr für Leib oder Leben begangen, so ist der Täter gleich einem Räuber zu bestrafen."
)
)
),
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")
clearGroup(mapproxy, "selected")
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)
selected_polygon_data <- NULL
if (prefix == "bez_") {
currently_selected_bezirk(rest_of_name)
currently_selected_stadtteil(paste("Bezirk", rest_of_name))
selected_polygon_data <- geo_bezirke[geo_bezirke[["leaflet_id"]] == click_event$id,]
}
if(prefix == "std_") {
currently_selected_bezirk(get_bezirk_by_stadtteil(rest_of_name))
currently_selected_stadtteil(rest_of_name)
selected_polygon_data <- geo_stadtteile[geo_stadtteile[["leaflet_id"]] == click_event$id,]
}
req(selected_polygon_data)
#neues Polygon über die anderen legen, wenn ein bezirk angeklickt wurde
leafletProxy("hhmap") %>%
clearGroup("selected") %>%
addPolygons(
data = selected_polygon_data,
layerId = id,
color = "#003063",
fillOpacity = 0.2,
weight = 4,
group = "selected"
)
}
})
output$hhmap <- renderLeaflet({
leaflet() %>%
addProviderTiles(providers$CartoDB.Positron) %>%
addPolygons(
data = geo_bezirke,
layerId = ~leaflet_id,
group = "layer_bezirke",
color = "#003063",
fillOpacity = 0.2, # Polygon fill transparency
highlightOptions = highlightOptions(
fillOpacity = 0.4,
color = "#003063",
weight = 4,
bringToFront = TRUE
),
) %>%
addPolygons(
data = geo_stadtteile,
group = "layer_stadtteile",
layerId = ~leaflet_id,
color = "#003063",
options = pathOptions(pane = "overlayPane"), # Use a leaflet option to ensure it's hidden
weight = 3,
fillOpacity = 0.2, # Polygon fill transparency
highlightOptions = highlightOptions(
fillOpacity = 0.4,
color = "#003063",
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)) +
geom_col(width = 0.7, fill = "#e10019") + # <-- Festlegen der Farbe direkt an allen Spalten angeknüpft nicht mehr anhand der Kategorie
geom_text(
# Die Text-Ästhetik soll der Wert aus der Spalte 'Erfasst' sein
aes(label = format(Erfasst, big.mark = ".", decimal.mark = ",")),
# Platzierung: Y-Wert des Textes = Wert der Spalte + Offset
# Wir verwenden den Offset, um den Text knapp über den Balken zu platzieren
# Wenn Sie den Text IN den Balken setzen möchten, setzen Sie y=Erfasst/2
vjust = -0.5, # Vertikale Ausrichtung: Negativer Wert platziert Text über dem Punkt
size = 4,
fontface = "bold"
) +
labs(
title = "Statistisch am häufigsten polizeilich registrierte Straftaten",
x = "Straftatbestand",
y = "Anzahl erfasster Fälle"
) +
theme_pander() + #neues theme aus ggthemes packages
# NEUE ANPASSUNG: Drehen der X-Achsen-Beschriftungen
theme(
plot.background = element_rect(
color = "darkgrey", # Farbe des Rahmens
linewidth = 0.4, # Dicke des Rahmens
fill = NA # Füllung: NA = transparent
),
plot.margin = margin(t = 20, r = 20, b = 20, l = 20, unit = "pt"),
legend.position = "none",
panel.grid.major.x = element_blank(),
panel.grid.minor.x = element_blank(), # vertikale grid lines entfernen
# X-Achsen-Titel (z.B. "Straftatbestand")
axis.title.x = element_text(
face = "bold",
family = "sans",
# Fügen Sie hier einen Abstand nach OBEN hinzu
margin = margin(t = 15) # t = top (oben) in Pixeln
),
# Y-Achsen-Titel (z.B. "Anzahl erfasster Fälle")
axis.title.y = element_text(
face = "bold",
family = "sans",
# Fügen Sie hier einen Abstand nach RECHTS hinzu
margin = margin(r = 15) # r = right (rechts) in Pixeln
),
)
}, res = 100)
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)

21
app.R Normal file
View File

@@ -0,0 +1,21 @@
library(rjson)
library(shiny)
library(bslib)
library(leaflet)
library(sf)
library(htmltools)
library(dplyr)
library(purrr)
library(ggplot2)
library(ggthemes)
library(stringr)
source("global.R")
source("ui.R")
source("server.R")
options(shiny.host = '0.0.0.0')
options(shiny.port = 8888)
shinyApp(ui = ui, server = server)

668
data.json

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

159
global.R Normal file
View File

@@ -0,0 +1,159 @@
library(rjson)
library(shiny)
library(bslib)
library(leaflet)
library(sf)
library(htmltools)
library(dplyr)
library(tidyr)
library(purrr)
library(ggplot2)
library(ggthemes)
library(stringr)
library(plotly)
library(htmlwidgets)
#große JSON Datei lesen
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
}
#Wichtige Informationen aus passender Ebene/Row von der JSON Datei holen, um sie für das tibble zu nutzen
#trimws steht für trim whitespace, sorgt für sauberen string beim Jahr
#map_df steht für map dataframe
#aufgekärte Fälle sind auch im tibble drin, hätten wir also bei mehr Zeit oder für ,ehr Funktionen auch direkt nutzen können
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=""),
)
})
}
#Vorbereitung für Balkendiagram top3 Straftaten
#Sortieren und Beschränken auf die Ränge 2, 3 und 4
#Sortieren: Absteigend nach "Erfasste Fälle". Der höchste Wert ist nun in Zeile 1.
#mit slice sind explizit die Ränge 2, 3 und 4 gemeint
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 = str_wrap(crime, width = 25),
Erfasst = as.integer(row[["Erfasste Fälle"]]),
)
})
top_3_tibble <- komplettes_tibble %>%
arrange(desc(Erfasst)) %>%
slice(2:4)
return(top_3_tibble)
}
#Vorbereitung für Balkendiagramm auf Vergleichs-tab
#Location beinhaltet Stadtteile und Bezirke, um alle Auswahlmöglichkeiten in einem drop down zu haben
#die App weiß, wann es sich um einen Bezirk handelt, wenn die ersten 7 Stellen mit dem Wort "Bezirk" übereinstimmen?
#Erfasste Fälle als Zahl (integer) angeben, die Straftaten (crimes) mit Zeilenumbruch bei bestimmter Breite angeben
#Sortierung nach Anzhal der erfassten Fälle absteigend
map_data_to_plot <- function(locations, crimes, year) {
year <- as.character(trimws(year))
req(locations)
req(crimes)
req(year)
return(map_df(locations, function(loc) {
bezirk <- ""
stadtteil <- ""
if (substring(loc, 1, 6) == "Bezirk") {
bezirk <- substring(loc, 8, nchar(loc))
stadtteil <- loc
}
else {
bezirk <- get_bezirk_by_stadtteil(loc)
stadtteil <- loc
}
komplettes_tibble <- map_df(crimes, function(crime) {
row <- crime_json[[bezirk]][[stadtteil]][[crime]][[year]]
tibble(
Name = str_wrap(crime, width = 25),
Erfasst = as.integer(row[["Erfasste Fälle"]]),
Location = loc
)
})
}) %>%
arrange(desc(Erfasst))
)
}
#lass mal hier statt delikt crime nehmen oder eben die anderen crimes delikt, oder gibt es einen Grund dass es anders heißt/auf deutsch ist?
#Mit dieser Funktion entnehmen wir die gewünschten Einträge, Straftat, Jahr, Erfasste Fälle direkt aus der JSON Datei und erhalten so unseren dataframe, den wir wofür nutzen können?
#sicherer Zugriff (verhindert Fehler bei fehlenden Einträgen)
get_intensity_df <- function(crime_json, delikt, jahr = "2024", feld = "Erfasste Fälle") {
do.call(rbind, lapply(names(crime_json), function(bezirk) {
stadtteile <- crime_json[[bezirk]]
data.frame(
bezirk = bezirk,
stadtteil = names(stadtteile),
intensity = sapply(stadtteile, function(st) {
val <- st[[delikt]][[jahr]][[feld]]
if (is.null(val)) NA else val
}),
row.names = NULL
)
}))
}
#GeoJson für Bezirke
#st_transform sorgt für die Umwandlung der Geo-Daten in das Standard-Koordinatensystem WGS 84, csr bedeutet Koordinatenreferenzsystem
#mit paste("bez_") bilden wir die jeweiligen Präfixe für Stadtteil und Bezirk, damit aufgrund dieses Merkmals die app die beiden Typen voneinander unterscheiden kann
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 für 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))
list_of_crimes <- sort(c(unique(unlist(
lapply(crime_json, function(a) {
unlist(
lapply(a, function(b) {
names(b)
}),
use.names = FALSE
)
}),
use.names = FALSE
)), ""))
#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
#unlist() vereint alle diese Vektoren zu einem einzigen Vektor.
#Ergebnis: c("aa1", "aa2", "bb1", "bb2", "cc1")
#unique(): auf Nummer sicher gehen, dass die Stadtteile alle eindeutig sind.
auswahlmöglichkeiten <- crime_json %>%
map(names) %>%
unlist() %>%
unique() %>%
sort() %>%
setdiff("Alle")

376
server.R Normal file
View File

@@ -0,0 +1,376 @@
get_map_layer_prefix <- function(text){
return(substring(text, 0, 4))
}
get_map_layer_name <- function(text) {
return(substring(text, 5))
}
#Server: Verarbeitung von Benutzereingaben und Daten
server <- function(input, output, session) {
currently_selected_bezirk <- reactiveVal("")
currently_selected_stadtteil <- reactiveVal("")
currently_selected_maptype <- reactiveVal("Bezirke")
currently_selected_heatmap_crime <- reactiveVal("Allgemeine Verstöße gem. § 29 BtMG -Konsumentendelikte-")
currently_compared_location <- reactiveVal(c(""))
currently_compared_crimes <- reactiveVal(c(""))
currently_compared_year <- reactiveVal(c("2024", "2023"))
#Aktualisieren der Auswahlmöglichkeiten bei Änderungen in den Daten?
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$heatmap, {
currently_selected_heatmap_crime(input$heatmap)
})
observeEvent(input$rd_maptype, {
currently_selected_maptype(input$rd_maptype)
})
observeEvent(input$vergleichs_jahr, {
currently_compared_year(input$vergleichs_jahr)
})
#Hamburg Karte in leaflet
#Info über Bezirk und Stadtteil je nach ausgewählter Ebene
#Heatmap Polygone mit bestimmter Farbpalette
#Polygone auf Heatmap Ebene hinzufügen
#Legende in Heatmap hinzufügen, falls Heatmap ausgewählt
#Stadtteil und Bezirk Ebenen verbergen falls Heatmap Polygone und Ebene ausgewählt sind
#Stadtteil Ebene verbergen, falls Bezirk ausgewählt ist
#Bezirk Ebene verbergen, falls Stadtteil ausgewählt ist
observe({
maptype <- currently_selected_maptype()
heatmap <- currently_selected_heatmap_crime()
mapproxy <- leafletProxy("hhmap")
clearGroup(mapproxy, "selected")
clearGroup(mapproxy, "layer_heatmap")
removeControl(mapproxy, "heatmap_legend")
if (heatmap != "") {
hideGroup(mapproxy, "layer_bezirke")
hideGroup(mapproxy, "layer_stadtteile")
heatmap_polygons <- if(maptype == "Bezirke") {
geo_bezirke %>%
mutate(bezirke_join = paste("Bezirk", bezirk)) %>%
left_join(get_intensity_df(crime_json, heatmap), by = c("bezirke_join" = "stadtteil"))
} else {
geo_stadtteile %>%
left_join(get_intensity_df(crime_json, heatmap), by = "stadtteil")
}
pal <- colorNumeric(
palette = "YlOrRd",
domain = heatmap_polygons$intensity,
na.color = "transparent",
alpha = 1
)
addPolygons(mapproxy,
data = heatmap_polygons,
layerId = paste("heat_", heatmap_polygons$leaflet_id, sep = ""),
group = "layer_heatmap",
label = get_map_layer_name(heatmap_polygons$leaflet_id),
color = "#003063",
fillColor = ~pal(heatmap_polygons$intensity),
weight = 3,
fillOpacity = 0.8,
highlightOptions = highlightOptions(
weight = 6,
bringToFront = TRUE
),
)
pal_legend <- colorNumeric(
palette = "YlOrRd",
domain = heatmap_polygons$intensity
)
addLegend(mapproxy,
pal = pal_legend,
opacity = 0.8,
values = heatmap_polygons$intensity,
title = paste("Fallzahlen:", heatmap),
position = "bottomright",
layerId = "heatmap_legend"
)
} else {
if (maptype == "Bezirke"){
hideGroup(mapproxy, "layer_stadtteile")
showGroup(mapproxy, "layer_bezirke")
}
else {
hideGroup(mapproxy, "layer_bezirke")
showGroup(mapproxy, "layer_stadtteile")
}
}
})
#Überprüfen, ob ein Polygon angeklickt wurde
#Eine bereits ausgewählte Ebene (durch Mausklick) abwählen, indem die Ebene auf die Auswahl hin überprüft wird
#Pipe Operator %>% nutzen um besseren Lesefluss und keine Verschachtelung zu haben
#Überprüfen, ob die ausgewählte Ebene ein Bezirk oder Stadtteil ist, indem die Präfixe bez_ und std_ auf Übereinstimmung abgeglichen werden und die id, also den Namen des Stadtteils oder Bezirks zurückgeben?
#mit req(slected_polygon_data) ein neues Polygon über die anderen legen, wenn ein Bezirk angeklickt wurde?
observeEvent(input$hhmap_shape_click, {
click_event <- input$hhmap_shape_click
if (!is.null(click_event$id)) {
if(click_event$group == "selected") {
currently_selected_bezirk("")
currently_selected_stadtteil("")
leafletProxy("hhmap") %>%
clearGroup("selected")
return()
}
clicked_polygon_id <- click_event$id
clicked_polygon_id <- sub("^heat_", "", clicked_polygon_id)
prefix <- get_map_layer_prefix(clicked_polygon_id)
rest_of_name <- get_map_layer_name(clicked_polygon_id)
selected_polygon_data <- NULL
if (prefix == "bez_") {
currently_selected_bezirk(rest_of_name)
currently_selected_stadtteil(paste("Bezirk", rest_of_name))
selected_polygon_data <- geo_bezirke[geo_bezirke[["leaflet_id"]] == clicked_polygon_id,]
}
if(prefix == "std_") {
currently_selected_bezirk(get_bezirk_by_stadtteil(rest_of_name))
currently_selected_stadtteil(rest_of_name)
selected_polygon_data <- geo_stadtteile[geo_stadtteile[["leaflet_id"]] == clicked_polygon_id,]
}
req(selected_polygon_data)
leafletProxy("hhmap") %>%
clearGroup("selected") %>%
addPolygons(
data = selected_polygon_data,
layerId = id,
label = selected_polygon_data$bezirk,
color = "#003063",
fillOpacity = 0.2,
weight = 4,
group = "selected"
)
}
})
#Hier wird die richtige Hamburg-Karte als output ausgewiesen, wir nutzen die CartoDB.Positron
#Es müssen separate Polygone für die Bezirke und Stadtteil Koordinaten angelegt werden
#die erste Color Option färbt die Fläche der Polygone
#die zweite Color Option färbt die Konturen (daher highlightoptions) der Polygone
#overlaypane sorgt für das Darüberlegen der Stadtteil Ebene auf die Bezirk Ebene
#setview ist der Kartenausschnitt, der voreingestellt sein soll, also hier Hamburg als Zentrum
#div() steht für division und sorgt für das Styling oder Layout von Inhalten, hier: die Inhalte "Bezirk:" und "Stadtteil:"
#bez und req(bez) sowie sdt und req(sdt) (müsste es nicht auch std sein, so wie oben? Nur wegen der Einheitlichkeit und so) führen dazu, dass der richtige Bezirk bzw. Stadtteil, die angeklickt wurden, auch als Auswahl neben der Karte rechts auftauchen
output$hhmap <- renderLeaflet({
leaflet(options = leafletOptions(zoomControl = FALSE)) %>%
addProviderTiles(providers$CartoDB.Positron) %>%
addPolygons(
data = geo_bezirke,
layerId = ~leaflet_id,
group = "layer_bezirke",
label = get_map_layer_name(geo_bezirke$leaflet_id),
color = "#003063",
fillOpacity = 0.2,
highlightOptions = highlightOptions(
fillOpacity = 0.4,
color = "#003063",
weight = 4,
bringToFront = TRUE
),
) %>%
addPolygons(
data = geo_stadtteile,
group = "layer_stadtteile",
label = get_map_layer_name(geo_stadtteile$leaflet_id),
layerId = ~leaflet_id,
color = "#003063",
options = pathOptions(pane = "overlayPane"),
weight = 3,
fillOpacity = 0.2,
highlightOptions = highlightOptions(
fillOpacity = 0.4,
color = "#003063",
weight = 4,
bringToFront = TRUE
),
) %>%
setView(
lng = 9.98716634776887,
lat = 53.5488439196432,
zoom = 11
) %>%
onRender(
"function(el, x) {
L.control.zoom({
position:'topright'
}).addTo(this);
}")
})
output$txt_map_selection_bezirk <- renderUI({
bez <- currently_selected_bezirk()
req(bez)
div(
style = "margin-top: 5px; margin-bottom: 0px; display: flex; align-items: center; gap: 8px;",
tags$span("Bezirk:", style = "font-weight: 700; font-size: 0.95rem;"),
tags$span(bez, style = "font-weight: 400; font-size: 0.95rem;")
)
})
output$txt_map_selection_stadtteil <- renderUI({
sdt <- currently_selected_stadtteil()
req(sdt)
div(
style = "margin-top: 2px; margin-bottom: 15px; display: flex; align-items: center; gap: 8px;",
tags$span("Stadtteil:", style = "font-weight: 700; font-size: 0.95rem;"),
tags$span(sdt, style = "font-weight: 400; font-size: 0.95rem;")
)
})
#Hier geht das Balkendiagramm auf dem Tab der Hamburg Karte los
#Wir nutzen ein tibble, um es etwas zu vereinfachen
#In diesem Diagramm bilden wir nur das Jahr 2024 ab, um es übersichtlich und prägnant zu halten
#Da bei allen Stadtteilen und Bezirken die Straftat "Straftaten insgesamt" am höchsten ist, ignorieren wir diesen Eintrag, der bei allen Orten auf der 1. Ebene, bzw bei Computern der 0. Ebene ist, also die Ebenen > 0
#Wir benutzen nicht mehr ggplot, sondern plotly, weil wir dort mehr Möglichkeiten haben und das Diagramm nicht so starr ist wie bei ggplot
#str_wrap sorgt für einen Zeielnumbruch, bei der Starftat falls, eine bestimmte Breite, hier von 15, überschritten wurde
#wir haben vorfedinierte Farben für bessere Übersichtlichkeit
#die Anzahl der erfassten Fälle machen wir als label außerhalb der Balken besser sichtbar
#mit <br> suggerieren wir Zeilenumbrüche, um kein Problem mit den Abständen nach oben oder unten zu bekommen
output$grph_top3 <- renderPlotly({
data_tibble <- map_data_to_top3_plot(
bezirk = currently_selected_bezirk(),
stadtteil = currently_selected_stadtteil(),
year = "2024"
)
req(nrow(data_tibble) > 0)
plot_ly(
data = data_tibble,
x = ~factor(
str_wrap(Name, width = 15),
levels = str_wrap(Name[order(-Erfasst)], width = 15)
),
y = ~Erfasst,
name = "Top 3",
text = format(data_tibble$Erfasst, big.mark = ".", decimal.mark = ","),
textposition = "outside",
type = "bar",
marker = list(
color = c("#698eb5", "#a2c1e0", "#cfe5fa")
)
) %>%
layout(
autosize = TRUE,
separators = ",.",
xaxis = list(
title = "<br><br><b>Straftat<b>",
tickangle = 0,
ticklabeloverflow = "allow"
),
yaxis = list(
title = "<b>Anzahl erfasster Fälle<b>",
tickformat=",.0d"
),
margin = list(pad = 5)
) %>%
config(
staticPlot = TRUE
)
})
observeEvent(input$vergleich_location, {
currently_compared_location(c(input$vergleich_location))
})
observeEvent(input$vergleich_straftat, {
currently_compared_crimes(input$vergleich_straftat)
})
#Hier geht das größere Balkendiagramm auf der Vergleichsseite los
#Auswahlmöglichkeiten bei den Vergleichsoptionen verknüpfen
#Farbpalette für die Balken, um nur Blautöne zu verwenden und die Aufmerksamkeit nicht abzulenken
#Kompromisslösung bei mehr als 6 Balken pro Ort, also mehr als 6 Straftaten im Vergleich: dann gehen die Fraben von vorne los
#Sortierung innerhalb des ausgewählten Orten absteigend und generell zwischen den Orten absteiegnd für gute Übersicht
output$vergleich_balkendiagramm <- renderPlotly({
data_tibble <- map_data_to_plot(
locations = currently_compared_location(),
crimes = currently_compared_crimes(),
year = currently_compared_year()
)
location_totals <- data_tibble %>%
group_by(Location) %>%
summarise(Total = sum(Erfasst)) %>%
arrange(Total)
data_tibble <- data_tibble %>%
mutate(Location = factor(Location, levels = location_totals$Location))
wide_data <- data_tibble %>%
pivot_wider(names_from = Name, values_from = Erfasst, values_fill = 0)
first_loc <- levels(data_tibble$Location)[length(levels(data_tibble$Location))]
crime_order <- wide_data %>%
filter(Location == first_loc) %>%
select(-Location) %>%
t() %>%
as.data.frame() %>%
arrange(V1) %>%
rownames()
wide_data <- wide_data %>%
select(Location, all_of(crime_order))
blue_palette <- colorRampPalette(tail(RColorBrewer::brewer.pal(8, "Blues"), 6))
plot <- plot_ly() %>%
layout(
barmode = 'group',
separators = ",.",
colorway = blue_palette(6),
xaxis = list(
title = "<b>Anzahl erfasster Fälle<b>",
tickformat=",.0d"
),
yaxis = list(
title = "<b>Straftat<b>",
tickangle = "-90"
),
legend = list(
orientation = "h",
x = 0.5,
itemwidth = 40,
xanchor = "center",
yanchor = "top",
valign = "top"
)
) %>%
config(
staticPlot = TRUE
)
for(i in seq_along(crime_order)) {
crime <- crime_order[i]
plot <- plot %>%
add_trace(
x = wide_data[[crime]],
y = wide_data$Location,
name = crime,
text = format(wide_data[[crime]], big.mark = ".", decimal.mark = ","),
textposition = "outside",
type = "bar",
orientation = "h",
legendrank = length(crime_order) - i
)
}
plot
})
}

488
ui.R Normal file
View File

@@ -0,0 +1,488 @@
library(bsicons)
source("global.R")
ui <- function() {
page_fillable(
h1("Polizeiliche Kriminalstatistik Hamburg",
style = "
color: #5181b5;
background: #fff;
padding: 20px 0px 20px 20px;
font-weight: 600;
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
border-radius: 0 0 12px 12px;
margin-left: 20px;
margin-right: 20px;
",
class = "display-4"
),
tags$head(
tags$style(
HTML("
body {
background-color: #f4f7f9;
transform-origin: 0 0;
}
.html-fill-container > .html-fill-item.bslib-mb-spacing {
border-style: none;
box-shadow: none;
}
.bslib-card .card-header {
background: #f4f7f9;
padding: 8px 20px 20px 20px;
border: none;
}
.nav.nav-tabs.card-header-tabs {
display: flex;
gap: 20px;
margin: 0 0 -8px 0;
}
.nav-item, .nav-item:focus {
background: var(--bs-card-color);
outline: none;
border-radius: 12px;
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
overflow: hidden;
will-change: transform, box-shadow;
}
.nav-item:hover {
outline: none;
box-shadow: 0 8px 16px rgba(0,0,0,0.3);
transform: translateY(-4px);
transition: 0.3s;
}
.nav-link.active {
outline: none;
border-radius: 12px;
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
}
.tab-content > .active.html-fill-container {
background: #f4f7f9;
margin: 0;
padding: 0;
}
.bslib-sidebar-layout > .sidebar {
height: 100%;
outline: none;
border-right: none;
padding-bottom: 20px;
padding-right: 0;
}
.sidebar-content.bslib-gap-spacing {
height: 100%;
background: #fff;
border-radius: 0 12px 12px 0;
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
}
.collapse-toggle[aria-expanded=\"false\"] {
border-radius: 12px;
padding: 12px;
width: 40px;
height: 40px;
border-12px solid white;
background: #fff;
box-shadow: 4px 4px 8px rgba(0,0,0,0.4);
margin-left: 20px;
top: 0 !important;
left: 0 !important;
}
.collapse-toggle:hover {
background: #eee !important;
}
.main.bslib-gap-spacing.html-fill-container {
outline: none;
padding-top: 0;
padding-bottom: 20px;
padding-left: 20px;
padding-right: 20px;
}
.bslib-grid-item.bslib-gap-spacing.html-fill-container {
background: #fff;
border-radius: 12px;
border: 10px solid white;
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
overflow: hidden;
}
.bslib-card .card-body {
padding: 0;
}
#hhmap {
border-radius: 12px;
}
#grph_top3 {
height: 100% !important;
}
.leaflet .legend {
opacity: 1;
background: white;
}
.selectize-input {
border-radius: 12px;
border: 1px solid #e2e8f0;
box-shadow: inset 1px 1px 3px rgba(0,0,0,0.02);
padding: 10px 15px;
}
.selectize-input.focus {
border-radius: 12px;
}
.selectize-dropdown-content .option {
border-bottom: 1px solid #dee2e6;
padding: 8px 10px;
}
.selectize-control.multi .selectize-input:after {
content: ' ';
display: block;
position: absolute;
top: 50%;
right: 15px;
margin-top: -3px;
width: 0;
height: 0;
border-style: solid;
border-width: 5px 5px 0 5px;
border-color: #333 transparent transparent transparent;
}
.selectize-control.multi .selectize-input {
padding-right: 35px !important;
}
.selectize-control.multi .selectize-input > div {
border-radius: 12px;
padding: 2px 16px 2px 16px;
background: #cfe5fa;
}
.selectize-control.multi .selectize-input > div.active {
background: #a2c1e0;
color: #000;
}
.selectize-dropdown.plugin-dropdown_header .selectize-dropdown-header {
display: none;
}
#vergleich_balkendiagramm {
border-radius: 12px;
overflow: hidden;
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
}
.accordion-scroll-container {
background-color: #f4f7f9;
padding: 0 20px 20px 20px;
}
.accordion {
border: none;
background: #transparent;
}
.accordion-item {
border: none;
margin-bottom: 15px !important; /* Abstand zwischen den Elementen für den Schweb-Effekt */
border-radius: 12px;
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
overflow: hidden;
}
.accordion-item:first-of-type, .accordion-item:last-of-type {
border-radius: 12px;
}
.accordion-item:hover {
box-shadow: 0 4px 8px rgba(0,0,0, 0.3);
}
.accordion-button {
font-weight: 600;
font-size: 16px !important;
color: #1a1a1a !important;
background-color: #ffffff !important;
color: #000;
border: none !important;
box-shadow: none !important;
}
.accordion-body {
padding: 20px;
font-size: 14px;
line-height: 1.6;
color: #555;
border-top: 1px solid #f0f0f0; /* Dezente Trennung zum Header */
}
"
)
)
),
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%; padding: 5px 10px 0 0;",
# 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: 2px 8px;
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 Standard Card-Umrandungen aus der App
)
),
div(
style ="padding: 0 15px; margin-top: 0px; overflow: hidden; display: block;",
h5("Statistisch am häufigsten polizeilich registrierte Straftaten",
style = "font-weight: 600; margin-top: 0px; margin-bottom: 25px; letter-spacing: -0.5px;"
),
uiOutput("txt_map_selection_bezirk"),
uiOutput("txt_map_selection_stadtteil")
),
plotlyOutput("grph_top3"),
),
col_widths = breakpoints(
sm = c(12, 12), # Auf kleinen Bildschirmen: untereinander (100% Breite)
md = c(8, 4) # Auf Desktop: nebeneinander (8 zu 4 Aufteilung)
)
),
sidebar = sidebar(
radioButtons(
inputId = "rd_maptype",
label = "Kartentyp",
choices = c("Bezirke", "Stadtteile"),
selected = "Bezirke"
),
selectizeInput(
inputId = "search",
label = tags$span(icon("search"),"Suche"),
choices = NULL,
selected = NULL,
multiple = FALSE, # Hier wahrscheinlich nur Einzelauswahl gewünscht
options = list(
placeholder = "Anfangen zu tippen...",
openOnFocus = FALSE,
allowEmptyOption = TRUE,
selectOnTab = FALSE,
plugins = list('dropdown_header')
)
),
tags$hr(style = "border-top: 2px solid #d7dbde; margin: 20px 0 0 0;"), # The divider line
div(
style = "margin-top: 20px; margin-bottom: 0px;",
tags$h5(
"Geografische Verteilung nach Fallzahlen",
style = "font-weight: 600; line-height: 1.1; margin-bottom: 5px;")
),
selectizeInput(
inputId = "heatmap",
label = "Wähle eine Straftat:",
choices = list_of_crimes,
selected = NULL,
options = list(
placeholder = "Keine Auswahl",
plugins = list('clear_button')
)
)
)
)
),
nav_panel("Interaktiver Vergleich",
layout_sidebar(
sidebar = sidebar(
title = "Vergleichs-Optionen",
width = 400,
uiOutput("vergleichs_inputs"),
selectizeInput(
"vergleich_location",
"Zu vergleichende Orte",
choices = auswahlmöglichkeiten,
multiple = TRUE
),
selectizeInput(
"vergleich_straftat",
"Zu vergleichende Straftaten",
choices = list_of_crimes,
multiple = TRUE
),
# 3. Gemeinsamer Input: Das Jahr
selectizeInput(
"vergleichs_jahr",
"Jahr wählen:",
choices = c(2024, 2023),
selected = 2024
)
),
plotlyOutput("vergleich_balkendiagramm")
)
),
#accordion schafft diese aufklappbaren drop down Inhalte
#wenn in einem accordion panel nur ein Eintrag existiert, wird nur eine Verschachtelung vorgenommen: accordion_panel -> accordion
#Sobald wir mehrere Einträge haben, gibt es eine etwas vertieftere Verschachtelung von accordion_panel -> accordion -> accordion_panel (wie bei Diebstahl der Fall)
#open = FALSE schließt alle Panels beim Start
nav_panel(
title = "Weiterführende Informationen",
div(
class = "accordion-scroll-container",
accordion(
open = FALSE,
accordion_panel(
title = h3("Allgemeine Verstöße gem. § 29 BtMG -Konsumentendelikte-", style = "margin: 0;"),
value = "headline_konsumenten",
accordion(
open = FALSE,
title = NULL,
value = "konsumenten",
p("Zu den allgemeinen Verstößen gegen § 29 BtMG zählen unter anderem die Fälle des Absatzes 1 Satz 1 Nr. 1. Dazu gehört, wer Betäubungsmittel unerlaubt anbaut, herstellt, abgibt, erwirbt, sich auf sonstige Weise verschafft oder unerlaubt für deren Erwerb oder Gebrauch wirbt.")
)
),
accordion_panel(
title = h3("Diebstahl", style = "margin: 0;"),
value = "headline_diebstahl_insg",
accordion(
open = FALSE,
accordion_panel(
title = h4("Diebstahl insgesamt", style = "margin: 0;"),
value = "headline_diebstahl",
p("Unter Diebstahl insgesamt werden alle Formen des Diebstahls zusammengefasst, bei denen eine fremde bewegliche Sache in der Absicht weggenommen wird, sie sich oder einem Dritten rechtswidrig zuzueignen (Grundtatbestand § 242 StGB). Der Summenschlüssel bündelt sowohl einfache als auch qualifizierte Diebstahlsdelikte, etwa mit Waffen, bei Bandentätigkeit oder unter Ausnutzung besonderer Umstände.")
),
accordion_panel(
title = h4("Wohnungseinbruchsdiebstahl", style = "margin: 0;"),
value = "headline_wohnungseinbruchsdiebstahl",
p("Der Wohnungseinbruchsdiebstahl nach § 244 Absatz 1 Nummer 3 StGB erfasst Fälle, in denen der Täter in eine Wohnung einbricht, einsteigt oder mit einem falschen Schlüssel bzw. unter Ausnutzung eines anderen unbefugten Zugangs eindringt, um dort zu stehlen.")
),
accordion_panel(
title = h4("Diebstahl von Kraftwagen", style = "margin: 0;"),
value = "headline_diebstahl_kfz",
p("Diebstahl von Kraftwagen umfasst die rechtswidrige Wegnahme von Kraftfahrzeugen wie Pkw, Lkw oder Motorrädern, die zum selbständigen Fahren bestimmt sind.")
),
accordion_panel(
title = h4("Diebstahl an/aus Kraftfahrzeugen", style = "margin: 0;"),
value = "headline_diebstahl_aus_kfz",
p("Beim Diebstahl an oder aus Kraftfahrzeugen werden keine Fahrzeuge selbst entwendet, sondern Gegenstände, die an ihnen angebracht oder in ihnen enthalten sind, etwa Felgen, Navigationsgeräte oder im Fahrzeug zurückgelassene Wertsachen.")
),
accordion_panel(
title = h4("Diebstahl von Fahrrädern", style = "margin: 0;"),
value = "headline_diebstahl_fahrräder",
p("Der Diebstahl von Fahrrädern erfasst die unbefugte Wegnahme von Fahrrädern aller Art, einschließlich EBikes, soweit diese rechtlich nicht als Kraftfahrzeuge eingestuft sind.")
),
accordion_panel(
title = h4("Ladendiebstahl", style = "margin: 0;"),
value = "headline_ladendiebstahl",
p("Ladendiebstahl beschreibt Diebstähle in oder an Verkaufsräumen von Handelsbetrieben, bei denen Waren ohne Bezahlung an sich genommen werden.")
),
accordion_panel(
title = h4("Taschendiebstahl", style = "margin: 0;"),
value = "headline_taschendiebstahl",
p("Taschendiebstahl umfasst Diebstähle aus Kleidung oder direkt am Körper getragenen Behältnissen wie Taschen, Rucksäcken oder Geldbörsen, die meist unbemerkt in Menschenmengen oder öffentlichen Verkehrsmitteln begangen werden.")
)
)
),
accordion_panel(
title = h3("Gewaltkriminalität", style = "margin: 0;"),
value = "headline_gewaltkriminalität",
accordion(
open = FALSE,
title = NULL,
value = "gewaltkriminalität",
p("Unter Gewaltkriminalität sind mehrere Deliktsbereiche umfasst. Dazu zählen Mord nach § 211 StGB, Totschlag und Tötung auf Verlangen nach §§ 212, 213, 216 StGB, Vergewaltigung, sexuelle Nötigung und sexueller Übergriff im besonders schweren Fall einschl. mit Todesfolge nach §§ 177, 178 StGB, Raub, räuberische Erpressung und räuberischer Angriff auf Kraftfahrer gem. §§ 249 bis 252, 255, 316a StGB, Körperverletzung mit Todesfolge gem. §§ 227, 231 StGB, Gefährliche und schwere Körperverletzung, Verstümmelung weiblicher Genitalien gem. §§ 224, 226, 226a, 231 StGB, Erpresserischer Menschenraub gem. § 239a StGB, Geiselnahme gem. § 239b StGB und Angriff auf den Luft- und Seeverkehr gem. § 316c StGB.")
)
),
accordion_panel(
title = h3("Illegaler Handel mit / Schmuggel von Rauschgiften gem. § 29 BtMG", style = "margin: 0;"),
value = "headline_schmuggel",
accordion(
open = FALSE,
title = NULL,
value = "schmuggel",
p("Illegalen Handel mit/Schmuggel von Rauschgiften betriebt, wer gemäß § 29 I S. 1 Nr. 1 BtMG unerlaubt mit Betäubungsmitteln Handel treibt, einführt, ausführt, veräußert und in den Verkehr bringt.")
)
),
accordion_panel(
title = h3("Körperverletzung", style = "margin: 0;"),
value = "headline_körperverletzung",
accordion(
open = FALSE,
accordion_panel(
title = h4("Körperverletzung insgesamt", style = "margin: 0;"),
value = "körperverletzung_223",
p("Unter Körperverletzung versteht sich nach § 223 I HS 1 StGB, wenn eine andere Person körperlich misshandelt oder an der Gesundheit geschädigt wird. Unter den Deliktsummenschlüssel 'Körperverletzung insgesamt' fallen auch unter anderem gefährliche und schwere Körperverletzung.")
),
accordion_panel(
title = h4("Gefährliche Körperverletzung", style = "margin: 0;"),
value = "g_körperverletzung",
p("Gefährliche Körperverletzung ist nach § 224 I StGB eine Körperverletzung durch die Beibringung von Gift oder anderen gesundheitsschädlichen Stoffen, mittels einer Waffe oder eines anderen gefährlichen Werkzeugs, mittels eines hinterlistigen Überfalls, mit einem anderen Beteiligten gemeinschaftlich oder mittels einer das Leben gefährdenden Behandlung.")
),
accordion_panel(
title = h4("Schwere Körperverletzung", style = "margin: 0;"),
value = "s_körperverletzung",
p("Schwere Körperverletzung hat nach § 226 I StGB zur Folge, dass die verletzte Person das Sehvermögen auf einem Auge oder beiden Augen, das Gehör, das Sprechvermögen oder die Fortpflanzungsfähigkeit, ein wichtiges Glied des Körpers verliert oder dauernd nicht mehr gebrauchen kann oder in erheblicher Weise dauernd entstellt wird oder in Siechtum, Lähmung oder geistige Krankheit oder Behinderung verfällt.")
)
)
),
accordion_panel(
title = h3("Raubdelikte", style = "margin: 0;"),
value = "headline_raub",
accordion(
open = FALSE,
accordion_panel(
title = h4("Raub nach §§ 249ff. StGB", style = "margin: 0;"),
value = "raub_249",
p("Raubdelikte sind Straftaten, bei denen jemand eine fremde bewegliche Sache wegnimmt, indem er Gewalt anwendet oder mit Gewalt droht, um sie sich oder einem Dritten rechtswidrig zuzueignen.")
),
accordion_panel(
title = h4("Räuberische Erpressung nach § 255 StGB", style = "margin: 0;"),
value = "raub_255",
p("Räuberische Erpressung ist eine schwere Form der Erpressung, bei der Gewalt gegen eine Person oder die Drohung mit gegenwärtiger Lebens- oder Gesundheitsgefahr eingesetzt wird, um eine Vermögensverfügung zu erzwingen, wodurch der Täter wie ein Räuber (§ 249 StGB) bestraft wird, also mit mindestens einem Jahr Freiheitsstrafe.")
),
accordion_panel(
title = h4("Räuberischer Angriff auf Kraftfahrer nach § 316a StGB", style = "margin: 0;"),
value = "raub_316a",
p("Ein räuberischer Angriff auf Kraftfahrer ist ein Verbrechen, bei dem ein Täter unter Ausnutzung der besonderen Situation im Straßenverkehr Gewalt gegen den Fahrer oder Mitfahrer anwendet, um einen Raub, räuberischen Diebstahl oder eine räuberische Erpressung zu begehen.")
),
accordion_panel(
title = h4("Sonstige Raubüberfälle auf Straßen, Wegen oder Plätzen", style = "margin: 0;"),
value = "sonst_raub_249",
p("Sonstige Raubüberfälle auf Straßen, Wegen oder Plätzen gehören zu Raubdelikten in diesem Fall ebenfalls zur Straßenkriminalität. Die allgemeine Raubdefinition ergibt sich aus § 249 StGB.")
)
)
),
accordion_panel(
title = h3("Rauschgiftdelikte", style = "margin: 0;"),
value = "headline_rauschgift",
accordion(
open = FALSE,
title = NULL,
value = "rauschgift",
p("Rauschgiftdelikte sind Straftaten, die gegen das Betäubungsmittelgesetz (BtMG) verstoßen, aber nicht unter die spezielleren Tatbestände des § 29 BtMG fallen. Dies betrifft insbesondere Fälle, in denen eine Person über 21 Jahren einer Person unter 18 Jahren Betäubungsmittel unerlaubt gibt, verabreicht oder zum unmittelbaren Konsum überlässt. Darüber hinaus betrifft es die Fälle, in denen Betäubungsmittel als Mitglied einer Bande gehandelt werden oder die Abgabe oder das Überlassen von Betäubungsmitteln zum leichtfertigen Tod einer Person führt.")
)
),
accordion_panel(
title = h3("Sachbeschädigung", style = "margin: 0;"),
value = "headline_sachbeschädigung",
accordion(
open = FALSE,
title = NULL,
value = "sachbeschädigung",
p("Sachbeschädigung gem. § 303 I StGB ist die rechtswidrige Beschädigung oder Zerstörung einer fremden Sache. Darunter zählen auch digitale Daten und die Datenverarbeitung sowie Bauwerke und wichtige Arbeitsmittel.")
)
),
accordion_panel(
title = h3("Straftaten gegen das Aufenthaltsgesetz, das Asylgesetz und das Freizügigkeitsgesetz/EU", style = "margin: 0;"),
value = "headline_aufenth",
accordion(
open = FALSE,
title = NULL,
value = "aufenth",
p("Hierunter fallen die in § 95 Aufenthaltsgesetz (AufenthG) genannten Fälle. Unter anderem zählen dazu Personen, die sich ohne erforderlichen Aufenthaltstitel im Bundesgebiet aufhalten. Wer illegal einreist, gegen behördlichen Ausreise-, Aufenthalts- oder Meldeanordnung widerspricht und falsche oder unvollständige Angaben im Zusammenhang des Aufenthaltsrecht oder Asylrechts tätigt.")
)
)
)
)
)
)
)
}