Skip to content

Commit d42b439

Browse files
teunbrandclaude
andcommitted
Add tests for PathRenderer line segmentation and trail mark conversion.
Tests cover: (1) metadata detection of varying material aesthetics, (2) trail mark conversion for varying linewidth, and (3) segmentation transforms for varying stroke/opacity. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent ef964ab commit d42b439

1 file changed

Lines changed: 183 additions & 0 deletions

File tree

src/writer/vegalite/layer.rs

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)