Skip to main content

esoc_scene/
mark.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2//! Visual mark types — the 9 primitives and batch variants.
3
4use crate::bounds::BoundingBox;
5use crate::style::{FillStyle, FontStyle, MarkerShape, StrokeStyle};
6
7/// A key for identifying marks across scene diffs (enter/update/exit).
8#[derive(Clone, Debug, PartialEq, Eq, Hash)]
9pub enum MarkKey {
10    /// Integer key (e.g., data row index).
11    Index(u64),
12    /// String key (e.g., category name).
13    Name(String),
14}
15
16/// Interpolation mode for line marks.
17#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
18pub enum Interpolation {
19    /// Straight line segments.
20    #[default]
21    Linear,
22    /// Step function (horizontal then vertical).
23    StepBefore,
24    /// Step function (vertical then horizontal).
25    StepAfter,
26    /// Monotone cubic spline.
27    Monotone,
28}
29
30/// A polyline with optional interpolation.
31#[derive(Clone, Debug)]
32pub struct LineMark {
33    /// Points along the line.
34    pub points: Vec<[f32; 2]>,
35    /// Line style.
36    pub stroke: StrokeStyle,
37    /// Interpolation mode.
38    pub interpolation: Interpolation,
39}
40
41/// A rectangle (bars, heatmap cells).
42#[derive(Clone, Debug)]
43pub struct RectMark {
44    /// Position and size.
45    pub bounds: BoundingBox,
46    /// Fill style.
47    pub fill: FillStyle,
48    /// Stroke style.
49    pub stroke: StrokeStyle,
50    /// Corner radius for rounded rectangles.
51    pub corner_radius: f32,
52}
53
54/// A point mark (scatter markers).
55#[derive(Clone, Debug)]
56pub struct PointMark {
57    /// Center position.
58    pub center: [f32; 2],
59    /// Marker size (diameter in pixels).
60    pub size: f32,
61    /// Marker shape.
62    pub shape: MarkerShape,
63    /// Fill style.
64    pub fill: FillStyle,
65    /// Stroke style.
66    pub stroke: StrokeStyle,
67}
68
69/// A filled area between two lines.
70#[derive(Clone, Debug)]
71pub struct AreaMark {
72    /// Upper boundary points.
73    pub upper: Vec<[f32; 2]>,
74    /// Lower boundary points (same x-values, different y).
75    pub lower: Vec<[f32; 2]>,
76    /// Fill style.
77    pub fill: FillStyle,
78    /// Stroke for the boundary lines.
79    pub stroke: StrokeStyle,
80}
81
82/// Text anchor alignment.
83#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
84pub enum TextAnchor {
85    /// Align to the start (left for LTR text).
86    #[default]
87    Start,
88    /// Center alignment.
89    Middle,
90    /// Align to the end (right for LTR text).
91    End,
92}
93
94/// A text label or annotation.
95#[derive(Clone, Debug)]
96pub struct TextMark {
97    /// Position.
98    pub position: [f32; 2],
99    /// Text content.
100    pub text: String,
101    /// Font style.
102    pub font: FontStyle,
103    /// Fill color for the text.
104    pub fill: FillStyle,
105    /// Rotation in degrees.
106    pub angle: f32,
107    /// Text anchor alignment.
108    pub anchor: TextAnchor,
109}
110
111/// An arc (pie/donut sector).
112#[derive(Clone, Debug)]
113pub struct ArcMark {
114    /// Center position.
115    pub center: [f32; 2],
116    /// Inner radius (0 for pie, >0 for donut).
117    pub inner_radius: f32,
118    /// Outer radius.
119    pub outer_radius: f32,
120    /// Start angle in radians.
121    pub start_angle: f32,
122    /// End angle in radians.
123    pub end_angle: f32,
124    /// Fill style.
125    pub fill: FillStyle,
126    /// Stroke style.
127    pub stroke: StrokeStyle,
128}
129
130/// A rule (grid line, reference line, whisker).
131#[derive(Clone, Debug)]
132pub struct RuleMark {
133    /// Line segments as `(start, end)` pairs.
134    pub segments: Vec<([f32; 2], [f32; 2])>,
135    /// Line style.
136    pub stroke: StrokeStyle,
137}
138
139/// A general path (Bezier curves, complex shapes).
140#[derive(Clone, Debug)]
141pub struct PathMark {
142    /// SVG-like path commands.
143    pub commands: Vec<PathCommand>,
144    /// Fill style.
145    pub fill: FillStyle,
146    /// Stroke style.
147    pub stroke: StrokeStyle,
148}
149
150/// SVG-like path command.
151#[derive(Clone, Copy, Debug)]
152pub enum PathCommand {
153    /// Move to absolute position.
154    MoveTo([f32; 2]),
155    /// Line to absolute position.
156    LineTo([f32; 2]),
157    /// Cubic bezier to (control1, control2, end).
158    CubicTo([f32; 2], [f32; 2], [f32; 2]),
159    /// Quadratic bezier to (control, end).
160    QuadTo([f32; 2], [f32; 2]),
161    /// Close the path.
162    Close,
163}
164
165/// An image/raster data mark.
166#[derive(Clone, Debug)]
167pub struct ImageMark {
168    /// Position and size.
169    pub bounds: BoundingBox,
170    /// RGBA pixel data.
171    pub data: Vec<u8>,
172    /// Width in pixels.
173    pub width: u32,
174    /// Height in pixels.
175    pub height: u32,
176}
177
178/// A single visual mark (one of 9 primitives).
179pub enum Mark {
180    /// Polyline.
181    Line(LineMark),
182    /// Rectangle.
183    Rect(RectMark),
184    /// Scatter point.
185    Point(PointMark),
186    /// Filled area.
187    Area(AreaMark),
188    /// Text label.
189    Text(TextMark),
190    /// Arc/sector.
191    Arc(ArcMark),
192    /// Grid/reference lines.
193    Rule(RuleMark),
194    /// Bezier path.
195    Path(PathMark),
196    /// Raster image.
197    Image(ImageMark),
198}
199
200/// Uniform or per-instance attribute.
201#[derive(Clone, Debug)]
202pub enum BatchAttr<T> {
203    /// One value shared by all instances.
204    Uniform(T),
205    /// Per-instance data.
206    Varying(Vec<T>),
207}
208
209impl<T: Clone> BatchAttr<T> {
210    /// Get the value for a given instance index.
211    pub fn get(&self, index: usize) -> &T {
212        match self {
213            Self::Uniform(v) => v,
214            Self::Varying(v) => {
215                debug_assert!(
216                    index < v.len(),
217                    "BatchAttr index {index} out of bounds (len {})",
218                    v.len()
219                );
220                &v[index]
221            }
222        }
223    }
224
225    /// Number of instances (1 for uniform, N for varying).
226    pub fn len(&self) -> usize {
227        match self {
228            Self::Uniform(_) => 1,
229            Self::Varying(v) => v.len(),
230        }
231    }
232
233    /// Whether this is empty.
234    pub fn is_empty(&self) -> bool {
235        match self {
236            Self::Uniform(_) => false,
237            Self::Varying(v) => v.is_empty(),
238        }
239    }
240}
241
242/// A batch of homogeneous marks for instanced rendering.
243pub enum MarkBatch {
244    /// Batch of scatter points.
245    Points {
246        /// Center positions.
247        positions: Vec<[f32; 2]>,
248        /// Per-point sizes.
249        sizes: BatchAttr<f32>,
250        /// Per-point fill styles.
251        fills: BatchAttr<FillStyle>,
252        /// Marker shape (uniform for whole batch).
253        shape: MarkerShape,
254        /// Per-point stroke styles.
255        strokes: BatchAttr<StrokeStyle>,
256    },
257    /// Batch of rule segments.
258    Rules {
259        /// Line segments.
260        segments: Vec<([f32; 2], [f32; 2])>,
261        /// Line style.
262        stroke: StrokeStyle,
263    },
264    /// Batch of rectangles.
265    Rects {
266        /// Rectangle bounds.
267        rects: Vec<BoundingBox>,
268        /// Per-rect fills.
269        fills: BatchAttr<FillStyle>,
270        /// Per-rect strokes.
271        strokes: BatchAttr<StrokeStyle>,
272        /// Corner radius.
273        corner_radius: f32,
274    },
275}
276
277impl MarkBatch {
278    /// Create a validated Points batch.
279    pub fn points(
280        positions: Vec<[f32; 2]>,
281        sizes: BatchAttr<f32>,
282        fills: BatchAttr<FillStyle>,
283        shape: MarkerShape,
284        strokes: BatchAttr<StrokeStyle>,
285    ) -> Result<Self, String> {
286        let batch = Self::Points {
287            positions,
288            sizes,
289            fills,
290            shape,
291            strokes,
292        };
293        batch.validate()?;
294        Ok(batch)
295    }
296
297    /// Create a validated Rects batch.
298    pub fn rects(
299        rects: Vec<BoundingBox>,
300        fills: BatchAttr<FillStyle>,
301        strokes: BatchAttr<StrokeStyle>,
302        corner_radius: f32,
303    ) -> Result<Self, String> {
304        let batch = Self::Rects {
305            rects,
306            fills,
307            strokes,
308            corner_radius,
309        };
310        batch.validate()?;
311        Ok(batch)
312    }
313
314    /// Validate that all Varying attributes match the batch size.
315    pub fn validate(&self) -> Result<(), String> {
316        match self {
317            Self::Points {
318                positions,
319                sizes,
320                fills,
321                strokes,
322                ..
323            } => {
324                let n = positions.len();
325                check_varying_len("sizes", sizes, n)?;
326                check_varying_len("fills", fills, n)?;
327                check_varying_len("strokes", strokes, n)?;
328            }
329            Self::Rects {
330                rects,
331                fills,
332                strokes,
333                ..
334            } => {
335                let n = rects.len();
336                check_varying_len("fills", fills, n)?;
337                check_varying_len("strokes", strokes, n)?;
338            }
339            Self::Rules { .. } => {}
340        }
341        Ok(())
342    }
343}
344
345fn check_varying_len<T: Clone>(
346    name: &str,
347    attr: &BatchAttr<T>,
348    expected: usize,
349) -> Result<(), String> {
350    if let BatchAttr::Varying(v) = attr {
351        if v.len() != expected {
352            return Err(format!(
353                "BatchAttr '{name}' has {} elements, expected {expected}",
354                v.len()
355            ));
356        }
357    }
358    Ok(())
359}