Skip to content

feat(pkg-r): add ggsql visualization tool#224

Draft
cpsievert wants to merge 8 commits intomainfrom
feat/ggsql-integration-r-v2
Draft

feat(pkg-r): add ggsql visualization tool#224
cpsievert wants to merge 8 commits intomainfrom
feat/ggsql-integration-r-v2

Conversation

@cpsievert
Copy link
Copy Markdown
Contributor

@cpsievert cpsievert commented Apr 28, 2026

Summary

R port of #201. Adds an opt-in visualize tool to querychat's R package, enabling LLM-generated data visualizations via ggsql. When enabled, the LLM can write ggsql queries (SQL + VISUALISE clause) that are executed and rendered as interactive charts inline in the chat.

Usage

Install

pak::pak("posit-dev/ggsql-r")

Example app

library(querychat)

qc <- QueryChat$new(
  palmerpenguins::penguins,
  tools = c("update", "query", "visualize")
)

qc$app()

The visualize tool is opt-in — include "visualize" in the tools vector alongside "query" and/or "update" to enable it.

What it looks like

Charts render inline in the chat with a collapsible footer showing the ggsql query (with syntax highlighting), a copy button, and save options:

Screenshot 2026-03-20 at 7 04 42 PM

Key changes

  • visualize tool (opt-in via tools = c("query", "visualize")): The LLM writes a full ggsql query, which is executed against the data source and rendered using ggsql's native Shiny bindings (ggsql::renderGgsql / ggsql::ggsqlOutput).
  • Two-stage ggsql pipeline (querychat_viz.R): Uses DataSource for SQL execution (preserving database pushdown), then feeds results into ggsql's DuckDB reader for VISUALISE processing.
  • Inline chart rendering: Charts render directly in the chat stream via Shiny's dynamic output system, with JS/CSS assets for the footer (show query toggle, save as PNG/SVG).
  • PNG feedback to LLM: A static PNG is generated (best-effort, requires V8 + rsvg) and sent back to the LLM so it can see what it produced.
  • collapsed parameter on querychat_query: Preparatory queries (e.g., inspecting data before visualizing) can start collapsed so the chart remains the focal point.
  • Prompt restructuring: System and tool prompts were updated to match the Python package — visualization best practices, ggsql syntax reference, and conditional sections based on which tools are enabled.
  • Bookmark support: Viz widget state is saved/restored during Shiny bookmarking.
  • Viz state is internal only: No public accessor methods or reactive values for viz state — consistent with the Python implementation.

Test plan

  • Unit tests for execute_ggsql, extract_visualise_table, has_layer_level_source
  • Unit tests for tool_visualize_dashboard (creation, callback, error handling)
  • Unit tests for truncate_error
  • Unit tests for collapsed parameter on query tool
  • Unit tests for viz prompt conditionals in system prompt
  • Full test suite passes (622 tests, 0 failures)

Port of the Python ggsql visualization feature (#201) to the R package.

Adds an opt-in `visualize` tool that enables LLM-generated data
visualizations via ggsql. When enabled, the LLM writes ggsql queries
(SQL + VISUALISE clause) that are executed and rendered as interactive
charts inline in the chat using ggsql's native Shiny bindings.
@cpsievert cpsievert force-pushed the feat/ggsql-integration-r-v2 branch from cd14057 to 5731d0a Compare April 28, 2026 22:54
cpsievert and others added 2 commits April 28, 2026 19:02
Replace shiny-workflows reusable workflows with a custom workflow
that installs ggsql's build prerequisites (Node.js, tree-sitter-cli,
ODBC) before R dependency installation. The previous workflow failed
because pak couldn't build ggsql from source without tree-sitter-cli.
Comment thread pkg-r/R/querychat_viz.R
Comment on lines +73 to +77
if (has_keyword) {
rlang::abort(
"VISUALISE clause was not recognized. VISUALISE and MAPPING accept column names only — no SQL expressions, CAST(), or functions. Move all data transformations to the SELECT clause, then reference the resulting column by name in VISUALISE."
)
}
Copy link
Copy Markdown
Contributor Author

@cpsievert cpsievert Apr 29, 2026

Choose a reason for hiding this comment

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

This may not be necessary anymore posit-dev/ggsql#389

@cpsievert cpsievert marked this pull request as draft April 29, 2026 14:14
Comment on lines +533 to +545
5. **Column casing in VISUALISE**: DuckDB lowercases unquoted column names in query results, and VISUALISE validates column references **case-sensitively**. If your source table has uppercase column names (e.g., from Snowflake), you **must** alias them to lowercase in the SELECT clause:
```sql
-- WRONG: VISUALISE references uppercase name, but DuckDB lowercases it in results
SELECT ROOM_TYPE, COUNT(*) AS listings FROM airbnb
VISUALISE ROOM_TYPE AS x, listings AS y
DRAW bar

-- CORRECT: Alias to lowercase, then reference the alias
SELECT ROOM_TYPE AS room_type, COUNT(*) AS listings FROM airbnb
VISUALISE room_type AS x, listings AS y
DRAW bar
```
As a general rule, always use lowercase column names and aliases in both SELECT and VISUALISE clauses.
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.

May not be necessary after posit-dev/ggsql#374

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant