Skip to content

Commit a691d0b

Browse files
teunbrandclaudethomasp85
authored
Annotations (#172)
* Add PLACE clause for annotation layers Introduces PLACE as a new top-level clause for creating annotation layers with literal values, similar to ggplot2's annotate() function. Changes: - Grammar: Add place_clause accepting geom and optional SETTING - DataSource: Add Annotation variant to mark annotation layers - Builder: Handle place_clause and set DataSource::Annotation - Execution: Add stub for annotation data source generation - Tests: Add parser test for PLACE clause PLACE layers use DataSource::Annotation as a marker and don't inherit global mappings. All aesthetic values come from SETTING parameters. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * Implement execution for PLACE annotation layers Completes the execution pipeline for PLACE layers: Changes: - Skip annotation layers in merge_global_mappings_into_layers() to prevent inheritance of global mappings from VISUALISE - Clarify annotation data source comment: 1-row dummy satisfies execution pipeline requirements (schema detection, type resolution) even though SETTING literals render as Vega-Lite datum values Annotation layers now properly isolated from global mappings while still working with the existing execution infrastructure. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * Transform position aesthetics for PLACE annotation layers Enables annotation layers to participate in scale training and coordinate transformation pipeline by moving positional/required aesthetics from SETTING parameters to mappings. Changes: - Transform parameter keys to internal space (x → pos1, y → pos2) - Move positional and required aesthetics from parameters to mappings for annotation layers in transform_aesthetics_to_internal() - Refactor place_clause handling in builder.rs into build_place_layer() - Add comprehensive test coverage: parser, execution, validation, and rendering tests verifying field vs value encoding Annotation position aesthetics now materialize as columns, enabling them to affect scale ranges and be properly encoded in Vega-Lite output as field encodings (not datum values). Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * Add length parameter to DataSource::Annotation Change DataSource::Annotation from a unit variant to a tuple variant containing the number of rows to generate (usize). This prepares for array expansion support in PLACE annotation layers. Changes: - DataSource::Annotation becomes DataSource::Annotation(usize) - Initialize annotation layers with length 1 in build_place_layer() - Generate multi-row VALUES clause in determine_layer_source() when n > 1 - Update all matches!() checks to use DataSource::Annotation(_) - Update test assertions to use pattern matching The length will be updated in process_annotation_layers() when arrays are present (to be implemented next). Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * Add array recycling support for PLACE annotation layers This commit implements array parameter recycling for annotation layers, allowing scalars and length-1 arrays to be automatically recycled to match the length of the longest array parameter. Changes: - Updated DataSource::Annotation to store row count: Annotation(usize) - Added recycle_value_to_length() helper in plot/main.rs - Implemented process_annotation_layers() to handle array recycling - Updated schema inference to handle arrays in literal aesthetic mappings - Modified literal_to_sql() to generate CASE WHEN statements for arrays - Fixed SQL generation for multi-row VALUES clause in annotation layers - Added tests for array recycling and mismatched length validation Recycling Rules: - Scalars recycle to max array length - Length-1 arrays recycle to max array length - All non-1 arrays must have same length (error on mismatch) - Only required/positional aesthetics get recycled when moved to mappings Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * Fix PLACE annotation layers with mixed-type array parameters This commit fixes a bug where PLACE text annotation layers with array parameters (e.g., label => [1, 'foo']) displayed incorrectly. Changes: - Add ArrayElement::homogenize() to coerce mixed-type arrays to a common type - Process annotation layers to move array parameters to mappings - Skip non-positional aesthetics for annotation layers in scale training - Remove string-to-number parsing in JSON serialization to preserve types The fix ensures that arrays like [1, 'foo'] are homogenized to ["1", "foo"] and rendered correctly as separate labels in the visualization. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * Refactor SQL generation and array recycling to methods on types Extract standalone functions into methods on their respective types for better encapsulation and API design: - Add ArrayElement::to_sql() for SQL literal conversion - Add ParameterValue::to_sql() for SQL literal conversion (including CASE statements for arrays) - Add ParameterValue::rep(n) for array recycling (replaces recycle_value_to_length) - Add ParameterValue::to_array_element() helper for scalar conversion Key improvements: - Simplified rep() to only homogenize arrays when arr.len() == n - Unified scalar handling through to_array_element() catch-all - Removed literal_to_sql() standalone function - Removed recycle_value_to_length() standalone function Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * Add is_scaled field to prevent annotation literals from using scales Fixes issue where PLACE layer literals (e.g., stroke => ['red', 'blue']) would incorrectly use scales from other layers. Changes: - Add is_scaled field to AestheticValue::Column - Add identity_column() helper for creating unscaled columns - Mark annotation layer non-positional literals with is_scaled: false - Use identity_scale when is_scaled is false in encoding Example: PLACE text with stroke => ['red', 'blue'] now renders with literal colors instead of being transformed by scales from other layers. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * error when writing array value encoding * add docs * Refactor annotation layer SQL generation for clarity Simplifies annotation layer handling by consolidating SQL generation logic: - Unified layer_source_query() to handle all source types including annotations - Moved determine_layer_source() from casting.rs to layer.rs and merged into layer_source_query() - Removed CASE statement approach in favor of direct VALUES clause generation - build_annotation_values_clause() now generates single SELECT...FROM VALUES statement - Updated ParameterValue::to_sql() to panic on arrays (arrays handled separately) - Cleaned up unused imports in casting.rs This eliminates redundant function calls and awkward unreachable!() cases, making the annotation SQL generation more straightforward. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * Move array recycling from AST to execution layer Improves separation of concerns by keeping AST stateless: - Changed DataSource::Annotation from Annotation(usize) to Annotation (unit variant) - AST no longer carries execution state (row count) - Moved array recycling logic from process_annotation_layers() to build_annotation_values_clause() - Recycling now happens on-the-fly during SQL generation - Validates array lengths and returns error on mismatch - Simplified process_annotation_layers() from ~85 to ~40 lines The AST now purely describes what the user wrote, while execution handles data preparation and recycling. All 16 annotation tests pass. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * Replace is_scaled flag with AnnotationColumn variant Implements a cleaner approach for handling annotation aesthetics: - Added AnnotationColumn variant to AestheticValue enum - AnnotationColumn used only for non-positional annotation aesthetics (color, size, etc) - Positional annotation aesthetics (x, y) use regular Column variant - Removed is_scaled field from Column variant - Scale training automatically skips AnnotationColumn variants - Vega-Lite writer uses identity scale for AnnotationColumn This makes the distinction explicit in the type system rather than carrying state in a boolean flag. Positional annotations participate in scale training (data coordinate space) while non-positional annotations use identity scales (visual space). All 16 annotation tests pass. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * Filter NULL aesthetics in annotation layers NULL aesthetics in PLACE SETTING mean "use geom default", not "create a mapping". Filter them out in process_annotation_layers() to prevent them from reaching VALUES clause generation or literal_to_series(). Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * Add unit tests for annotation layer functions Reorganize test suite to replace integration tests with unit tests where appropriate, improving test speed and clarity. Added unit tests: - src/execute/layer.rs: 5 tests for build_annotation_values_clause() * Single scalar values * Array recycling * Mismatched array lengths (error case) * Multiple arrays with same length * Mixed types (number, string, boolean) - src/plot/main.rs: 6 tests for process_annotation_layers() * Moves positional aesthetics to mappings * Moves required aesthetics to mappings * Moves array parameters to mappings * Filters NULL aesthetics * Skips non-annotation layers * Preserves existing mappings Removed redundant integration tests: - test_place_array_recycling_scalar (now unit test) - test_place_array_mismatched_lengths_error (now unit test) All 12 annotation-related tests pass. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * Fix annotation layers to work with stat geoms Annotation layers now support stat geoms (histogram, density, etc.) by using the same column→aesthetic renaming pipeline as regular layers. Previously, annotation layers created prefixed aesthetic columns directly (__ggsql_aes_pos1__), causing naming conflicts when stat transforms tried to rename their output columns to the same names. Changes: - Use raw aesthetic names in VALUES clause (pos1, not __ggsql_aes_pos1__) - Move annotation processing from parse-time to execution-time - Rename build_annotation_values_clause() to process_annotation_layer() - Fix is_scaled parameter (was inverted for Column vs AnnotationColumn) - Make label required for text geom - Flatten nested conditionals with early exits - Remove obsolete Plot::process_annotation_layers() Positional aesthetics now correctly participate in scale training with data-derived domains, while non-positional aesthetics use identity scales. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * bit of cleanup * soothe clippy * Document PLACE clause in CLAUDE.md Add terse documentation for PLACE annotation layers and update type definitions to reflect current implementation (Position, DataSource, AnnotationColumn). Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * use place in examples * Add PLACE clause syntax highlighting to VS Code and Quarto The PLACE clause was added to the grammar but was missing from the syntax highlighting definitions. This adds complete highlighting support for PLACE clauses in both VS Code (TextMate grammar) and Quarto documentation (KDE XML). Changes: - VS Code: Add place-clause pattern with geom type and SETTING support - Quarto: Add PlaceClause context with proper clause transitions - Both: Update all clause boundaries to recognize PLACE Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * try to cast literals as date * add some more examples * Fix failing PLACE annotation layer tests Three tests were failing due to incorrect aesthetics and expectations: 1. Text geom uses 'fontsize' aesthetic, not 'size' - updated test queries 2. Test incorrectly expected no fill on annotation layers - annotation layers correctly inherit geom defaults (fill='black' for text), they just don't inherit global mappings from VISUALISE clause 3. Simplified test_end_to_end_place_field_vs_value_encoding to use point instead of text geom, avoiding complex font-based Vega-Lite nesting All 1215 library tests now pass. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * cargo fmt * Apply suggestions from code review Co-authored-by: Thomas Lin Pedersen <thomasp85@gmail.com> --------- Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com> Co-authored-by: Thomas Lin Pedersen <thomasp85@gmail.com>
1 parent 4188798 commit a691d0b

25 files changed

Lines changed: 1476 additions & 140 deletions

File tree

CLAUDE.md

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,8 +322,10 @@ pub enum GlobalMappingItem {
322322

323323
pub struct Layer {
324324
pub geom: Geom, // Geometric object type
325+
pub position: Position, // Position adjustment (from SETTING position)
325326
pub aesthetics: HashMap<String, AestheticValue>, // Aesthetic mappings (from MAPPING)
326327
pub parameters: HashMap<String, ParameterValue>, // Geom parameters (from SETTING)
328+
pub source: Option<DataSource>, // Data source (from MAPPING ... FROM or PLACE)
327329
pub filter: Option<FilterExpression>, // Layer filter (from FILTER)
328330
pub partition_by: Vec<String>, // Grouping columns (from PARTITION BY)
329331
}
@@ -337,8 +339,22 @@ pub enum Geom {
337339
Text, Segment, Arrow, Rule, Linear, ErrorBar,
338340
}
339341

342+
pub enum DataSource {
343+
Identifier(String), // Table/CTE name
344+
FilePath(String), // File path (quoted)
345+
Annotation, // PLACE clause (no external data)
346+
}
347+
348+
pub enum Position {
349+
Identity, // No adjustment
350+
Stack, // Stack elements (bars, areas)
351+
Dodge, // Dodge side-by-side (bars)
352+
Jitter, // Jitter points randomly
353+
}
354+
340355
pub enum AestheticValue {
341-
Column(String), // Unquoted column reference: revenue AS x
356+
Column(String), // Column from data: revenue AS x
357+
AnnotationColumn(String), // Annotation literal (PLACE): uses identity scale
342358
Literal(ParameterValue), // Quoted literal: 'value' AS fill
343359
}
344360

@@ -1178,6 +1194,7 @@ Where `<global_mapping>` can be:
11781194
| ----------- | ---------- | ----------------- | ----------------------------------------- |
11791195
| `VISUALISE` | ✅ Yes | Entry point | `VISUALISE date AS x, revenue AS y` |
11801196
| `DRAW` | ✅ Yes | Define layers | `DRAW line MAPPING date AS x, value AS y` |
1197+
| `PLACE` | ✅ Yes | Annotation layers | `PLACE point SETTING x => 5, y => 10` |
11811198
| `SCALE` | ✅ Yes | Configure scales | `SCALE x VIA date` |
11821199
| `FACET` | ❌ No | Small multiples | `FACET region` |
11831200
| `PROJECT` | ❌ No | Coordinate system | `PROJECT TO cartesian` |
@@ -1289,6 +1306,29 @@ DRAW line
12891306
FILTER year >= 2020
12901307
```
12911308

1309+
### PLACE Clause (Annotation Layers)
1310+
1311+
**Syntax**:
1312+
1313+
```sql
1314+
PLACE <geom> SETTING <aesthetic/parameter> => <value>, ...
1315+
```
1316+
1317+
Creates annotation layers with literal values only (no data mappings). All aesthetics set via SETTING; supports arrays that are recycled to longest length. No FILTER/PARTITION BY/ORDER BY support.
1318+
1319+
**Examples**:
1320+
1321+
```sql
1322+
-- Single annotation
1323+
PLACE point SETTING x => 5, y => 10, color => 'red'
1324+
1325+
-- Multiple annotations (array recycling)
1326+
PLACE point SETTING x => [1, 2, 3], y => [10, 20, 30]
1327+
1328+
-- Reference line
1329+
PLACE rule SETTING x => 5, color => 'red'
1330+
```
1331+
12921332
### SCALE Clause
12931333

12941334
**Syntax**:

doc/_quarto.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ website:
4040
href: syntax/clause/visualise.qmd
4141
- text: "`DRAW`"
4242
href: syntax/clause/draw.qmd
43+
- text: "`PLACE`"
44+
href: syntax/clause/place.qmd
4345
- text: "`SCALE`"
4446
href: syntax/clause/scale.qmd
4547
- text: "`FACET`"
@@ -77,6 +79,8 @@ website:
7779
href: syntax/clause/visualise.qmd
7880
- text: "`DRAW`"
7981
href: syntax/clause/draw.qmd
82+
- text: "`PLACE`"
83+
href: syntax/clause/place.qmd
8084
- text: "`SCALE`"
8185
href: syntax/clause/scale.qmd
8286
- text: "`FACET`"

doc/ggsql.xml

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@
9090
<!-- ggsql Main Clause Keywords (triggers context switch) -->
9191
<list name="clause_keywords">
9292
<item>DRAW</item>
93+
<item>PLACE</item>
9394
<item>SCALE</item>
9495
<item>PROJECT</item>
9596
<item>FACET</item>
@@ -404,6 +405,7 @@
404405

405406
<!-- Clause keywords trigger context switches -->
406407
<WordDetect attribute="Keyword" context="DrawClause" String="DRAW" insensitive="true"/>
408+
<WordDetect attribute="Keyword" context="PlaceClause" String="PLACE" insensitive="true"/>
407409
<WordDetect attribute="Keyword" context="ScaleClause" String="SCALE" insensitive="true"/>
408410
<WordDetect attribute="Keyword" context="ProjectClause" String="PROJECT" insensitive="true"/>
409411
<WordDetect attribute="Keyword" context="FacetClause" String="FACET" insensitive="true"/>
@@ -451,6 +453,7 @@
451453

452454
<!-- Exit to other clause contexts -->
453455
<WordDetect attribute="Keyword" context="DrawClause" String="DRAW" insensitive="true"/>
456+
<WordDetect attribute="Keyword" context="PlaceClause" String="PLACE" insensitive="true"/>
454457
<WordDetect attribute="Keyword" context="ScaleClause" String="SCALE" insensitive="true"/>
455458
<WordDetect attribute="Keyword" context="ProjectClause" String="PROJECT" insensitive="true"/>
456459
<WordDetect attribute="Keyword" context="FacetClause" String="FACET" insensitive="true"/>
@@ -481,6 +484,46 @@
481484
<DetectChar char="," attribute="Symbol" context="#stay"/>
482485
</context>
483486

487+
<!-- PLACE clause context (annotation layers) -->
488+
<context name="PlaceClause" attribute="Normal Text" lineEndContext="#stay">
489+
<Detect2Chars char="-" char1="-" attribute="Comment" context="Comment"/>
490+
<Detect2Chars char="/" char1="*" attribute="Comment" context="CommentMulti" beginRegion="comment"/>
491+
<DetectChar char="'" attribute="String" context="StringSingle"/>
492+
<DetectChar char="&quot;" attribute="String" context="StringDouble"/>
493+
<RegExpr attribute="Number" context="#stay" String="-?[0-9]+\.?[0-9]*([eE][+-]?[0-9]+)?"/>
494+
495+
<!-- Exit to other clause contexts -->
496+
<WordDetect attribute="Keyword" context="DrawClause" String="DRAW" insensitive="true"/>
497+
<WordDetect attribute="Keyword" context="PlaceClause" String="PLACE" insensitive="true"/>
498+
<WordDetect attribute="Keyword" context="ScaleClause" String="SCALE" insensitive="true"/>
499+
<WordDetect attribute="Keyword" context="ProjectClause" String="PROJECT" insensitive="true"/>
500+
<WordDetect attribute="Keyword" context="FacetClause" String="FACET" insensitive="true"/>
501+
<WordDetect attribute="Keyword" context="LabelClause" String="LABEL" insensitive="true"/>
502+
<WordDetect attribute="Keyword" context="ThemeClause" String="THEME" insensitive="true"/>
503+
<WordDetect attribute="Keyword" context="Normal" String="VISUALISE" insensitive="true"/>
504+
<WordDetect attribute="Keyword" context="Normal" String="VISUALIZE" insensitive="true"/>
505+
<WordDetect attribute="Keyword" context="Normal" String="SELECT" insensitive="true"/>
506+
<WordDetect attribute="Keyword" context="Normal" String="WHERE" insensitive="true"/>
507+
<WordDetect attribute="Keyword" context="Normal" String="WITH" insensitive="true"/>
508+
509+
<!-- Geom types - same as DRAW -->
510+
<keyword attribute="Data Type" context="#stay" String="geoms"/>
511+
512+
<!-- Aesthetics -->
513+
<keyword attribute="Attribute" context="#stay" String="aesthetics"/>
514+
515+
<!-- Sub-keywords (only SETTING supported, not FILTER/PARTITION/ORDER) -->
516+
<WordDetect attribute="Keyword" context="#stay" String="SETTING" insensitive="true"/>
517+
518+
<StringDetect attribute="Operator" context="#stay" String="=&gt;"/>
519+
<AnyChar attribute="Operator" context="#stay" String="=!&lt;&gt;+-*/%"/>
520+
<DetectChar char="(" attribute="Symbol" context="#stay"/>
521+
<DetectChar char=")" attribute="Symbol" context="#stay"/>
522+
<DetectChar char="[" attribute="Symbol" context="#stay"/>
523+
<DetectChar char="]" attribute="Symbol" context="#stay"/>
524+
<DetectChar char="," attribute="Symbol" context="#stay"/>
525+
</context>
526+
484527
<!-- SCALE clause context -->
485528
<context name="ScaleClause" attribute="Normal Text" lineEndContext="#stay">
486529
<Detect2Chars char="-" char1="-" attribute="Comment" context="Comment"/>
@@ -491,6 +534,7 @@
491534

492535
<!-- Exit to other clause contexts -->
493536
<WordDetect attribute="Keyword" context="DrawClause" String="DRAW" insensitive="true"/>
537+
<WordDetect attribute="Keyword" context="PlaceClause" String="PLACE" insensitive="true"/>
494538
<WordDetect attribute="Keyword" context="ScaleClause" String="SCALE" insensitive="true"/>
495539
<WordDetect attribute="Keyword" context="ProjectClause" String="PROJECT" insensitive="true"/>
496540
<WordDetect attribute="Keyword" context="FacetClause" String="FACET" insensitive="true"/>
@@ -537,6 +581,7 @@
537581

538582
<!-- Exit to other clause contexts -->
539583
<WordDetect attribute="Keyword" context="DrawClause" String="DRAW" insensitive="true"/>
584+
<WordDetect attribute="Keyword" context="PlaceClause" String="PLACE" insensitive="true"/>
540585
<WordDetect attribute="Keyword" context="ScaleClause" String="SCALE" insensitive="true"/>
541586
<WordDetect attribute="Keyword" context="ProjectClause" String="PROJECT" insensitive="true"/>
542587
<WordDetect attribute="Keyword" context="FacetClause" String="FACET" insensitive="true"/>
@@ -580,6 +625,7 @@
580625

581626
<!-- Exit to other clause contexts -->
582627
<WordDetect attribute="Keyword" context="DrawClause" String="DRAW" insensitive="true"/>
628+
<WordDetect attribute="Keyword" context="PlaceClause" String="PLACE" insensitive="true"/>
583629
<WordDetect attribute="Keyword" context="ScaleClause" String="SCALE" insensitive="true"/>
584630
<WordDetect attribute="Keyword" context="ProjectClause" String="PROJECT" insensitive="true"/>
585631
<WordDetect attribute="Keyword" context="FacetClause" String="FACET" insensitive="true"/>
@@ -622,6 +668,7 @@
622668

623669
<!-- Exit to other clause contexts -->
624670
<WordDetect attribute="Keyword" context="DrawClause" String="DRAW" insensitive="true"/>
671+
<WordDetect attribute="Keyword" context="PlaceClause" String="PLACE" insensitive="true"/>
625672
<WordDetect attribute="Keyword" context="ScaleClause" String="SCALE" insensitive="true"/>
626673
<WordDetect attribute="Keyword" context="ProjectClause" String="PROJECT" insensitive="true"/>
627674
<WordDetect attribute="Keyword" context="FacetClause" String="FACET" insensitive="true"/>
@@ -659,6 +706,7 @@
659706

660707
<!-- Exit to other clause contexts -->
661708
<WordDetect attribute="Keyword" context="DrawClause" String="DRAW" insensitive="true"/>
709+
<WordDetect attribute="Keyword" context="PlaceClause" String="PLACE" insensitive="true"/>
662710
<WordDetect attribute="Keyword" context="ScaleClause" String="SCALE" insensitive="true"/>
663711
<WordDetect attribute="Keyword" context="ProjectClause" String="PROJECT" insensitive="true"/>
664712
<WordDetect attribute="Keyword" context="FacetClause" String="FACET" insensitive="true"/>
@@ -699,6 +747,7 @@
699747

700748
<!-- Exit to other clause contexts -->
701749
<WordDetect attribute="Keyword" context="DrawClause" String="DRAW" insensitive="true"/>
750+
<WordDetect attribute="Keyword" context="PlaceClause" String="PLACE" insensitive="true"/>
702751
<WordDetect attribute="Keyword" context="ScaleClause" String="SCALE" insensitive="true"/>
703752
<WordDetect attribute="Keyword" context="ProjectClause" String="PROJECT" insensitive="true"/>
704753
<WordDetect attribute="Keyword" context="FacetClause" String="FACET" insensitive="true"/>

doc/syntax/clause/place.qmd

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
---
2+
title: "Create annotation layers with `PLACE`"
3+
---
4+
5+
The `PLACE` clause is the little sibling of the [`DRAW` clause](draw.qmd) that also creates layers.
6+
A layer created with `PLACE` has no mappings to data, all aesthetics are set using literal values instead.
7+
8+
## Clause syntax
9+
The `PLACE` clause takes just a type and a setting clause, all of them required.
10+
11+
```ggsql
12+
PLACE <layer-type>
13+
SETTING <parameter/aesthetic> => <value>, ...
14+
```
15+
16+
Like `DRAW`, the layer type is required and specifies the type of layer to draw, like `point` or `text`.
17+
It defines how the remaining settings are interpreted.
18+
The [main syntax page](../index.qmd#layers) has a list of all available layer types
19+
20+
Unlike the `DRAW` clause, the `PLACE` clause does not support `FILTER`, `PARTITION BY`, and `ORDER BY` clauses since
21+
everything is declared inline.
22+
23+
### `SETTING`
24+
```ggsql
25+
SETTING <parameter/aesthetic> => <value>, ...
26+
```
27+
28+
The `SETTING` clause can be used for two different things:
29+
30+
* *Setting aesthetics*: All aesthetics in `PLACE` layers are specified using literal value, e.g. 'red' (as in the color red).
31+
Aesthetics that are set will not go through a scale but will use the provided value as-is.
32+
You cannot set an aesthetic to a column, only to a literal values.
33+
Contrary to `DRAW` layers, `PLACE` layers can take multiple literal values in an array.
34+
* *Setting parameters*: Some layers take additional arguments that control how they behave.
35+
Often, but not always, these modify the statistical transformation in some way.
36+
An example would be the binwidth parameter in histogram which controls the width of each bin during histogram calculation.
37+
This is not a statistical property since it is not related to each record, but to the calculation as a whole.

doc/syntax/index.qmd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ ggsql augments the standard SQL syntax with a number of new clauses to describe
77

88
- [`VISUALISE`](clause/visualise.qmd) initiates the visualisation part of the query
99
- [`DRAW`](clause/draw.qmd) adds a new layer to the visualisation
10+
- [`PLACE`](clause/place.qmd) adds an annotation layer
1011
- [`SCALE`](clause/scale.qmd) specify how an aesthetic should be scaled
1112
- [`FACET`](clause/facet.qmd) describes how data should be split into small multiples
1213
- [`PROJECT`](clause/project.qmd) is used for selecting the coordinate system to use

doc/syntax/layer/type/linear.qmd

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,10 @@ Add a simple reference line to a scatterplot:
4646
```{ggsql}
4747
VISUALISE FROM ggsql:penguins
4848
DRAW point MAPPING bill_len AS x, bill_dep AS y
49-
DRAW linear MAPPING 0.4 AS coef, -1 AS intercept
49+
PLACE linear SETTING coef => 0.4, intercept => -1
5050
```
5151

52-
Add multiple reference lines with different colors from a separate dataset:
52+
Add multiple reference lines with different colors from a separate dataset. Note we're mapping from data here, so we use `DRAW` instead of `PLACE`.
5353

5454
```{ggsql}
5555
WITH lines AS (

doc/syntax/layer/type/rect.qmd

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -92,17 +92,15 @@ VISUALISE start AS xmin, end AS xmax, min AS ymin, max AS ymax
9292
DRAW rect
9393
```
9494

95-
Using a rectangle as an annotation.
96-
97-
<!-- When annotations work, replace this with an annotation layer -->
95+
Using a rectangle as an annotation. Note we're using the `PLACE` clause here instead of `DRAW` because we're not mapping from data.
9896

9997
```{ggsql}
10098
VISUALISE FROM ggsql:airquality
101-
DRAW rect MAPPING
102-
'1973-06-01' AS xmin,
103-
'1973-06-30' AS xmax,
104-
50 AS ymin,
105-
100 AS ymax,
106-
'June' AS colour
99+
PLACE rect SETTING
100+
xmin => '1973-06-01',
101+
xmax => '1973-06-30',
102+
ymin => 50,
103+
ymax => 100,
104+
colour => 'dodgerblue'
107105
DRAW line MAPPING Date AS x, Temp AS y
108106
```

doc/syntax/layer/type/rule.qmd

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,18 +40,18 @@ WHERE Month = 5
4040
4141
VISUALISE
4242
DRAW line MAPPING date AS x, temperature AS y
43-
DRAW rule MAPPING 70 AS y
43+
PLACE rule SETTING y => 70
4444
```
4545

4646
Add a vertical line to mark a specific value:
4747

4848
```{ggsql}
4949
VISUALISE FROM ggsql:penguins
5050
DRAW point MAPPING bill_len AS x, bill_dep AS y
51-
DRAW rule MAPPING 45 AS x
51+
PLACE rule SETTING x => 45
5252
```
5353

54-
Add multiple threshold lines with different colors:
54+
Add multiple threshold lines with different colors. Note that because we're mapping data from data, we use the `DRAW` clause instead of the `PLACE` clause.
5555

5656
```{ggsql}
5757
WITH thresholds AS (

doc/syntax/layer/type/text.qmd

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,3 +137,13 @@ VISUALISE island AS x, n AS y
137137
SCALE y FROM [0, 200]
138138
```
139139

140+
You can use `PLACE` to annotate a plot directly without needing to map data.
141+
142+
```{ggsql}
143+
VISUALISE bill_len AS x, bill_dep AS y FROM ggsql:penguins
144+
DRAW point MAPPING species AS colour
145+
PLACE text SETTING
146+
label => ['Adelie', 'Chinstrap', 'Gentoo'],
147+
x => [40, 50, 50],
148+
y => [19, 19, 15]
149+
```

0 commit comments

Comments
 (0)