dockViewR

Dynamically add panel

You can add panels to an existing dock with add_panel() which expects a panel() object.

Toggle code
library(dockViewR)
library(shiny)
library(bslib)
library(visNetwork)

nodes <- data.frame(id = 1:3)
edges <- data.frame(from = c(1, 2), to = c(1, 3))

ui <- page_fillable(
  actionButton("btn", "add Panel"),
  dockViewOutput("dock")
)

server <- function(input, output, session) {
  exportTestValues(
    panel_ids = get_panels_ids("dock"),
    active_group = get_active_group("dock"),
    grid = get_grid("dock")
  )

  output$dock <- renderDockView({
    dock_view(
      panels = list(
        panel(
          id = "1",
          title = "Panel 1",
          content = tagList(
            sliderInput(
              "obs",
              "Number of observations:",
              min = 0,
              max = 1000,
              value = 500
            ),
            plotOutput("distPlot")
          )
        ),
        panel(
          id = "2",
          title = "Panel 2",
          content = tagList(
            visNetworkOutput("network")
          ),
          position = list(
            referencePanel = "1",
            direction = "right"
          ),
          minimumWidth = 500
        ),
        panel(
          id = "3",
          title = "Panel 3",
          content = tagList(
            selectInput(
              "variable",
              "Variable:",
              c("Cylinders" = "cyl", "Transmission" = "am", "Gears" = "gear")
            ),
            tableOutput("data")
          ),
          position = list(
            referencePanel = "2",
            direction = "below"
          )
        )
      ),
      theme = "replit"
    )
  })

  output$distPlot <- renderPlot({
    req(input$obs)
    hist(rnorm(input$obs))
  })

  output$network <- renderVisNetwork({
    visNetwork(nodes, edges, width = "100%")
  })

  output$data <- renderTable(
    {
      mtcars[, c("mpg", input$variable), drop = FALSE]
    },
    rownames = TRUE
  )

  output$plot <- renderPlot({
    dist <- switch(
      input$dist,
      norm = rnorm,
      unif = runif,
      lnorm = rlnorm,
      exp = rexp,
      rnorm
    )

    hist(dist(500))
  })

  observeEvent(input$btn, {
    pnl <- panel(
      id = "new_1",
      title = "Dynamic panel",
      content = tagList(
        radioButtons(
          "dist",
          "Distribution type:",
          c(
            "Normal" = "norm",
            "Uniform" = "unif",
            "Log-normal" = "lnorm",
            "Exponential" = "exp"
          )
        ),
        plotOutput("plot")
      ),
      position = list(
        referencePanel = "1",
        direction = "within"
      )
    )
    add_panel(
      "dock",
      pnl
    )
  })
}

shinyApp(ui, server)


Dynamically remove panels

You can remove panels from an existing dock with remove_panel() which expects the id of the panel to remove, in addition to the dock id.

Toggle code
library(dockViewR)
library(shiny)
library(bslib)
library(visNetwork)

nodes <- data.frame(id = 1:3)
edges <- data.frame(from = c(1, 2), to = c(1, 3))

ui <- page_fillable(
  selectInput("selinp", "Panel ids", choices = NULL),
  actionButton("btn", "remove Panel"),
  dockViewOutput("dock")
)

