This function sets up server-side infrastructure to support draggable and resizable overlays on a plot. This may be useful in applications where users need to define regions on the plot for further input or processing. Currently, the overlays are only designed to move along the x axis of the plot.
Arguments
- outputId
The ID of the plot output (as used in
overlayPlotOutput()
).- nrect
Number of overlay rectangles to support.
- width
Optional default overlay width in plot coordinates. If
NULL
(default), set to 10% of the plot width.- snap
Function to "snap" overlay coordinates to a grid, or
"none"
(default) for no snapping. See details for how to specify the snap function.- colours
A function to assign custom colours to the overlays. Should be a function that takes a single integer (the number of overlays) and returns colours in hexadecimal notation (e.g. "#FF0000"). Do not provide opacity here as a fourth channel; use the
opacity
argument instead.- opacity
Numeric value (0 to 1) indicating overlay transparency.
- icon
A Shiny icon to show the dropdown menu.
- stagger
Vertical offset between stacked overlays, as a proportion of height.
- style
Named list of character vectors with additional CSS styling attributes for the overlays. If an element is named "background-color" then this will override the
colours
andopacity
arguments. Vectors are recycled to lengthnrect
.- debug
If
TRUE
, prints changes to input values to the console for debugging purposes.
Value
A shiny::reactiveValues()
object with the following named fields:
- n
Number of overlays (read-only).
- active
Logical vector of length
n
; indicates which overlays are active.- show
Logical vector; controls whether overlays are visible.
- editing
Index of the overlay currently being edited via the dropdown menu, if any;
NA
otherwise (read-only).- last
Index of the most recently added overlay (read-only).
- snap
Coordinate snapping function.
- px, pw
Numeric vector; overlay x-position and width in pixels (see note).
- py, ph
Numeric vector; overlay y-position and height in pixels (read-only).
- cx0, cx1
Numeric vector; overlay x-bounds in plot coordinates (see note).
- label
Character vector of labels shown at the top of each overlay.
- outputId
The output ID of the plot display area (read-only).
- bound_cx, bound_cw
x-position and width of the bounding area in plot coordinates (read-only).
- bound_px, bound_pw
x-position and width of the bounding area in pixels (read-only).
- bound_py, bound_ph
y-position and height of the bounding area in pixels (read-only).
- stagger
Amount of vertical staggering, as proportion of height.
- style
Named list of character vectors; additional styling for rectangular overlays.
- update_cx(i)
Function to update
cx0
/cx1
frompx
/pw
for overlaysi
(see note).- update_px(i)
Function to update
px
/pw
fromcx0
/cx1
for overlaysi
(see note).
Note: Fields marked "read-only" above should not be changed. Other fields can
be changed in your reactive code and this will modify the overlays and their
properties. The fields px
and pw
which specify the pixel coordinates of
each overlay can be modified, but any modifications should be placed in a
shiny::isolate()
call, with a call to ov$update_cx(i)
at the end to
update cx0
and cx1
and apply snapping. Similarly, the fields
cx0
and cx1
which specify the plot coordinates of each overlay can be
modified, but modifications should be placed in a shiny::isolate()
call
with a call to ov$update_px(i)
at the end to update px
and pw
and apply snapping. The i
parameter to these functions can be left out
to apply changes to all overlays, or you can pass in the indices of just
the overlay(s) to be updated.
Details
Call this function once from your server code to initialise a set of overlay
rectangles for a specific plot. It creates reactive handlers for move,
resize, and dropdown menu actions, and allows adding new overlays by
dragging an overlayToken()
onto the plot. The function returns a
shiny::reactiveValues()
object which you should keep for further use; in
the examples and documentation, this object is typically called ov
.
This function also defines a dynamic output UI slot with ID
paste0(outputId, "_menu")
, which can be rendered using shiny::renderUI()
.
When a user clicks the overlay's dropdown icon, this menu becomes visible
and can be populated with inputs for editing overlay-specific settings, e.g.
labels or numeric parameters tied to that overlay.
If you provide a coordinate snapping function (snap
argument), it should
have the signature function(ov, i)
where ov
is the
shiny::reactiveValues()
object defining the overlays and their settings,
and i
is the set of indices for the rectangles to be updated. When the
position of any of the overlays is changed, the snapping function will be
applied. In this function, you should make sure that all ov$cx0[i]
and
ov$cx1[i]
are within the coordinate bounds defined by the plot, i.e.
constrained by ov$bound_cx
and ov$bound_cw
, when the function returns.
This means, for example, if you are "rounding down" ov$cx0[i]
to some
nearest multiple of a number, you should make sure it doesn't become less
than ov$bound_cx
. Finally, the snapping function will get triggered when
the x axis range of the plot changes, so it may be a good idea to provide
one if the user might place an overlay onto the plot, but then change the x
axis range of the plot such that the overlay is no longer visible. You can
detect this by verifying whether the overlay rectangles are "out of bounds"
at the top of your snapping function. See example below.
Examples
# Example of a valid snapping function: snap to nearest round number and
# make sure the overlay is at least 2 units wide.
mysnap <- function(ov, i) {
# remove any "out of bounds" overlays
oob <- seq_len(ov$n) %in% i &
(ov$cx0 < ov$bound_cx | ov$cx1 > ov$bound_cx + ov$bound_cw)
ov$active[oob] <- FALSE
# adjust position and with
widths <- pmax(2, round(ov$cx1[i] - ov$cx0[i]))
ov$cx0[i] <- pmax(round(ov$bound_cx),
pmin(round(ov$bound_cx + ov$bound_cw) - widths, round(ov$cx0[i])))
ov$cx1[i] <- pmin(round(ov$bound_cx + ov$bound_cw), ov$cx0[i] + widths)
}
ui <- shiny::fluidPage(
useOverlay(),
overlayPlotOutput("my_plot", 640, 480),
overlayToken("add", "Raise")
# further UI elements here . . .
)
server <- function(input, output) {
ov <- overlayServer("my_plot", 4, 1, snap = mysnap)
output$my_plot_menu <- renderUI({
i <- req(ov$editing)
textInput("label_input", "Overlay label", value = ov$label[i])
})
observeEvent(input$label_input, {
i <- req(ov$editing)
ov$label[i] <- input$label_input
})
output$my_plot <- shiny::renderPlot({
df <- data.frame(x = seq(0, 2 * pi, length.out = 200))
df$y <- sin(df$x) + 0.1 * sum(ov$active * (df$x > ov$cx0 & df$x < ov$cx1))
plot(df, type = "l")
overlayBounds(ov, "base")
})
# further server code here . . .
}
if (interactive()) {
shiny::shinyApp(ui, server)
}