@@ -322,4 +322,80 @@ mod tests {
322322 assert ! ( ( -0.3 ..=0.3 ) . contains( & v) ) ;
323323 }
324324 }
325+
326+ #[ test]
327+ fn test_stack_resets_per_facet_panel ( ) {
328+ // Stacking should compute independently within each facet panel.
329+ // Without this, bars in the second facet panel stack on top of
330+ // cumulative values from the first panel (see issue #244).
331+ //
332+ // Two facet panels (F1, F2) each with the same x="A" and two
333+ // fill groups (X, Y). Stacking within each panel should start from 0.
334+ let df = df ! {
335+ "__ggsql_aes_pos1__" => [ "A" , "A" , "A" , "A" ] ,
336+ "__ggsql_aes_pos2__" => [ 10.0 , 20.0 , 30.0 , 40.0 ] ,
337+ "__ggsql_aes_pos2end__" => [ 0.0 , 0.0 , 0.0 , 0.0 ] ,
338+ "__ggsql_aes_fill__" => [ "X" , "Y" , "X" , "Y" ] ,
339+ "__ggsql_aes_facet1__" => [ "F1" , "F1" , "F2" , "F2" ] ,
340+ }
341+ . unwrap ( ) ;
342+
343+ let mut layer = crate :: plot:: Layer :: new ( Geom :: bar ( ) ) ;
344+ layer. mappings = {
345+ let mut m = Mappings :: new ( ) ;
346+ m. insert ( "pos1" , AestheticValue :: standard_column ( "__ggsql_aes_pos1__" ) ) ;
347+ m. insert ( "pos2" , AestheticValue :: standard_column ( "__ggsql_aes_pos2__" ) ) ;
348+ m. insert ( "pos2end" , AestheticValue :: standard_column ( "__ggsql_aes_pos2end__" ) ) ;
349+ m. insert ( "fill" , AestheticValue :: standard_column ( "__ggsql_aes_fill__" ) ) ;
350+ m. insert ( "facet1" , AestheticValue :: standard_column ( "__ggsql_aes_facet1__" ) ) ;
351+ m
352+ } ;
353+ layer. partition_by = vec ! [
354+ "__ggsql_aes_fill__" . to_string( ) ,
355+ "__ggsql_aes_facet1__" . to_string( ) ,
356+ ] ;
357+ layer. position = Position :: stack ( ) ;
358+ layer. data_key = Some ( "__ggsql_layer_0__" . to_string ( ) ) ;
359+
360+ let mut spec = Plot :: new ( ) ;
361+ spec. scales . push ( make_discrete_scale ( "pos1" ) ) ;
362+ spec. scales . push ( make_continuous_scale ( "pos2" ) ) ;
363+ let mut data_map = HashMap :: new ( ) ;
364+ data_map. insert ( "__ggsql_layer_0__" . to_string ( ) , df) ;
365+
366+ let mut spec_with_layer = spec;
367+ spec_with_layer. layers . push ( layer) ;
368+
369+ apply_position_adjustments ( & mut spec_with_layer, & mut data_map) . unwrap ( ) ;
370+
371+ let result_df = data_map. get ( "__ggsql_layer_0__" ) . unwrap ( ) ;
372+
373+ // Sort by facet then fill so we can assert in predictable order
374+ let result_df = result_df
375+ . clone ( )
376+ . lazy ( )
377+ . sort_by_exprs (
378+ [ col ( "__ggsql_aes_facet1__" ) , col ( "__ggsql_aes_fill__" ) ] ,
379+ SortMultipleOptions :: default ( ) ,
380+ )
381+ . collect ( )
382+ . unwrap ( ) ;
383+
384+ let pos2 = result_df. column ( "__ggsql_aes_pos2__" ) . unwrap ( ) . f64 ( ) . unwrap ( ) ;
385+ let pos2end = result_df. column ( "__ggsql_aes_pos2end__" ) . unwrap ( ) . f64 ( ) . unwrap ( ) ;
386+
387+ let pos2_vals: Vec < f64 > = pos2. into_iter ( ) . flatten ( ) . collect ( ) ;
388+ let pos2end_vals: Vec < f64 > = pos2end. into_iter ( ) . flatten ( ) . collect ( ) ;
389+
390+ // Expected (sorted by facet, fill):
391+ // F1/X: pos2end=0, pos2=10 (first in panel, starts at 0)
392+ // F1/Y: pos2end=10, pos2=30 (stacks on X)
393+ // F2/X: pos2end=0, pos2=30 (first in panel, should reset to 0)
394+ // F2/Y: pos2end=30, pos2=70 (stacks on X)
395+ assert_eq ! (
396+ pos2end_vals[ 2 ] , 0.0 ,
397+ "F2 panel first bar should start at 0, not carry over from F1. pos2end={:?}, pos2={:?}" ,
398+ pos2end_vals, pos2_vals
399+ ) ;
400+ }
325401}
0 commit comments