Data

Column

Filters

Column

Map

Reactable (Columns can be resized.)

Info

Overview

  • This flexdashboard, was designed to practice web scraping, geocoding, and interactive mapping. It was developed by Toyin L. Ola.

  • The data were scraped from a March 28, 2022 Richmond Magazine article written by Eileen Mellon.

Resources

  • This project was inspired by Silvia Canelón’s excellent R-Ladies Philly workshop on webscraping, geocoding, and interactive mapping. See all materials, including presentation slides and a RStudio Cloud project, in this GitHub repository. The workshop recording is available on YouTube.

  • The rvest chapter of the companion ebook for Stanford’s Data Challenge Lab provided invaluable tips for easily determining the desired CCS selector(s) to scrape the webpage of interest.

  • Shannon Pigelli’s June 2022 tutorial on purrr on her website Piping Hot Data greatly assisted in wrangling the scraped bakery data.

  • Matt Dray has awesome examples of flexdashboard development using the crosstalk package in this GH repo.

  • Meghan Harris (The Tidy Trekker) has a terrific tutorial on customizing and styling flexdashboards.

---
title: "Richmond, Virginia Metropolitan Area Bakeries"
date: "October 2022"
output:
  flexdashboard::flex_dashboard:
    source_code: embed
---

```{r setup, include=FALSE}

knitr::opts_chunk$set(echo = FALSE)

source("all_custom_functions.R")

usingPackages("here", "tidyverse", "robotstxt", "crosstalk", "tidygeocoder", "leaflet", "leaflet.extras", "reactable", "flexdashboard")

library(here)
library(tidyverse)
library(crosstalk)
library(tidygeocoder)
library(leaflet)
library(leaflet.extras)
library(reactable)
library(flexdashboard)

```

```{r scrape-web, include = FALSE, eval = FALSE}

# Change chunk option to eval = TRUE in order to source a R script to scrape the web for the bakeries' data in lieu of loading # data that has already been scraped.

source("bakery_web_scraping.R")

```

```{r clean-data, include = FALSE, eval = FALSE}

# Change chunk option to eval = TRUE in order to source a R script to clean data instead of loading data that has already been
# cleaned.

source("bakery_data_cleaning.R")

```

```{r geocode-data, include = FALSE, eval = FALSE}

# Change chunk option to eval = TRUE in order to source script to geocode data instead of loading data in next chunk.

source("bakery_data_geocoding.R")

```

```{r load-clean-and-geocoded-data, include = FALSE}

here()

path <- here()

bakery_table <- read.csv( paste0(path,"/output/geocoded_bakery_table.csv") )

bakery_table <- bakery_table  %>% select( -c("X") ) %>%  # remove "X" column from reading in csv
  rename( `Bakery Name` = Bakery.Name,                   # fix column names from reading in csv
          `Gluten-Free` = Gluten.Free, 
          `Appointment Only` = Appointment.Only,
          `Dairy-Free` = Dairy.Free, 
          `Multiple Locations` = Multiple.Locations,
          `Farmer's Market` = Farmer.s.Market,
  )

```

```{r shared-data, include = FALSE}

# Add generic lat, long since leaflet cannot handle NA and SharedData cannot be filtered

bakery_table$Latitude <- ifelse(is.na(bakery_table$Latitude), "37.541290", bakery_table$Latitude )

bakery_table$Longitude <- ifelse(is.na(bakery_table$Longitude), "-77.434769", bakery_table$Longitude )


# Ensure lat and long are numeric

bakery_table$Latitude <- as.numeric(bakery_table$Latitude)

bakery_table$Longitude <- as.numeric(bakery_table$Longitude)


# Make a crosstalk shared dataset

shared <- bakery_table %>% crosstalk::SharedData$new()

```

```{css, include = FALSE}

@import url('https://fonts.googleapis.com/css2?family=Karla:ital,wght@0,300;0,400;1,300;1,400&display=swap');

```


**Data**
=====================================  


Column {data-width=175}
-------------------------------------

### Filters

```{r filters}


  filter_checkbox(
    id = "appt-only",
    label = "Appointment Only",
    sharedData = shared,
    group = ~`Appointment Only`,
    inline = TRUE                # display options horizontally instead of vertically
  )

  filter_checkbox(
    id = "dairy-free",
    label = "Dairy-Free",
    sharedData = shared,
    group = ~`Dairy-Free`,
    inline = TRUE
  )

  filter_checkbox(
    id = "delivery",
    label = "Delivery",
    sharedData = shared,
    group = ~Delivery,
    inline = TRUE
  )

  filter_checkbox(
    id = "market",
    label = "Farmer's Market",
    sharedData = shared,
    group = ~`Farmer's Market`,
    inline = TRUE
  )

  filter_checkbox(
    id = "gluten-free",
    label = "Gluten-Free",
    sharedData = shared,
    group = ~`Gluten-Free`,
    inline = TRUE
  )

  filter_checkbox(
    id = "online",
    label = "Online",
    sharedData = shared,
    group = ~Online,
    inline = TRUE
  )

  filter_checkbox(
    id = "vegan",
    label = "Vegan",
    sharedData = shared,
    group = ~Vegan,
    inline = TRUE
  )

