From 2d1f711dafe7ed706a6ef82f50815325892b66b5 Mon Sep 17 00:00:00 2001 From: Jon Mease Date: Fri, 12 Jan 2024 20:19:07 -0500 Subject: [PATCH] Support dashes in rule mark --- .../rule/dashed_rules.dims.json | 6 + .../vega-scenegraphs/rule/dashed_rules.png | Bin 0 -> 48185 bytes .../rule/dashed_rules.sg.json | 63 +++++++++ .../vega-specs/rule/dashed_rules.vg.json | 39 ++++++ .../rule/wide_transparent_caps.vg.json | 16 --- sg2d-vega/src/error.rs | 3 + sg2d-vega/src/marks/rule.rs | 24 ++++ sg2d-wgpu/src/marks/rule.rs | 132 +++++++++++++++--- sg2d-wgpu/tests/test_image_baselines.rs | 1 + sg2d/src/marks/rule.rs | 9 ++ 10 files changed, 254 insertions(+), 39 deletions(-) create mode 100644 sg2d-vega-test-data/vega-scenegraphs/rule/dashed_rules.dims.json create mode 100644 sg2d-vega-test-data/vega-scenegraphs/rule/dashed_rules.png create mode 100644 sg2d-vega-test-data/vega-scenegraphs/rule/dashed_rules.sg.json create mode 100644 sg2d-vega-test-data/vega-specs/rule/dashed_rules.vg.json diff --git a/sg2d-vega-test-data/vega-scenegraphs/rule/dashed_rules.dims.json b/sg2d-vega-test-data/vega-scenegraphs/rule/dashed_rules.dims.json new file mode 100644 index 0000000..1ec583a --- /dev/null +++ b/sg2d-vega-test-data/vega-scenegraphs/rule/dashed_rules.dims.json @@ -0,0 +1,6 @@ +{ + "width": 410, + "height": 410, + "origin_x": 5, + "origin_y": 5 +} \ No newline at end of file diff --git a/sg2d-vega-test-data/vega-scenegraphs/rule/dashed_rules.png b/sg2d-vega-test-data/vega-scenegraphs/rule/dashed_rules.png new file mode 100644 index 0000000000000000000000000000000000000000..0c8509ddee25dcc8c0c419ef3c6f3b04dd17d731 GIT binary patch literal 48185 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 literal 0 HcmV?d00001 diff --git a/sg2d-vega-test-data/vega-scenegraphs/rule/dashed_rules.sg.json b/sg2d-vega-test-data/vega-scenegraphs/rule/dashed_rules.sg.json new file mode 100644 index 0000000..c983177 --- /dev/null +++ b/sg2d-vega-test-data/vega-scenegraphs/rule/dashed_rules.sg.json @@ -0,0 +1,63 @@ +{ + "marktype": "group", + "name": "root", + "role": "frame", + "interactive": true, + "clip": false, + "items": [ + { + "items": [ + { + "marktype": "rule", + "name": "marks", + "role": "mark", + "interactive": true, + "clip": false, + "items": [ + { + "x": 340, + "y": 15, + "opacity": 0.7, + "stroke": "orange", + "strokeWidth": 8, + "strokeCap": "butt", + "strokeDash": "12 8,4", + "x2": 60, + "y2": 380 + }, + { + "x": 20, + "y": 15, + "opacity": 0.7, + "stroke": "blue", + "strokeWidth": 4, + "strokeCap": "square", + "strokeDash": "8,16", + "x2": 320, + "y2": 340 + }, + { + "x": 80, + "y": 15, + "opacity": 0.7, + "stroke": "green", + "strokeWidth": 6, + "strokeCap": "round", + "strokeDash": "12", + "x2": 220, + "y2": 380 + } + ], + "zindex": 0 + } + ], + "x": 0, + "y": 0, + "width": 400, + "height": 400, + "fill": "transparent", + "stroke": "transparent" + } + ], + "zindex": 0 +} \ No newline at end of file diff --git a/sg2d-vega-test-data/vega-specs/rule/dashed_rules.vg.json b/sg2d-vega-test-data/vega-specs/rule/dashed_rules.vg.json new file mode 100644 index 0000000..c6468ef --- /dev/null +++ b/sg2d-vega-test-data/vega-specs/rule/dashed_rules.vg.json @@ -0,0 +1,39 @@ +{ + "$schema": "https://vega.github.io/schema/vega/v5.json", + "description": "A scatterplot showing horsepower and miles per gallons for various cars.", + "background": "white", + "padding": 5, + "width": 400, + "height": 400, + "style": "cell", + "config": {"style": {"cell": {"stroke": "transparent"}}}, + "data": [{ + "name": "source_0", + "values": [ + {"x": 340, "x2": 60, "y": 15, "y2": 380, "fill": "orange", "cap": "butt", "width": 8, "dash": "12 8,4"}, + {"x": 20, "x2": 320, "y": 15, "y2": 340, "fill": "blue", "cap": "square", "width": 4, "dash": "8,16"}, + {"x": 80, "x2": 220, "y": 15, "y2": 380, "fill": "green", "cap": "round", "width": 6, "dash": "12"} + ] + }], + "marks": [ + { + "name": "marks", + "type": "rule", + "style": ["rule"], + "from": {"data": "source_0"}, + "encode": { + "update": { + "strokeWidth": {"field": "width"}, + "stroke": {"field": "fill"}, + "x": {"field": "x" }, + "x2": {"field": "x2"}, + "y": {"field": "y"}, + "y2": {"field": "y2"}, + "opacity": {"value": 0.7}, + "strokeCap": {"field": "cap"}, + "strokeDash": {"field": "dash"} + } + } + } + ] +} diff --git a/sg2d-vega-test-data/vega-specs/rule/wide_transparent_caps.vg.json b/sg2d-vega-test-data/vega-specs/rule/wide_transparent_caps.vg.json index 307bb36..99ba5f1 100644 --- a/sg2d-vega-test-data/vega-specs/rule/wide_transparent_caps.vg.json +++ b/sg2d-vega-test-data/vega-specs/rule/wide_transparent_caps.vg.json @@ -34,21 +34,5 @@ } } } - ], - "scales": [ - { - "name": "x", - "type": "linear", - "domain": [0, 100], - "range": [0, {"signal": "width"}], - "zero": true - }, - { - "name": "y", - "type": "linear", - "domain": [0, 100], - "range": [{"signal": "height"}, 0], - "zero": true - } ] } diff --git a/sg2d-vega/src/error.rs b/sg2d-vega/src/error.rs index b4dd05c..2067ec3 100644 --- a/sg2d-vega/src/error.rs +++ b/sg2d-vega/src/error.rs @@ -11,6 +11,9 @@ pub enum VegaSceneGraphError { // ParseError doesn't implement std::Error, so #[from] doesn't seem to work #[error("Error parsing SVG path")] InvalidSvgPath(lyon_extra::parser::ParseError), + + #[error("Invalid dash string: {0}")] + InvalidDashString(String), } impl From for VegaSceneGraphError { diff --git a/sg2d-vega/src/marks/rule.rs b/sg2d-vega/src/marks/rule.rs index ba93da9..99c8659 100644 --- a/sg2d-vega/src/marks/rule.rs +++ b/sg2d-vega/src/marks/rule.rs @@ -16,6 +16,7 @@ pub struct VegaRuleItem { pub stroke_width: Option, pub stroke_cap: Option, pub stroke_opacity: Option, + pub stroke_dash: Option, pub opacity: Option, pub zindex: Option, } @@ -41,6 +42,7 @@ impl VegaMarkContainer { let mut stroke = Vec::<[f32; 4]>::new(); let mut stroke_width = Vec::::new(); let mut stroke_cap = Vec::::new(); + let mut stroke_dash = Vec::>::new(); let mut zindex = Vec::::new(); // For each item, append explicit values to corresponding vector @@ -64,6 +66,10 @@ impl VegaMarkContainer { stroke_cap.push(s); } + if let Some(dash) = &item.stroke_dash { + stroke_dash.push(parse_dash_str(dash)?); + } + if let Some(v) = item.zindex { zindex.push(v); } @@ -96,6 +102,11 @@ impl VegaMarkContainer { if stroke_cap.len() == len { mark.stroke_cap = EncodingValue::Array { values: stroke_cap }; } + if stroke_dash.len() == len { + mark.stroke_dash = Some(EncodingValue::Array { + values: stroke_dash, + }); + } if zindex.len() == len { let mut indices: Vec = (0..len).collect(); indices.sort_by_key(|i| zindex[*i]); @@ -105,3 +116,16 @@ impl VegaMarkContainer { Ok(SceneMark::Rule(mark)) } } + +fn parse_dash_str(dash_str: &str) -> Result, VegaSceneGraphError> { + let clean_dash_str = dash_str.replace(',', " "); + let mut dashes: Vec = Vec::new(); + for s in clean_dash_str.split_whitespace() { + let d = s + .parse::() + .map_err(|_| VegaSceneGraphError::InvalidDashString(dash_str.to_string()))? + .abs(); + dashes.push(d); + } + Ok(dashes) +} diff --git a/sg2d-wgpu/src/marks/rule.rs b/sg2d-wgpu/src/marks/rule.rs index 60fc93e..1e87967 100644 --- a/sg2d-wgpu/src/marks/rule.rs +++ b/sg2d-wgpu/src/marks/rule.rs @@ -51,29 +51,115 @@ const INSTANCE_ATTRIBUTES: [wgpu::VertexAttribute; 7] = wgpu::vertex_attr_array! ]; impl RuleInstance { - pub fn iter_from_spec(mark: &RuleMark) -> impl Iterator + '_ { - izip!( - mark.x0_iter(), - mark.y0_iter(), - mark.x1_iter(), - mark.y1_iter(), - mark.stroke_iter(), - mark.stroke_width_iter(), - mark.stroke_cap_iter(), - ) - .map(|(x0, y0, x1, y1, stroke, stroke_width, cap)| RuleInstance { - x0: *x0, - y0: *y0, - x1: *x1, - y1: *y1, - stroke: *stroke, - stroke_width: *stroke_width, - stroke_cap: match cap { - StrokeCap::Butt => STROKE_CAP_BUTT, - StrokeCap::Square => STROKE_CAP_SQUARE, - StrokeCap::Round => STROKE_CAP_ROUND, - }, - }) + pub fn iter_from_spec(mark: &RuleMark) -> Box + '_> { + if let Some(stroke_dash_iter) = mark.stroke_dash_iter() { + // Rule has a dash specification, so we create an individual RuleInstance for each dash + // in each Rule mark item. + Box::new( + izip!( + stroke_dash_iter, + mark.x0_iter(), + mark.y0_iter(), + mark.x1_iter(), + mark.y1_iter(), + mark.stroke_iter(), + mark.stroke_width_iter(), + mark.stroke_cap_iter(), + ) + .flat_map( + |(stroke_dash, x0, y0, x1, y1, stroke, stroke_width, cap)| { + // Next index into stroke_dash array + let mut dash_idx = 0; + + // Distance along line from (x0,y0) to (x1,y1) where the next dash will start + let mut start_dash_dist: f32 = 0.0; + + // Length of the line from (x0,y0) to (x1,y1) + let rule_len = ((x1 - x0).powi(2) + (y1 - y0).powi(2)).sqrt(); + + // Coponents of unit vector along (x0,y0) to (x1,y1) + let xhat = (x1 - x0) / rule_len; + let yhat = (y1 - y0) / rule_len; + + // Vector of rule instances, one for each dash segment + let mut dash_rules: Vec = Vec::new(); + + // Whether the next dash length represents a drawn dash (draw == true) + // or a gap (draw == false) + let mut draw = true; + + while start_dash_dist < rule_len { + let end_dash_dist = + if start_dash_dist + stroke_dash[dash_idx] >= rule_len { + // The final dash/gap should be truncated to the end of the rule + rule_len + } else { + // The dash/gap fits entirely in the rule + start_dash_dist + stroke_dash[dash_idx] + }; + + if draw { + let dash_x0 = x0 + xhat * start_dash_dist; + let dash_y0 = y0 + yhat * start_dash_dist; + let dash_x1 = x0 + xhat * end_dash_dist; + let dash_y1 = y0 + yhat * end_dash_dist; + + dash_rules.push(RuleInstance { + x0: dash_x0, + y0: dash_y0, + x1: dash_x1, + y1: dash_y1, + stroke: *stroke, + stroke_width: *stroke_width, + stroke_cap: match cap { + StrokeCap::Butt => STROKE_CAP_BUTT, + StrokeCap::Square => STROKE_CAP_SQUARE, + StrokeCap::Round => STROKE_CAP_ROUND, + }, + }) + } + + // update start dist for next dash/gap + start_dash_dist = end_dash_dist; + + // increment index and cycle back to start of start of dash array + dash_idx = (dash_idx + 1) % stroke_dash.len(); + + // Alternate between drawn dash and gap + draw = !draw; + } + + dash_rules + }, + ), + ) + } else { + // Rule has no dash specification, so we create one RuleInstance per Rule mark item + Box::new( + izip!( + mark.x0_iter(), + mark.y0_iter(), + mark.x1_iter(), + mark.y1_iter(), + mark.stroke_iter(), + mark.stroke_width_iter(), + mark.stroke_cap_iter(), + ) + .map(|(x0, y0, x1, y1, stroke, stroke_width, cap)| RuleInstance { + x0: *x0, + y0: *y0, + x1: *x1, + y1: *y1, + stroke: *stroke, + stroke_width: *stroke_width, + stroke_cap: match cap { + StrokeCap::Butt => STROKE_CAP_BUTT, + StrokeCap::Square => STROKE_CAP_SQUARE, + StrokeCap::Round => STROKE_CAP_ROUND, + }, + }), + ) + } } } diff --git a/sg2d-wgpu/tests/test_image_baselines.rs b/sg2d-wgpu/tests/test_image_baselines.rs index 0329fb8..b4b421d 100644 --- a/sg2d-wgpu/tests/test_image_baselines.rs +++ b/sg2d-wgpu/tests/test_image_baselines.rs @@ -41,6 +41,7 @@ mod test_image_baselines { case("symbol", "mixed_symbols", 0.001), case("rule", "wide_rule_axes", 0.0001), case("rule", "wide_transparent_caps", 0.0001), + case("rule", "dashed_rules", 0.0001), case("text", "bar_axis_labels", 0.025) )] fn test_image_baseline(category: &str, spec_name: &str, tolerance: f64) { diff --git a/sg2d/src/marks/rule.rs b/sg2d/src/marks/rule.rs index 5daf18a..c7c668b 100644 --- a/sg2d/src/marks/rule.rs +++ b/sg2d/src/marks/rule.rs @@ -7,6 +7,7 @@ pub struct RuleMark { pub name: String, pub clip: bool, pub len: u32, + pub stroke_dash: Option>>, pub x0: EncodingValue, pub y0: EncodingValue, pub x1: EncodingValue, @@ -42,6 +43,13 @@ impl RuleMark { self.stroke_cap .as_iter(self.len as usize, self.indices.as_ref()) } + pub fn stroke_dash_iter(&self) -> Option> + '_>> { + if let Some(stroke_dash) = &self.stroke_dash { + Some(stroke_dash.as_iter(self.len as usize, self.indices.as_ref())) + } else { + None + } + } } impl Default for RuleMark { @@ -50,6 +58,7 @@ impl Default for RuleMark { name: "rule_mark".to_string(), clip: true, len: 1, + stroke_dash: None, x0: EncodingValue::Scalar { value: 0.0 }, y0: EncodingValue::Scalar { value: 0.0 }, x1: EncodingValue::Scalar { value: 0.0 },