Interactive Data Visualisation

R packages

library(plotly)
#install.packages("dygraphs")
library(dygraphs)
library(tidyverse)
library(tsibble)
library(ggiraph)

Data packages

library(covid19srilanka)
library(palmerpenguins)

Attaching package: 'palmerpenguins'
The following objects are masked from 'package:datasets':

    penguins, penguins_raw

Dygraphs in Time Series

Data preprocessing

data(covid.cases)
head(covid.cases)
# A tibble: 6 × 3
  Date       Type      Count
  <date>     <chr>     <dbl>
1 2020-03-29 Confirmed   115
2 2020-03-29 Recovered    10
3 2020-03-29 Deaths        1
4 2020-03-29 Active      104
5 2020-03-30 Confirmed   120
6 2020-03-30 Recovered    11
library(tsibble)
library(dplyr)
library(tidyr)

# Convert to tsibble
covid.cases <- covid.cases |> 
  mutate(Date = as.Date(Date)) |>
  as_tsibble(index = Date, key = Type)


# Check if the data is regular (e.g., daily, weekly, monthly)
is_regular(covid.cases)
[1] TRUE
# Identify the interval
interval(covid.cases)
<interval[1]>
[1] 1D
# Fill missing dates (if any) with NA
covid.cases_filled <- covid.cases |> 
  fill_gaps()

# (Optional) replace missing counts with 0 instead of NA
covid.cases_filled <- covid.cases |> 
  fill_gaps(Count = 0)

Time series plot

Preparing data for dygraphs

# Pivot to wide format for dygraphs
covid_wide <- covid.cases |>
  pivot_wider(names_from = Type, values_from = Count)

# Convert to xts (remove Date column and set as rownames)
covid_xts <- xts::xts(covid_wide |> select(-Date), order.by = covid_wide$Date)
head(covid_xts)
           Active Actives Confirmed Deaths Recovered       Date
2020-03-29    104    <NA>       115      1        10 2020-03-29
2020-03-30    108    <NA>       120      1        11 2020-03-30
2020-03-31    104    <NA>       122      2        16 2020-03-31
2020-04-01    123    <NA>       143      2        18 2020-04-01
2020-04-02    124    <NA>       148      3        21 2020-04-02
2020-04-03    125    <NA>       151      4        22 2020-04-03
# Plot with dygraphs
dygraph(covid_xts) |> 
  dyRangeSelector() |>   # adds interactive date range selector
  dyOptions(stackedGraph = FALSE) |> 
  dyLegend(show = "follow")

ggplotly

g <- ggplot(covid.cases, aes(x = Date, y = Count, group = Type, color=Type)) +
  geom_line(alpha = 0.4) 
ggplotly(g, tooltip = c("Type"))

using plotly

# Basic plotly line plot
plot_ly(
  data = covid.cases,
  x = ~Date,
  y = ~Count,
  color = ~Type,      # group lines by 'Type'
  type = "scatter",
  mode = "lines",
  line = list(width = 2, dash = "solid"),  # optional styling
  opacity = 0.4       # mimic ggplot alpha
) %>%
  layout(
    title = "COVID Cases Over Time",
    xaxis = list(title = "Date"),
    yaxis = list(title = "Count")
  )

Scatterplot

plotly: ggplotly

p1 <- penguins |> 
  ggplot(aes(x = bill_length_mm, y = bill_depth_mm, 
             color = species, shape = species)) + 
  geom_point(size = 2) + 
  geom_smooth(method = "lm", formula = "y ~ x") +
  labs(x = "Bill length (mm)", y = "Bill width (mm)") +
  theme_minimal()
p1
Warning: Removed 2 rows containing non-finite outside the scale range
(`stat_smooth()`).
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).

ggplotly(p1)
Warning: Removed 2 rows containing non-finite outside the scale range
(`stat_smooth()`).

plotly: plotly

penguins |> 
  plot_ly(x = ~bill_length_mm, y = ~flipper_length_mm, 
          color = ~species, symbol = ~species,
          type = "scatter", mode = "markers",  marker = list(size = 10)) |> 
  layout(
    plot_bgcolor = 'white',
    xaxis = list(title = "Bill Length (mm)", zeroline = FALSE, ticklen = 5),
    yaxis = list(title = "Flipper Length (mm)", zeroline = FALSE, ticklen = 5),
    title = "Bill length vs. bill width"
  )
Warning: Ignoring 2 observations

ggiraph

p2 <- penguins |> 
  ggplot(aes(x = bill_length_mm, y = bill_depth_mm, 
             color = species, shape = species)) + 
  geom_point_interactive(
    aes(tooltip = paste("bill_length_mm:", bill_length_mm, "<br>",
                        "bill_depth_mm:", bill_depth_mm)),
    size = 2
  ) + 
  geom_smooth_interactive(method = "lm", formula = "y ~ x")
girafe(ggobj = p2)
Warning: Removed 2 rows containing non-finite outside the scale range
(`stat_smooth()`).
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_interactive_point()`).

Bar graph

ggiraph

p3 <- penguins |> 
  ggplot(aes(x = island)) +
  geom_bar_interactive(aes(tooltip = paste("count:", after_stat(count)),
                           data_id = island))
girafe(ggobj = p3)

plotly:plotly

penguins |> 
  count(island) |> 
  plot_ly(data = _, x = ~island, y = ~n, type = "bar") |> 
  layout(barmode = 'stack')

plotly:ggplotly

p3 <- penguins |> 
  ggplot(aes(x = island)) +
  geom_bar()
ggplotly(p3)

Your turn: Draw histogram for flipper length

Faceting with interactive plots

p4 <- penguins |> 
  ggplot(aes(x = bill_length_mm, y = flipper_length_mm)) +
  geom_point_interactive(
    aes(color = species, shape = species,
        tooltip = paste("bill_length_mm:", bill_length_mm, "<br>",
                        "flipper_length_mm:", flipper_length_mm, "<br>",
                        "species:", species))
  ) +
  facet_wrap(~island)
girafe(ggobj = p4)
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_interactive_point()`).
p5 <- penguins |> 
  ggplot(aes(x = bill_length_mm, y = flipper_length_mm)) +
  geom_point(aes(color = species, shape = species)) +
  facet_wrap(~island)
ggplotly(p5)
penguins |>
  group_by(island) |>
  group_map(~{
    plot_ly(data = ., x = ~bill_length_mm, y = ~flipper_length_mm, 
                     color = ~species, type = "scatter", mode = "markers")
    }, .keep = TRUE) |>
  subplot(nrows = 1, shareX = TRUE, shareY = TRUE)
Warning: Ignoring 1 observations
Warning: Ignoring 1 observations

Linked interactivity across the subplots

library(dplyr)
library(plotly)
library(palmerpenguins)

# Prepare data with highlight by species
penguins1 <- penguins %>%
  highlight_key(~species)

# ---- Plot 1: Flipper length vs Body mass ----
fig1 <- plot_ly(
  data  = penguins1,
  x     = ~flipper_length_mm,
  y     = ~body_mass_g,
  type  = "scatter",
  mode  = "markers",
  color = ~species
)

# ---- Plot 2: Bill length vs Bill depth ----
fig2 <- plot_ly(
  data  = penguins1,
  x     = ~bill_length_mm,
  y     = ~bill_depth_mm,
  type  = "scatter",
  mode  = "markers",
  color = ~species
)

# ---- Combine both side by side ----
subplot(fig1, fig2, nrows = 1, shareY = FALSE, titleX = TRUE, titleY = TRUE)
Warning: Ignoring 2 observations
Warning: Ignoring 2 observations
library(dplyr)
library(plotly)
library(palmerpenguins)

# Add a unique ID for each row
penguins1 <- penguins %>%
  mutate(id = row_number()) %>%
  highlight_key(~id)

# ---- Plot 1: Flipper length vs Body mass ----
fig1 <- plot_ly(
  data  = penguins1,
  x     = ~flipper_length_mm,
  y     = ~body_mass_g,
  type  = "scatter",
  mode  = "markers",
  color = ~species
)

# ---- Plot 2: Bill length vs Bill depth ----
fig2 <- plot_ly(
  data  = penguins1,
  x     = ~bill_length_mm,
  y     = ~bill_depth_mm,
  type  = "scatter",
  mode  = "markers",
  color = ~species
)

# ---- Combine both plots side by side ----
subplot(fig1, fig2, nrows = 1, shareY = FALSE, titleX = TRUE, titleY = TRUE) %>%
  highlight(on = "plotly_selected", off = "plotly_doubleclick")
Warning: Ignoring 2 observations
Warning: Ignoring 2 observations