DataCommons / app.R
luciagomez's picture
Update app.R
831d877 verified
required_packages <- c("leaflet","shiny","dplyr","RColorBrewer","scales","lattice","plotly","bslib","shinydashboard","remotes")
remotes::install_github("briatte/ggnet")
#remotes::install_github("rspatial/terra")
options(repos = c(CRAN = "https://cloud.r-project.org"))
for (pkg in required_packages) {
if (!requireNamespace(pkg, quietly = TRUE)) { install.packages(pkg) }
library(pkg, character.only = TRUE)
}
lapply(c("leaflet","shiny","dplyr","RColorBrewer","scales","lattice",
"plotly","ggnet","bslib","shinydashboard"), require, character.only = TRUE)
# install.packages("leafpop") # this is for the popups in leaflet
# for testing, set working directory to the app directory
#setwd("/Users/luciagomezteijeiro/Library/CloudStorage/OneDrive-UniversitédeGenève/BPL/Nisa_Lucia_Vini/philea/app")
# Load data
geo <- as.data.frame(readRDS("data/geo_QC_annotated_connectivity_rescaled.rds"))
geo <- geo[!is.na(geo$lat | !is.na(geo$lon)),] # remove foundations without addresses
p_members_data <- readRDS("data/p_members_data.rds")
p_purposes_data <- readRDS("data/p_purposes_data.rds")
p_geo_data <- readRDS("data/p_geo_data.rds")
# PREPARE POPUP FOR MAP AS FORMATED TEXT
geo$popup_content <- paste0(
"<b><span style='font-size:16px;'>", ifelse(is.na(geo$name_clean), "No name available", geo$name_clean), "</span></b><br>","<br>",
"<b>GPS Coordinates:</b> ", ifelse(is.na(geo$lat), "No latitude available", round(geo$lat,2)), ", ", ifelse(is.na(geo$lon), "No longitude available", round(geo$lon,2)), "<br>", # Example variable 1
"<b>ID:</b> ", ifelse(is.na(geo$ID), "Not available", geo$ID), "<br>","<br>",
"<b><span style='font-size:14px;'> Overall Connectivity Score:</b> ", ifelse(is.na(geo$overall_connectivity), "Not available", round(geo$overall_connectivity,2)), "<br>",
"<b>Board Connectivity Score: </b> ", ifelse(is.na(geo$strength_memb_scaled), "Not available", round(geo$strength_memb_scaled,2)), "<br>",
"<b>Purpose Connectivity Score: </b> ", ifelse(is.na(geo$strength_purp_scaled), "Not available", round(geo$strength_purp_scaled,2)), "<br>",
"<b>Geography Connectivity Score: </b> ", ifelse(is.na(geo$strength_geo_scaled), "Not available", round(geo$strength_geo_scaled,2)), "<br>","<br>",
"<b><span style='font-size:14px;'> Most connected foundations:</b> ","<br>",
"<b>Closer by board: </b> ", ifelse(is.na(geo$top_memb), "Not available", geo$top_memb), "<br>",
"<b>Closer by purpose: </b> ", ifelse(is.na(geo$top_purp), "Not available", geo$top_purp), "<br>",
"<b>Closer by geography: </b> ", ifelse(is.na(geo$top_geo), "Not available", geo$top_geo), "<br>"
# "<b>Funding:</b> $", format(funding, big.mark = ","), "<br>", # If you want to add financials
# "<b>Website:</b> <a href='", geo$website, "' target='_blank'>Visit</a>" # If you want a clickable URL
)
ui <- navbarPage("Spanish Philanthropic Landscape",id="nav",
# THEME
# --- # --- # --- # --- # --- # --- # --- # --- # --- # --- # --- # --- # --- # --- #
#theme = bs_theme(version = 4), # uncomment this to have a theme selector in the navbar, do the same in server side with bs_themer()
theme = bs_theme(version = 4, bootswatch = "lumen"),
tags$style(HTML(
# custom css to modify theme
"
.navbar-brand { font-size: 25px !important;}
body { font-size: 16px; }
h1 { font-size: 40px; }
h2, h3, h4, h5, h6 { font-size: 20px; }
.navbar, .navbar-nav, .navbar-default, .navbar-header { font-size: 20px !important; }
.container-fluid { font-size: 16px; }
"
)),
# --- # --- # --- # --- # --- # --- # --- # --- # --- # --- # --- # --- # --- # --- #
# FIRST PAGE - INTERACTIVE MAP
# --- # --- # --- # --- # --- # --- # --- # --- # --- # --- # --- # --- # --- # --- #
tabPanel("Interactive Map",
fluidRow(column(12,
absolutePanel(top = 10, left = 20, right=20, width = "95%",
div(class="container-fluid",
selectizeInput("name_clean",
label=tagList(
tags$strong("Highlight one or several Foundations by Name")
),
choices = NULL, multiple = TRUE,
options = list(placeholder = "Search a foundation..."),width="100%") )
),
div(style = "margin-left: 20px; margin-right:20px; margin-top: 80px; font-size: 12px; color: #555; text-align: left;",
"Use the search bar to find and highlight foundations on the map.
All foundations are displayed by default in blue, while the selected ones will be highlighted in red.")
)),
fluidRow(column(12,
div(style = "padding-top: 25px;margin-left: 20px; margin-right:20px;", leafletOutput("map",height = "600px"))
)),
fluidRow(column(12,
div(style= "margin-left: 20px; margin-right:20px; margin-top:20px; font-size: 12px; color: #555; text-align: center;",
"Data compiled for ",
tags$em("PIO Winter Meeting 2025"),
" by Lucia Gomez Teijeiro (1), Nisa Thomas (2) and Jack O'Neill (3) (University of Geneva (1,2) / BFH (1) and Philea (3) respectively, 2025)",
tags$br(),
"Methodology derived from the Mapping Philanthropy research project. Open Source and available at",
tags$a(href="https://github.com/gomez-L/mapping_philanthropy","https://github.com/gomez-L/mapping_philanthropy",target="https://github.com/gomez-L/mapping_philanthropy")
)))
),
# --- # --- # --- # --- # --- # --- # --- # --- # --- # --- # --- # --- # --- # --- #
# SECOND PAGE - NETWORK EXPLORER
# --- # --- # --- # --- # --- # --- # --- # --- # --- # --- # --- # --- # --- # --- #
tabPanel("Network Explorer",
fluidRow(column(12,
absolutePanel(top = 10, left = 20, right=20, width = "95%",
div(class="container-fluid",
selectizeInput("label_plotly",
label=tagList(
tags$strong("Highlight one or several Foundations by Name")
),
choices = NULL, multiple = TRUE,
options = list(placeholder = "Search a foundation..."),width="100%") )
),
div(style = "margin-left: 20px; margin-right:20px; margin-top: 80px; margin-bottom: 20px; font-size: 12px; color: #555; text-align: left;",
HTML(
"Use the search bar to find and highlight foundations in all networks at the same time.
All foundations are displayed by default in blue, while the selected ones will be highlighted in red.<br>
Here you will discover that foundation similarity is heterogeneous, as it depends on the dimension you explore 😊 !"))
)),
fluidRow(
column(4, # First Column - 1/3 is 4, as total width is 12 elements)
box(
status = "primary",width=12,style = "background-color: rgba(128, 128, 128, 0.1); border: 0.5px solid #000000;",
plotlyOutput("p_members", height = "60vh"),
p(HTML(paste("Network arranging foundations by the number of board members in common.<br>
Dot size indicates the number of board members each foundation has.<br>
Foundations in the periphery have less board members in common, those overlapping have connected boards")),
style="margin-left:10px; margin-right:10px; margin-top:10px; margin-bottom:10px; font-size:12px; text-align:center;")
)),
column(4, # Second column
box(
status = "primary",width=12,style = "background-color: rgba(128, 128, 128, 0.1); border: 0.5px solid #000000;",
plotlyOutput("p_purposes", height = "60vh"),
p(HTML(paste("Network arranging foundations by the similarity in their mission statements.<br>
Transformer-based instructed retrieval ensures that purpose similarity is captured.
Foundations in the periphery have more specific purposes, those in the center broader purposes.")),
style="margin-left:10px; margin-right:10px; margin-top:10px; margin-bottom:10px; font-size:12px; text-align:center;")
)),
column(4, # Third Column
box(
status = "primary",width=12,style = "background-color: rgba(128, 128, 128, 0.1); border: 0.5px solid #000000;",
plotlyOutput("p_geo", height = "60vh"),
p(HTML(paste("Network arranging foundations by their geographical proximity, in kilometers.<br>It resembles a map.<br><br><br>")),
style="margin-left:10px; margin-right:10px; margin-top:10px; margin-bottom:10px; font-size:12px; text-align:center;")
)),
),
fluidRow(column(12,
div(style= "margin-left: 20px; margin-right:20px; margin-top:20px; margin-bottom:50px; font-size: 12px; color: #555; text-align: center;",
"Data compiled for ",
tags$em("PIO Winter Meeting 2025"),
" by Lucia Gomez Teijeiro (1), Nisa Thomas (2) and Jack O'Neill (3) (University of Geneva (1,2) / BFH (1) and Philea (3) respectively, 2025)",
tags$br(),
"Methodology derived from the Mapping Philanthropy research project. Open Source and available at",
tags$a(href="https://github.com/gomez-L/mapping_philanthropy","https://github.com/gomez-L/mapping_philanthropy",target="https://github.com/gomez-L/mapping_philanthropy")
)))
),
)
# --- # --- # --- # --- # --- # --- # --- # --- # --- # --- # --- # --- # --- # --- #
server <- function(input, output, session) {
#bs_themer() # uncomment this to have a theme selector in the navbar, do the same in ui side with theme = bs_theme(version = 4),
# INTERACTIVE MAP
# --- # --- # --- # --- # --- # --- # --- # --- # --- # --- # --- # --- # --- # --- #
# DEFINE SELECTIZEINPUT FOR MAP
# Load foundation names dynamically
observe({ updateSelectizeInput(session, "name_clean", choices = unique(geo$name_clean), server = TRUE) })
# OBSERVE MAP
# Render initial Leaflet Map with all points in blue
output$map <- renderLeaflet({ leaflet(geo) %>% addTiles() %>%
addCircleMarkers(lng =~lon,lat =~lat,label=~name_clean,color ="blue",layerId =~name_clean,radius=3,stroke=FALSE,fillOpacity=1,
popup =~popup_content)
})
# Observe selection and update marker colors
observeEvent(input$name_clean, {
leafletProxy("map") %>%
clearMarkers() %>%
addCircleMarkers(data = geo, lng = ~lon, lat = ~lat,
label = ~name_clean,color =~ifelse(name_clean %in% input$name_clean,"red","blue"),layerId = ~name_clean, # color changes with selection
radius=~ifelse(name_clean %in% input$name_clean,10,3), # size changes with selection
stroke=FALSE,fillOpacity=1,popup =~popup_content )
})
# --- # --- # --- # --- # --- # --- # --- # --- # --- # --- # --- # --- # --- # --- #
# NETWORK EXPLORER
# --- # --- # --- # --- # --- # --- # --- # --- # --- # --- # --- # --- # --- # --- #
# DEFINE SELECTIZEINPUT FOR NETWORK EXPLORER
observe({ updateSelectizeInput(session, "label_plotly", choices = unique(c(geo$name_clean,p_members_data$label,p_purposes_data$label)), server = TRUE) })
output$p_members <- renderPlotly({
plot_ly(p_members_data, x = ~x, y = ~y) %>%
add_markers(color = I("blue"),alpha = 0.8,marker = list(size = ~n_members),text=~label,hoverinfo="text") %>%
filter(label %in% input$label_plotly) %>%
add_markers(color = I("red"),alpha = 0.8,marker = list(size=20),text=~label,hoverinfo="text") %>%
toWebGL() %>% layout(showlegend = FALSE,title=list(text="Shared Board Members",font=list(size=20),y=0.9,yanchor="top"),
xaxis = list(visible = FALSE,scaleanchor = "y"),yaxis = list(visible = FALSE),
margin = list(t = 100, r = 0, b = 0, l = 0),hoverlabel = list(bgcolor = "white") )
})
output$p_purposes <- renderPlotly({
plot_ly(p_purposes_data, x = ~x, y = ~y) %>%
add_markers(color = I("blue"), alpha = 0.5,text=~label,hoverinfo="text") %>%
filter(label %in% input$label_plotly) %>%
# WE COULD HAVE DETERMINED THE SIZE!! IT COULD HAVE BEEN THE PURPOSE HETEROGENEITY
add_markers(color = I("red"),alpha = 0.8,marker=list(size=20),text=~label,hoverinfo="text") %>%
toWebGL() %>% layout(showlegend = FALSE,title=list(text="Purpose Similarity",font=list(size=20),y=0.9,yanchor="top"),
xaxis = list(visible = FALSE,scaleanchor = "y"),yaxis = list(visible = FALSE),
margin = list(t = 100, r = 0, b = 0, l = 0),hoverlabel = list(bgcolor = "white") )
})
output$p_geo <- renderPlotly({
plot_ly(p_geo_data, x = ~(-y), y = ~x) %>%
add_markers(color = I("blue"), alpha = 0.5,text=~label,hoverinfo="text") %>%
filter(label %in% input$label_plotly) %>%
# WE COULD HAVE DETERMINED THE SIZE!! IT COULD HAVE BEEN THE CLOSENESS TO A CITY CENTER
add_markers(color = I("red"),alpha = 0.8,marker=list(size=20),text=~label,hoverinfo="text") %>%
toWebGL() %>% layout(showlegend = FALSE,title=list(text="Geographic Proximity",font=list(size=20),y=0.9,yanchor="top"),
xaxis = list(visible = FALSE,scaleanchor = "y"),yaxis = list(visible = FALSE),
margin = list(t = 100, r = 0, b = 0, l = 0),hoverlabel = list(bgcolor = "white") )
})
# --- # --- # --- # --- # --- # --- # --- # --- # --- # --- # --- # --- # --- # --- #
}
shinyApp(ui, server)