@@ -4163,4 +4163,187 @@ mod tests {
41634163 "Error message should mention conflicting aesthetics"
41644164 ) ;
41654165 }
4166+
4167+ #[ test]
4168+ fn test_path_renderer_varying_aesthetics_metadata ( ) {
4169+ use crate :: plot:: { AestheticValue , Geom , Layer } ;
4170+ use polars:: prelude:: * ;
4171+
4172+ let renderer = PathRenderer ;
4173+ let mut layer = Layer :: new ( Geom :: line ( ) ) ;
4174+
4175+ // Create DataFrame with varying stroke
4176+ let df = df ! {
4177+ naming:: aesthetic_column( "pos1" ) . as_str( ) => & [ 1.0 , 2.0 , 3.0 ] ,
4178+ naming:: aesthetic_column( "pos2" ) . as_str( ) => & [ 10.0 , 20.0 , 30.0 ] ,
4179+ "color" . to_string( ) . as_str( ) => & [ 1.0 , 2.0 , 3.0 ] ,
4180+ }
4181+ . unwrap ( ) ;
4182+
4183+ // Map stroke to color column (continuous, not in partition_by)
4184+ layer. mappings . insert (
4185+ "stroke" . to_string ( ) ,
4186+ AestheticValue :: standard_column ( "color" ) ,
4187+ ) ;
4188+
4189+ // Prepare data - should detect varying stroke
4190+ let prepared = renderer
4191+ . prepare_data ( & df, & layer, "test" , & HashMap :: new ( ) )
4192+ . unwrap ( ) ;
4193+
4194+ match prepared {
4195+ PreparedData :: Single { metadata, .. } => {
4196+ let varying_aesthetics = metadata
4197+ . downcast_ref :: < Vec < & ' static str > > ( )
4198+ . expect ( "Metadata should be Vec<&str>" ) ;
4199+ assert_eq ! ( varying_aesthetics. len( ) , 1 ) ;
4200+ assert ! ( varying_aesthetics. contains( & "stroke" ) ) ;
4201+ }
4202+ _ => panic ! ( "Expected Single variant" ) ,
4203+ }
4204+ }
4205+
4206+ #[ test]
4207+ fn test_path_renderer_trail_mark_for_varying_linewidth ( ) {
4208+ use crate :: plot:: { AestheticValue , Geom , Layer } ;
4209+ use polars:: prelude:: * ;
4210+
4211+ let renderer = PathRenderer ;
4212+ let mut layer = Layer :: new ( Geom :: line ( ) ) ;
4213+
4214+ // Create DataFrame with varying linewidth
4215+ let df = df ! {
4216+ naming:: aesthetic_column( "pos1" ) . as_str( ) => & [ 1.0 , 2.0 , 3.0 ] ,
4217+ naming:: aesthetic_column( "pos2" ) . as_str( ) => & [ 10.0 , 20.0 , 30.0 ] ,
4218+ naming:: aesthetic_column( "linewidth" ) . as_str( ) => & [ 1.0 , 3.0 , 5.0 ] ,
4219+ }
4220+ . unwrap ( ) ;
4221+
4222+ // Map linewidth to column
4223+ layer. mappings . insert (
4224+ "linewidth" . to_string ( ) ,
4225+ AestheticValue :: standard_column ( naming:: aesthetic_column ( "linewidth" ) ) ,
4226+ ) ;
4227+
4228+ // Prepare data
4229+ let prepared = renderer
4230+ . prepare_data ( & df, & layer, "test" , & HashMap :: new ( ) )
4231+ . unwrap ( ) ;
4232+
4233+ // Create a mock layer spec
4234+ let layer_spec = json ! ( {
4235+ "mark" : { "type" : "line" , "clip" : true } ,
4236+ "encoding" : {
4237+ "x" : { "field" : naming:: aesthetic_column( "pos1" ) , "type" : "quantitative" } ,
4238+ "y" : { "field" : naming:: aesthetic_column( "pos2" ) , "type" : "quantitative" } ,
4239+ "strokeWidth" : { "field" : naming:: aesthetic_column( "linewidth" ) , "type" : "quantitative" }
4240+ }
4241+ } ) ;
4242+
4243+ // Finalize should switch to trail mark and translate encodings
4244+ let result = renderer
4245+ . finalize ( layer_spec. clone ( ) , & layer, "test" , & prepared)
4246+ . unwrap ( ) ;
4247+
4248+ assert_eq ! ( result. len( ) , 1 ) ;
4249+ let spec = & result[ 0 ] ;
4250+
4251+ // Check mark type is trail
4252+ assert_eq ! ( spec[ "mark" ] [ "type" ] , "trail" ) ;
4253+ assert_eq ! ( spec[ "mark" ] [ "stroke" ] , json!( null) ) ;
4254+
4255+ // Check encoding translations
4256+ let encoding = spec[ "encoding" ] . as_object ( ) . unwrap ( ) ;
4257+ assert ! ( encoding. contains_key( "size" ) , "Should have size encoding" ) ;
4258+ assert ! (
4259+ !encoding. contains_key( "strokeWidth" ) ,
4260+ "strokeWidth should be removed"
4261+ ) ;
4262+ // No stroke mapping in this test, so no fill expected
4263+ assert ! ( !encoding. contains_key( "stroke" ) , "stroke should be removed" ) ;
4264+ }
4265+
4266+ #[ test]
4267+ fn test_path_renderer_segmentation_for_varying_stroke ( ) {
4268+ use crate :: plot:: { AestheticValue , Geom , Layer } ;
4269+ use polars:: prelude:: * ;
4270+
4271+ let renderer = PathRenderer ;
4272+ let mut layer = Layer :: new ( Geom :: line ( ) ) ;
4273+
4274+ // Create DataFrame with varying stroke
4275+ let df = df ! {
4276+ naming:: aesthetic_column( "pos1" ) . as_str( ) => & [ 1.0 , 2.0 , 3.0 ] ,
4277+ naming:: aesthetic_column( "pos2" ) . as_str( ) => & [ 10.0 , 20.0 , 30.0 ] ,
4278+ "color" . to_string( ) . as_str( ) => & [ 1.0 , 2.0 , 3.0 ] ,
4279+ ROW_INDEX_COLUMN => & [ 0 , 1 , 2 ] ,
4280+ }
4281+ . unwrap ( ) ;
4282+
4283+ // Map stroke to color column
4284+ layer. mappings . insert (
4285+ "stroke" . to_string ( ) ,
4286+ AestheticValue :: standard_column ( "color" ) ,
4287+ ) ;
4288+
4289+ // Prepare data
4290+ let prepared = renderer
4291+ . prepare_data ( & df, & layer, "test" , & HashMap :: new ( ) )
4292+ . unwrap ( ) ;
4293+
4294+ // Create a mock layer spec
4295+ let layer_spec = json ! ( {
4296+ "mark" : { "type" : "line" , "clip" : true } ,
4297+ "encoding" : {
4298+ "x" : { "field" : naming:: aesthetic_column( "pos1" ) , "type" : "quantitative" } ,
4299+ "y" : { "field" : naming:: aesthetic_column( "pos2" ) , "type" : "quantitative" } ,
4300+ "stroke" : { "field" : "color" , "type" : "nominal" }
4301+ }
4302+ } ) ;
4303+
4304+ // Finalize should apply segmentation transforms
4305+ let result = renderer
4306+ . finalize ( layer_spec. clone ( ) , & layer, "test" , & prepared)
4307+ . unwrap ( ) ;
4308+
4309+ assert_eq ! ( result. len( ) , 1 ) ;
4310+ let spec = & result[ 0 ] ;
4311+
4312+ // Check transforms exist
4313+ let transforms = spec[ "transform" ] . as_array ( ) . expect ( "Should have transforms" ) ;
4314+ assert ! ( !transforms. is_empty( ) ) ;
4315+
4316+ // Check for window transform (lead operation)
4317+ let has_window = transforms. iter ( ) . any ( |t| t. get ( "window" ) . is_some ( ) ) ;
4318+ assert ! ( has_window, "Should have window transform for lead" ) ;
4319+
4320+ // Check for flatten transform
4321+ let has_flatten = transforms. iter ( ) . any ( |t| t. get ( "flatten" ) . is_some ( ) ) ;
4322+ assert ! ( has_flatten, "Should have flatten transform" ) ;
4323+
4324+ // Check for detail encoding with segment_id
4325+ let encoding = spec[ "encoding" ] . as_object ( ) . unwrap ( ) ;
4326+ assert ! ( encoding. contains_key( "detail" ) , "Should have detail encoding" ) ;
4327+ assert_eq ! (
4328+ encoding[ "detail" ] [ "field" ] ,
4329+ "__segment_id__" ,
4330+ "Detail should use segment_id"
4331+ ) ;
4332+
4333+ // Check that x/y use _final fields
4334+ assert ! (
4335+ encoding[ "x" ] [ "field" ]
4336+ . as_str( )
4337+ . unwrap( )
4338+ . ends_with( "_final" ) ,
4339+ "x should use _final field"
4340+ ) ;
4341+ assert ! (
4342+ encoding[ "y" ] [ "field" ]
4343+ . as_str( )
4344+ . unwrap( )
4345+ . ends_with( "_final" ) ,
4346+ "y should use _final field"
4347+ ) ;
4348+ }
41664349}
0 commit comments