Dokument po difoltu prikazuje rezultate (izlaz); kod je sakriven. Klikni na dugme „Code” (gore desno, ili pored svakog bloka) da vidiš kod koji ga je proizveo.

1. Paketi

Učitavanje i, po potrebi, instalacija svih paketa korišćenih u analizi (obrada podataka, prostorne operacije, vizuelizacija, kartogram i animacija).

pkgs <- c("readxl", "readr", "dplyr", "tidyr", "stringr",
          "sf", "ggplot2", "cartogram", "scales", "patchwork",
          "RColorBrewer", "rmapshaper", "gganimate", "transformr",
          "gifski", "ragg")
to_install <- pkgs[!pkgs %in% rownames(installed.packages())]
if (length(to_install)) install.packages(to_install)
invisible(lapply(pkgs, library, character.only = TRUE))

2. Rezultati po biračkim mestima

Učitavaju se rezultati na nivou biračkog mesta iz Excel fajla (list Rezultati_BM). Fajl sadrži kolone lista_1 … lista_30; u list_names se tim oznakama pridružuju stvarni nazivi lista (koristi se u legendi i paletama).

xlsx_path <- "15_10_53_04.11.2026.xlsx"   # <<< putanja do rezultata
bm <- read_excel(xlsx_path, sheet = "Rezultati_BM")

list_names <- c(
  lista_3  = "Studentska lista - Studenti pobeđuju",
  lista_1  = "Ujedinjena Srbija",
  lista_2  = "Faktor Stabilnosti",
  lista_5  = "Savez Vojvodjanskih Mađara",
  lista_10 = "SDA Sandžaka - Čuvari Sandžaka",
  lista_17 = "Albanska koalicija"
  # ... dopuni; nedefinisane ostaju "lista_n"
)

list_cols <- grep("^lista_", names(bm), value = TRUE)

3. Agregacija na nivo opštine

Glasovi se sabiraju sa biračkih mesta na nivo opštine (po sifra_opstine). Za svaku opštinu se zatim određuje pobednička lista (ona sa najviše glasova), njen udeo u važećim glasovima i margina nad drugoplasiranom.

op <- bm %>%
  mutate(sifra_opstine = as.character(sifra_opstine)) %>%
  group_by(sifra_opstine) %>%
  summarise(
    opstina = first(opstina),
    upisani = sum(upisani_biraci,  na.rm = TRUE),
    glasali = sum(glasali_biraci,  na.rm = TRUE),
    vazeci  = sum(vazeci_glasovi,  na.rm = TRUE),
    across(all_of(list_cols), ~ sum(.x, na.rm = TRUE)),
    .groups = "drop"
  ) %>%
  mutate(izlaznost = glasali / upisani)

win <- op %>%
  select(sifra_opstine, vazeci, all_of(list_cols)) %>%
  pivot_longer(all_of(list_cols), names_to = "lista", values_to = "glasovi") %>%
  group_by(sifra_opstine) %>%
  arrange(desc(glasovi), .by_group = TRUE) %>%
  summarise(
    pobednik = first(lista),
    g1 = first(glasovi),
    g2 = dplyr::nth(glasovi, 2),
    vazeci = first(vazeci),
    .groups = "drop"
  ) %>%
  mutate(
    udeo_pobednika = g1 / vazeci,
    margina        = (g1 - g2) / vazeci,
    pobednik_ime   = ifelse(pobednik %in% names(list_names),
                            list_names[pobednik], pobednik)
  )

op <- op %>% left_join(
  select(win, sifra_opstine, pobednik, pobednik_ime, udeo_pobednika, margina),
  by = "sifra_opstine")

4. Geometrija opština (CSV sa WKT, uključuje KiM)

Granice opština se čitaju iz CSV-a sa WKT kolonom geometry. Sloj uključuje opštine na KiM. Koordinate su u UTM 34N (EPSG:32634), pa se CRS samo dodeljuje.

csv_path <- "opstine.csv"   # <<< putanja do granica opština

geo <- read_csv(csv_path, show_col_types = FALSE) %>%
  mutate(sifra_opstine = as.character(opstina_maticni_broj))

srb <- st_as_sf(geo, wkt = "geometry", crs = 32634)
srb <- st_make_valid(srb)

5. Spajanje (po matičnom broju — egzaktno)

Rezultati i geometrija se spajaju po matičnom broju opštine — egzaktno, bez poklapanja imena. Ispis pokazuje eventualne opštine bez rezultata (npr. delovi KiM) i rezultate bez geometrije (kontrola kvaliteta spajanja).

map_df <- srb %>% left_join(st_drop_geometry(op), by = "sifra_opstine")

message("Opštine bez rezultata: ",
        paste(map_df$opstina_ime[is.na(map_df$pobednik)], collapse = ", "))
message("Rezultati bez geometrije: ",
        paste(op$opstina[!op$sifra_opstine %in% srb$sifra_opstine], collapse = ", "))

6. Boje

Paleta boja po pobedničkoj listi. Eksplicitno su definisane boje za glavne liste; za ostale (manjinske/lokalne pobednike) boje se dodeljuju automatski.

pal <- c(
  "Studentska lista - Studenti pobeđuju" = "#1FA187",
  "Ujedinjena Srbija"                    = "#4C72D4",
  "Faktor Stabilnosti"                   = "#B2182B",
  "Savez Vojvodjanskih Mađara"           = "#8C7B1F",
  "SDA Sandžaka - Čuvari Sandžaka"        = "#E05A4F",
  "Albanska koalicija"                   = "#D94801"
)
lev <- sort(unique(na.omit(map_df$pobednik_ime)))
extra <- lev[!lev %in% names(pal)]
if (length(extra)) pal <- c(pal, setNames(scales::hue_pal()(length(extra)), extra))

7. Slika 1 — geografska mapa pobednika

Šta prikazuje: svaka opština obojena je prema listi koja je u njoj pobedila. Ovo je geografski prikaz — površina opštine diktira vizuelni utisak (velike ruralne opštine „naduvavaju” svoju boju bez obzira na broj glasača).

m1 <- ggplot(map_df) +
  geom_sf(aes(fill = pobednik_ime), color = "white", linewidth = 0.15) +
  scale_fill_manual(values = pal, name = "Pobednička lista", na.value = "grey85") +
  labs(title = "Pobednik po opštinama",
       subtitle = "Geografski prikaz — „zemlja ne glasa\"") +
  theme_void(base_size = 13) + theme(legend.position = "right")

bb <- st_bbox(map_df)


m1 + annotate("text",
              x = (bb["xmin"] + bb["xmax"]) / 2,
              y = (bb["ymin"] + bb["ymax"]) / 2,
              label = "@aleksandarM",
              size = 16, color = "grey50", alpha = 0.3, fontface = "bold")

ggsave("mapa1_pobednik.png", m1, width = 9, height = 9, dpi = 200, bg = "white")

8. Slika 2 — Dorling kartogram (skaliran po broju glasača)

Šta prikazuje: iste opštine kao gore, ali svaka je krug srazmeran broju glasača (glasali), obojen po pobedničkoj listi. Teritorija više ne dominira — vidi se gde su glasovi stvarno koncentrisani (gradski centri postaju veliki krugovi).

cart_src <- map_df %>% filter(!is.na(glasali), glasali > 0)
dorling  <- cartogram_dorling(cart_src, weight = "glasali", k = 0.6)

m2 <- ggplot(dorling) +
  geom_sf(aes(fill = pobednik_ime), color = "white", linewidth = 0.2) +
  scale_fill_manual(values = pal, name = "Pobednička lista") +
  labs(title = "Pobednik ponderisan brojem glasača",
       subtitle = "Dorling kartogram — „...ali ljudi glasaju\"") +
  theme_void(base_size = 13) + theme(legend.position = "right")

m2 + annotate("text",
              x = (bb["xmin"] + bb["xmax"]) / 2,
              y = (bb["ymin"] + bb["ymax"]) / 2,
              label = "@aleksandarM",
              size = 16, color = "grey50", alpha = 0.3, fontface = "bold")

ggsave("mapa2_dorling.png", m2, width = 9, height = 9, dpi = 200, bg = "white")

9. PNG — start vs. end

Šta prikazuje: dve prethodne mape jedna pored druge — levo geografija („zemlja”), desno glasači („ljudi”). Direktno poređenje otkriva koliko se slika menja kada se sa površine pređe na broj glasača.

steps <- (m1 + theme(legend.position = "none") + labs(subtitle = "Start: geografija")) +
         (m2 + labs(subtitle = "End: glasači")) +
         patchwork::plot_layout(ncol = 2, guides = "collect") +
         patchwork::plot_annotation(title = "Zemlja ne glasa, ljudi glasaju",
                                    subtitle = "Rezultati po opštinama, Srbija (sa KiM)")

steps + annotate("text",
              x = (bb["xmin"] + bb["xmax"]) / 2,
              y = (bb["ymin"] + bb["ymax"]) / 2,
              label = "@aleksandarM",
              size = 16, color = "grey50", alpha = 0.3, fontface = "bold")

ggsave("steps.png", steps, width = 16, height = 9, dpi = 200, bg = "white")

10. Animacija (zemlja → ljudi)

Šta prikazuje: animirani prelaz iz geografske mape u Dorling kartogram. Boja je prednost liste 3 nad listom 1 ((lista_3 − lista_1)/vazeci): tirkizno = vodi lista 3, plavo = vodi lista 1, belo = tesno. U pokretu se vidi kako se velike ruralne opštine skupljaju (malo glasača), a gradovi šire.

