Skip to main content

plotlars_core/plots/
array2dplot.rs

1use bon::bon;
2
3use crate::{
4    components::{Axis, Text},
5    ir::layout::LayoutIR,
6    ir::trace::{Array2dPlotIR, TraceIR},
7};
8
9/// A structure representing a 2D array plot.
10///
11/// The `Array2dPlot` struct allows for visualizing 2D arrays of RGB color values as images or heatmaps.
12/// Each element in the 2D array corresponds to a pixel, with its color defined by an `[u8; 3]` RGB triplet.
13/// This struct supports customizable titles, axis labels, and axis configurations for better presentation.
14///
15/// # Backend Support
16///
17/// | Backend | Supported |
18/// |---------|-----------|
19/// | Plotly  | Yes       |
20/// | Plotters| --        |
21///
22/// # Arguments
23///
24/// * `data` - A 2D vector of RGB triplets (`&[Vec<[u8; 3]>]`) representing pixel colors for the plot.
25/// * `plot_title` - An optional `Text` struct specifying the title of the plot.
26/// * `x_title` - An optional `Text` struct specifying the title of the x-axis.
27/// * `y_title` - An optional `Text` struct specifying the title of the y-axis.
28/// * `x_axis` - An optional reference to an `Axis` struct for customizing the x-axis.
29/// * `y_axis` - An optional reference to an `Axis` struct for customizing the y-axis.
30///
31/// # Example
32///
33/// ## Basic 2D Array Plot
34///
35/// ```rust
36/// use plotlars::{Array2dPlot, Plot, Text};
37///
38/// let data = vec![
39///     vec![[255, 0, 0], [0, 255, 0], [0, 0, 255]],
40///     vec![[0, 0, 255], [255, 0, 0], [0, 255, 0]],
41///     vec![[0, 255, 0], [0, 0, 255], [255, 0, 0]],
42/// ];
43///
44/// Array2dPlot::builder()
45///     .data(&data)
46///     .plot_title(
47///         Text::from("Array2D Plot")
48///             .font("Arial")
49///             .size(18)
50///     )
51///     .build()
52///     .plot();
53/// ```
54///
55/// ![Example](https://imgur.com/LMrqAaT.png)
56#[allow(dead_code)]
57#[derive(Clone)]
58pub struct Array2dPlot {
59    traces: Vec<TraceIR>,
60    layout: LayoutIR,
61}
62
63#[bon]
64impl Array2dPlot {
65    #[builder(on(String, into), on(Text, into))]
66    pub fn new(
67        data: &[Vec<[u8; 3]>],
68        plot_title: Option<Text>,
69        x_title: Option<Text>,
70        y_title: Option<Text>,
71        x_axis: Option<&Axis>,
72        y_axis: Option<&Axis>,
73    ) -> Self {
74        let ir_trace = TraceIR::Array2dPlot(Array2dPlotIR {
75            data: data.to_vec(),
76        });
77        let traces = vec![ir_trace];
78
79        let layout = LayoutIR {
80            title: plot_title,
81            x_title,
82            y_title,
83            y2_title: None,
84            z_title: None,
85            legend_title: None,
86            legend: None,
87            dimensions: None,
88            bar_mode: None,
89            box_mode: None,
90            box_gap: None,
91            margin_bottom: None,
92            axes_2d: Some(crate::ir::layout::Axes2dIR {
93                x_axis: x_axis.cloned(),
94                y_axis: y_axis.cloned(),
95                y2_axis: None,
96            }),
97            scene_3d: None,
98            polar: None,
99            mapbox: None,
100            grid: None,
101            annotations: vec![],
102        };
103
104        Self { traces, layout }
105    }
106}
107
108#[bon]
109impl Array2dPlot {
110    #[builder(
111        start_fn = try_builder,
112        finish_fn = try_build,
113        builder_type = Array2dPlotTryBuilder,
114        on(String, into),
115        on(Text, into),
116    )]
117    pub fn try_new(
118        data: &[Vec<[u8; 3]>],
119        plot_title: Option<Text>,
120        x_title: Option<Text>,
121        y_title: Option<Text>,
122        x_axis: Option<&Axis>,
123        y_axis: Option<&Axis>,
124    ) -> Result<Self, crate::io::PlotlarsError> {
125        std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
126            Self::__orig_new(data, plot_title, x_title, y_title, x_axis, y_axis)
127        }))
128        .map_err(|panic| {
129            let msg = panic
130                .downcast_ref::<String>()
131                .cloned()
132                .or_else(|| panic.downcast_ref::<&str>().map(|s| s.to_string()))
133                .unwrap_or_else(|| "unknown error".to_string());
134            crate::io::PlotlarsError::PlotBuild { message: msg }
135        })
136    }
137}
138
139impl crate::Plot for Array2dPlot {
140    fn ir_traces(&self) -> &[TraceIR] {
141        &self.traces
142    }
143
144    fn ir_layout(&self) -> &LayoutIR {
145        &self.layout
146    }
147}
148
149#[cfg(test)]
150mod tests {
151    use super::*;
152    use crate::Plot;
153
154    fn pixel_data() -> Vec<Vec<[u8; 3]>> {
155        vec![
156            vec![[255, 0, 0], [0, 255, 0]],
157            vec![[0, 0, 255], [255, 255, 0]],
158        ]
159    }
160
161    #[test]
162    fn test_basic_one_trace() {
163        let data = pixel_data();
164        let plot = Array2dPlot::builder().data(&data).build();
165        assert_eq!(plot.ir_traces().len(), 1);
166    }
167
168    #[test]
169    fn test_trace_variant() {
170        let data = pixel_data();
171        let plot = Array2dPlot::builder().data(&data).build();
172        assert!(matches!(plot.ir_traces()[0], TraceIR::Array2dPlot(_)));
173    }
174
175    #[test]
176    fn test_layout_no_axes_by_default() {
177        let data = pixel_data();
178        let plot = Array2dPlot::builder().data(&data).build();
179        let axes = plot.ir_layout().axes_2d.as_ref().unwrap();
180        assert!(axes.x_axis.is_none());
181        assert!(axes.y_axis.is_none());
182    }
183}