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:
- The dock id.
- The panel id: can be a string or numeric value.
- The position. If left NULL, the panel is moved to the latest index. Otherwise choose one of
"left", "right", "top", "bottom", "center"
. -
group
: id of the panel that belongs to another group. The panel will be moved relative to the second group, depending on the position parameter. If left NULL, it is added on the right side. - index: If panels belong to the same group, you can use index to move the target panel at the desired position. When group is left NULL, index must be passed and cannot exceed the total number of panels or be negative.
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:
- grid: a list representing the dock layout.
-
panels: a list having the same structure as
panel()
composing the dock. - activeGroup: the current active group (a string).
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()
:
-
get_panels()
returns the panels element ofget_dock()
.-
get_panels_ids()
returns a character vector containing all panel ids fromget_panels()
.
-
-
get_active_group()
extracts the activeGroup component ofget_dock()
as a string. -
get_grid()
returns the grid element ofget_dock()
which is a list. -get_groups()
returns a list of panel groups fromget_grid()
.-
get_groups_ids()
returns a character vector of groups ids fromget_groups()
. -
get_groups_panels()
returns a list of character vector containing the ids of each panel within each group.
-
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(
addTab = list(enable = TRUE),
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)