Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Package: gglite
Title: Lightweight Data Visualization via the Grammar of Graphics
Version: 0.0.2
Version: 0.0.3
Authors@R: person("Yihui", "Xie", role = c("aut", "cre"), email = "xie@yihui.name",
comment = c(ORCID = "0000-0003-0645-5666"))
Description: A lightweight R interface to the AntV G2 JavaScript visualization
Expand Down
3 changes: 3 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# gglite 0.1

- Added Shiny examples in `examples/shiny.Rmd`.
- Added a `\dontrun{}` example to `render_shiny()` showing minimal Shiny app setup.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't include news

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 78c79dd — NEWS.md is back to just the # gglite 0.1 heading.

A lightweight R interface to the
[AntV G2](https://g2.antv.antgroup.com/) JavaScript visualization library with
a ggplot2-style API. Create interactive charts using the Grammar of Graphics
Expand Down
26 changes: 26 additions & 0 deletions R/render.R
Original file line number Diff line number Diff line change
Expand Up @@ -177,9 +177,35 @@ record_print.g2 = function(x, ...) {

#' Render a Chart in Shiny
#'
#' Render a `g2` chart in a Shiny app by sending the chart spec to a container
#' element via a custom message. The UI must load the G2 CDN scripts and the
#' gglite message handler; the container `<div>` must have a non-zero height.
#' Call `render_shiny()` inside `observe()` or `observeEvent()` to re-render
#' the chart reactively.
#'
#' @param chart A `g2` object.
#' @param session The Shiny session object.
#' @param output_id The container element ID on the client side.
#' @examples
#' \dontrun{
#' library(shiny)
#' ui = fluidPage(
#' tags$head(
#' tags$script(src = 'https://unpkg.com/@antv/g2@5/dist/g2.min.js'),
#' tags$script(
#' src = 'https://cdn.jsdelivr.net/npm/@xiee/utils/js/g2-column.min.js'
#' ),
#' includeScript(system.file('www/g2-handler.js', package = 'gglite'))
#' ),
#' div(id = 'chart1', style = 'height:480px')
#' )
#' server = function(input, output, session) {
#' render_shiny(
#' g2(mtcars, x = 'mpg', y = 'hp') |> mark_point(), session, 'chart1'
#' )
#' }
#' shinyApp(ui, server)
#' }
#' @export
render_shiny = function(chart, session, output_id) {
spec = build_config(chart)
Expand Down
142 changes: 142 additions & 0 deletions examples/shiny.Rmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
---
title: Shiny
---

gglite charts can be embedded in [Shiny](https://shiny.posit.co/) applications
using `render_shiny()`. Unlike `htmlwidgets`-based packages, gglite uses
Shiny's custom message passing to send chart specs to the browser, where the G2
library renders them.

## Setup

Every Shiny app using gglite needs three script tags in the `<head>` of the UI:

1. The G2 JavaScript library from a CDN.
2. The gglite column-major data helper.
3. The gglite Shiny message handler (`g2-handler.js`), bundled with the package.

```r
tags$head(
tags$script(src = 'https://unpkg.com/@antv/g2@5/dist/g2.min.js'),
tags$script(
src = 'https://cdn.jsdelivr.net/npm/@xiee/utils/js/g2-column.min.js'
),
includeScript(system.file('www/g2-handler.js', package = 'gglite'))
)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's tedious to repeat these lines in every shiny app, and these are also deep technical details that end-users shouldn't worry about; can we provide the traditional shiny syntax like g2output() and renderG2()? g2output() will make sure to include the JS dependencies into shiny's meta, and output$ID = renderG2() feels more familiar to shiny users

if that's possible, we can get rid of render_shiny()

i don't want to introduce hard dependencies for this task; dependencies in Suggests will be fine

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 78c79dd. render_shiny() is removed and replaced with a proper Shiny output binding:

  • g2output(outputId) — creates the placeholder div and automatically injects the G2 CDN, column-major helper, and the new output binding JS as htmlDependencies; no manual tags$head(...) needed
  • output$ID = renderG2({...}) — standard Shiny render pattern; uses markRenderFunction so reactivity works out of the box

examples/shiny.Rmd now shows just g2output('chart1') in the UI and output$chart1 = renderG2({...}) in the server, with three examples (basic, reactive, multiple charts).

Tested the binding in headless Playwright: element found with shiny-bound-output class, spec delivered correctly as a JS object (Shiny embeds xfun_raw_string json as raw JSON preserving column-major format), chart renders and old chart is properly destroyed on re-render.

```

The container `<div>` must have an explicit height (G2 charts will not appear
in a zero-height container) and an `id` that matches the `output_id` argument
you pass to `render_shiny()`:

```r
div(id = 'my-chart', style = 'height:480px')
```

## Basic Example

The simplest app renders a static chart when the session starts. Call
`render_shiny()` directly in the server function:

```r
library(shiny)
library(gglite)

ui = fluidPage(
tags$head(
tags$script(src = 'https://unpkg.com/@antv/g2@5/dist/g2.min.js'),
tags$script(
src = 'https://cdn.jsdelivr.net/npm/@xiee/utils/js/g2-column.min.js'
),
includeScript(system.file('www/g2-handler.js', package = 'gglite'))
),
div(id = 'chart1', style = 'height:480px')
)

server = function(input, output, session) {
render_shiny(
g2(mtcars, x = 'mpg', y = 'hp') |> mark_point(),
session, 'chart1'
)
}

shinyApp(ui, server)
```

## Reactive Example

Wrap `render_shiny()` in `observeEvent()` (or `observe()`) to re-render the
chart whenever an input changes. Pass `ignoreNULL = FALSE` so the chart also
renders on startup. The following app lets the user pick the y-axis variable
from a dropdown:

```r
library(shiny)
library(gglite)

ui = fluidPage(
tags$head(
tags$script(src = 'https://unpkg.com/@antv/g2@5/dist/g2.min.js'),
tags$script(
src = 'https://cdn.jsdelivr.net/npm/@xiee/utils/js/g2-column.min.js'
),
includeScript(system.file('www/g2-handler.js', package = 'gglite'))
),
selectInput('yvar', 'Y variable', choices = c('hp', 'wt', 'qsec', 'drat')),
div(id = 'chart1', style = 'height:480px')
)

server = function(input, output, session) {
observeEvent(input$yvar, {
render_shiny(
g2(mtcars, x = 'mpg', y = input$yvar) |>
mark_point() |>
title_of(input$yvar),
session, 'chart1'
)
}, ignoreNULL = FALSE)
}

shinyApp(ui, server)
```

Each time the user picks a different variable, `render_shiny()` sends the
updated spec to the browser and G2 re-renders the chart in place.

## Multiple Charts

You can render several charts independently by giving each container a distinct
`id` and calling `render_shiny()` once per chart:

```r
library(shiny)
library(gglite)

ui = fluidPage(
tags$head(
tags$script(src = 'https://unpkg.com/@antv/g2@5/dist/g2.min.js'),
tags$script(
src = 'https://cdn.jsdelivr.net/npm/@xiee/utils/js/g2-column.min.js'
),
includeScript(system.file('www/g2-handler.js', package = 'gglite'))
),
fluidRow(
column(6, div(id = 'scatter', style = 'height:400px')),
column(6, div(id = 'bars', style = 'height:400px'))
)
)

server = function(input, output, session) {
render_shiny(
g2(mtcars, x = 'mpg', y = 'hp') |> mark_point(),
session, 'scatter'
)
freq = as.data.frame(table(cyl = mtcars$cyl))
render_shiny(
g2(freq, x = 'cyl', y = 'Freq') |> mark_interval(),
session, 'bars'
)
}

shinyApp(ui, server)
```
27 changes: 26 additions & 1 deletion man/render_shiny.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading