Add Shiny bindings and adopt htmlwidgets for visualization rendering#1
Merged
Conversation
3e7fc1c to
8d17b35
Compare
Collaborator
|
This looks good - thanks a bunch. I did some work on the engine in the meantime to better support native knitr sizing options so you'll have to incorporate that. Otherwise it LGTM |
Adds ggsqlOutput/renderGgsql for rendering ggsql visualizations in Shiny apps, backed by a <ggsql-viz> custom element that renders Vega-Lite specs via vegaEmbed. Includes ggsql_session_reader() for managing DuckDB reader lifecycle per session. All dependency scripts use defer for correct load ordering. The Shiny output binding is jQuery-free. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Documents where inst/lib/ JS files are sourced from (jsdelivr CDN) and pins their versions for reproducible updates. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Custom elements default to display:inline, which silently ignores width/height CSS. Move display:block into the component stylesheet (along with overflow:hidden) so it applies in both knitr and Shiny paths, and remove the now-redundant inline display:block from tag.R. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Restore the responsive scaling behavior from the old vegalite_html: render into an inner container with min-width 450px, and use a ResizeObserver to apply transform:scale() when the host element is narrower. Also add @ts-check + JSDoc annotations throughout. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Drop the default `envir = knitr::knit_global()` so callers must pass the environment explicitly. Also remove a stale section header and document the GC-based reader cleanup in Shiny. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
b5b040a to
fb4c95b
Compare
Replace hand-rolled HTML rendering (custom <ggsql-viz> element, manual Shiny OutputBinding, htmltools tag building) with a standard htmlwidgets widget. All HTML output now flows through ggsql_widget() which calls htmlwidgets::createWidget(). This gives us bslib fill support, bindCache(), Shiny size reporting, RStudio Viewer pane sizing, and saveWidget() potential with one rendering path to maintain. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
29b4275 to
5788e57
Compare
Reduces inline style churn in renderValue by using CSS classes for display, overflow, alignment, and container sizing. Only truly dynamic properties (aspect-ratio, scale transform) remain as inline styles. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…dgets - Remove `asp`, `caption`, and `align` params from `ggsql_widget()` and the JS custom element. knitr pre-computes fig.asp into fig.height, and htmlwidgets' `resolveSizing` handles width/height via `knitr.figure = TRUE`. - Route both engine and knit_print.Spec through `knitr::knit_print(widget)` so htmlwidgets resolves sizing from knitr chunk options natively. - Warn when `fig.cap` is used with the interactive vegalite writer, since custom engine output bypasses knitr's figure caption machinery. - Attach CSS via explicit htmlDependency (top-level `stylesheet` YAML field is not read by htmlwidgets). - Fix test helpers to work outside a real pandoc session (`screenshot.force = FALSE`, `rmarkdown.pandoc.to = "html"`). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…"container"
Custom elements default to display:inline, which reports 0 for
clientWidth/clientHeight. In Shiny the CSS dependency loads after
renderValue runs, so Vega's ResizeObserver-based container sizing saw 0
and rendered a zero-width SVG.
The fix unifies the simple and compound spec paths: both now read
clientWidth/clientHeight and pass explicit pixel dimensions into the
spec. For simple specs, autosize: { type: "fit", contains: "padding" }
ensures the SVG fits within the given bounds. widget_html prepends
display:block inline so the element is block-level from first paint.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Express all htmlwidget dependencies (vega, vega-lite, vega-embed, ggsql-viz-sizing, ggsql-viz-styles) programmatically via vegalite_dependencies() instead of the static YAML file. This makes dependencies composable for future writer types. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Separates third-party Vega libs (vega, vega-lite, vega-embed) from ggsql's own widget assets (sizing JS, styles CSS) so future writers can reuse one set without the other. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Detect facet/hconcat/vconcat/concat specs and compute explicit pixel dimensions from the container size, with padding and legend heuristics ported from querychat's Python implementation. Single-view specs continue to use width/height: "container". On resize, compound specs re-embed when width drifts >20%, otherwise use CSS transform. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…hain Move the htmlwidgets JavaScript source from a single hand-edited .js file into a TypeScript project under srcts/. Adds esbuild bundling, tsc type checking, and vitest tests. Extracts compound chart sizing logic into its own module and renames ggsql_viz -> ggsql_vega throughout.
…tBox Fix compound chart sizing to detect row/column grid facets (not just wrapped facets) and prefer clientHeight over inline style when reading the host element bounding box.
cpsievert
commented
Apr 24, 2026
| Imports: | ||
| cli, | ||
| htmltools, | ||
| htmlwidgets, |
Contributor
Author
There was a problem hiding this comment.
FWIW, the next release of htmlwidgets will move rmarkdown from Suggests to Import
Collaborator
There was a problem hiding this comment.
hmm... I guess this is not too horrible given the prevalence of RMarkdown and the fact that this package provides a knitr engine for it
Add srcts/, tools/check-js.mjs, and tools/update-vega.sh to .Rbuildignore so they don't end up in the CRAN tarball. Move Vega version strings into a generated R/vega-versions.R file that tools/update-vega.sh writes automatically, so version numbers live in one place instead of three.
The bin shim shadowed the real tsc binary to rewrite bare `tsc tsconfig.json` into `tsc -p tsconfig.json`, but the typecheck script already passes `-p` explicitly. Remove the wrapper and the package.json bin field.
Contributor
Author
|
@thomasp85 ok, ready for your eyes now |
Collaborator
|
Thanks for your work on this, Carson |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
ggsqlOutput()/renderGgsql()/ggsql_session_reader()forembedding ggsql visualizations in Shiny apps.
<script>+sprintf()HTML generation inthe knitr engine with an htmlwidgets widget
(
ggsql_vega), which now handles all HTML rendering contexts: knitr chunks,print(), and Shiny.Why htmlwidgets?
The old
vegalite_html()path generated standalone JavaScript blobs for everyvisualization — each one loaded Vega from a CDN, managed its own script
deduplication via a global promise, and implemented responsive scaling inline.
This worked for simple static documents but couldn't participate in Shiny's
output binding protocol, and the CDN dependency meant visualizations didn't
render offline or inside networks that block external scripts.
htmlwidgets gives us a standard rendering surface that works everywhere R
produces HTML. Vega, Vega-Lite, and Vega-Embed are now vendored as
htmlDependencyobjects underinst/htmlwidgets/lib/, so dependencydeduplication and script loading are handled by htmltools automatically. The
Shiny output binding comes for free from the htmlwidgets framework.
Shiny API
ggsqlOutput(outputId)/renderGgsql(expr)— standard Shinyoutput/render pair.
renderGgsql()accepts either a raw ggsql query string ora pre-computed
Spec. Query strings can user:varnamedata references, whichresolve against the render expression's environment (not just
knit_global()).ggsql_session_reader(reader)— registers a session-scoped default readerso you don't have to pass one to every
renderGgsql()call. Cleaned upautomatically when the session ends.
Widget runtime
The browser-side code lives in
srcts/as TypeScript and is built toinst/htmlwidgets/ggsql_vega.js. It handles:vegaEmbedwith responsive resize.dimensions across sub-plots so they fill the host container.
Dropped:
fig.capandfig.alignfor interactive outputThe old inline-HTML path supported
fig.cap(wrapping output in a<figure>/<figcaption>) andfig.align(CSS margin alignment) for thevegalitewriter. These are dropped for interactive HTML output, consistent with how
htmlwidgets are treated generally.
In practice, the previous
fig.capapproach didn't work in Quarto anyway, sinceQuarto manages figure environments and captions at a higher level and the raw
<figure>wrapper was invisible to it. Thevegalite_svgandvegalite_pngwriters still support
fig.capsince their output passes through knitr's normalgraphics pipeline.
The engine now emits a warning if
fig.capis set with thevegalitewriter,directing users to the SVG/PNG writers for captioned figures.
Changes to knitr engine output
The engine's
vegalitewriter path now returns an htmlwidgets object instead ofa raw HTML string. This means sizing, dependency management, and responsive
behavior are delegated to the htmlwidgets framework and its sizing policy rather
than being implemented ad-hoc in inline JavaScript.
resolve_data_refs()gained anenvirparameter (defaulting toknit_global()in the engine, the render expression's environment in Shiny) sothe same resolution logic works in both contexts.