Plotly (IDV)

1 What is Plotly?

  • Plotly for R is an interactive, browser-based charting library built on the open source javascript graphing library, plotly.js. It works entirely locally, through the HTML widgets framework”
  • Plotly.js is “a high-level, declarative charting library. plotly.js ships with 20 chart types, including 3D charts, statistical graphs, and SVG maps.” It is built on top of d3.js and stack.gl.
    • Why not use closed-source commercial alternatives such as Tableau or HighCharts?
  • D3.js (or just D3 for Data-Driven Documents) is a JavaScript library for producing dynamic, interactive data visualizations in web browsers. It makes use of the widely implemented SVG, HTML5, and CSS standards.”
  • stackgl is an open software ecosystem for WebGL, built on top of browserify and npm. Inspired by the Unix philosophy, stackgl modules”do one thing, and do it well”.
  • WebGL (Web Graphics Library) is a JavaScript API for rendering interactive 3D computer graphics and 2D graphics within any compatible web browser without the use of plug-ins.
  • Plotly has interfaces with R, Python (Matlab and Julia)
  • Overview of charts
  • Get the cheat sheet

2 Example(s) for starters

p <- plot_ly(data = swiss, x = ~Catholic, y = ~Fertility) # Save plot as p
p # Display plot



p <- ggplot(data = swiss, aes(x = Catholic, y = Fertility)) +
     geom_point()
     ggplotly(p) # Save plot as p
p # Display plot


3 Interactive elements

  • Symbols
    • : Download plot as png
    • : Change mouse function to zoom
    • : Pan, i.e. push plot around
    • : Select data with box
    • : Select data with a lasso
    • : Zoom in
    • : Zoom out
    • : Autoscale (so all data is shown)
    • : Reset axes
    • : Show data closest on hover (e.g. X and Y coords)
    • : Compare data on hoover (what does that do?)
    • : Plotly Symbol (leads to their website)

4 Basic workings

  • Plotly charts are described declaratively in the call signature (function) of plotly::plot_ly, plotly::add_trace, and plotly::layout

  • Every aspect of a plotly chart (the colors, the grid-lines, the data, and so on) has a corresponding key in these call signatures

  • Plotly’s graph description places attributes into two categories

    • traces which describe a single series of data in a graph
    • layout attributes that apply to the rest of the chart, like the title, xaxis, or annotations
  • More in the R figure reference and plotly for R

5 Basic functions

  • plot_ly(): Transform data into a plotly visualization
    • plot_geo(): For initializing a plotly-geo object
    • layout(), add_trace(), style(): For modifying any plotly object
    • plotly_empty(): Generate empty plot
  • add_trace(p, ..., data = NULL, inherit = TRUE): Add trace(s) to a plotly visualization
    • add_markers(), add_text(), add_lines(): See ?add_trace
  • layout(p, ..., data = NULL): Modify the layout of a plotly visualization
  • style(p, ..., traces = 1): Modify trace(s) of an existing plotly visualization
  • How can we read: https://plot.ly/r/reference/ ?
  • Most graph parameters are added directly in the plot_ly() function or afterwards ggplotly(): Translate a ggplot2 object to a plotly object + Possibility to add plotly R code with pipes %>%

6 Ways of setting up Plotly graph

  • Recommendation: Try ggplotly() first and switch to plot_ly() if you can’t get the desired result
# 1. Plotly code using pipes
plot_ly(data = swiss, x = ~Catholic, y = ~Fertility) %>%
    add_markers() %>%
    layout(xaxis = list(range = c(0,50)))



# 2. Plotly code adding to plotly object
p <- plot_ly(data = swiss, x = ~Catholic, y = ~Fertility) %>%
    add_markers()
p <- p %>%
    layout(xaxis = list(range = c(0,50)))
p



# 3. Create ggplot graph, convert to plotly and additionally modify if necessary
p <- ggplot(data = swiss, aes(x = Catholic, y = Fertility)) +
     geom_point()

ggplotly(p) %>%
    layout(xaxis = list(range = c(0,50)))

7 Plot types: Basic 2D

  • Below a few basic plotly plot types. Please copy the code and run it with R (remember to load the plotly package).

  • Set with plot_ly(..., type = "scatter") (overview)

  • 2D types: scatter, bar, box, heatmap, histogram, histogram2d, area, pie, contour

plot_ly(data = swiss, 
        x = swiss$Catholic, 
        y = swiss$Fertility, 
        type = "scatter", mode = "markers") %>%
  layout(title = "Scatterplot", 
         autosize = F, width = 300, height = 300)
d <- data.frame(x = c("SPD", "CDU", "Green"), y = c(40, 25, 37))
plot_ly(data = d, x = ~x, y = ~y, type = "bar") %>%
  layout(title = "Barplot", 
         autosize = F, width = 300, height = 300, 
         xaxis = list(title= "Voters"),
         yaxis = list(title= "N"))
plot_ly(data = swiss, y = ~Catholic, type = "box", name = "Catholic") %>% 
  add_trace(y = ~Education, type = "box", name = "Education") %>%
  layout(title = "Boxplot", 
         autosize = F, width = 300, height = 300)
plot_ly(z = volcano, type = "heatmap") %>%
  layout(title = "Heatmap", 
         autosize = F, width = 300, height = 300)
plot_ly(x = swiss$Catholic, type = "histogram") %>%
  layout(title = "Histogram", 
         autosize = F, width = 300, height = 300,
         xaxis = list(title = "Catholic %"),
         yaxis = list(title = "Density (abs.)")
         )
d <- data.frame(labels = c("SPD", "CDU", "Green"), values = c(40, 25, 37))
ax <- list(
  title = "",
  zeroline = FALSE,
  showline = FALSE,
  showticklabels = FALSE,
  showgrid = FALSE
)
plot_ly(d, labels = d$labels, values = d$values, type = "pie") %>%
  layout(title = "Pie Chart") %>%
  layout(autosize = F, width = 300, height = 300, xaxis = ax, yaxis = ax)
plot_ly(z = volcano, type = "contour") %>%
  layout(title = "Contour plot", 
         autosize = F, width = 300, height = 300)

8 Advanced topics (skip!)

8.1 Layout

  • Specify in layout()
    • margins =, width =, height =, font =
    • xaxis = list(): title, type, ticks, range
    • yaxis = list(): title, type, ticks, range

8.1.1 Layout: Margins

  • Specified with layout(margin = list(..)) (see here)
    • b: Sets the bottom margin (in px)
    • l: Sets the left margin (in px)
    • r: Sets the right margin (in px)
    • t: Sets the top margin (in px)
    • pad: Sets the amount of padding (in px) between the plotting area and the axis lines
  • See https://paulcbauer.shinyapps.io/plotlylayout/
p <- plot_ly(data = swiss, x = ~Catholic, y = ~Fertility, type = "scatter", mode = "markers")
  p <- layout(p, autosize = F, 
         paper_bgcolor='#D4D4D4',
         plot_bgcolor='white',
         width = 300, 
         height = 300, 
         margin = list(b = 80, l = 80, r = 80, t = 100, pad = 0, autoexpand = TRUE)
         )
  p

8.1.2 Layout: Axes

  • See all the options here
  • layout = list(xaxis = list(...), yaxis = list(...)) ::: {.cell}
p <- plot_ly(data = swiss, x = ~Catholic, y = ~Fertility, type = "scatter", mode = "markers")
  layout(p, autosize = T,
         
         xaxis = list(
            name = "Fertility",
            showexponent = "all",
            showticklabels = TRUE,
            color = "black",
            # categoryarray (dataframe column, list, vector) Sets the order 
            # in which categories on this axis appear. Only has an effect 
            # if `categoryorder` is set to "array". Used with `categoryorder`.
            #showticksuffix = "all",
            titlefont = list(color = "red", family = "Times New Roman", size = 12),
            linecolor = "red",
            mirror = TRUE,
            tickvalssrc = list(nticks = 3, linewidth = 4, autorange = TRUE),
            ticktextsrc = list(tickprefix = "-", position = 0, tickformat = "", tickmode = "auto"),
            title = "Fertility",
            ticks = "outside",
            overlaying = "free",
            rangemode = "normal",
            showtickprefix = "all",
            zeroline = TRUE,
            domain = "[0, 2]", # ???
            gridcolor = "red",
            type = "-", #(enumerated: "-" | "linear" | "log" | 
            #"date" | "category" ), X-axis type
            separatethousands = TRUE, # Sep. 1000s
            zerolinewidth = 2,
            ticklen = 8,
            categoryorder = "trace", #(enumerated: "trace" | "category ascending"
            #| "category descending" | "array" )
            hoverformat = "",
            ticksuffix = "y",
            fixedrange = FALSE, # allow x-axis zoom
            showline = TRUE,
            # ticktext (dataframe column, list, vector) Sets the text 
            #displayed at the ticks position via `tickvals`. Only has 
            #an effect if `tickmode` is set to "array". Used with `tickvals`
            showgrid = TRUE,
            # tickvals (dataframe column, list, vector) Sets the values at 
            #which ticks on this axis appear. Only has an effect if 
            #`tickmode` is set to "array". Used with `ticktext`.
            tickfont = list(color = "green", family = "Arial", size = 13),
            tickwidth = 3,
            tick0 = 0, # position of first tick
            dtick = 10, # tick interval
            tickangle = 45,
            gridwidth = 2,
            # side (enumerated: "top" | "bottom" | "left" | "right" ) 
            zerolinecolor = "blue",
            range = c(0,50),
            tickcolor = "yellow",
            anchor = "free"
            # exponentformat (enumerated: "none" | "e" | "E" | "power" | "SI" | "B" ) # formatting of tick exponent
         )
         
         )

:::

8.1.3 Exercise: Layout and basic plot types

  • Take the code and plot below and modify the layout/axes so that you get the same plot but with the layout further below. Tip append the layout with %>% layout(...) ::: {.cell}
plot_ly(data = swiss, x = ~Catholic, y = ~Fertility, type = "scatter", mode = "markers")

:::

8.2 Markers & Lines: Symbols

  • Try out different markers with this app [Paused!]
  • mode = "markers+lines" or mode = "lines"
  • marker = list(...)
    • color = '#000000'
    • size = 10 # px marker size
    • opacity = 0.5
    • symbol = 'circle'
    • line = list(color = '#000000', width = 10)) # px width of bounding lines
  • line = list(...) see arguments for line

8.2.1 Markers

swiss$Catholic.cat <- cut(swiss$Catholic, breaks = c(-Inf, 30, 60, Inf), 
                          c("Low","Middle","High"))
p <- plot_ly(mode = "markers", 
             type = "scatter",
             data = swiss, 
             x = swiss$Agriculture, 
             y = swiss$Fertility,
             marker = list(color = "black"))
p <- layout(p, showlegend = FALSE)
p
# Symbol type per category
p <- plot_ly(data = swiss, 
             x = swiss$Agriculture, 
             y = swiss$Fertility, 
             symbol = swiss$Catholic.cat, 
             mode = "markers", 
             type = "scatter", 
             marker = list(color = "black", size = swiss$Education))
p <- layout(p, showlegend = FALSE)
p

8.2.2 Lines

p <- plot_ly(data = swiss, x = swiss$Agriculture, y = swiss$Fertility, 
             mode = "lines", type = "scatter",
             line = list(type = "dash", color = "black"))
p
# THE ORDER MATTERS!
swiss <- dplyr::arrange(swiss, Agriculture)
p <- plot_ly(data = swiss, x = ~Agriculture, y = ~Fertility, 
             mode = "markers+lines", type = "scatter",
             line = list(type = "dash", color = "black"))
