diff --git a/CLAUDE.md b/CLAUDE.md index cf082c6a..e24b68b8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1480,7 +1480,7 @@ PROJECT [, ...] TO [SETTING ] | Coord Type | Default Aesthetics | Description | |------------|-------------------|-------------| | `cartesian` | `x`, `y` | Standard x/y Cartesian coordinates | -| `polar` | `theta`, `radius` | Polar coordinates (for pie charts, rose plots) | +| `polar` | `angle`, `radius` | Polar coordinates (for pie charts, rose plots) | **Flipping Axes**: @@ -1505,7 +1505,7 @@ Note: For axis limits, use `SCALE x FROM [min, max]` or `SCALE y FROM [min, max] **Polar**: -- `theta => ` - Which aesthetic maps to angle (defaults to `y`) +- No special properties (angle/radius aesthetics are used directly) **Important Notes**: @@ -1534,10 +1534,10 @@ PROJECT y, x TO cartesian -- Custom aesthetic names PROJECT myX, myY TO cartesian --- Polar for pie chart (using default theta/radius aesthetics) +-- Polar for pie chart (using default angle/radius aesthetics) PROJECT TO polar --- Polar with y/x aesthetics (y becomes theta, x becomes radius) +-- Polar with y/x aesthetics (y becomes angle, x becomes radius) PROJECT y, x TO polar -- Polar with start angle offset (3 o'clock position) diff --git a/doc/ggsql.xml b/doc/ggsql.xml index 74c8b8f2..39437676 100644 --- a/doc/ggsql.xml +++ b/doc/ggsql.xml @@ -242,7 +242,7 @@ xlim ylim ratio - theta + angle clip diff --git a/doc/syntax/clause/project.qmd b/doc/syntax/clause/project.qmd index d7753ce5..f62292ff 100644 --- a/doc/syntax/clause/project.qmd +++ b/doc/syntax/clause/project.qmd @@ -32,6 +32,6 @@ This clause behaves much like the `SETTINGS` clause in `DRAW`, in that it allows 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 * If `x`, `y` or any of their variants are mapped to, a Cartesian coordinate system is used -* If `theta`, `radius` or any of their variants are mapped to, a polar coordinate system is used +* If `angle`, `radius` or any of their variants are mapped to, a polar coordinate system is used * If none of the above applies, the plot defaults to a Cartesian coordinate system -* If multiple applies (e.g. mapping to both x and theta) an error is thrown +* If multiple applies (e.g. mapping to both x and angle) an error is thrown diff --git a/doc/syntax/coord/polar.qmd b/doc/syntax/coord/polar.qmd index 75adaab0..348e33e7 100644 --- a/doc/syntax/coord/polar.qmd +++ b/doc/syntax/coord/polar.qmd @@ -8,7 +8,7 @@ The polar coordinate system interprets its primary aesthetic as the angular posi The polar coordinate system has the following default positional aesthetics which will be used if no others have been provided: * **Primary**: `radius` (distance from center) -* **Secondary**: `theta` (angular position) +* **Secondary**: `angle` (angular position) Users can provide their own aesthetic names if needed. For example, if using `x` and `y` aesthetics: @@ -16,16 +16,16 @@ Users can provide their own aesthetic names if needed. For example, if using `x` PROJECT y, x TO polar ``` -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. +This maps `y` to radius and `x` to angle. This is useful when converting from a cartesian coordinate system without editing all the mappings. ## Settings * `clip`: Should data be removed if it appears outside the bounds of the coordinate system. Defaults to `true` -* `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). +* `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). - `0` = 12 o'clock position (top) - `90` = 3 o'clock position (right) - `-90` or `270` = 9 o'clock position (left) - `180` = 6 o'clock position (bottom) -* `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. +* `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. * `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. - `0` = full pie (no hole) - `0.3` = donut with 30% hole @@ -33,7 +33,7 @@ This maps `y` to radius and `x` to theta (angle). This is useful when converting ## Examples -### Pie chart using theta/radius aesthetics +### Pie chart using angle/radius aesthetics ```{ggsql} VISUALISE species AS fill FROM ggsql:penguins DRAW bar diff --git a/ggsql-vscode/syntaxes/ggsql.tmLanguage.json b/ggsql-vscode/syntaxes/ggsql.tmLanguage.json index ecd49865..788cd42c 100644 --- a/ggsql-vscode/syntaxes/ggsql.tmLanguage.json +++ b/ggsql-vscode/syntaxes/ggsql.tmLanguage.json @@ -375,7 +375,7 @@ }, { "name": "support.type.property.ggsql", - "match": "\\b(xlim|ylim|ratio|theta|clip)\\b" + "match": "\\b(xlim|ylim|ratio|angle|clip)\\b" }, { "include": "#common-clause-patterns" } ] diff --git a/src/parser/builder.rs b/src/parser/builder.rs index 7767679e..9b889ca1 100644 --- a/src/parser/builder.rs +++ b/src/parser/builder.rs @@ -289,7 +289,7 @@ fn build_visualise_statement(node: &Node, source: &SourceTree) -> Result { // This must happen after all clauses are processed (especially PROJECT and FACET) spec.initialize_aesthetic_context(); - // Transform all aesthetic keys from user-facing (x/y or theta/radius) to internal (pos1/pos2) + // Transform all aesthetic keys from user-facing (x/y or angle/radius) to internal (pos1/pos2) // This enables generic handling throughout the pipeline and must happen before merge // since geom definitions use internal names for their supported/required aesthetics spec.transform_aesthetics_to_internal(); @@ -1343,7 +1343,7 @@ mod tests { fn test_project_default_aesthetics_polar() { let query = r#" VISUALISE - DRAW bar MAPPING category AS theta, value AS radius + DRAW bar MAPPING category AS angle, value AS radius PROJECT TO polar "#; @@ -1354,7 +1354,7 @@ mod tests { let project = specs[0].project.as_ref().unwrap(); assert_eq!( project.aesthetics, - vec!["radius".to_string(), "theta".to_string()] + vec!["radius".to_string(), "angle".to_string()] ); } @@ -3399,8 +3399,8 @@ mod tests { } #[test] - fn test_infer_polar_from_theta_radius_mappings() { - let query = "VISUALISE DRAW bar MAPPING cat AS theta, val AS radius"; + fn test_infer_polar_from_angle_radius_mappings() { + let query = "VISUALISE DRAW bar MAPPING cat AS angle, val AS radius"; let result = parse_test_query(query); assert!(result.is_ok()); @@ -3409,15 +3409,15 @@ mod tests { // Should infer polar projection let project = specs[0].project.as_ref().unwrap(); assert_eq!(project.coord.coord_kind(), CoordKind::Polar); - assert_eq!(project.aesthetics, vec!["radius", "theta"]); + assert_eq!(project.aesthetics, vec!["radius", "angle"]); } #[test] fn test_explicit_project_overrides_inference() { - // Explicitly use cartesian even though mappings use theta + // Explicitly use cartesian even though mappings use angle let query = r#" VISUALISE - DRAW bar MAPPING cat AS theta, val AS radius + DRAW bar MAPPING cat AS angle, val AS radius PROJECT TO cartesian "#; @@ -3432,8 +3432,8 @@ mod tests { #[test] fn test_conflicting_aesthetics_error() { - // Using both x and theta should error - let query = "VISUALISE DRAW point MAPPING a AS x, b AS theta"; + // Using both x and angle should error + let query = "VISUALISE DRAW point MAPPING a AS x, b AS angle"; let result = parse_test_query(query); assert!(result.is_err()); diff --git a/src/plot/aesthetic.rs b/src/plot/aesthetic.rs index 161d9cec..dcb724ab 100644 --- a/src/plot/aesthetic.rs +++ b/src/plot/aesthetic.rs @@ -19,7 +19,7 @@ //! # Internal vs User-Facing Aesthetics //! //! The pipeline uses internal positional aesthetic names (pos1, pos2, etc.) that are -//! transformed from user-facing names (x/y or theta/radius) early in the pipeline +//! transformed from user-facing names (x/y or angle/radius) early in the pipeline //! and transformed back for output. This is handled by `AestheticContext`. use std::collections::HashMap; @@ -87,7 +87,7 @@ pub const NON_POSITIONAL: &[&str] = &[ /// Comprehensive context for aesthetic operations. /// /// Uses HashMaps for efficient O(1) lookups between user-facing and internal aesthetic names. -/// Used to transform between user-facing aesthetic names (x/y or theta/radius) +/// Used to transform between user-facing aesthetic names (x/y or angle/radius) /// and internal names (pos1/pos2), as well as facet aesthetics (panel/row/column) /// to internal facet names (facet1/facet2). /// @@ -102,8 +102,8 @@ pub const NON_POSITIONAL: &[&str] = &[ /// assert_eq!(ctx.map_user_to_internal("ymin"), Some("pos2min")); /// /// // For polar coords -/// let ctx = AestheticContext::from_static(&["theta", "radius"], &[]); -/// assert_eq!(ctx.map_user_to_internal("theta"), Some("pos1")); +/// let ctx = AestheticContext::from_static(&["angle", "radius"], &[]); +/// assert_eq!(ctx.map_user_to_internal("angle"), Some("pos1")); /// assert_eq!(ctx.map_user_to_internal("radius"), Some("pos2")); /// /// // With facets @@ -212,7 +212,7 @@ impl AestheticContext { /// Map user aesthetic (positional or facet) to internal name. /// - /// Positional: "x" → "pos1", "ymin" → "pos2min", "theta" → "pos1" + /// Positional: "x" → "pos1", "ymin" → "pos2min", "angle" → "pos1" /// Facet: "panel" → "facet1", "row" → "facet1", "column" → "facet2" /// /// Note: Facet mappings work regardless of whether a FACET clause exists, @@ -242,7 +242,7 @@ impl AestheticContext { /// Map internal aesthetic to user-facing name (reverse of map_user_to_internal). /// - /// Positional: "pos1" → "x", "pos2min" → "ymin", "pos1" → "theta" (for polar) + /// Positional: "pos1" → "x", "pos2min" → "ymin", "pos1" → "angle" (for polar) /// Facet: "facet1" → "panel" (wrap), "facet1" → "row" (grid), "facet2" → "column" (grid) /// Non-positional: "color" → "color" (unchanged) /// @@ -331,7 +331,7 @@ impl AestheticContext { &self.internal_primaries } - /// Get user positional aesthetics (x, y or theta, radius or custom names) + /// Get user positional aesthetics (x, y or angle, radius or custom names) pub fn user_positional(&self) -> &[String] { &self.user_primaries } @@ -515,7 +515,7 @@ mod tests { assert!(!is_positional_aesthetic("x")); assert!(!is_positional_aesthetic("y")); assert!(!is_positional_aesthetic("xmin")); - assert!(!is_positional_aesthetic("theta")); + assert!(!is_positional_aesthetic("angle")); // Non-positional assert!(!is_positional_aesthetic("color")); @@ -549,10 +549,10 @@ mod tests { #[test] fn test_aesthetic_context_polar() { - let ctx = AestheticContext::from_static(&["theta", "radius"], &[]); + let ctx = AestheticContext::from_static(&["angle", "radius"], &[]); // User positional names - assert_eq!(ctx.user_positional(), &["theta", "radius"]); + assert_eq!(ctx.user_positional(), &["angle", "radius"]); // Primary internal names let primary: Vec<&str> = ctx @@ -586,12 +586,12 @@ mod tests { #[test] fn test_aesthetic_context_polar_mapping() { - let ctx = AestheticContext::from_static(&["theta", "radius"], &[]); + let ctx = AestheticContext::from_static(&["angle", "radius"], &[]); // User to internal - assert_eq!(ctx.map_user_to_internal("theta"), Some("pos1")); + assert_eq!(ctx.map_user_to_internal("angle"), Some("pos1")); assert_eq!(ctx.map_user_to_internal("radius"), Some("pos2")); - assert_eq!(ctx.map_user_to_internal("thetaend"), Some("pos1end")); + assert_eq!(ctx.map_user_to_internal("angleend"), Some("pos1end")); assert_eq!(ctx.map_user_to_internal("radiusmin"), Some("pos2min")); } @@ -687,14 +687,14 @@ mod tests { #[test] fn test_aesthetic_context_internal_to_user_polar() { - let ctx = AestheticContext::from_static(&["theta", "radius"], &[]); + let ctx = AestheticContext::from_static(&["angle", "radius"], &[]); // Primary aesthetics map to polar names - assert_eq!(ctx.map_internal_to_user("pos1"), "theta"); + assert_eq!(ctx.map_internal_to_user("pos1"), "angle"); assert_eq!(ctx.map_internal_to_user("pos2"), "radius"); // Variants - assert_eq!(ctx.map_internal_to_user("pos1end"), "thetaend"); + assert_eq!(ctx.map_internal_to_user("pos1end"), "angleend"); assert_eq!(ctx.map_internal_to_user("pos2min"), "radiusmin"); assert_eq!(ctx.map_internal_to_user("pos2max"), "radiusmax"); } diff --git a/src/plot/facet/resolve.rs b/src/plot/facet/resolve.rs index 670bd335..777e515d 100644 --- a/src/plot/facet/resolve.rs +++ b/src/plot/facet/resolve.rs @@ -95,7 +95,7 @@ fn compute_default_ncol(num_levels: usize) -> i64 { /// /// * `facet` - The facet to resolve /// * `context` - Data context with unique values -/// * `positional_names` - Valid positional aesthetic names (e.g., ["x", "y"] or ["theta", "radius"]) +/// * `positional_names` - Valid positional aesthetic names (e.g., ["x", "y"] or ["angle", "radius"]) pub fn resolve_properties( facet: &mut Facet, context: &FacetDataContext, @@ -157,7 +157,7 @@ pub fn resolve_properties( /// # Arguments /// /// * `facet` - The facet to validate -/// * `positional_names` - Valid positional aesthetic names (e.g., ["x", "y"] or ["theta", "radius"]) +/// * `positional_names` - Valid positional aesthetic names (e.g., ["x", "y"] or ["angle", "radius"]) fn validate_free_property(facet: &Facet, positional_names: &[&str]) -> Result<(), String> { if let Some(value) = facet.properties.get("free") { match value { @@ -239,7 +239,7 @@ fn format_options(names: &[&str]) -> String { /// /// Transforms user-provided values to a boolean vector (position-indexed): /// - User writes: `free => 'x'` → stored as: `free => [true, false]` -/// - User writes: `free => 'theta'` → stored as: `free => [true, false]` +/// - User writes: `free => 'angle'` → stored as: `free => [true, false]` /// - User writes: `free => ['x', 'y']` → stored as: `free => [true, true]` /// - User writes: `free => null` or absent → stored as: `free => [false, false]` /// @@ -387,7 +387,7 @@ mod tests { /// Default positional names for cartesian coords const CARTESIAN: &[&str] = &["x", "y"]; /// Positional names for polar coords - const POLAR: &[&str] = &["theta", "radius"]; + const POLAR: &[&str] = &["angle", "radius"]; fn make_wrap_facet() -> Facet { Facet::new(FacetLayout::Wrap { @@ -868,17 +868,17 @@ mod tests { // ======================================== #[test] - fn test_free_property_theta_valid() { + fn test_free_property_angle_valid() { let mut facet = make_wrap_facet(); facet.properties.insert( "free".to_string(), - ParameterValue::String("theta".to_string()), + ParameterValue::String("angle".to_string()), ); let context = make_context(5); let result = resolve_properties(&mut facet, &context, POLAR); assert!(result.is_ok()); - // theta is first positional -> [true, false] + // angle is first positional -> [true, false] assert_eq!(get_free_bools(&facet), Some(vec![true, false])); } @@ -903,7 +903,7 @@ mod tests { facet.properties.insert( "free".to_string(), ParameterValue::Array(vec![ - crate::plot::ArrayElement::String("theta".to_string()), + crate::plot::ArrayElement::String("angle".to_string()), crate::plot::ArrayElement::String("radius".to_string()), ]), ); @@ -929,16 +929,16 @@ mod tests { assert!(result.is_err()); let err = result.unwrap_err(); assert!(err.contains("'x'")); - assert!(err.contains("theta") || err.contains("radius")); + assert!(err.contains("angle") || err.contains("radius")); } #[test] fn test_error_polar_names_in_cartesian() { - // theta/radius should not be valid for cartesian coords + // angle/radius should not be valid for cartesian coords let mut facet = make_wrap_facet(); facet.properties.insert( "free".to_string(), - ParameterValue::String("theta".to_string()), + ParameterValue::String("angle".to_string()), ); let context = make_context(5); @@ -946,7 +946,7 @@ mod tests { assert!(result.is_err()); let err = result.unwrap_err(); - assert!(err.contains("'theta'")); + assert!(err.contains("'angle'")); assert!(err.contains("'x'") || err.contains("'y'")); } diff --git a/src/plot/main.rs b/src/plot/main.rs index 523f9c96..e014d400 100644 --- a/src/plot/main.rs +++ b/src/plot/main.rs @@ -809,18 +809,18 @@ mod tests { #[test] fn test_label_transform_with_polar_project() { - // LABEL theta/radius with polar should transform to pos1/pos2 + // LABEL angle/radius with polar should transform to pos1/pos2 use crate::plot::projection::{Coord, Projection}; let mut spec = Plot::new(); spec.project = Some(Projection { coord: Coord::polar(), - aesthetics: vec!["theta".to_string(), "radius".to_string()], + aesthetics: vec!["angle".to_string(), "radius".to_string()], properties: HashMap::new(), }); spec.labels = Some(Labels { labels: HashMap::from([ - ("theta".to_string(), "Angle".to_string()), + ("angle".to_string(), "Angle".to_string()), ("radius".to_string(), "Distance".to_string()), ]), }); diff --git a/src/plot/projection/coord/mod.rs b/src/plot/projection/coord/mod.rs index 27023036..825e6584 100644 --- a/src/plot/projection/coord/mod.rs +++ b/src/plot/projection/coord/mod.rs @@ -67,7 +67,7 @@ pub trait CoordTrait: std::fmt::Debug + std::fmt::Display + Send + Sync { /// Primary positional aesthetic names for this coord. /// /// Returns the user-facing positional aesthetic names. - /// e.g., ["x", "y"] for cartesian, ["radius", "theta"] for polar. + /// e.g., ["x", "y"] for cartesian, ["radius", "angle"] for polar. /// /// These names are transformed to internal names (pos1, pos2, etc.) /// early in the pipeline and transformed back for output. @@ -160,7 +160,7 @@ impl Coord { } /// Primary positional aesthetic names for this coord. - /// e.g., ["x", "y"] for cartesian, ["radius", "theta"] for polar. + /// e.g., ["x", "y"] for cartesian, ["radius", "angle"] for polar. pub fn positional_aesthetic_names(&self) -> &'static [&'static str] { self.0.positional_aesthetic_names() } @@ -286,6 +286,6 @@ mod tests { assert_eq!(cartesian.positional_aesthetic_names(), &["x", "y"]); let polar = Coord::polar(); - assert_eq!(polar.positional_aesthetic_names(), &["radius", "theta"]); + assert_eq!(polar.positional_aesthetic_names(), &["radius", "angle"]); } } diff --git a/src/plot/projection/coord/polar.rs b/src/plot/projection/coord/polar.rs index 95ba5000..b881b018 100644 --- a/src/plot/projection/coord/polar.rs +++ b/src/plot/projection/coord/polar.rs @@ -17,7 +17,7 @@ impl CoordTrait for Polar { } fn positional_aesthetic_names(&self) -> &'static [&'static str] { - &["radius", "theta"] + &["radius", "angle"] } fn default_properties(&self) -> &'static [DefaultParam] { diff --git a/src/plot/projection/resolve.rs b/src/plot/projection/resolve.rs index 24921206..338408b8 100644 --- a/src/plot/projection/resolve.rs +++ b/src/plot/projection/resolve.rs @@ -13,14 +13,14 @@ use crate::plot::Mappings; const CARTESIAN_PRIMARIES: &[&str] = &["x", "y"]; /// Polar primary aesthetic names -const POLAR_PRIMARIES: &[&str] = &["theta", "radius"]; +const POLAR_PRIMARIES: &[&str] = &["angle", "radius"]; /// Resolve coordinate system for a Plot /// /// If `project` is `Some`, returns `Ok(None)` (keep existing, no changes needed). /// If `project` is `None`, infers coord from aesthetic mappings: /// - x/y/xmin/xmax/ymin/ymax → Cartesian -/// - theta/radius/thetamin/... → Polar +/// - angle/radius/anglemin/... → Polar /// - Both → Error /// - Neither → Ok(None) (caller should use default Cartesian) /// @@ -54,7 +54,7 @@ pub fn resolve_coord( // Determine result if found_cartesian && found_polar { return Err( - "Conflicting aesthetics: cannot use both cartesian (x/y) and polar (theta/radius) \ + "Conflicting aesthetics: cannot use both cartesian (x/y) and polar (angle/radius) \ aesthetics in the same plot. Use PROJECT TO cartesian or PROJECT TO polar to \ specify the coordinate system explicitly." .to_string(), @@ -103,7 +103,7 @@ fn check_aesthetic(aesthetic: &str, found_cartesian: &mut bool, found_polar: &mu return; } - // Strip positional suffix if present (xmin -> x, thetamax -> theta) + // Strip positional suffix if present (xmin -> x, anglemax -> angle) let primary = strip_positional_suffix(aesthetic); // Check against cartesian primaries @@ -118,7 +118,7 @@ fn check_aesthetic(aesthetic: &str, found_cartesian: &mut bool, found_polar: &mu } /// Strip positional suffix from an aesthetic name. -/// e.g., "xmin" -> "x", "thetamax" -> "theta", "y" -> "y" +/// e.g., "xmin" -> "x", "anglemax" -> "angle", "y" -> "y" fn strip_positional_suffix(name: &str) -> &str { for suffix in POSITIONAL_SUFFIXES { if let Some(base) = name.strip_suffix(suffix) { @@ -153,7 +153,7 @@ mod tests { aesthetics: vec!["x".to_string(), "y".to_string()], properties: HashMap::new(), }; - let global = mappings_with(&["theta", "radius"]); // Would infer polar + let global = mappings_with(&["angle", "radius"]); // Would infer polar let layers: Vec<&Mappings> = vec![]; let result = resolve_coord(Some(&project), &global, &layers); @@ -211,8 +211,8 @@ mod tests { // ======================================== #[test] - fn test_infer_polar_from_theta_radius() { - let global = mappings_with(&["theta", "radius"]); + fn test_infer_polar_from_angle_radius() { + let global = mappings_with(&["angle", "radius"]); let layers: Vec<&Mappings> = vec![]; let result = resolve_coord(None, &global, &layers); @@ -221,12 +221,12 @@ mod tests { assert!(inferred.is_some()); let proj = inferred.unwrap(); assert_eq!(proj.coord.coord_kind(), CoordKind::Polar); - assert_eq!(proj.aesthetics, vec!["radius", "theta"]); + assert_eq!(proj.aesthetics, vec!["radius", "angle"]); } #[test] fn test_infer_polar_from_variants() { - let global = mappings_with(&["thetamin", "radiusmax"]); + let global = mappings_with(&["anglemin", "radiusmax"]); let layers: Vec<&Mappings> = vec![]; let result = resolve_coord(None, &global, &layers); @@ -240,7 +240,7 @@ mod tests { #[test] fn test_infer_polar_from_layer() { let global = Mappings::new(); - let layer = mappings_with(&["theta", "radius"]); + let layer = mappings_with(&["angle", "radius"]); let layers: Vec<&Mappings> = vec![&layer]; let result = resolve_coord(None, &global, &layers); @@ -284,7 +284,7 @@ mod tests { #[test] fn test_conflict_error() { - let global = mappings_with(&["x", "theta"]); + let global = mappings_with(&["x", "angle"]); let layers: Vec<&Mappings> = vec![]; let result = resolve_coord(None, &global, &layers); @@ -298,7 +298,7 @@ mod tests { #[test] fn test_conflict_across_global_and_layer() { let global = mappings_with(&["x", "y"]); - let layer = mappings_with(&["theta"]); + let layer = mappings_with(&["angle"]); let layers: Vec<&Mappings> = vec![&layer]; let result = resolve_coord(None, &global, &layers); @@ -328,7 +328,7 @@ mod tests { #[test] fn test_wildcard_with_polar() { let mut global = Mappings::with_wildcard(); - global.insert("theta", AestheticValue::standard_column("cat")); + global.insert("angle", AestheticValue::standard_column("cat")); let layers: Vec<&Mappings> = vec![]; let result = resolve_coord(None, &global, &layers); @@ -362,8 +362,8 @@ mod tests { assert_eq!(strip_positional_suffix("xend"), "x"); assert_eq!(strip_positional_suffix("ymin"), "y"); assert_eq!(strip_positional_suffix("ymax"), "y"); - assert_eq!(strip_positional_suffix("theta"), "theta"); - assert_eq!(strip_positional_suffix("thetamin"), "theta"); + assert_eq!(strip_positional_suffix("angle"), "angle"); + assert_eq!(strip_positional_suffix("anglemin"), "angle"); assert_eq!(strip_positional_suffix("radiusmax"), "radius"); } } diff --git a/src/plot/projection/types.rs b/src/plot/projection/types.rs index d2cffd1c..2e168db1 100644 --- a/src/plot/projection/types.rs +++ b/src/plot/projection/types.rs @@ -15,7 +15,7 @@ pub struct Projection { pub coord: Coord, /// Positional aesthetic names (resolved: explicit or coord defaults) /// Always populated after building - never empty. - /// e.g., ["x", "y"] for cartesian, ["radius", "theta"] for polar, + /// e.g., ["x", "y"] for cartesian, ["radius", "angle"] for polar, /// or custom names like ["a", "b"] if user specifies them. pub aesthetics: Vec, /// Projection-specific options diff --git a/src/plot/types.rs b/src/plot/types.rs index ec28b525..c0639868 100644 --- a/src/plot/types.rs +++ b/src/plot/types.rs @@ -112,7 +112,7 @@ impl Mappings { /// Transform aesthetic keys from user-facing to internal names. /// /// Uses the provided AestheticContext to map user-facing positional aesthetic names - /// (e.g., "x", "y", "theta", "radius") to internal names (e.g., "pos1", "pos2"). + /// (e.g., "x", "y", "angle", "radius") to internal names (e.g., "pos1", "pos2"). /// Non-positional aesthetics (e.g., "color", "size") are left unchanged. pub fn transform_to_internal(&mut self, ctx: &super::AestheticContext) { let original_aesthetics = std::mem::take(&mut self.aesthetics); diff --git a/src/reader/mod.rs b/src/reader/mod.rs index 9bc7da7d..b74c7755 100644 --- a/src/reader/mod.rs +++ b/src/reader/mod.rs @@ -652,10 +652,10 @@ mod tests { let json2: serde_json::Value = serde_json::from_str(&result2).unwrap(); check_encoding_keys(&json2, "PROJECT x, y TO polar"); - // Test case 3: PROJECT TO polar (default radius/theta names) + // Test case 3: PROJECT TO polar (default radius/angle names) let query3 = r#" SELECT * FROM (VALUES ('A', 10), ('B', 20)) AS t(category, value) - VISUALISE value AS theta, category AS fill + VISUALISE value AS angle, category AS fill DRAW bar PROJECT TO polar "#; @@ -1220,14 +1220,14 @@ mod tests { #[test] fn test_label_with_polar_project() { - // End-to-end test: LABEL theta/radius with PROJECT TO polar + // End-to-end test: LABEL angle/radius with PROJECT TO polar let reader = DuckDBReader::from_connection_string("duckdb://memory").unwrap(); let query = r#" SELECT * FROM (VALUES ('A', 10), ('B', 20)) AS t(category, value) - VISUALISE value AS theta, category AS fill + VISUALISE value AS angle, category AS fill DRAW bar PROJECT TO polar - LABEL theta => 'Angle', radius => 'Distance' + LABEL angle => 'Angle', radius => 'Distance' "#; let spec = reader.execute(query).unwrap(); diff --git a/tree-sitter-ggsql/grammar.js b/tree-sitter-ggsql/grammar.js index f5f01d34..1291c4fa 100644 --- a/tree-sitter-ggsql/grammar.js +++ b/tree-sitter-ggsql/grammar.js @@ -672,8 +672,8 @@ module.exports = grammar({ // Position aesthetics (cartesian) 'x', 'y', 'xmin', 'xmax', 'ymin', 'ymax', 'xend', 'yend', // Position aesthetics (polar) - 'theta', 'radius', 'thetamin', 'thetamax', 'radiusmin', 'radiusmax', - 'thetaend', 'radiusend', + 'angle', 'radius', 'anglemin', 'anglemax', 'radiusmin', 'radiusmax', + 'angleend', 'radiusend', // Aggregation aesthetic (for bar charts) 'weight', // Color aesthetics @@ -793,8 +793,8 @@ module.exports = grammar({ // PROJECT TO cartesian (defaults to x, y) // PROJECT x, y TO cartesian (explicit aesthetics) // PROJECT a, b TO cartesian (custom aesthetic names) - // PROJECT TO polar (defaults to theta, radius) - // PROJECT theta, radius TO polar (explicit aesthetics) + // PROJECT TO polar (defaults to angle, radius) + // PROJECT angle, radius TO polar (explicit aesthetics) // PROJECT TO cartesian SETTING clip => true project_clause: $ => seq( caseInsensitive('PROJECT'), diff --git a/tree-sitter-ggsql/test/corpus/basic.txt b/tree-sitter-ggsql/test/corpus/basic.txt index 70eec146..49c41211 100644 --- a/tree-sitter-ggsql/test/corpus/basic.txt +++ b/tree-sitter-ggsql/test/corpus/basic.txt @@ -649,7 +649,7 @@ PROJECT TO cartesian SETTING ratio => 1 PROJECT TO polar (default aesthetics) ================================================================================ -VISUALISE theta, radius +VISUALISE angle, radius DRAW point PROJECT TO polar @@ -681,7 +681,7 @@ PROJECT TO polar PROJECT TO polar with SETTING start ================================================================================ -VISUALISE theta, radius +VISUALISE angle, radius DRAW bar PROJECT TO polar SETTING start => 90