Skip to main content

plotlars_core/plots/
image.rs

1use bon::bon;
2
3use crate::{
4    components::{Axis, Text},
5    ir::layout::LayoutIR,
6    ir::trace::{ImageIR, TraceIR},
7};
8
9/// A structure representing an image plot.
10///
11/// The `Image` struct allows for the integration of image data into plots, enabling visualization of raster data
12/// or standalone images within a plotting context. It supports customizable titles, axis labels, legend configuration,
13/// and layout adjustments for better presentation.
14///
15/// # Backend Support
16///
17/// | Backend | Supported |
18/// |---------|-----------|
19/// | Plotly  | Yes       |
20/// | Plotters| --        |
21///
22/// # Arguments
23///
24/// * `path` - A string slice specifying the file path of the image to be displayed.
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/// ```rust
34/// use plotlars::{Axis, Image, Plot};
35///
36/// let axis = Axis::new()
37///     .show_axis(false);
38///
39/// Image::builder()
40///     .path("data/image.png")
41///     .x_axis(&axis)
42///     .y_axis(&axis)
43///     .plot_title("Image Plot")
44///     .build()
45///     .plot();
46/// ```
47///
48/// ![Example](https://imgur.com/PAtdaHj.png)
49#[derive(Clone)]
50#[allow(dead_code)]
51pub struct Image {
52    traces: Vec<TraceIR>,
53    layout: LayoutIR,
54}
55
56#[bon]
57impl Image {
58    #[builder(on(String, into), on(Text, into))]
59    pub fn new(
60        path: &str,
61        plot_title: Option<Text>,
62        x_title: Option<Text>,
63        y_title: Option<Text>,
64        x_axis: Option<&Axis>,
65        y_axis: Option<&Axis>,
66    ) -> Self {
67        // Build IR
68        let ir_trace = Self::create_ir_trace(path);
69        let traces = vec![ir_trace];
70        let layout = LayoutIR {
71            title: plot_title.clone(),
72            x_title: x_title.clone(),
73            y_title: y_title.clone(),
74            y2_title: None,
75            z_title: None,
76            legend_title: None,
77            legend: None,
78            dimensions: None,
79            bar_mode: None,
80            box_mode: None,
81            box_gap: None,
82            margin_bottom: None,
83            axes_2d: Some(crate::ir::layout::Axes2dIR {
84                x_axis: x_axis.cloned(),
85                y_axis: y_axis.cloned(),
86                y2_axis: None,
87            }),
88            scene_3d: None,
89            polar: None,
90            mapbox: None,
91            grid: None,
92            annotations: vec![],
93        };
94
95        // Build plotly types from IR for backward compatibility
96        Self { traces, layout }
97    }
98}
99
100#[bon]
101impl Image {
102    #[builder(
103        start_fn = try_builder,
104        finish_fn = try_build,
105        builder_type = ImageTryBuilder,
106        on(String, into),
107        on(Text, into),
108    )]
109    pub fn try_new(
110        path: &str,
111        plot_title: Option<Text>,
112        x_title: Option<Text>,
113        y_title: Option<Text>,
114        x_axis: Option<&Axis>,
115        y_axis: Option<&Axis>,
116    ) -> Result<Self, crate::io::PlotlarsError> {
117        std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
118            Self::__orig_new(path, plot_title, x_title, y_title, x_axis, y_axis)
119        }))
120        .map_err(|panic| {
121            let msg = panic
122                .downcast_ref::<String>()
123                .cloned()
124                .or_else(|| panic.downcast_ref::<&str>().map(|s| s.to_string()))
125                .unwrap_or_else(|| "unknown error".to_string());
126            crate::io::PlotlarsError::PlotBuild { message: msg }
127        })
128    }
129}
130
131impl Image {
132    fn create_ir_trace(path: &str) -> TraceIR {
133        let im: image::ImageBuffer<image::Rgb<u8>, Vec<u8>> =
134            image::open(path).unwrap().into_rgb8();
135
136        let (width, height) = im.dimensions();
137        let mut pixels = vec![vec![[0u8; 3]; width as usize]; height as usize];
138
139        for (x, y, pixel) in im.enumerate_pixels() {
140            pixels[y as usize][x as usize] = [pixel[0], pixel[1], pixel[2]];
141        }
142
143        TraceIR::Image(ImageIR { pixels })
144    }
145}
146
147impl crate::Plot for Image {
148    fn ir_traces(&self) -> &[TraceIR] {
149        &self.traces
150    }
151
152    fn ir_layout(&self) -> &LayoutIR {
153        &self.layout
154    }
155}
156
157#[cfg(test)]
158mod tests {
159    use super::*;
160    use crate::Plot;
161
162    fn image_path() -> String {
163        let manifest = env!("CARGO_MANIFEST_DIR");
164        format!("{}/../../data/image.png", manifest)
165    }
166
167    #[test]
168    fn test_basic_one_trace() {
169        let plot = Image::builder().path(&image_path()).build();
170        assert_eq!(plot.ir_traces().len(), 1);
171    }
172
173    #[test]
174    fn test_trace_variant() {
175        let plot = Image::builder().path(&image_path()).build();
176        assert!(matches!(plot.ir_traces()[0], TraceIR::Image(_)));
177    }
178}