Skip to content
Snippets Groups Projects
aktualisiere_karten.R 21.39 KiB
#' aktualisiere_karten.R
#' 
#' Die Funktionen, um die Grafiken zu aktualisieren - und Hilfsfunktionen
#' 

#---- Generiere den Fortschrittsbalken ----


generiere_auszählungsbalken <- function(anz = gezaehlt,max_s = stimmbezirke_n,ts = ts) {
  fortschritt <- floor(anz/max_s*100)
  annotate_str <- paste0("Ausgezählt sind ",
                         # Container Fake-Balken
                         "<span style='height:24px;display: flex;justify-content: space-around;align-items: flex-end; width: 100%;'>",
                         # Vordere Pufferzelle 70px
                         "<span style='width:70px; text-align:center;'>",
                         anz,
                         "</span>",
                         # dunkelblauer Balken
                         "<span style='width:",
                         fortschritt,
                         "%; background:#002747; height:16px;'></span>",
                         # grauer Balken
                         "<span style='width:",
                         100-fortschritt,
                         "%; background:#CCC; height:16px;'></span>",
                         # Hintere Pufferzelle 5px
                         "<span style='width:5px;'></span>",
                         # Ende Fake-Balken
                         "</span>",
                         "<br>",
                         " von ",max_s,
                         " Stimmbezirken - ",
                         "<strong>Stand: ",
                         format.Date(ts, "%d.%m.%y, %H:%M Uhr"),
                         "</strong>"
  )
  
}

generiere_auszählung_nurtext <- function(anz = gezaehlt,max_s = stimmbezirke_n,ts = ts) {
  fortschritt <- floor(anz/max_s*100)
  annotate_str <- paste0("Ausgezählt: ",
                         anz,
                         " von ",max_s,
                         " Stimmbezirken - ",
                         "<strong>Stand: ",
                         format.Date(ts, "%d.%m.%y, %H:%M Uhr"),
                         "</strong>"
  )
  
}

#---- Hilfsfunktionen: Switcher generieren, Farben anpassen ----
# Funktion gibt Schwarz zurück, wenn Farbe hell ist, und Weiß, wenn sie 
# relativ dunkel ist. 
font_colour <- function(colour) {
  # convert color to hexadecimal format and extract RGB values
  hex <- substr(colour, 2, 7)
  r <- strtoi(paste0("0x",substr(hex, 1, 2)))
  g <- strtoi(paste0("0x",substr(hex, 3, 4)))
  b <- strtoi(paste0("0x",substr(hex, 5, 6)))

  # suggested by chatGPT: 
  # calculate the brightness of the color using the formula Y = 0.2126*R + 0.7152*G + 0.0722*B
  brightness <- 0.2126 * r + 0.7152 * g + 0.0722 * b
  # compare the brightness to a reference value and return the result
  return(ifelse(brightness > 128,"#000000","#FFFFFF"))
}

aufhellen <- function(Farbwert, heller = 128) {
  #' Funktion gibt einen um 0x404040 aufgehellten Farbwert zurück
  hex <- substr(Farbwert, 2, 7)
  r <- strtoi(paste0("0x",substr(hex, 1, 2))) + heller
  g <- strtoi(paste0("0x",substr(hex, 3, 4))) + heller
  b <- strtoi(paste0("0x",substr(hex, 5, 6))) + heller
  if (r > 255) r <- 255
  if (g > 255) g <- 255
  if (b > 255) b <- 255
  return(paste0("#",as.hexmode(r),as.hexmode(g),as.hexmode(b)))
  
}

# Generiere den Linktext für den Switcher
link_text <- function(id,id_colour,text) {
  lt = paste0("<a target='_self' href='https://datawrapper.dwcdn.net/",
                id,"' style='background:",id_colour,
                "; padding:1px 3px; border-radius:3px; color:",font_colour(id_colour),
                "; cursor:pointer;' rel='nofollow noopener noreferrer'>",
                text,"</a> ")
  return(lt)
}

# Gibt einen String mit HTML/CSS zurück. 
# Nutzt die Kandidierenden-Datei 
generiere_switcher <- function(switcher_df,selected = 0) {
  # Ist der Switcher auf 0 - der Stärkste-Kandidaten-Übersichtskarte?
  if (selected == 0) {
    text <- link_text(karte_sieger_id,"#F0F0FF","<strong>Sieger nach Stadtteil</strong>")
  } else {
    text <- link_text(karte_sieger_id,"#333333","Sieger nach Stadtteil")
  }
  for (i in 1:nrow(switcher_df)) {
    if (i == selected) {
      switcher_df$html[i] <- link_text(switcher_df$dw_id[i],
                                       "#F0F0FF",
                                       paste0("<strong>",switcher_df$Name[i],"</strong>"))
    } else {
      switcher_df$html[i] <- link_text(switcher_df$dw_id[i],
                                       switcher_df$Farbwert[i],
                                       switcher_df$Name[i])
    }

  }
  return(paste0(text,paste0(switcher_df$html,collapse="")))
} 

# HTML-Code für die Tooltipp-Mouseovers generieren
# Für alle Karten: Den jeweiligen Kandidaten in den Titel, 
# orientieren an top (Anzahl der Top-Kandidaten),
# Bargraph-Code generieren. 
#
# Die Mouseovers stehen in den Schlüsseln
# - visualize[["tooltip"]][["title"]]
# - visualize[["tooltip"]][["body"]]

karten_titel_html <- function(kandidat_s) {
  # TBD
}

karten_body_html <- function(top = 5) {
  # TBD
  text <- "<p style='font-weight: 700;'>Ausgezählt: {{ meldungen_anz }} von {{ meldungen_max }} Wahllokalen{{ meldungen_max != meldungen_anz ? ' - Trendergebnis' : ''}}</p>"
  # Generiere String mit allen Prozent-Variablen
  prozent_s <- paste0(paste0("prozent",1:top),collapse=",")
  # Tabellenöffnung
  text <- text %>% paste0("<table style='width: 100%; border-spacing: 0.1em; border-collapse: collapse;'><thead>")
  for (i in 1:top) {
    text <- text %>%  paste0("<tr><th>{{ kand",i,
                             " }}</th><td style='width: 120px; height=16px;'>",
                             "<div style='width: {{ ROUND(((prozent",i,
                             " / MAX(",
                             prozent_s,
                             ")) *100),1) }}%; background-color: {{ farbe",i,
                             " }}; padding-bottom: 5%; padding-top: 5%; border-radius: 5px;'></div></div></td>",
                              "<td style='padding-left: 3px; text-align: right; font-size: 0.8em;'>{{ FORMAT(prozent",i,
                              ", '0.0%') }}</td></tr>")
  }
  # Tabelle abbinden
  text <- text %>% paste0(
    "</thead></table>",
    "<p>Wahlberechtigte: {{ FORMAT(wahlberechtigt, '0,0') }}, abgegebene Stimmen: {{ FORMAT(stimmen, '0,0') }}, Briefwahl: {{ FORMAT(stimmen_wahlschein, '0,0') }}, ungültig {{ FORMAT(ungueltig, '0,0') }}"
  )
} 


# Schreibe die Switcher und die Farbtabellen in alle betroffenen Datawrapper-Karten
vorbereitung_alle_karten <- function() {
  # Vorbereiten: df mit Kandidierenden und Datawrapper-ids
  # Alle Datawrapper-IDs in einen Vektor extrahieren
  id_df <- config_df %>% 
    filter(str_detect(name,"_kand[0-9]+")) %>% 
    mutate(Nummer = as.integer(str_extract(name,"[0-9]+"))) %>% 
    select(Nummer,dw_id = value)#
  # Mach aus der Switcher-Tabelle eine globale Variable
  switcher_df <<- kandidaten_df %>% 
    select(Nummer, Vorname, Name, Parteikürzel, Farbwert) %>% 
    left_join(id_df, by="Nummer")
  text_pre <- "<strong>Wählen Sie eine Karte über die Felder:</strong><br>"
  text_post <- "<br><br>Klick auf den Stadtteil zeigt, wer dort führt"
  balken_text <- generiere_auszählungsbalken(gezaehlt,stimmbezirke_n,ts)
  dw_intro=paste0(text_pre,generiere_switcher(switcher_df,0),text_post)
  # Farbskala und Mouseover anpassen
  metadata_chart <- dw_retrieve_chart_metadata(karte_sieger_id)
  visualize <- metadata_chart[["content"]][["metadata"]][["visualize"]]
  # Farbwerte für die Kandidierenden
  # erst mal löschen
  visualize[["colorscale"]][["map"]] <- NULL
  visualize[["colorscale"]][["map"]] <- setNames(as.list(kandidaten_df$Farbwert),
                                                 kandidaten_df$Name)
  # Karten-Tooltipp anpassen
  # visualize[["tooltip"]][["title"]] <- karten_title_html(kandidat_s)
  visualize[["tooltip"]][["body"]] <- karten_body_html(top)
  
  dw_edit_chart(karte_sieger_id,intro = dw_intro, annotate = balken_text, visualize = visualize)
  # dw_data_to_chart()
  # dw_publish_chart(karte_sieger_id)
  
  # Jetzt die n Choroplethkarten für alle Kandidaten
  # Müssen als Kopie angelegt sein. 
  for (i in 1:nrow(switcher_df)) {
    dw_intro <- paste0(text_pre,generiere_switcher(switcher_df,0),text_post)
    titel_s <- paste0(switcher_df$Vorname[i]," ",
                     switcher_df$Name[i]," (",
                     switcher_df$Parteikürzel[i],") - ",
                     "Ergebnis nach Stadtteil")
    kandidat_s <- paste0(switcher_df$name[i],
                         " (",
                         switcher_df$Parteikürzel[i])
    metadata_chart <- dw_retrieve_chart_metadata(switcher_df$dw_id[i])
    visualize <- metadata_chart[["content"]][["metadata"]][["visualize"]]
    # Zwei Farben: Parteifarbe bei Pos. 1, aufgehellte Parteifarbe
    # (zu RGB jeweils 0x40 addiert) bei Pos. 0
    visualize[["colorscale"]][["colors"]][[2]]$color <- switcher_df$Farbwert[i]
    visualize[["colorscale"]][["colors"]][[2]]$position <- 1
    visualize[["colorscale"]][["colors"]][[1]]$color <- aufhellen(switcher_df$Farbwert[i])
    visualize[["colorscale"]][["colors"]][[1]]$position <- 0
    # Karten-Tooltipp anpassen
    # visualize[["tooltip"]][["title"]] <- karten_title_html(kandidat_s)
    visualize[["tooltip"]][["body"]] <- karten_body_html(top)
    dw_edit_chart(switcher_df$dw_id[i],
                  title = titel_s,
                  intro = dw_intro,
                  visualize = visualize,
                  annotate = balken_text)
    # dw_data_to_chart()
    # dw_publish_chart(switcher_df$dw_id)
  }
}

#---- Generiere und pushe die Grafiken für Social Media
generiere_socialmedia <- function() {
  # Fairly straightforward. Rufe zwei Datawrapper-Karten über die API auf, 
  # generiere aus ihnen PNGs, benenne sie mit Zeitstempel, schiebe die auf 
  # den Bucket und gib einen Text mit Link zurück. 
  #
  # Die beiden Karten sind: 
  # - die Aufmacher-Grafik = top_id
  # - die nüchterne Balkengrafik mit allen 20 Kandidat:innen S9BbQ
  #
  # Die Funktion aktualisiert KEINE Daten, sondern nimmt das, was gerade im 
  # Datawrapper ist. Das ggf extra mit dw_data_to_chart(meine_df,social1_id,parse_dates =TRUE)
  # 
  # Setzt gültigen Zeitstempel ts voraus!
  
  # Erste Grafik ist sowieso aktuell und wird nur anders exportiert.
  # dw_data_to_chart(tag_df,social1_id,parse_dates =TRUE)
  social1_png <- dw_export_chart(social1_id,type = "png",unit="px",mode="rgb", scale = 1, 
                  width = 640, height = 640, plain = TRUE, transparent = T)
  social1_fname <- paste0("png/social1_",format.Date(ts,"%Y-%m-%d--%H%Mh"),".png")
  # Zweite Grafik muss aktualisiert und vermetadatet werden
  # Metadaten anpassen: Farbcodes für Parteien
  metadata_chart <- dw_retrieve_chart_metadata(social2_id)
  # Save the visualise path
  visualize <- metadata_chart[["content"]][["metadata"]][["visualize"]]
  visualize[["color-category"]][["map"]] <- 
    setNames(as.list(kandidaten_df$Farbwert), 
             paste0(kandidaten_df$Name," (",
                    kandidaten_df$Parteikürzel,")"))
  dw_edit_chart(chart_id = social2_id, visualize = visualize)
  dw_data_to_chart(kand_tabelle_df, chart_id = social2_id)
  social2_png <- dw_export_chart(social2_id,type = "png",unit="px",mode="rgb", scale = 1, 
                  width = 640, height = 640, plain = TRUE, transparent = T)
  social2_fname <- paste0("png/social2_",format.Date(ts,"%Y-%m-%d--%H%Mh"),".png")
  # PNG-Dateien generieren...
  if (!dir.exists("png")) {dir.create("png")}
  magick::image_write(social1_png,social1_fname)
  magick::image_write(social2_png,social2_fname)
  #...und auf den Bucket schieben. 
  if (SERVER) {
    system(paste0('gsutil -h "Cache-Control:no-cache, max_age=0" ',
                  'cp ',social1_fname,' gs://d.data.gcp.cloud.hr.de/', social1_fname))
    system(paste0('gsutil -h "Cache-Control:no-cache, max_age=0" ',
                  'cp ',social2_fname,' gs://d.data.gcp.cloud.hr.de/', social2_fname))
  }
  linktext <- paste0("<a href='https://d.data.gcp.cloud.hr.de/",social1_fname,
                     "'>Download Social-Grafik 1 (5 stärkste)</a><br/>",
                     "<a href='https://d.data.gcp.cloud.hr.de/",social2_fname,
                     "'>Download Social-Grafik 2 (alle Stimmen)</a><br/>")
  return(linktext)
}

#---- Datawrapper-Grafiken generieren ----
aktualisiere_top <- function(kand_tabelle_df,top=5) {
  daten_df <- kand_tabelle_df %>% 
    arrange(desc(Prozent)) %>% 
    select(`Kandidat/in`,Stimmenanteil = Prozent) %>% 
    head(top)
  # Daten pushen
  dw_data_to_chart(daten_df,chart_id = top_id)
  # Intro_Text nicht anpassen. 
  # Balken reinrendern
  balken_text <- generiere_auszählungsbalken(gezaehlt,stimmbezirke_n,ts)
  # Metadaten anpassen: Farbcodes für Parteien
  metadata_chart <- dw_retrieve_chart_metadata(top_id)
  # Save the visualise path
  visualize <- metadata_chart[["content"]][["metadata"]][["visualize"]]
  # Der Schlüssel liegt unter custom-colors als Liste
  visualize[["custom-colors"]] <- 
    setNames(as.list(kandidaten_df$Farbwert), 
             paste0(kandidaten_df$Name," (",
                    kandidaten_df$Parteikürzel,")"))
  dw_edit_chart(chart_id = top_id,annotate = balken_text, visualize=visualize)
  dw <- dw_publish_chart(chart_id = top_id)
}

aktualisiere_tabelle_alle <- function(kand_tabelle_df) {
  # Daten und Metadaten hochladen, für die Balkengrafik mit allen 
  # Stimmen für alle Kandidaten
  dw_data_to_chart(kand_tabelle_df, chart_id = tabelle_alle_id)
  balken_text <- generiere_auszählung_nurtext(gezaehlt,stimmbezirke_n,ts)
  # Metadaten anpassen: Farbcodes für Parteien
  metadata_chart <- dw_retrieve_chart_metadata(tabelle_alle_id)
  # Save the visualise path
  visualize <- metadata_chart[["content"]][["metadata"]][["visualize"]]
  visualize[["columns"]][["Prozent"]][["customColorBarBackground"]] <- NULL
  visualize[["columns"]][["Stimmen"]][["customColorBarBackground"]] <- 
    setNames(as.list(kandidaten_df$Farbwert), 
             kandidaten_df$Nummer)
  # Irrtümlich waren die Werte auch noch in visualize[["custom-color"]] gespeichert.
  visualize[["custom-colors"]] <- NULL
  visualize[["color-category"]] <- NULL
  dw_edit_chart(chart_id = tabelle_alle_id, annotate = balken_text, visualize = visualize)
  dw_publish_chart(chart_id = tabelle_alle_id)
}

aktualisiere_karten <- function(ergänzt_df) {
  # Als erstes die Übersichtskarte
  cat("Aktualisiere Karten\n")
  # Die noch überhaupt nicht gezählten Bezirke ausfiltern
  ergänzt_f_df <- ergänzt_df %>% filter(meldungen_anz > 0)
  balken_text = generiere_auszählungsbalken(gezaehlt,stimmbezirke_n,ts)
  dw_edit_chart(chart_id = karte_sieger_id, annotate = balken_text)
  dw_data_to_chart(ergänzt_f_df,chart_id = karte_sieger_id)
  dw <- dw_publish_chart(karte_sieger_id)
  # Jetzt die Choropleth-Karten für alle Kandidierenden
  for (i in 1:nrow(switcher_df)) {
    dw_edit_chart(chart_id=switcher_df$dw_id[i],annotate = balken_text)
    dw_data_to_chart(ergänzt_f_df, chart_id = switcher_df$dw_id[i])
    dw <- dw_publish_chart(switcher_df$dw_id[i])
  }
  cat("Karten neu publiziert\n")
}

aktualisiere_hochburgen <- function(hochburgen_df) {
  # Das ist ziemlich geradeheraus. 
  dw_data_to_chart(hochburgen_df, chart_id = hochburgen_id)
  balken_text <- generiere_auszählung_nurtext(gezaehlt,stimmbezirke_n,ts)
  # Metadaten anpassen: Farbcodes für Parteien
  metadata_chart <- dw_retrieve_chart_metadata(hochburgen_id)
  # Save the visualise path
  visualize <- metadata_chart[["content"]][["metadata"]][["visualize"]]
  # Die Farben für die Kandidaten werden in dieser Tabelle nur für die Balkengrafiken
  # in der Spalte "Prozent" benötigt und richten sich nach der Nummer.
  visualize[["columns"]][["Prozent"]][["customColorBarBackground"]] <- 
    setNames(as.list(kandidaten_df$Farbwert), 
             kandidaten_df$Nummer)
  # Irrtümlich waren die Werte auch noch in visualize[["custom-color"]] gespeichert.
  visualize[["custom-colors"]] <- NULL
  dw_edit_chart(chart_id = hochburgen_id, annotate = balken_text, visualize = visualize)
  dw_publish_chart(chart_id = hochburgen_id)
  cat("Hochburgen-Grafik neu publiziert\n")
}

