library(plotly)
library(tidyr)
library(ggplot2)

data = read.csv2("https://bundeswahlleiter.de/dam/jcr/0c8deca8-4030-419c-9fa8-5ab770cfa123/btw_rws_zwst-1953.csv", skip = 12)
data = data[data$Geschlecht == "Summe" & data$Bundestagswahl>1970,]
colnames(data)[3] = "Altersgruppe"
colnames(data)[8] = "LINKE"

# Gesamtergebnisse für Wahlen 1994 und 1998 ergänzen (trotz ausgesetzter repräsentativer Wahlstatistik)´
data = rbind(
  data,
  #https://www.bundeswahlleiter.de/bundestagswahlen/1994.html
  list("Bundestagswahl"=1994, "Geschlecht"="Summe", "Altersgruppe"="Summe", "CDU"=34.2, "SPD"=36.4, "LINKE"=4.4, "GRÜNE"=7.3, "CSU"=7.3, "FDP"=6.9, "AfD"=NA, "Sonstige"=3.6),
  #https://www.bundeswahlleiter.de/bundestagswahlen/1998.html
  list("Bundestagswahl"=1998, "Geschlecht"="Summe", "Altersgruppe"="Summe", "CDU"=28.4, "SPD"=40.9, "LINKE"=5.1, "GRÜNE"=6.7, "CSU"=6.7, "FDP"=6.2, "AfD"=NA, "Sonstige"=5.9)
)

data["CDU/CSU"] = data$CDU + ifelse(!is.na(data$CSU), data$CSU, 0)

kabinette = data.frame(
  wahl = as.factor(c(1972, 1976, 1980, 1983, 1987, 1990, 1994, 1998, 2002, 2005, 2009, 2013, 2017, 2021)),
  parteien = c("SPD,FDP", "SPD,FDP", "SPD,FDP", "CDU/CSU,FDP", "CDU/CSU,FDP", "CDU/CSU,FDP", "CDU/CSU,FDP", "SPD,GRÜNE", "SPD,GRÜNE", "CDU/CSU,SPD", "CDU/CSU,FDP", "CDU/CSU,SPD", "CDU/CSU,SPD", "SPD,GRÜNE,FDP"),
  kabinett = c("Brandt II + Schmidt I", "Schmidt II", "Schmidt III", "Kohl II", "Kohl III", "Kohl IV", "Kohl V", "Schröder I", "Schröder II", "Merkel I", "Merkel II", "Merkel III", "Merkel IV", "Scholz I")
)
#kabinette=kabinette[-c(7:8),] # 1994 und 1998 entfernen (repräsentative Wahlstatistik wurde ausgesetzt)
#kabinette = kabinette[9:14,] # just show 2002-2021, set fig.width=8, fig.height=3
rownames(kabinette) = kabinette$wahl

zeitpunkte = c("18 - 24", "25 - 34", "35 - 44", "45 - 59", "60 und mehr")

for (i in 1:nrow(kabinette)) {
  wahl = data[data$Bundestagswahl == kabinette[i, "wahl"],]
  if (nrow(wahl) == 0) next
  rownames(wahl) = wahl$Altersgruppe
  parteien = strsplit(kabinette[i, "parteien"], ",", fixed=T)[[1]]
  summe = wahl["Summe", parteien]
  
  kabinette[i, zeitpunkte] = rowSums(wahl[zeitpunkte, parteien])-sum(summe)
  
  kabinette[i, "Summe"] = sum(summe)
  
  kabinette[i, "Gewinner1"] = parteien[1]
  kabinette[i, "Gewinner2"] = parteien[2]
  kabinette[i, "Gewinner3"] = parteien[3]
  kabinette[i, "Prozente1"] = summe[1]/sum(summe)
  kabinette[i, "Prozente2"] = summe[2]/sum(summe)
  kabinette[i, "Prozente3"] = ifelse(!is.na(parteien[3]), summe[3]/sum(summe), NA)
}

colorcode = list("CDU/CSU" = "black", "SPD" = "#e3000f", "GRÜNE" = "#46962b", "FDP" = "yellow", "LINKE" = "#be3075", "AfD" = "#009ee0", "Sonstige" = "#ffffff")

get_tooltip = function(Wahl, Altersgruppe) {
  sub = data[data$Bundestagswahl == Wahl & data$Altersgruppe == Altersgruppe,]
  parteien = names(colorcode)[!is.na(sub[names(colorcode)])]
  paste(sapply(
    parteien,
    function(partei) paste0("<span style='", ifelse(partei=="CDU/CSU", "text-shadow: 0px 0px 3px #ffffff; ", ""), "color:", colorcode[[partei]], "'>", partei, ": ", sub[partei], "%</span>")
  )[order(sub[parteien], decreasing=T)], collapse="\n")
}

kabinette$tooltip = sapply(kabinette$wahl, function(x) get_tooltip(x, "Summe"))

p1 = ggplotly(ggplot(data.frame(wahl=rep(kabinette$wahl, n=2), partei=factor(c(kabinette$Gewinner1, kabinette$Gewinner2, kabinette$Gewinner3), levels=rev(names(colorcode))), prozente=c(kabinette$Prozente1, kabinette$Prozente2, kabinette$Prozente3))) +
  geom_col(aes(prozente, wahl, fill=partei), position = position_fill()) +
  geom_text(aes(x=0.5, wahl, label=paste0(Summe, "%"), text=tooltip), data=kabinette, color="white") +
  scale_fill_manual(values=colorcode) +
  theme_minimal() + theme(panel.grid.major = element_blank(), panel.grid.minor = element_blank(), legend.position="none", axis.title.x=element_blank(), axis.text.x=element_blank(), axis.ticks.x=element_blank(), axis.title.y=element_blank(), axis.text.y=element_blank(), axis.ticks.y=element_blank()), tooltip="text") %>%
  style(hoverinfo = "none", traces = 1:4) %>%
  layout(hovermode="closest", xaxis=list(fixedrange=T), yaxis=list(fixedrange=T))

overview = kabinette[,c("wahl", zeitpunkte)]
overview = as.data.frame(pivot_longer(overview, cols=2:ncol(overview)))
colnames(overview)[3] = "Abweichung"
overview$tooltip = sapply(1:nrow(overview), function(i) get_tooltip(overview[i, "wahl"], overview[i, "name"]))
overview[is.na(overview$Abweichung), "tooltip"] = ""
overview$name[overview$name == "60 und mehr"] = "60 +"

p2 = ggplotly(ggplot(overview, aes(name, wahl, fill=Abweichung)) +
  geom_tile(aes(fill = Abweichung), na.rm=T) +
  geom_text(aes(label = ifelse(!is.na(Abweichung), paste0("<span style='color:", ifelse(abs(Abweichung)<5, "black", "white"), "'>", ifelse(Abweichung>0, "+", ""), round(Abweichung,1), "%</span>"), ""), text=tooltip)) +
  scale_fill_gradient2(low=scales::muted("violetred"), mid="white", high=scales::muted("aquamarine")) +
  theme_minimal() + theme(panel.grid.major = element_blank(), panel.grid.minor = element_blank()), tooltip="text") %>%
  style(hoverinfo = "none", traces = c(1)) %>%
  layout(xaxis=list(fixedrange=T, tickfont=list(size=16)), yaxis=list(fixedrange=T, tickfont=list(size=16)))

partial_bundle(subplot(p1, plotly_empty() %>% layout(xaxis=list(fixedrange=T), yaxis=list(fixedrange=T)), p2, widths=c(0.2,0.01,0.79)) %>%
  config(displayModeBar = FALSE))

Die linke Spalte zeigt die deutschen Regierungskoalitionen, die nach den Bundestagswahlen seit 1972 gebildet wurden. Die Prozentzahlen beziehen sich auf den Zweitstimmen-Anteil der Regierungsparteien, gibt also in etwa den Anteil der Wählerschaft wider, der durch die Regierung repräsentiert wird. Der Tooltip über den Prozentzahlen listet die die Zweitstimmen der verschiedenen Parteien auf. Der Zeitraum wurde gewählt, da das Wahlalter 1970 auf 18 Jahre reduziert wurde.

Die rechte Tabelle zeigt die Abweichungen des Zweitstimmen-Anteils der deutschen Regierungsparteien je nach Altersgruppe an, ermittelt aus der repräsentativen Wahlstatistik. Violett hervorgehobene Altersgruppen haben verhältnismässig weniger Zweitstimmen für die Regierungsparteien abgegeben als der Durchschnitt, während grüne die Regierungsparteien häufiger wählten. 1994 und 1998 wurde die repräsentative Wahlstatistik ausgesetzt. Der Anteil der verschiedenen Altersgruppen an der Bevölkerung variiert von Jahr zu Jahr und unterliegt dem demographischen Wandel.

Datenquelle: Bundeswahlleiter (csv) / (pdf)


The left column shows the German government coalitions formed since 1972. The percentages indicate the proportion the government parties gained together in the respective parliamentary election (in secondary votes), thus roughly representing the proportion of voters represented by the government. The tooltip over the percentages shows the votes for each party. The timeframe was chosen as the voting age was reduced to 18 in 1970.

The table on the right shows the deviation of German government party votes by age groups, derived through the representative electoral statistics. Violet age groups voted less for government parties compared to the average, while green age groups voted for them more. The representative electoral statistics were suspended in 1994 and 1998. The proportion of the different age groups among the total population varies from year to year and is subject to demographic change.

Source: Bundeswahlleiter (csv) / (pdf)

LS0tCnRpdGxlOiAiQWJ3ZWljaHVuZyBkZXMgWndlaXRzdGltbWVuLUFudGVpbHMgZGVyIGRldXRzY2hlbiBSZWdpZXJ1bmdzcGFydGVpZW4gbmFjaCBBbHRlciAxOTcyLTIwMjEiCnN1YnRpdGxlOiAiRGV2aWF0aW9uIG9mIG1lYW4gc2hhcmUgb2Ygdm90ZXMgb2YgR2VybWFuIGdvdmVybm1lbnQgcGFydGllcyBieSBhZ2UgMTk3Mi0yMDIxIgphdXRob3I6ICI8YSBocmVmPSdodHRwczovL3R3aXR0ZXIuY29tL3RpbXdvZWxmbGUnPlRpbSBXb2VsZmxlPC9hPiIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUKLS0tCgpgYGB7cix3YXJuaW5nPUYsbWVzc2FnZT1GLGZpZy53aWR0aD0xMC41LGZpZy5oZWlnaHQ9NS41fQpsaWJyYXJ5KHBsb3RseSkKbGlicmFyeSh0aWR5cikKbGlicmFyeShnZ3Bsb3QyKQoKZGF0YSA9IHJlYWQuY3N2MigiaHR0cHM6Ly9idW5kZXN3YWhsbGVpdGVyLmRlL2RhbS9qY3IvMGM4ZGVjYTgtNDAzMC00MTljLTlmYTgtNWFiNzcwY2ZhMTIzL2J0d19yd3NfendzdC0xOTUzLmNzdiIsIHNraXAgPSAxMikKZGF0YSA9IGRhdGFbZGF0YSRHZXNjaGxlY2h0ID09ICJTdW1tZSIgJiBkYXRhJEJ1bmRlc3RhZ3N3YWhsPjE5NzAsXQpjb2xuYW1lcyhkYXRhKVszXSA9ICJBbHRlcnNncnVwcGUiCmNvbG5hbWVzKGRhdGEpWzhdID0gIkxJTktFIgoKIyBHZXNhbXRlcmdlYm5pc3NlIGbDvHIgV2FobGVuIDE5OTQgdW5kIDE5OTggZXJnw6RuemVuICh0cm90eiBhdXNnZXNldHp0ZXIgcmVwcsOkc2VudGF0aXZlciBXYWhsc3RhdGlzdGlrKcK0CmRhdGEgPSByYmluZCgKICBkYXRhLAogICNodHRwczovL3d3dy5idW5kZXN3YWhsbGVpdGVyLmRlL2J1bmRlc3RhZ3N3YWhsZW4vMTk5NC5odG1sCiAgbGlzdCgiQnVuZGVzdGFnc3dhaGwiPTE5OTQsICJHZXNjaGxlY2h0Ij0iU3VtbWUiLCAiQWx0ZXJzZ3J1cHBlIj0iU3VtbWUiLCAiQ0RVIj0zNC4yLCAiU1BEIj0zNi40LCAiTElOS0UiPTQuNCwgIkdSw5xORSI9Ny4zLCAiQ1NVIj03LjMsICJGRFAiPTYuOSwgIkFmRCI9TkEsICJTb25zdGlnZSI9My42KSwKICAjaHR0cHM6Ly93d3cuYnVuZGVzd2FobGxlaXRlci5kZS9idW5kZXN0YWdzd2FobGVuLzE5OTguaHRtbAogIGxpc3QoIkJ1bmRlc3RhZ3N3YWhsIj0xOTk4LCAiR2VzY2hsZWNodCI9IlN1bW1lIiwgIkFsdGVyc2dydXBwZSI9IlN1bW1lIiwgIkNEVSI9MjguNCwgIlNQRCI9NDAuOSwgIkxJTktFIj01LjEsICJHUsOcTkUiPTYuNywgIkNTVSI9Ni43LCAiRkRQIj02LjIsICJBZkQiPU5BLCAiU29uc3RpZ2UiPTUuOSkKKQoKZGF0YVsiQ0RVL0NTVSJdID0gZGF0YSRDRFUgKyBpZmVsc2UoIWlzLm5hKGRhdGEkQ1NVKSwgZGF0YSRDU1UsIDApCgprYWJpbmV0dGUgPSBkYXRhLmZyYW1lKAogIHdhaGwgPSBhcy5mYWN0b3IoYygxOTcyLCAxOTc2LCAxOTgwLCAxOTgzLCAxOTg3LCAxOTkwLCAxOTk0LCAxOTk4LCAyMDAyLCAyMDA1LCAyMDA5LCAyMDEzLCAyMDE3LCAyMDIxKSksCiAgcGFydGVpZW4gPSBjKCJTUEQsRkRQIiwgIlNQRCxGRFAiLCAiU1BELEZEUCIsICJDRFUvQ1NVLEZEUCIsICJDRFUvQ1NVLEZEUCIsICJDRFUvQ1NVLEZEUCIsICJDRFUvQ1NVLEZEUCIsICJTUEQsR1LDnE5FIiwgIlNQRCxHUsOcTkUiLCAiQ0RVL0NTVSxTUEQiLCAiQ0RVL0NTVSxGRFAiLCAiQ0RVL0NTVSxTUEQiLCAiQ0RVL0NTVSxTUEQiLCAiU1BELEdSw5xORSxGRFAiKSwKICBrYWJpbmV0dCA9IGMoIkJyYW5kdCBJSSArIFNjaG1pZHQgSSIsICJTY2htaWR0IElJIiwgIlNjaG1pZHQgSUlJIiwgIktvaGwgSUkiLCAiS29obCBJSUkiLCAiS29obCBJViIsICJLb2hsIFYiLCAiU2NocsO2ZGVyIEkiLCAiU2NocsO2ZGVyIElJIiwgIk1lcmtlbCBJIiwgIk1lcmtlbCBJSSIsICJNZXJrZWwgSUlJIiwgIk1lcmtlbCBJViIsICJTY2hvbHogSSIpCikKI2thYmluZXR0ZT1rYWJpbmV0dGVbLWMoNzo4KSxdICMgMTk5NCB1bmQgMTk5OCBlbnRmZXJuZW4gKHJlcHLDpHNlbnRhdGl2ZSBXYWhsc3RhdGlzdGlrIHd1cmRlIGF1c2dlc2V0enQpCiNrYWJpbmV0dGUgPSBrYWJpbmV0dGVbOToxNCxdICMganVzdCBzaG93IDIwMDItMjAyMSwgc2V0IGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTMKcm93bmFtZXMoa2FiaW5ldHRlKSA9IGthYmluZXR0ZSR3YWhsCgp6ZWl0cHVua3RlID0gYygiMTggLSAyNCIsICIyNSAtIDM0IiwgIjM1IC0gNDQiLCAiNDUgLSA1OSIsICI2MCB1bmQgbWVociIpCgpmb3IgKGkgaW4gMTpucm93KGthYmluZXR0ZSkpIHsKICB3YWhsID0gZGF0YVtkYXRhJEJ1bmRlc3RhZ3N3YWhsID09IGthYmluZXR0ZVtpLCAid2FobCJdLF0KICBpZiAobnJvdyh3YWhsKSA9PSAwKSBuZXh0CiAgcm93bmFtZXMod2FobCkgPSB3YWhsJEFsdGVyc2dydXBwZQogIHBhcnRlaWVuID0gc3Ryc3BsaXQoa2FiaW5ldHRlW2ksICJwYXJ0ZWllbiJdLCAiLCIsIGZpeGVkPVQpW1sxXV0KICBzdW1tZSA9IHdhaGxbIlN1bW1lIiwgcGFydGVpZW5dCiAgCiAga2FiaW5ldHRlW2ksIHplaXRwdW5rdGVdID0gcm93U3Vtcyh3YWhsW3plaXRwdW5rdGUsIHBhcnRlaWVuXSktc3VtKHN1bW1lKQogIAogIGthYmluZXR0ZVtpLCAiU3VtbWUiXSA9IHN1bShzdW1tZSkKICAKICBrYWJpbmV0dGVbaSwgIkdld2lubmVyMSJdID0gcGFydGVpZW5bMV0KICBrYWJpbmV0dGVbaSwgIkdld2lubmVyMiJdID0gcGFydGVpZW5bMl0KICBrYWJpbmV0dGVbaSwgIkdld2lubmVyMyJdID0gcGFydGVpZW5bM10KICBrYWJpbmV0dGVbaSwgIlByb3plbnRlMSJdID0gc3VtbWVbMV0vc3VtKHN1bW1lKQogIGthYmluZXR0ZVtpLCAiUHJvemVudGUyIl0gPSBzdW1tZVsyXS9zdW0oc3VtbWUpCiAga2FiaW5ldHRlW2ksICJQcm96ZW50ZTMiXSA9IGlmZWxzZSghaXMubmEocGFydGVpZW5bM10pLCBzdW1tZVszXS9zdW0oc3VtbWUpLCBOQSkKfQoKY29sb3Jjb2RlID0gbGlzdCgiQ0RVL0NTVSIgPSAiYmxhY2siLCAiU1BEIiA9ICIjZTMwMDBmIiwgIkdSw5xORSIgPSAiIzQ2OTYyYiIsICJGRFAiID0gInllbGxvdyIsICJMSU5LRSIgPSAiI2JlMzA3NSIsICJBZkQiID0gIiMwMDllZTAiLCAiU29uc3RpZ2UiID0gIiNmZmZmZmYiKQoKZ2V0X3Rvb2x0aXAgPSBmdW5jdGlvbihXYWhsLCBBbHRlcnNncnVwcGUpIHsKICBzdWIgPSBkYXRhW2RhdGEkQnVuZGVzdGFnc3dhaGwgPT0gV2FobCAmIGRhdGEkQWx0ZXJzZ3J1cHBlID09IEFsdGVyc2dydXBwZSxdCiAgcGFydGVpZW4gPSBuYW1lcyhjb2xvcmNvZGUpWyFpcy5uYShzdWJbbmFtZXMoY29sb3Jjb2RlKV0pXQogIHBhc3RlKHNhcHBseSgKICAgIHBhcnRlaWVuLAogICAgZnVuY3Rpb24ocGFydGVpKSBwYXN0ZTAoIjxzcGFuIHN0eWxlPSciLCBpZmVsc2UocGFydGVpPT0iQ0RVL0NTVSIsICJ0ZXh0LXNoYWRvdzogMHB4IDBweCAzcHggI2ZmZmZmZjsgIiwgIiIpLCAiY29sb3I6IiwgY29sb3Jjb2RlW1twYXJ0ZWldXSwgIic+IiwgcGFydGVpLCAiOiAiLCBzdWJbcGFydGVpXSwgIiU8L3NwYW4+IikKICApW29yZGVyKHN1YltwYXJ0ZWllbl0sIGRlY3JlYXNpbmc9VCldLCBjb2xsYXBzZT0iXG4iKQp9CgprYWJpbmV0dGUkdG9vbHRpcCA9IHNhcHBseShrYWJpbmV0dGUkd2FobCwgZnVuY3Rpb24oeCkgZ2V0X3Rvb2x0aXAoeCwgIlN1bW1lIikpCgpwMSA9IGdncGxvdGx5KGdncGxvdChkYXRhLmZyYW1lKHdhaGw9cmVwKGthYmluZXR0ZSR3YWhsLCBuPTIpLCBwYXJ0ZWk9ZmFjdG9yKGMoa2FiaW5ldHRlJEdld2lubmVyMSwga2FiaW5ldHRlJEdld2lubmVyMiwga2FiaW5ldHRlJEdld2lubmVyMyksIGxldmVscz1yZXYobmFtZXMoY29sb3Jjb2RlKSkpLCBwcm96ZW50ZT1jKGthYmluZXR0ZSRQcm96ZW50ZTEsIGthYmluZXR0ZSRQcm96ZW50ZTIsIGthYmluZXR0ZSRQcm96ZW50ZTMpKSkgKwogIGdlb21fY29sKGFlcyhwcm96ZW50ZSwgd2FobCwgZmlsbD1wYXJ0ZWkpLCBwb3NpdGlvbiA9IHBvc2l0aW9uX2ZpbGwoKSkgKwogIGdlb21fdGV4dChhZXMoeD0wLjUsIHdhaGwsIGxhYmVsPXBhc3RlMChTdW1tZSwgIiUiKSwgdGV4dD10b29sdGlwKSwgZGF0YT1rYWJpbmV0dGUsIGNvbG9yPSJ3aGl0ZSIpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9Y29sb3Jjb2RlKSArCiAgdGhlbWVfbWluaW1hbCgpICsgdGhlbWUocGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfYmxhbmsoKSwgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSwgbGVnZW5kLnBvc2l0aW9uPSJub25lIiwgYXhpcy50aXRsZS54PWVsZW1lbnRfYmxhbmsoKSwgYXhpcy50ZXh0Lng9ZWxlbWVudF9ibGFuaygpLCBheGlzLnRpY2tzLng9ZWxlbWVudF9ibGFuaygpLCBheGlzLnRpdGxlLnk9ZWxlbWVudF9ibGFuaygpLCBheGlzLnRleHQueT1lbGVtZW50X2JsYW5rKCksIGF4aXMudGlja3MueT1lbGVtZW50X2JsYW5rKCkpLCB0b29sdGlwPSJ0ZXh0IikgJT4lCiAgc3R5bGUoaG92ZXJpbmZvID0gIm5vbmUiLCB0cmFjZXMgPSAxOjQpICU+JQogIGxheW91dChob3Zlcm1vZGU9ImNsb3Nlc3QiLCB4YXhpcz1saXN0KGZpeGVkcmFuZ2U9VCksIHlheGlzPWxpc3QoZml4ZWRyYW5nZT1UKSkKCm92ZXJ2aWV3ID0ga2FiaW5ldHRlWyxjKCJ3YWhsIiwgemVpdHB1bmt0ZSldCm92ZXJ2aWV3ID0gYXMuZGF0YS5mcmFtZShwaXZvdF9sb25nZXIob3ZlcnZpZXcsIGNvbHM9MjpuY29sKG92ZXJ2aWV3KSkpCmNvbG5hbWVzKG92ZXJ2aWV3KVszXSA9ICJBYndlaWNodW5nIgpvdmVydmlldyR0b29sdGlwID0gc2FwcGx5KDE6bnJvdyhvdmVydmlldyksIGZ1bmN0aW9uKGkpIGdldF90b29sdGlwKG92ZXJ2aWV3W2ksICJ3YWhsIl0sIG92ZXJ2aWV3W2ksICJuYW1lIl0pKQpvdmVydmlld1tpcy5uYShvdmVydmlldyRBYndlaWNodW5nKSwgInRvb2x0aXAiXSA9ICIiCm92ZXJ2aWV3JG5hbWVbb3ZlcnZpZXckbmFtZSA9PSAiNjAgdW5kIG1laHIiXSA9ICI2MCArIgoKcDIgPSBnZ3Bsb3RseShnZ3Bsb3Qob3ZlcnZpZXcsIGFlcyhuYW1lLCB3YWhsLCBmaWxsPUFid2VpY2h1bmcpKSArCiAgZ2VvbV90aWxlKGFlcyhmaWxsID0gQWJ3ZWljaHVuZyksIG5hLnJtPVQpICsKICBnZW9tX3RleHQoYWVzKGxhYmVsID0gaWZlbHNlKCFpcy5uYShBYndlaWNodW5nKSwgcGFzdGUwKCI8c3BhbiBzdHlsZT0nY29sb3I6IiwgaWZlbHNlKGFicyhBYndlaWNodW5nKTw1LCAiYmxhY2siLCAid2hpdGUiKSwgIic+IiwgaWZlbHNlKEFid2VpY2h1bmc+MCwgIisiLCAiIiksIHJvdW5kKEFid2VpY2h1bmcsMSksICIlPC9zcGFuPiIpLCAiIiksIHRleHQ9dG9vbHRpcCkpICsKICBzY2FsZV9maWxsX2dyYWRpZW50Mihsb3c9c2NhbGVzOjptdXRlZCgidmlvbGV0cmVkIiksIG1pZD0id2hpdGUiLCBoaWdoPXNjYWxlczo6bXV0ZWQoImFxdWFtYXJpbmUiKSkgKwogIHRoZW1lX21pbmltYWwoKSArIHRoZW1lKHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2JsYW5rKCksIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCkpLCB0b29sdGlwPSJ0ZXh0IikgJT4lCiAgc3R5bGUoaG92ZXJpbmZvID0gIm5vbmUiLCB0cmFjZXMgPSBjKDEpKSAlPiUKICBsYXlvdXQoeGF4aXM9bGlzdChmaXhlZHJhbmdlPVQsIHRpY2tmb250PWxpc3Qoc2l6ZT0xNikpLCB5YXhpcz1saXN0KGZpeGVkcmFuZ2U9VCwgdGlja2ZvbnQ9bGlzdChzaXplPTE2KSkpCgpwYXJ0aWFsX2J1bmRsZShzdWJwbG90KHAxLCBwbG90bHlfZW1wdHkoKSAlPiUgbGF5b3V0KHhheGlzPWxpc3QoZml4ZWRyYW5nZT1UKSwgeWF4aXM9bGlzdChmaXhlZHJhbmdlPVQpKSwgcDIsIHdpZHRocz1jKDAuMiwwLjAxLDAuNzkpKSAlPiUKICBjb25maWcoZGlzcGxheU1vZGVCYXIgPSBGQUxTRSkpCmBgYAoKRGllIGxpbmtlIFNwYWx0ZSB6ZWlndCBkaWUgZGV1dHNjaGVuIFJlZ2llcnVuZ3Nrb2FsaXRpb25lbiwgZGllIG5hY2ggZGVuIEJ1bmRlc3RhZ3N3YWhsZW4gc2VpdCAxOTcyIGdlYmlsZGV0IHd1cmRlbi4gRGllIFByb3plbnR6YWhsZW4gYmV6aWVoZW4gc2ljaCBhdWYgZGVuIFp3ZWl0c3RpbW1lbi1BbnRlaWwgZGVyIFJlZ2llcnVuZ3NwYXJ0ZWllbiwgZ2lidCBhbHNvIGluIGV0d2EgZGVuIEFudGVpbCBkZXIgV8OkaGxlcnNjaGFmdCB3aWRlciwgZGVyIGR1cmNoIGRpZSBSZWdpZXJ1bmcgcmVwcsOkc2VudGllcnQgd2lyZC4gRGVyIFRvb2x0aXAgw7xiZXIgZGVuIFByb3plbnR6YWhsZW4gbGlzdGV0IGRpZSBkaWUgWndlaXRzdGltbWVuIGRlciB2ZXJzY2hpZWRlbmVuIFBhcnRlaWVuIGF1Zi4gRGVyIFplaXRyYXVtIHd1cmRlIGdld8OkaGx0LCBkYSBkYXMgW1dhaGxhbHRlciAxOTcwIGF1ZiAxOCBKYWhyZV0oaHR0cHM6Ly9kZS53aWtpcGVkaWEub3JnL3dpa2kvV2FobHJlY2h0I0RldXRzY2hsYW5kKSByZWR1emllcnQgd3VyZGUuCgpEaWUgcmVjaHRlIFRhYmVsbGUgemVpZ3QgZGllIEFid2VpY2h1bmdlbiBkZXMgWndlaXRzdGltbWVuLUFudGVpbHMgZGVyIGRldXRzY2hlbiBSZWdpZXJ1bmdzcGFydGVpZW4gamUgbmFjaCBBbHRlcnNncnVwcGUgYW4sIGVybWl0dGVsdCBhdXMgZGVyIFtyZXByw6RzZW50YXRpdmVuIFdhaGxzdGF0aXN0aWtdKGh0dHBzOi8vd3d3LmJ1bmRlc3dhaGxsZWl0ZXIuZGUvYnVuZGVzdGFnc3dhaGxlbi8yMDIxL2luZm9ybWF0aW9uZW4td2FlaGxlci9yd3MuaHRtbCkuIDxzcGFuIHN0eWxlPSJjb2xvcjojZmZmOyBiYWNrZ3JvdW5kLWNvbG9yOiM4YzAwNWU7IGJveC1zaGFkb3c6IDBweCAtMXB4IDBweCAzcHggIzhjMDA1ZTsiPlZpb2xldHQ8L3NwYW4+IGhlcnZvcmdlaG9iZW5lIEFsdGVyc2dydXBwZW4gaGFiZW4gdmVyaMOkbHRuaXNtw6Rzc2lnIHdlbmlnZXIgWndlaXRzdGltbWVuIGbDvHIgZGllIFJlZ2llcnVuZ3NwYXJ0ZWllbiBhYmdlZ2ViZW4gYWxzIGRlciBEdXJjaHNjaG5pdHQsIHfDpGhyZW5kIDxzcGFuIHN0eWxlPSJjb2xvcjojZmZmOyBiYWNrZ3JvdW5kLWNvbG9yOiMxZDY3Mzg7IGJveC1zaGFkb3c6IDBweCAtMXB4IDBweCAzcHggIzFkNjczODsiPmdyw7xuZTwvc3Bhbj4gZGllIFJlZ2llcnVuZ3NwYXJ0ZWllbiBow6R1ZmlnZXIgd8OkaGx0ZW4uIDE5OTQgdW5kIDE5OTggd3VyZGUgZGllIHJlcHLDpHNlbnRhdGl2ZSBXYWhsc3RhdGlzdGlrIFthdXNnZXNldHp0XShodHRwczovL2RlLndpa2lwZWRpYS5vcmcvd2lraS9SZXByJUMzJUE0c2VudGF0aXZlX1dhaGxzdGF0aXN0aWsjR2VzY2hpY2h0ZSkuIERlciBBbnRlaWwgZGVyIHZlcnNjaGllZGVuZW4gQWx0ZXJzZ3J1cHBlbiBhbiBkZXIgQmV2w7Zsa2VydW5nIHZhcmlpZXJ0IHZvbiBKYWhyIHp1IEphaHIgdW5kIHVudGVybGllZ3QgZGVtIFtkZW1vZ3JhcGhpc2NoZW4gV2FuZGVsXShodHRwczovL2RlLndpa2lwZWRpYS5vcmcvd2lraS9EZW1vZ3JhZmlzY2hlcl9XYW5kZWxfaW5fRGV1dHNjaGxhbmQpLgoKRGF0ZW5xdWVsbGU6IFtCdW5kZXN3YWhsbGVpdGVyIChjc3YpXShodHRwczovL2J1bmRlc3dhaGxsZWl0ZXIuZGUvZGFtL2pjci8wYzhkZWNhOC00MDMwLTQxOWMtOWZhOC01YWI3NzBjZmExMjMvYnR3X3J3c196d3N0LTE5NTMuY3N2KSAvIFsocGRmKV0oaHR0cHM6Ly9idW5kZXN3YWhsbGVpdGVyLmRlL2RhbS9qY3IvOGFkMGNhMWYtYTAzNy00OGY4LWI5ZjQtYjU5OWRkMzgwZjAyL2J0dzIxX2hlZnQ0LnBkZikKCioqKioqKgoKVGhlIGxlZnQgY29sdW1uIHNob3dzIHRoZSBHZXJtYW4gZ292ZXJubWVudCBjb2FsaXRpb25zIGZvcm1lZCBzaW5jZSAxOTcyLiBUaGUgcGVyY2VudGFnZXMgaW5kaWNhdGUgdGhlIHByb3BvcnRpb24gdGhlIGdvdmVybm1lbnQgcGFydGllcyBnYWluZWQgdG9nZXRoZXIgaW4gdGhlIHJlc3BlY3RpdmUgcGFybGlhbWVudGFyeSBlbGVjdGlvbiAoaW4gc2Vjb25kYXJ5IHZvdGVzKSwgdGh1cyByb3VnaGx5IHJlcHJlc2VudGluZyB0aGUgcHJvcG9ydGlvbiBvZiB2b3RlcnMgcmVwcmVzZW50ZWQgYnkgdGhlIGdvdmVybm1lbnQuIFRoZSB0b29sdGlwIG92ZXIgdGhlIHBlcmNlbnRhZ2VzIHNob3dzIHRoZSB2b3RlcyBmb3IgZWFjaCBwYXJ0eS4gVGhlIHRpbWVmcmFtZSB3YXMgY2hvc2VuIGFzIHRoZSBbdm90aW5nIGFnZSB3YXMgcmVkdWNlZCB0byAxOCBpbiAxOTcwXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9TdWZmcmFnZSNHZXJtYW55KS4KClRoZSB0YWJsZSBvbiB0aGUgcmlnaHQgc2hvd3MgdGhlIGRldmlhdGlvbiBvZiBHZXJtYW4gZ292ZXJubWVudCBwYXJ0eSB2b3RlcyBieSBhZ2UgZ3JvdXBzLCBkZXJpdmVkIHRocm91Z2ggdGhlIFtyZXByZXNlbnRhdGl2ZSBlbGVjdG9yYWwgc3RhdGlzdGljc10oaHR0cHM6Ly93d3cuYnVuZGVzd2FobGxlaXRlci5kZS9lbi9idW5kZXN0YWdzd2FobGVuLzIwMjEvaW5mb3JtYXRpb25lbi13YWVobGVyL3J3cy5odG1sKS4gPHNwYW4gc3R5bGU9ImNvbG9yOiNmZmY7IGJhY2tncm91bmQtY29sb3I6IzhjMDA1ZTsgYm94LXNoYWRvdzogMHB4IC0xcHggMHB4IDNweCAjOGMwMDVlOyI+VmlvbGV0PC9zcGFuPiBhZ2UgZ3JvdXBzIHZvdGVkIGxlc3MgZm9yIGdvdmVybm1lbnQgcGFydGllcyBjb21wYXJlZCB0byB0aGUgYXZlcmFnZSwgd2hpbGUgPHNwYW4gc3R5bGU9ImNvbG9yOiNmZmY7IGJhY2tncm91bmQtY29sb3I6IzFkNjczODsgYm94LXNoYWRvdzogMHB4IC0xcHggMHB4IDNweCAjMWQ2NzM4OyI+Z3JlZW48L3NwYW4+IGFnZSBncm91cHMgdm90ZWQgZm9yIHRoZW0gbW9yZS4gVGhlIHJlcHJlc2VudGF0aXZlIGVsZWN0b3JhbCBzdGF0aXN0aWNzIHdlcmUgc3VzcGVuZGVkIGluIDE5OTQgYW5kIDE5OTguIFRoZSBwcm9wb3J0aW9uIG9mIHRoZSBkaWZmZXJlbnQgYWdlIGdyb3VwcyBhbW9uZyB0aGUgdG90YWwgcG9wdWxhdGlvbiB2YXJpZXMgZnJvbSB5ZWFyIHRvIHllYXIgYW5kIGlzIHN1YmplY3QgdG8gW2RlbW9ncmFwaGljIGNoYW5nZV0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvQWdlaW5nX29mX0V1cm9wZSNHZXJtYW55KS4KClNvdXJjZTogW0J1bmRlc3dhaGxsZWl0ZXIgKGNzdildKGh0dHBzOi8vYnVuZGVzd2FobGxlaXRlci5kZS9kYW0vamNyLzBjOGRlY2E4LTQwMzAtNDE5Yy05ZmE4LTVhYjc3MGNmYTEyMy9idHdfcndzX3p3c3QtMTk1My5jc3YpIC8gWyhwZGYpXShodHRwczovL2J1bmRlc3dhaGxsZWl0ZXIuZGUvZGFtL2pjci84YWQwY2ExZi1hMDM3LTQ4ZjgtYjlmNC1iNTk5ZGQzODBmMDIvYnR3MjFfaGVmdDQucGRmKQo=