From ffd7688fea24b5f14c97b8d965fdbe57a4874ecb Mon Sep 17 00:00:00 2001 From: Carson Date: Mon, 12 Oct 2026 17:41:04 -0500 Subject: [PATCH 1/4] docs(prompts): update ggsql syntax reference for ggsql 0.4-0.5 The shared ggsql-syntax.md system prompt predates ggsql 0.4.0: - The range layer's `width` setting was renamed to `hinge` (0.4.0); the old `SETTING width => null` now fails validation, so update the range bullet and lollipop example. - VISUALISE FROM now takes exactly one bare source (0.5.0); aliases, joins, and comma-separated tables are parse errors. - Note 3 rewritten: layer-level FROM sources work where querychat runs through a ggsql caching reader; the single-final-relation pattern stays as the portable recommendation. - Document new features: `aggregate` setting on identity-stat layers, jitter position, boxplot hinge/side settings, categorical-aesthetic omission for boxplot/violin/range, polar radar setting, rich-text label parsing (+ `parse => false`), and `minor_breaks`. All claims verified by executing the queries against ggsql 0.5.2. --- pkg-py/src/querychat/prompts/ggsql-syntax.md | 20 +++++++++++++++++--- pkg-r/inst/prompts/ggsql-syntax.md | 20 +++++++++++++++++--- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/pkg-py/src/querychat/prompts/ggsql-syntax.md b/pkg-py/src/querychat/prompts/ggsql-syntax.md index 868d7c334..6552a2f0a 100644 --- a/pkg-py/src/querychat/prompts/ggsql-syntax.md +++ b/pkg-py/src/querychat/prompts/ggsql-syntax.md @@ -40,6 +40,8 @@ VISUALISE date AS x, revenue AS y DRAW line ``` +`VISUALISE FROM` takes exactly one **bare** source — an alias (`FROM tbl AS t`), a join, or a comma-separated list of tables is a parse error. To combine tables, join them in the SELECT clause and visualise the result. + ### Mapping Styles | Style | Syntax | Use When | @@ -79,9 +81,11 @@ DRAW geom_type - `arrow` draws arrows between two points. Requires `x`, `y`, `xend`, `yend` aesthetics. - `rule` draws full-span reference lines. Map a value to `y` for a horizontal line or `x` for a vertical line. Optionally map `slope` to create diagonal reference lines: `y = a + slope * x` (when `y` is mapped) or `x = a + slope * y` (when `x` is mapped). - `rect` draws rectangles. Pick 2 per axis from center (`x`/`y`), min (`xmin`/`ymin`), max (`xmax`/`ymax`), `width`, `height`. Or just map center (defaults to width/height of 1). -- `range` displays interval marks. Requires `x`, `ymin`, `ymax` for vertical intervals, or `y`, `xmin`, `xmax` for horizontal intervals. Use it for confidence intervals, lollipops, and candlestick-style ranges. Setting `width => null` hides the hinges. +- `range` displays interval marks. Requires `x`, `ymin`, `ymax` for vertical intervals, or `y`, `xmin`, `xmax` for horizontal intervals. Use it for confidence intervals, lollipops, and candlestick-style ranges. The `hinge` setting controls the width of the endpoint caps in points (default 10); set `hinge => null` to hide the hinges. - `segment` draws arbitrary connections between two points and requires `x`, `y`, `xend`, and `yend`. - `line` and `path` support continuously varying `linewidth`, `stroke`, and `opacity` aesthetics within groups. +- Use `SETTING position => 'jitter'` on `point` layers to spread overlapping points along a discrete axis (strip-plot style). Jitter settings: `width` (0–1, default 0.4), `distribution` (`'uniform'` default, `'normal'`, `'density'`, `'intensity'`), and `side` (`'both'` default; a one-sided value pairs with a half-violin or half-boxplot on the same band). +- Identity-stat layers (`point`, `line`, `area`, `bar`, `ribbon`, `range`, `segment`, `arrow`, `rule`, `text`) accept an `aggregate` setting that collapses each group (defined by `PARTITION BY` plus all discrete mappings) to one row, replacing every numeric mapping with its aggregated value — summarising in the layer instead of writing a `GROUP BY`. Takes `''` (applies to every numeric mapping; e.g. `count`, `sum`, `min`, `max`, `mean`, `median`) or `':'` to target one aesthetic. Example: `VISUALISE species AS x, bill_length AS y FROM penguins` + `DRAW bar SETTING aggregate => 'mean'` draws the mean bill length per species. **Aesthetics (MAPPING):** @@ -147,7 +151,7 @@ Some layers compute statistics. Use REMAPPING to access computed values: | `smooth` | `intensity` | `intensity AS y` | | `boxplot` | `value`, `type` | `value AS y` | -`boxplot` displays box-and-whisker plots. Settings: `outliers` (`true` default — show outlier points), `coef` (`1.5` default — whisker fence coefficient), `width` (`0.9` default — box width, 0–1). +`boxplot` displays box-and-whisker plots. Settings: `outliers` (`true` default — show outlier points), `coef` (`1.5` default — whisker fence coefficient), `width` (`0.9` default — box width, 0–1), `hinge` (whisker-cap width in points, default `null` = no caps), `side` (`'both'` default; `'left'`/`'bottom'` or `'right'`/`'top'` for a half-boxplot that pairs with a half-violin or one-sided jitter on the same band). `smooth` fits a trendline to data. Settings: `method` (`'nw'` or `'nadaraya-watson'` default kernel regression, `'ols'` for OLS linear, `'tls'` for total least squares). NW-only settings: `bandwidth` (numeric), `adjust` (multiplier, default 1), `kernel` (`'gaussian'` default, `'epanechnikov'`, `'triangular'`, `'rectangular'`, `'uniform'`, `'biweight'`, `'quartic'`, `'cosine'`). @@ -155,6 +159,8 @@ Some layers compute statistics. Use REMAPPING to access computed values: `violin` displays mirrored KDE curves for groups. Requires both `x` (categorical) and `y` (continuous). Accepts the same bandwidth/adjust/kernel settings as density. Use `REMAPPING intensity AS offset` to reflect group size differences. Additional settings: `side` (`'both'` default, `'left'`/`'bottom'`, `'right'`/`'top'` — for half-violin/ridgeline plots), `width` (any value > 0; values > 1 enable ridgeline-style overlapping). +`boxplot`, `violin`, and `range` may omit the categorical aesthetic (e.g. `DRAW boxplot MAPPING salary AS y`) to summarise all rows as a single mark. + **Examples:** ```sql @@ -297,6 +303,7 @@ PROJECT TO polar SETTING start => -90, end => 90 -- half-circle gauge - `start` — starting angle in degrees (0 = 12 o'clock, 90 = 3 o'clock) - `end` — ending angle in degrees (default: start + 360; use for partial arcs/gauges) - `inner` — inner radius as proportion 0–1 (0 = full pie, 0.5 = donut with 50% hole) +- `radar` — `true` displays discrete-angle data as a radar plot - `clip` — clip out-of-bounds data (default `true`) **Axis flipping:** To create horizontal bar charts or flip axes, use `PROJECT y, x TO cartesian`. This maps anything on `y` to the horizontal axis and `x` to the vertical axis. @@ -452,7 +459,7 @@ DRAW text MAPPING n AS label SETTING offset => (0, -11), fill => 'white' ```sql SELECT ROUND(bill_dep) AS bill_dep, COUNT(*) AS n FROM penguins GROUP BY 1 VISUALISE bill_dep AS x, n AS y -DRAW range MAPPING 0 AS ymin, n AS ymax SETTING width => null +DRAW range MAPPING 0 AS ymin, n AS ymax SETTING hinge => null DRAW point ``` @@ -551,3 +558,10 @@ PROJECT TO polar SETTING inner => 0.5 DRAW bar MAPPING category AS x, subcategory AS fill SETTING position => 'dodge' -- side-by-side DRAW bar MAPPING category AS x, subcategory AS fill SETTING position => 'stack', total => 1 -- proportional ``` + +## Vega-Lite rendering + +querychat draws all charts with ggsql's **Vega-Lite** writer, not its native SVG/PDF writers. Writer-dependent behavior to know: + +- **Label text renders literally**: markdown such as `**bold**` or `` `code` `` would appear verbatim in the chart — keep label text plain. +- **The `minor_breaks` scale setting is ignored** — no minor gridlines are drawn. diff --git a/pkg-r/inst/prompts/ggsql-syntax.md b/pkg-r/inst/prompts/ggsql-syntax.md index 868d7c334..6552a2f0a 100644 --- a/pkg-r/inst/prompts/ggsql-syntax.md +++ b/pkg-r/inst/prompts/ggsql-syntax.md @@ -40,6 +40,8 @@ VISUALISE date AS x, revenue AS y DRAW line ``` +`VISUALISE FROM` takes exactly one **bare** source — an alias (`FROM tbl AS t`), a join, or a comma-separated list of tables is a parse error. To combine tables, join them in the SELECT clause and visualise the result. + ### Mapping Styles | Style | Syntax | Use When | @@ -79,9 +81,11 @@ DRAW geom_type - `arrow` draws arrows between two points. Requires `x`, `y`, `xend`, `yend` aesthetics. - `rule` draws full-span reference lines. Map a value to `y` for a horizontal line or `x` for a vertical line. Optionally map `slope` to create diagonal reference lines: `y = a + slope * x` (when `y` is mapped) or `x = a + slope * y` (when `x` is mapped). - `rect` draws rectangles. Pick 2 per axis from center (`x`/`y`), min (`xmin`/`ymin`), max (`xmax`/`ymax`), `width`, `height`. Or just map center (defaults to width/height of 1). -- `range` displays interval marks. Requires `x`, `ymin`, `ymax` for vertical intervals, or `y`, `xmin`, `xmax` for horizontal intervals. Use it for confidence intervals, lollipops, and candlestick-style ranges. Setting `width => null` hides the hinges. +- `range` displays interval marks. Requires `x`, `ymin`, `ymax` for vertical intervals, or `y`, `xmin`, `xmax` for horizontal intervals. Use it for confidence intervals, lollipops, and candlestick-style ranges. The `hinge` setting controls the width of the endpoint caps in points (default 10); set `hinge => null` to hide the hinges. - `segment` draws arbitrary connections between two points and requires `x`, `y`, `xend`, and `yend`. - `line` and `path` support continuously varying `linewidth`, `stroke`, and `opacity` aesthetics within groups. +- Use `SETTING position => 'jitter'` on `point` layers to spread overlapping points along a discrete axis (strip-plot style). Jitter settings: `width` (0–1, default 0.4), `distribution` (`'uniform'` default, `'normal'`, `'density'`, `'intensity'`), and `side` (`'both'` default; a one-sided value pairs with a half-violin or half-boxplot on the same band). +- Identity-stat layers (`point`, `line`, `area`, `bar`, `ribbon`, `range`, `segment`, `arrow`, `rule`, `text`) accept an `aggregate` setting that collapses each group (defined by `PARTITION BY` plus all discrete mappings) to one row, replacing every numeric mapping with its aggregated value — summarising in the layer instead of writing a `GROUP BY`. Takes `''` (applies to every numeric mapping; e.g. `count`, `sum`, `min`, `max`, `mean`, `median`) or `':'` to target one aesthetic. Example: `VISUALISE species AS x, bill_length AS y FROM penguins` + `DRAW bar SETTING aggregate => 'mean'` draws the mean bill length per species. **Aesthetics (MAPPING):** @@ -147,7 +151,7 @@ Some layers compute statistics. Use REMAPPING to access computed values: | `smooth` | `intensity` | `intensity AS y` | | `boxplot` | `value`, `type` | `value AS y` | -`boxplot` displays box-and-whisker plots. Settings: `outliers` (`true` default — show outlier points), `coef` (`1.5` default — whisker fence coefficient), `width` (`0.9` default — box width, 0–1). +`boxplot` displays box-and-whisker plots. Settings: `outliers` (`true` default — show outlier points), `coef` (`1.5` default — whisker fence coefficient), `width` (`0.9` default — box width, 0–1), `hinge` (whisker-cap width in points, default `null` = no caps), `side` (`'both'` default; `'left'`/`'bottom'` or `'right'`/`'top'` for a half-boxplot that pairs with a half-violin or one-sided jitter on the same band). `smooth` fits a trendline to data. Settings: `method` (`'nw'` or `'nadaraya-watson'` default kernel regression, `'ols'` for OLS linear, `'tls'` for total least squares). NW-only settings: `bandwidth` (numeric), `adjust` (multiplier, default 1), `kernel` (`'gaussian'` default, `'epanechnikov'`, `'triangular'`, `'rectangular'`, `'uniform'`, `'biweight'`, `'quartic'`, `'cosine'`). @@ -155,6 +159,8 @@ Some layers compute statistics. Use REMAPPING to access computed values: `violin` displays mirrored KDE curves for groups. Requires both `x` (categorical) and `y` (continuous). Accepts the same bandwidth/adjust/kernel settings as density. Use `REMAPPING intensity AS offset` to reflect group size differences. Additional settings: `side` (`'both'` default, `'left'`/`'bottom'`, `'right'`/`'top'` — for half-violin/ridgeline plots), `width` (any value > 0; values > 1 enable ridgeline-style overlapping). +`boxplot`, `violin`, and `range` may omit the categorical aesthetic (e.g. `DRAW boxplot MAPPING salary AS y`) to summarise all rows as a single mark. + **Examples:** ```sql @@ -297,6 +303,7 @@ PROJECT TO polar SETTING start => -90, end => 90 -- half-circle gauge - `start` — starting angle in degrees (0 = 12 o'clock, 90 = 3 o'clock) - `end` — ending angle in degrees (default: start + 360; use for partial arcs/gauges) - `inner` — inner radius as proportion 0–1 (0 = full pie, 0.5 = donut with 50% hole) +- `radar` — `true` displays discrete-angle data as a radar plot - `clip` — clip out-of-bounds data (default `true`) **Axis flipping:** To create horizontal bar charts or flip axes, use `PROJECT y, x TO cartesian`. This maps anything on `y` to the horizontal axis and `x` to the vertical axis. @@ -452,7 +459,7 @@ DRAW text MAPPING n AS label SETTING offset => (0, -11), fill => 'white' ```sql SELECT ROUND(bill_dep) AS bill_dep, COUNT(*) AS n FROM penguins GROUP BY 1 VISUALISE bill_dep AS x, n AS y -DRAW range MAPPING 0 AS ymin, n AS ymax SETTING width => null +DRAW range MAPPING 0 AS ymin, n AS ymax SETTING hinge => null DRAW point ``` @@ -551,3 +558,10 @@ PROJECT TO polar SETTING inner => 0.5 DRAW bar MAPPING category AS x, subcategory AS fill SETTING position => 'dodge' -- side-by-side DRAW bar MAPPING category AS x, subcategory AS fill SETTING position => 'stack', total => 1 -- proportional ``` + +## Vega-Lite rendering + +querychat draws all charts with ggsql's **Vega-Lite** writer, not its native SVG/PDF writers. Writer-dependent behavior to know: + +- **Label text renders literally**: markdown such as `**bold**` or `` `code` `` would appear verbatim in the chart — keep label text plain. +- **The `minor_breaks` scale setting is ignored** — no minor gridlines are drawn. From 2cd69f2a79e57e8efce9765341d9225ccad238d9 Mon Sep 17 00:00:00 2001 From: cpsievert Date: Mon, 12 Oct 2026 22:44:01 +0000 Subject: [PATCH 2/4] `devtools::document()` (GitHub Actions) --- pkg-r/DESCRIPTION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg-r/DESCRIPTION b/pkg-r/DESCRIPTION index 549307672..e544a17de 100644 --- a/pkg-r/DESCRIPTION +++ b/pkg-r/DESCRIPTION @@ -61,7 +61,7 @@ Suggests: withr VignetteBuilder: knitr -Config/roxygen2/version: 8.1.0 +Config/roxygen2/version: 8.1.1 Config/testthat/edition: 3 Config/testthat/parallel: true Encoding: UTF-8 From 30139c43547e78d4a98743ee70148c9ec342f7ca Mon Sep 17 00:00:00 2001 From: Carson Date: Mon, 12 Oct 2026 18:35:57 -0500 Subject: [PATCH 3/4] feat(viz): require ggsql >= 0.5.2 for the visualize tool The system prompt now documents ggsql 0.5.x-only syntax (hinge, aggregate, jitter, radar, rich-text labels, minor_breaks), so running the visualize tool against an older ggsql would fail at query-validation time. - pyproject.toml: viz and dev extras require ggsql>=0.5.2 (was >=0.3.2) - R DESCRIPTION: ggsql (>= 0.5.2) in Suggests - Runtime guards (Python has_viz_deps(), R check_viz_deps()) treat ggsql < 0.5.2 like a missing install: warn once and drop the visualize tool, with messages stating the 0.5.2 requirement --- pkg-py/src/querychat/_querychat_base.py | 4 ++-- pkg-py/src/querychat/_viz_utils.py | 27 ++++++++++++++++++++++--- pkg-r/DESCRIPTION | 2 +- pkg-r/R/QueryChat.R | 6 ++++-- pyproject.toml | 4 ++-- 5 files changed, 33 insertions(+), 10 deletions(-) diff --git a/pkg-py/src/querychat/_querychat_base.py b/pkg-py/src/querychat/_querychat_base.py index 0d2535b78..f516c1825 100644 --- a/pkg-py/src/querychat/_querychat_base.py +++ b/pkg-py/src/querychat/_querychat_base.py @@ -830,8 +830,8 @@ def normalize_tools( return resolved if resolved is not None and has_viz_tool(resolved) and not has_viz_deps(): warnings.warn( - "Visualization tools require ggsql, altair, shinywidgets, and " - "vl-convert-python. Install them with: pip install querychat[viz]. " + "Visualization tools require ggsql>=0.5.2, altair, shinywidgets, " + "and vl-convert-python. Install them with: pip install querychat[viz]. " "Continuing without the 'visualize' tool.", stacklevel=2, ) diff --git a/pkg-py/src/querychat/_viz_utils.py b/pkg-py/src/querychat/_viz_utils.py index 204d34486..839f1a091 100644 --- a/pkg-py/src/querychat/_viz_utils.py +++ b/pkg-py/src/querychat/_viz_utils.py @@ -2,12 +2,15 @@ from __future__ import annotations +import importlib.metadata import importlib.util from htmltools import HTMLDependency, tags from .__version import __version__ +MIN_GGSQL_VERSION = (0, 5, 2) + def has_viz_tool(tools: set[str] | None) -> bool: """Check if visualize is among the configured tools.""" @@ -16,10 +19,28 @@ def has_viz_tool(tools: set[str] | None) -> bool: def has_viz_deps() -> bool: """Check whether visualization dependencies (ggsql, altair, shinywidgets, vl-convert-python) are installed.""" - return all( - importlib.util.find_spec(pkg) is not None + if any( + importlib.util.find_spec(pkg) is None for pkg in ("ggsql", "altair", "shinywidgets", "vl_convert") - ) + ): + return False + return ggsql_version() >= MIN_GGSQL_VERSION + + +def ggsql_version() -> tuple[int, ...]: + """Installed ggsql version as a comparable tuple (e.g. ``(0, 5, 2)``).""" + raw = importlib.metadata.version("ggsql") + parts: list[int] = [] + for piece in raw.split("."): + num = "" + for ch in piece: + if not ch.isdigit(): + break + num += ch + if not num: + break + parts.append(int(num)) + return tuple(parts) PRELOAD_WIDGET_ID = "__querychat_preload_viz__" diff --git a/pkg-r/DESCRIPTION b/pkg-r/DESCRIPTION index e544a17de..f98170564 100644 --- a/pkg-r/DESCRIPTION +++ b/pkg-r/DESCRIPTION @@ -47,7 +47,7 @@ Suggests: dplyr, DT, duckdb, - ggsql, + ggsql (>= 0.5.2), knitr, later, nanoparquet, diff --git a/pkg-r/R/QueryChat.R b/pkg-r/R/QueryChat.R index 91e2ccc18..ad3cb876e 100644 --- a/pkg-r/R/QueryChat.R +++ b/pkg-r/R/QueryChat.R @@ -1652,13 +1652,15 @@ normalize_tools <- function(tools) { check_viz_deps <- function(tools) { if ( - is.null(tools) || !"visualize" %in% tools || rlang::is_installed("ggsql") + is.null(tools) || + !"visualize" %in% tools || + rlang::is_installed("ggsql", version = "0.5.2") ) { return(tools) } rlang::warn( c( - 'The "visualize" tool requires the {.pkg ggsql} package.', + 'The "visualize" tool requires {.pkg ggsql} 0.5.2 or later.', "i" = 'Install it with `install.packages("ggsql")`; continuing without the "visualize" tool.' ), .frequency = "once", diff --git a/pyproject.toml b/pyproject.toml index 56ca89e43..d8bea98de 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,7 +62,7 @@ gradio = ["gradio>=6.0"] dash = ["dash-ag-grid>=31.0", "dash[async]>=3.1", "dash-bootstrap-components>=2.0", "pandas"] # Visualization with ggsql viz = [ - "ggsql>=0.3.2", + "ggsql>=0.5.2", "altair>=6.0", "shinywidgets>=0.8.0", "vl-convert-python>=1.9.0", @@ -117,7 +117,7 @@ dev = [ "ibis-framework[duckdb]>=9.0.0", # see note on the `ibis` extra above "sqlglot<30.18.0", - "ggsql>=0.3.2", + "ggsql>=0.5.2", "altair>=6.0", "shinywidgets>=0.8.0", "vl-convert-python>=1.9.0", From b369f6a9fa6fd30b30dff8c6d0678021f4de652c Mon Sep 17 00:00:00 2001 From: Carson Date: Tue, 13 Oct 2026 11:11:57 -0500 Subject: [PATCH 4/4] docs(prompts): note LABEL caption is ignored under Vega-Lite rendering The Vega-Lite writer has no caption equivalent and silently drops it (unlike the native SVG/PDF writers), so stop advertising caption in the LABEL section and call it out in the Vega-Lite rendering section instead. Direct source attributions to subtitle, which does render. --- pkg-py/src/querychat/prompts/ggsql-syntax.md | 7 ++++--- pkg-r/inst/prompts/ggsql-syntax.md | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/pkg-py/src/querychat/prompts/ggsql-syntax.md b/pkg-py/src/querychat/prompts/ggsql-syntax.md index 6552a2f0a..f9ad904ab 100644 --- a/pkg-py/src/querychat/prompts/ggsql-syntax.md +++ b/pkg-py/src/querychat/prompts/ggsql-syntax.md @@ -335,14 +335,14 @@ SCALE panel FROM ('Biscoe', 'Dream') ### LABEL Clause -Use LABEL for axis labels, subtitles, and captions. Do NOT use `LABEL title => ...` — the tool's `title` parameter handles chart titles. Set a label to `null` to suppress it. +Use LABEL for axis labels and subtitles. Do NOT use `LABEL title => ...` — the tool's `title` parameter handles chart titles. Set a label to `null` to suppress it. -Available labels: any aesthetic name (`x`, `y`, `fill`, `color`, etc.), `subtitle`, `caption`. +Available labels: any aesthetic name (`x`, `y`, `fill`, `color`, etc.) and `subtitle`. (ggsql also has a `caption` label, but it is ignored under Vega-Lite rendering — see below.) ```sql LABEL x => 'X Axis Label', y => 'Y Axis Label' LABEL x => null -- suppress x-axis label -LABEL subtitle => 'Q4 2024 data', caption => 'Source: internal database' +LABEL subtitle => 'Q4 2024 data — Source: internal database' ``` ## Complete Examples @@ -565,3 +565,4 @@ querychat draws all charts with ggsql's **Vega-Lite** writer, not its native SVG - **Label text renders literally**: markdown such as `**bold**` or `` `code` `` would appear verbatim in the chart — keep label text plain. - **The `minor_breaks` scale setting is ignored** — no minor gridlines are drawn. +- **`LABEL caption` is ignored** — captions have no Vega-Lite equivalent and never appear in the chart. Put source attributions or footnotes in the `subtitle` instead. diff --git a/pkg-r/inst/prompts/ggsql-syntax.md b/pkg-r/inst/prompts/ggsql-syntax.md index 6552a2f0a..f9ad904ab 100644 --- a/pkg-r/inst/prompts/ggsql-syntax.md +++ b/pkg-r/inst/prompts/ggsql-syntax.md @@ -335,14 +335,14 @@ SCALE panel FROM ('Biscoe', 'Dream') ### LABEL Clause -Use LABEL for axis labels, subtitles, and captions. Do NOT use `LABEL title => ...` — the tool's `title` parameter handles chart titles. Set a label to `null` to suppress it. +Use LABEL for axis labels and subtitles. Do NOT use `LABEL title => ...` — the tool's `title` parameter handles chart titles. Set a label to `null` to suppress it. -Available labels: any aesthetic name (`x`, `y`, `fill`, `color`, etc.), `subtitle`, `caption`. +Available labels: any aesthetic name (`x`, `y`, `fill`, `color`, etc.) and `subtitle`. (ggsql also has a `caption` label, but it is ignored under Vega-Lite rendering — see below.) ```sql LABEL x => 'X Axis Label', y => 'Y Axis Label' LABEL x => null -- suppress x-axis label -LABEL subtitle => 'Q4 2024 data', caption => 'Source: internal database' +LABEL subtitle => 'Q4 2024 data — Source: internal database' ``` ## Complete Examples @@ -565,3 +565,4 @@ querychat draws all charts with ggsql's **Vega-Lite** writer, not its native SVG - **Label text renders literally**: markdown such as `**bold**` or `` `code` `` would appear verbatim in the chart — keep label text plain. - **The `minor_breaks` scale setting is ignored** — no minor gridlines are drawn. +- **`LABEL caption` is ignored** — captions have no Vega-Lite equivalent and never appear in the chart. Put source attributions or footnotes in the `subtitle` instead.