Skip to content

How to create transparent slices for missing categories in scatterpie charts on maps? #53

Description

@nikosGeography

I'm creating pie charts overlaid on a map using ggplot2, sf, and scatterpie. My point shapefile contains 58 cities with binary land use columns (retail, industrial, airport) where 1 = present and 0 = absent.

The issue is that cities with fewer land use types show pies with fewer slices (e.g., a city with only industrial land use shows a single-slice pie). I want all pie charts to have exactly 3 slices, where missing land use types appear as transparent slices for visual consistency.

# Load required libraries
library(sf)
library(ggplot2)
library(dplyr)
library(scatterpie)

# Read the shapefiles
world_cities <- read_sf("path/world_cities_filtered.shp")

# extract coordinates from the geometry column
coords <- st_coordinates(world_cities)
world_cities_df <- world_cities %>%
  st_drop_geometry() %>%
  mutate(
    lon = coords[, 1],
    lat = coords[, 2]
  )

# map with pie charts
map_plot <- ggplot() +
  theme_void() +
  theme(
    panel.grid.major = element_line(color = "darkgray", size = 0.3, linetype = 2),
    legend.position = "bottom",
    legend.title = element_text(size = 12, face = "bold"),
    legend.text = element_text(size = 10),
    plot.title = element_text(size = 16, face = "bold", hjust = 0.5),
    plot.subtitle = element_text(size = 12, hjust = 0.5)
  ) +
  coord_sf(expand = FALSE,
           datum = st_crs(countries)) +
  geom_scatterpie(data = world_cities_df,
                  aes(x = lon, y = lat),
                  cols = c("retail", "industrial", "airport"),
                  pie_scale = 1.5,  # Adjust this to change pie size
                  alpha = 0.8) +
  scale_fill_manual(values = c("retail" = "#E74C3C", 
                               "industrial" = "#3498DB", 
                               "airport" = "#2ECC71"),
                    name = "Archetype",
                    labels = c("Airport", "Industrial", "Retail"))

print(map_plot)

This approach creates very thin slices for missing categories, but they're still somewhat visible rather than truly transparent. Sample data:

> dput(world_cities)
structure(list(CITY_NAME = c("Shenzhen", "Santiago", "Lima", 
"Buenos Aires", "Sao Paulo", "Montevide", "Rio de Janeiro", 
"Calgary", "Los Angeles", "Dallas", "Mexico City", "Toronto", 
"Chicago", "Rome", "Cairo", "Athens", "Istanbul", "Jeddah", "Frankfurt", 
"Milan", "Vienna", "Munich", "Berlin", "Lahore", "Delhi", "Almaty", 
"Mumbai", "Pune", "Shanghai", "Wuhan", "Guangzhou", "Beijing", 
"Seoul", "Fukuoka", "Hong Kong", "Tokyo", "Osaka", "Brisbane", 
"Washington D.C.", "New York", "Caracas", "London", "Manchester", 
"Madrid", "Paris", "Amsterdam", "Geneva", "Warsaw", "Riyadh", 
"Dubai", "Abu Dhabi", "Baku", "Cape Town", "Dar es Salaam", "Nairobi", 
"Johannesburg", "Sydney", "Melbourne"), lu_num = c(2L, 2L, 2L, 
2L, 2L, 1L, 1L, 3L, 3L, 2L, 2L, 2L, 2L, 2L, 2L, 1L, 1L, 2L, 1L, 
3L, 1L, 1L, 1L, 2L, 2L, 3L, 2L, 2L, 1L, 2L, 2L, 1L, 2L, 3L, 2L, 
2L, 1L, 3L, 1L, 3L, 2L, 2L, 3L, 3L, 2L, 3L, 1L, 3L, 3L, 2L, 2L, 
2L, 2L, 2L, 2L, 2L, 3L, 3L), retail = c(0L, 0L, 1L, 1L, 1L, 0L, 
0L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 0L, 0L, 0L, 0L, 0L, 1L, 0L, 0L, 
0L, 0L, 0L, 1L, 0L, 0L, 0L, 1L, 0L, 0L, 0L, 1L, 0L, 0L, 0L, 1L, 
0L, 1L, 1L, 0L, 1L, 1L, 1L, 1L, 0L, 1L, 1L, 0L, 0L, 1L, 0L, 0L, 
0L, 1L, 1L, 1L), industrial = c(1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 
1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 
1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 
1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 
1L, 1L), airport = c(1L, 1L, 0L, 0L, 0L, 0L, 0L, 1L, 1L, 0L, 
0L, 0L, 0L, 0L, 1L, 0L, 0L, 1L, 0L, 1L, 0L, 0L, 0L, 1L, 1L, 1L, 
1L, 1L, 0L, 0L, 1L, 0L, 1L, 1L, 1L, 1L, 0L, 1L, 0L, 1L, 0L, 1L, 
1L, 1L, 0L, 1L, 0L, 1L, 1L, 1L, 1L, 0L, 1L, 1L, 1L, 0L, 1L, 1L
), geometry = structure(list(structure(c(114.052516072688, 22.6710752741631
), class = c("XY", "POINT", "sfg")), structure(c(-70.647515553854, 
-33.4750230512851), class = c("XY", "POINT", "sfg")), structure(c(-77.0450036007241, 
-12.0819959357647), class = c("XY", "POINT", "sfg")), structure(c(-58.4498336968446, 
-34.622496010243), class = c("XY", "POINT", "sfg")), structure(c(-46.6229965826814, 
-23.5809989994226), class = c("XY", "POINT", "sfg")), structure(c(-56.1699985882875, 
-34.9200000502336), class = c("XY", "POINT", "sfg")), structure(c(-43.4551855922148, 
-22.7215710345035), class = c("XY", "POINT", "sfg")), structure(c(-114.049997573253, 
51.0299999453473), class = c("XY", "POINT", "sfg")), structure(c(-118.250000641271, 
34.0000019590779), class = c("XY", "POINT", "sfg")), structure(c(-96.6636896048789, 
32.7637260006132), class = c("XY", "POINT", "sfg")), structure(c(-99.1275746461327, 
19.4270490779828), class = c("XY", "POINT", "sfg")), structure(c(-79.4126335823368, 
43.7207669366832), class = c("XY", "POINT", "sfg")), structure(c(-87.6412976068233, 
41.8265459875429), class = c("XY", "POINT", "sfg")), structure(c(12.519999338143, 
41.8799970439333), class = c("XY", "POINT", "sfg")), structure(c(31.250799318015, 
30.0779099967854), class = c("XY", "POINT", "sfg")), structure(c(23.6529993798512, 
37.9439999862214), class = c("XY", "POINT", "sfg")), structure(c(29.0060014026546, 
41.0660009627707), class = c("XY", "POINT", "sfg")), structure(c(39.173004319785, 
21.5430030712411), class = c("XY", "POINT", "sfg")), structure(c(8.66816131201369, 
50.1300000207709), class = c("XY", "POINT", "sfg")), structure(c(9.18999930279142, 
45.4730040647418), class = c("XY", "POINT", "sfg")), structure(c(16.3209784439172, 
48.2021190334445), class = c("XY", "POINT", "sfg")), structure(c(11.5429503873952, 
48.1409729869083), class = c("XY", "POINT", "sfg")), structure(c(13.3275693578572, 
52.5162689233538), class = c("XY", "POINT", "sfg")), structure(c(74.340999441186, 
31.5450000806422), class = c("XY", "POINT", "sfg")), structure(c(77.2166614428691, 
28.6666650214145), class = c("XY", "POINT", "sfg")), structure(c(76.9126234460844, 
43.2550619959582), class = c("XY", "POINT", "sfg")), structure(c(72.8260023344842, 
19.077002983341), class = c("XY", "POINT", "sfg")), structure(c(73.8522724138133, 
18.5357430029184), class = c("XY", "POINT", "sfg")), structure(c(121.473000419805, 
31.2479999383934), class = c("XY", "POINT", "sfg")), structure(c(114.279003280991, 
30.5730000363321), class = c("XY", "POINT", "sfg")), structure(c(113.293611306089, 
23.0961870216222), class = c("XY", "POINT", "sfg")), structure(c(116.388036416661, 
39.9061890457427), class = c("XY", "POINT", "sfg")), structure(c(126.935244328844, 
37.5423570795889), class = c("XY", "POINT", "sfg")), structure(c(130.401990296501, 
33.5799989714409), class = c("XY", "POINT", "sfg")), structure(c(114.176997333231, 
22.2740009886894), class = c("XY", "POINT", "sfg")), structure(c(139.809006365241, 
35.683002048058), class = c("XY", "POINT", "sfg")), structure(c(135.51900335441, 
34.6359960388313), class = c("XY", "POINT", "sfg")), structure(c(153.026001368553, 
-27.453995931682), class = c("XY", "POINT", "sfg")), structure(c(-76.9538336884421, 
38.8909080742766), class = c("XY", "POINT", "sfg")), structure(c(-73.9052366295063, 
40.7078640410705), class = c("XY", "POINT", "sfg")), structure(c(-66.8982775618213, 
10.4960429483843), class = c("XY", "POINT", "sfg")), structure(c(-0.178001676555652, 
51.4879109366984), class = c("XY", "POINT", "sfg")), structure(c(-2.26178068198436, 
53.4796649757786), class = c("XY", "POINT", "sfg")), structure(c(-3.69097169824494, 
40.4422200735065), class = c("XY", "POINT", "sfg")), structure(c(2.3549531482218, 
48.8582874334995), class = c("XY", "POINT", "sfg")), structure(c(4.89483932469335, 
52.3730429819271), class = c("XY", "POINT", "sfg")), structure(c(6.13400429687772, 
46.2020039324906), class = c("XY", "POINT", "sfg")), structure(c(21.0118773681439, 
52.2449460530621), class = c("XY", "POINT", "sfg")), structure(c(46.770003317039, 
24.6500009682933), class = c("XY", "POINT", "sfg")), structure(c(55.3290033394721, 
25.2710010701508), class = c("XY", "POINT", "sfg")), structure(c(54.3709984136918, 
24.4760040024004), class = c("XY", "POINT", "sfg")), structure(c(49.8159993038217, 
40.3239960652242), class = c("XY", "POINT", "sfg")), structure(c(18.4820043939735, 
-33.9789959226824), class = c("XY", "POINT", "sfg")), structure(c(39.2533472981898, 
-6.8173560640002), class = c("XY", "POINT", "sfg")), structure(c(36.8039973486453, 
-1.26999894459972), class = c("XY", "POINT", "sfg")), structure(c(28.0043104457209, 
-26.1789570809208), class = c("XY", "POINT", "sfg")), structure(c(151.028199398186, 
-33.8897699469433), class = c("XY", "POINT", "sfg")), structure(c(145.075104313526, 
-37.8529559698376), class = c("XY", "POINT", "sfg"))), n_empty = 0L, crs = structure(list(
    input = "WGS 84", wkt = "GEOGCRS[\"WGS 84\",\n    DATUM[\"World Geodetic System 1984\",\n        ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n            LENGTHUNIT[\"metre\",1]]],\n    PRIMEM[\"Greenwich\",0,\n        ANGLEUNIT[\"degree\",0.0174532925199433]],\n    CS[ellipsoidal,2],\n        AXIS[\"latitude\",north,\n            ORDER[1],\n            ANGLEUNIT[\"degree\",0.0174532925199433]],\n        AXIS[\"longitude\",east,\n            ORDER[2],\n            ANGLEUNIT[\"degree\",0.0174532925199433]],\n    ID[\"EPSG\",4326]]"), class = "crs"), class = c("sfc_POINT", 
"sfc"), precision = 0, bbox = structure(c(xmin = -118.250000641271, 
ymin = -37.8529559698376, xmax = 153.026001368553, ymax = 53.4796649757786
), class = "bbox"))), row.names = c(NA, -58L), class = c("sf", 
"tbl_df", "tbl", "data.frame"), sf_column = "geometry", agr = structure(c(CITY_NAME = NA_integer_, 
lu_num = NA_integer_, retail = NA_integer_, industrial = NA_integer_, 
airport = NA_integer_), class = "factor", levels = c("constant", 
"aggregate", "identity")))

