Skip to contents

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_key values 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").