server <- function(input, output, session) {
  exportTestValues(
    panel_ids = get_panels_ids("dock"),
    active_group = get_active_group("dock"),
    grid = get_grid("dock")
  )
  observeEvent(get_panels_ids("dock"), {
    updateSelectInput(
      session = session,
      inputId = "selinp",
      choices = get_panels_ids("dock")
    )
  })

  output$dock <- renderDockView({
    dock_view(
      panels = list(
        panel(
          id = "1",
          title = "Panel 1",
          content = tagList(
            sliderInput(
              "obs",
              "Number of observations:",
              min = 0,
              max = 1000,
              value = 500
            ),
            plotOutput("distPlot")
          )
        ),
        panel(
          id = "2",
          title = "Panel 2",
          content = tagList(
            visNetworkOutput("network")
          ),
          position = list(
            referencePanel = "1",
            direction = "right"
          ),
          minimumWidth = 500
        ),
        panel(
          id = "3",
          title = "Panel 3",
          content = tagList(
            selectInput(
              "variable",
              "Variable:",
              c("Cylinders" = "cyl", "Transmission" = "am", "Gears" = "gear")
            ),
            tableOutput("data")
          ),
          position = list(
            referencePanel = "2",
            direction = "below"
          )
        )
      ),
      theme = "replit"
    )
  })

  output$distPlot <- renderPlot({
    req(input$obs)
    hist(rnorm(input$obs))
  })

  output$network <- renderVisNetwork({
    visNetwork(nodes, edges, width = "100%")
  })

  output$data <- renderTable(
    {
      mtcars[, c("mpg", input$variable), drop = FALSE]
    },
    rownames = TRUE
  )

  output$plot <- renderPlot({
    dist <- switch(
      input$dist,
      norm = rnorm,
      unif = runif,
      lnorm = rlnorm,
      exp = rexp,
      rnorm
    )

    hist(dist(500))
  })

  observeEvent(input$btn, {
    req(input$selinp)
    remove_panel("dock", input$selinp)
  })
}

shinyApp(ui, server)


Dynamically move panel

You can move individual panels in the dock with move_panel() which expects:

Toggle code
library(shiny)
library(bslib)
library(dockViewR)

ui <- fluidPage(
  h1("Panels within the same group"),
  actionButton("move", "Move Panel 1"),
  dockViewOutput("dock"),
  h1("Panels with different groups"),
  actionButton("move2", "Move Panel 1"),
  dockViewOutput("dock2"),
)

server <- function(input, output, session) {
  exportTestValues(
    panel_ids = get_panels_ids("dock"),
    active_group = get_active_group("dock"),
    grid = get_grid("dock")
  )

  output$dock <- renderDockView({
    dock_view(
      panels = list(
        panel(
          id = "1",
          title = "Panel 1",
          content = tagList(
            sliderInput(
              "obs",
              "Number of observations:",
              min = 0,
              max = 1000,
              value = 500
            ),
            plotOutput("distPlot")
          )
        ),
        panel(
          id = "2",
          title = "Panel 2",
          content = tagList(
            selectInput(
              "variable",
              "Variable:",
              c("Cylinders" = "cyl", "Transmission" = "am", "Gears" = "gear")
            ),
            tableOutput("data")
          ),
        ),
        panel(
          id = "3",
          title = "Panel 3",
          content = h1("Panel 3")
        )
      ),
      theme = "light-spaced"
    )
  })

  output$dock2 <- renderDockView({
    dock_view(
      panels = list(
        panel(
          id = "1",
          title = "Panel 1",
          content = "Panel 1"
        ),
        panel(
          id = "2",
          title = "Panel 2",
          content = "Panel 2",
          position = list(
            referencePanel = "1",
            direction = "within"
          )
        ),
        panel(
          id = "3",
          title = "Panel 3",
          content = h1("Panel 3"),
          position = list(
            referencePanel = "1",
            direction = "right"
          )
        )
      ),
      theme = "light-spaced"
    )
  })

  output$distPlot <- renderPlot({
    req(input$obs)
    hist(rnorm(input$obs))
  })
  output$data <- renderTable(
    {
      mtcars[, c("mpg", input$variable), drop = FALSE]
    },
    rownames = TRUE
  )

  observeEvent(input$move, {
    move_panel(
      "dock",
      id = "1",
      index = 3
    )
  })

  observeEvent(input$move2, {
    move_panel(
      "dock2",
      id = "1",
      group = "3",
      position = "bottom"
    )
  })
}

shinyApp(ui, server)


