1pub mod bar;
7pub mod bar_groups;
8pub mod digital;
9pub mod dummy;
10pub mod error_bars;
11pub mod heatmap;
12pub mod histogram;
13pub mod image;
14pub mod inf_lines;
15pub mod line;
16pub mod pie;
17pub mod scatter;
18pub mod shaded;
19pub mod stairs;
20pub mod stems;
21pub mod text;
22
23pub use bar::*;
25pub use bar_groups::*;
26pub use digital::*;
27pub use dummy::*;
28pub use error_bars::*;
29pub use heatmap::*;
30pub use histogram::*;
31pub use image::*;
32pub use inf_lines::*;
33pub use line::*;
34pub use pie::*;
35pub use scatter::*;
36pub use shaded::*;
37pub use stairs::*;
38pub use stems::*;
39pub use text::*;
40
41pub trait Plot {
43 fn plot(&self);
45
46 fn label(&self) -> &str;
48}
49
50pub trait PlotData {
52 fn label(&self) -> &str;
54
55 fn data_len(&self) -> usize;
57
58 fn is_empty(&self) -> bool {
60 self.data_len() == 0
61 }
62
63 fn validate(&self) -> Result<(), PlotError> {
65 if self.is_empty() {
66 Err(PlotError::EmptyData)
67 } else {
68 Ok(())
69 }
70 }
71}
72
73#[derive(Debug, Clone, PartialEq)]
75pub enum PlotError {
76 DataLengthMismatch { x_len: usize, y_len: usize },
78 EmptyData,
80 InvalidData(String),
82 StringConversion(String),
84 PlotCreationFailed(String),
86}
87
88impl std::fmt::Display for PlotError {
89 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90 match self {
91 PlotError::DataLengthMismatch { x_len, y_len } => {
92 write!(
93 f,
94 "Data length mismatch: x has {} elements, y has {} elements",
95 x_len, y_len
96 )
97 }
98 PlotError::EmptyData => write!(f, "Data is empty"),
99 PlotError::InvalidData(msg) => write!(f, "Invalid data: {}", msg),
100 PlotError::StringConversion(msg) => write!(f, "String conversion error: {}", msg),
101 PlotError::PlotCreationFailed(msg) => write!(f, "Plot creation failed: {}", msg),
102 }
103 }
104}
105
106impl std::error::Error for PlotError {}
107
108pub fn validate_data_lengths<T, U>(data1: &[T], data2: &[U]) -> Result<(), PlotError> {
113 if data1.is_empty() || data2.is_empty() {
114 return Err(PlotError::EmptyData);
115 }
116
117 if data1.len() != data2.len() {
118 return Err(PlotError::DataLengthMismatch {
119 x_len: data1.len(),
120 y_len: data2.len(),
121 });
122 }
123
124 Ok(())
125}
126
127pub fn safe_cstring(s: &str) -> std::ffi::CString {
130 let cleaned = s.replace('\0', "");
132 std::ffi::CString::new(cleaned).unwrap_or_else(|_| {
133 std::ffi::CString::new("").unwrap()
135 })
136}
137
138pub struct PlotBuilder<'a> {
140 plot_type: PlotType<'a>,
141}
142
143pub enum PlotType<'a> {
145 Line {
146 label: &'a str,
147 x_data: &'a [f64],
148 y_data: &'a [f64],
149 },
150 Scatter {
151 label: &'a str,
152 x_data: &'a [f64],
153 y_data: &'a [f64],
154 },
155 Bar {
156 label: &'a str,
157 values: &'a [f64],
158 width: f64,
159 },
160 Histogram {
161 label: &'a str,
162 values: &'a [f64],
163 bins: i32,
164 },
165 Heatmap {
166 label: &'a str,
167 values: &'a [f64],
168 rows: usize,
169 cols: usize,
170 },
171 PieChart {
172 labels: Vec<&'a str>,
173 values: &'a [f64],
174 center: (f64, f64),
175 radius: f64,
176 },
177}
178
179impl<'a> PlotBuilder<'a> {
180 pub fn line(label: &'a str, x_data: &'a [f64], y_data: &'a [f64]) -> Self {
182 Self {
183 plot_type: PlotType::Line {
184 label,
185 x_data,
186 y_data,
187 },
188 }
189 }
190
191 pub fn scatter(label: &'a str, x_data: &'a [f64], y_data: &'a [f64]) -> Self {
193 Self {
194 plot_type: PlotType::Scatter {
195 label,
196 x_data,
197 y_data,
198 },
199 }
200 }
201
202 pub fn bar(label: &'a str, values: &'a [f64]) -> Self {
204 Self {
205 plot_type: PlotType::Bar {
206 label,
207 values,
208 width: 0.67,
209 },
210 }
211 }
212
213 pub fn histogram(label: &'a str, values: &'a [f64]) -> Self {
215 Self {
216 plot_type: PlotType::Histogram {
217 label,
218 values,
219 bins: crate::BinMethod::Sturges as i32,
220 },
221 }
222 }
223
224 pub fn heatmap(label: &'a str, values: &'a [f64], rows: usize, cols: usize) -> Self {
226 Self {
227 plot_type: PlotType::Heatmap {
228 label,
229 values,
230 rows,
231 cols,
232 },
233 }
234 }
235
236 pub fn pie_chart(
238 labels: Vec<&'a str>,
239 values: &'a [f64],
240 center: (f64, f64),
241 radius: f64,
242 ) -> Self {
243 Self {
244 plot_type: PlotType::PieChart {
245 labels,
246 values,
247 center,
248 radius,
249 },
250 }
251 }
252
253 pub fn build(self) -> Result<(), PlotError> {
255 match self.plot_type {
256 PlotType::Line {
257 label,
258 x_data,
259 y_data,
260 } => {
261 let plot = line::LinePlot::new(label, x_data, y_data);
262 plot.validate()?;
263 plot.plot();
264 }
265 PlotType::Scatter {
266 label,
267 x_data,
268 y_data,
269 } => {
270 let plot = scatter::ScatterPlot::new(label, x_data, y_data);
271 plot.validate()?;
272 plot.plot();
273 }
274 PlotType::Bar {
275 label,
276 values,
277 width,
278 } => {
279 let plot = bar::BarPlot::new(label, values).with_bar_size(width);
280 plot.validate()?;
281 plot.plot();
282 }
283 PlotType::Histogram {
284 label,
285 values,
286 bins,
287 } => {
288 let plot = histogram::HistogramPlot::new(label, values).with_bins(bins);
289 plot.validate()?;
290 plot.plot();
291 }
292 PlotType::Heatmap {
293 label,
294 values,
295 rows,
296 cols,
297 } => {
298 let plot = heatmap::HeatmapPlot::new(label, values, rows, cols);
299 plot.validate()?;
300 plot.plot();
301 }
302 PlotType::PieChart {
303 labels,
304 values,
305 center,
306 radius,
307 } => {
308 let plot = pie::PieChartPlot::new(labels, values, center.0, center.1, radius);
309 plot.validate()?;
310 plot.plot();
311 }
312 }
313 Ok(())
314 }
315}