plotlars/common/
plot.rs

1use std::env;
2
3use plotly::{Layout, Plot as Plotly, Trace};
4
5#[cfg(any(
6    feature = "static_export_chromedriver",
7    feature = "static_export_geckodriver",
8    feature = "static_export_default"
9))]
10use plotly_static::ImageFormat;
11
12use serde::Serialize;
13
14/// A trait representing a generic plot that can be displayed or rendered.
15pub trait Plot {
16    fn plot(&self);
17
18    fn write_html(&self, path: impl Into<String>);
19
20    fn to_json(&self) -> Result<String, serde_json::Error>;
21
22    fn to_html(&self) -> String;
23
24    fn to_inline_html(&self, plot_div_id: Option<&str>) -> String;
25
26    #[cfg(any(
27        feature = "static_export_chromedriver",
28        feature = "static_export_geckodriver",
29        feature = "static_export_default"
30    ))]
31    fn write_image(
32        &self,
33        path: impl Into<String>,
34        width: usize,
35        height: usize,
36        scale: f64,
37    ) -> Result<(), std::boxed::Box<(dyn std::error::Error + 'static)>>;
38}
39
40/// Helper trait for internal use by the `Plot` trait implementation.
41/// Can be used to get the underlying layout and traces of a plot (for example, to create a subplot).
42pub trait PlotHelper {
43    fn get_layout(&self) -> &Layout;
44    fn get_traces(&self) -> &Vec<Box<dyn Trace + 'static>>;
45
46    #[cfg(any(
47        feature = "static_export_chromedriver",
48        feature = "static_export_geckodriver",
49        feature = "static_export_default"
50    ))]
51    fn get_image_format(
52        &self,
53        extension: &str,
54    ) -> Result<ImageFormat, std::boxed::Box<(dyn std::error::Error + 'static)>> {
55        match extension {
56            "png" => Ok(ImageFormat::PNG),
57            "jpg" => Ok(ImageFormat::JPEG),
58            "jpeg" => Ok(ImageFormat::JPEG),
59            "webp" => Ok(ImageFormat::WEBP),
60            "svg" => Ok(ImageFormat::SVG),
61            _ => Err(format!("Unsupported image format: {extension}").into()),
62        }
63    }
64}
65
66// Implement the public trait `Plot` for any type that implements `PlotHelper`.
67impl<T> Plot for T
68where
69    T: PlotHelper + Serialize + Clone,
70{
71    fn plot(&self) {
72        let mut plot = Plotly::new();
73        plot.set_layout(self.get_layout().to_owned());
74        plot.add_traces(self.get_traces().to_owned());
75
76        match env::var("EVCXR_IS_RUNTIME") {
77            Ok(_) => plot.notebook_display(),
78            _ => plot.show(),
79        }
80    }
81
82    fn write_html(&self, path: impl Into<String>) {
83        let mut plot = Plotly::new();
84        plot.set_layout(self.get_layout().to_owned());
85        plot.add_traces(self.get_traces().to_owned());
86        plot.write_html(path.into());
87    }
88
89    fn to_json(&self) -> Result<String, serde_json::Error> {
90        serde_json::to_string(self)
91    }
92
93    fn to_html(&self) -> String {
94        let mut plot = Plotly::new();
95        plot.set_layout(self.get_layout().to_owned());
96        plot.add_traces(self.get_traces().to_owned());
97        plot.to_html()
98    }
99
100    fn to_inline_html(&self, plot_div_id: Option<&str>) -> String {
101        let mut plot = Plotly::new();
102        plot.set_layout(self.get_layout().to_owned());
103        plot.add_traces(self.get_traces().to_owned());
104        plot.to_inline_html(plot_div_id)
105    }
106
107    #[cfg(any(
108        feature = "static_export_chromedriver",
109        feature = "static_export_geckodriver",
110        feature = "static_export_default"
111    ))]
112    fn write_image(
113        &self,
114        path: impl Into<String>,
115        width: usize,
116        height: usize,
117        scale: f64,
118    ) -> Result<(), std::boxed::Box<(dyn std::error::Error + 'static)>> {
119        let mut plot = Plotly::new();
120        plot.set_layout(self.get_layout().to_owned());
121        plot.add_traces(self.get_traces().to_owned());
122
123        if let Some((filename, extension)) = path.into().rsplit_once('.') {
124            let format = self.get_image_format(extension)?;
125            plot.write_image(filename, format, width, height, scale)?;
126        } else {
127            Err("No extension provided for image.")?;
128        }
129
130        Ok(())
131    }
132}