use bon::bon;
use plotly::{layout::Layout as LayoutPlotly, Trace};
use serde::ser::{Serialize, SerializeStruct, Serializer};
use serde_json::Value;
use plotlars_core::components::{Dimensions, Text};
use plotlars_core::Plot;
mod custom_legend;
mod irregular;
mod regular;
mod shared;
#[derive(Clone)]
pub struct SubplotGrid {
traces: Vec<Box<dyn Trace + 'static>>,
layout: LayoutPlotly,
layout_json: Option<Value>,
}
impl Serialize for SubplotGrid {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut state = serializer.serialize_struct("SubplotGrid", 2)?;
state.serialize_field("traces", &self.traces)?;
if let Some(ref layout_json) = self.layout_json {
state.serialize_field("layout", layout_json)?;
} else {
state.serialize_field("layout", &self.layout)?;
}
state.end()
}
}
#[bon]
impl SubplotGrid {
#[builder(on(String, into), on(Text, into), finish_fn = build)]
pub fn regular(
plots: Vec<&dyn Plot>,
rows: Option<usize>,
cols: Option<usize>,
title: Option<Text>,
h_gap: Option<f64>,
v_gap: Option<f64>,
dimensions: Option<&Dimensions>,
) -> Self {
regular::build_regular(plots, rows, cols, title, h_gap, v_gap, None, dimensions)
}
#[builder(on(String, into), on(Text, into), finish_fn = build)]
pub fn irregular(
plots: Vec<(&dyn Plot, usize, usize, usize, usize)>,
rows: Option<usize>,
cols: Option<usize>,
title: Option<Text>,
h_gap: Option<f64>,
v_gap: Option<f64>,
dimensions: Option<&Dimensions>,
) -> Self {
irregular::build_irregular(plots, rows, cols, title, h_gap, v_gap, dimensions)
}
}
impl SubplotGrid {
pub fn plot(&self) {
use std::env;
match env::var("EVCXR_IS_RUNTIME") {
Ok(_) => {
let mut plotly_plot = plotly::Plot::new();
plotly_plot.set_layout(self.layout.clone());
for trace in self.traces.clone() {
plotly_plot.add_trace(trace);
}
plotly_plot.evcxr_display();
}
_ => {
let html = self.to_html();
let dir = std::env::temp_dir();
let path = dir.join("plotlars_subplot_grid.html");
std::fs::write(&path, &html).expect("Failed to write HTML file");
crate::render::open_html_file(&path);
}
}
}
pub fn write_html(&self, path: impl Into<String>) {
let html = self.to_html();
std::fs::write(path.into(), html).expect("Failed to write HTML file");
}
pub fn to_json(&self) -> Result<String, serde_json::Error> {
let layout_val = self
.layout_json
.as_ref()
.cloned()
.unwrap_or_else(|| serde_json::to_value(&self.layout).unwrap_or(Value::Null));
let traces_json: Vec<Value> = self
.traces
.iter()
.map(|t| {
let s = t.to_json();
serde_json::from_str(&s).unwrap_or(Value::Null)
})
.collect();
let output = serde_json::json!({
"traces": traces_json,
"layout": layout_val,
});
serde_json::to_string(&output)
}
pub fn to_html(&self) -> String {
let plot_json = self.to_json().unwrap();
let escaped_json = plot_json
.replace('\\', "\\\\")
.replace('\'', "\\'")
.replace('\n', "\\n")
.replace('\r', "\\r");
format!(
r#"<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<script src="https://cdn.plot.ly/plotly-3.0.1.min.js"></script>
</head>
<body>
<div id="plotly-div" style="width:100%;height:100%;"></div>
<script type="text/javascript">
var plotData = JSON.parse('{}');
Plotly.newPlot('plotly-div', plotData.traces, plotData.layout, {{responsive: true}});
</script>
</body>
</html>"#,
escaped_json
)
}
pub fn to_inline_html(&self, plot_div_id: Option<&str>) -> String {
let div_id = plot_div_id.unwrap_or("plotly-div");
let plot_json = self.to_json().unwrap();
format!(
r#"<div>
<script src="https://cdn.plot.ly/plotly-3.0.1.min.js"></script>
<div id="{div_id}" class="plotly-graph-div" style="height:100%; width:100%;"></div>
<script type="text/javascript">
var plotData = {plot_json};
Plotly.newPlot("{div_id}", plotData.traces, plotData.layout, {{responsive: true}});
</script>
</div>"#,
div_id = div_id,
plot_json = plot_json
)
}
}