You can add panels to an existing dock with add_panel()
which expects a panel()
object.
library(dockViewR)
library(shiny)
library(bslib)
library(visNetwork)
<- data.frame(id = 1:3)
nodes <- data.frame(from = c(1, 2), to = c(1, 3))
edges
<- page_fillable(
ui actionButton("btn", "add Panel"),
dockViewOutput("dock")
)
<- function(input, output, session) {
server exportTestValues(
panel_ids = get_panels_ids("dock"),
active_group = get_active_group("dock"),
grid = get_grid("dock")
)
$dock <- renderDockView({
outputdock_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"
)
})
$distPlot <- renderPlot({
outputreq(input$obs)
hist(rnorm(input$obs))
})
$network <- renderVisNetwork({
outputvisNetwork(nodes, edges, width = "100%")
})
$data <- renderTable(
output
{c("mpg", input$variable), drop = FALSE]
mtcars[,
},rownames = TRUE
)
$plot <- renderPlot({
output<- switch(
dist $dist,
inputnorm = rnorm,
unif = runif,
lnorm = rlnorm,
exp = rexp,
rnorm
)
hist(dist(500))
})
observeEvent(input$btn, {
<- panel(
pnl 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)
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
.
library(dockViewR)
library(shiny)
library(bslib)
library(visNetwork)
<- data.frame(id = 1:3)
nodes <- data.frame(from = c(1, 2), to = c(1, 3))
edges
<- page_fillable(
ui selectInput("selinp", "Panel ids", choices = NULL),
actionButton("btn", "remove Panel"),
dockViewOutput("dock")
)
<- function(input, output, session) {
server 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")
)
})
$dock <- renderDockView({
outputdock_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"
)
})
$distPlot <- renderPlot({
outputreq(input$obs)
hist(rnorm(input$obs))
})
$network <- renderVisNetwork({
outputvisNetwork(nodes, edges, width = "100%")
})
$data <- renderTable(
output
{c("mpg", input$variable), drop = FALSE]
mtcars[,
},rownames = TRUE
)
$plot <- renderPlot({
output<- switch(
dist $dist,
inputnorm = 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)
You can move individual panels in the dock with move_panel()
which expects:
"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.library(shiny)
library(bslib)
library(dockViewR)
<- fluidPage(
ui 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"),
)
<- function(input, output, session) {
server exportTestValues(
panel_ids = get_panels_ids("dock"),
active_group = get_active_group("dock"),
grid = get_grid("dock")
)
$dock <- renderDockView({
outputdock_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"
)
})
$dock2 <- renderDockView({
outputdock_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"
)
})
$distPlot <- renderPlot({
outputreq(input$obs)
hist(rnorm(input$obs))
})$data <- renderTable(
output
{c("mpg", input$variable), drop = FALSE]
mtcars[,
},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)
You can move groups of panels using 2 different APIs described below.
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
.
library(shiny)
library(dockViewR)
<- fluidPage(
ui actionButton(
"move",
"Move Group with group-id 1 to the righ of group with group-id 2"
),dockViewOutput("dock"),
)
<- function(input, output, session) {
server exportTestValues(
panel_ids = get_panels_ids("dock"),
active_group = get_active_group("dock"),
grid = get_grid("dock")
)$dock <- renderDockView({
outputdock_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)
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.
library(shiny)
library(dockViewR)
<- fluidPage(
ui actionButton(
"move",
"Move Group that contains Panel 1 to the right of group
that contains Panel 3"
),dockViewOutput("dock"),
)
<- function(input, output, session) {
server exportTestValues(
panel_ids = get_panels_ids("dock"),
active_group = get_active_group("dock"),
grid = get_grid("dock")
)$dock <- renderDockView({
outputdock_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)
You can access the state of the dock which can return something like:
:::test_dock
dockViewR#> $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:
panel()
composing the dock.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 of get_dock()
.
get_panels_ids()
returns a character vector containing all panel ids from get_panels()
.get_active_group()
extracts the activeGroup component of get_dock()
as a string.get_grid()
returns the grid element of get_dock()
which is a list. -get_groups()
returns a list of panel groups from get_grid()
.
get_groups_ids()
returns a character vector of groups ids from get_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.
library(shiny)
library(bslib)
library(dockViewR)
<- fluidPage(
ui 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")
)
<- function(input, output, session) {
server <- reactiveVal(NULL)
dock_states
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), {
<- c(dock_states(), list(input$dock_state))
states 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]])
})
$dock <- renderDockView({
outputdock_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"
)
})
$distPlot <- renderPlot({
outputreq(input$obs)
hist(rnorm(input$obs))
})
}
shinyApp(ui, server)
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).
library(dockViewR)
library(shiny)
library(bslib)
<- page_fillable(
ui div(
class = "d-flex justify-content-center",
actionButton("insert", "Insert inside panel"),
selectInput("selinp", "Panel ids", choices = NULL)
),dockViewOutput("dock")
)
<- function(input, output, session) {
server exportTestValues(
n_panels = length(get_panels_ids("dock"))
)
observeEvent(get_panels_ids("dock"), {
updateSelectInput(
session = session,
inputId = "selinp",
choices = get_panels_ids("dock")
)
})
$dock <- renderDockView({
outputdock_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"
)
})
$distPlot <- renderPlot({
outputreq(input$obs)
hist(rnorm(input$obs))
})
$plot <- renderPlot({
output<- switch(
dist $dist,
inputnorm = 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)