Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
4 changes: 3 additions & 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 All @@ -13,8 +13,10 @@ Depends: R (>= 4.1.0)
Imports:
xfun
Suggests:
htmltools,
litedown,
knitr,
shiny,
testit
Encoding: UTF-8
Roxygen: list(markdown = TRUE)
Expand Down
3 changes: 2 additions & 1 deletion NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export(encode)
export(facet_circle)
export(facet_rect)
export(g2)
export(g2Output)
export(interact)
export(labels_of)
export(legend_of)
Expand Down Expand Up @@ -50,7 +51,7 @@ export(mark_treemap)
export(mark_vector)
export(mark_word_cloud)
export(preview)
export(render_shiny)
export(renderG2)
export(scale_of)
export(scrollbar_of)
export(slider_of)
Expand Down
13 changes: 0 additions & 13 deletions R/render.R
Original file line number Diff line number Diff line change
Expand Up @@ -175,16 +175,3 @@ record_print.g2 = function(x, ...) {
}
}

#' Render a Chart in Shiny
#'
#' @param chart A `g2` object.
#' @param session The Shiny session object.
#' @param output_id The container element ID on the client side.
#' @export
render_shiny = function(chart, session, output_id) {
spec = build_config(chart)
ctor = chart$options
session$sendCustomMessage('g2-render', list(
id = output_id, ctor = ctor, spec = spec
))
}
65 changes: 65 additions & 0 deletions R/shiny.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#' Shiny Output for a G2 Chart
#'
#' Create a placeholder in the UI for a G2 chart rendered by [renderG2()].
#' The required JavaScript dependencies (G2 library, column-major data helper,
#' and output binding) are automatically attached to the page.
#'
#' @param outputId Output variable name to read the chart from.
#' @param width,height CSS dimensions for the chart container.
#' @return A Shiny UI element.
#' @examples
#' \dontrun{
#' library(shiny)
#' ui = fluidPage(g2Output('chart1'))
#' server = function(input, output, session) {
#' output$chart1 = renderG2({
#' g2(mtcars, x = 'mpg', y = 'hp') |> mark_point()
#' })
#' }
#' shinyApp(ui, server)
#' }
#' @export
g2Output = function(outputId, width = '100%', height = '480px') {
dep_g2 = htmltools::htmlDependency(
'g2', '5',
src = c(href = 'https://unpkg.com/@antv/g2@5/dist'),
script = 'g2.min.js'
)
dep_col = htmltools::htmlDependency(
'g2-column', '1',
src = c(href = 'https://cdn.jsdelivr.net/npm/@xiee/utils/js'),
script = 'g2-column.min.js'
)
dep_binding = htmltools::htmlDependency(
'g2-binding', as.character(utils::packageVersion('gglite')),
src = system.file('www', package = 'gglite'),
script = 'g2-binding.js'
)
shiny::tagList(
dep_g2, dep_col, dep_binding,
shiny::div(
id = outputId, class = 'gglite-output',
style = paste0('width:', width, ';height:', height)
)
)
}

#' Render a G2 Chart in Shiny
#'
#' Create a reactive G2 chart for use with [g2Output()].
#' Assign it to an output slot: `output$ID = renderG2({...})`.
#'
#' @param expr An expression that returns a `g2` object.
#' @param env The environment in which to evaluate `expr`.
#' @param quoted Whether `expr` is already quoted.
#' @return A render function for use with [g2Output()].
#' @export
renderG2 = function(expr, env = parent.frame(), quoted = FALSE) {
if (!quoted) expr = substitute(expr)
expr_env = env
func = function() eval(expr, envir = expr_env)
shiny::markRenderFunction(g2Output, function() {
chart = func()
list(ctor = chart$options, spec = xfun::tojson(build_config(chart)))
})
}
86 changes: 86 additions & 0 deletions examples/shiny.Rmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
---
title: Shiny
---

gglite charts can be embedded in [Shiny](https://shiny.posit.co/) applications
using `g2Output()` and `renderG2()`. These follow the familiar Shiny output/
render pattern: `g2Output()` creates the placeholder in the UI and
automatically injects the required JavaScript dependencies, while `renderG2()`
creates the reactive chart in the server.

## Basic Example

The simplest app renders a static scatter plot:

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

ui = fluidPage(
g2Output('chart1')
)

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

shinyApp(ui, server)
```

## Reactive Example

Wrap the chart in `renderG2({})` and reference reactive inputs directly. The
chart re-renders automatically whenever an input changes. The following app
lets the user pick the y-axis variable:

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

ui = fluidPage(
selectInput('xvar', 'X variable',
choices = c('hp', 'wt', 'qsec', 'drat')),
g2Output('chart1')
)

server = function(input, output, session) {
output$chart1 = renderG2({
g2(mtcars, x = input$xvar, y = 'mpg') |>
mark_point() |>
title_of(paste('mpg vs', input$xvar))
})
}

shinyApp(ui, server)
```

## Multiple Charts

Use a distinct `outputId` for each chart and assign a separate `renderG2()`
for each one:

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

ui = fluidPage(
fluidRow(
column(6, g2Output('scatter')),
column(6, g2Output('bars'))
)
)

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

shinyApp(ui, server)
```
29 changes: 29 additions & 0 deletions inst/www/g2-binding.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Shiny output binding for gglite / AntV G2 charts
'use strict';

$(document).ready(function() {
const g2OutputBinding = new Shiny.OutputBinding();
Object.assign(g2OutputBinding, {
find: function(scope) {
return $(scope).find('.gglite-output');
},
renderValue: function(el, data) {
if (!data) return;
if (el._g2chart) {
el._g2chart.destroy();
el._g2chart = null;
}
const ctor = Object.assign({}, data.ctor, { container: el.id });
const spec = data.spec; // already parsed (Shiny embeds xfun JSON as object)
const chart = new G2.Chart(ctor);
chart.options(spec);
chart.render();
el._g2chart = chart;
},
renderError: function(el, err) {
console.error('gglite:', err.message);
}
});

Shiny.outputBindings.register(g2OutputBinding, 'gglite.g2');
});
33 changes: 33 additions & 0 deletions man/g2Output.Rd

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

22 changes: 22 additions & 0 deletions man/renderG2.Rd

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

18 changes: 0 additions & 18 deletions man/render_shiny.Rd

This file was deleted.

Loading