Dynamically move groups

You can move groups of panels using 2 different APIs described below.

Group point of view

To move a group of panel(s), move_group() works by selecting the group source id, that is from, and the group target id, to. Position is relative to the to.

Toggle code
library(shiny)
library(dockViewR)

ui <- fluidPage(
  actionButton(
    "move",
    "Move Group with group-id 1 to the righ of group with group-id 2"
  ),
  dockViewOutput("dock"),
)

server <- function(input, output, session) {
  exportTestValues(
    panel_ids = get_panels_ids("dock"),
    active_group = get_active_group("dock"),
    grid = get_grid("dock")
  )
  output$dock <- renderDockView({
    dock_view(
      panels = list(
        panel(
          id = "1",
          title = "Panel 1",
          content = "Panel 1"
        ),
        panel(
          id = "2",
          title = "Panel 2",
          content = "Panel 2",
          position = list(
            referencePanel = "1",
            direction = "within"
          )
        ),
        panel(
          id = "3",
          title = "Panel 3",
          content = h1("Panel 3"),
          position = list(
            referencePanel = "1",
            direction = "right"
          )
        ),
        panel(
          id = "4",
          title = "Panel 4",
          content = h1("Panel 4"),
          position = list(
            referencePanel = "3",
            direction = "within"
          )
        ),
        panel(
          id = "5",
          title = "Panel 5",
          content = h1("Panel 5"),
          position = list(
            referencePanel = "4",
            direction = "right"
          )
        ),
        panel(
          id = "6",
          title = "Panel 6",
          content = h1("Panel 6"),
          position = list(
            referencePanel = "5",
            direction = "within"
          )
        )
      ),
      theme = "light-spaced"
    )
  })

  observeEvent(input$move, {
    move_group(
      "dock",
      from = "1",
      to = "2",
      position = "right"
    )
  })
}

shinyApp(ui, server)


Panel point of view

Another approach is possible with move_group2, which works from the point of view of a panel. This means given from which the panel id, {dockViewR} is able to find the group where it belongs to. Same for the to. This way you don’t have to worry about group ids, which are implicit.

Toggle code
library(shiny)
library(dockViewR)

ui <- fluidPage(
  actionButton(
    "move",
    "Move Group that contains Panel 1 to the right of group 
    that contains Panel 3"
  ),
  dockViewOutput("dock"),
)

server <- function(input, output, session) {
  exportTestValues(
    panel_ids = get_panels_ids("dock"),
    active_group = get_active_group("dock"),
    grid = get_grid("dock")
  )
  output$dock <- renderDockView({
    dock_view(
      panels = list(
        panel(
          id = "1",
          title = "Panel 1",
          content = "Panel 1"
        ),
        panel(
          id = "2",
          title = "Panel 2",
          content = "Panel 2",
          position = list(
            referencePanel = "1",
            direction = "within"
          )
        ),
        panel(
          id = "3",
          title = "Panel 3",
          content = h1("Panel 3"),
          position = list(
            referencePanel = "1",
            direction = "right"
          )
        ),
        panel(
          id = "4",
          title = "Panel 4",
          content = h1("Panel 4"),
          position = list(
            referencePanel = "3",
            direction = "within"
          )
        ),
        panel(
          id = "5",
          title = "Panel 5",
          content = h1("Panel 5"),
          position = list(
            referencePanel = "4",
            direction = "right"
          )
        ),
        panel(
          id = "6",
          title = "Panel 6",
          content = h1("Panel 6"),
          position = list(
            referencePanel = "5",
            direction = "within"
          )
        )
      ),
      theme = "light-spaced"
    )
  })

  observeEvent(input$move, {
    move_group2(
      "dock",
      from = "1",
      to = "3",
      position = "right"
    )
  })
}

shinyApp(ui, server)


Get the state of the dock

You can access the state of the dock which can return something like:

dockViewR:::test_dock
#> $grid
#> $grid$root
#> $grid$root$type
#> [1] "branch"
#> 
#> $grid$root$data
#> $grid$root$data[[1]]
#> $grid$root$data[[1]]$type
#> [1] "leaf"
#> 
#> $grid$root$data[[1]]$data
#> $grid$root$data[[1]]$data$views
#> $grid$root$data[[1]]$data$views[[1]]
#> [1] "test"
#> 
#> $grid$root$data[[1]]$data$views[[2]]
#> [1] "2"
#> 
#> 
#> $grid$root$data[[1]]$data$activeView
#> [1] "2"
#> 
#> $grid$root$data[[1]]$data$id
#> [1] "1"
#> 
#> 
#> $grid$root$data[[1]]$size
#> [1] 95
#> 
#> 
#> $grid$root$data[[2]]
#> $grid$root$data[[2]]$type
#> [1] "leaf"
#> 
#> $grid$root$data[[2]]$data
#> $grid$root$data[[2]]$data$views
#> $grid$root$data[[2]]$data$views[[1]]
#> [1] "3"
#> 
#> 
#> $grid$root$data[[2]]$data$activeView
#> [1] "3"
#> 
#> $grid$root$data[[2]]$data$id
#> [1] "2"
#> 
#> 
#> $grid$root$data[[2]]$size
#> [1] 95
#> 
#> 
#> 
#> $grid$root$size
#> [1] 0
#> 
#> 
#> $grid$width
#> [1] 0
#> 
#> $grid$height
#> [1] 0
#> 
#> $grid$orientation
#> [1] "HORIZONTAL"
#> 
#> 
#> $panels
#> $panels$`2`
#> $panels$`2`$id
#> [1] "2"
#> 
#> $panels$`2`$contentComponent
#> [1] "default"
#> 
#> $panels$`2`$params
#> $panels$`2`$params$content
#> $panels$`2`$params$content$head
#> [1] ""
#> 
#> $panels$`2`$params$content$singletons
#> list()
#> 
#> $panels$`2`$params$content$dependencies
#> list()
#> 
#> $panels$`2`$params$content$html
#> [1] "Panel 2"
#> 
#> 
#> $panels$`2`$params$id
#> [1] "2"
#> 
#> 
#> $panels$`2`$title
#> [1] "Panel 2"
#> 
#> 
#> $panels$`3`
#> $panels$`3`$id
#> [1] "3"
#> 
#> $panels$`3`$contentComponent
#> [1] "default"
#> 
#> $panels$`3`$params
#> $panels$`3`$params$content
#> $panels$`3`$params$content$head
#> [1] ""
#> 
#> $panels$`3`$params$content$singletons
#> list()
#> 
#> $panels$`3`$params$content$dependencies
#> list()
#> 
#> $panels$`3`$params$content$html
#> [1] "<h1>Panel 3</h1>"
#> 
#> 
#> $panels$`3`$params$id
#> [1] "3"
#> 
#> 
#> $panels$`3`$title
#> [1] "Panel 3"
#> 
#> 
#> $panels$test
#> $panels$test$id
#> [1] "test"
#> 
#> $panels$test$contentComponent
#> [1] "default"
#> 
#> $panels$test$params
#> $panels$test$params$content
#> $panels$test$params$content$head
#> [1] ""
#> 
#> $panels$test$params$content$singletons
#> list()
#> 
#> $panels$test$params$content$dependencies
#> list()
#> 
#> $panels$test$params$content$html
#> [1] "Panel 1"
#> 
#> 
#> $panels$test$params$id
#> [1] "test"
#> 
#> 
#> $panels$test$title
#> [1] "Panel 1"
#> 
#> 
#> 
#> $activeGroup
#> [1] "2"

The dock state is a deeply nested list:

str(dockViewR:::test_dock)
#> List of 3
#>  $ grid       :List of 4
#>   ..$ root       :List of 3
#>   .. ..$ type: chr "branch"
#>   .. ..$ data:List of 2
#>   .. .. ..$ :List of 3
#>   .. .. .. ..$ type: chr "leaf"
#>   .. .. .. ..$ data:List of 3
#>   .. .. .. .. ..$ views     :List of 2
#>   .. .. .. .. .. ..$ : chr "test"
#>   .. .. .. .. .. ..$ : chr "2"
#>   .. .. .. .. ..$ activeView: chr "2"
#>   .. .. .. .. ..$ id        : chr "1"
#>   .. .. .. ..$ size: int 95
#>   .. .. ..$ :List of 3
#>   .. .. .. ..$ type: chr "leaf"
#>   .. .. .. ..$ data:List of 3
#>   .. .. .. .. ..$ views     :List of 1
#>   .. .. .. .. .. ..$ : chr "3"
#>   .. .. .. .. ..$ activeView: chr "3"
#>   .. .. .. .. ..$ id        : chr "2"
#>   .. .. .. ..$ size: int 95
#>   .. ..$ size: int 0
#>   ..$ width      : int 0
#>   ..$ height     : int 0
#>   ..$ orientation: chr "HORIZONTAL"
#>  $ panels     :List of 3
#>   ..$ 2   :List of 4
#>   .. ..$ id              : chr "2"
#>   .. ..$ contentComponent: chr "default"
#>   .. ..$ params          :List of 2
#>   .. .. ..$ content:List of 4
#>   .. .. .. ..$ head        : chr ""
#>   .. .. .. ..$ singletons  : list()
#>   .. .. .. ..$ dependencies: list()
#>   .. .. .. ..$ html        : chr "Panel 2"
#>   .. .. ..$ id     : chr "2"
#>   .. ..$ title           : chr "Panel 2"
#>   ..$ 3   :List of 4
#>   .. ..$ id              : chr "3"
#>   .. ..$ contentComponent: chr "default"
#>   .. ..$ params          :List of 2
#>   .. .. ..$ content:List of 4
#>   .. .. .. ..$ head        : chr ""
#>   .. .. .. ..$ singletons  : list()
#>   .. .. .. ..$ dependencies: list()
#>   .. .. .. ..$ html        : chr "<h1>Panel 3</h1>"
#>   .. .. ..$ id     : chr "3"
#>   .. ..$ title           : chr "Panel 3"
#>   ..$ test:List of 4
#>   .. ..$ id              : chr "test"
#>   .. ..$ contentComponent: chr "default"
#>   .. ..$ params          :List of 2
#>   .. .. ..$ content:List of 4
#>   .. .. .. ..$ head        : chr ""
#>   .. .. .. ..$ singletons  : list()
#>   .. .. .. ..$ dependencies: list()
#>   .. .. .. ..$ html        : chr "Panel 1"
#>   .. .. ..$ id     : chr "test"
#>   .. ..$ title           : chr "Panel 1"
#>  $ activeGroup: chr "2"

On the top level it has 3 elements:

Within the Shiny server function, on can access the state of the dock with get_dock(), passing the dock id (since the app may have multiple docks).

Each other function allows to deep dive into the returned value of get_dock():

save_dock() and restore_dock() are used for their side effect to allow to respectively serialise and restore a dock object, as shown in the following demonstration app.

Each time a panel moves, or a group is maximized, the dock state is updated.

Toggle code
library(shiny)
library(bslib)
library(dockViewR)

ui <- fluidPage(
  h1("Serialise dock state"),
  div(
    class = "d-flex justify-content-center",
    actionButton("save", "Save layout"),
    actionButton("restore", "Restore saved layout"),
    selectInput("states", "Select a state", NULL)
  ),
  dockViewOutput("dock")
)

server <- function(input, output, session) {
  dock_states <- reactiveVal(NULL)

  observeEvent(
    req(input$dock_state),
    {
      move_panel("dock", id = "test", group = "3", position = "top")
    },
    once = TRUE
  )

  observeEvent(input$save, {
    save_dock("dock")
  })

  observeEvent(req(input$dock_state), {
    states <- c(dock_states(), list(input$dock_state))
    dock_states(setNames(states, seq_along(states)))
  })

  exportTestValues(
    n_states = length(dock_states()),
    panel_ids = get_panels_ids("dock"),
    active_group = get_active_group("dock"),
    grid = get_grid("dock")
  )

  observeEvent(dock_states(), {
    updateSelectInput(session, "states", choices = names(dock_states()))
  })

  observeEvent(input$restore, {
    restore_dock("dock", dock_states()[[input$states]])
  })

  output$dock <- renderDockView({
    dock_view(
      panels = list(
        panel(
          id = "test",
          title = "Panel 1",
          content = tagList(
            sliderInput(
              "obs",
              "Number of observations:",
              min = 0,
              max = 1000,
              value = 500
            ),
            plotOutput("distPlot")
          )
        ),
        panel(
          id = 2,
          title = "Panel 2",
          content = "Panel 2",
          position = list(
            referencePanel = "test",
            direction = "within"
          )
        ),
        panel(
          id = 3,
          title = "Panel 3",
          content = h1("Panel 3"),
          position = list(
            referencePanel = "test",
            direction = "right"
          )
        )
      ),
      theme = "light-spaced"
    )
  })

  output$distPlot <- renderPlot({
    req(input$obs)
    hist(rnorm(input$obs))
  })
}

shinyApp(ui, server)


Replace panel content

You may have noticed that you can add panels on the fly by using the + icon next to the panel tab. This panel has a unique id given on the fly and you can’t know it when you start the app. Using the dock state, you can find this new id and replace the panel content with shiny::insertUI() and shiny::removeUI(), as shown below. In brief, the expected selector would be something like #<DOCK_ID>-<PANEL_ID > * (with multiple = TRUE to remove elements).

Toggle code
library(dockViewR)
library(shiny)
library(bslib)

ui <- page_fillable(
  div(
    class = "d-flex justify-content-center",
    actionButton("insert", "Insert inside panel"),
    selectInput("selinp", "Panel ids", choices = NULL)
  ),
  dockViewOutput("dock")
)

server <- function(input, output, session) {
  exportTestValues(
    n_panels = length(get_panels_ids("dock"))
  )

  observeEvent(get_panels_ids("dock"), {
    updateSelectInput(
      session = session,
      inputId = "selinp",
      choices = get_panels_ids("dock")
    )
  })

  output$dock <- renderDockView({
    dock_view(
      panels = list(
        panel(
          id = "1",
          title = "Panel 1",
          content = tagList(
            sliderInput(
              "obs",
              "Number of observations:",
              min = 0,
              max = 1000,
              value = 500
            ),
            plotOutput("distPlot")
          )
        )
      ),
      theme = "replit"
    )
  })

  output$distPlot <- renderPlot({
    req(input$obs)
    hist(rnorm(input$obs))
  })

  output$plot <- renderPlot({
    dist <- switch(
      input$dist,
      norm = rnorm,
      unif = runif,
      lnorm = rlnorm,
      exp = rexp,
      rnorm
    )

    hist(dist(500))
  })

  observeEvent(input$insert, {
    removeUI(
      selector = sprintf("#dock-%s > *", input$selinp),
      multiple = TRUE
    )
    insertUI(
      selector = sprintf("#dock-%s", input$selinp),
      where = "beforeEnd",
      ui = tagList(
        radioButtons(
          "dist",
          "Distribution type:",
          c(
            "Normal" = "norm",
            "Uniform" = "unif",
            "Log-normal" = "lnorm",
            "Exponential" = "exp"
          )
        ),
        plotOutput("plot")
      )
    )
  })
}

shinyApp(ui, server)