@@ -1308,12 +1308,24 @@ impl GeomRenderer for ViolinRenderer {
13081308 } ) ;
13091309 let offset_col = naming:: aesthetic_column ( "offset" ) ;
13101310
1311- // It'll be implemented as an offset.
1312- let violin_offset = format ! ( "[datum.{offset}, -datum.{offset}]" , offset = offset_col) ;
1313-
13141311 // Read orientation from layer (already resolved during execution)
13151312 let is_horizontal = is_transposed ( layer) ;
13161313
1314+ // It'll be implemented as an offset.
1315+ let mut violin_offset = format ! ( "[datum.{offset}, -datum.{offset}]" , offset = offset_col) ;
1316+ if let Some ( ParameterValue :: String ( side) ) = layer. parameters . get ( "side" ) {
1317+ let positive = if is_horizontal {
1318+ matches ! ( side. as_str( ) , "bottom" | "left" )
1319+ } else {
1320+ matches ! ( side. as_str( ) , "top" | "right" )
1321+ } ;
1322+ violin_offset = if positive {
1323+ format ! ( "[datum.{offset}]" , offset = offset_col)
1324+ } else {
1325+ format ! ( "[-datum.{offset}]" , offset = offset_col)
1326+ } ;
1327+ }
1328+
13171329 // Continuous axis column for order calculation:
13181330 // - Vertical: pos2 (y-axis has continuous density values)
13191331 // - Horizontal: pos1 (x-axis has continuous density values)
@@ -1380,6 +1392,8 @@ impl GeomRenderer for ViolinRenderer {
13801392 // Read orientation from layer (already resolved during execution)
13811393 let is_horizontal = is_transposed ( layer) ;
13821394
1395+ encoding. remove ( "offset" ) ;
1396+
13831397 // Categorical axis for detail encoding:
13841398 // - Vertical: x channel (categorical groups on x-axis)
13851399 // - Horizontal: y channel (categorical groups on y-axis)
@@ -3210,6 +3224,114 @@ mod tests {
32103224 ) ;
32113225 }
32123226
3227+ #[ test]
3228+ fn test_violin_ridge_parameter ( ) {
3229+ use crate :: naming;
3230+ use crate :: plot:: ParameterValue ;
3231+
3232+ let offset_col = naming:: aesthetic_column ( "offset" ) ;
3233+
3234+ fn get_violin_offset_expr ( ridge : Option < & str > , is_horizontal : bool ) -> String {
3235+ let mut layer = Layer :: new ( crate :: plot:: Geom :: violin ( ) ) ;
3236+ if let Some ( r) = ridge {
3237+ layer
3238+ . parameters
3239+ . insert ( "side" . to_string ( ) , ParameterValue :: String ( r. to_string ( ) ) ) ;
3240+ }
3241+
3242+ // Set orientation parameter for horizontal case
3243+ if is_horizontal {
3244+ layer. parameters . insert (
3245+ "orientation" . to_string ( ) ,
3246+ ParameterValue :: String ( "transposed" . to_string ( ) ) ,
3247+ ) ;
3248+ }
3249+
3250+ let mut layer_spec = if is_horizontal {
3251+ json ! ( {
3252+ "mark" : { "type" : "line" } ,
3253+ "encoding" : {
3254+ "x" : { "field" : naming:: aesthetic_column( "pos2" ) , "type" : "quantitative" } ,
3255+ "y" : { "field" : "species" , "type" : "nominal" }
3256+ }
3257+ } )
3258+ } else {
3259+ json ! ( {
3260+ "mark" : { "type" : "line" } ,
3261+ "encoding" : {
3262+ "x" : { "field" : "species" , "type" : "nominal" } ,
3263+ "y" : { "field" : naming:: aesthetic_column( "pos2" ) , "type" : "quantitative" }
3264+ }
3265+ } )
3266+ } ;
3267+
3268+ ViolinRenderer
3269+ . modify_spec ( & mut layer_spec, & layer, & RenderContext :: new ( & [ ] ) )
3270+ . unwrap ( ) ;
3271+
3272+ layer_spec[ "transform" ]
3273+ . as_array ( )
3274+ . unwrap ( )
3275+ . iter ( )
3276+ . find ( |t| t. get ( "as" ) . and_then ( |a| a. as_str ( ) ) == Some ( "violin_offsets" ) )
3277+ . unwrap ( ) [ "calculate" ]
3278+ . as_str ( )
3279+ . unwrap ( )
3280+ . to_string ( )
3281+ }
3282+
3283+ // Default "both" - mirrors on both sides (vertical orientation)
3284+ let expr = get_violin_offset_expr ( None , false ) ;
3285+ assert ! (
3286+ expr. contains( & format!( "[datum.{}, -datum.{}]" , offset_col, offset_col) )
3287+ || expr. contains( & format!( "[-datum.{}, datum.{}]" , offset_col, offset_col) ) ,
3288+ "Default should mirror both sides: {}" ,
3289+ expr
3290+ ) ;
3291+
3292+ // Vertical orientation (default): x=nominal, y=quantitative
3293+ // "left" and "bottom" - only negative offset
3294+ assert_eq ! (
3295+ get_violin_offset_expr( Some ( "left" ) , false ) ,
3296+ format!( "[-datum.{}]" , offset_col)
3297+ ) ;
3298+ assert_eq ! (
3299+ get_violin_offset_expr( Some ( "bottom" ) , false ) ,
3300+ format!( "[-datum.{}]" , offset_col)
3301+ ) ;
3302+
3303+ // "right" and "top" - only positive offset
3304+ assert_eq ! (
3305+ get_violin_offset_expr( Some ( "right" ) , false ) ,
3306+ format!( "[datum.{}]" , offset_col)
3307+ ) ;
3308+ assert_eq ! (
3309+ get_violin_offset_expr( Some ( "top" ) , false ) ,
3310+ format!( "[datum.{}]" , offset_col)
3311+ ) ;
3312+
3313+ // Horizontal orientation: x=quantitative, y=nominal
3314+ // "bottom" and "left" - only positive offset
3315+ assert_eq ! (
3316+ get_violin_offset_expr( Some ( "bottom" ) , true ) ,
3317+ format!( "[datum.{}]" , offset_col)
3318+ ) ;
3319+ assert_eq ! (
3320+ get_violin_offset_expr( Some ( "left" ) , true ) ,
3321+ format!( "[datum.{}]" , offset_col)
3322+ ) ;
3323+
3324+ // "top" and "right" - only negative offset
3325+ assert_eq ! (
3326+ get_violin_offset_expr( Some ( "top" ) , true ) ,
3327+ format!( "[-datum.{}]" , offset_col)
3328+ ) ;
3329+ assert_eq ! (
3330+ get_violin_offset_expr( Some ( "right" ) , true ) ,
3331+ format!( "[-datum.{}]" , offset_col)
3332+ ) ;
3333+ }
3334+
32133335 #[ test]
32143336 fn test_render_context_get_extent ( ) {
32153337 use crate :: plot:: { ArrayElement , Scale } ;
0 commit comments