Skip to content

Instantly share code, notes, and snippets.

@JosiahParry
Created April 1, 2025 17:13
Show Gist options
  • Save JosiahParry/bb24ac77a566477b47c97af40e3dd32d to your computer and use it in GitHub Desktop.
Save JosiahParry/bb24ac77a566477b47c97af40e3dd32d to your computer and use it in GitHub Desktop.
library(shiny)
library(calcite)
library(htmltools)
library(arcgisutils)
# read in our sample dataset
earthquakes <- sf::st_read(
"https://github.com/R-ArcGIS/calcite/raw/refs/heads/main/dev/earthquakes.fgb"
)
# define our pagination size
page_size <- 12
# define some CSS styles to help make our card container pretty
container_css <- css(
margin = "0.75rem",
display = "grid",
"grid-template-columns" = "repeat(auto-fit, minmax(300px, 1fr))",
"grid-gap" = "1rem",
"justify-content" = "space-evenly"
)
# Helper function to create a card for each filter result
# This takes a list item and creates a `calcite_card()`
make_card <- function(.x) {
# extract the object ID
.oid <- .x$attributes$OBJECTID
# parse the date
.date <- from_esri_date(.x$attributes$eventTime)
# create a pretty formatted date
.date_fmt <- format(.date, "%a %b %d %Y %H:%M:%S %Z")
# extract longitude, latidue, and earthquake magnitude
.lng <- .x$attributes$longitude
.lat <- .x$attributes$latitude
.mag <- .x$attributes$mag
# create a url to open to that displays the loctation of the earthquake
mapviewer_link <- paste0(
"https://www.arcgis.com/apps/mapviewer/index.html?marker=", .lng, ";", .lat,
";4326;titleName;;Magnitude:", .mag,
"&center=", .lng, ";", .lat, "&level=6"
)
# create the card now
calcite_card(
# we set the id to be `card-objectid`
id = sprintf("card-%s", .oid),
# fill the card's title slot with the place name in bold
span(slot = "title", tags$b(.x$attributes$place)),
# fill the card's subtitle slot using the pretty formatted date
span(slot = "subtitle", sprintf("Occurred on: %s", .date_fmt)),
# create a nice chip to display the magnitude
calcite_chip(
appearance = "outline-fill",
scale = "s",
kind = "inverse",
icon = "graph-time-series",
sprintf("Magnitude: %s", round(as.numeric(.x$attributes$mag), 2)),
style = css("color" = "var(--calcite-color-status-danger)")
),
# this button opens to the map viewer
calcite_button(
label = "Open in map",
"icon-end" = "launch",
slot = "footer-end",
target = "_blank",
width = "full",
href = mapviewer_link,
"Open in map"
)
)
}
# Helper function to update the cards displayed after filtering
# we use the `start_idx` to subset our items. this is found from our
# calcite_pagination() component
update_cards <- function(items, output, session, start_idx = 1) {
# calculate how many items we have
total <- length(items)
# if we have 0 items we want to clear the container and change the message
if (total == 0) {
# set the `note` properties to danger
update_calcite("note", kind = "danger", icon = "exclamation-mark-triangle", open = TRUE)
# change the number of records container
output$number_records <- renderUI(div("Sorry, 0 records found."))
# Clear cards
# render nothing for the cards
output$cards <- renderUI(div(
class = "card-container",
style = container_css
))
} else {
# when we have >1 item we open the note and show how many items were found
update_calcite("note", kind = "brand", icon = "information", open = TRUE)
output$number_records <- renderUI(div(sprintf("%i records found.", total)))
# here we calculate the end index of the cards
end_idx <- min(start_idx + page_size - 1, total)
# now we create our cards from the subset and render the HTML
output$cards <- renderUI(
div(
tagList(
!!!lapply(
items[start_idx:end_idx],
make_card
)
),
class = "card-container",
style = container_css
)
)
# we close the initial note here
update_calcite("initial-note", open = FALSE)
# Update pagination details
update_calcite("pagination", totalItems = total, startItem = start_idx)
}
}
# define the UI
ui <- calcite_shell(
# Heading for the app
calcite_panel(
heading = "Earthquake results",
"heading-level" = 1,
description = "Search by location to display results",
# filter provides search filtering
calcite_filter(id = "filter_id", placeholder = "Try searching Alaska", style = "l"),
# the initial notice to prompt userrs
calcite_notice(
id = "initial-note",
icon = "information",
open = TRUE,
div(slot = "title", "Try searching a place of interest"),
div(slot = "message", "Results will display when text is entered."),
style = css(position = "relative", "margin" = "0.75rem")
),
# the notice to show while searching
calcite_notice(
id = "note",
htmlOutput("number_records", container = tags$div, slot = "title"),
style = css(position = "relative", "margin" = "0.75rem")
),
# this is where the cards go after we've filtered
htmlOutput("cards"),
# bottom pagination for search results
calcite_pagination(id = "pagination", slot = "footer", "page-size" = page_size)
)
)
server <- function(input, output, session) {
# set the filter to have earthquakes as the items
# we don't need the geometry for the UI so we drop it to be faster
# use as_features to convert the dataframe into an array of elements for
# javascript. Otherwise each column would be treated as an item
quakes <- as_features(sf::st_drop_geometry(earthquakes))
# set the items initially
update_calcite("filter_id", items = quakes)
# Observe filter value changes
# this is ran whenever the search values change
observeEvent(input$filter_id_value, {
# the search value
filter_value <- input$filter_id_value
# the array of items returned from the filtering
filtered_items <- input$filter_id_filteredItems$values
if (!is.null(filter_value) && nzchar(filter_value)) {
# Render the cards or show "0 records found"
update_cards(filtered_items, output, session)
} else {
# here we open the initial note and clear the cards
update_calcite("initial-note", open = TRUE)
update_calcite("note", open = FALSE)
output$cards <- renderUI(div(class = "card-container"))
}
})
# when a user changes the page, we subset the filtered items
# and render the cards
observeEvent(input$pagination_startItem, {
filtered_items <- input$filter_id_filteredItems$values
if (!is.null(filtered_items)) {
start_idx <- input$pagination_startItem$values
update_cards(filtered_items, output, session, start_idx)
}
})
}
shinyApp(ui, server)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment