blockr.dag

library(blockr.dag)
library(blockr.core)
library(blockr.dock)

Basic Usage

Starting a DAG Board

We create an empty board from blockr.dock and serve it with the new_dag_extension() extension:

# Create an empty board.
serve(new_dock_board(extensions = new_dag_extension()))

Pin block positions

The board is the single source of truth for the DAG: nodes, edges, combos and their styling are always derived from it. The extension owns only board-independent view attributes, currently node positions, passed as positions (a named list keyed by block id) and persisted across save / restore. The auto-layout currently still computes final placement at cold start, so positions are not yet honored over it (a planned follow-up):

serve(
  new_dock_board(
    blocks = c(
      a = new_dataset_block("iris"),
      b = new_head_block()
    ),
    links = list(from = "a", to = "b", input = "data"),
    extensions = new_dag_extension(
      positions = list(
        a = list(x = 200, y = 150),
        b = list(x = 200, y = 350)
      )
    )
  )
)

Options

blockr.dag’s behaviour is tuned through a handful of R options(), set before serve(). They split into options owned by blockr.dag and options it inherits from g6R, the engine that renders and drives the DAG.

blockr.dag options

Option Default Description
blockr.dag.svg_renderer FALSE Render the DAG with g6R’s SVG renderer instead of the default canvas renderer. Canvas is the default because the SVG element reports offsetWidth == 0, which makes the underlying g-lite client/canvas coordinate scaling ignore the page zoom factor and desyncs hit-testing below 100% browser zoom (drops and port grabs silently fail). The SVG renderer keeps every element in the DOM, so it is mainly useful for shinytest2 end-to-end tests, which opt in via AppDriver$new(options = list(blockr.dag.svg_renderer = TRUE)).

g6R options

Because blockr.dag renders through g6R, several g6R options shape DAG behaviour. Set them alongside the board:

options(
  g6R.mode = "dev",
  g6R.directed_graph = TRUE,
  g6R.layout_on_data_change = TRUE
)

serve(new_dock_board(extensions = new_dag_extension()))
Option Default Description
g6R.mode "prod" "dev" surfaces in-app notifications (e.g. why an edge drop was rejected); "prod" keeps them silent. Use "dev" while building.
g6R.directed_graph FALSE Maintain parent/child (tree) relationships as edges and nodes are added or removed through the g6R proxy. A DAG is directed, so this is typically TRUE; g6R also auto-enables it when nodes declare children. The tree bookkeeping is only kept in sync when you mutate the graph through the proxy functions, not the raw G6 JS API.
g6R.layout_on_data_change FALSE Re-run the layout whenever the graph data changes (a block or edge is added/removed), so the DAG re-arranges itself. With FALSE, existing nodes keep their position and only new elements are placed.
g6R.preserve_elements_position FALSE Preserve node positions across redraws instead of recomputing them.

User Interface Elements

Keyboard Shortcuts

  • Shift + Drag: Create connections between blocks.
  • Ctrl + Backspace: Remove selected elements.
  • Alt/Option(Mac) + Drag: Multi-select elements.

Developer Guide

Where does blockr.dag fit in?

In the blockr ecosystem, blockr.dag is a high level extension that usually comes at the end of the stack. It depends on blockr.core for core functionalities and blockr.dock for the user interface. It also relies on external packages like g6R for rendering and interaction, as well as extra block packages like blockr.dplyr and blockr.ggplot for additional block types:

graph TD
    subgraph Core Layer
        blockr.core[blockr.core]
    end

    subgraph Extension Blocks
        blockr.dplyr[blockr.dplyr]
        blockr.ggplot[blockr.ggplot]
    end

    subgraph UI Layer
        blockr.dock[blockr.dock]
        blockr.dag[blockr.dag]
    end

    subgraph External Dependencies
        dockViewR[dockViewR]
        g6R[g6R]
    end

    blockr.core --> blockr.dock
    blockr.dock --> blockr.dag
    dockViewR --> blockr.dock
    g6R --> blockr.dag

    blockr.core --> blockr.dplyr
    blockr.core --> blockr.ggplot

    blockr.dplyr -.-> blockr.dock
    blockr.dplyr -.-> blockr.dag
    blockr.ggplot -.-> blockr.dock
    blockr.ggplot -.-> blockr.dag

Maintaining the DAG state

Server-side observers handle state synchronization between the block.core board and the g6R output. update and board are inherited from blockr.core:

update is composed of:

Category Operation Description
blocks add New blocks to add (blocks object)
mod Modified blocks (blocks object)
rm Deleted blocks (block IDS, character)
stacks add New stacks to add (stacks object)
mod Modified stacks (stacks object)
rm Deleted stacks (stacks IDS, character)
links add New links to add (links object)
mod Modified links (links object)
rm Deleted links (links IDS, character())

We listen to update() and apply the necessary changes to the g6R widget via its proxy functions:

# Update observer handles board changes
update_observer <- function(update, board, proxy) {
  observeEvent(update(), {
    upd <- update()

    if (length(upd$blocks$add)) {
      add_nodes(upd$blocks$add, board$board, proxy)
    }

    if (length(upd$stacks$mod)) {
      update_combos(upd$stacks$mod, board$board, proxy)
    }

    # Handle other update types...
  })
}

Extending the Package

Custom context menu and toolbar

You can learn more in the following vignettes:

Extension block callback

blockr.dock exposes a callback system to extend block functionality. You can learn more in the following vignette.