Related
I want to create a map that displays traffic KPIs by date and location. The user is able to select a day of traffic with a slider, and a traffic KPI with radio buttons. The data is not showing up on the map.
I have created a reactive object that filters the data based on radio button and slider. The code to render the LeafLet map works outside the app, showing the circles for the data.
The data frame is structured as follows:
date,lat,long,pageviews,unique_visitors
01.01.2019,6.7304,-3.49,206,238
04.01.2019,7.1604,3.35,223,275
07.01.2019,52.25,-4.25,272,407
10.01.2019,46.9757,-123.8095,44,448
13.01.2019,45.4646,-98.468,98,269
16.01.2019,35.1351,-79.432,443,337
19.01.2019,39.5146,-76.173,385,21
22.01.2019,57.1704,-2.08,273,371
25.01.2019,18.2301,42.5001,115,195
28.01.2019,5.32,-4.04,7,27
31.01.2019,32.4543,-99.7384,217,136
03.02.2019,38.923,-97.2251,337,15
06.02.2019,2.7017,33.6761,201,390
09.02.2019,36.7089,-81.9713,177,201
12.02.2019,30.1204,74.29,65,82
15.02.2019,5.4667,-3.2,261,229
18.02.2019,7.1904,1.99,364,38
21.02.2019,3.9837,13.1833,131,74
24.02.2019,-22.7167,-65.7,357,198
27.02.2019,39.4228,-74.4944,297,399
02.03.2019,24.4667,54.3666,382,147
05.03.2019,34.4504,40.9186,8,373
08.03.2019,9.0833,7.5333,83,182
11.03.2019,-9.6954,-65.3597,243,444
14.03.2019,16.85,-99.916,420,29
-> It's stored under "joined" outside of the app (I'm joining two tables) and I call it at the beginning of the pipeline in the reactive object
When I select the date and metric, the output is structured as follows:
lat,long,selected_metric
lat is latitude and long is longitude
I guess the issue is how I'm calling the dataframe in renderLeaflet, as it is a reactive object I'm not sure if the ~ command works to call the columns.
# Required packages
library(shiny)
library(leaflet)
library(dplyr)
# Define UI for application that shows a map
ui <- fluidPage(
# App title
titlePanel("Metrics by location"),
# Input: select date range
sliderInput("traffic_date",
"Date:",
min = as.Date("2019-01-01","%Y-%m-%d"),
max = as.Date("2019-07-31","%Y-%m-%d"),
value=as.Date("2019-07-31"),
timeFormat="%Y-%m-%d"),
# Input: select metric
radioButtons("metric",
"Metric",
c("Pageviews" = "pageviews",
"Unique Visitors" = "unique_visitors"),
selected = "pageviews"),
# Main panel for Output
mainPanel(
# Output: map
leafletOutput("mymap")
)
)
# Define server commands to draw map with data
server <- function(input, output) {
# Reactive expression to generate dataframe for selected date and metric
d <- reactive({
day <- input$traffic_date
show_metric <- input$metric
d <- joined %>%
filter(date == day) %>%
select(lat,long,show_metric) %>%
rename(selected_metric = show_metric)
})
# Note: the last pipeline element renames the metric column back to a neutral name
#create the map
output$mymap <- renderLeaflet({
leaflet(d()) %>%
addTiles() %>%
setView(8.36,46.84,7) %>%
addCircles(lat = ~ lat,
lng = ~ long,
weight = 1,
radius = ~ selected_metric)
})
}
# Run app
shinyApp(ui, server)
Currently the code returns an empty map, and I'm not sure which step I'm missing to display the circles.
Thank you for the help!
I think your issue is the use of radius. See below taken from the help documentation:
radius
a numeric vector of radii for the circles; it can also be a one-sided formula, in which case the radius values are derived from the data (units in meters for circles, and pixels for circle markers)
I realised the markers were there they were just really small. Try multiplying the selected_metric by 10000 or changing to use addCircleMarkers.
Update
Using your data set which I converted to date and numeric where applicable and removing setView() so that the map automatically zooms to points out of that range. One of the issues I had was I initially couldn't see points as they were in Africa for example. Also many dates within the range above don't have data to display circles.
# Required packages
library(shiny)
library(leaflet)
library(dplyr)
# Define UI for application that shows a map
ui <- fluidPage(
# App title
titlePanel("Metrics by location"),
# Input: select date range
sliderInput("traffic_date",
"Date:",
min = as.Date("2019-01-01","%Y-%m-%d"),
max = as.Date("2019-07-31","%Y-%m-%d"),
value=as.Date("2019-01-01"),
timeFormat="%Y-%m-%d"),
# Input: select metric
radioButtons("metric",
"Metric",
c("Pageviews" = "pageviews",
"Unique Visitors" = "unique_visitors"),
selected = "pageviews"),
# Main panel for Output
mainPanel(
# Output: map
leafletOutput("mymap")
)
)
# Define server commands to draw map with data
server <- function(input, output) {
# Reactive expression to generate dataframe for selected date and metric
d <- reactive({
day <- input$traffic_date
show_metric <- input$metric
d <- joined %>%
filter(date == day) %>%
select(lat,long,show_metric) %>%
rename(selected_metric = show_metric)
})
# Note: the last pipeline element renames the metric column back to a neutral name
#create the map
output$mymap <- renderLeaflet({
leaflet(d()) %>%
addTiles() %>%
# setView(8.36,46.84,7) %>%
addCircles(lat = ~ lat,
lng = ~ long,
weight = 1,
radius = ~ selected_metric)
})
}
# Run app
shinyApp(ui, server)
I'm using the leaftlet.extras R package to add Gps control inside a map.
I'm using the extension addControlGPS inside my code :
... %>%
addControlGPS(options = gpsOptions(position = "topleft", activate = TRUE,
autoCenter = TRUE, maxZoom = 60,
setView = TRUE)) %>%
...
The controller works ok.
I need to extract the Gps coordinates to re-use in my code as arguments for other functions. Is there any way to do that ?
Every time the gps location updates, the coordinates are written to map.id+'_gps_located'. You can find all leaflet.extras bindings in the htmlwidgets/bindings folder in their git.
Working example
library(leaflet)
library(leaflet.extras)
library(shiny)
ui <- fluidPage(
leafletOutput('map')
)
server <- function(input, output, session) {
output$map <- renderLeaflet({ leaflet()%>%addTiles() %>%
addControlGPS(options = gpsOptions(position = "topleft", activate = TRUE,
autoCenter = TRUE, maxZoom = 60,
setView = TRUE))})
observe(
print(input$map_gps_located)
)
}
shinyApp(ui, server)
I've recently had a similar problem with an app I was working on.
You can extract the gps coordinates from a leaflet map by using the _marker_click feature, where is the map label you specify as an output for the leaflet rendering statement.
In my case here's the chunk of code I used to retrieve the coords. In my case the output name of the map object was parksMap therefore the full input to consider in the event observation was parksMap_marker_click. This statement can be saved in a variable (in my case pin), that stores the coordinates data. Finally you need to wrap it all in a reactive expression to be able to save every coordinate when clicking on a point in the leaflet map.
# code to load the park card once the click event on a marker is intercepted
observeEvent(input$parksMap_marker_click, {
pin <- input$parksMap_marker_click
#print(Sys.time()) #uncomment to log coords
#print(pin) #uncomment to log coords
selectedPoint <- reactive(parks[parks$Latitude == pin$lat & parks$Longitude == pin$lng,])
leafletProxy("parksMap", data = selectedPoint()) %>% clearPopups() %>%
addPopups(~Longitude,
~Latitude,
popup = ~park_card(selectedPoint()$ParkName, selectedPoint()$ParkCode, selectedPoint()$State, selectedPoint()$Acres, selectedPoint()$Latitude, selectedPoint()$Longitude)
)
})
The full github repo of the app is available here.
I am having an issue with using Plotly on my shiny server. I have a clustering app where I bring in a data file and you are able to perform two variable and three variable clustering. The first tab is the two variable clustering. I use ggplot() to create the plot and then using Plotly's ggplotly() function to make it a plotly object to enable interactivity. This renders fine on the app's page.
The issue here is when plotting the three variable clustering. Instead of ggplot() and then ggploty(), I use Plotly's function plot_ly(). This allows me to pass an x, y, and z variable. It works like a charm on my local machine, but when on the shiny server I get this error Error: An error has occurred. Check your logs or contact the app author for clarification. I open my logs and do a hard refresh of the app, no logs are showing. My package versions are the same for both my shiny server and my local machine. The error is not every telling here and I have tried to google around but I am getting no where.
Here is the code:
server.R
## Needed Lib's
library(shiny)
library(ggplot2)
library(plotly)
library(DT)
library(cluster)
## Initiate the severer
shinyServer(
function(input, output, session) {
## Display the text that describes why to use this app/method for grouping
output$text <- renderText({
## Let the user know which tab they have selected
{paste0("You are viewing the \"", input$ClusterChoice, "\"")}
})
## Display the text that describes why to use this app/method for grouping
output$why_text <- renderText({
## Let the user know which tab they have selected
"This is why"
})
# Variable #1
output$varselect1 <- renderUI({
selectInput("var1", label = "Select first variable for clustering:",
choices = names(dataset()), selected = names(dataset())[1])
})
# Variable #2
output$varselect2 <- renderUI({
selectInput("var2", label = "Select second variable for clustering:",
choices = names(dataset()), selected = names(dataset())[2])
})
## Clustering with third variable
# Variable #1
output$varselect3 <- renderUI({
selectInput("var3", label = "Select third variable for clustering (only works in Multiple Variable Tiering Tab):",
choices = names(dataset()), selected = names(dataset())[3])
})
## Read in the data
dataset <- reactive({
infile <- input$datafile
if (is.null(infile)) {
return(NULL)
}
else {read.csv(infile$datapath)}
})
## Compute the K means algo
compute_kmeans <- reactive({
# Choose between simple K means or a more complex K means with third variable
if (input$ClusterChoice == 'Two Variable Tiering') {
data <- subset(dataset(), select = c(input$var1, input$var2))
# Scale the data. Using "standardized" here. This will center and then scale the data
#data <- scale(data, center = TRUE)
# Change some columns names
colnames(data) <- c('x', 'y')
data <- na.omit(data)
# Set the seed
set.seed(111)
# Cluster
Kclust <- kmeans(data, input$k)
# Save results to a list
kmean.result <- list(kmean.result = data.frame(data, cluster = as.factor(Kclust$cluster)))
return(kmean.result)
}
# K means with third variable
else { (input$ClusterChoice == 'Multiple Variable Tiering')
three_var_data <- subset(dataset(), select = c(input$var1, input$var2, input$var3))
# Scale the data. Using "standardized" here. This will center and then scale the data
#three_var_data <- scale(three_var_data, center = TRUE)
three_var_data <- na.omit(three_var_data)
# Set the seed
set.seed(111)
# Cluster
Kclust <- kmeans(three_var_data, input$k)
kmean.result <- list(kmean.result = data.frame(three_var_data, cluster = as.factor(Kclust$cluster)))
return(kmean.result)
}
})
## Create a dataframe of the K means & K means with third variable results
kmeans_results <- reactive({
# For only two variables
if (input$ClusterChoice == 'Two Variable Tiering') {
# Call the method that computes the k means
data <- compute_kmeans()
# Save the results into a df
results <- data$kmean.result
results_df <- data.frame(results)
colnames(results_df) <- c(input$var1, input$var2, 'Grouping')
return(results_df)
}
# For more than two variables
else { (input$ClusterChoice == 'Multiple Variable Tiering')
# Call the method that computes the k means with third variable
three_var_data <- compute_kmeans()
# Save the results into a df
three_var_results <- three_var_data$kmean.result
three_var_results_df <- data.frame(three_var_results)
colnames(three_var_results_df) <- c(input$var1,
input$var2,
input$var3,
'Grouping')
return(three_var_results_df)
}
})
## Results of each K means & K means with third variable
master_results <- reactive({
# Call the method that stores the raw input data
data <- dataset()
# For only two variables
if(input$ClusterChoice == 'Two Variable Tiering') {
# Call the method that stores the K means results
kmean_results <- kmeans_results()
# Merge the K means results with the raw input data
results <- merge(kmean_results, data)
results <- results[!duplicated(results), ]
return(results)
}
# For more than two variables
else { (input$ClusterChoice == 'Multiple Variable Tiering')
# Call the method that stores the K means with third variable results
kmean_three_var_results <- kmeans_results()
# Merge the K means with third variable results with the raw input data
three_var_resutls <- merge(kmean_three_var_results, data)
three_var_resutls <- three_var_resutls[!duplicated(three_var_resutls), ]
return(three_var_resutls)
}
})
## Plot the K means results
output$plot <- renderPlotly({
graphics.off()
pdf(NULL)
# For only two variables
if (input$ClusterChoice == 'Two Variable Tiering') {
# Call the K means results method
results <- master_results()
# Change the x & y variables so we can call it in the tool-tip
x_axis <- results[[input$var1]]
y_axis <- results[[input$var2]]
# Plot
plot <- ggplot(data = results,
aes(x = x_axis,
y = y_axis,
color = Grouping,
name = Markets)) +
geom_point(size = 2) +
ggtitle("Grouping Results") +
labs(x = input$var1, y = input$var2)
# Use Plotly for interactivity
plotly_plot <- ggplotly(plot)
return(plotly_plot)
}
# For more than two variables
else if (input$ClusterChoice == 'Multiple Variable Tiering')
# Call the K means and third variable
three_var_resutls <- master_results()
# Change x, y, & z variables so we can call it in the tool-tip
x_axis <- three_var_resutls[[input$var1]]
y_axis <- three_var_resutls[[input$var2]]
z_axis <- three_var_resutls[[input$var3]]
# Plot (using Plotly for interactivity)
three_var_plot <- plot_ly(three_var_resutls,
x = x_axis,
y = y_axis,
z = z_axis,
color = factor(three_var_resutls$Grouping),
text = ~paste('Markets:', three_var_resutls$Markets,
'<br>Grouping:', three_var_resutls$Grouping)) %>%
add_markers() %>%
layout(title = 'Grouping Results',
scene = list(xaxis = list(title = input$var1),
yaxis = list(title = input$var2),
zaxis = list(title = input$var3)))
return(three_var_plot)
})
## Render the K means and K means with third variable results to a data table
output$cluster_table <- DT::renderDataTable({
# For only two variables
if (input$ClusterChoice == 'Two Variable Tiering') {
# Call results method
results <- master_results()
# Render data table
datatable(results,
# Get rid of row indexes
rownames = FALSE,
# Enable downloading options
extensions = 'Buttons',
options = list(
dom = "Blfrtip",
buttons =
list("copy", list(
extend = "collection",
buttons = c("csv", "excel", "pdf"),
text = "Download")),
lengthMenu = list(c(10, 20, -1),
c(10, 20, "All")),
pageLength = 10))
}
else if (input$ClusterChoice == 'Multiple Variable Tiering') {
# Call results method
results_three_var <- master_results()
# Render data table
datatable(results_three_var,
# Get rid of row indexes
rownames = FALSE,
# Enable downloading options
extensions = 'Buttons',
options = list(
dom = "Blfrtip",
buttons =
list("copy", list(
extend = "collection",
buttons = c("csv", "excel", "pdf"),
text = "Download")),
lengthMenu = list(c(10, 20, -1),
c(10, 20, "All")),
pageLength = 10))
}
})
}
)
ui.r
## Needed Lib's
library(shiny)
library(plotly)
## Start the UI renderer
shinyUI(
pageWithSidebar(
## The TITLE!
headerPanel("Grouping Data Together"),
## This is where the up-loader and drop downs live
sidebarPanel(
fileInput('datafile',
'Choose CSV file',
accept=c('text/csv', 'text/comma-separated-values,text/plain')),
uiOutput("varselect1"),
uiOutput("varselect2"),
uiOutput("varselect3"),
numericInput('k', 'Number of clusters', value = 3, min = 1, step = 1)),
## The main panel where all the shit happens
mainPanel(
textOutput("text"),
tabsetPanel(id = 'ClusterChoice',
tabPanel("Two Variable Tiering", value = 'Two Variable Tiering'),
tabPanel("Multiple Variable Tiering", value = 'Multiple Variable Tiering'),
tabPanel("Why Do It This Way?", value = "Why Do It This Way?")
),
## Description of this app
h2("Description"),
p("This tool's main functionality is to quickly put your data into groups. The file that is being uploaded
should only be the data that you want to group together. For example, if you have data that is broken out
by Markets (DMA) and you want to group those Markets into similar groupings you should upload all data
that you think is important to those groupings. Then via the drop downs you can select the 2 or 3 variables
you think best represents the groupings."),
## The tabs
h2("The Different Tabs"),
p("Tab #1: This tab is only when you want to use 2 variables in your data to create the groupings."),
p("Tab #2: This tab is only when you want to use 3 variables in your data to create the groupings."),
## Instructions for using this app
h2("Instructions"),
p("Please follow these instructions to create your groupings."),
p("1. Upload a data file (.csv) that will be used for making your groups.
The first row in the .csv file should be the column headers of the data.
The first column in the .csv file should be where your data starts.
THERE SHOULD BE NO 'BUFFERS' AROUND YOUR DATA FILE, i.e. empty rows and columns."),
p("2. Pick the variables you want to create the groupings off of via the drop-downs"),
p("3. Indicate the desired number of groups via the last drop-down."),
## The plot instructions
h2("Visualizing Your Grouped Data"),
p("You can download the plot as a .png file by hovering over the plot and selecting the
camera icon in the upper right hand side of the plot."),
# Plotly function that links the UI to the Server
plotlyOutput('plot', height = 700),
## The grouping data table instructions
h2("The Groupings"),
p("Instructions for downloading data:"),
p("1. To download the data use the drop-down below, labeled 'Show entries', to show 'All' entries."),
p("2. Click the 'Download' button and then select the type of file (PDF, excel, csv)."),
p("3. (Optional) You can copy the data that is shown to your clipboard is paste it into an excel/csv document."),
# JS DataTable function that links the UI to the Server
DT::dataTableOutput("cluster_table"))
)
)
I have a leaflet map which I created in R and I would like to add a dropdown filter based on fields in a column. The code looks straightforward in JS, see example here Leaflet dropdown filter, however I can't figure out how to adjust the code for R.
library(leaflet.extras)
n = c(2, 3, 5)
long = c(102.1,102.13,102.2)
lat = c(55,55.1,55.15)
select_cols= c("a","b","c")
df = data.frame(n, long, lat,select_cols)
pal <- colorFactor(c("navy", "red","green"), domain = c("a","b","c"))
leaflet(df)%>% addTiles()%>%
addCircleMarkers(lng = long,lat=lat,radius= ~n*2,
color=~pal(select_cols),stroke=F,fillOpacity = 1)
Here is the JS code that adds a dropdown filter.
var legend = L.control({position: 'topright'});
legend.onAdd = function (map) {
var div = L.DomUtil.create('div', 'info legend');
div.innerHTML = '<select><option>1</option><option>2</option>
<option>3</option></select>';
div.firstChild.onmousedown = div.firstChild.ondblclick =
L.DomEvent.stopPropagation;
return div;
};
legend.addTo(map);
I know how to add the "show hide layers" feature, however I have over 20 different fields and I think it would be easier if the user could select the associated field using a drop down box.
In case anyone is looking for something similar, I found a solution using crosstalk. See the code below for an example.
library(crosstalk)
library(tidyverse)
library(leaflet.extras)
quakes <- quakes %>%
dplyr::mutate(mag.level = cut(mag,c(3,4,5,6),
labels = c('3.01-4.00', '4.01-5.00', '5.01-6.00')))
quakes_sd<- SharedData$new(quakes)
map<- leaflet(quakes_sd)%>%addProviderTiles(providers$Esri.WorldTopoMap)%>% addCircles()
#add filter
bscols(
filter_select("Magnitude Level", "Magnitude Level", quakes_sd, ~mag.level)
)
bscols(map)
I am making an R Shiny app where users will enter their city, state, and zip code, press a button, and then their location (Lat, Lon) will become the new center of the map. The inputs are collected via this section of code in the server.R:
output$ntextCounter <- eventReactive(input$goButton, {
citySelected <- as.character(input$city)
stateSelected <- as.character(input$state)
zipCodeSelected <- as.character(input$zipCode)
location2 <- stri_detect(paste(as.character(input$city),
as.character(input$state), as.character(input$zipCode), sep=", "), fixed = locationData$Location, opts_regex=stri_opts_regex(case_insensitive=TRUE))
counter <<- counter + 1
lat1 <- as.numeric(locationData[which(location2),]$latitude)
lon1 <- as.numeric(locationData[which(location2),]$longitude)
return(c(lat1, lon1))
})
I am able to easily view the new latitude/longitude values in the UI using:
verbatimTextOutput("ntextCounter")
But I need to be able to pass these values, "return(c(lat1, lon1))", to the center = c(Lat, Lon) in the leaflet map in the ui.r:
leafletMap("map", "100%", 365,
initialTileLayer = "http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
initialTileLayerAttribution = HTML('© OpenStreetMap contributors, CC-BY-SA'),
options=list(center = c(38.25, -93.85), zoom = 4, maxBounds = list(list(1, -180), list(78, 180))
)),
I have an initial map center at c(38.25, -93.85), but ultimately I want to be able to pass it changing values from ntextCounter. I'm not sure if this is a scoping issue or what but I need help getting the new lat/lon values into the leaflet map center.
Any assistance would be greatly appreciated. Thanks in advance.
It seems you are creating your leaflet on the ui-side. If you want it to be responsive to inputs you've got to do that on the server side with renderLeaflet.
Your coordinates could be stored in a reactiveValues and you'd update them with a observeEvent:
location <- reactiveValues(lat = 38.25, lon = -93.85)
observeEvent(input$goButton, {
city <- as.character(input$city)
state <- as.character(input$state)
zipCode <- as.character(input$zipCode)
newloc <- stri_detect(paste(city, state, zipCode, sep=", "),
fixed = locationData$Location,
opts_regex=stri_opts_regex(case_insensitive=TRUE))
location$lat <- as.numeric(locationData[which(newloc),]$latitude)
location$lon <- as.numeric(locationData[which(newloc),]$longitude)
})