Quarto & Plotly

R: Prepare data

library(tidyverse)
data <- palmerpenguins::penguins %>% 
  mutate(across(where(is.factor), as.character)) %>% 
  filter(!is.na(sex))
data %>% 
  ggplot(aes(x = sex, group = island, fill = island)) +
    geom_bar(position = position_dodge())

head(data)
# A tibble: 6 × 8
  species island    bill_length_mm bill_depth_mm flipper_l…¹ body_…² sex    year
  <chr>   <chr>              <dbl>         <dbl>       <int>   <int> <chr> <int>
1 Adelie  Torgersen           39.1          18.7         181    3750 male   2007
2 Adelie  Torgersen           39.5          17.4         186    3800 fema…  2007
3 Adelie  Torgersen           40.3          18           195    3250 fema…  2007
4 Adelie  Torgersen           36.7          19.3         193    3450 fema…  2007
5 Adelie  Torgersen           39.3          20.6         190    3650 male   2007
6 Adelie  Torgersen           38.9          17.8         181    3625 fema…  2007
# … with abbreviated variable names ¹​flipper_length_mm, ²​body_mass_g

OJS: Expose data for OJS

ojs_define(data = transpose(data))

d3 is needed for grouping / aggregation

d3 = require("d3-array")
Plotly = require("https://cdn.plot.ly/plotly-2.16.1.min.js")
Inputs.table(data)

Aggregate (with JS / D3)

Goal: reduce data to group counts with JavaScript
This way we preserve runtime reactivity and don’t need to rely on static R inputs

Group by Species & Sex, Count Totals (length)

data_aggregated = d3
  .flatRollup(
    data,
    (facet) => facet.length,  // index: 2  
    (row) => row.island,      // index: 0
    (row) => row.sex          // index: 1
  )
  // abstraction / parametarisation for Plotly (x,y, name) here
  // .map((entry) => ({ island: entry[0], sex: entry[1], n: entry[2] }))
  .map((entry) => ({ name: entry[0], x: entry[1], y: entry[2] }))
  .sort((a,b) => a.name.localeCompare(b.name)) // to put facet names in A-Z order
data_grouped = d3.group(data_aggregated, (d) => d.name)

Facet Result Template

Create a template for an object for a single facet (~ trace)

toVars = ["x", "y", "name", "type"]
resultObject = ({})
toVars.forEach((variable) => (resultObject[variable] = []))
// the manual mapping is only necessary if you don't abstract during aggregation
fromVars = [
  {from: "sex", to: "x"},
  {from: "n", to: "y" }
]

Aggregate Facets

result = [...data_grouped.entries()].map((trace) => {
  const traceObj = JSON.parse(JSON.stringify(resultObject)); // copy the Object template
  traceObj.name = trace[0]; // facet's name / Map's key (here: island)
  traceObj.type = "bar"; // TODO: make global param
  traceObj.text = trace[1].map((entry) => `n: ${entry.y}`); // TODO: make n a param
  trace[1].forEach((entry) => {
    // fromVars.forEach((mapping) => {
    //   traceObj[mapping.to].push(entry[mapping.from]);
    ["x", "y"].forEach((mapping) => {
      traceObj[mapping].push(entry[mapping]);
    });
  });
  return traceObj;
})

Plot data with Plotly

Plot Options

options = ({
  title: "Count of Penguins by Gender & Island",
  barmode: doStack ? "stack" : "group" // "group", "stack", "relative"
})

Reactive Stack Toogle

Plotly Plot

Plotly.newPlot("plot-canvas", result, options)