From fbf1a9483e81109c8f785ee7d0b0cdd326d5ceae Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Fri, 12 Jun 2026 13:55:43 +0200 Subject: [PATCH] Roll out `Parameters` type alias across the codebase Replace all `HashMap` type annotations and `HashMap::new()` calls (where the result is typed as Parameters) with the `Parameters` type alias defined in `plot/types.rs`. Re-export `Parameters` from `lib.rs` for external consumers. Co-Authored-By: Claude Opus 4.6 --- src/execute/scale.rs | 6 +- src/lib.rs | 2 +- src/parser/builder.rs | 24 ++---- src/plot/facet/types.rs | 7 +- src/plot/layer/geom/area.rs | 4 +- src/plot/layer/geom/bar.rs | 5 +- src/plot/layer/geom/boxplot.rs | 11 ++- src/plot/layer/geom/density.rs | 32 ++++--- src/plot/layer/geom/histogram.rs | 8 +- src/plot/layer/geom/line.rs | 4 +- src/plot/layer/geom/mod.rs | 33 ++------ src/plot/layer/geom/ribbon.rs | 4 +- src/plot/layer/geom/rule.rs | 4 +- src/plot/layer/geom/smooth.rs | 4 +- src/plot/layer/geom/spatial.rs | 4 +- src/plot/layer/geom/stat_aggregate.rs | 13 +-- src/plot/layer/geom/text.rs | 11 +-- src/plot/layer/geom/tile.rs | 39 +++++---- src/plot/layer/geom/violin.rs | 29 +++---- src/plot/layer/mod.rs | 9 +- src/plot/main.rs | 9 +- src/plot/projection/coord/cartesian.rs | 7 +- src/plot/projection/coord/map.rs | 23 +++--- src/plot/projection/coord/map_projections.rs | 22 ++--- src/plot/projection/coord/mod.rs | 17 ++-- src/plot/projection/coord/polar.rs | 15 ++-- src/plot/projection/resolve.rs | 15 ++-- src/plot/projection/types.rs | 12 +-- src/plot/scale/scale_type/mod.rs | 87 ++++++++++---------- src/plot/scale/types.rs | 6 +- src/plot/types.rs | 3 + src/writer/vegalite/encoding.rs | 3 +- src/writer/vegalite/layer.rs | 7 +- src/writer/vegalite/mod.rs | 20 ++--- 34 files changed, 226 insertions(+), 273 deletions(-) diff --git a/src/execute/scale.rs b/src/execute/scale.rs index dd176026f..0dc39c77f 100644 --- a/src/execute/scale.rs +++ b/src/execute/scale.rs @@ -1416,7 +1416,7 @@ pub fn apply_oob_to_column_discrete( #[cfg(test)] mod tests { use super::*; - use crate::plot::ArrayElement; + use crate::plot::{ArrayElement, Parameters}; use crate::Geom; use arrow::datatypes::DataType; @@ -1722,8 +1722,8 @@ mod tests { spec.project = Some(Projection { coord, aesthetics, - properties: std::collections::HashMap::new(), - computed: std::collections::HashMap::new(), + properties: Parameters::new(), + computed: Parameters::new(), }); // Create scale for pos2 (theta in polar) without explicit expand diff --git a/src/lib.rs b/src/lib.rs index 3e7401578..bdb8a521f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -55,7 +55,7 @@ pub mod validate; // Re-export key types for convenience pub use plot::{ - AestheticValue, DataSource, Facet, FacetLayout, Geom, Layer, Mappings, Plot, Scale, + AestheticValue, DataSource, Facet, FacetLayout, Geom, Layer, Mappings, Parameters, Plot, Scale, SqlExpression, }; diff --git a/src/parser/builder.rs b/src/parser/builder.rs index 97149a2e5..fa5d4d4e0 100644 --- a/src/parser/builder.rs +++ b/src/parser/builder.rs @@ -453,7 +453,7 @@ fn build_layer(node: &Node, source: &SourceTree) -> Result { let mut geom = Geom::point(); // default let mut aesthetics = Mappings::new(); let mut remappings = Mappings::new(); - let mut parameters = HashMap::new(); + let mut parameters = Parameters::new(); let mut partition_by = Vec::new(); let mut filter = None; let mut order_by = None; @@ -542,11 +542,8 @@ fn build_place_layer(node: &Node, source: &SourceTree) -> Result { } /// Parse a setting_clause: SETTING param => value, ... -fn parse_setting_clause( - node: &Node, - source: &SourceTree, -) -> Result> { - let mut parameters = HashMap::new(); +fn parse_setting_clause(node: &Node, source: &SourceTree) -> Result { + let mut parameters = Parameters::new(); // Find all parameter_assignment nodes let query = "(parameter_assignment) @param"; @@ -671,7 +668,7 @@ fn build_scale(node: &Node, source: &SourceTree) -> Result { let mut output_range: Option = None; let mut transform: Option = None; let mut explicit_transform = false; - let mut properties = HashMap::new(); + let mut properties = Parameters::new(); let mut label_mapping: Option>> = None; let mut label_template = "{}".to_string(); @@ -904,7 +901,7 @@ fn parse_scale_renaming_clause( fn build_facet(node: &Node, source: &SourceTree) -> Result { let mut row_vars = Vec::new(); let mut column_vars = Vec::new(); - let mut properties = HashMap::new(); + let mut properties = Parameters::new(); let mut cursor = node.walk(); let mut next_vars_are_cols = false; @@ -971,7 +968,7 @@ fn parse_facet_vars(node: &Node, source: &SourceTree) -> Result> { /// Aesthetics are optional and default to the coord's standard names. fn build_project(node: &Node, source: &SourceTree) -> Result { let mut coord_type_name: Option = None; - let mut properties = HashMap::new(); + let mut properties = Parameters::new(); let mut user_aesthetics: Option> = None; let mut cursor = node.walk(); @@ -1032,7 +1029,7 @@ fn build_project(node: &Node, source: &SourceTree) -> Result { coord, aesthetics, properties, - computed: HashMap::new(), + computed: Parameters::new(), }) } @@ -1096,7 +1093,7 @@ fn parse_single_project_property( fn validate_project_properties( coord: &Coord, coord_type_name: Option<&str>, - properties: &HashMap, + properties: &Parameters, ) -> Result<()> { coord .resolve_properties(coord_type_name, properties) @@ -1104,10 +1101,7 @@ fn validate_project_properties( Ok(()) } -fn parse_coord_system( - name: Option<&str>, - properties: &HashMap, -) -> Result { +fn parse_coord_system(name: Option<&str>, properties: &Parameters) -> Result { use crate::plot::projection::coord::map_projections::NAMED_PROJECTIONS; match name { Some("cartesian") | None => Ok(Coord::cartesian()), diff --git a/src/plot/facet/types.rs b/src/plot/facet/types.rs index cb25bbc22..1fd70bdc1 100644 --- a/src/plot/facet/types.rs +++ b/src/plot/facet/types.rs @@ -2,10 +2,9 @@ //! //! This module defines faceting configuration for small multiples. -use crate::plot::types::{DefaultParamValue, ParamConstraint, ParamDefinition}; +use crate::plot::types::{DefaultParamValue, ParamConstraint, ParamDefinition, Parameters}; use crate::plot::ParameterValue; use serde::{Deserialize, Serialize}; -use std::collections::HashMap; /// Faceting specification (from FACET clause) #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -15,7 +14,7 @@ pub struct Facet { /// Properties from SETTING clause (e.g., scales, ncol, missing) /// After resolution, includes validated and defaulted values #[serde(default)] - pub properties: HashMap, + pub properties: Parameters, /// Whether properties have been resolved (validated and defaults applied) #[serde(skip, default)] pub resolved: bool, @@ -41,7 +40,7 @@ impl Facet { pub fn new(layout: FacetLayout) -> Self { Self { layout, - properties: HashMap::new(), + properties: Parameters::new(), resolved: false, } } diff --git a/src/plot/layer/geom/area.rs b/src/plot/layer/geom/area.rs index 266add133..dde81778a 100644 --- a/src/plot/layer/geom/area.rs +++ b/src/plot/layer/geom/area.rs @@ -1,7 +1,7 @@ //! Area geom implementation use crate::plot::layer::orientation::{ALIGNED, ORIENTATION_VALUES}; -use crate::plot::types::DefaultAestheticValue; +use crate::plot::types::{DefaultAestheticValue, Parameters}; use crate::plot::{DefaultParamValue, ParamDefinition}; use crate::Mappings; @@ -66,7 +66,7 @@ impl GeomTrait for Area { schema: &crate::plot::Schema, aesthetics: &Mappings, group_by: &[String], - parameters: &std::collections::HashMap, + parameters: &Parameters, _execute_query: &dyn Fn(&str) -> crate::Result, dialect: &dyn crate::reader::SqlDialect, aesthetic_ctx: &crate::plot::aesthetic::AestheticContext, diff --git a/src/plot/layer/geom/bar.rs b/src/plot/layer/geom/bar.rs index b1441cbbd..cf25d5780 100644 --- a/src/plot/layer/geom/bar.rs +++ b/src/plot/layer/geom/bar.rs @@ -1,6 +1,5 @@ //! Bar geom implementation -use std::collections::HashMap; use std::collections::HashSet; use super::stat_aggregate; @@ -10,7 +9,7 @@ use super::{ ParamConstraint, ParamDefinition, StatResult, }; use crate::naming; -use crate::plot::types::{DefaultAestheticValue, ParameterValue}; +use crate::plot::types::{DefaultAestheticValue, Parameters}; use crate::reader::SqlDialect; use crate::{DataFrame, GgsqlError, Mappings, Result}; @@ -90,7 +89,7 @@ impl GeomTrait for Bar { schema: &Schema, aesthetics: &Mappings, group_by: &[String], - parameters: &HashMap, + parameters: &Parameters, _execute_query: &dyn Fn(&str) -> Result, dialect: &dyn SqlDialect, aesthetic_ctx: &crate::plot::aesthetic::AestheticContext, diff --git a/src/plot/layer/geom/boxplot.rs b/src/plot/layer/geom/boxplot.rs index 11d866b5e..0c5bcd450 100644 --- a/src/plot/layer/geom/boxplot.rs +++ b/src/plot/layer/geom/boxplot.rs @@ -1,14 +1,12 @@ //! Boxplot geom implementation -use std::collections::HashMap; - use super::types::{wrap_with_dummy_axis, POSITION_VALUES, SIDE_VALUES}; use super::{DefaultAesthetics, GeomTrait, GeomType}; use crate::{ naming, plot::{ geom::types::get_column_name, DefaultAestheticValue, DefaultParamValue, ParamConstraint, - ParamDefinition, ParameterValue, StatResult, + ParamDefinition, ParameterValue, Parameters, StatResult, }, reader::SqlDialect, DataFrame, GgsqlError, Mappings, Result, @@ -101,7 +99,7 @@ impl GeomTrait for Boxplot { _schema: &crate::plot::Schema, aesthetics: &Mappings, group_by: &[String], - parameters: &HashMap, + parameters: &Parameters, _execute_query: &dyn Fn(&str) -> Result, dialect: &dyn SqlDialect, aesthetic_ctx: &crate::plot::aesthetic::AestheticContext, @@ -127,7 +125,7 @@ fn stat_boxplot( query: &str, aesthetics: &Mappings, group_by: &[String], - parameters: &HashMap, + parameters: &Parameters, dialect: &dyn SqlDialect, aesthetic_ctx: &crate::plot::aesthetic::AestheticContext, ) -> Result { @@ -340,6 +338,7 @@ fn boxplot_sql_append_outliers( #[cfg(test)] mod tests { use super::*; + use crate::plot::Parameters; use crate::reader::AnsiDialect; // ==================== SQL Generation Tests (Compact) ==================== @@ -639,7 +638,7 @@ mod tests { "pos2".to_string(), AestheticValue::standard_column("value".to_string()), ); - let mut parameters: HashMap = HashMap::new(); + let mut parameters = Parameters::new(); parameters.insert("coef".to_string(), ParameterValue::Number(1.5)); parameters.insert("outliers".to_string(), ParameterValue::Boolean(true)); diff --git a/src/plot/layer/geom/density.rs b/src/plot/layer/geom/density.rs index d8f094f02..c81139b82 100644 --- a/src/plot/layer/geom/density.rs +++ b/src/plot/layer/geom/density.rs @@ -6,13 +6,11 @@ use crate::{ naming, plot::{ geom::types::get_column_name, DefaultAestheticValue, DefaultParamValue, ParamConstraint, - ParamDefinition, ParameterValue, StatResult, + ParamDefinition, ParameterValue, Parameters, StatResult, }, reader::SqlDialect, GgsqlError, Mappings, Result, }; -use std::collections::HashMap; - /// Gaussian kernel normalization constant: 1/sqrt(2*pi) /// Precomputed at compile time to avoid repeated SQRT and PI() calls in SQL const GAUSSIAN_NORM: f64 = 0.3989422804014327; // 1.0 / (2.0 * std::f64::consts::PI).sqrt() @@ -104,7 +102,7 @@ impl GeomTrait for Density { _schema: &crate::plot::Schema, aesthetics: &Mappings, group_by: &[String], - parameters: &std::collections::HashMap, + parameters: &Parameters, _execute_query: &dyn Fn(&str) -> crate::Result, dialect: &dyn SqlDialect, aesthetic_ctx: &crate::plot::aesthetic::AestheticContext, @@ -154,7 +152,7 @@ pub(crate) fn stat_density( value_aesthetic: &str, smooth_aesthetic: Option<&str>, group_by: &[String], - parameters: &HashMap, + parameters: &Parameters, dialect: &dyn SqlDialect, aesthetic_ctx: &crate::plot::aesthetic::AestheticContext, ) -> Result { @@ -214,7 +212,7 @@ fn density_sql_bandwidth( from: &str, groups: &[String], value: &str, - parameters: &HashMap, + parameters: &Parameters, dialect: &dyn SqlDialect, ) -> String { let adjust = match parameters.get("adjust") { @@ -279,10 +277,7 @@ fn silverman_rule( format!("{adjust} * {min_expr} * POW(COUNT(*), -0.2)") } -fn choose_kde_kernel( - parameters: &HashMap, - smooth: Option, -) -> Result { +fn choose_kde_kernel(parameters: &Parameters, smooth: Option) -> Result { let kernel = match parameters.get("kernel") { Some(ParameterValue::String(krnl)) => krnl.as_str(), _ => { @@ -609,6 +604,7 @@ fn compute_density( #[cfg(test)] mod tests { use super::*; + use crate::plot::Parameters; use crate::reader::duckdb::DuckDBReader; use crate::reader::AnsiDialect; use crate::reader::Reader; @@ -618,7 +614,7 @@ mod tests { fn test_density_sql_no_groups() { let query = "SELECT x FROM (VALUES (1.0), (2.0), (3.0)) AS t(x)"; let groups: Vec = vec![]; - let mut parameters = HashMap::new(); + let mut parameters = Parameters::new(); parameters.insert("bandwidth".to_string(), ParameterValue::Number(0.5)); parameters.insert( "kernel".to_string(), @@ -694,7 +690,7 @@ mod tests { fn test_density_sql_with_two_groups() { let query = "SELECT x, region, category FROM (VALUES (1.0, 'A', 'X'), (2.0, 'B', 'Y')) AS t(x, region, category)"; let groups = vec!["region".to_string(), "category".to_string()]; - let mut parameters = HashMap::new(); + let mut parameters = Parameters::new(); parameters.insert("bandwidth".to_string(), ParameterValue::Number(0.5)); parameters.insert( "kernel".to_string(), @@ -816,7 +812,7 @@ mod tests { // Test 1: No groups let query = "SELECT x FROM (VALUES (1.0), (2.0), (3.0), (4.0), (5.0)) AS t(x)"; let groups: Vec = vec![]; - let parameters = HashMap::new(); // No explicit bandwidth - will compute + let parameters = Parameters::new(); // No explicit bandwidth - will compute let bw_cte = density_sql_bandwidth(query, &groups, "x", ¶meters, &AnsiDialect); @@ -872,7 +868,7 @@ mod tests { fn test_kernel_integration(kernel_name: &str, tolerance: f64) { let query = "SELECT x FROM (VALUES (1.0), (2.0), (3.0), (4.0), (5.0)) AS t(x)"; let groups: Vec = vec![]; - let mut parameters = HashMap::new(); + let mut parameters = Parameters::new(); parameters.insert("bandwidth".to_string(), ParameterValue::Number(1.0)); parameters.insert( "kernel".to_string(), @@ -968,7 +964,7 @@ mod tests { #[test] fn test_kernel_invalid() { - let mut parameters = HashMap::new(); + let mut parameters = Parameters::new(); parameters.insert( "kernel".to_string(), ParameterValue::String("invalid_kernel".to_string()), @@ -991,7 +987,7 @@ mod tests { // Compare weighted and unweighted results let query = "SELECT x FROM (VALUES (1.0), (2.0), (3.0)) AS t(x)"; let groups: Vec = vec![]; - let mut parameters = HashMap::new(); + let mut parameters = Parameters::new(); parameters.insert("bandwidth".to_string(), ParameterValue::Number(0.5)); parameters.insert( "kernel".to_string(), @@ -1148,7 +1144,7 @@ mod tests { let query = "SELECT x FROM bench_data"; let groups: Vec = vec![]; - let mut parameters = HashMap::new(); + let mut parameters = Parameters::new(); parameters.insert("bandwidth".to_string(), ParameterValue::Number(5.0)); parameters.insert( "kernel".to_string(), @@ -1197,7 +1193,7 @@ mod tests { #[test] fn stat_density_missing_value_aesthetic_emits_user_facing_name() { let mappings = crate::Mappings::new(); - let parameters = std::collections::HashMap::new(); + let parameters = Parameters::new(); let dialect = AnsiDialect; let ctx = crate::plot::aesthetic::AestheticContext::from_static(&["x", "y"], &[]); diff --git a/src/plot/layer/geom/histogram.rs b/src/plot/layer/geom/histogram.rs index 6c55c0852..83ff4a473 100644 --- a/src/plot/layer/geom/histogram.rs +++ b/src/plot/layer/geom/histogram.rs @@ -1,14 +1,12 @@ //! Histogram geom implementation -use std::collections::HashMap; - use super::types::{get_quoted_column_name, CLOSED_VALUES, POSITION_VALUES}; use super::{ DefaultAesthetics, DefaultParamValue, GeomTrait, GeomType, ParamConstraint, ParamDefinition, StatResult, }; use crate::naming; -use crate::plot::types::{DefaultAestheticValue, ParameterValue}; +use crate::plot::types::{DefaultAestheticValue, ParameterValue, Parameters}; use crate::reader::SqlDialect; use crate::{DataFrame, GgsqlError, Mappings, Result}; @@ -90,7 +88,7 @@ impl GeomTrait for Histogram { _schema: &Schema, aesthetics: &Mappings, group_by: &[String], - parameters: &HashMap, + parameters: &Parameters, execute_query: &dyn Fn(&str) -> Result, dialect: &dyn SqlDialect, aesthetic_ctx: &crate::plot::aesthetic::AestheticContext, @@ -118,7 +116,7 @@ fn stat_histogram( query: &str, aesthetics: &Mappings, group_by: &[String], - parameters: &HashMap, + parameters: &Parameters, execute_query: &dyn Fn(&str) -> Result, dialect: &dyn SqlDialect, aesthetic_ctx: &crate::plot::aesthetic::AestheticContext, diff --git a/src/plot/layer/geom/line.rs b/src/plot/layer/geom/line.rs index b40b975f5..26282f6ce 100644 --- a/src/plot/layer/geom/line.rs +++ b/src/plot/layer/geom/line.rs @@ -7,7 +7,7 @@ use super::{ ParamConstraint, ParamDefinition, StatResult, }; use crate::plot::layer::orientation::{ALIGNED, ORIENTATION_VALUES}; -use crate::plot::types::DefaultAestheticValue; +use crate::plot::types::{DefaultAestheticValue, Parameters}; use crate::Mappings; /// Line geom - line charts with connected points @@ -54,7 +54,7 @@ impl GeomTrait for Line { schema: &crate::plot::Schema, aesthetics: &Mappings, group_by: &[String], - parameters: &std::collections::HashMap, + parameters: &Parameters, _execute_query: &dyn Fn(&str) -> crate::Result, dialect: &dyn crate::reader::SqlDialect, aesthetic_ctx: &crate::plot::aesthetic::AestheticContext, diff --git a/src/plot/layer/geom/mod.rs b/src/plot/layer/geom/mod.rs index 7b510a1d4..6d9d960f4 100644 --- a/src/plot/layer/geom/mod.rs +++ b/src/plot/layer/geom/mod.rs @@ -23,7 +23,6 @@ use crate::plot::types::DefaultAestheticValue; use crate::{naming, DataFrame, Mappings, Result}; use serde::{Deserialize, Serialize}; -use std::collections::HashMap; use std::sync::Arc; pub mod types; @@ -78,7 +77,7 @@ pub use violin::Violin; use crate::plot::aesthetic::AestheticContext; use crate::plot::projection::Projection; -use crate::plot::types::{ParameterValue, Schema}; +use crate::plot::types::{ParameterValue, Parameters, Schema}; use crate::reader::SqlDialect; /// Enum of all geom types for pattern matching and serialization @@ -242,7 +241,7 @@ pub trait GeomTrait: std::fmt::Debug + std::fmt::Display + Send + Sync { schema: &Schema, aesthetics: &Mappings, group_by: &[String], - parameters: &HashMap, + parameters: &Parameters, _execute_query: &dyn Fn(&str) -> Result, dialect: &dyn SqlDialect, aesthetic_ctx: &AestheticContext, @@ -283,11 +282,7 @@ pub trait GeomTrait: std::fmt::Debug + std::fmt::Display + Send + Sync { /// /// Used by violin to scale the offset column to [0, 0.5 * width] using global /// max normalization before Vega-Lite rendering. - fn post_process( - &self, - df: DataFrame, - _parameters: &HashMap, - ) -> Result { + fn post_process(&self, df: DataFrame, _parameters: &Parameters) -> Result { Ok(df) } @@ -320,11 +315,7 @@ pub trait GeomTrait: std::fmt::Debug + std::fmt::Display + Send + Sync { /// This is called after parameters are validated, which allows for internal /// parameters. /// The default implementation does nothing. - fn setup_layer( - &self, - _mappings: &mut Mappings, - _parameters: &mut HashMap, - ) -> Result<()> { + fn setup_layer(&self, _mappings: &mut Mappings, _parameters: &mut Parameters) -> Result<()> { Ok(()) } @@ -406,7 +397,7 @@ pub(crate) fn project_position_columns( } /// True when `parameters["aggregate"]` is set to a non-null string or array. -pub(crate) fn has_aggregate_param(parameters: &HashMap) -> bool { +pub(crate) fn has_aggregate_param(parameters: &Parameters) -> bool { matches!( parameters.get("aggregate"), Some(ParameterValue::String(_)) | Some(ParameterValue::Array(_)) @@ -611,7 +602,7 @@ impl Geom { schema: &Schema, aesthetics: &Mappings, group_by: &[String], - parameters: &HashMap, + parameters: &Parameters, execute_query: &dyn Fn(&str) -> Result, dialect: &dyn SqlDialect, aesthetic_ctx: &AestheticContext, @@ -629,11 +620,7 @@ impl Geom { } /// Post-process DataFrame after stat query execution - pub fn post_process( - &self, - df: DataFrame, - parameters: &HashMap, - ) -> Result { + pub fn post_process(&self, df: DataFrame, parameters: &Parameters) -> Result { self.0.post_process(df, parameters) } @@ -651,11 +638,7 @@ impl Geom { } /// Adjust layer mappings and parameters based on geom-specific logic - pub fn setup_layer( - &self, - mappings: &mut Mappings, - parameters: &mut HashMap, - ) -> Result<()> { + pub fn setup_layer(&self, mappings: &mut Mappings, parameters: &mut Parameters) -> Result<()> { self.0.setup_layer(mappings, parameters) } diff --git a/src/plot/layer/geom/ribbon.rs b/src/plot/layer/geom/ribbon.rs index 47f9bc26d..838a7dfc8 100644 --- a/src/plot/layer/geom/ribbon.rs +++ b/src/plot/layer/geom/ribbon.rs @@ -3,7 +3,7 @@ use super::stat_aggregate; use super::types::{wrap_with_order_by, POSITION_VALUES}; use super::{has_aggregate_param, DefaultAesthetics, GeomTrait, GeomType, StatResult}; -use crate::plot::types::DefaultAestheticValue; +use crate::plot::types::{DefaultAestheticValue, Parameters}; use crate::plot::{DefaultParamValue, ParamConstraint, ParamDefinition}; use crate::Mappings; @@ -53,7 +53,7 @@ impl GeomTrait for Ribbon { schema: &crate::plot::Schema, aesthetics: &Mappings, group_by: &[String], - parameters: &std::collections::HashMap, + parameters: &Parameters, _execute_query: &dyn Fn(&str) -> crate::Result, dialect: &dyn crate::reader::SqlDialect, aesthetic_ctx: &crate::plot::aesthetic::AestheticContext, diff --git a/src/plot/layer/geom/rule.rs b/src/plot/layer/geom/rule.rs index 90f7c4e9e..2069f5dd1 100644 --- a/src/plot/layer/geom/rule.rs +++ b/src/plot/layer/geom/rule.rs @@ -1,7 +1,7 @@ //! Rule geom implementation use super::{DefaultAesthetics, GeomTrait, GeomType, ParamDefinition}; -use crate::plot::types::DefaultAestheticValue; +use crate::plot::types::{DefaultAestheticValue, Parameters}; /// Rule geom - horizontal and vertical reference lines #[derive(Debug, Clone, Copy)] @@ -61,7 +61,7 @@ impl GeomTrait for Rule { fn setup_layer( &self, mappings: &mut crate::plot::layer::Mappings, - parameters: &mut std::collections::HashMap, + parameters: &mut Parameters, ) -> crate::Result<()> { use crate::plot::layer::AestheticValue; use crate::plot::ParameterValue; diff --git a/src/plot/layer/geom/smooth.rs b/src/plot/layer/geom/smooth.rs index 31a42cfb0..c8496331e 100644 --- a/src/plot/layer/geom/smooth.rs +++ b/src/plot/layer/geom/smooth.rs @@ -5,7 +5,7 @@ use super::{ DefaultAesthetics, DefaultParamValue, GeomTrait, GeomType, ParamConstraint, ParamDefinition, }; use crate::plot::geom::types::get_quoted_column_name; -use crate::plot::types::DefaultAestheticValue; +use crate::plot::types::{DefaultAestheticValue, Parameters}; use crate::plot::{ParameterValue, StatResult}; use crate::reader::SqlDialect; use crate::{naming, GgsqlError, Mappings, Result}; @@ -93,7 +93,7 @@ impl GeomTrait for Smooth { _schema: &crate::plot::Schema, aesthetics: &Mappings, group_by: &[String], - parameters: &std::collections::HashMap, + parameters: &Parameters, _execute_query: &dyn Fn(&str) -> crate::Result, dialect: &dyn SqlDialect, aesthetic_ctx: &crate::plot::aesthetic::AestheticContext, diff --git a/src/plot/layer/geom/spatial.rs b/src/plot/layer/geom/spatial.rs index f49badc5f..a0c09bfce 100644 --- a/src/plot/layer/geom/spatial.rs +++ b/src/plot/layer/geom/spatial.rs @@ -3,7 +3,7 @@ use crate::naming; use crate::plot::projection::coord::map::clip_boundary_table; use crate::plot::projection::coord::CoordKind; use crate::plot::projection::Projection; -use crate::plot::types::DefaultAestheticValue; +use crate::plot::types::{DefaultAestheticValue, Parameters}; use crate::plot::ParameterValue; use crate::reader::SqlDialect; use crate::Mappings; @@ -54,7 +54,7 @@ impl GeomTrait for Spatial { _schema: &crate::plot::Schema, _aesthetics: &Mappings, _group_by: &[String], - _parameters: &std::collections::HashMap, + _parameters: &Parameters, execute_query: &dyn Fn(&str) -> crate::Result, dialect: &dyn crate::reader::SqlDialect, _aesthetic_ctx: &crate::plot::aesthetic::AestheticContext, diff --git a/src/plot/layer/geom/stat_aggregate.rs b/src/plot/layer/geom/stat_aggregate.rs index 2db7240c0..17cf67f90 100644 --- a/src/plot/layer/geom/stat_aggregate.rs +++ b/src/plot/layer/geom/stat_aggregate.rs @@ -33,7 +33,7 @@ use regex::Regex; use super::types::StatResult; use crate::naming; use crate::plot::aesthetic::AestheticContext; -use crate::plot::types::{ArrayElement, ParameterValue, Schema}; +use crate::plot::types::{ArrayElement, ParameterValue, Parameters, Schema}; use crate::reader::SqlDialect; use crate::{GgsqlError, Mappings, Result}; @@ -617,7 +617,7 @@ pub(crate) fn resolve_aggregate_targets( /// doesn't need a schema — so post-stat callers can use it without rebuilding /// type information from a materialised DataFrame. pub fn targeted_aesthetics( - parameters: &HashMap, + parameters: &Parameters, aesthetics: &Mappings, aesthetic_ctx: &AestheticContext, ) -> HashSet { @@ -652,7 +652,7 @@ pub fn targeted_aesthetics( /// when the stat will return `Identity` and no aesthetic is touched. Parse /// errors are swallowed; the stat itself surfaces a clean diagnostic. pub fn aggregated_aesthetics( - parameters: &HashMap, + parameters: &Parameters, aesthetics: &Mappings, schema: &Schema, aesthetic_ctx: &AestheticContext, @@ -720,7 +720,7 @@ pub fn apply( schema: &Schema, aesthetics: &Mappings, group_by: &[String], - parameters: &HashMap, + parameters: &Parameters, dialect: &dyn SqlDialect, aesthetic_ctx: &AestheticContext, domain_aesthetics: &[&'static str], @@ -1041,6 +1041,7 @@ mod tests { use super::*; use crate::plot::aesthetic::AestheticContext; use crate::plot::types::{AestheticValue, ColumnInfo}; + use crate::plot::Parameters; use arrow::datatypes::DataType; /// A test dialect that mimics DuckDB: native QUANTILE_CONT plus the @@ -1116,7 +1117,7 @@ mod tests { dialect: &dyn SqlDialect, domain: &[&'static str], ) -> Result { - let mut p = HashMap::new(); + let mut p = Parameters::new(); p.insert("aggregate".to_string(), params); let ctx = cartesian_ctx(); apply( @@ -1340,7 +1341,7 @@ mod tests { fn returns_identity_when_param_unset() { let aes = Mappings::new(); let schema: Schema = vec![]; - let p: HashMap = HashMap::new(); + let p = Parameters::new(); let ctx = cartesian_ctx(); let result = apply( "SELECT * FROM t", diff --git a/src/plot/layer/geom/text.rs b/src/plot/layer/geom/text.rs index 5bc3f959f..3a7bb6512 100644 --- a/src/plot/layer/geom/text.rs +++ b/src/plot/layer/geom/text.rs @@ -3,14 +3,13 @@ use super::types::POSITION_VALUES; use super::{ project_position_columns, DefaultAesthetics, DefaultParamValue, GeomTrait, GeomType, - ParamConstraint, ParamDefinition, ParameterValue, + ParamConstraint, ParamDefinition, }; use crate::plot::projection::Projection; -use crate::plot::types::DefaultAestheticValue; +use crate::plot::types::{DefaultAestheticValue, ParameterValue, Parameters}; use crate::plot::{ArrayConstraint, NumberConstraint}; use crate::reader::SqlDialect; use crate::{naming, DataFrame, Result}; -use std::collections::HashMap; /// Text geom - text labels at positions #[derive(Debug, Clone, Copy)] @@ -81,11 +80,7 @@ impl GeomTrait for Text { project_position_columns(query, projection, dialect, columns) } - fn post_process( - &self, - df: DataFrame, - parameters: &HashMap, - ) -> Result { + fn post_process(&self, df: DataFrame, parameters: &Parameters) -> Result { // Check if format parameter is specified let format_template = match parameters.get("format") { Some(ParameterValue::String(template)) => template, diff --git a/src/plot/layer/geom/tile.rs b/src/plot/layer/geom/tile.rs index dac354f8d..e3493757b 100644 --- a/src/plot/layer/geom/tile.rs +++ b/src/plot/layer/geom/tile.rs @@ -1,7 +1,5 @@ //! Tile geom implementation with flexible parameter specification -use std::collections::HashMap; - use super::stat_aggregate; use super::types::POSITION_VALUES; use super::types::{get_column_name, get_quoted_column_name}; @@ -9,7 +7,7 @@ use super::{ has_aggregate_param, DefaultAesthetics, GeomTrait, GeomType, ParamConstraint, StatResult, }; use crate::naming; -use crate::plot::types::{ColumnInfo, DefaultAestheticValue, ParameterValue}; +use crate::plot::types::{ColumnInfo, DefaultAestheticValue, Parameters}; use crate::plot::{DefaultParamValue, ParamDefinition}; use crate::reader::SqlDialect; use crate::{DataFrame, GgsqlError, Mappings, Result}; @@ -113,7 +111,7 @@ impl GeomTrait for Tile { schema: &Schema, aesthetics: &Mappings, group_by: &[String], - parameters: &HashMap, + parameters: &Parameters, _execute_query: &dyn Fn(&str) -> Result, dialect: &dyn SqlDialect, aesthetic_ctx: &crate::plot::aesthetic::AestheticContext, @@ -245,7 +243,7 @@ impl std::fmt::Display for Tile { fn process_direction( axis: &str, aesthetics: &Mappings, - parameters: &HashMap, + parameters: &Parameters, schema: &Schema, display_name: &str, ) -> Result<(Vec, Vec)> { @@ -324,7 +322,7 @@ fn stat_tile( schema: &Schema, aesthetics: &Mappings, _group_by: &[String], - parameters: &HashMap, + parameters: &Parameters, aesthetic_ctx: &crate::plot::aesthetic::AestheticContext, ) -> Result { let display_x = aesthetic_ctx.map_internal_to_user("pos1"); @@ -472,7 +470,8 @@ fn generate_continuous_position_expressions( #[cfg(test)] mod tests { use super::*; - use crate::plot::types::{AestheticValue, ColumnInfo}; + use crate::plot::types::{AestheticValue, ColumnInfo, ParameterValue}; + use crate::plot::Parameters; use arrow::datatypes::DataType; // ==================== Helper Functions ==================== @@ -634,7 +633,7 @@ mod tests { let aesthetics = create_aesthetics(&all_mappings); let schema = create_schema(&[]); let group_by = vec![]; - let parameters = HashMap::new(); + let parameters = Parameters::new(); let ctx = crate::plot::aesthetic::AestheticContext::from_static(&["x", "y"], &[]); let result = stat_tile( @@ -746,7 +745,7 @@ mod tests { let aesthetics = create_aesthetics(&all_mappings); let schema = create_schema(&[]); let group_by = vec![]; - let parameters = HashMap::new(); + let parameters = Parameters::new(); let ctx = crate::plot::aesthetic::AestheticContext::from_static(&["x", "y"], &[]); let result = stat_tile( @@ -813,7 +812,7 @@ mod tests { let aesthetics = create_aesthetics(&["pos1", "width", "pos2min", "pos2max"]); let schema = create_schema(&["pos1"]); let group_by = vec![]; - let parameters = HashMap::new(); + let parameters = Parameters::new(); let ctx = crate::plot::aesthetic::AestheticContext::from_static(&["x", "y"], &[]); let result = stat_tile( @@ -846,7 +845,7 @@ mod tests { let aesthetics = create_aesthetics(&["pos1min", "pos1max", "pos2", "height"]); let schema = create_schema(&["pos2"]); let group_by = vec![]; - let parameters = HashMap::new(); + let parameters = Parameters::new(); let ctx = crate::plot::aesthetic::AestheticContext::from_static(&["x", "y"], &[]); let result = stat_tile( @@ -879,7 +878,7 @@ mod tests { let aesthetics = create_aesthetics(&["pos1", "width", "pos2", "height"]); let schema = create_schema(&["pos1", "pos2"]); let group_by = vec![]; - let parameters = HashMap::new(); + let parameters = Parameters::new(); let ctx = crate::plot::aesthetic::AestheticContext::from_static(&["x", "y"], &[]); let result = stat_tile( @@ -914,7 +913,7 @@ mod tests { let aesthetics = create_aesthetics(&["pos1", "pos2min", "pos2max"]); let schema = create_schema(&[]); let group_by = vec![]; - let parameters = HashMap::new(); + let parameters = Parameters::new(); let ctx = crate::plot::aesthetic::AestheticContext::from_static(&["x", "y"], &[]); let result = stat_tile( @@ -947,7 +946,7 @@ mod tests { let aesthetics = create_aesthetics(&["pos1", "pos1min", "pos1max", "pos2min", "pos2max"]); let schema = create_schema(&[]); let group_by = vec![]; - let parameters = HashMap::new(); + let parameters = Parameters::new(); let ctx = crate::plot::aesthetic::AestheticContext::from_static(&["x", "y"], &[]); let result = stat_tile( @@ -968,7 +967,7 @@ mod tests { let aesthetics = create_aesthetics(&["pos1", "pos1min", "pos2min", "pos2max"]); let schema = create_schema(&["pos1"]); let group_by = vec![]; - let parameters = HashMap::new(); + let parameters = Parameters::new(); let ctx = crate::plot::aesthetic::AestheticContext::from_static(&["x", "y"], &[]); let result = stat_tile( @@ -990,7 +989,7 @@ mod tests { let aesthetics = create_aesthetics(&["pos1", "pos2min", "pos2max"]); let schema = create_schema(&["pos1"]); let group_by = vec![]; - let parameters = HashMap::new(); + let parameters = Parameters::new(); let ctx = crate::plot::aesthetic::AestheticContext::from_static(&["x", "y"], &[]); let result = stat_tile( @@ -1024,7 +1023,7 @@ mod tests { // Include fill in schema (it's a non-consumed aesthetic) let schema = create_schema_with_extra(&["pos1", "pos2"], &["__ggsql_aes_fill__"]); let group_by = vec![]; - let parameters = HashMap::new(); + let parameters = Parameters::new(); let ctx = crate::plot::aesthetic::AestheticContext::from_static(&["x", "y"], &[]); let result = stat_tile( @@ -1069,7 +1068,7 @@ mod tests { max: None, }); let ctx = AestheticContext::from_static(&["x", "y"], &[]); - let mut parameters = HashMap::new(); + let mut parameters = Parameters::new(); parameters.insert( "aggregate".to_string(), ParameterValue::String("mean".to_string()), @@ -1140,7 +1139,7 @@ mod tests { max: None, }); let ctx = AestheticContext::from_static(&["x", "y"], &[]); - let mut parameters = HashMap::new(); + let mut parameters = Parameters::new(); parameters.insert( "aggregate".to_string(), ParameterValue::Array(vec![ @@ -1192,7 +1191,7 @@ mod tests { let aesthetics = create_aesthetics(&["pos1", "pos2"]); let schema = create_schema(&["pos1", "pos2"]); let group_by = vec![]; - let mut parameters = HashMap::new(); + let mut parameters = Parameters::new(); parameters.insert("width".to_string(), ParameterValue::Number(0.7)); parameters.insert("height".to_string(), ParameterValue::Number(0.9)); diff --git a/src/plot/layer/geom/violin.rs b/src/plot/layer/geom/violin.rs index 6b80500fb..389e460c5 100644 --- a/src/plot/layer/geom/violin.rs +++ b/src/plot/layer/geom/violin.rs @@ -6,12 +6,10 @@ use crate::{ naming, plot::{ geom::types::get_column_name, DefaultAestheticValue, DefaultParamValue, ParamConstraint, - ParamDefinition, ParameterValue, + ParamDefinition, ParameterValue, Parameters, }, DataFrame, GgsqlError, Mappings, Result, }; -use std::collections::HashMap; - /// Valid kernel types for violin density estimation const KERNEL_VALUES: &[&str] = &[ "gaussian", @@ -118,7 +116,7 @@ impl GeomTrait for Violin { _schema: &crate::plot::Schema, aesthetics: &Mappings, group_by: &[String], - parameters: &HashMap, + parameters: &Parameters, _execute_query: &dyn Fn(&str) -> crate::Result, dialect: &dyn crate::reader::SqlDialect, aesthetic_ctx: &crate::plot::aesthetic::AestheticContext, @@ -138,11 +136,7 @@ impl GeomTrait for Violin { /// Uses global max normalization so relative differences across groups are preserved: /// - Narrow distributions will have higher peaks (normalized density) /// - Groups with more data will be wider when using intensity remapping - fn post_process( - &self, - df: DataFrame, - parameters: &HashMap, - ) -> Result { + fn post_process(&self, df: DataFrame, parameters: &Parameters) -> Result { let offset_col = naming::aesthetic_column("offset"); // Get width parameter (default 0.9) @@ -207,7 +201,7 @@ fn stat_violin( query: &str, aesthetics: &Mappings, group_by: &[String], - parameters: &HashMap, + parameters: &Parameters, dialect: &dyn crate::reader::SqlDialect, aesthetic_ctx: &crate::plot::aesthetic::AestheticContext, ) -> Result { @@ -285,6 +279,7 @@ fn stat_violin( mod tests { use super::*; use crate::plot::AestheticValue; + use crate::plot::Parameters; use crate::reader::duckdb::DuckDBReader; use crate::reader::AnsiDialect; use crate::reader::Reader; @@ -334,7 +329,7 @@ mod tests { let query = "SELECT species, flipper_length FROM penguins"; let aesthetics = create_basic_aesthetics(); let groups: Vec = vec![]; - let mut parameters = HashMap::new(); + let mut parameters = Parameters::new(); parameters.insert("bandwidth".to_string(), ParameterValue::Number(5.0)); parameters.insert( "kernel".to_string(), @@ -398,7 +393,7 @@ mod tests { let query = "SELECT species, flipper_length, island FROM penguins"; let aesthetics = create_aesthetics_with_color(); let groups = vec!["island".to_string()]; // Additional grouping via color aesthetic - let mut parameters = HashMap::new(); + let mut parameters = Parameters::new(); parameters.insert("bandwidth".to_string(), ParameterValue::Number(5.0)); parameters.insert( "kernel".to_string(), @@ -518,7 +513,7 @@ mod tests { let query = "SELECT species, flipper_length FROM penguins"; let aesthetics = create_basic_aesthetics(); let groups: Vec = vec![]; - let mut parameters = HashMap::new(); + let mut parameters = Parameters::new(); parameters.insert("bandwidth".to_string(), ParameterValue::Number(5.0)); parameters.insert( "kernel".to_string(), @@ -578,7 +573,7 @@ mod tests { // With default width 0.9, half_width = 0.45 // Offset should be scaled to [0, 0.45] - let parameters = HashMap::new(); + let parameters = Parameters::new(); let result = violin.post_process(df, ¶meters).unwrap(); let scaled_arr = crate::array_util::as_f64(result.column(&offset_col).unwrap()).unwrap(); @@ -612,7 +607,7 @@ mod tests { .unwrap(); // With width 0.6, half_width = 0.3 - let mut parameters = HashMap::new(); + let mut parameters = Parameters::new(); parameters.insert("width".to_string(), ParameterValue::Number(0.6)); let result = violin.post_process(df, ¶meters).unwrap(); @@ -639,7 +634,7 @@ mod tests { AestheticValue::standard_column("flipper_length".to_string()), ); let groups: Vec = vec![]; - let mut parameters = HashMap::new(); + let mut parameters = Parameters::new(); parameters.insert("bandwidth".to_string(), ParameterValue::Number(5.0)); parameters.insert( "kernel".to_string(), @@ -690,7 +685,7 @@ mod tests { } .unwrap(); - let parameters = HashMap::new(); + let parameters = Parameters::new(); let result = violin.post_process(df.clone(), ¶meters).unwrap(); // Should return unchanged DataFrame diff --git a/src/plot/layer/mod.rs b/src/plot/layer/mod.rs index 47f1e7aaf..0c48201ab 100644 --- a/src/plot/layer/mod.rs +++ b/src/plot/layer/mod.rs @@ -4,7 +4,7 @@ //! a single visualization layer (from DRAW clause) in a ggsql specification. use serde::{Deserialize, Serialize}; -use std::collections::{HashMap, HashSet}; +use std::collections::HashSet; // Geom is a submodule of layer pub mod geom; @@ -30,7 +30,8 @@ use crate::{ plot::{ is_facet_aesthetic, parse_position, types::{ - validate_parameter, AestheticValue, DataSource, Mappings, ParameterValue, SqlExpression, + validate_parameter, AestheticValue, DataSource, Mappings, ParameterValue, Parameters, + SqlExpression, }, }, AestheticContext, @@ -68,7 +69,7 @@ pub struct Layer { /// Maps stat-computed columns (e.g., "count") to aesthetic channels (e.g., "y") pub remappings: Mappings, /// Geom parameters (not aesthetic mappings) - pub parameters: HashMap, + pub parameters: Parameters, /// Optional data source for this layer (from MAPPING ... FROM) pub source: Option, /// Optional filter expression for this layer (from FILTER clause) @@ -97,7 +98,7 @@ impl Layer { position: Position::default(), mappings: Mappings::new(), remappings: Mappings::new(), - parameters: HashMap::new(), + parameters: Parameters::new(), source: None, filter: None, order_by: None, diff --git a/src/plot/main.rs b/src/plot/main.rs index 3f78da465..3c0b1ffa6 100644 --- a/src/plot/main.rs +++ b/src/plot/main.rs @@ -303,6 +303,7 @@ impl Default for Plot { #[cfg(test)] mod tests { use super::*; + use crate::plot::Parameters; #[test] fn test_plot_creation() { @@ -773,8 +774,8 @@ mod tests { spec.project = Some(Projection { coord: Coord::cartesian(), aesthetics: vec!["y".to_string(), "x".to_string()], - properties: HashMap::new(), - computed: HashMap::new(), + properties: Parameters::new(), + computed: Parameters::new(), }); spec.labels = Some(Labels { labels: HashMap::from([ @@ -804,8 +805,8 @@ mod tests { spec.project = Some(Projection { coord: Coord::polar(), aesthetics: vec!["angle".to_string(), "radius".to_string()], - properties: HashMap::new(), - computed: HashMap::new(), + properties: Parameters::new(), + computed: Parameters::new(), }); spec.labels = Some(Labels { labels: HashMap::from([ diff --git a/src/plot/projection/coord/cartesian.rs b/src/plot/projection/coord/cartesian.rs index 479e2fc46..472d96df8 100644 --- a/src/plot/projection/coord/cartesian.rs +++ b/src/plot/projection/coord/cartesian.rs @@ -40,8 +40,7 @@ impl CoordTrait for Cartesian { #[cfg(test)] mod tests { use super::*; - use crate::plot::ParameterValue; - use std::collections::HashMap; + use crate::plot::{ParameterValue, Parameters}; #[test] fn test_cartesian_properties() { @@ -62,7 +61,7 @@ mod tests { #[test] fn test_cartesian_resolve_valid_properties() { let cartesian = Cartesian; - let props = HashMap::new(); + let props = Parameters::new(); // Empty properties should resolve successfully let resolved = cartesian.resolve_properties(None, &props); assert!(resolved.is_ok()); @@ -71,7 +70,7 @@ mod tests { #[test] fn test_cartesian_rejects_unknown_property() { let cartesian = Cartesian; - let mut props = HashMap::new(); + let mut props = Parameters::new(); props.insert( "unknown".to_string(), ParameterValue::String("value".to_string()), diff --git a/src/plot/projection/coord/map.rs b/src/plot/projection/coord/map.rs index d27b24e37..ee588c0b0 100644 --- a/src/plot/projection/coord/map.rs +++ b/src/plot/projection/coord/map.rs @@ -1,12 +1,10 @@ //! Map coordinate system — helper functions for map projection transforms. -use std::collections::HashMap; - use super::map_projections::MapProjectionTrait; use crate::naming; use crate::plot::layer::geom::GeomType; use crate::plot::scale::breaks::graticule_breaks; -use crate::plot::{DataSource, Layer, ParameterValue}; +use crate::plot::{DataSource, Layer, ParameterValue, Parameters}; use crate::reader::SqlDialect; use crate::DataFrame; @@ -792,7 +790,7 @@ pub(crate) fn resolve_map_projection( /// database engine may still handle it). Returns `None` when the property is absent. fn resolve_epsg_property( key: &str, - properties: &HashMap, + properties: &Parameters, execute_query: &dyn Fn(&str) -> crate::Result, ) -> Option { let code: u32 = match properties.get(key) { @@ -895,19 +893,18 @@ fn builtin_epsg_lookup(code: u32) -> Option { mod tests { use super::*; use crate::plot::projection::coord::{Coord, CoordKind}; - use crate::plot::ParameterValue; - use std::collections::HashMap; + use crate::plot::{ParameterValue, Parameters}; #[test] fn test_map_properties() { - let coord = Coord::map("crs", &HashMap::new()); + let coord = Coord::map("crs", &Parameters::new()); assert_eq!(coord.coord_kind(), CoordKind::Map); assert_eq!(coord.position_aesthetic_names(), &["lon", "lat"]); } #[test] fn test_map_default_properties() { - let coord = Coord::map("crs", &HashMap::new()); + let coord = Coord::map("crs", &Parameters::new()); let defaults = coord.default_properties(); let names: Vec<&str> = defaults.iter().map(|p| p.name).collect(); assert!(names.contains(&"target")); @@ -921,7 +918,7 @@ mod tests { #[test] fn test_map_accepts_target_string() { - let mut props = HashMap::new(); + let mut props = Parameters::new(); props.insert( "target".to_string(), ParameterValue::String("+proj=merc".to_string()), @@ -939,8 +936,8 @@ mod tests { #[test] fn test_map_rejects_unknown_property() { - let coord = Coord::map("crs", &HashMap::new()); - let mut props = HashMap::new(); + let coord = Coord::map("crs", &Parameters::new()); + let mut props = Parameters::new(); props.insert( "unknown".to_string(), ParameterValue::String("value".to_string()), @@ -954,7 +951,7 @@ mod tests { #[test] fn test_target_rejects_origin_and_parallel() { - let mut props = HashMap::new(); + let mut props = Parameters::new(); props.insert( "target".to_string(), ParameterValue::String("+proj=ortho".to_string()), @@ -970,7 +967,7 @@ mod tests { #[test] fn test_origin_rejects_latitude_out_of_range() { - let mut props = HashMap::new(); + let mut props = Parameters::new(); props.insert( "origin".to_string(), ParameterValue::Array(vec![ diff --git a/src/plot/projection/coord/map_projections.rs b/src/plot/projection/coord/map_projections.rs index bfe32756d..a99a85a2d 100644 --- a/src/plot/projection/coord/map_projections.rs +++ b/src/plot/projection/coord/map_projections.rs @@ -1,11 +1,10 @@ -use std::collections::HashMap; use std::fmt; use std::sync::Arc; use super::CoordKind; use crate::plot::types::{ validate_parameter, ArrayElement, DefaultParamValue, ParamConstraint, ParamDefinition, - TypeConstraint, + Parameters, TypeConstraint, }; use crate::plot::{Layer, ParameterValue}; use crate::reader::SqlDialect; @@ -180,8 +179,8 @@ impl super::CoordTrait for T { fn resolve_properties( &self, coord_type_name: Option<&str>, - properties: &HashMap, - ) -> Result, String> { + properties: &Parameters, + ) -> Result { if let Some(name) = coord_type_name { if name != "crs" && properties.contains_key("target") { return Err(format!( @@ -373,7 +372,7 @@ macro_rules! build_projection { /// Returns `None` if the name is not recognised. pub fn build_map_projection( name: Option<&str>, - properties: &HashMap, + properties: &Parameters, ) -> Option> { let name = name?; let obj: Arc = build_projection!(name, properties); @@ -1316,10 +1315,11 @@ fn antimeridian_crossing_lat(a: (f64, f64), b: (f64, f64)) -> f64 { #[cfg(test)] mod tests { use super::*; + use crate::plot::Parameters; fn build_map_projection_trait( name: Option<&str>, - properties: &HashMap, + properties: &Parameters, ) -> Option> { let name = name?; let obj: Arc = build_projection!(name, properties); @@ -1327,7 +1327,7 @@ mod tests { } fn build_from_proj_str(crs: &str) -> Arc { - let mut properties = HashMap::new(); + let mut properties = Parameters::new(); properties.insert( "target".to_string(), ParameterValue::String(crs.to_string()), @@ -1355,7 +1355,7 @@ mod tests { #[test] fn new_from_target_albers() { - let mut props = HashMap::new(); + let mut props = Parameters::new(); props.insert( "target".to_string(), ParameterValue::String("+proj=aea +lon_0=5 +lat_0=10 +lat_1=30 +lat_2=50".to_string()), @@ -1371,7 +1371,7 @@ mod tests { #[test] fn new_named_albers_with_settings() { - let mut props = HashMap::new(); + let mut props = Parameters::new(); props.insert( "origin".to_string(), ParameterValue::Array(vec![ @@ -1552,7 +1552,7 @@ mod tests { #[test] fn utm_from_named_origin() { - let mut props = HashMap::new(); + let mut props = Parameters::new(); props.insert( "origin".to_string(), ParameterValue::Array(vec![ArrayElement::Number(5.0), ArrayElement::Number(52.0)]), @@ -1564,7 +1564,7 @@ mod tests { #[test] fn utm_southern_hemisphere() { - let mut props = HashMap::new(); + let mut props = Parameters::new(); props.insert( "origin".to_string(), ParameterValue::Array(vec![ diff --git a/src/plot/projection/coord/mod.rs b/src/plot/projection/coord/mod.rs index 66d80cff9..09bf70f7c 100644 --- a/src/plot/projection/coord/mod.rs +++ b/src/plot/projection/coord/mod.rs @@ -21,11 +21,10 @@ //! ``` use serde::{Deserialize, Serialize}; -use std::collections::HashMap; use std::sync::Arc; -use crate::plot::types::{validate_parameter, ParamDefinition}; -use crate::plot::{Layer, ParameterValue}; +use crate::plot::types::{validate_parameter, ParamDefinition, Parameters}; +use crate::plot::Layer; use crate::reader::SqlDialect; use crate::DataFrame; @@ -93,8 +92,8 @@ pub trait CoordTrait: std::fmt::Debug + Send + Sync { fn resolve_properties( &self, _coord_type_name: Option<&str>, - properties: &HashMap, - ) -> Result, String> { + properties: &Parameters, + ) -> Result { let defaults = self.default_properties(); // Validate values against constraints @@ -192,7 +191,7 @@ impl Coord { } /// Create a Map coord type from a projection name and properties. - pub fn map(name: &str, properties: &HashMap) -> Self { + pub fn map(name: &str, properties: &Parameters) -> Self { Self( map_projections::build_map_projection(Some(name), properties) .expect("map coord name must be a known projection or 'map'"), @@ -204,7 +203,7 @@ impl Coord { match kind { CoordKind::Cartesian => Self::cartesian(), CoordKind::Polar => Self::polar(), - CoordKind::Map => Self::map("crs", &HashMap::new()), + CoordKind::Map => Self::map("crs", &Parameters::new()), } } @@ -233,8 +232,8 @@ impl Coord { pub fn resolve_properties( &self, coord_type_name: Option<&str>, - properties: &HashMap, - ) -> Result, String> { + properties: &Parameters, + ) -> Result { self.0.resolve_properties(coord_type_name, properties) } diff --git a/src/plot/projection/coord/polar.rs b/src/plot/projection/coord/polar.rs index b4261abfa..16b0ee54f 100644 --- a/src/plot/projection/coord/polar.rs +++ b/src/plot/projection/coord/polar.rs @@ -55,8 +55,7 @@ impl CoordTrait for Polar { #[cfg(test)] mod tests { use super::*; - use crate::plot::ParameterValue; - use std::collections::HashMap; + use crate::plot::{ParameterValue, Parameters}; #[test] fn test_polar_properties() { @@ -92,7 +91,7 @@ mod tests { #[test] fn test_polar_rejects_unknown_property() { let polar = Polar; - let mut props = HashMap::new(); + let mut props = Parameters::new(); props.insert( "unknown".to_string(), ParameterValue::String("value".to_string()), @@ -107,7 +106,7 @@ mod tests { #[test] fn test_polar_resolve_with_explicit_start() { let polar = Polar; - let mut props = HashMap::new(); + let mut props = Parameters::new(); props.insert("start".to_string(), ParameterValue::Number(90.0)); let resolved = polar.resolve_properties(None, &props); @@ -122,7 +121,7 @@ mod tests { #[test] fn test_polar_resolve_adds_start_default() { let polar = Polar; - let props = HashMap::new(); + let props = Parameters::new(); let resolved = polar.resolve_properties(None, &props); assert!(resolved.is_ok()); @@ -134,7 +133,7 @@ mod tests { #[test] fn test_polar_resolve_with_explicit_end() { let polar = Polar; - let mut props = HashMap::new(); + let mut props = Parameters::new(); props.insert("end".to_string(), ParameterValue::Number(180.0)); let resolved = polar.resolve_properties(None, &props); @@ -148,7 +147,7 @@ mod tests { #[test] fn test_polar_resolve_with_start_and_end() { let polar = Polar; - let mut props = HashMap::new(); + let mut props = Parameters::new(); props.insert("start".to_string(), ParameterValue::Number(-90.0)); props.insert("end".to_string(), ParameterValue::Number(90.0)); @@ -165,7 +164,7 @@ mod tests { #[test] fn test_polar_resolve_with_inner() { let polar = Polar; - let mut props = HashMap::new(); + let mut props = Parameters::new(); props.insert("inner".to_string(), ParameterValue::Number(0.5)); let resolved = polar.resolve_properties(None, &props); diff --git a/src/plot/projection/resolve.rs b/src/plot/projection/resolve.rs index 48f6e39a6..edc03b397 100644 --- a/src/plot/projection/resolve.rs +++ b/src/plot/projection/resolve.rs @@ -3,13 +3,12 @@ //! Resolves the default coordinate system by inspecting aesthetic mappings, //! and post-scale resolution of projection properties like `radar`. -use std::collections::HashMap; - use super::coord::{Coord, CoordKind}; use super::Projection; use crate::plot::aesthetic::{MATERIAL_AESTHETICS, POSITION_SUFFIXES}; use crate::plot::layer::GeomType; use crate::plot::scale::ScaleTypeKind; +use crate::plot::Parameters; use crate::plot::{Mappings, ParameterValue, Scale}; use crate::GgsqlError; @@ -106,8 +105,8 @@ pub fn resolve_coord( return Ok(Some(Projection { coord, aesthetics, - properties: HashMap::new(), - computed: HashMap::new(), + properties: Parameters::new(), + computed: Parameters::new(), })); } @@ -122,8 +121,8 @@ pub fn resolve_coord( return Ok(Some(Projection { coord, aesthetics, - properties: HashMap::new(), - computed: HashMap::new(), + properties: Parameters::new(), + computed: Parameters::new(), })); } @@ -138,8 +137,8 @@ pub fn resolve_coord( return Ok(Some(Projection { coord, aesthetics, - properties: HashMap::new(), - computed: HashMap::new(), + properties: Parameters::new(), + computed: Parameters::new(), })); } diff --git a/src/plot/projection/types.rs b/src/plot/projection/types.rs index b8c4fb49a..76d3b89a8 100644 --- a/src/plot/projection/types.rs +++ b/src/plot/projection/types.rs @@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize}; use std::collections::HashMap; use super::coord::Coord; -use crate::plot::{Layer, ParameterValue}; +use crate::plot::{Layer, Parameters}; use crate::reader::SqlDialect; use crate::DataFrame; @@ -21,11 +21,11 @@ pub struct Projection { /// or custom names like ["a", "b"] if user specifies them. pub aesthetics: Vec, /// Projection-specific options - pub properties: HashMap, + pub properties: Parameters, /// Values computed at execution time (e.g., clip boundary WKT). /// Not user-facing; populated by apply_projection_transforms. #[serde(default, skip_serializing_if = "HashMap::is_empty")] - pub computed: HashMap, + pub computed: Parameters, } impl Projection { @@ -41,7 +41,7 @@ impl Projection { /// Create a default Map projection (lon, lat). pub fn map() -> Self { - Self::with_defaults(Coord::map("crs", &HashMap::new())) + Self::with_defaults(Coord::map("crs", &Parameters::new())) } fn with_defaults(coord: Coord) -> Self { @@ -53,8 +53,8 @@ impl Projection { Self { coord, aesthetics, - properties: HashMap::new(), - computed: HashMap::new(), + properties: Parameters::new(), + computed: Parameters::new(), } } diff --git a/src/plot/scale/scale_type/mod.rs b/src/plot/scale/scale_type/mod.rs index 32e10cb7c..4b2c7df83 100644 --- a/src/plot/scale/scale_type/mod.rs +++ b/src/plot/scale/scale_type/mod.rs @@ -23,12 +23,11 @@ use arrow::array::{Array, ArrayRef}; use arrow::datatypes::DataType; use serde::{Deserialize, Serialize}; -use std::collections::HashMap; use std::sync::Arc; use super::transform::{Transform, TransformKind}; use crate::plot::aesthetic::{is_facet_aesthetic, is_position_aesthetic}; -use crate::plot::types::{validate_parameter, DefaultParamValue, ParamDefinition}; +use crate::plot::types::{validate_parameter, DefaultParamValue, ParamDefinition, Parameters}; use crate::plot::{ArrayElement, ColumnInfo, ParameterValue}; // Scale type implementations @@ -628,8 +627,8 @@ pub trait ScaleTypeTrait: std::fmt::Debug + std::fmt::Display + Send + Sync { fn resolve_properties( &self, aesthetic: &str, - properties: &HashMap, - ) -> Result, String> { + properties: &Parameters, + ) -> Result { let defaults = self.default_properties(); let is_position = is_position_aesthetic(aesthetic); @@ -726,7 +725,7 @@ pub trait ScaleTypeTrait: std::fmt::Debug + std::fmt::Display + Send + Sync { fn resolve_breaks( &self, input_range: Option<&[ArrayElement]>, - properties: &HashMap, + properties: &Parameters, transform: Option<&Transform>, ) -> Option> { // Only applicable to continuous-like scales @@ -1261,8 +1260,8 @@ impl ScaleType { pub fn resolve_properties( &self, aesthetic: &str, - properties: &HashMap, - ) -> Result, String> { + properties: &Parameters, + ) -> Result { self.0.resolve_properties(aesthetic, properties) } @@ -1305,7 +1304,7 @@ impl ScaleType { pub fn resolve_breaks( &self, input_range: Option<&[ArrayElement]>, - properties: &HashMap, + properties: &Parameters, transform: Option<&Transform>, ) -> Option> { self.0.resolve_breaks(input_range, properties, transform) @@ -1722,7 +1721,7 @@ pub(crate) fn expand_numeric_range_selective( /// Get expand factors from properties, using defaults for continuous/temporal scales. #[allow(dead_code)] -pub(crate) fn get_expand_factors(properties: &HashMap) -> (f64, f64) { +pub(crate) fn get_expand_factors(properties: &Parameters) -> (f64, f64) { properties .get("expand") .and_then(parse_expand_value) @@ -1734,7 +1733,7 @@ pub(crate) fn get_expand_factors(properties: &HashMap) - /// Uses `context.default_expand` if set (e.g., for polar full-circle theta). /// Users can still explicitly set expand via SETTING if they want expansion. pub(crate) fn get_expand_factors_for_aesthetic( - properties: &HashMap, + properties: &Parameters, _aesthetic: &str, context: &ScaleDataContext, user_explicit_expand: bool, @@ -2410,14 +2409,14 @@ mod tests { #[test] fn test_resolve_properties_rejection_cases() { // Unknown property rejected - let mut props = HashMap::new(); + let mut props = Parameters::new(); props.insert("unknown".to_string(), ParameterValue::Number(1.0)); let result = ScaleType::continuous().resolve_properties("x", &props); assert!(result.is_err()); assert!(result.unwrap_err().contains("unknown")); // Discrete rejects expand - let mut expand_props = HashMap::new(); + let mut expand_props = Parameters::new(); expand_props.insert("expand".to_string(), ParameterValue::Number(0.1)); let result = ScaleType::discrete().resolve_properties("color", &expand_props); assert!(result.is_err()); @@ -2428,7 +2427,7 @@ mod tests { assert!(result.is_err()); // Binned rejects oob='keep' - let mut keep_props = HashMap::new(); + let mut keep_props = Parameters::new(); keep_props.insert( "oob".to_string(), ParameterValue::String("keep".to_string()), @@ -2446,7 +2445,7 @@ mod tests { #[test] fn test_resolve_properties_defaults() { // Continuous position: default expand - let props = HashMap::new(); + let props = Parameters::new(); let resolved = ScaleType::continuous() .resolve_properties("pos1", &props) .unwrap(); @@ -2482,7 +2481,7 @@ mod tests { #[test] fn test_resolve_properties_user_values_preserved() { - let mut props = HashMap::new(); + let mut props = Parameters::new(); props.insert("expand".to_string(), ParameterValue::Number(0.1)); let resolved = ScaleType::continuous() .resolve_properties("pos1", &props) @@ -2503,7 +2502,7 @@ mod tests { } // Binned allows squish oob - let mut oob_props = HashMap::new(); + let mut oob_props = Parameters::new(); oob_props.insert( "oob".to_string(), ParameterValue::String("squish".to_string()), @@ -2520,7 +2519,7 @@ mod tests { "pos1", "pos1min", "pos1max", "pos1end", "pos2", "pos2min", "pos2max", "pos2end", ]; - let mut props = HashMap::new(); + let mut props = Parameters::new(); props.insert("expand".to_string(), ParameterValue::Number(0.1)); // Position aesthetics should allow expand @@ -2552,7 +2551,7 @@ mod tests { "pos1", "pos1min", "pos1max", "pos1end", "pos2", "pos2min", "pos2max", "pos2end", ]; - let props = HashMap::new(); + let props = Parameters::new(); // Position aesthetics default to 'keep' for aesthetic in internal_position.iter() { @@ -2585,7 +2584,7 @@ mod tests { fn test_oob_valid_and_invalid_values() { // Valid values accepted for oob_value in &["censor", "squish", "keep"] { - let mut props = HashMap::new(); + let mut props = Parameters::new(); props.insert( "oob".to_string(), ParameterValue::String(oob_value.to_string()), @@ -2600,7 +2599,7 @@ mod tests { } // Invalid value rejected with helpful message - let mut props = HashMap::new(); + let mut props = Parameters::new(); props.insert("oob".to_string(), ParameterValue::String("invalid".into())); let result = ScaleType::continuous().resolve_properties("x", &props); assert!(result.is_err()); @@ -2614,7 +2613,7 @@ mod tests { #[test] fn test_oob_user_value_preserved() { - let mut props = HashMap::new(); + let mut props = Parameters::new(); props.insert("oob".to_string(), ParameterValue::String("squish".into())); let resolved = ScaleType::continuous() .resolve_properties("x", &props) @@ -2627,7 +2626,7 @@ mod tests { #[test] fn test_oob_scale_type_support() { - let props = HashMap::new(); + let props = Parameters::new(); // Continuous and binned support oob for scale_type in &[ScaleType::continuous(), ScaleType::binned()] { @@ -2640,7 +2639,7 @@ mod tests { } // Identity and discrete reject oob - let mut oob_props = HashMap::new(); + let mut oob_props = Parameters::new(); oob_props.insert("oob".to_string(), ParameterValue::String("censor".into())); assert!(ScaleType::identity() .resolve_properties("color", &oob_props) @@ -2852,7 +2851,7 @@ mod tests { #[test] fn test_reverse_property_default_false() { - let props = HashMap::new(); + let props = Parameters::new(); // Continuous scale should have reverse default to false let resolved = ScaleType::continuous() @@ -2875,7 +2874,7 @@ mod tests { #[test] fn test_reverse_property_accepts_true() { - let mut props = HashMap::new(); + let mut props = Parameters::new(); props.insert("reverse".to_string(), ParameterValue::Boolean(true)); let resolved = ScaleType::continuous() @@ -2889,7 +2888,7 @@ mod tests { #[test] fn test_reverse_property_supported_by_all_scales() { - let mut props = HashMap::new(); + let mut props = Parameters::new(); props.insert("reverse".to_string(), ParameterValue::Boolean(true)); // All scale types should support reverse property @@ -2917,7 +2916,7 @@ mod tests { #[test] fn test_identity_scale_rejects_reverse_property() { // Identity scale should not support reverse (no properties at all) - let mut props = HashMap::new(); + let mut props = Parameters::new(); props.insert("reverse".to_string(), ParameterValue::Boolean(true)); let result = ScaleType::identity().resolve_properties("x", &props); @@ -2930,7 +2929,7 @@ mod tests { #[test] fn test_breaks_property_default_is_7() { - let props = HashMap::new(); + let props = Parameters::new(); let resolved = ScaleType::continuous() .resolve_properties("x", &props) .unwrap(); @@ -2939,7 +2938,7 @@ mod tests { #[test] fn test_pretty_property_default_is_true() { - let props = HashMap::new(); + let props = Parameters::new(); let resolved = ScaleType::continuous() .resolve_properties("x", &props) .unwrap(); @@ -2948,7 +2947,7 @@ mod tests { #[test] fn test_breaks_property_accepts_number() { - let mut props = HashMap::new(); + let mut props = Parameters::new(); props.insert("breaks".to_string(), ParameterValue::Number(10.0)); let result = ScaleType::continuous().resolve_properties("x", &props); @@ -2961,7 +2960,7 @@ mod tests { fn test_breaks_property_accepts_array() { use crate::plot::ArrayElement; - let mut props = HashMap::new(); + let mut props = Parameters::new(); props.insert( "breaks".to_string(), ParameterValue::Array(vec![ @@ -2977,7 +2976,7 @@ mod tests { #[test] fn test_pretty_property_accepts_false() { - let mut props = HashMap::new(); + let mut props = Parameters::new(); props.insert("pretty".to_string(), ParameterValue::Boolean(false)); let result = ScaleType::continuous().resolve_properties("x", &props); @@ -2991,7 +2990,7 @@ mod tests { #[test] fn test_breaks_supported_by_continuous_scales() { - let mut props = HashMap::new(); + let mut props = Parameters::new(); props.insert("breaks".to_string(), ParameterValue::Number(5.0)); for scale_type in &[ScaleType::continuous(), ScaleType::binned()] { @@ -3006,7 +3005,7 @@ mod tests { #[test] fn test_discrete_does_not_support_breaks() { - let mut props = HashMap::new(); + let mut props = Parameters::new(); props.insert("breaks".to_string(), ParameterValue::Number(5.0)); let result = ScaleType::discrete().resolve_properties("x", &props); @@ -3016,7 +3015,7 @@ mod tests { #[test] fn test_identity_does_not_support_breaks() { - let mut props = HashMap::new(); + let mut props = Parameters::new(); props.insert("breaks".to_string(), ParameterValue::Number(5.0)); let result = ScaleType::identity().resolve_properties("x", &props); @@ -3026,7 +3025,7 @@ mod tests { #[test] fn test_breaks_available_for_material_aesthetics() { // breaks should work for color legends too - let mut props = HashMap::new(); + let mut props = Parameters::new(); props.insert("breaks".to_string(), ParameterValue::Number(4.0)); let result = ScaleType::continuous().resolve_properties("color", &props); @@ -3040,7 +3039,7 @@ mod tests { #[test] fn test_resolve_breaks_continuous_identity() { let input_range = Some(vec![ArrayElement::Number(0.0), ArrayElement::Number(100.0)]); - let mut props = HashMap::new(); + let mut props = Parameters::new(); props.insert("breaks".to_string(), ParameterValue::Number(5.0)); props.insert("pretty".to_string(), ParameterValue::Boolean(true)); @@ -3060,7 +3059,7 @@ mod tests { ArrayElement::Number(1.0), ArrayElement::Number(1000.0), ]); - let mut props = HashMap::new(); + let mut props = Parameters::new(); props.insert("breaks".to_string(), ParameterValue::Number(10.0)); props.insert("pretty".to_string(), ParameterValue::Boolean(false)); @@ -3080,7 +3079,7 @@ mod tests { #[test] fn test_resolve_breaks_continuous_log10_pretty() { let input_range = Some(vec![ArrayElement::Number(1.0), ArrayElement::Number(100.0)]); - let mut props = HashMap::new(); + let mut props = Parameters::new(); props.insert("breaks".to_string(), ParameterValue::Number(10.0)); props.insert("pretty".to_string(), ParameterValue::Boolean(true)); @@ -3099,7 +3098,7 @@ mod tests { #[test] fn test_resolve_breaks_continuous_sqrt() { let input_range = Some(vec![ArrayElement::Number(0.0), ArrayElement::Number(100.0)]); - let mut props = HashMap::new(); + let mut props = Parameters::new(); props.insert("breaks".to_string(), ParameterValue::Number(5.0)); props.insert("pretty".to_string(), ParameterValue::Boolean(false)); @@ -3121,7 +3120,7 @@ mod tests { #[test] fn test_resolve_breaks_discrete_returns_none() { let input_range = Some(vec![ArrayElement::Number(0.0), ArrayElement::Number(100.0)]); - let props = HashMap::new(); + let props = Parameters::new(); let breaks = ScaleType::discrete().resolve_breaks(input_range.as_deref(), &props, None); @@ -3132,7 +3131,7 @@ mod tests { #[test] fn test_resolve_breaks_identity_scale_returns_none() { let input_range = Some(vec![ArrayElement::Number(0.0), ArrayElement::Number(100.0)]); - let props = HashMap::new(); + let props = Parameters::new(); let breaks = ScaleType::identity().resolve_breaks(input_range.as_deref(), &props, None); @@ -3142,7 +3141,7 @@ mod tests { #[test] fn test_resolve_breaks_no_input_range() { - let props = HashMap::new(); + let props = Parameters::new(); let breaks = ScaleType::continuous().resolve_breaks(None, &props, None); @@ -3153,7 +3152,7 @@ mod tests { #[test] fn test_resolve_breaks_uses_default_count() { let input_range = Some(vec![ArrayElement::Number(0.0), ArrayElement::Number(100.0)]); - let props = HashMap::new(); // No explicit breaks count + let props = Parameters::new(); // No explicit breaks count let identity = Transform::identity(); let breaks = diff --git a/src/plot/scale/types.rs b/src/plot/scale/types.rs index 13d228184..6cc3289f6 100644 --- a/src/plot/scale/types.rs +++ b/src/plot/scale/types.rs @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use super::super::types::{ArrayElement, ParameterValue}; +use super::super::types::{ArrayElement, ParameterValue, Parameters}; use super::scale_type::ScaleType; use super::transform::Transform; @@ -54,7 +54,7 @@ pub struct Scale { /// Additional scale properties (SETTING clause) /// Note: `breaks` can be either a Number (count) or Array (explicit positions). /// If scalar at parse time, it's converted to Array during resolution. - pub properties: HashMap, + pub properties: Parameters, /// Whether this scale has been resolved (set by resolve() method) /// Used to skip re-resolution of pre-resolved scales (e.g., Binned scales) #[serde(default)] @@ -83,7 +83,7 @@ impl Scale { output_range: None, transform: None, explicit_transform: false, - properties: HashMap::new(), + properties: Parameters::new(), resolved: false, label_mapping: None, label_template: "{}".to_string(), diff --git a/src/plot/types.rs b/src/plot/types.rs index ba69fc8f1..702fc24c8 100644 --- a/src/plot/types.rs +++ b/src/plot/types.rs @@ -352,6 +352,9 @@ pub enum ParameterValue { Null, } +/// A collection of named parameter values, typically from `SET` clauses. +pub type Parameters = HashMap; + impl std::fmt::Display for ParameterValue { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { diff --git a/src/writer/vegalite/encoding.rs b/src/writer/vegalite/encoding.rs index 4c796e602..4ab08a58a 100644 --- a/src/writer/vegalite/encoding.rs +++ b/src/writer/vegalite/encoding.rs @@ -1131,6 +1131,7 @@ pub(super) fn build_detail_encoding(partition_by: &[String]) -> Option { #[cfg(test)] mod tests { use super::*; + use crate::plot::Parameters; #[test] fn test_build_label_expr_temporal_uses_utc_format() { @@ -1254,7 +1255,7 @@ mod tests { output_range: None, transform: None, explicit_transform: false, - properties: std::collections::HashMap::new(), + properties: Parameters::new(), resolved: false, label_mapping: None, label_template: "{}".to_string(), diff --git a/src/writer/vegalite/layer.rs b/src/writer/vegalite/layer.rs index 7ef0b0b5e..f33d9fe4e 100644 --- a/src/writer/vegalite/layer.rs +++ b/src/writer/vegalite/layer.rs @@ -2477,6 +2477,7 @@ pub fn get_renderer(geom: &Geom) -> Box { #[cfg(test)] mod tests { use super::*; + use crate::plot::Parameters; #[test] fn test_violin_detail_encoding() { @@ -4172,7 +4173,7 @@ mod tests { output_range: None, transform: None, explicit_transform: false, - properties: std::collections::HashMap::new(), + properties: Parameters::new(), resolved: false, label_mapping: None, label_template: "{}".to_string(), @@ -4205,7 +4206,7 @@ mod tests { output_range: None, transform: None, explicit_transform: false, - properties: std::collections::HashMap::new(), + properties: Parameters::new(), resolved: false, label_mapping: None, label_template: "{}".to_string(), @@ -4234,7 +4235,7 @@ mod tests { output_range: None, transform: None, explicit_transform: false, - properties: std::collections::HashMap::new(), + properties: Parameters::new(), resolved: false, label_mapping: None, label_template: "{}".to_string(), diff --git a/src/writer/vegalite/mod.rs b/src/writer/vegalite/mod.rs index 8c86aa493..e7e62b882 100644 --- a/src/writer/vegalite/mod.rs +++ b/src/writer/vegalite/mod.rs @@ -26,7 +26,7 @@ mod layer; mod projection; use crate::plot::ArrayElement; -use crate::plot::{ParameterValue, Scale, ScaleTypeKind}; +use crate::plot::{ParameterValue, Parameters, Scale, ScaleTypeKind}; use crate::writer::Writer; use crate::{naming, AestheticValue, DataFrame, GgsqlError, Plot, Result}; use serde_json::{json, Value}; @@ -664,7 +664,7 @@ fn apply_facet_ordering(vl_spec: &mut Value, facet_col: &str, index_map: &[(Valu /// - Polar: pos1 -> "radius", pos2 -> "theta" fn apply_facet_scale_resolution( vl_spec: &mut Value, - properties: &HashMap, + properties: &Parameters, projection: &dyn ProjectionRenderer, ) { let Some(ParameterValue::Array(arr)) = properties.get("free") else { @@ -1018,11 +1018,7 @@ pub(super) fn escape_vega_string(s: &str) -> String { /// - ncol: Number of columns for wrap facets (maps to Vega-Lite's "columns") /// /// Note: free is handled separately by apply_facet_scale_resolution -fn apply_facet_properties( - vl_spec: &mut Value, - properties: &HashMap, - is_wrap: bool, -) { +fn apply_facet_properties(vl_spec: &mut Value, properties: &Parameters, is_wrap: bool) { for (name, value) in properties { match name.as_str() { "ncol" if is_wrap => { @@ -2657,7 +2653,7 @@ mod tests { // Add facet with free => [true, true] (both x and y free) // This is the normalized format after facet resolution - let mut facet_properties = HashMap::new(); + let mut facet_properties = Parameters::new(); facet_properties.insert( "free".to_string(), ParameterValue::Array(vec![ @@ -2737,7 +2733,7 @@ mod tests { // Add facet with free => [false, true] (only y is free) // This is the normalized format after facet resolution - let mut facet_properties = HashMap::new(); + let mut facet_properties = Parameters::new(); facet_properties.insert( "free".to_string(), ParameterValue::Array(vec![ @@ -2834,7 +2830,7 @@ mod tests { layout: FacetLayout::Wrap { variables: vec!["category".to_string()], }, - properties: HashMap::new(), // No free property + properties: Parameters::new(), // No free property resolved: true, }); @@ -3030,8 +3026,8 @@ mod tests { spec.project = Some(Projection { aesthetics: vec!["angle".to_string(), "radius".to_string()], coord: Coord::polar(), - properties: HashMap::new(), - computed: HashMap::new(), + properties: Parameters::new(), + computed: Parameters::new(), }); let layer = Layer::new(Geom::point()) .with_aesthetic(