flow_plots/scatter_data.rs
1//! Scatter plot data types supporting simple, gate-colored, and z-axis colored plots.
2
3/// Scatter plot data with optional per-point metadata for overlay and continuous coloring.
4///
5/// Use [`From`] to convert from `Vec<(f32, f32)>` for simple plots.
6///
7/// # Examples
8///
9/// Simple scatter (solid or density):
10/// ```
11/// # use flow_plots::scatter_data::ScatterPlotData;
12/// let data: ScatterPlotData = vec![(1.0, 2.0), (3.0, 4.0)].into();
13/// ```
14///
15/// Scatter with discrete gate colors:
16/// ```
17/// # use flow_plots::scatter_data::ScatterPlotData;
18/// let points = vec![(1.0, 2.0), (3.0, 4.0), (5.0, 6.0)];
19/// let gate_ids = vec![0, 1, 0]; // gate index per point
20/// let data = ScatterPlotData::with_gates(points, gate_ids);
21/// ```
22///
23/// Scatter colored by continuous z-axis:
24/// ```
25/// # use flow_plots::scatter_data::ScatterPlotData;
26/// let points = vec![(1.0, 2.0), (3.0, 4.0)];
27/// let z_values = vec![0.5, 1.0];
28/// let data = ScatterPlotData::with_z(points, z_values);
29/// ```
30#[derive(Clone, Debug)]
31pub struct ScatterPlotData {
32 /// (x, y) coordinate pairs
33 pub points: Vec<(f32, f32)>,
34 /// Gate index per point (for discrete overlay coloring). Indexes into `gate_colors` in options.
35 pub gate_ids: Option<Vec<u32>>,
36 /// Z-axis value per point (for continuous colormap coloring)
37 pub z_values: Option<Vec<f32>>,
38}
39
40impl ScatterPlotData {
41 /// Create simple scatter data (no gates, no z-axis).
42 pub fn new(points: Vec<(f32, f32)>) -> Self {
43 Self {
44 points,
45 gate_ids: None,
46 z_values: None,
47 }
48 }
49
50 /// Create scatter data with discrete gate IDs for overlay coloring.
51 ///
52 /// # Errors
53 /// Returns `Err` if `gate_ids.len() != points.len()`.
54 pub fn with_gates(
55 points: Vec<(f32, f32)>,
56 gate_ids: Vec<u32>,
57 ) -> Result<Self, ScatterDataError> {
58 if gate_ids.len() != points.len() {
59 return Err(ScatterDataError {
60 points_len: points.len(),
61 metadata_len: gate_ids.len(),
62 field: "gate_ids",
63 });
64 }
65 Ok(Self {
66 points,
67 gate_ids: Some(gate_ids),
68 z_values: None,
69 })
70 }
71
72 /// Create scatter data with z-axis values for continuous coloring.
73 ///
74 /// # Errors
75 /// Returns `Err` if `z_values.len() != points.len()`.
76 pub fn with_z(points: Vec<(f32, f32)>, z_values: Vec<f32>) -> Result<Self, ScatterDataError> {
77 if z_values.len() != points.len() {
78 return Err(ScatterDataError {
79 points_len: points.len(),
80 metadata_len: z_values.len(),
81 field: "z_values",
82 });
83 }
84 Ok(Self {
85 points,
86 gate_ids: None,
87 z_values: Some(z_values),
88 })
89 }
90
91 /// Slice of (x, y) points.
92 pub fn xy(&self) -> &[(f32, f32)] {
93 &self.points
94 }
95
96 /// Whether this data has gate overlay info.
97 pub fn has_gates(&self) -> bool {
98 self.gate_ids.is_some()
99 }
100
101 /// Whether this data has z-axis values for continuous coloring.
102 pub fn has_z(&self) -> bool {
103 self.z_values.is_some()
104 }
105}
106
107impl From<Vec<(f32, f32)>> for ScatterPlotData {
108 fn from(points: Vec<(f32, f32)>) -> Self {
109 Self::new(points)
110 }
111}
112
113/// Error when constructing scatter plot data with invalid metadata lengths.
114#[derive(Debug, Clone)]
115pub struct ScatterDataError {
116 points_len: usize,
117 metadata_len: usize,
118 field: &'static str,
119}
120
121impl std::fmt::Display for ScatterDataError {
122 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
123 write!(
124 f,
125 "length mismatch: points has {} elements but {} has {}",
126 self.points_len, self.field, self.metadata_len
127 )
128 }
129}
130
131impl std::error::Error for ScatterDataError {}