diff --git a/avenger-vega/src/marks/group.rs b/avenger-vega/src/marks/group.rs index 6e8aa72..93fe74f 100644 --- a/avenger-vega/src/marks/group.rs +++ b/avenger-vega/src/marks/group.rs @@ -3,6 +3,8 @@ use crate::marks::mark::{VegaMark, VegaMarkItem}; use avenger::marks::group::{GroupBounds, SceneGroup}; use avenger::marks::mark::SceneMark; use serde::{Deserialize, Serialize}; +use avenger::marks::value::{Gradient}; +use crate::marks::values::CssColorOrGradient; #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct VegaGroupItem { @@ -11,8 +13,18 @@ pub struct VegaGroupItem { pub(crate) x: f32, #[serde(default)] pub(crate) y: f32, + pub name: Option, pub width: Option, pub height: Option, + pub fill: Option, + pub stroke: Option, + pub stroke_width: Option, + pub corner_radius: Option, + pub opacity: Option, + pub fill_opacity: Option, + pub stroke_opacity: Option, + pub stroke_offset: Option, + pub zindex: Option, } impl VegaMarkItem for VegaGroupItem {} @@ -63,7 +75,23 @@ impl VegaGroupItem { }; marks.extend(item_marks); } + + let mut gradients = Vec::::new(); + let fill = if let Some(v) = &self.fill { + let opacity = self.fill_opacity.unwrap_or(1.0) * self.opacity.unwrap_or(1.0); + Some(v.to_color_or_grad(opacity, &mut gradients)?) + } else { + None + }; + let stroke = if let Some(v) = &self.stroke { + let opacity = self.fill_opacity.unwrap_or(1.0) * self.opacity.unwrap_or(1.0); + Some(v.to_color_or_grad(opacity, &mut gradients)?) + } else { + None + }; + Ok(SceneGroup { + name: self.name.clone().unwrap_or_else(|| "group_item".to_string()), bounds: GroupBounds { x: self.x, y: self.y, @@ -71,6 +99,12 @@ impl VegaGroupItem { height: self.height, }, marks, + gradients, + fill, + stroke, + stroke_width: self.stroke_width, + stroke_offset: self.stroke_offset, + corner_radius: self.corner_radius, }) } } diff --git a/avenger-vega/src/marks/symbol.rs b/avenger-vega/src/marks/symbol.rs index 2b7a39b..a33db10 100644 --- a/avenger-vega/src/marks/symbol.rs +++ b/avenger-vega/src/marks/symbol.rs @@ -82,6 +82,7 @@ impl VegaMarkContainer { line_marks.push(SceneMark::Line(mark)); } return Ok(SceneMark::Group(SceneGroup { + name: "symbol_line_legend".to_string(), bounds: GroupBounds { x: 0.0, y: 0.0, @@ -89,6 +90,12 @@ impl VegaMarkContainer { height: None, }, marks: line_marks, + gradients: vec![], + fill: None, + stroke: None, + stroke_width: None, + stroke_offset: None, + corner_radius: None, })); } diff --git a/avenger-wgpu/src/canvas.rs b/avenger-wgpu/src/canvas.rs index 8ba0aa0..0aba5ab 100644 --- a/avenger-wgpu/src/canvas.rs +++ b/avenger-wgpu/src/canvas.rs @@ -267,6 +267,11 @@ pub trait Canvas { group: &SceneGroup, group_bounds: GroupBounds, ) -> Result<(), Sg2dWgpuError> { + // Maybe add rect around group boundary + if let Some(rect) = group.make_rect() { + self.add_rect_mark(&rect, group_bounds)?; + } + // Compute new group bounds let group_bounds = GroupBounds { x: group_bounds.x + group.bounds.x, diff --git a/avenger/src/marks/group.rs b/avenger/src/marks/group.rs index 7b17738..481b99b 100644 --- a/avenger/src/marks/group.rs +++ b/avenger/src/marks/group.rs @@ -1,5 +1,7 @@ use crate::marks::mark::SceneMark; use serde::{Deserialize, Serialize}; +use crate::marks::rect::RectMark; +use crate::marks::value::{ColorOrGradient, EncodingValue, Gradient}; #[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub struct GroupBounds { @@ -22,6 +24,47 @@ impl Default for GroupBounds { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SceneGroup { + pub name: String, pub bounds: GroupBounds, pub marks: Vec, + pub gradients: Vec, + pub fill: Option, + pub stroke: Option, + pub stroke_width: Option, + pub stroke_offset: Option, + pub corner_radius: Option, } + +impl SceneGroup { + pub fn make_rect(&self) -> Option { + if self.fill.is_none() && self.stroke.is_none() { + return None + } + let stroke_width = self.stroke_width.unwrap_or(if self.stroke.is_some() { 1.0 } else { 0.0 }); + let stroke_offset = if let Some(stroke_offset) = self.stroke_offset { + stroke_offset + } else { + // From Vega's default stroke offset logic + if (self.stroke.is_some() && stroke_width > 0.5 && stroke_width < 1.5) { + 0.5 - (stroke_width - 1.0).abs() + } else { + 0.0 + } + }; + Some(RectMark { + name: format!("rect_{}", self.name), + clip: false, + len: 1, + gradients: self.gradients.clone(), + x: EncodingValue::Scalar { value: self.bounds.x + stroke_offset }, + y: EncodingValue::Scalar { value: self.bounds.y + stroke_offset }, + width: EncodingValue::Scalar { value: self.bounds.width.unwrap_or(0.0) }, + height: EncodingValue::Scalar { value: self.bounds.height.unwrap_or(0.0) }, + fill: EncodingValue::Scalar {value: self.fill.clone().unwrap_or(ColorOrGradient::Color([0.0, 0.0, 0.0, 0.0]))}, + stroke: EncodingValue::Scalar {value: self.stroke.clone().unwrap_or(ColorOrGradient::Color([0.0, 0.0, 0.0, 0.0]))}, + stroke_width: EncodingValue::Scalar { value: stroke_width }, + corner_radius: EncodingValue::Scalar { value: self.corner_radius.unwrap_or(0.0) }, + indices: None, + }) + } +} \ No newline at end of file