Is there a better method in scatterpie to create truly transparent slices for categories with value 0, while maintaining consistent 3-slice pie structure across all cities?

> sessionInfo()
R version 4.5.1 (2025-06-13 ucrt)
Platform: x86_64-w64-mingw32/x64
Running under: Windows 11 x64 (build 26100)

Matrix products: default
  LAPACK version 3.12.1

locale:
[1] LC_COLLATE=English_United States.utf8  LC_CTYPE=English_United States.utf8    LC_MONETARY=English_United States.utf8
[4] LC_NUMERIC=C                           LC_TIME=English_United States.utf8    

time zone: Europe/Bucharest
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] scatterpie_0.2.6 ggplot2_4.0.0    dplyr_1.1.4      sf_1.0-21       

loaded via a namespace (and not attached):
 [1] gtable_0.3.6       crayon_1.5.3       compiler_4.5.1     tidyselect_1.2.1   Rcpp_1.1.0         dichromat_2.0-0.1  tidyr_1.3.1       
 [8] ggfun_0.2.0        scales_1.4.0       R6_2.6.1           generics_0.1.4     classInt_0.4-11    yulab.utils_0.2.1  MASS_7.3-65       
[15] polyclip_1.10-7    tibble_3.3.0       units_0.8-7        DBI_1.2.3          pillar_1.11.0      RColorBrewer_1.1-3 rlang_1.1.6       
[22] fs_1.6.6           S7_0.2.0           cli_3.6.5          withr_3.0.2        magrittr_2.0.4     tweenr_2.0.3       class_7.3-23      
[29] digest_0.6.37      grid_4.5.1         rstudioapi_0.17.1  ggforce_0.5.0      rappdirs_0.3.3     lifecycle_1.0.4    vctrs_0.6.5       
[36] KernSmooth_2.23-26 proxy_0.4-27       glue_1.8.0         farver_2.1.2       e1071_1.7-16       purrr_1.1.0        tools_4.5.1       
[43] pkgconfig_2.0.3

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions