Overview
myIO provides standard htmlwidgets Shiny bindings, so
charts work like any other output in your Shiny app. This vignette
covers the basics, interactive I/O patterns, and common recipes.
Minimal App
Use myIOOutput() in the UI and renderMyIO()
in the server:
library(shiny)
library(myIO)
ui <- fluidPage(
titlePanel("myIO + Shiny"),
myIOOutput("chart", width = "100%", height = "400px")
)
server <- function(input, output) {
output$chart <- renderMyIO({
myIO() |>
addIoLayer(
type = "point",
color = "steelblue",
label = "scatter",
data = mtcars,
mapping = list(x_var = "wt", y_var = "mpg")
)
})
}
shinyApp(ui, server)Reactive Data
Because renderMyIO() is reactive, the chart updates
automatically when inputs change. Use standard Shiny reactivity to
filter or transform data:
library(shiny)
library(myIO)
ui <- fluidPage(
titlePanel("Reactive myIO Chart"),
sidebarLayout(
sidebarPanel(
selectInput("cyl", "Cylinders:",
choices = c("All", sort(unique(mtcars$cyl))),
selected = "All"
),
selectInput("chart_type", "Chart Type:",
choices = c("point", "bar", "line"),
selected = "point"
)
),
mainPanel(
myIOOutput("chart", height = "500px")
)
)
)
server <- function(input, output) {
filtered_data <- reactive({
if (input$cyl == "All") mtcars else mtcars[mtcars$cyl == as.numeric(input$cyl), ]
})
output$chart <- renderMyIO({
myIO() |>
addIoLayer(
type = input$chart_type,
color = "#E69F00",
label = "data",
data = filtered_data(),
mapping = list(x_var = "wt", y_var = "mpg")
) |>
setAxisFormat(xLabel = "Weight (1000 lbs)", yLabel = "Miles per Gallon") |>
setMargin(top = 20, bottom = 70, left = 60, right = 10)
})
}
shinyApp(ui, server)Brush Selection
setBrush() enables rectangle selection on a chart. The
selected data points are available as a Shiny reactive input:
library(shiny)
library(myIO)
ui <- fluidPage(
titlePanel("Brush Selection"),
fluidRow(
column(8, myIOOutput("chart", height = "400px")),
column(4,
h4("Selected Points"),
verbatimTextOutput("selected")
)
)
)
server <- function(input, output) {
output$chart <- renderMyIO({
myIO() |>
addIoLayer(
type = "point",
color = "#4E79A7",
label = "scatter",
data = mtcars,
mapping = list(x_var = "wt", y_var = "mpg")
) |>
setBrush() |>
setAxisFormat(xLabel = "Weight", yLabel = "MPG")
})
output$selected <- renderPrint({
brushed <- input$`myIO-chart-brushed`
if (is.null(brushed)) return("Drag to select points")
sel <- jsonlite::fromJSON(brushed)
if (length(sel$keys) == 0) return("No points selected")
paste(length(sel$keys), "points selected")
})
}
shinyApp(ui, server)The brush input key follows the pattern
myIO-{outputId}-brushed. The payload includes:
-
data— array of selected row objects -
keys—_source_keyvalues for each selected point -
extent— brush bounds in data coordinates
Use direction = "x" or direction = "y" for
single-axis brushing.
Click-to-Annotate
setAnnotation() enables click-to-label mode. Each
annotation is stored as structured data and available as a Shiny
reactive:
library(shiny)
library(myIO)
ui <- fluidPage(
titlePanel("Data Annotation"),
fluidRow(
column(8, myIOOutput("chart", height = "400px")),
column(4,
h4("Annotations"),
tableOutput("annotations")
)
)
)
server <- function(input, output) {
output$chart <- renderMyIO({
myIO() |>
addIoLayer(
type = "point",
color = "#4E79A7",
label = "scatter",
data = mtcars,
mapping = list(x_var = "wt", y_var = "mpg")
) |>
setAnnotation(
labels = c("outlier", "interesting", "normal"),
colors = c(outlier = "#E63946", interesting = "#F4A261", normal = "#2A9D8F")
) |>
setAxisFormat(xLabel = "Weight", yLabel = "MPG")
})
output$annotations <- renderTable({
ann <- input$`myIO-chart-annotated`
if (is.null(ann)) return(data.frame())
parsed <- jsonlite::fromJSON(ann)
if (length(parsed$annotations) == 0) return(data.frame())
parsed$annotations[, c("label", "x", "y", "category")]
})
}
shinyApp(ui, server)The annotation input key is myIO-{outputId}-annotated.
Each annotation includes _source_key, x,
y, label, category (color), and
timestamp.
Linked Brushing
setLinked() connects two or more myIO charts via
Crosstalk. Brushing points in one chart highlights them in the
other:
library(shiny)
library(myIO)
library(crosstalk)
ui <- fluidPage(
titlePanel("Linked Brushing"),
fluidRow(
column(6, myIOOutput("scatter1", height = "350px")),
column(6, myIOOutput("scatter2", height = "350px"))
)
)
server <- function(input, output) {
shared <- crosstalk::SharedData$new(mtcars, key = ~rownames(mtcars))
output$scatter1 <- renderMyIO({
myIO() |>
addIoLayer(
type = "point",
color = "#4E79A7",
label = "wt vs mpg",
data = shared$data(),
mapping = list(x_var = "wt", y_var = "mpg")
) |>
setBrush() |>
setLinked(shared, mode = "source") |>
setAxisFormat(xLabel = "Weight", yLabel = "MPG")
})
output$scatter2 <- renderMyIO({
myIO() |>
addIoLayer(
type = "point",
color = "#E15759",
label = "hp vs mpg",
data = shared$data(),
mapping = list(x_var = "hp", y_var = "mpg")
) |>
setLinked(shared, mode = "target") |>
setAxisFormat(xLabel = "Horsepower", yLabel = "MPG")
})
}
shinyApp(ui, server)Both charts share the same SharedData object. The
mode parameter controls direction: "source"
emits selections, "target" receives them, and
"both" (default) does both.
Set filter = TRUE to hide non-matching points instead of
dimming them.
Parameter Sliders
setSlider() adds a slider below the chart that sends its
value as a Shiny input. This is useful for adjusting transform
parameters (confidence level, smoothing span, polynomial degree) without
building a separate sidebar:
library(shiny)
library(myIO)
ui <- fluidPage(
titlePanel("Interactive Regression"),
myIOOutput("chart", height = "450px")
)
server <- function(input, output) {
output$chart <- renderMyIO({
ci <- input$`myIO-chart-slider-ci_level` %||% 0.95
myIO(data = mtcars) |>
addIoLayer(
type = "regression",
label = "fit",
mapping = list(x_var = "wt", y_var = "mpg"),
options = list(method = "lm", showCI = TRUE, level = ci)
) |>
setSlider("ci_level", "Confidence Level", 0.80, 0.99, ci, 0.01) |>
setAxisFormat(xLabel = "Weight", yLabel = "MPG")
})
}
shinyApp(ui, server)The slider input key is myIO-{outputId}-slider-{param}.
Use debounce to control how often the slider fires (default
200ms).
Multiple Charts
Add multiple myIOOutput() calls to display several
charts side by side. Each chart gets its own output ID:
library(shiny)
library(myIO)
ui <- fluidPage(
titlePanel("Multiple Charts"),
fluidRow(
column(6, myIOOutput("scatter", height = "350px")),
column(6, myIOOutput("bars", height = "350px"))
)
)
server <- function(input, output) {
output$scatter <- renderMyIO({
myIO() |>
addIoLayer(
type = "point",
color = "#56B4E9",
label = "scatter",
data = mtcars,
mapping = list(x_var = "wt", y_var = "mpg")
) |>
setAxisFormat(xLabel = "Weight", yLabel = "MPG")
})
output$bars <- renderMyIO({
myIO() |>
addIoLayer(
type = "bar",
color = "#E69F00",
label = "bars",
data = mtcars,
mapping = list(x_var = "cyl", y_var = "mpg")
) |>
defineCategoricalAxis(xAxis = TRUE) |>
setAxisFormat(xLabel = "Cylinders", yLabel = "MPG")
})
}
shinyApp(ui, server)Theming in Shiny
Theme tokens from setTheme() work the same way in
Shiny:
library(shiny)
library(myIO)
ui <- fluidPage(
style = "background-color: #1a1a2e; color: #e0e0e0;",
titlePanel("Dark Mode Dashboard"),
myIOOutput("chart", height = "400px")
)
server <- function(input, output) {
output$chart <- renderMyIO({
myIO() |>
addIoLayer(
type = "line",
color = c("#48dbfb", "#ff6b6b", "#feca57", "#ff9ff3", "#00d2ff"),
label = "Month",
data = within(airquality, Month <- paste0("M", Month)),
mapping = list(x_var = "Day", y_var = "Temp", group = "Month")
) |>
setTheme(
text_color = "#b0b0b0",
grid_color = "#2d2d2d",
bg = "#1a1a2e",
font = "Inter, system-ui, sans-serif"
) |>
setAxisFormat(xLabel = "Day", yLabel = "Temperature")
})
}
shinyApp(ui, server)Sizing
myIOOutput() accepts width and
height as CSS units. Charts are responsive by default and
will adapt to their container:
# Fixed size
myIOOutput("chart1", width = "600px", height = "400px")
# Fill container width
myIOOutput("chart2", width = "100%", height = "500px")Shiny Input Reference
All myIO Shiny inputs follow the naming pattern
myIO-{outputId}-{event}:
| Input key | Event | Payload |
|---|---|---|
myIO-{id}-rollover |
Hover on data element | JSON data point |
myIO-{id}-dragEnd |
Point drag completed | JSON { point, layerLabel }
|
myIO-{id}-brushed |
Brush selection | JSON { data, extent, keys }
|
myIO-{id}-annotated |
Annotation added/removed | JSON { annotations, action }
|
myIO-{id}-slider-{param} |
Slider value changed | Numeric value |
myIO-{id}-error |
Render error | Error message string |
Tips
-
Transitions: Use
setTransitionSpeed(speed = 0)to disable animations if the chart updates frequently from reactive inputs. - Export: Charts include built-in CSV and SVG export buttons that work in Shiny without extra configuration.
-
Drag points:
dragPoints()enables interactive point dragging. In a Shiny context, you can use this for exploratory what-if analysis. - Brush + annotation: Both can be active simultaneously. Clicks on data points open the annotation popover; dragging on empty space activates the brush.
-
Slider debounce: For heavy transforms, increase the
debounce:
setSlider("span", "Span", 0.1, 1.0, 0.5, debounce = 500).
Deep Dive
For a comprehensive walkthrough with the live demo app embedded, see
the package documentation at
vignette("shiny-integration", package = "myIO").
