use std::fs;
use std::path::Path;
use serde_json::json;
pub struct GreekSurface {
pub xs: Vec<f64>,
pub ys: Vec<f64>,
pub z: Vec<Vec<f64>>,
}
pub fn linspace(a: f64, b: f64, n: usize) -> Vec<f64> {
if n <= 1 {
return vec![a];
}
(0..n).map(|i| a + (b - a) * i as f64 / (n - 1) as f64).collect()
}
pub fn greek_surface(xs: &[f64], ys: &[f64], f: impl Fn(f64, f64) -> f64) -> GreekSurface {
let z = xs.iter().map(|&x| ys.iter().map(|&y| f(x, y)).collect()).collect();
GreekSurface { xs: xs.to_vec(), ys: ys.to_vec(), z }
}
pub struct Labels<'a> {
pub title: &'a str,
pub x: &'a str,
pub y: &'a str,
pub z: &'a str,
}
const PLOTLY_CDN: &str = "https://cdn.plot.ly/plotly-2.35.2.min.js";
pub fn save_surface_html(surface: &GreekSurface, path: &str, labels: &Labels) {
let nx = surface.xs.len();
let ny = surface.ys.len();
let z: Vec<Vec<f64>> =
(0..ny).map(|j| (0..nx).map(|i| surface.z[i][j]).collect()).collect();
let data = json!([{
"type": "surface",
"x": surface.xs,
"y": surface.ys,
"z": z,
"colorscale": "Viridis",
"colorbar": { "title": { "text": labels.z } },
"contours": { "z": {
"show": true,
"usecolormap": true,
"highlightcolor": "#ffffff",
"project": { "z": true }
}},
"hovertemplate":
format!("{}: %{{x:.3f}}<br>{}: %{{y:.3f}}<br>{}: %{{z:.5f}}<extra></extra>",
labels.x, labels.y, labels.z),
}]);
let layout = json!({
"title": { "text": labels.title },
"autosize": true,
"margin": { "l": 0, "r": 0, "t": 50, "b": 0 },
"scene": {
"xaxis": { "title": { "text": labels.x } },
"yaxis": { "title": { "text": labels.y } },
"zaxis": { "title": { "text": labels.z } },
"camera": { "eye": { "x": 1.7, "y": -1.7, "z": 0.9 } }
}
});
let html = format!(
"<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\"/>\n\
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"/>\n\
<title>{title}</title>\n\
<script src=\"{cdn}\" charset=\"utf-8\"></script>\n\
<style>html,body{{height:100%;margin:0}}#plot{{width:100vw;height:100vh}}</style>\n\
</head>\n<body>\n<div id=\"plot\"></div>\n<script>\n\
Plotly.newPlot('plot', {data}, {layout}, {{responsive:true}});\n\
</script>\n</body>\n</html>\n",
title = labels.title,
cdn = PLOTLY_CDN,
data = serde_json::to_string(&data).unwrap(),
layout = serde_json::to_string(&layout).unwrap(),
);
if let Some(parent) = Path::new(path).parent() {
let _ = fs::create_dir_all(parent);
}
fs::write(path, html).unwrap_or_else(|e| panic!("cannot write {path}: {e}"));
println!(" saved {path}");
}
pub struct LineSeries {
pub name: String,
pub xs: Vec<f64>,
pub ys: Vec<f64>,
}
#[allow(dead_code, clippy::too_many_arguments)]
pub fn save_lines_html(
series: &[LineSeries],
path: &str,
title: &str,
x_label: &str,
y_label: &str,
log_x: bool,
log_y: bool,
) {
let data: Vec<serde_json::Value> = series
.iter()
.map(|s| {
json!({
"type": "scatter",
"mode": "lines+markers",
"name": s.name,
"x": s.xs,
"y": s.ys,
})
})
.collect();
let layout = json!({
"title": { "text": title },
"xaxis": { "title": { "text": x_label }, "type": if log_x { "log" } else { "linear" } },
"yaxis": { "title": { "text": y_label }, "type": if log_y { "log" } else { "linear" } },
"legend": { "orientation": "h", "y": -0.2 },
"margin": { "t": 60 },
});
if let Some(parent) = std::path::Path::new(path).parent() {
let _ = std::fs::create_dir_all(parent);
}
let html = format!(
"<!DOCTYPE html>
<html>
<head>
<meta charset=\"utf-8\"/>
<script src=\"{cdn}\" charset=\"utf-8\"></script>
<title>{title}</title>
</head>
<body>
<div id=\"plot\" style=\"width:100%;height:92vh\"></div>
<script>
Plotly.newPlot('plot', {data}, {layout});
</script>
</body>
</html>
",
cdn = PLOTLY_CDN,
data = serde_json::Value::Array(data),
layout = layout,
);
std::fs::write(path, html).expect("write plot html");
}