-
Notifications
You must be signed in to change notification settings - Fork 1
Add canvas() function; remove layout/dimension args from g2(); add renderer support
#46
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
015e410
858bbf8
e63e39d
d929966
0a35006
9185b44
3158851
0354379
fc712b1
0915829
5ae40ea
a441e04
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -120,11 +120,6 @@ check_chart = function(fn, chart, args) { | |
| #' inside inline JavaScript functions that cannot be statically detected. | ||
| #' @param ... Aesthetic mappings as `name = ~column` formulas or a positional | ||
| #' formula for `x`/`y`. Character strings are also accepted. | ||
| #' @param width,height Width and height of the chart in pixels. | ||
| #' @param padding,margin,inset Layout spacing in pixels. Each can be a scalar | ||
| #' (applied to all sides) or a length-4 vector `c(top, right, bottom, left)`; | ||
| #' use `NA` to skip individual sides. `NULL` (the default) leaves the value | ||
| #' unset. | ||
| #' @param title Chart title string, a convenient alternative to piping into | ||
| #' [title_()] separately. | ||
| #' @param subtitle Chart subtitle string. | ||
|
|
@@ -142,11 +137,7 @@ check_chart = function(fn, chart, args) { | |
| #' | ||
| #' # Title and subtitle | ||
| #' g2(mtcars, hp ~ mpg, title = 'Motor Trend Cars', subtitle = 'mpg vs hp') | ||
| g2 = function( | ||
| data = NULL, ..., width = NULL, height = 480, | ||
| padding = NULL, margin = NULL, inset = NULL, | ||
| title = NULL, subtitle = NULL | ||
| ) { | ||
| g2 = function(data = NULL, ..., title = NULL, subtitle = NULL) { | ||
| dots = list(...) | ||
| # A positional (unnamed) formula like `hp ~ mpg` or `~ mpg` as the first arg | ||
| has_formula = length(dots) && | ||
|
|
@@ -171,7 +162,7 @@ g2 = function( | |
| } | ||
| chart = structure(list( | ||
| data = data, | ||
| options = list(width = width, height = height, autoFit = if (is.null(width)) TRUE), | ||
| options = list(height = 480L, autoFit = TRUE), | ||
| layers = list(), | ||
| scales = list(), | ||
| coords = NULL, | ||
|
|
@@ -184,16 +175,115 @@ g2 = function( | |
| legends = list(), | ||
| chart_title = dropNulls(list(title = title, subtitle = subtitle)), | ||
| facet = facet_from_formula, | ||
| layout = c( | ||
| process_layout('padding', padding), | ||
| process_layout('margin', margin), | ||
| process_layout('inset', inset) | ||
| ) | ||
| layout = list() | ||
| ), class = 'g2') | ||
| if (length(dots)) chart$aesthetics = modifyList(chart$aesthetics, dots) | ||
| chart | ||
| } | ||
|
|
||
| #' Configure Canvas Options | ||
| #' | ||
| #' Set chart dimensions, layout spacing, and renderer for a G2 chart. Pipe | ||
| #' this after [g2()] to customize the canvas before rendering. | ||
| #' | ||
| #' @section Renderer: | ||
| #' The `renderer` argument controls which rendering backend G2 uses: | ||
| #' \describe{ | ||
| #' \item{`"Canvas"` (default)}{Uses the Canvas 2D API via the full | ||
| #' `g2.min.js` bundle (~1.1 MB). Fast and appropriate for most charts.} | ||
| #' \item{`"SVG"`}{Uses SVG rendering. Requires loading the G2 lite bundle | ||
| #' plus `@antv/g` and `@antv/g-svg` (~1.5 MB total). SVG output can be | ||
| #' inspected in browser DevTools and is useful for debugging.} | ||
| #' \item{`"WebGL"`}{Uses WebGL for GPU-accelerated rendering. Requires | ||
| #' loading the G2 lite bundle plus `@antv/g` and `@antv/g-webgl` (~1.9 MB | ||
| #' total). Best suited for charts with very large numbers of data points.} | ||
| #' } | ||
| #' | ||
| #' **Important caveat for multi-chart documents:** `g2.min.js` and | ||
| #' `g2.lite.min.js` cannot coexist on the same page. If you want to use SVG or | ||
| #' WebGL rendering in a document that contains multiple charts (e.g., R | ||
| #' Markdown, Quarto, Jupyter), you must declare a global renderer option at the | ||
| #' top of the document: | ||
| #' | ||
| #' ```r | ||
| #' options(gglite.renderer = 'svg') # or 'webgl' | ||
| #' ``` | ||
| #' | ||
| #' When this option is set, **all** charts in the document switch to the | ||
| #' `g2.lite.min.js` CDN. Individual charts can still override the renderer via | ||
| #' `canvas(renderer = ...)` — for example, when `options(gglite.renderer = | ||
| #' 'svg')` is set globally, a specific chart can use | ||
| #' `g2(...) |> canvas(renderer = 'webgl')`. | ||
| #' | ||
| #' For **standalone plots** previewed in the browser (via [print.g2()]), no | ||
| #' global option is needed because each plot is a separate HTML page. | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. don't document the details here; instead, point users to |
||
| #' | ||
| #' @param chart A `g2` object, or `NULL` to create a deferred modifier. | ||
| #' @param width Width of the chart in pixels. `NULL` (default) enables | ||
| #' auto-fit to the container width. | ||
| #' @param height Height of the chart in pixels. Default is `480`. | ||
| #' @param padding,margin,inset Layout spacing in pixels. Each can be a scalar | ||
| #' (applied to all sides) or a length-4 vector `c(top, right, bottom, left)`; | ||
| #' use `NA` to skip individual sides. `NULL` (the default) leaves the value | ||
| #' unset. | ||
| #' @param renderer The rendering backend: `"Canvas"` (default), `"SVG"`, or | ||
| #' `"WebGL"` (case-insensitive). See the **Renderer** section for details. | ||
| #' @param ... Additional top-level chart options passed to `chart.options()` in | ||
| #' JavaScript (e.g., `clip = TRUE`, `depth = 400`). | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is depth = 400 a sensible value? make sure you didn't make up a meaningless value |
||
| #' @return The modified `g2` object (or a `g2_mod` when `chart` is `NULL`). | ||
| #' @export | ||
| #' @examples | ||
| #' # Set chart dimensions | ||
| #' g2(mtcars, hp ~ mpg) |> canvas(width = 600, height = 400) | ||
| #' | ||
| #' # Add padding | ||
| #' g2(mtcars, hp ~ mpg) |> canvas(padding = 30) | ||
| #' | ||
| #' # SVG renderer (standalone; no global option needed) | ||
| #' g2(mtcars, hp ~ mpg) |> canvas(renderer = 'svg') | ||
| canvas = function( | ||
| chart = NULL, width = NULL, height = 480, | ||
| padding = NULL, margin = NULL, inset = NULL, | ||
| renderer = NULL, ... | ||
| ) { | ||
| args = list( | ||
| width = width, height = height, | ||
| padding = padding, margin = margin, inset = inset, | ||
| renderer = renderer, ... | ||
| ) | ||
| mod = check_chart(canvas, chart, args) | ||
| if (!is.null(mod)) return(mod) | ||
|
|
||
| # Dimensions | ||
| chart$options = dropNulls(list( | ||
| width = width, | ||
| height = height, | ||
| autoFit = if (is.null(width)) TRUE | ||
| )) | ||
|
|
||
| # Layout spacing | ||
| chart$layout = c( | ||
| process_layout('padding', padding), | ||
| process_layout('margin', margin), | ||
| process_layout('inset', inset) | ||
| ) | ||
|
|
||
| # Renderer | ||
| if (!is.null(renderer)) { | ||
| r = tolower(renderer) | ||
| r = match.arg(r, c('canvas', 'svg', 'webgl')) | ||
| chart$renderer = r | ||
| } | ||
|
|
||
| # Extra top-level chart.options() args (e.g., clip, depth) | ||
| extra = list(...) | ||
| if (length(extra)) chart$canvas_extra = modifyList( | ||
| as.list(chart$canvas_extra), extra | ||
| ) | ||
|
|
||
| chart | ||
| } | ||
|
|
||
| #' Set Aesthetic Mappings | ||
| #' | ||
| #' Map data columns to visual channels (x, y, color, size, shape, etc.). | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -205,6 +205,7 @@ build_config = function(chart) { | |||||||
| config$slider = chart$sliders | ||||||||
| config$scrollbar = chart$scrollbars | ||||||||
| if (length(chart$layout)) config = modifyList(config, chart$layout) | ||||||||
| if (length(chart$canvas_extra)) config = modifyList(config, chart$canvas_extra) | ||||||||
|
|
||||||||
| # Theme: merge global option with per-chart theme | ||||||||
| theme = modifyList(as.list(getOption('gglite.theme')), as.list(chart$theme)) | ||||||||
|
|
@@ -225,6 +226,51 @@ build_config = function(chart) { | |||||||
|
|
||||||||
| # ---- HTML generation ---- | ||||||||
|
|
||||||||
| # Returns the effective renderer string for a chart ('canvas', 'svg', 'webgl'). | ||||||||
| # Per-chart setting (chart$renderer) takes precedence over the global option. | ||||||||
| effective_renderer = function(chart) { | ||||||||
| chart$renderer %||% tolower(getOption('gglite.renderer') %||% 'canvas') | ||||||||
| } | ||||||||
|
|
||||||||
| # Returns TRUE when the page should use g2.lite (non-canvas global option set, | ||||||||
| # or per-chart renderer is svg/webgl). | ||||||||
| needs_lite = function(chart) { | ||||||||
| global_r = tolower(getOption('gglite.renderer') %||% 'canvas') | ||||||||
| global_r != 'canvas' || isTRUE(chart$renderer %in% c('svg', 'webgl')) | ||||||||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. lite is needed if gglite.renderer is not null (even if it's set to 'canvas', we should still use lite)
Suggested change
|
||||||||
| } | ||||||||
|
|
||||||||
| # CDN URLs for a given renderer mode ('canvas' uses g2.min.js; 'svg'/'webgl' | ||||||||
| # use g2.lite.min.js + @antv/g + renderer-specific package). | ||||||||
| g2_cdn_urls = function(renderer = 'canvas') { | ||||||||
| if (renderer == 'canvas') | ||||||||
| return(c(g2_cdn(), g2_patches_cdn)) | ||||||||
| r_url = switch(renderer, | ||||||||
| svg = 'https://unpkg.com/@antv/g-svg', | ||||||||
| webgl = 'https://unpkg.com/@antv/g-webgl' | ||||||||
| ) | ||||||||
| c( | ||||||||
| 'https://unpkg.com/@antv/g', | ||||||||
| r_url, | ||||||||
| 'https://unpkg.com/@antv/g2@5/dist/g2.lite.min.js', | ||||||||
| g2_patches_cdn | ||||||||
| ) | ||||||||
| } | ||||||||
|
|
||||||||
| cdn_scripts = function(renderer = 'canvas') { | ||||||||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this function should receive a g2() object instead, and pass it to g2_cdn() instead; inside g2_cdn(), you test need_lite(); if true, return g2.min.js; otherwise return g2.lite urls according to the effective renderer of the g2() object |
||||||||
| sprintf('<script src="%s" defer></script>', g2_cdn_urls(renderer)) | ||||||||
| } | ||||||||
|
|
||||||||
| g2_html_page = function(body, renderer = 'canvas') { | ||||||||
| paste(c( | ||||||||
| '<!DOCTYPE html>', '<html>', '<head>', | ||||||||
| '<meta charset="utf-8">', | ||||||||
| cdn_scripts(renderer), | ||||||||
| '</head>', '<body>', | ||||||||
| body, | ||||||||
| '</body>', '</html>' | ||||||||
| ), collapse = '\n') | ||||||||
| } | ||||||||
|
|
||||||||
| #' Generate Chart HTML | ||||||||
| #' | ||||||||
| #' Create an HTML string containing a container `<div>` and a `<script>` block | ||||||||
|
|
@@ -284,6 +330,14 @@ chart_html = function(chart, id = NULL, width = NULL, height = NULL) { | |||||||
|
|
||||||||
| if (nzchar(style)) style = paste0(' style="', style, '"') | ||||||||
|
|
||||||||
| # Renderer setup: when using g2.lite (non-canvas renderer or global option | ||||||||
| # set), pass a raw JS renderer instantiation directly in the ctor. | ||||||||
| if (needs_lite(chart)) { | ||||||||
| r = effective_renderer(chart) | ||||||||
| r_ns = switch(r, svg = 'window.G.SVG', webgl = 'window.G.WebGL', canvas = 'window.G.Canvas2D') | ||||||||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. are you sure it's window.G.Canvas2D instead of window.G.Canvas? download https://unpkg.com/@antv/g-canvas and double check |
||||||||
| ctor$renderer = js(paste0('new ', r_ns, '.Renderer()')) | ||||||||
| } | ||||||||
|
|
||||||||
| if (is.null(id)) { | ||||||||
| div = paste0('<div data-gglite-container', style, '></div>\n') | ||||||||
| ctor$container = js('el') | ||||||||
|
|
@@ -301,21 +355,6 @@ chart_html = function(chart, id = NULL, width = NULL, height = NULL) { | |||||||
| paste0(div, '<script type="module">\n', spec_js, ctor_js, options_js, render_js, '</script>') | ||||||||
| } | ||||||||
|
|
||||||||
| cdn_scripts = function() { | ||||||||
| sprintf('<script src="%s" defer></script>', c(g2_cdn(), g2_patches_cdn)) | ||||||||
| } | ||||||||
|
|
||||||||
| g2_html_page = function(body) { | ||||||||
| paste(c( | ||||||||
| '<!DOCTYPE html>', '<html>', '<head>', | ||||||||
| '<meta charset="utf-8">', | ||||||||
| cdn_scripts(), | ||||||||
| '</head>', '<body>', | ||||||||
| body, | ||||||||
| '</body>', '</html>' | ||||||||
| ), collapse = '\n') | ||||||||
| } | ||||||||
|
|
||||||||
| #' Preview a Chart in the Viewer or Browser | ||||||||
| #' | ||||||||
| #' @param x A `g2` object. | ||||||||
|
|
@@ -324,7 +363,7 @@ g2_html_page = function(body) { | |||||||
| #' @export | ||||||||
| print.g2 = function(x, ...) { | ||||||||
| #TODO: xfun >= 0.57.3 no longer needs paste() | ||||||||
| xfun::html_view(g2_html_page(chart_html(x, ...))) | ||||||||
| xfun::html_view(g2_html_page(chart_html(x, ...), renderer = effective_renderer(x))) | ||||||||
| invisible(x) | ||||||||
| } | ||||||||
|
|
||||||||
|
|
@@ -343,7 +382,9 @@ knit_print.g2 = function(x, ...) { | |||||||
| html = chart_html(x) | ||||||||
| if (!isTRUE(knitr::opts_knit$get(.knitr.flag))) { | ||||||||
| knitr::opts_knit$set(setNames(list(TRUE), .knitr.flag)) | ||||||||
| html = paste(c(cdn_scripts(), html), collapse = '\n') | ||||||||
| # CDN choice is driven by global option so all charts use consistent scripts | ||||||||
| global_r = tolower(getOption('gglite.renderer') %||% 'canvas') | ||||||||
| html = paste(c(cdn_scripts(global_r), html), collapse = '\n') | ||||||||
| } | ||||||||
| structure(html, class = c('knit_asis', 'html')) | ||||||||
| } | ||||||||
|
|
@@ -358,7 +399,7 @@ knit_print.g2 = function(x, ...) { | |||||||
| #' @param ... Ignored. | ||||||||
| #' @return A character string of complete HTML. | ||||||||
| #' @noRd | ||||||||
| repr_html.g2 = function(obj, ...) g2_html_page(chart_html(obj)) | ||||||||
| repr_html.g2 = function(obj, ...) g2_html_page(chart_html(obj), renderer = effective_renderer(obj)) | ||||||||
|
|
||||||||
| #' Text Representation for Jupyter Notebooks | ||||||||
| #' | ||||||||
|
|
@@ -381,7 +422,7 @@ repr_text.g2 = function(obj, ...) { | |||||||
| #' @importFrom xfun record_print | ||||||||
| #' @export | ||||||||
| record_print.g2 = function(x, ...) { | ||||||||
| xfun::new_record(c(cdn_scripts(), chart_html(x, ...), ''), 'asis') | ||||||||
| xfun::new_record(c(cdn_scripts(effective_renderer(x)), chart_html(x, ...), ''), 'asis') | ||||||||
| } | ||||||||
|
|
||||||||
| register_methods = function(pkgs, generics) { | ||||||||
|
|
||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
g2() should not have this
optionsat all; these default options should go to build_config() or chart_html()