use nalgebra::{DMatrix, DVector};
use textplots::{Chart, Plot, Shape};
pub fn plots(arg: String, values: Vec<String>, t_result: DVector<f64>, y_result: DMatrix<f64>) {
use plotters::prelude::*;
let x = t_result;
let y = y_result;
let x_min = x.min();
let x_max = x.max();
for col in 0..y.ncols() {
let y_col = y.column(col);
let y_min = y_col.min();
let y_max = y_col.max();
let varname = values[col].clone();
let filename = format!("{}.png", varname);
let root_area = BitMapBackend::new(&filename, (800, 600)).into_drawing_area();
root_area.fill(&WHITE).unwrap();
let mut chart = ChartBuilder::on(&root_area)
.caption(format!("{}", varname), ("sans-serif", 50))
.margin(10)
.x_label_area_size(30)
.y_label_area_size(30)
.build_cartesian_2d(x_min * 0.95..x_max * 1.05, y_min * 0.95..y_max * 1.05)
.unwrap();
chart
.configure_mesh()
.x_desc(&arg)
.y_desc(&varname)
.draw()
.unwrap();
let series: Vec<(f64, f64)> = x.iter().zip(y_col.iter()).map(|(&x, &y)| (x, y)).collect();
chart
.draw_series(LineSeries::new(series, &Palette99::pick(col)))
.unwrap()
.label(format!(" {}", varname))
.legend(move |(x, y)| {
PathElement::new(vec![(x, y), (x + 20, y)], &Palette99::pick(col))
});
chart
.configure_series_labels()
.background_style(&WHITE.mix(0.8))
.border_style(&BLACK)
.draw()
.unwrap();
root_area.present().unwrap();
}
}
use gnuplot::{AxesCommon, Caption, Color, Figure};
pub fn plots_gnulot(
arg: String,
values: Vec<String>,
t_result: DVector<f64>,
y_result: DMatrix<f64>,
) {
let x = t_result;
for col in 0..y_result.ncols() {
let mut fg = Figure::new();
let y_col: Vec<f64> = y_result.column(col).iter().copied().collect();
let varname = &values[col];
fg.axes2d()
.set_title(&varname, &[])
.set_x_label(&arg, &[])
.set_y_label(&varname, &[])
.lines(
x.as_slice(),
&y_col,
&[Caption(&varname), Color(gnuplot::ColorType::Black)],
);
let filename = format!("{}.png", varname);
fg.save_to_png(&filename, 800, 600).unwrap();
}
}
pub fn plots_terminal(
arg: String,
values: Vec<String>,
t_result: DVector<f64>,
y_result: DMatrix<f64>,
) {
let x = &t_result;
let x_min = x.min();
let x_max = x.max();
for col in 0..y_result.ncols() {
let y_col = y_result.column(col);
let y_min = y_col.min();
let y_max = y_col.max();
let varname = &values[col];
let points: Vec<(f32, f32)> = x
.iter()
.zip(y_col.iter())
.map(|(&x_val, &y_val)| (x_val as f32, y_val as f32))
.collect();
println!(
"\n{} = {} vs {} (range: {:.3} to {:.3})",
varname, varname, arg, y_min, y_max
);
println!("{}", "=".repeat(60));
Chart::new(120, 30, x_min as f32, x_max as f32)
.lineplot(&Shape::Lines(&points))
.display();
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::f64::consts::PI;
use std::path::{Path, PathBuf};
struct CurrentDirGuard {
original: PathBuf,
}
impl CurrentDirGuard {
fn change_to(path: &Path) -> Self {
let original = std::env::current_dir().expect("current dir should be available");
std::env::set_current_dir(path).expect("current dir should switch to tempdir");
Self { original }
}
}
impl Drop for CurrentDirGuard {
fn drop(&mut self) {
let _ = std::env::set_current_dir(&self.original);
}
}
#[test]
fn test_plots_terminal() {
let n_points = 50;
let t: Vec<f64> = (0..n_points)
.map(|i| i as f64 * 2.0 * PI / (n_points - 1) as f64)
.collect();
let t_result = DVector::from_vec(t.clone());
let sin_values: Vec<f64> = t.iter().map(|&x| x.sin()).collect();
let cos_values: Vec<f64> = t.iter().map(|&x| x.cos()).collect();
let mut y_matrix = DMatrix::zeros(n_points, 2);
for i in 0..n_points {
y_matrix[(i, 0)] = sin_values[i];
y_matrix[(i, 1)] = cos_values[i];
}
let arg = "t".to_string();
let values = vec!["sin(t)".to_string(), "cos(t)".to_string()];
println!("Testing terminal plots with sine and cosine functions:");
plots_terminal(arg, values, t_result, y_matrix);
}
#[test]
fn legacy_plotters_plots_writes_one_png_per_series() {
let dir = tempfile::tempdir().expect("tempdir should be created");
let _guard = CurrentDirGuard::change_to(dir.path());
let n_points = 50;
let t: Vec<f64> = (0..n_points)
.map(|i| i as f64 * 2.0 * PI / (n_points - 1) as f64)
.collect();
let t_result = DVector::from_vec(t.clone());
let mut y_matrix = DMatrix::zeros(n_points, 2);
for (i, x) in t.iter().enumerate() {
y_matrix[(i, 0)] = x.sin();
y_matrix[(i, 1)] = x.cos();
}
plots(
"t".to_string(),
vec!["sin".to_string(), "cos".to_string()],
t_result,
y_matrix,
);
for filename in ["sin.png", "cos.png"] {
let path = dir.path().join(filename);
assert!(path.exists(), "{filename} should be created by plots()");
let metadata = std::fs::metadata(&path).expect("plot file metadata should be readable");
assert!(
metadata.len() > 0,
"{filename} should not be an empty PNG file"
);
}
}
}