diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..991c663 Binary files /dev/null and b/.DS_Store differ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3a31c70 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.Rproj.user +.Rhistory +.RData +.Ruserdata +.rds +mapboxtoken_setup.R \ No newline at end of file diff --git a/Images/.DS_Store b/Images/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/Images/.DS_Store differ diff --git a/Images/green-job-mapping-logo-small.png b/Images/green-job-mapping-logo-small.png new file mode 100644 index 0000000..5ad1a5e Binary files /dev/null and b/Images/green-job-mapping-logo-small.png differ diff --git a/Images/green-job-mapping-logo.png b/Images/green-job-mapping-logo.png new file mode 100644 index 0000000..c1f88b8 Binary files /dev/null and b/Images/green-job-mapping-logo.png differ diff --git a/README.md b/README.md index 8ca0026..f35f327 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ -README +README: CCRC Green Job Mapping ================ +![Green Job Mapping logo](images/green-job-mapping-logo-small.png) + ## Overview docs for Mapbox GL JS : @@ -8,6 +10,10 @@ docs for Mapbox GL JS : This app built for Hover function for demand side, we will use the US county population to have a try! + + +--- + ## Data used for the app data folder, private: @@ -15,11 +21,19 @@ data folder, private: Downlaod the data folder,rename it as raw-data. + + +--- + ## Raw data used to create the data used for the app `pre-county-population.R` is used to create fake “demand” side data, which is US county level population data from tidycensus package. + + +--- + ## Wei update the app on Feb 18th. UI_app.R and www/mapbox-script.JS are main files for the app. @@ -28,4 +42,9 @@ APP website:https://ed-analytics.shinyapps.io/CCRC_Mapping_JS/ To run this app locally, set up the map token as a source file. -The hover UI: ![Hover UI](Images/Hover.png) + +--- + +## The hover UI + +![Hover UI](images/hover.png) diff --git a/UI_app.R b/UI_app.R deleted file mode 100644 index 64e5fdc..0000000 --- a/UI_app.R +++ /dev/null @@ -1,140 +0,0 @@ -library(shiny) -library(jsonlite) -library(geojsonio) -library(dplyr) -source("mapboxtoken_setup.R") - -# Load data -hdallyears <- readRDS("hdallyears.rds") -ipeds_green_summed <- readRDS("ipeds_green_summed.rds") -counties_sf <- readRDS("counties_sf_processed.rds") - -# Ensure GeoJSON data has unique IDs for feature-state -counties_sf <- counties_sf %>% - mutate(id = row_number()) # Assign unique IDs to each county - -# Merge data -hdallyears_joined <- hdallyears %>% - left_join(ipeds_green_summed, by = "unitid") - -# UI -ui <- fluidPage( - titlePanel("CCRC Green Job Seek Mapping"), - - # The entire page is divided into left and right columns: - fluidRow( - # Left: supply category selector, 3/12 of width - column(3, - wellPanel( - selectInput("selected_green_category", - "Select Supply Category:", - choices = c("Green New & Emerging", - "Green Enhanced Skills", - "Green Increased Demand")) - ) - ), - # Right side: search controls above, map below, 9/12ths of width - column(9, - wellPanel( - fluidRow( - column(8, - textInput("search_term", "Search by Institution:", - placeholder = "Type institution here...", width = "100%") - ), - column(4, - div(style = "margin-top: 25px;", - actionButton("search_btn", "Search"), - # The clearMap() function on the front-end using HTML button binding - tags$button("Clear", onclick = "clearMap()", - style = "margin-left: 10px;", class = "btn btn-default") - ) - ) - ) - ), - # Map area - div(id = "map", style = "height: 700px;") - ) - ), - - # footers - fluidRow( - column( - 12, align = "center", - tags$footer( - style = "margin-top: 20px; padding: 10px; font-size: 12px; background-color: #f8f9fa; border-top: 1px solid #e9ecef;", - HTML("Created by Wei Wang, Joshua Rosenberg, Cameron Sublet and Bret Staudt Willet with the Community College Research Center at Teachers College, Columbia. - Source code at: GitHub. - Thanks to funding from JC Morgan Chase.") - ) - ) - ), - - # Introducing Mapbox GL JS Resources and Custom JS Files - tags$head( - tags$link(href = "https://api.mapbox.com/mapbox-gl-js/v2.14.1/mapbox-gl.css", rel = "stylesheet"), - tags$script(src = "https://api.mapbox.com/mapbox-gl-js/v2.14.1/mapbox-gl.js"), - tags$script(src = "mapbox-script.js"), - tags$script(HTML(paste0("const mapboxToken = '", mapbox_token, "';"))) - ) -) - - - -# Server -server <- function(input, output, session) { - # Save counties_sf data to reactiveValues - map_data <- reactiveValues( - county_data = counties_sf - ) - - # Convert counties_sf to GeoJSON and send to front end - observe({ - if (!inherits(map_data$county_data, "sf")) { - stop("map_data$county_data must be an sf object") - } - - counties_geojson <- geojsonio::geojson_json(map_data$county_data) - - # Debug: output partial GeoJSON data - print("Sending GeoJSON data to frontend") - print(substr(counties_geojson, 1, 500)) - - session$sendCustomMessage(type = "updateCounties", counties_geojson) - }) - - # Search button: find records based on the entered school name and selected supply category - observeEvent(input$search_btn, { - req(input$search_term) - req(input$selected_green_category) - - # Filter data by school name (instnm) and supply category (greencat) - search_result <- hdallyears_joined %>% - filter(grepl(input$search_term, instnm, ignore.case = TRUE), - greencat == input$selected_green_category) %>% - head(1) - - if (nrow(search_result) > 0) { - # Constructing the HTML content of the popup window: displaying the name of the school, the selected category, and the size value - popup_text <- paste0( - "", search_result$instnm, "
", - "Category: ", input$selected_green_category, "
", - "Size: ", search_result$size - ) - coords <- list( - lng = search_result$longitud, - lat = search_result$latitude, - popup = popup_text - ) - # Send search results to the front end - session$sendCustomMessage(type = "updateSearch", coords) - } else { - showNotification("No Institution Found!", type = "error") - } - }) - - - # Note: The Clear button calls the front-end clearMap() function directly in the UI. - # So there's no need for an additional observeEvent to handle the Clear button here. -} - -shinyApp(ui, server) diff --git a/app.R b/app.R deleted file mode 100644 index 5c384e5..0000000 --- a/app.R +++ /dev/null @@ -1,132 +0,0 @@ -library(shiny) -library(jsonlite) -library(geojsonio) -library(dplyr) - -# Load data -hdallyears <- readRDS("hdallyears.rds") -ipeds_green_summed <- readRDS("ipeds_green_summed.rds") -counties_sf <- readRDS("counties_sf_processed.rds") - -# Ensure GeoJSON data has unique IDs for feature-state -counties_sf <- counties_sf %>% - mutate(id = row_number()) # Assign unique IDs to each county - -# Merge data -hdallyears_joined <- hdallyears %>% - left_join(ipeds_green_summed, by = "unitid") - -# UI -ui <- fluidPage( - titlePanel("CCRC Mapping with Mapbox GL JS"), - - fluidRow( - column( - width = 10, - div( - style = "display: flex; align-items: center;", - textInput("search_term", "Search by Institution:", - placeholder = "Type institution here...", width = "100%"), - actionButton("search_btn", "Search", style = "margin-left: 10px;"), - # Use HTML buttons and bind the clearMap() function directly to the frontend. - tags$button("Clear", onclick = "clearMap()", style = "margin-left: 10px;", class = "btn btn-default") - ) - ) - ), - - fluidRow( - column(6, align = "center", - selectInput("selected_green_category", - "Select Supply Category:", - choices = c("Green New & Emerging", "Green Enhanced Skills", "Green Increased Demand")) - ) - ), - - - - # Map output with JavaScript integration - fluidRow( - column(12, tags$div(id = "map", style = "height: 700px;")) - ), - - # Include Mapbox GL JS resources and our custom JS file - tags$head( - tags$link(href = "https://api.mapbox.com/mapbox-gl-js/v2.14.1/mapbox-gl.css", rel = "stylesheet"), - tags$script(src = "https://api.mapbox.com/mapbox-gl-js/v2.14.1/mapbox-gl.js"), - tags$script(src = "mapbox-script.js"), - tags$script(HTML(paste0("const mapboxToken = '", Sys.getenv("MAPBOX_TOKEN"), "';"))) - ), - - fluidRow( - column( - width = 12, align = "center", - tags$footer( - style = "margin-top: 20px; padding: 10px; font-size: 12px; background-color: #f8f9fa; border-top: 1px solid #e9ecef;", - HTML("Created by Wei Wang, Joshua Rosenberg, and Cameron Sublet at the University of Tennessee, Knoxville with the Community College Research Center at Teachers College, Columbia. - Source code at: GitHub. - Thanks to funding from JC Morgan Chase.") - ) - ) - ) - - -) - - - -# Server -server <- function(input, output, session) { - # Save counties_sf data to reactiveValues - map_data <- reactiveValues( - county_data = counties_sf - ) - - # Convert counties_sf to GeoJSON and send to front end - observe({ - if (!inherits(map_data$county_data, "sf")) { - stop("map_data$county_data must be an sf object") - } - - counties_geojson <- geojsonio::geojson_json(map_data$county_data) - - # Debug: output partial GeoJSON data - print("Sending GeoJSON data to frontend") - print(substr(counties_geojson, 1, 500)) - - session$sendCustomMessage(type = "updateCounties", counties_geojson) - }) - - # Search button: find records based on the entered school name and selected supply category - observeEvent(input$search_btn, { - req(input$search_term) - req(input$selected_green_category) - - # Filter data by school name (instnm) and supply category (greencat) - search_result <- hdallyears_joined %>% - filter(grepl(input$search_term, instnm, ignore.case = TRUE), - greencat == input$selected_green_category) %>% - head(1) - - if (nrow(search_result) > 0) { - # Constructing the HTML content of the popup window: displaying the name of the school, the selected category, and the size value - popup_text <- paste0( - "", search_result$instnm, "
", - "Category: ", input$selected_green_category, "
", - "Size: ", search_result$size - ) - coords <- list( - lng = search_result$longitud, - lat = search_result$latitude, - popup = popup_text - ) - # Send search results to the front end - session$sendCustomMessage(type = "updateSearch", coords) - } else { - showNotification("No Institution Found!", type = "error") - } - }) - # Note: The Clear button calls the front-end clearMap() function directly in the UI. - # So there's no need for an additional observeEvent to handle the Clear button here. -} - -shinyApp(ui, server) diff --git a/data/counties_sf_processed.rds b/data/counties_sf_processed.rds new file mode 100644 index 0000000..075c847 Binary files /dev/null and b/data/counties_sf_processed.rds differ diff --git a/data/hdallyears.rds b/data/hdallyears.rds new file mode 100644 index 0000000..0e0e2ab Binary files /dev/null and b/data/hdallyears.rds differ diff --git a/data/ipeds_green_summed.rds b/data/ipeds_green_summed.rds new file mode 100644 index 0000000..a97dacd Binary files /dev/null and b/data/ipeds_green_summed.rds differ diff --git a/pre-county-population.R b/pre-county-population.R index e9c1615..f320e28 100644 --- a/pre-county-population.R +++ b/pre-county-population.R @@ -7,12 +7,13 @@ library(tidyverse) library(haven) library(tidycensus) -county_population <- get_acs( - geography = "county", - variables = "B01003_001E", # Total population variable - year = 2021, - survey = "acs5" # 5-year data -) +county_population <- + get_acs( + geography = "county", + variables = "B01003_001E", # Total population variable + year = 2021, + survey = "acs5" # 5-year data + ) head(county_population) @@ -46,7 +47,7 @@ counties_sf_size2 <- st_transform(counties_sf_size1, crs = 4326) #counties_sf_size2 <- counties_sf_size2 %>% #as_mapbox_source() -counties_sf_size2 %>% write_rds("counties_sf_processed.rds") +counties_sf_size2 %>% write_rds("data/counties_sf_processed.rds") @@ -62,7 +63,7 @@ ipeds_green_summed <- ipeds_green %>% ipeds_green_summed <- ipeds_green_summed %>% pivot_longer(-unitid, names_to = "greencat", values_to = "size") -write_rds(ipeds_green_summed, "ipeds_green_summed.rds") +write_rds(ipeds_green_summed, "data/ipeds_green_summed.rds") @@ -71,4 +72,4 @@ hdallyears <- read_dta("raw-data/hdallyears.dta") hdallyears <- hdallyears %>% filter(year == 2020) -write_rds(hdallyears, "hdallyears.rds") \ No newline at end of file +write_rds(hdallyears, "data/hdallyears.rds") \ No newline at end of file diff --git a/server.R b/server.R new file mode 100644 index 0000000..0063fd2 --- /dev/null +++ b/server.R @@ -0,0 +1,83 @@ +library(dplyr) +library(geojsonio) +library(jsonlite) + + + +################################################################################ + +# Load data +hdallyears <- readRDS("data/hdallyears.rds") +ipeds_green_summed <- readRDS("data/ipeds_green_summed.rds") +counties_sf <- readRDS("data/counties_sf_processed.rds") + +# Ensure GeoJSON data has unique IDs for feature-state +counties_sf <- + counties_sf %>% + mutate(id = row_number()) # Assign unique IDs to each county + +# Merge data +hdallyears_joined <- + hdallyears %>% + left_join(ipeds_green_summed, by = "unitid") + + + +################################################################################ + +function(input, output, session) { + # Save counties_sf data to reactiveValues + map_data <- + reactiveValues( + county_data = counties_sf + ) + + # Convert counties_sf to GeoJSON and send to front end + observe({ + if (!inherits(map_data$county_data, "sf")) { + stop("map_data$county_data must be an sf object") + } + + counties_geojson <- geojsonio::geojson_json(map_data$county_data) + + # Debug: output partial GeoJSON data + print("Sending GeoJSON data to frontend") + print(substr(counties_geojson, 1, 500)) + + session$sendCustomMessage(type = "updateCounties", counties_geojson) + }) + + # Search button: find records based on the entered school name and selected supply category + observeEvent(input$search_btn, { + req(input$search_term) + req(input$selected_green_category) + + # Filter data by school name (instnm) and supply category (greencat) + search_result <- hdallyears_joined %>% + filter(grepl(input$search_term, instnm, ignore.case = TRUE), + greencat == input$selected_green_category) %>% + head(1) + + if (nrow(search_result) > 0) { + # Constructing the HTML content of the popup window: displaying the name of the school, the selected category, and the size value + popup_text <- paste0( + "", search_result$instnm, "
", + "Category: ", input$selected_green_category, "
", + "Size: ", search_result$size + ) + coords <- list( + lng = search_result$longitud, + lat = search_result$latitude, + popup = popup_text + ) + # Send search results to the front end + session$sendCustomMessage(type = "updateSearch", coords) + } else { + showNotification("No Institution Found!", type = "error") + } + }) + + + # Note: The Clear button calls the front-end clearMap() function directly in the UI. + # So there's no need for an additional observeEvent to handle the Clear button here. +} \ No newline at end of file diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..0d7a367 --- /dev/null +++ b/styles.css @@ -0,0 +1,125 @@ +/* import Google fonts */ +@import url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100..900;1,100..900&display=swap'); + +@import url('https://fonts.googleapis.com/css2?family=Exo:ital,wght@0,100..900;1,100..900&display=swap'); + + +input[type="number"] { + max-width: 80%; +} + + + +/* Customize the navbar background and font */ +.navbar { + background-color: #eeeae8; /* Change to your desired background color */ +} + +.navbar-brand { + font-family: 'Exo', sans-serif; /* Change to your desired font family */ + color: #424c49; /* Change the font color */ + font-size: 28px; /* Change the font size */ +} + +.navbar-nav > li > a.active, +.navbar-nav > li.active > a, +.navbar-nav > li > a:hover, +.navbar-nav > li > a:focus { + background-color: #99ea85 !important; /* Change the hover and focus color */ + font-weight: 600 !important; +} + +/* Ensures the brand text and image align properly */ +.navbar-header .navbar-brand { + display: flex; + align-items: center; +} + +.navbar-header .navbar-brand img { + margin-right: 6px; /* Space between the logo and the text */ +} + + + + +div.outer { + position: fixed; + top: 42px; + left: 0; + right: 0; + bottom: 0; + overflow: hidden; + padding: 0; +} + + + +/* Customize fonts */ +body, label, input, button, select { + font-family: 'Roboto', sans-serif; + font-weight: 300; + font-size: 14px; + color: #424c49; +} + +title, h1, h2 { + font-family: 'Exo', serif; + font-weight: 600; +} + +h3, h4 { + font-family: 'Exo', serif; + font-weight: 400; +} + + + +/* change hyperlink text */ +a { + color: #0645ad; /* standard blue hyperlink color */ + font-weight: 400; +} + +/* change hyperlink hover text */ +a:hover { + color: #e04300; + text-decoration: underline; +} + + + +#controls { + /* Appearance */ + background-color: #99ea85; + border: 1px solid #beb0a7; + padding: 0 20px 20px 20px; + cursor: move; + /* Fade out while not hovering */ + opacity: 0.7; + zoom: 0.8; + transition: opacity 200ms 600ms; +} + +#controls:hover { + /* Fade in while hovering */ + opacity: 0.95; + transition-delay: 0; +} + +/* Position and style footer */ +.footer { + font-size: 14px; + background-color: #eeeae8; + padding: 0px 20px; + width: 100%; + text-align: center; + position: fixed; + bottom: 0; + left: 0; + border-top: 1px solid #beb0a7; +} + +/* If not using map tiles, show a white background */ +.mapbox-container { + background-color: white !important; +} diff --git a/ui.R b/ui.R new file mode 100644 index 0000000..dba8ed4 --- /dev/null +++ b/ui.R @@ -0,0 +1,136 @@ +source("mapboxtoken_setup.R") + + + +################################################################################ +## Navigation bar at top of window +################################################################################ + +navbarPage( + + tags$head( + includeCSS("styles.css") + ), + + title = + div( + img(src = "green-job-mapping-logo-small.png", + height = "28px"), + "CCRC Green Jobs Mapping" + ), + id="nav", + + +################################################################################ +## First tab: Window for interactive map +################################################################################ + + tabPanel("Interactive Map", + div(class="outer", + div(id = "map", style = "height: 100%;"), + + + +################################################################################ +## Floating panel for input controls +################################################################################ + + absolutePanel(id = "controls", + class = "panel panel-default", + fixed = TRUE, + draggable = TRUE, + top = 60, left = "auto", right = 20, bottom = "auto", + width = 360, height = "auto", + + h2("Green Jobs Explorer"), + + selectInput("selected_green_category", + "Select Supply Category:", + choices = c("Green New & Emerging", + "Green Enhanced Skills", + "Green Increased Demand") + ), + textInput("search_term", "Search by Institution:", + placeholder = "Type institution here...", + width = "100%" + ), + actionButton("search_btn", "Search"), + tags$button("Clear", onclick = "clearMap()", + style = "margin-left: 10px;", class = "btn btn-default" + ) + ), + ) + ), + + + + + + +################################################################################ +## Second Tab +################################################################################ + + tabPanel("Background", + img(src = "green-job-mapping-logo-small.png", + height = "60px"), + h2("About the CCRC Green Jobs Mapping App"), + fluidRow( + column(3, + selectInput("states", "States", c("All states"="", structure(state.abb, names=state.name), "Washington, DC"="DC"), multiple=TRUE) + ), + column(3, + conditionalPanel("input.states", + selectInput("cities", "Cities", c("All cities"=""), multiple=TRUE) + ) + ), + column(3, + conditionalPanel("input.states", + selectInput("zipcodes", "Zipcodes", c("All zipcodes"=""), multiple=TRUE) + ) + ) + ), + fluidRow( + column(1, + numericInput("minScore", "Min score", min=0, max=100, value=0) + ), + column(1, + numericInput("maxScore", "Max score", min=0, max=100, value=100) + ) + ) + ), + + + +################################################################################ +## Citation at bottom of window +################################################################################ + + tags$footer( + class = "footer", + tags$p("Created by Wei Wang, Joshua Rosenberg, Cameron Sublet, and Bret Staudt Willet,", + "with the", + tags$a(href="https://ccrc.tc.columbia.edu/", "Community College Research Center"), + "at Teachers College, Columbia."), + tags$p("Source code on", + tags$a(href="https://github.com/wwang93/CCRC_Mapping_JS", "GitHub."), + "Thanks to funding from JC Morgan Chase." + ) + ), + + + + +################################################################################ +## Introducing Mapbox GL JS Resources and Custom JS Files +################################################################################ + + tags$head( + tags$link(href = "https://api.mapbox.com/mapbox-gl-js/v2.14.1/mapbox-gl.css", rel = "stylesheet"), + tags$script(src = "https://api.mapbox.com/mapbox-gl-js/v2.14.1/mapbox-gl.js"), + tags$script(src = "mapbox-script.js"), + tags$script(HTML(paste0("const mapboxToken = '", mapbox_token, "';"))) + ) + + +) \ No newline at end of file diff --git a/www/green-job-mapping-logo-small.png b/www/green-job-mapping-logo-small.png new file mode 100644 index 0000000..5ad1a5e Binary files /dev/null and b/www/green-job-mapping-logo-small.png differ