Commit a691d0b
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
- doc
- syntax
- clause
- layer/type
- ggsql-vscode/syntaxes
- src
- execute
- parser
- plot
- layer
- writer/vegalite
- tree-sitter-ggsql
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
322 | 322 | | |
323 | 323 | | |
324 | 324 | | |
| 325 | + | |
325 | 326 | | |
326 | 327 | | |
| 328 | + | |
327 | 329 | | |
328 | 330 | | |
329 | 331 | | |
| |||
337 | 339 | | |
338 | 340 | | |
339 | 341 | | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
340 | 355 | | |
341 | | - | |
| 356 | + | |
| 357 | + | |
342 | 358 | | |
343 | 359 | | |
344 | 360 | | |
| |||
1178 | 1194 | | |
1179 | 1195 | | |
1180 | 1196 | | |
| 1197 | + | |
1181 | 1198 | | |
1182 | 1199 | | |
1183 | 1200 | | |
| |||
1289 | 1306 | | |
1290 | 1307 | | |
1291 | 1308 | | |
| 1309 | + | |
| 1310 | + | |
| 1311 | + | |
| 1312 | + | |
| 1313 | + | |
| 1314 | + | |
| 1315 | + | |
| 1316 | + | |
| 1317 | + | |
| 1318 | + | |
| 1319 | + | |
| 1320 | + | |
| 1321 | + | |
| 1322 | + | |
| 1323 | + | |
| 1324 | + | |
| 1325 | + | |
| 1326 | + | |
| 1327 | + | |
| 1328 | + | |
| 1329 | + | |
| 1330 | + | |
| 1331 | + | |
1292 | 1332 | | |
1293 | 1333 | | |
1294 | 1334 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
40 | 40 | | |
41 | 41 | | |
42 | 42 | | |
| 43 | + | |
| 44 | + | |
43 | 45 | | |
44 | 46 | | |
45 | 47 | | |
| |||
77 | 79 | | |
78 | 80 | | |
79 | 81 | | |
| 82 | + | |
| 83 | + | |
80 | 84 | | |
81 | 85 | | |
82 | 86 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
90 | 90 | | |
91 | 91 | | |
92 | 92 | | |
| 93 | + | |
93 | 94 | | |
94 | 95 | | |
95 | 96 | | |
| |||
404 | 405 | | |
405 | 406 | | |
406 | 407 | | |
| 408 | + | |
407 | 409 | | |
408 | 410 | | |
409 | 411 | | |
| |||
451 | 453 | | |
452 | 454 | | |
453 | 455 | | |
| 456 | + | |
454 | 457 | | |
455 | 458 | | |
456 | 459 | | |
| |||
481 | 484 | | |
482 | 485 | | |
483 | 486 | | |
| 487 | + | |
| 488 | + | |
| 489 | + | |
| 490 | + | |
| 491 | + | |
| 492 | + | |
| 493 | + | |
| 494 | + | |
| 495 | + | |
| 496 | + | |
| 497 | + | |
| 498 | + | |
| 499 | + | |
| 500 | + | |
| 501 | + | |
| 502 | + | |
| 503 | + | |
| 504 | + | |
| 505 | + | |
| 506 | + | |
| 507 | + | |
| 508 | + | |
| 509 | + | |
| 510 | + | |
| 511 | + | |
| 512 | + | |
| 513 | + | |
| 514 | + | |
| 515 | + | |
| 516 | + | |
| 517 | + | |
| 518 | + | |
| 519 | + | |
| 520 | + | |
| 521 | + | |
| 522 | + | |
| 523 | + | |
| 524 | + | |
| 525 | + | |
| 526 | + | |
484 | 527 | | |
485 | 528 | | |
486 | 529 | | |
| |||
491 | 534 | | |
492 | 535 | | |
493 | 536 | | |
| 537 | + | |
494 | 538 | | |
495 | 539 | | |
496 | 540 | | |
| |||
537 | 581 | | |
538 | 582 | | |
539 | 583 | | |
| 584 | + | |
540 | 585 | | |
541 | 586 | | |
542 | 587 | | |
| |||
580 | 625 | | |
581 | 626 | | |
582 | 627 | | |
| 628 | + | |
583 | 629 | | |
584 | 630 | | |
585 | 631 | | |
| |||
622 | 668 | | |
623 | 669 | | |
624 | 670 | | |
| 671 | + | |
625 | 672 | | |
626 | 673 | | |
627 | 674 | | |
| |||
659 | 706 | | |
660 | 707 | | |
661 | 708 | | |
| 709 | + | |
662 | 710 | | |
663 | 711 | | |
664 | 712 | | |
| |||
699 | 747 | | |
700 | 748 | | |
701 | 749 | | |
| 750 | + | |
702 | 751 | | |
703 | 752 | | |
704 | 753 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
| 10 | + | |
10 | 11 | | |
11 | 12 | | |
12 | 13 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
46 | 46 | | |
47 | 47 | | |
48 | 48 | | |
49 | | - | |
| 49 | + | |
50 | 50 | | |
51 | 51 | | |
52 | | - | |
| 52 | + | |
53 | 53 | | |
54 | 54 | | |
55 | 55 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
92 | 92 | | |
93 | 93 | | |
94 | 94 | | |
95 | | - | |
96 | | - | |
97 | | - | |
| 95 | + | |
98 | 96 | | |
99 | 97 | | |
100 | 98 | | |
101 | | - | |
102 | | - | |
103 | | - | |
104 | | - | |
105 | | - | |
106 | | - | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
107 | 105 | | |
108 | 106 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
40 | 40 | | |
41 | 41 | | |
42 | 42 | | |
43 | | - | |
| 43 | + | |
44 | 44 | | |
45 | 45 | | |
46 | 46 | | |
47 | 47 | | |
48 | 48 | | |
49 | 49 | | |
50 | 50 | | |
51 | | - | |
| 51 | + | |
52 | 52 | | |
53 | 53 | | |
54 | | - | |
| 54 | + | |
55 | 55 | | |
56 | 56 | | |
57 | 57 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
137 | 137 | | |
138 | 138 | | |
139 | 139 | | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
0 commit comments