# Prepare contested-vote classifications and shared plot metadata.
votes_contested <- items_votes %>%
mutate(vote_id = paste(legis_period, item_url, sep = "|")) %>%
group_by(vote_id, legis_period, item_url) %>%
summarise(
vote_types_n = n_distinct(party_infavor),
vote_record = paste(paste(text, party_infavor, sep = ":"), collapse = "; "),
# vote_favor_share = sum(fraction[party_infavor], na.rm = TRUE) /
# sum(fraction, na.rm = TRUE),
.groups = "drop"
) %>%
mutate(contested = if_else(vote_types_n > 1, "split", "unanimous"))
# Count voted items per legislative period for plot annotations and facet labels.
items_voted_n <- items_votes %>%
mutate(vote_id = paste(legis_period, item_url, sep = "|")) %>%
distinct(legis_period, vote_id) %>%
count(legis_period, name = "items_voted_n") %>%
mutate(items_voted_label = glue::glue("n={items_voted_n}"))
# Define governing parties by legislative period for facet labels and party ordering. This could be also done programmatically with ParlAT's get_mps, but I wanted to keep as direct as possible here.
gov_parties <- tribble(
~legis_period , ~text , ~gov_rank ,
"XX" , "SPÖ" , 1 ,
"XX" , "ÖVP" , 2 ,
"XXI" , "ÖVP" , 1 ,
"XXI" , "F" , 2 ,
"XXII" , "ÖVP" , 1 ,
"XXII" , "F" , 2 ,
"XXII" , "F-BZÖ" , 3 ,
"XXIII" , "SPÖ" , 1 ,
"XXIII" , "ÖVP" , 2 ,
"XXIV" , "SPÖ" , 1 ,
"XXIV" , "ÖVP" , 2 ,
"XXV" , "SPÖ" , 1 ,
"XXV" , "ÖVP" , 2 ,
"XXVI" , "ÖVP" , 1 ,
"XXVI" , "FPÖ" , 2 ,
"XXVII" , "ÖVP" , 1 ,
"XXVII" , "GRÜNE" , 2 ,
"XXVIII" , "ÖVP" , 1 ,
"XXVIII" , "SPÖ" , 2 ,
"XXVIII" , "NEOS" , 3
)
# Collapse governing parties into one display label per legislative period. Will be used for annotations later.
gov_party_labels <- gov_parties %>%
arrange(legis_period, gov_rank) %>%
summarise(
gov_parties = paste(text, collapse = " + "),
.by = legis_period
)
# Build reusable facet labels with period years, government parties, and total items.
legis_period_labels <- ParlAT::get_legis_periods() %>%
filter(legis_period_abbrev %in% unique(items_votes$legis_period)) %>%
left_join(items_voted_n, by = c("legis_period_abbrev" = "legis_period")) %>%
left_join(
gov_party_labels,
by = c("legis_period_abbrev" = "legis_period")
) %>%
mutate(
years = if_else(
is.na(date_end),
paste0(format(date_start, "%Y"), "-present"),
paste0(format(date_start, "%Y"), "-", format(date_end, "%Y"))
),
label = paste0(
"<b>",
legis_period_abbrev,
"</b>",
" (",
years,
")",
"<br>",
"Gov: ",
gov_parties,
"<br>",
"Total items: ",
scales::number(items_voted_n, accuracy = 1)
)
) %>%
select(name = legis_period_abbrev, value = label) %>%
tibble::deframe()
# Create a compact description of included item types for graph captions.
items_label_str <- items_scope %>%
distinct(type_doc, type_doc_long) %>%
arrange(type_doc) %>%
mutate(label = paste0(type_doc_long, " (", type_doc, ")")) %>%
pull(label) %>%
paste(collapse = ", ")
# Define one shared caption used by all vote visualisations.
vote_graph_caption <- paste0(
"Voting on the following items ('Verhandlungsgegenstände') was included: ",
items_label_str,
"<br><br>Data: www.parlament.gv.at via ParlAT package, as of ",
data_cutoff,
"<br>Graphic: Roland Schmidt, https://werk.statt.codes | Bluesky: @zoowalk.bsky.social"
)
# Shared sizes for a consistent look across all graphs. Every plot renders at
# out-width 100% in the same body-outset-right column, so on-screen text size depends
# only on point-size / fig-width. Keeping fig-width identical in every chunk
# header (gg_fig_width) plus these point sizes guarantees equal visible text.
gg_fig_width <- 11 # set as fig-width in every plot chunk header
gg_base_size <- 12
gg_title_size <- 17
gg_subtitle_size <- 13.5
gg_caption_size <- 10.5
gg_strip_size <- 12
gg_axis_size <- 10
gg_axis_colour <- "#4D4D4D"
gg_annot_size <- 3.7 # geom_text data-label size (mm)
gg_plot_family <- "Noto Sans"
noto_sans_svg_fonts <- list(
systemfonts::fonts_as_import(
gg_plot_family,
weight = c("normal", "bold"),
type = "import",
repositories = NULL,
may_embed = TRUE
)
)
svg_noto <- function(filename, width, height, ...) {
svglite::svglite(
filename = filename,
width = width,
height = height,
web_fonts = noto_sans_svg_fonts,
...
)
}
vote_stance_colors <- c(
"In favour" = "#4575b4",
"Not in favour" = "#d73027"
)
vote_split_colors <- c(
"unanimous" = "#7f7f7f",
"split" = "#e69f00"
)
# Shared theme applied to every plot; per-plot specifics are layered on top.
theme_vote_plot <- function(base_size = gg_base_size) {
theme_minimal(base_size = base_size, base_family = gg_plot_family) +
theme(
panel.grid.minor = element_blank(),
plot.title.position = "plot",
plot.caption.position = "plot",
plot.title = element_text(
hjust = 0,
size = gg_title_size,
family = gg_plot_family,
face = "bold",
margin = ggplot2::margin(b = 8)
),
plot.subtitle = ggtext::element_textbox_simple(
size = gg_subtitle_size,
family = gg_plot_family,
lineheight = 1.15,
margin = ggplot2::margin(t = 4, b = 12)
),
plot.caption = ggtext::element_textbox_simple(
hjust = 0,
halign = 0,
size = gg_caption_size,
family = gg_plot_family,
lineheight = 1.1,
margin = ggplot2::margin(t = 14)
),
strip.text = ggtext::element_markdown(
size = gg_strip_size,
family = gg_plot_family,
lineheight = 1.2,
hjust = 0
),
axis.text = element_text(
size = gg_axis_size,
family = gg_plot_family,
colour = gg_axis_colour
),
axis.title = element_text(
family = gg_plot_family,
colour = gg_axis_colour
),
legend.position = "bottom",
legend.justification = "left",
plot.margin = ggplot2::margin(t = 8, r = 8, b = 8, l = 8)
)
}
# Calculate relative shares of unanimous and split votes per legislative period.
votes_contested_shares <- votes_contested %>%
mutate(
contested = factor(contested, levels = c("unanimous", "split"))
) %>%
count(legis_period, contested, name = "votes_n") %>%
mutate(
vote_share = votes_n / sum(votes_n),
vote_share_label = scales::percent(vote_share, accuracy = 1),
.by = legis_period
) %>%
left_join(items_voted_n, by = "legis_period")