When you want to show spatial data with additional information for each data point you can use shiny for interactive exploration.
This is what we want to achieve. The user can select one or more points on the map to see additional information for the position.
To run the example successfully, I needed the following packages.
We will work with some example data, consisting in random points and for each of the points some temporal information.
lon | lat | month_1 | month_2 | month_3 | month_4 | month_5 | month_6 | month_7 |
---|---|---|---|---|---|---|---|---|
9.0 | 52.2 | 30 | 31 | 41 | 58 | 76 | 91 | 76 |
9.5 | 53.6 | 24 | 34 | 26 | 43 | 56 | 65 | 69 |
12.3 | 49.4 | 78 | 94 | 108 | 128 | 134 | 136 | 134 |
8.3 | 52.2 | 28 | 42 | 52 | 65 | 67 | 70 | 90 |
8.2 | 50.1 | 58 | 76 | 78 | 74 | 73 | 95 | 108 |
We first convert the data to be in tidy format (using pivot_longer
from the {tidyr} package), i.e. we will have one column called month and one column containing the values.
lon | lat | month | value |
---|---|---|---|
9 | 52.2 | 1 | 30 |
9 | 52.2 | 2 | 31 |
9 | 52.2 | 3 | 41 |
9 | 52.2 | 4 | 58 |
9 | 52.2 | 5 | 76 |
9 | 52.2 | 6 | 91 |
9 | 52.2 | 7 | 76 |
9 | 52.2 | 8 | 74 |
Let’s see what we can do with this data. First, we can use borders
to show a quick map.
map <- ggplot(df)+
geom_point(aes(x=lon,y=lat))+
borders(regions="Germany")+
coord_quickmap()+
theme_void()
map
As we want to be able to select points interactively, we want to display which points were selected. This is what the map would look like.
exmpl <- df %>% slice(1) %>% select(lon,lat)
map +
geom_point(data=exmpl,aes(x=lon,y=lat),size=3,col="red")
Last, we want to show the temporal development for the selected points. This can be an easy line chart, for the filtered point.
To make the Shiny app short and concise, I will put the functionalities which create the three plots into functions.
plot_map(df)
: Creates the map with the points.plot_selected_point_in_map(df,lon,lat)
: Creates the same map but highlighting the points in lon
and lat
.show_detail(df,lon,lat)
: Creates the line plot for selected points.First, we will define the user interface. In this case we have a sidebar panel which contains the map and a main panel where the line plot will appear when points are selected.
The important lines to notice are that in plotOutput
for the map, we will add the parameters click
and brush
. Doing this, we can later define what happens when someone clicks on the points.
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
p("Please select one or various points."),
plotOutput("example_map",
click = "plot1_click",
brush = "plot1_brush"
)
),
mainPanel(
plotOutput("time_plot")
)
)
)
Inside the server function, we have several steps, which I will explain one by one. First, we need some variables that will be updated when the user selects points. lat
, lon
and time
are empty in the beginning. For map, we will plot the initial map which shows all points.
vals <- reactiveValues(lat=NULL,
lon=NULL,
map=plot_map(df),
time = NULL)
For the two plot outputs it is easy, we will just return the respective variable of the reactive value, i.e. the map and the line chart.
output$example_map <- renderPlot({
return(vals$map)
})
output$time_plot <- renderPlot({
return(vals$time)
})
The key for the triggered action is the process of selecting a point. Remember that in the ui
part we specified another parameter for the map: click = "plot1_click"
. Here we will specify what happens when someone clicks on the map.
First, we will check if there is a close point to the click in the dataset df
. If this is not the case, nothing happens. Otherwise, we will assign the coordinates of the selected point to vals$lat
and vals$lon
our reactive variables. Additional we will change the map from the standard map to the map with selected red points and will show the line plot for the selected points. As these are reactive values, our plots will be updated automatically.
observeEvent(input$plot1_click, {
point <- nearPoints(df %>% distinct(lon,lat), input$plot1_click, addDist = FALSE)
if(length(point[[1]])==0) {} #happens when no point is selected
else {
vals$lon <- point[[1]]
vals$lat <- point[[2]]
vals$map <- plot_selected_point_in_map(df,vals$lon,vals$lat)
vals$time <- show_detail(df,vals$lon,vals$lat)
}
})
You will see that the brush option is very similar. Note, that there are two other options, hover
and dblclick
which can trigger actions when you just hover over a points or double click. This would be the complete server
function when putting the steps together.
server <- function(input, output) {
vals <- reactiveValues(lat=NULL,
lon=NULL,
map=plot_map(df),
time = NULL)
output$example_map <- renderPlot({
return(vals$map)
})
output$time_plot <- renderPlot({
return(vals$time)
})
observeEvent(input$plot1_click, {
point <- nearPoints(df %>% distinct(lon,lat), input$plot1_click, addDist = FALSE)
if(length(point[[1]])==0) {} #happens when no point is selected
else {
vals$lon <- point[[1]]
vals$lat <- point[[2]]
vals$map <- plot_selected_point_in_map(df,vals$lon,vals$lat)
vals$time <- show_detail(df,vals$lon,vals$lat)
}
})
observeEvent(input$plot1_brush, {
point <- brushedPoints(df %>% distinct(lon,lat), input$plot1_brush)
if(length(point[[1]])==0) {} #happens when no point is selected
else {
vals$lon <- point[[1]]
vals$lat <- point[[2]]
vals$map <- plot_selected_point_in_map(df,vals$lon,vals$lat)
vals$time <- show_detail(df,vals$lon,vals$lat)
}
})
}
Later I transformed the data to long format.
df <- df %>%
tidyr::pivot_longer(cols=starts_with("month"), names_to = "month",values_to="value") %>%
mutate(month=readr::parse_number(month))