# base <- map_df %>%
#   filter(!is.na(vazeci), vazeci > 0) %>%
#   mutate(prednost_l3 = (lista_3 - lista_1) / vazeci)
# 
# base <- ms_simplify(base, keep = 0.02, keep_shapes = TRUE)   # ubrzanje
# 
# geo_s <- base %>% mutate(state = "1. Zemlja")
# 
# if (file.exists("dorling.rds")) {                            # keširaj spor Dorling
#   dor_s <- readRDS("dorling.rds")
# } else {
#   dor_s <- cartogram_dorling(base, weight = "glasali", k = 0.6) %>%
#     mutate(state = "2. Ljudi")
#   saveRDS(dor_s, "dorling.rds")
# }
# 
# anim_df <- rbind(
#   geo_s %>% select(sifra_opstine, prednost_l3, state, geometry),
#   dor_s %>% select(sifra_opstine, prednost_l3, state, geometry)
# )
# 
# p <- ggplot(anim_df) +
#   geom_sf(aes(fill = prednost_l3, group = sifra_opstine), color = NA) +
#   scale_fill_gradient2(low = "#4C72D4", mid = "grey95", high = "#1FA187",
#                        midpoint = 0, guide = "none") +   # usklađeno sa pal
#   theme_void() +
#   transition_states(state, transition_length = 3, state_length = 1.5) +
#   ease_aes("cubic-in-out")
# 
# animate(p, nframes = 50, fps = 20, bg = "white",
#         device = "ragg_png", width = 700, height = 550, units = "px", res = 100,
#         renderer = gifski_renderer("animacija_srbija.gif"))

base <- map_df %>%
  filter(!is.na(vazeci), vazeci > 0) %>%
  mutate(prednost_l3 = (lista_3 - lista_1) / vazeci)

base <- ms_simplify(base, keep = 0.02, keep_shapes = TRUE)   # ubrzanje

geo_s <- base %>% mutate(state = "1. Zemlja")

if (file.exists("dorling.rds")) {                            # keširaj spor Dorling
  dor_s <- readRDS("dorling.rds")
} else {
  dor_s <- cartogram_dorling(base, weight = "glasali", k = 0.6) %>%
    mutate(state = "2. Ljudi")
  saveRDS(dor_s, "dorling.rds")
}

anim_df <- rbind(
  geo_s %>% select(sifra_opstine, prednost_l3, state, geometry),
  dor_s %>% select(sifra_opstine, prednost_l3, state, geometry)
)

bb <- st_bbox(anim_df)                                       # za poziciju žiga

p <- ggplot(anim_df) +
  geom_sf(aes(fill = prednost_l3, group = sifra_opstine), color = NA) +
  scale_fill_gradient2(low = "#4C72D4", mid = "grey95", high = "#1FA187",
                       midpoint = 0, guide = "none") +   # usklađeno sa pal
  annotate("text",
           x = (bb["xmin"] + bb["xmax"]) / 2,               # horizontalni centar
           y = bb["ymin"],                                  # dole
           label = "@aleksandarM", vjust = -0.5, size = 5,
           color = "grey50", alpha = 0.4, fontface = "bold") +
  theme_void() +
  transition_states(state, transition_length = 3, state_length = 1.5) +
  ease_aes("cubic-in-out")

animate(p, nframes = 50, fps = 20, bg = "white",
        device = "ragg_png", width = 700, height = 550, units = "px", res = 100,
        renderer = gifski_renderer("animacija_srbija.gif"))

11. Udeo republičkog pobednika (binovana skala)

Šta prikazuje: udeo liste 3 (republički pobednik) u važećim glasovima, na binovanoj skali usklađenoj sa paletom pobednika (tirkizno = visok udeo liste 3, plavo liste 1 = nizak, centar na 50%). Levo geografija, desno Dorling — ista skala na obe.

baseR <- map_df %>%
  filter(!is.na(vazeci), vazeci > 0) %>%
  mutate(udeo_l3 = 100 * lista_3 / vazeci)

dorR <- cartogram_dorling(baseR, weight = "glasali", k = 0.6)

div_cols <- colorRampPalette(c("#4C72D4", "grey95", "#1FA187"))(9)  # lista_1 -> lista_3
fill_l3 <- scale_fill_stepsn(
  colours = div_cols,
  breaks  = c(35, 40, 45, 50, 55, 60, 65),
  limits  = c(25, 75),
  name    = "Procenat glasova za listu 3 (Studenti pobeđuju)",
  na.value = "grey85",
  guide   = guide_colorsteps(barwidth = 20, barheight = 0.6,
                             title.position = "top", title.hjust = 0.5)
)

m_geo <- ggplot(baseR) + geom_sf(aes(fill = udeo_l3), color = NA) +
  fill_l3 + theme_void()
m_dor <- ggplot(dorR) + geom_sf(aes(fill = udeo_l3), color = "white", linewidth = 0.1) +
  fill_l3 + theme_void()

fig <- (m_geo + m_dor) + plot_layout(guides = "collect") &
  theme(legend.position = "bottom")

fig + annotate("text",
              x = (bb["xmin"] + bb["xmax"]) / 2,
              y = (bb["ymin"] + bb["ymax"]) / 2,
              label = "@aleksandarM",
              size = 14, color = "grey50", alpha = 0.3, fontface = "bold")

ggsave("steps_republika_l3.png", fig, width = 16, height = 7, dpi = 200, bg = "white")