@@ -271,6 +271,17 @@ fn build_layer_encoding(
271271 channel_name = "fillOpacity" . to_string ( ) ;
272272 }
273273
274+ // Secondary positional channels (x2, y2, theta2, radius2) only support
275+ // field/datum/value in Vega-Lite — not type, scale, axis, or title
276+ if matches ! ( channel_name. as_str( ) , "x2" | "y2" | "theta2" | "radius2" ) {
277+ let secondary_encoding = match value {
278+ AestheticValue :: Column { name : col, .. } => json ! ( { "field" : col} ) ,
279+ AestheticValue :: Literal ( lit) => json ! ( { "value" : lit. to_json( ) } ) ,
280+ } ;
281+ encoding. insert ( channel_name, secondary_encoding) ;
282+ continue ;
283+ }
284+
274285 let channel_encoding = build_encoding_channel ( aesthetic, value, & mut enc_ctx) ?;
275286 encoding. insert ( channel_name, channel_encoding) ;
276287
@@ -2345,4 +2356,69 @@ mod tests {
23452356 "x encoding SHOULD have domain when using fixed scales"
23462357 ) ;
23472358 }
2359+
2360+ #[ test]
2361+ fn test_secondary_channels_have_no_disallowed_properties ( ) {
2362+ // Vega-Lite secondary channels (x2, y2, theta2, radius2) only support:
2363+ // field, aggregate, bandPosition, bin, timeUnit, title, value.
2364+ // Properties like type, scale, and axis must NOT be emitted.
2365+ let writer = VegaLiteWriter :: new ( ) ;
2366+
2367+ // Segment geom requires pos1end and pos2end as column mappings,
2368+ // which map to x2 and y2 in Vega-Lite.
2369+ let mut spec = Plot :: new ( ) ;
2370+ let layer = Layer :: new ( Geom :: segment ( ) )
2371+ . with_aesthetic (
2372+ "x" . to_string ( ) ,
2373+ AestheticValue :: standard_column ( "x1" . to_string ( ) ) ,
2374+ )
2375+ . with_aesthetic (
2376+ "y" . to_string ( ) ,
2377+ AestheticValue :: standard_column ( "y1" . to_string ( ) ) ,
2378+ )
2379+ . with_aesthetic (
2380+ "xend" . to_string ( ) ,
2381+ AestheticValue :: standard_column ( "x2" . to_string ( ) ) ,
2382+ )
2383+ . with_aesthetic (
2384+ "yend" . to_string ( ) ,
2385+ AestheticValue :: standard_column ( "y2" . to_string ( ) ) ,
2386+ ) ;
2387+ spec. layers . push ( layer) ;
2388+
2389+ let df = df ! {
2390+ "x1" => & [ 0 , 1 ] ,
2391+ "y1" => & [ 0 , 1 ] ,
2392+ "x2" => & [ 1 , 2 ] ,
2393+ "y2" => & [ 1 , 2 ] ,
2394+ }
2395+ . unwrap ( ) ;
2396+
2397+ transform_spec ( & mut spec) ;
2398+ let json_str = writer. write ( & spec, & wrap_data ( df) ) . unwrap ( ) ;
2399+ let vl_spec: Value = serde_json:: from_str ( & json_str) . unwrap ( ) ;
2400+
2401+ for channel in [ "x2" , "y2" ] {
2402+ for layer in vl_spec[ "layer" ] . as_array ( ) . unwrap ( ) {
2403+ if let Some ( enc) = layer. get ( "encoding" ) . and_then ( |e| e. get ( channel) ) {
2404+ assert ! (
2405+ enc. get( "field" ) . is_some( ) ,
2406+ "{channel} should have 'field': {enc}"
2407+ ) ;
2408+ assert ! (
2409+ enc. get( "type" ) . is_none( ) ,
2410+ "{channel} should not have 'type': {enc}"
2411+ ) ;
2412+ assert ! (
2413+ enc. get( "scale" ) . is_none( ) ,
2414+ "{channel} should not have 'scale': {enc}"
2415+ ) ;
2416+ assert ! (
2417+ enc. get( "axis" ) . is_none( ) ,
2418+ "{channel} should not have 'axis': {enc}"
2419+ ) ;
2420+ }
2421+ }
2422+ }
2423+ }
23482424}
0 commit comments