aktualisiere_ergebnistabelle <- function(stadtteildaten_df) {
  # Nr des Stadtteils, Stadtteil, Wahlbeteiligung (Info), Ergebnis
  # Wahlbeteiligung und Ergebnis sind jeweils HTML-Text mit den Daten
  # Unleserlich, aber funktional
  e_tmp_df <- stadtteildaten_df %>% 
    select(nr,name,meldungen_anz:ncol(.)) %>% 
    # Nach Stadtteil sortieren
    arrange(name) %>% 
    mutate(sort = row_number())
  # Nochmal ansetzen, um als erste Zeile das Gesamtergebnis einzusetzen
  ergebnistabelle_df <- e_tmp_df %>% summarize(nr = 0, sort = 0,
                               name = "GESAMTERGEBNIS",
                               across(meldungen_anz:ncol(.), ~sum(.,na.r =FALSE))) %>% 
    bind_rows(e_tmp_df) %>% 
    # Mit den Kandidaten-Namen anreichern
    # Ins Langformat umformen, Nummer ist die Kandidatennummer
    pivot_longer(cols=starts_with("D"),names_to = "Nummer", values_to = "Stimmen") %>% 
    mutate(Prozent = if_else(Stimmen == 0,0,Stimmen / gueltig * 100)) %>% 
    # D1... in Integer umwandeln
    mutate(Nummer = as.numeric(str_extract(Nummer,"[0-9]+"))) %>% 
    left_join(kandidaten_df %>% select (Nummer, Vorname, Name, Parteikürzel),
              by = "Nummer") %>% 
    mutate(`Kandidat/in` = paste0(Name," (",Parteikürzel,")")) %>% 
    # Kandidaten-Tabelle wieder zurückpivotieren
    select(-Vorname, -Name, -Parteikürzel, -Nummer) %>%
    # Nach Stadtteil gruppieren
    group_by(sort,nr,name) %>% 
    # Zusätzliche Variable: Stadtteil gezählt oder TREND?
    mutate(trend = meldungen_anz < meldungen_max) %>% 
    # Big bad summary - jeweils Daten aus den Spalten generieren
    summarize(Stadtteil = paste0("<strong>",first(name),
                                "</strong><br><br>",
                                # TREND oder ERGEBNIS?
                                if_else(first(trend),
                                        paste0("TREND: ",
                                               first(meldungen_anz)," von ",
                                               first(meldungen_max)," Stimmbezirken ausgezählt"),
                                        #...oder alles ausgezählt?
                                        paste0("Alle ",
                                               first(meldungen_max),
                                               " Stimmbezirke ausgezählt"))),
              Wahlbeteiligung = paste0("Wahlberechtigt: ",
                                       # Wenn noch nicht ausgezählt, leer lassen
                                       if_else(first(trend),"",
                                               first(wahlberechtigt) %>% 
                                         format(big.mark = ".", decimal.mark =",")),
                                       "<br>",
                                       "abg. Stimmen: ",first(stimmen) %>% format(big.mark = ".", decimal.mark =","),
                                       " (",
                                       # Nicht gezählte Bezirke haben 0 Wahlberechtigte
                                       if_else(first(trend),"--", 
                                                (first(stimmen)/first(wahlberechtigt) *100) %>% 
                                         round(digits=1) %>% format(decimal.mark=",", nsmall = 1)),
                                       "%)<br>",
                                       "davon Briefwahl: ",
                                       first(stimmen_wahlschein) %>% format(big.mark = ".", decimal.mark =","),
                                       " (",
                                       # Falls noch nicht alles ausgezählt, keinen Prozentwert angeben
                                       if_else(first(trend),
                                               "--",
                                       (first(stimmen_wahlschein) / first(stimmen) * 100) %>% 
                                         round(digits = 1) %>% format(decimal.mark=",", nsmall = 1)),
                                       "%)<br>",
                                       "<br>Ungültig: ",
                                       first(ungueltig) %>% format(big.mark = ".", decimal.mark =","),
                                       "<br>Gültige Stimmen: ",
                                       first(gueltig) %>% format(big.mark = ".", decimal.mark =",")),
              # Hier geschachtelte paste0-Aufrufe: 
              # Der innere baut einen Vektor mit allen Kandidaten plus Ergebnissen
              # Der äußere fügt diesen Vektor zu einem String zusammen (getrennt durch <br>)
              Ergebnis = paste0(paste0("<strong>",`Kandidat/in`,"</strong>: ",
                                       Stimmen %>% format(big.mark=".",decimal.mark=","),
                                       " (",
                                       Prozent %>% round(1) %>% format(decimal.mark=",", nsmall=1),"%)"),
                collapse="<br>")
              ) %>% 
    ungroup() %>% 
    arrange(sort) %>% 
    select(-name,-sort)
  dw_data_to_chart(ergebnistabelle_df %>% select(-nr), chart_id = tabelle_stadtteile_id)
    # Trendergebnis? Schreibe "Trend" oder "Endergebnis" in den Titel
  gezählt <- e_tmp_df %>% pull(meldungen_anz) %>% sum(.)
  stimmbezirke_n <- e_tmp_df %>% pull(meldungen_max) %>% sum(.)
  ts <- stadtteildaten_df %>% pull(zeitstempel) %>% first()
  titel_s <- paste0(ifelse(gezählt < stimmbezirke_n,"TREND: ",""),
                    "Ergebnisse nach Stadtteil")
  dw_edit_chart(chart_id = tabelle_stadtteile_id,title = titel_s,
                annotate=generiere_auszählung_nurtext(gezählt,stimmbezirke_n,ts))
  dw_publish_chart(tabelle_stadtteile_id)
  cat("Ergebnistabelle nach Stadtteil publiziert\n")
  return(ergebnistabelle_df)
}