Skip to content

Commit 3f25913

Browse files
authored
Change dimension name to angle (#243)
1 parent d6f6765 commit 3f25913

17 files changed

Lines changed: 87 additions & 87 deletions

File tree

CLAUDE.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1480,7 +1480,7 @@ PROJECT [<aesthetic>, ...] TO <coord_type> [SETTING <properties>]
14801480
| Coord Type | Default Aesthetics | Description |
14811481
|------------|-------------------|-------------|
14821482
| `cartesian` | `x`, `y` | Standard x/y Cartesian coordinates |
1483-
| `polar` | `theta`, `radius` | Polar coordinates (for pie charts, rose plots) |
1483+
| `polar` | `angle`, `radius` | Polar coordinates (for pie charts, rose plots) |
14841484

14851485
**Flipping Axes**:
14861486

@@ -1505,7 +1505,7 @@ Note: For axis limits, use `SCALE x FROM [min, max]` or `SCALE y FROM [min, max]
15051505

15061506
**Polar**:
15071507

1508-
- `theta => <aesthetic>` - Which aesthetic maps to angle (defaults to `y`)
1508+
- No special properties (angle/radius aesthetics are used directly)
15091509

15101510
**Important Notes**:
15111511

@@ -1534,10 +1534,10 @@ PROJECT y, x TO cartesian
15341534
-- Custom aesthetic names
15351535
PROJECT myX, myY TO cartesian
15361536

1537-
-- Polar for pie chart (using default theta/radius aesthetics)
1537+
-- Polar for pie chart (using default angle/radius aesthetics)
15381538
PROJECT TO polar
15391539

1540-
-- Polar with y/x aesthetics (y becomes theta, x becomes radius)
1540+
-- Polar with y/x aesthetics (y becomes angle, x becomes radius)
15411541
PROJECT y, x TO polar
15421542

15431543
-- Polar with start angle offset (3 o'clock position)

doc/ggsql.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@
242242
<item>xlim</item>
243243
<item>ylim</item>
244244
<item>ratio</item>
245-
<item>theta</item>
245+
<item>angle</item>
246246
<item>clip</item>
247247
</list>
248248

doc/syntax/clause/project.qmd

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,6 @@ This clause behaves much like the `SETTINGS` clause in `DRAW`, in that it allows
3232
If you do not provide a `PROJECT` clause then the coordinate system will be picked for you based on the mappings in your query. The logic is as follows
3333

3434
* If `x`, `y` or any of their variants are mapped to, a Cartesian coordinate system is used
35-
* If `theta`, `radius` or any of their variants are mapped to, a polar coordinate system is used
35+
* If `angle`, `radius` or any of their variants are mapped to, a polar coordinate system is used
3636
* If none of the above applies, the plot defaults to a Cartesian coordinate system
37-
* If multiple applies (e.g. mapping to both x and theta) an error is thrown
37+
* If multiple applies (e.g. mapping to both x and angle) an error is thrown

doc/syntax/coord/polar.qmd

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,32 +8,32 @@ The polar coordinate system interprets its primary aesthetic as the angular posi
88
The polar coordinate system has the following default positional aesthetics which will be used if no others have been provided:
99

1010
* **Primary**: `radius` (distance from center)
11-
* **Secondary**: `theta` (angular position)
11+
* **Secondary**: `angle` (angular position)
1212

1313
Users can provide their own aesthetic names if needed. For example, if using `x` and `y` aesthetics:
1414

1515
```ggsql
1616
PROJECT y, x TO polar
1717
```
1818

19-
This maps `y` to radius and `x` to theta (angle). This is useful when converting from a cartesian coordinate system without editing all the mappings.
19+
This maps `y` to radius and `x` to angle. This is useful when converting from a cartesian coordinate system without editing all the mappings.
2020

2121
## Settings
2222
* `clip`: Should data be removed if it appears outside the bounds of the coordinate system. Defaults to `true`
23-
* `start`: The starting angle in degrees for the theta scale. Controls where "0" on the angular axis begins. Defaults to `0` (12 o'clock position).
23+
* `start`: The starting angle in degrees for the angle scale. Controls where "0" on the angular axis begins. Defaults to `0` (12 o'clock position).
2424
- `0` = 12 o'clock position (top)
2525
- `90` = 3 o'clock position (right)
2626
- `-90` or `270` = 9 o'clock position (left)
2727
- `180` = 6 o'clock position (bottom)
28-
* `end`: The ending angle in degrees for the theta scale. Defaults to `start + 360` (a full circle). Use this with `start` to create partial polar plots like gauge charts or half-circle visualizations.
28+
* `end`: The ending angle in degrees for the angle scale. Defaults to `start + 360` (a full circle). Use this with `start` to create partial polar plots like gauge charts or half-circle visualizations.
2929
* `inner`: The inner radius as a proportion (0 to 1) of the outer radius. Defaults to `0` (no hole). Setting this creates a donut chart where the inner portion is empty.
3030
- `0` = full pie (no hole)
3131
- `0.3` = donut with 30% hole
3232
- `0.5` = donut with 50% hole
3333

3434
## Examples
3535

36-
### Pie chart using theta/radius aesthetics
36+
### Pie chart using angle/radius aesthetics
3737
```{ggsql}
3838
VISUALISE species AS fill FROM ggsql:penguins
3939
DRAW bar

ggsql-vscode/syntaxes/ggsql.tmLanguage.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,7 @@
375375
},
376376
{
377377
"name": "support.type.property.ggsql",
378-
"match": "\\b(xlim|ylim|ratio|theta|clip)\\b"
378+
"match": "\\b(xlim|ylim|ratio|angle|clip)\\b"
379379
},
380380
{ "include": "#common-clause-patterns" }
381381
]

src/parser/builder.rs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ fn build_visualise_statement(node: &Node, source: &SourceTree) -> Result<Plot> {
289289
// This must happen after all clauses are processed (especially PROJECT and FACET)
290290
spec.initialize_aesthetic_context();
291291

292-
// Transform all aesthetic keys from user-facing (x/y or theta/radius) to internal (pos1/pos2)
292+
// Transform all aesthetic keys from user-facing (x/y or angle/radius) to internal (pos1/pos2)
293293
// This enables generic handling throughout the pipeline and must happen before merge
294294
// since geom definitions use internal names for their supported/required aesthetics
295295
spec.transform_aesthetics_to_internal();
@@ -1343,7 +1343,7 @@ mod tests {
13431343
fn test_project_default_aesthetics_polar() {
13441344
let query = r#"
13451345
VISUALISE
1346-
DRAW bar MAPPING category AS theta, value AS radius
1346+
DRAW bar MAPPING category AS angle, value AS radius
13471347
PROJECT TO polar
13481348
"#;
13491349

@@ -1354,7 +1354,7 @@ mod tests {
13541354
let project = specs[0].project.as_ref().unwrap();
13551355
assert_eq!(
13561356
project.aesthetics,
1357-
vec!["radius".to_string(), "theta".to_string()]
1357+
vec!["radius".to_string(), "angle".to_string()]
13581358
);
13591359
}
13601360

@@ -3399,8 +3399,8 @@ mod tests {
33993399
}
34003400

34013401
#[test]
3402-
fn test_infer_polar_from_theta_radius_mappings() {
3403-
let query = "VISUALISE DRAW bar MAPPING cat AS theta, val AS radius";
3402+
fn test_infer_polar_from_angle_radius_mappings() {
3403+
let query = "VISUALISE DRAW bar MAPPING cat AS angle, val AS radius";
34043404

34053405
let result = parse_test_query(query);
34063406
assert!(result.is_ok());
@@ -3409,15 +3409,15 @@ mod tests {
34093409
// Should infer polar projection
34103410
let project = specs[0].project.as_ref().unwrap();
34113411
assert_eq!(project.coord.coord_kind(), CoordKind::Polar);
3412-
assert_eq!(project.aesthetics, vec!["radius", "theta"]);
3412+
assert_eq!(project.aesthetics, vec!["radius", "angle"]);
34133413
}
34143414

34153415
#[test]
34163416
fn test_explicit_project_overrides_inference() {
3417-
// Explicitly use cartesian even though mappings use theta
3417+
// Explicitly use cartesian even though mappings use angle
34183418
let query = r#"
34193419
VISUALISE
3420-
DRAW bar MAPPING cat AS theta, val AS radius
3420+
DRAW bar MAPPING cat AS angle, val AS radius
34213421
PROJECT TO cartesian
34223422
"#;
34233423

@@ -3432,8 +3432,8 @@ mod tests {
34323432

34333433
#[test]
34343434
fn test_conflicting_aesthetics_error() {
3435-
// Using both x and theta should error
3436-
let query = "VISUALISE DRAW point MAPPING a AS x, b AS theta";
3435+
// Using both x and angle should error
3436+
let query = "VISUALISE DRAW point MAPPING a AS x, b AS angle";
34373437

34383438
let result = parse_test_query(query);
34393439
assert!(result.is_err());

src/plot/aesthetic.rs

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
//! # Internal vs User-Facing Aesthetics
2020
//!
2121
//! The pipeline uses internal positional aesthetic names (pos1, pos2, etc.) that are
22-
//! transformed from user-facing names (x/y or theta/radius) early in the pipeline
22+
//! transformed from user-facing names (x/y or angle/radius) early in the pipeline
2323
//! and transformed back for output. This is handled by `AestheticContext`.
2424
2525
use std::collections::HashMap;
@@ -87,7 +87,7 @@ pub const NON_POSITIONAL: &[&str] = &[
8787
/// Comprehensive context for aesthetic operations.
8888
///
8989
/// Uses HashMaps for efficient O(1) lookups between user-facing and internal aesthetic names.
90-
/// Used to transform between user-facing aesthetic names (x/y or theta/radius)
90+
/// Used to transform between user-facing aesthetic names (x/y or angle/radius)
9191
/// and internal names (pos1/pos2), as well as facet aesthetics (panel/row/column)
9292
/// to internal facet names (facet1/facet2).
9393
///
@@ -102,8 +102,8 @@ pub const NON_POSITIONAL: &[&str] = &[
102102
/// assert_eq!(ctx.map_user_to_internal("ymin"), Some("pos2min"));
103103
///
104104
/// // For polar coords
105-
/// let ctx = AestheticContext::from_static(&["theta", "radius"], &[]);
106-
/// assert_eq!(ctx.map_user_to_internal("theta"), Some("pos1"));
105+
/// let ctx = AestheticContext::from_static(&["angle", "radius"], &[]);
106+
/// assert_eq!(ctx.map_user_to_internal("angle"), Some("pos1"));
107107
/// assert_eq!(ctx.map_user_to_internal("radius"), Some("pos2"));
108108
///
109109
/// // With facets
@@ -212,7 +212,7 @@ impl AestheticContext {
212212

213213
/// Map user aesthetic (positional or facet) to internal name.
214214
///
215-
/// Positional: "x" → "pos1", "ymin" → "pos2min", "theta" → "pos1"
215+
/// Positional: "x" → "pos1", "ymin" → "pos2min", "angle" → "pos1"
216216
/// Facet: "panel" → "facet1", "row" → "facet1", "column" → "facet2"
217217
///
218218
/// Note: Facet mappings work regardless of whether a FACET clause exists,
@@ -242,7 +242,7 @@ impl AestheticContext {
242242

243243
/// Map internal aesthetic to user-facing name (reverse of map_user_to_internal).
244244
///
245-
/// Positional: "pos1" → "x", "pos2min" → "ymin", "pos1" → "theta" (for polar)
245+
/// Positional: "pos1" → "x", "pos2min" → "ymin", "pos1" → "angle" (for polar)
246246
/// Facet: "facet1" → "panel" (wrap), "facet1" → "row" (grid), "facet2" → "column" (grid)
247247
/// Non-positional: "color" → "color" (unchanged)
248248
///
@@ -331,7 +331,7 @@ impl AestheticContext {
331331
&self.internal_primaries
332332
}
333333

334-
/// Get user positional aesthetics (x, y or theta, radius or custom names)
334+
/// Get user positional aesthetics (x, y or angle, radius or custom names)
335335
pub fn user_positional(&self) -> &[String] {
336336
&self.user_primaries
337337
}
@@ -515,7 +515,7 @@ mod tests {
515515
assert!(!is_positional_aesthetic("x"));
516516
assert!(!is_positional_aesthetic("y"));
517517
assert!(!is_positional_aesthetic("xmin"));
518-
assert!(!is_positional_aesthetic("theta"));
518+
assert!(!is_positional_aesthetic("angle"));
519519

520520
// Non-positional
521521
assert!(!is_positional_aesthetic("color"));
@@ -549,10 +549,10 @@ mod tests {
549549

550550
#[test]
551551
fn test_aesthetic_context_polar() {
552-
let ctx = AestheticContext::from_static(&["theta", "radius"], &[]);
552+
let ctx = AestheticContext::from_static(&["angle", "radius"], &[]);
553553

554554
// User positional names
555-
assert_eq!(ctx.user_positional(), &["theta", "radius"]);
555+
assert_eq!(ctx.user_positional(), &["angle", "radius"]);
556556

557557
// Primary internal names
558558
let primary: Vec<&str> = ctx
@@ -586,12 +586,12 @@ mod tests {
586586

587587
#[test]
588588
fn test_aesthetic_context_polar_mapping() {
589-
let ctx = AestheticContext::from_static(&["theta", "radius"], &[]);
589+
let ctx = AestheticContext::from_static(&["angle", "radius"], &[]);
590590

591591
// User to internal
592-
assert_eq!(ctx.map_user_to_internal("theta"), Some("pos1"));
592+
assert_eq!(ctx.map_user_to_internal("angle"), Some("pos1"));
593593
assert_eq!(ctx.map_user_to_internal("radius"), Some("pos2"));
594-
assert_eq!(ctx.map_user_to_internal("thetaend"), Some("pos1end"));
594+
assert_eq!(ctx.map_user_to_internal("angleend"), Some("pos1end"));
595595
assert_eq!(ctx.map_user_to_internal("radiusmin"), Some("pos2min"));
596596
}
597597

@@ -687,14 +687,14 @@ mod tests {
687687

688688
#[test]
689689
fn test_aesthetic_context_internal_to_user_polar() {
690-
let ctx = AestheticContext::from_static(&["theta", "radius"], &[]);
690+
let ctx = AestheticContext::from_static(&["angle", "radius"], &[]);
691691

692692
// Primary aesthetics map to polar names
693-
assert_eq!(ctx.map_internal_to_user("pos1"), "theta");
693+
assert_eq!(ctx.map_internal_to_user("pos1"), "angle");
694694
assert_eq!(ctx.map_internal_to_user("pos2"), "radius");
695695

696696
// Variants
697-
assert_eq!(ctx.map_internal_to_user("pos1end"), "thetaend");
697+
assert_eq!(ctx.map_internal_to_user("pos1end"), "angleend");
698698
assert_eq!(ctx.map_internal_to_user("pos2min"), "radiusmin");
699699
assert_eq!(ctx.map_internal_to_user("pos2max"), "radiusmax");
700700
}

src/plot/facet/resolve.rs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ fn compute_default_ncol(num_levels: usize) -> i64 {
9595
///
9696
/// * `facet` - The facet to resolve
9797
/// * `context` - Data context with unique values
98-
/// * `positional_names` - Valid positional aesthetic names (e.g., ["x", "y"] or ["theta", "radius"])
98+
/// * `positional_names` - Valid positional aesthetic names (e.g., ["x", "y"] or ["angle", "radius"])
9999
pub fn resolve_properties(
100100
facet: &mut Facet,
101101
context: &FacetDataContext,
@@ -157,7 +157,7 @@ pub fn resolve_properties(
157157
/// # Arguments
158158
///
159159
/// * `facet` - The facet to validate
160-
/// * `positional_names` - Valid positional aesthetic names (e.g., ["x", "y"] or ["theta", "radius"])
160+
/// * `positional_names` - Valid positional aesthetic names (e.g., ["x", "y"] or ["angle", "radius"])
161161
fn validate_free_property(facet: &Facet, positional_names: &[&str]) -> Result<(), String> {
162162
if let Some(value) = facet.properties.get("free") {
163163
match value {
@@ -239,7 +239,7 @@ fn format_options(names: &[&str]) -> String {
239239
///
240240
/// Transforms user-provided values to a boolean vector (position-indexed):
241241
/// - User writes: `free => 'x'` → stored as: `free => [true, false]`
242-
/// - User writes: `free => 'theta'` → stored as: `free => [true, false]`
242+
/// - User writes: `free => 'angle'` → stored as: `free => [true, false]`
243243
/// - User writes: `free => ['x', 'y']` → stored as: `free => [true, true]`
244244
/// - User writes: `free => null` or absent → stored as: `free => [false, false]`
245245
///
@@ -387,7 +387,7 @@ mod tests {
387387
/// Default positional names for cartesian coords
388388
const CARTESIAN: &[&str] = &["x", "y"];
389389
/// Positional names for polar coords
390-
const POLAR: &[&str] = &["theta", "radius"];
390+
const POLAR: &[&str] = &["angle", "radius"];
391391

392392
fn make_wrap_facet() -> Facet {
393393
Facet::new(FacetLayout::Wrap {
@@ -868,17 +868,17 @@ mod tests {
868868
// ========================================
869869

870870
#[test]
871-
fn test_free_property_theta_valid() {
871+
fn test_free_property_angle_valid() {
872872
let mut facet = make_wrap_facet();
873873
facet.properties.insert(
874874
"free".to_string(),
875-
ParameterValue::String("theta".to_string()),
875+
ParameterValue::String("angle".to_string()),
876876
);
877877

878878
let context = make_context(5);
879879
let result = resolve_properties(&mut facet, &context, POLAR);
880880
assert!(result.is_ok());
881-
// theta is first positional -> [true, false]
881+
// angle is first positional -> [true, false]
882882
assert_eq!(get_free_bools(&facet), Some(vec![true, false]));
883883
}
884884

@@ -903,7 +903,7 @@ mod tests {
903903
facet.properties.insert(
904904
"free".to_string(),
905905
ParameterValue::Array(vec![
906-
crate::plot::ArrayElement::String("theta".to_string()),
906+
crate::plot::ArrayElement::String("angle".to_string()),
907907
crate::plot::ArrayElement::String("radius".to_string()),
908908
]),
909909
);
@@ -929,24 +929,24 @@ mod tests {
929929
assert!(result.is_err());
930930
let err = result.unwrap_err();
931931
assert!(err.contains("'x'"));
932-
assert!(err.contains("theta") || err.contains("radius"));
932+
assert!(err.contains("angle") || err.contains("radius"));
933933
}
934934

935935
#[test]
936936
fn test_error_polar_names_in_cartesian() {
937-
// theta/radius should not be valid for cartesian coords
937+
// angle/radius should not be valid for cartesian coords
938938
let mut facet = make_wrap_facet();
939939
facet.properties.insert(
940940
"free".to_string(),
941-
ParameterValue::String("theta".to_string()),
941+
ParameterValue::String("angle".to_string()),
942942
);
943943

944944
let context = make_context(5);
945945
let result = resolve_properties(&mut facet, &context, CARTESIAN);
946946

947947
assert!(result.is_err());
948948
let err = result.unwrap_err();
949-
assert!(err.contains("'theta'"));
949+
assert!(err.contains("'angle'"));
950950
assert!(err.contains("'x'") || err.contains("'y'"));
951951
}
952952

src/plot/main.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -809,18 +809,18 @@ mod tests {
809809

810810
#[test]
811811
fn test_label_transform_with_polar_project() {
812-
// LABEL theta/radius with polar should transform to pos1/pos2
812+
// LABEL angle/radius with polar should transform to pos1/pos2
813813
use crate::plot::projection::{Coord, Projection};
814814

815815
let mut spec = Plot::new();
816816
spec.project = Some(Projection {
817817
coord: Coord::polar(),
818-
aesthetics: vec!["theta".to_string(), "radius".to_string()],
818+
aesthetics: vec!["angle".to_string(), "radius".to_string()],
819819
properties: HashMap::new(),
820820
});
821821
spec.labels = Some(Labels {
822822
labels: HashMap::from([
823-
("theta".to_string(), "Angle".to_string()),
823+
("angle".to_string(), "Angle".to_string()),
824824
("radius".to_string(), "Distance".to_string()),
825825
]),
826826
});

0 commit comments

Comments
 (0)