Skip to content

Commit 97e2c59

Browse files
Copilotyihui-botyihui
authored
Add Shiny output binding with g2Output() and renderG2() (#6)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: yihui-bot <264330240+yihui-bot@users.noreply.github.com> Co-authored-by: Yihui Xie <xie@yihui.name>
1 parent d60857b commit 97e2c59

File tree

9 files changed

+240
-33
lines changed

9 files changed

+240
-33
lines changed

DESCRIPTION

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Package: gglite
22
Title: Lightweight Data Visualization via the Grammar of Graphics
3-
Version: 0.0.2
3+
Version: 0.0.3
44
Authors@R: person("Yihui", "Xie", role = c("aut", "cre"), email = "xie@yihui.name",
55
comment = c(ORCID = "0000-0003-0645-5666"))
66
Description: A lightweight R interface to the AntV G2 JavaScript visualization
@@ -13,8 +13,10 @@ Depends: R (>= 4.1.0)
1313
Imports:
1414
xfun
1515
Suggests:
16+
htmltools,
1617
litedown,
1718
knitr,
19+
shiny,
1820
testit
1921
Encoding: UTF-8
2022
Roxygen: list(markdown = TRUE)

NAMESPACE

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export(encode)
1111
export(facet_circle)
1212
export(facet_rect)
1313
export(g2)
14+
export(g2Output)
1415
export(interact)
1516
export(labels_of)
1617
export(legend_of)
@@ -50,7 +51,7 @@ export(mark_treemap)
5051
export(mark_vector)
5152
export(mark_word_cloud)
5253
export(preview)
53-
export(render_shiny)
54+
export(renderG2)
5455
export(scale_of)
5556
export(scrollbar_of)
5657
export(slider_of)

R/render.R

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -175,16 +175,3 @@ record_print.g2 = function(x, ...) {
175175
}
176176
}
177177

178-
#' Render a Chart in Shiny
179-
#'
180-
#' @param chart A `g2` object.
181-
#' @param session The Shiny session object.
182-
#' @param output_id The container element ID on the client side.
183-
#' @export
184-
render_shiny = function(chart, session, output_id) {
185-
spec = build_config(chart)
186-
ctor = chart$options
187-
session$sendCustomMessage('g2-render', list(
188-
id = output_id, ctor = ctor, spec = spec
189-
))
190-
}

R/shiny.R

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
#' Shiny Output for a G2 Chart
2+
#'
3+
#' Create a placeholder in the UI for a G2 chart rendered by [renderG2()].
4+
#' The required JavaScript dependencies (G2 library, column-major data helper,
5+
#' and output binding) are automatically attached to the page.
6+
#'
7+
#' @param outputId Output variable name to read the chart from.
8+
#' @param width,height CSS dimensions for the chart container.
9+
#' @return A Shiny UI element.
10+
#' @examples
11+
#' \dontrun{
12+
#' library(shiny)
13+
#' ui = fluidPage(g2Output('chart1'))
14+
#' server = function(input, output, session) {
15+
#' output$chart1 = renderG2({
16+
#' g2(mtcars, x = 'mpg', y = 'hp') |> mark_point()
17+
#' })
18+
#' }
19+
#' shinyApp(ui, server)
20+
#' }
21+
#' @export
22+
g2Output = function(outputId, width = '100%', height = '480px') {
23+
dep_g2 = htmltools::htmlDependency(
24+
'g2', '5',
25+
src = c(href = 'https://unpkg.com/@antv/g2@5/dist'),
26+
script = 'g2.min.js'
27+
)
28+
dep_col = htmltools::htmlDependency(
29+
'g2-column', '1',
30+
src = c(href = 'https://cdn.jsdelivr.net/npm/@xiee/utils/js'),
31+
script = 'g2-column.min.js'
32+
)
33+
dep_binding = htmltools::htmlDependency(
34+
'g2-binding', as.character(utils::packageVersion('gglite')),
35+
src = system.file('www', package = 'gglite'),
36+
script = 'g2-binding.js'
37+
)
38+
shiny::tagList(
39+
dep_g2, dep_col, dep_binding,
40+
shiny::div(
41+
id = outputId, class = 'gglite-output',
42+
style = paste0('width:', width, ';height:', height)
43+
)
44+
)
45+
}
46+
47+
#' Render a G2 Chart in Shiny
48+
#'
49+
#' Create a reactive G2 chart for use with [g2Output()].
50+
#' Assign it to an output slot: `output$ID = renderG2({...})`.
51+
#'
52+
#' @param expr An expression that returns a `g2` object.
53+
#' @param env The environment in which to evaluate `expr`.
54+
#' @param quoted Whether `expr` is already quoted.
55+
#' @return A render function for use with [g2Output()].
56+
#' @export
57+
renderG2 = function(expr, env = parent.frame(), quoted = FALSE) {
58+
if (!quoted) expr = substitute(expr)
59+
expr_env = env
60+
func = function() eval(expr, envir = expr_env)
61+
shiny::markRenderFunction(g2Output, function() {
62+
chart = func()
63+
list(ctor = chart$options, spec = xfun::tojson(build_config(chart)))
64+
})
65+
}

examples/shiny.Rmd

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
---
2+
title: Shiny
3+
---
4+
5+
gglite charts can be embedded in [Shiny](https://shiny.posit.co/) applications
6+
using `g2Output()` and `renderG2()`. These follow the familiar Shiny output/
7+
render pattern: `g2Output()` creates the placeholder in the UI and
8+
automatically injects the required JavaScript dependencies, while `renderG2()`
9+
creates the reactive chart in the server.
10+
11+
## Basic Example
12+
13+
The simplest app renders a static scatter plot:
14+
15+
```r
16+
library(shiny)
17+
library(gglite)
18+
19+
ui = fluidPage(
20+
g2Output('chart1')
21+
)
22+
23+
server = function(input, output, session) {
24+
output$chart1 = renderG2({
25+
g2(mtcars, x = 'mpg', y = 'hp') |> mark_point()
26+
})
27+
}
28+
29+
shinyApp(ui, server)
30+
```
31+
32+
## Reactive Example
33+
34+
Wrap the chart in `renderG2({})` and reference reactive inputs directly. The
35+
chart re-renders automatically whenever an input changes. The following app
36+
lets the user pick the y-axis variable:
37+
38+
```r
39+
library(shiny)
40+
library(gglite)
41+
42+
ui = fluidPage(
43+
selectInput('xvar', 'X variable',
44+
choices = c('hp', 'wt', 'qsec', 'drat')),
45+
g2Output('chart1')
46+
)
47+
48+
server = function(input, output, session) {
49+
output$chart1 = renderG2({
50+
g2(mtcars, x = input$xvar, y = 'mpg') |>
51+
mark_point() |>
52+
title_of(paste('mpg vs', input$xvar))
53+
})
54+
}
55+
56+
shinyApp(ui, server)
57+
```
58+
59+
## Multiple Charts
60+
61+
Use a distinct `outputId` for each chart and assign a separate `renderG2()`
62+
for each one:
63+
64+
```r
65+
library(shiny)
66+
library(gglite)
67+
68+
ui = fluidPage(
69+
fluidRow(
70+
column(6, g2Output('scatter')),
71+
column(6, g2Output('bars'))
72+
)
73+
)
74+
75+
server = function(input, output, session) {
76+
output$scatter = renderG2({
77+
g2(mtcars, x = 'mpg', y = 'hp') |> mark_point()
78+
})
79+
output$bars = renderG2({
80+
freq = as.data.frame(table(cyl = mtcars$cyl))
81+
g2(freq, x = 'cyl', y = 'Freq') |> mark_interval()
82+
})
83+
}
84+
85+
shinyApp(ui, server)
86+
```

inst/www/g2-binding.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Shiny output binding for gglite / AntV G2 charts
2+
'use strict';
3+
4+
$(document).ready(function() {
5+
const g2OutputBinding = new Shiny.OutputBinding();
6+
Object.assign(g2OutputBinding, {
7+
find: function(scope) {
8+
return $(scope).find('.gglite-output');
9+
},
10+
renderValue: function(el, data) {
11+
if (!data) return;
12+
if (el._g2chart) {
13+
el._g2chart.destroy();
14+
el._g2chart = null;
15+
}
16+
const ctor = Object.assign({}, data.ctor, { container: el.id });
17+
const spec = data.spec; // already parsed (Shiny embeds xfun JSON as object)
18+
const chart = new G2.Chart(ctor);
19+
chart.options(spec);
20+
chart.render();
21+
el._g2chart = chart;
22+
},
23+
renderError: function(el, err) {
24+
console.error('gglite:', err.message);
25+
}
26+
});
27+
28+
Shiny.outputBindings.register(g2OutputBinding, 'gglite.g2');
29+
});

man/g2Output.Rd

Lines changed: 33 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/renderG2.Rd

Lines changed: 22 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/render_shiny.Rd

Lines changed: 0 additions & 18 deletions
This file was deleted.

0 commit comments

Comments
 (0)