Compare commits
76 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5c0bfc24db | |||
| 0860828339 | |||
| 11228e8941 | |||
| 3f72ba1679 | |||
| 7da43631ff | |||
| 9ebc754c44 | |||
| 2fd7e150f3 | |||
| 09ef5ecff0 | |||
| a7f7aafd1f | |||
| 5700c61421 | |||
| 08e883533f | |||
| cee19cefe0 | |||
| 688573fe02 | |||
| 2af26d5f54 | |||
| f2fe7d105c | |||
| 86d88b94fb | |||
| 4fc9efcf64 | |||
| 6ee2b5a1d2 | |||
| 6db3a49f6f | |||
| 077f2576e2 | |||
| d0f36ce7c5 | |||
| 93123d0ff6 | |||
| fdf2cf8331 | |||
| 10d0a9ad05 | |||
| 3c74b98827 | |||
| bc7e9ad756 | |||
| 5a6882b500 | |||
| 1cd6fda01b | |||
| b389ff1338 | |||
| 2094db061d | |||
| 5eefe3fe02 | |||
| 414cb97670 | |||
| c0018c6886 | |||
| dc790f554d | |||
| d31650131b | |||
| b651e71276 | |||
| 7a7b7f9a8a | |||
| b67758b11b | |||
| b3c0f7a6b8 | |||
| a7d3106a6b | |||
| 6ef042d072 | |||
| 12502c4208 | |||
| 78508ab9e7 | |||
| 44ed1e71f0 | |||
| 36446df4ae | |||
| 49a39e8cf0 | |||
| c043c0120a | |||
| 18030bfae6 | |||
| 8ec5060277 | |||
| e55117d944 | |||
| 7156e7a7d7 | |||
| f6bec4a271 | |||
| 467b963cf5 | |||
| 9637836c90 | |||
| ab8d5abd42 | |||
| 3664acde0f | |||
| 7f51f9fd42 | |||
| b0018a1615 | |||
| f5c0c94e77 | |||
| edb880b9b8 | |||
| b8c0bf80ab | |||
| 59e28905ad | |||
| 85a40b26ca | |||
| afe5f52bcc | |||
| 4619970bd2 | |||
| 81d63ae727 | |||
| 3cab59b60f | |||
| eade9edb17 | |||
| af7112ad87 | |||
| 425f199983 | |||
| 97fb3dccf1 | |||
| fd903abad1 | |||
| 494517b05a | |||
| 8a4d36c790 | |||
| d48f8cfb25 | |||
| 7ba1b9cd4c |
424
IT Shiny App.R
424
IT Shiny App.R
@@ -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
21
app.R
Normal 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)
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
159
global.R
Normal file
159
global.R
Normal 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
376
server.R
Normal 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
488
ui.R
Normal 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 E‑Bikes, 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.")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user