```


Column {data-width=825}
-------------------------------------

### Map

```{r map}

# Design custom popup

popInfoCircles <- paste(
  "<h2 style='font-family: Karla, sans-serif; font-size: 1.6em; color:#43464C;'>",bakery_table$`Bakery Name`, "</h2>",
  "<p style='font-family: Karla, sans-serif; font-style: italic; font-size: 1.5em; color:#9197A6;'>",     
  bakery_table$Description, "</p>",
  "<p style='font-family: Karla, sans-serif; font-weight: normal; text-align: center; font-size: 1em; 
  color:#9197A6;'>", bakery_table$Address, "</p>"
)

## to include a hyperlink to each bakery's website, use "<h2 style='font-family: Karla, sans-serif; font-size: 1.6em; color:#43464C;'>", "<a href=", bakery_table$Hyperlink, ">", bakery_table$`Bakery Name`, "</a></h2>"


# Make leaflet map

leaflet( data = shared, options = tileOptions(minZoom = 9, maxZoom = 19) ) %>%
  addCircles( lat = ~Latitude,          # add marker
              lng = ~Longitude,
              fillColor = "#009E91",    # customize marker
              popup = popInfoCircles,   # customize popup
              label = ~`Bakery Name`,
              labelOptions = labelOptions( style = list("font-family" = "Karla, sans-serif","font-size" = "1.2em") )
            ) %>%
  addProviderTiles(providers$CartoDB.Positron) %>%    # add background map
  setView(mean(bakery_table$Longitude), 
          mean(bakery_table$Latitude), 
          zoom = 9) %>%
  leaflet.extras::addFullscreenControl()

```

### Reactable   *(Columns can be resized.)*

```{r searchable-reactable}

reactable(data = shared, searchable = TRUE, resizable = TRUE, wrap = TRUE )

```


**Info**
===================================== 

### Overview {data-height=125}

- This flexdashboard, was designed to practice web scraping, geocoding, and interactive mapping. It was developed by Toyin L. Ola.

- The data were scraped from a March 28, 2022 *Richmond Magazine* [article](https://richmondmagazine.com/restaurants-in-richmond/richmond-area-bakeries/) written by Eileen Mellon.

### Navigation Tips

Filters:

- Use the filters in the left-hand sidebar to filter the map and table. 

- There are filters for bakeries that offer baked goods that are dairy-free, gluten-free, and vegan. There are also filters for bakeries that deliver or are appointment-only. Filters for online and farmer's market bakeries without physical locations are also included. The special category filters are based on the narrative provided in *Richmond Magazine* as of March 2022, so the bakeries' offerings may have changed. Contact the bakeries for the latest information.

Map:

- Hover over a map marker to see the name of the bakery. 

- Click on a map marker to open a popup displaying the bakery name, description, and address. 

- There is an option to view the map in fullscreen.

- For bakeries without physical addresses, generic latitude and longitude for Richmond VA (37.541290, -77.434769) were used.

Reactable:

- The reactable is searchable. See the search box in the upper right-hand corner of the table.

- The reactable columns can be resized. Hover over a column header until the cursor changes, and, then, click and drag to resize.

- The reactable columns can be sorted in descending or ascending order. Click the column header of interest.

### Resources {data-height=250}

- This project was inspired by Silvia Canelón's excellent R-Ladies Philly workshop on webscraping, geocoding,
and interactive mapping. See all materials, including presentation slides and a RStudio Cloud project, in [this GitHub repository](https://github.com/spcanelon/2022-ccd-sips). The workshop recording is available on [YouTube](https://www.youtube.com/watch?v=tcfHr0oeOMw).

- The rvest [chapter](https://dcl-wrangle.stanford.edu/rvest.html) of the companion ebook for Stanford's Data Challenge Lab provided invaluable tips for easily determining the desired CCS selector(s) to scrape the webpage of interest.

- Shannon Pigelli's June 2022 [tutorial on purrr](https://www.pipinghotdata.com/talks/2022-06-08-iterating-well-with-purrr/) on her website Piping Hot Data greatly assisted in wrangling the scraped bakery data.

- Matt Dray has awesome examples of flexdashboard development using the crosstalk package in [this GH repo](https://github.com/matt-dray/earl18-crosstalk).

- Meghan Harris (The Tidy Trekker) has a terrific [tutorial](https://www.thetidytrekker.com/post/dull-dashboards) on customizing and styling flexdashboards.