No-code data analysis and dashboards with the blockr ecosystem

Nicolas Bennett

2025-04-10

What is blockr about?

Blocks!

Blocks!

Blocks!

Demo

Analysis

How do different models of planes perform? What about Airbus vs. Boeing?

pln <- nycflights13::planes |>
  filter(seats >= 150) |>
  select(tailnum, manufacturer, model) |>
  mutate(manufacturer = sub(" INDUSTRIE$", "", manufacturer))

dat <- nycflights13::flights |>
  mutate(delay = -(arr_delay - dep_delay)) |>
  select(tailnum, delay) |>
  inner_join(pln, by = "tailnum")

dat <- dat |>
  summarize(count = n(), .by = c(manufacturer, model)) |>
  mutate(pos = rank(-count)) |>
  filter(pos <= 10) |>
  left_join(dat, by = c("manufacturer", "model"))

Endpoints

t.test(
  delay ~ manufacturer,
  data = dat
)

    Welch Two Sample t-test

data:  delay by manufacturer
t = -21.506, df = 58976, p-value < 2.2e-16
alternative hypothesis: true difference in means between group AIRBUS and group BOEING is not equal to 0
95 percent confidence interval:
 -3.146901 -2.621219
sample estimates:
mean in group AIRBUS mean in group BOEING 
             5.35713              8.24119 

Interactive blockr dashboard

Graph view with blocks to the side

Extensibility

A framework?

  • blockr.core objects/tools for creating blocks/links/stacks and managing a board
  • blockr.ui front end, extending the core functionality
  • Block packages such as blockr.dplyr

We provide 2 APIs: one for adding blocks and one for customizing/extending the front end.

Adding a new block (1/5)

  1. Specify a block constructor
new_lm_block <- function(response = character(), ...) {
  new_block(
    server = function(id, dat) {
      moduleServer(
        # ...
      )
    },
    ui = function(id) {
      tagList(
        # ...
      )
    },
    class = c("lm_block", "model_block"),
    ...
  )
}

Adding a new block (1/5)

  1. Specify a block constructor
new_lm_block <- function(response = character(), ...) {
  new_block(
    server = function(id, dat) {
      moduleServer(
        id,
        function(input, output, session) {

          resp <- reactiveVal(response)
          observeEvent(input$resp, resp(input$resp))

          cols <- reactive(colnames(dat()))
          observeEvent(
            cols(),
            updateSelectInput(session, "resp", "Response", cols(), resp())
          )

          list(
            expr = reactive(
              bquote(
                stats::lm(.(y) ~ ., data = dat),
                list(y = as.name(resp()))
              )
            ),
            state = list(response = resp)
          )
        }
      )
    },
    ui = function(id) {
      selectInput(NS(id, "resp"), "Response", response, response)
    },
    class = c("lm_block", "model_block"),
    ...
  )
}

Adding a new block (2/5)

  1. (Optionally) provide some generic implementations for display
block_ui.model_block <- function(id, x, ...) {
  verbatimTextOutput(NS(id, "result"))
}

block_output.model_block <- function(x, result, session) {
  renderPrint(result)
}

Adding a new block (3/5)

  1. (Optionally) preview the block in a standalone app
serve(new_lm_block(), dat = datasets::attitude)

Preview of an lm block

Adding a new block (4/5)

  1. Register the block for use in a dashboard
register_block(
  new_lm_block,
  name = "LM block",
  description = "Linear model block"
)

Adding a new block (5/5)

Our current (WIP) block extension packages:

Your own?

Extending the front-end (1/2)

A set of plugins (each a shiny module) can be user-supplied:

  • preserve_board: Serialization/deserialization
  • manage_*: Add/remove blocks, links and stacks
  • notify_user: Handling of notifications
  • generate_code: Code export
  • edit_*: Per block/stack options

Extending the front-end (2/2)

serve(
  new_board(
    c(a = new_dataset_block("mtcars"))
  )
)

serve(
  new_custom_board(
    c(a = new_dataset_block("mtcars"))
  )
)

Challenges

Limitations of the shiny API

  • Remove (#2439) or reorder (#4190) entries in a reactiveValues object: for variadic blocks (e.g. rbind) we need to be able to grow, shrink and reorder entries.
  • Cleanup of dynamically created modules (#2281): blocks (and stacks) are represented by modules which are dynamically added and removed.

In both cases we were able to come up with solutions (that circumvent the public API).

Outro

A collaborative effort

Joint work with David Granjon (cynkra GmbH),

with contributions by John Coene (The Y Company), Karma Tarap (Bristol-Myers Squibb) and Christoph Sax (cynkra GmbH),

funded by Bristol-Myers Squibb.

Further materials

Thanks for having me!

Backup

Interactive blockr dashboard

Dashboard builder with graph overview

Interactive blockr dashboard

Dashboard view

Extending the front-end (2/2)

serve(
  new_board(
    c(a = new_dataset_block("mtcars"))
  )
)

serve(
  new_custom_board(
    c(a = new_dataset_block("mtcars"))
  )
)

Extending the front-end (2/2)

serve(
  new_board(
    c(a = new_dataset_block("mtcars"))
  )
)

serve(
  new_custom_board(
    c(a = new_dataset_block("mtcars"))
  )
)

Extending the front-end (2/2)

serve(
  new_board(
    c(a = new_dataset_block("mtcars"))
  )
)

serve(
  new_custom_board(
    c(a = new_dataset_block("mtcars"))
  )
)