plotlars_plotly/subplot_grid/
mod.rs1use bon::bon;
2use plotly::{layout::Layout as LayoutPlotly, Trace};
3use serde::ser::{Serialize, SerializeStruct, Serializer};
4use serde_json::Value;
5
6use plotlars_core::components::{Dimensions, Text};
7use plotlars_core::Plot;
8
9mod custom_legend;
10mod irregular;
11mod regular;
12mod shared;
13
14#[derive(Clone)]
31pub struct SubplotGrid {
32 traces: Vec<Box<dyn Trace + 'static>>,
33 layout: LayoutPlotly,
34 layout_json: Option<Value>,
35}
36
37impl Serialize for SubplotGrid {
38 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
39 where
40 S: Serializer,
41 {
42 let mut state = serializer.serialize_struct("SubplotGrid", 2)?;
43 state.serialize_field("traces", &self.traces)?;
44
45 if let Some(ref layout_json) = self.layout_json {
46 state.serialize_field("layout", layout_json)?;
47 } else {
48 state.serialize_field("layout", &self.layout)?;
49 }
50
51 state.end()
52 }
53}
54
55#[bon]
56impl SubplotGrid {
57 #[builder(on(String, into), on(Text, into), finish_fn = build)]
74 pub fn regular(
75 plots: Vec<&dyn Plot>,
76 rows: Option<usize>,
77 cols: Option<usize>,
78 title: Option<Text>,
79 h_gap: Option<f64>,
80 v_gap: Option<f64>,
81 dimensions: Option<&Dimensions>,
82 ) -> Self {
83 regular::build_regular(plots, rows, cols, title, h_gap, v_gap, None, dimensions)
84 }
85
86 #[builder(on(String, into), on(Text, into), finish_fn = build)]
108 pub fn irregular(
109 plots: Vec<(&dyn Plot, usize, usize, usize, usize)>,
110 rows: Option<usize>,
111 cols: Option<usize>,
112 title: Option<Text>,
113 h_gap: Option<f64>,
114 v_gap: Option<f64>,
115 dimensions: Option<&Dimensions>,
116 ) -> Self {
117 irregular::build_irregular(plots, rows, cols, title, h_gap, v_gap, dimensions)
118 }
119}
120
121impl SubplotGrid {
125 pub fn plot(&self) {
127 use std::env;
128
129 match env::var("EVCXR_IS_RUNTIME") {
130 Ok(_) => {
131 let mut plotly_plot = plotly::Plot::new();
132 plotly_plot.set_layout(self.layout.clone());
133 for trace in self.traces.clone() {
134 plotly_plot.add_trace(trace);
135 }
136 plotly_plot.evcxr_display();
137 }
138 _ => {
139 let html = self.to_html();
140 let dir = std::env::temp_dir();
141 let path = dir.join("plotlars_subplot_grid.html");
142 std::fs::write(&path, &html).expect("Failed to write HTML file");
143 crate::render::open_html_file(&path);
144 }
145 }
146 }
147
148 pub fn write_html(&self, path: impl Into<String>) {
150 let html = self.to_html();
151 std::fs::write(path.into(), html).expect("Failed to write HTML file");
152 }
153
154 pub fn to_json(&self) -> Result<String, serde_json::Error> {
156 let layout_val = self
157 .layout_json
158 .as_ref()
159 .cloned()
160 .unwrap_or_else(|| serde_json::to_value(&self.layout).unwrap_or(Value::Null));
161 let traces_json: Vec<Value> = self
162 .traces
163 .iter()
164 .map(|t| {
165 let s = t.to_json();
166 serde_json::from_str(&s).unwrap_or(Value::Null)
167 })
168 .collect();
169 let output = serde_json::json!({
170 "traces": traces_json,
171 "layout": layout_val,
172 });
173 serde_json::to_string(&output)
174 }
175
176 pub fn to_html(&self) -> String {
178 let plot_json = self.to_json().unwrap();
179 let escaped_json = plot_json
180 .replace('\\', "\\\\")
181 .replace('\'', "\\'")
182 .replace('\n', "\\n")
183 .replace('\r', "\\r");
184 format!(
185 r#"<!DOCTYPE html>
186<html>
187<head>
188 <meta charset="utf-8" />
189 <script src="https://cdn.plot.ly/plotly-3.0.1.min.js"></script>
190</head>
191<body>
192 <div id="plotly-div" style="width:100%;height:100%;"></div>
193 <script type="text/javascript">
194 var plotData = JSON.parse('{}');
195 Plotly.newPlot('plotly-div', plotData.traces, plotData.layout, {{responsive: true}});
196 </script>
197</body>
198</html>"#,
199 escaped_json
200 )
201 }
202
203 pub fn to_inline_html(&self, plot_div_id: Option<&str>) -> String {
205 let div_id = plot_div_id.unwrap_or("plotly-div");
206 let plot_json = self.to_json().unwrap();
207 format!(
208 r#"<div>
209<script src="https://cdn.plot.ly/plotly-3.0.1.min.js"></script>
210<div id="{div_id}" class="plotly-graph-div" style="height:100%; width:100%;"></div>
211<script type="text/javascript">
212 var plotData = {plot_json};
213 Plotly.newPlot("{div_id}", plotData.traces, plotData.layout, {{responsive: true}});
214</script>
215</div>"#,
216 div_id = div_id,
217 plot_json = plot_json
218 )
219 }
220}