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
14pub 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
40pub(crate) trait PlotHelper {
42 fn get_layout(&self) -> &Layout;
43 fn get_traces(&self) -> &Vec<Box<dyn Trace + 'static>>;
44
45 #[cfg(any(
46 feature = "static_export_chromedriver",
47 feature = "static_export_geckodriver",
48 feature = "static_export_default"
49 ))]
50 fn get_image_format(
51 &self,
52 extension: &str,
53 ) -> Result<ImageFormat, std::boxed::Box<(dyn std::error::Error + 'static)>> {
54 match extension {
55 "png" => Ok(ImageFormat::PNG),
56 "jpg" => Ok(ImageFormat::JPEG),
57 "jpeg" => Ok(ImageFormat::JPEG),
58 "webp" => Ok(ImageFormat::WEBP),
59 "svg" => Ok(ImageFormat::SVG),
60 _ => Err(format!("Unsupported image format: {extension}").into()),
61 }
62 }
63}
64
65impl<T> Plot for T
67where
68 T: PlotHelper + Serialize + Clone,
69{
70 fn plot(&self) {
71 let mut plot = Plotly::new();
72 plot.set_layout(self.get_layout().to_owned());
73 plot.add_traces(self.get_traces().to_owned());
74
75 match env::var("EVCXR_IS_RUNTIME") {
76 Ok(_) => plot.notebook_display(),
77 _ => plot.show(),
78 }
79 }
80
81 fn write_html(&self, path: impl Into<String>) {
82 let mut plot = Plotly::new();
83 plot.set_layout(self.get_layout().to_owned());
84 plot.add_traces(self.get_traces().to_owned());
85 plot.write_html(path.into());
86 }
87
88 fn to_json(&self) -> Result<String, serde_json::Error> {
89 serde_json::to_string(self)
90 }
91
92 fn to_html(&self) -> String {
93 let mut plot = Plotly::new();
94 plot.set_layout(self.get_layout().to_owned());
95 plot.add_traces(self.get_traces().to_owned());
96 plot.to_html()
97 }
98
99 fn to_inline_html(&self, plot_div_id: Option<&str>) -> String {
100 let mut plot = Plotly::new();
101 plot.set_layout(self.get_layout().to_owned());
102 plot.add_traces(self.get_traces().to_owned());
103 plot.to_inline_html(plot_div_id)
104 }
105
106 #[cfg(any(
107 feature = "static_export_chromedriver",
108 feature = "static_export_geckodriver",
109 feature = "static_export_default"
110 ))]
111 fn write_image(
112 &self,
113 path: impl Into<String>,
114 width: usize,
115 height: usize,
116 scale: f64,
117 ) -> Result<(), std::boxed::Box<(dyn std::error::Error + 'static)>> {
118 let mut plot = Plotly::new();
119 plot.set_layout(self.get_layout().to_owned());
120 plot.add_traces(self.get_traces().to_owned());
121
122 if let Some((filename, extension)) = path.into().rsplit_once('.') {
123 let format = self.get_image_format(extension)?;
124 plot.write_image(filename, format, width, height, scale)?;
125 } else {
126 Err("No extension provided for image.")?;
127 }
128
129 Ok(())
130 }
131}