p
# MARKERS + LINES: Symbool categorization according to third variable
swiss$Catholic.cat <- cut(swiss$Catholic, breaks = c(-Inf, 30, 60, Inf), 
                          c("Low","Middle","High"))
p <- plot_ly(type = "scatter",
             mode = "markers+lines", 
             data = swiss, 
             x = swiss$Agriculture, 
             y = swiss$Fertility,
             marker = list(color = "black", size = 10),
             line = list(color = "black"),
             symbol = ~Catholic.cat
             ) # colors = c("red", "blue", "green")
p <- layout(p, showlegend = FALSE)
p
p <- plot_ly(type = "scatter", 
             mode = "lines",
             data = swiss, 
             x = swiss$Agriculture, 
             y = swiss$Fertility, 
             line = list(color = "black", 
                         dash = "dash")) # Beware: Wrong in plotly reference
p <- layout(p, showlegend = FALSE)
p

8.3 Shapes: Circles etc.

p <- plot_ly(x = swiss$Catholic, 
             y = swiss$Fertility, 
             type = "scatter", 
             mode = "markers", 
             text = rownames(swiss), 
             color = swiss$Education, 
             size = swiss$Infant.Mortality, 
             opacity = 0.8) %>% hide_colorbar()
p <- layout(p,
            shapes = list(
              list(type = 'circle',
                   xref = 'x', x0 = 10, x1 = 20,
                   yref = 'y', y0 = 20, y1 = 40,
                   fillcolor = 'rgb(50, 20, 90)', 
                   line = list(color = 'rgb(50, 20, 90)'),
                   opacity = 0.2),
            list(type = "rect",
                 fillcolor = "blue", 
                 line = list(color = "blue"), opacity = 0.3,
                 x0 = 50, x1 = 70, xref = "x",
                 y0 = 60, y1 = 90, yref = "y")),
            yaxis = list(gridcolor = "gray"),
            xaxis = list(gridcolor = "gray")
            )
p
# THE ONE WHO FINDS OUT HOW TO HIDE THE SCALE GETS A TWIX!

8.4 Colouring data

8.4.1 Colouring data: Continuous

p <- plot_ly(data = swiss, x = swiss$Catholic, y = swiss$Fertility, 
             color = swiss$Catholic, mode = "markers", type = "scatter")
p <- layout(p, showlegend = FALSE)  %>% hide_colorbar()
p

8.4.2 Colouring data: Categories

swiss$Catholic.cat <- cut(swiss$Catholic, 
                          breaks = c(-Inf, 30, 60, Inf), c("Low","Middle","High"))

p <- plot_ly(data = swiss, x = swiss$Catholic, y = swiss$Fertility, 
             color = swiss$Catholic.cat, mode = "markers", type = "scatter")
p <- layout(p, showlegend = FALSE)
p
swiss$Catholic.cat <- cut(swiss$Catholic, 
                          breaks = c(-Inf, 30, 60, Inf), c("Low","Middle","High"))

p <- plot_ly(data = swiss, x = swiss$Catholic, y = swiss$Fertility, 
             color = swiss$Catholic.cat, mode = "markers", type = "scatter",
             colors = c("red", "blue", "green"))
p <- layout(p, showlegend = FALSE)
p
swiss$Catholic.cat <- cut(swiss$Catholic, 
                          breaks = c(-Inf, 30, 60, Inf), c("Low","Middle","High"))
plot_ly(data = swiss, 
        x = ~Catholic, 
        y = ~Fertility,
        color = ~Catholic.cat, 
        colors = c("black", "yellow", "red"),
        size = ~Education, mode = "markers") %>%
  layout(showlegend = FALSE)
# Symbol type and color
p <- plot_ly(data = swiss, 
             x = swiss$Agriculture, 
             y = swiss$Fertility, 
             mode = "markers", 
             type = "scatter")
p <- add_lines(p, 
               symbol = ~Catholic.cat, 
               linetype = ~Catholic.cat, 
               mode = "lines")
p <- layout(p, showlegend = FALSE)
p
p <- plot_ly(type = "scatter",
             mode = "markers", 
             data = swiss, 
             x = swiss$Agriculture, 
             y = swiss$Fertility, 
             symbol = swiss$Catholic.cat,
             marker = list(size = 15)
             )
p <- layout(p, showlegend = FALSE)
p

8.5 Exercise: Markers & Colours

  • Try to construct the plot below. Tipp: You need to add marker = list(...) including the arguments color = ..., colorscale=..., size = .... Moreover, there are some readymade colorscales such as 'Greys', 'YlGnBu', 'Greens', 'YlOrRd', 'Bluered', 'RdBu', 'Reds', 'Blues', 'Picnic'.
  • How many dimensions does this plot visualize? (ähem proportional size?!)

8.6 Legends

  • Legends in the plotly R reference
  • How could you assure that the scales don’t change? ::: {.cell}
swiss$Catholic.cat <- cut(swiss$Catholic, 
                          breaks = c(-Inf, 30, 60, Inf), c("% Catholic: Low","% Catholic: Middle","% Catholic: High"))
p <- plot_ly(data = swiss, 
        x = ~Education, 
        y = ~Fertility,
        color = ~Catholic.cat, 
        colors = c("black", "yellow", "red"),
        size = ~Education, mode = "markers")

# LEGEND
p <- layout(p, 
            legend = list(
                          legendgroup="", # Sets legend group for trace; 
                          #Traces of same legend group hide/show at same time
                          font = list(family = "sans-serif", 
                                      size = 12, color = "#000"),
                          bgcolor = "#E2E2E2", bordercolor = "black",
                          borderwidth = 2, x = 0.9, y = 0.9,
                          #yanchor = "auto",
                          #xanchor = "auto",
                          traceorder = "reversed"
                          #orientation = "v",
))
p

:::

8.7 Annotations: Dynamic

  • Added with text = "...."
  • Without coordinates hoverinfo = "text"
  • Turn off hoverinfo: hoverinfo = "none" ::: {.cell}
swiss$Catholic.cat <- cut(swiss$Catholic, 
                          breaks = c(-Inf, 30, 60, Inf), 
                          c("% Catholic: Low","% Catholic: Middle","% Catholic: High"))
names <- rownames(swiss)
p <- plot_ly(data = swiss, 
        x = ~Education, 
        y = ~Fertility,
        color = ~Catholic.cat, 
        colors = c("black", "yellow", "red"),
        size = ~Catholic, mode = "markers",
        text = ~paste(names, ": ", swiss$Catholic, " %", sep=""),
        hoverinfo = "text")
p

:::

8.8 Annotations: Static

swiss$Catholic.cat <- cut(swiss$Catholic, 
                          breaks = c(-Inf, 30, 60, Inf), 
                          c("% Catholic: Low","% Catholic: Middle","% Catholic: High"))
p <- plot_ly(data = swiss, 
        x = ~Education, 
        y = ~Fertility,
        color = ~Catholic.cat, 
        colors = c("black", "yellow", "red"),
        size = ~Education, mode = "markers",
        text = ~paste(rownames(swiss)))

m <- swiss[swiss$Education>=30, ]
a <- list(
  x = m$Education,
  y = m$Fertility,
  text = rownames(m),
  xref = "x", # https://plot.ly/r/reference/#layout-annotations-xref
  yref = "y",
  showarrow = TRUE,
  arrowhead = 1,
  ax = 20, # https://plot.ly/r/reference/#layout-annotations-ax
  ay = -40
  )
p <- layout(p, annotations = a)
p

:::

8.9 Exporting/Saving Graphs

  • PNG: Easiest way is to use and download the plot as a png.
  • Printing in the browser is annoying
  • Margin problem.. cut online (e.g. https://pdfresizer.com/crop)
  • Now there is orca but it’s tedious to install…
p <- plot_ly(data = swiss, x = ~Catholic, y = ~Fertility, type = "scatter", mode = "markers")
getwd(...)
setwd(...)
export(p, file = "./generated-graphs/plotly.png") # PNG
htmlwidgets::saveWidget(p, "test.html") # HTML
# htmlwidget should be standalone
webshot::webshot("test.html", "test.pdf") # PDF -> Margin problem (cut!)
webshot::webshot("test.html", "test.png") # PNG -> Poorer Quality

8.10 Small multiples (1)

  • Christopher Scheiner (1611): changing configurations of sunspots over time (Friendly 2006)
  • subplot(..., nrows = 1, widths = NULL, heights = NULL, margin = 0.02, shareX = FALSE, shareY = FALSE, titleX = shareX, titleY = shareY, which_layout = "merge")
    • ?subplot
    • nrows: number of rows for laying out plots in a grid-like structure. Only used if no domain is already specified.
    • widths: relative width of each column on a 0-1 scale. By default all columns have an equal relative width.
    • heights: relative height of each row on a 0-1 scale. By default all rows have an equal relative height.
    • margin: either a single value or four values (all between 0 and 1). If four values are provided, the first is used as the left margin, the second is used as the right margin, the third is used as the top margin, and the fourth is used as the bottom margin. If a single value is provided, it will be used as all four margins.
    • shareX and shareY: Should the x-axis/yaxis be shared amongst the subplots?
    • titleX and titleY: Should x-axis/y-axis titles be retained?

8.11 Small multiples (2)

p1 <- plot_ly(data = swiss, x = ~Catholic, y = ~Fertility, 
              type = "scatter", mode = "markers", name = "Fertility ~ Catholic") %>%
  layout(xaxis = list(range = list(0,100)),
          yaxis = list(range = list(0,100)))
p2 <-  plot_ly(data = swiss, x = ~Education, y = ~Fertility, 
               type = "scatter", mode = "markers", name = "Fertility ~ Education") %>%
  layout(xaxis = list(range = list(0,100)),
          yaxis = list(range = list(0,100)))
p3 <-  plot_ly(data = swiss, x = ~Examination, y = ~Fertility, 
               type = "scatter", mode = "markers", name = "Fertility ~ Examination") %>%
  layout(xaxis = list(range = list(0,100)),
          yaxis = list(range = list(0,100)))
p4 <-  plot_ly(data = swiss, x = ~Agriculture, y = ~Fertility, 
               type = "scatter", mode = "markers", name = "Fertility ~ Agriculture") %>%
  layout(xaxis = list(range = list(0,100)),
          yaxis = list(range = list(0,100)))
p.sm <- subplot(p1, p2, p3, p4, nrows=2, shareX = FALSE, shareY = FALSE,
                titleX = T, titleY = T, margin = c(0.1,0.1,0.1,0.1))  %>%
  layout(showlegend = FALSE)
# export(p.sm, file = "./generated-graphs/smallmultiples.png")
p.sm

8.12 Exercise: Dynamic annotations and small multiples

  • Recreate the plot below (take the plot/code above as an example!) and save it both as PNG and as PDF.
  • Add additional information (e.g. size of dots according to Infant.Mortality)
  • If the dataset is too boring use your own data!

8.13 Client side linking

  • Source: https://plotly-r.com/client-side-linking.html
  • Client side linking: a particular approach to linking views known as graphical (database) queries using the R package plotly
    • Write R code to pose graphical queries that operate entirely client-side in a web browser (i.e., no special web server or callback to R is required)
  • highlight_key(): Pose queries
  • highlight(): trigger queries and visually render them
  • Potential problem: Data is on the client side!
data(txhousing, package = "ggplot2")

# declare `city` as the SQL 'query by' column
tx <- highlight_key(txhousing, ~city)

# initiate a plotly object
base <- plot_ly(tx, color = I("black")) %>% 
  group_by(city)

# create a time series of median house price
time_series <- base %>%
  group_by(city) %>%
  add_lines(x = ~date, y = ~median)
#time_series

highlight(
  time_series, 
  on = "plotly_click", 
  selectize = TRUE, 
  dynamic = TRUE, 
  persistent = TRUE
)