use crate::error_types::*;
use self::AxesVariant::*;
use crate::axes2d::*;
use crate::axes3d::*;
use crate::options::{GnuplotVersion, MultiplotFillDirection, MultiplotFillOrder};
use crate::util::escape;
use crate::writer::Writer;
use std::fs::File;
use std::io::{BufWriter, Write};
use std::path::{Path, PathBuf};
use std::process::{Child, Command, Stdio};
use std::str;
enum AxesVariant
{
Axes2DType(Axes2D),
Axes3DType(Axes3D),
NewPage,
}
impl AxesVariant
{
fn write_out(&self, writer: &mut dyn Writer, auto_layout: bool, version: GnuplotVersion)
{
match *self
{
Axes2DType(ref a) => a.write_out(writer, auto_layout, version),
Axes3DType(ref a) => a.write_out(writer, auto_layout, version),
NewPage =>
{
writeln!(writer, "unset multiplot");
writeln!(writer, "set multiplot");
}
}
}
fn reset_state(&self, writer: &mut dyn Writer)
{
match *self
{
Axes2DType(ref a) => a.reset_state(writer),
Axes3DType(ref a) => a.reset_state(writer),
_ => (),
}
}
}
struct MultiplotOptions
{
rows: usize,
columns: usize,
title: Option<String>,
scale_x: Option<f32>,
scale_y: Option<f32>,
offset_x: Option<f32>,
offset_y: Option<f32>,
fill_order: Option<MultiplotFillOrder>,
fill_direction: Option<MultiplotFillDirection>,
}
impl MultiplotOptions
{
pub fn new() -> MultiplotOptions
{
MultiplotOptions {
rows: 1,
columns: 1,
title: None,
scale_x: None,
scale_y: None,
offset_x: None,
offset_y: None,
fill_order: None,
fill_direction: None,
}
}
}
pub struct CloseSentinel
{
gnuplot: Child,
}
impl CloseSentinel
{
fn new(gnuplot: Child) -> Self
{
CloseSentinel { gnuplot }
}
pub fn wait(&mut self) -> std::io::Result<std::process::ExitStatus>
{
self.gnuplot.wait()
}
pub fn try_wait(&mut self) -> std::io::Result<Option<std::process::ExitStatus>>
{
self.gnuplot.try_wait()
}
}
impl Drop for CloseSentinel
{
fn drop(&mut self)
{
self.wait().unwrap();
}
}
pub struct Figure
{
axes: Vec<AxesVariant>,
terminal: String,
enhanced_text: bool,
output_file: Option<PathBuf>,
post_commands: String,
pre_commands: String,
gnuplot: Option<Child>,
version: Option<GnuplotVersion>,
multiplot_options: Option<MultiplotOptions>,
}
impl Default for GnuplotVersion
{
fn default() -> GnuplotVersion
{
GnuplotVersion { major: 5, minor: 0 }
}
}
impl Default for Figure
{
fn default() -> Self
{
Self::new()
}
}
impl Figure
{
pub fn new() -> Figure
{
Figure {
axes: Vec::new(),
terminal: "".into(),
enhanced_text: true,
output_file: None,
gnuplot: None,
post_commands: "".into(),
pre_commands: "".into(),
version: None,
multiplot_options: None,
}
}
pub fn set_terminal<'l>(&'l mut self, terminal: &str, output_file: &str) -> &'l mut Figure
{
self.terminal = terminal.into();
self.output_file = if output_file.is_empty()
{
None
}
else
{
Some(output_file.into())
};
self
}
pub fn set_enhanced_text(&mut self, enhanced: bool) -> &mut Figure
{
self.enhanced_text = enhanced;
self
}
pub fn set_post_commands(&mut self, post_commands: &str) -> &mut Figure
{
self.post_commands = post_commands.into();
self
}
pub fn set_pre_commands(&mut self, pre_commands: &str) -> &mut Figure
{
self.pre_commands = pre_commands.into();
self
}
pub fn set_gnuplot_version(&mut self, version: Option<GnuplotVersion>) -> &mut Figure
{
self.version = version;
self
}
pub fn get_gnuplot_version(&self) -> GnuplotVersion
{
self.version.unwrap_or_default()
}
pub fn set_multiplot_layout(&mut self, rows: usize, columns: usize) -> &mut Self
{
let multiplot_options = self
.multiplot_options
.get_or_insert(MultiplotOptions::new());
multiplot_options.rows = rows;
multiplot_options.columns = columns;
self
}
pub fn set_title(&mut self, title: &str) -> &mut Self
{
let multiplot_options = self
.multiplot_options
.get_or_insert(MultiplotOptions::new());
multiplot_options.title = Some(title.into());
self
}
pub fn set_scale(&mut self, scale_x: f32, scale_y: f32) -> &mut Self
{
let multiplot_options = self
.multiplot_options
.get_or_insert(MultiplotOptions::new());
multiplot_options.scale_x = Some(scale_x);
multiplot_options.scale_y = Some(scale_y);
self
}
pub fn set_offset(&mut self, offset_x: f32, offset_y: f32) -> &mut Self
{
let multiplot_options = self
.multiplot_options
.get_or_insert(MultiplotOptions::new());
multiplot_options.offset_x = Some(offset_x);
multiplot_options.offset_y = Some(offset_y);
self
}
pub fn set_multiplot_fill_order(
&mut self, order: MultiplotFillOrder, direction: MultiplotFillDirection,
) -> &mut Self
{
let multiplot_options = self
.multiplot_options
.get_or_insert(MultiplotOptions::new());
multiplot_options.fill_order = Some(order);
multiplot_options.fill_direction = Some(direction);
self
}
pub fn axes2d(&mut self) -> &mut Axes2D
{
self.axes.push(Axes2DType(Axes2D::new()));
let l = self.axes.len();
match self.axes[l - 1]
{
Axes2DType(ref mut a) => a,
_ => unreachable!(),
}
}
pub fn axes3d(&mut self) -> &mut Axes3D
{
self.axes.push(Axes3DType(Axes3D::new()));
let l = self.axes.len();
match self.axes[l - 1]
{
Axes3DType(ref mut a) => a,
_ => unreachable!(),
}
}
pub fn new_page(&mut self) -> &mut Figure
{
self.axes.push(NewPage);
self
}
pub fn show_and_keep_running(&mut self) -> Result<&mut Figure, GnuplotInitError>
{
if self.axes.is_empty()
{
return Ok(self);
}
if self.version.is_none()
{
let output = Command::new("gnuplot").arg("--version").output()?;
if let Ok(version_string) = str::from_utf8(&output.stdout)
{
let parts: Vec<_> = version_string.split(|c| c == ' ' || c == '.').collect();
if parts.len() > 2 && parts[0] == "gnuplot"
{
if let (Ok(major), Ok(minor)) =
(parts[1].parse::<i32>(), parts[2].parse::<i32>())
{
self.version = Some(GnuplotVersion { major, minor });
}
}
}
}
if self.gnuplot.is_none()
{
self.gnuplot = Some(
Command::new("gnuplot")
.arg("-p")
.stdin(Stdio::piped())
.spawn()
.expect(
"Couldn't spawn gnuplot. Make sure it is installed and available in PATH.",
),
);
}
{
let mut gnuplot = self.gnuplot.take();
if let Some(p) = gnuplot.as_mut()
{
let stdin = p.stdin.as_mut().expect("No stdin!?");
self.echo(stdin);
stdin.flush();
};
self.gnuplot = gnuplot;
}
Ok(self)
}
pub fn show(&mut self) -> Result<CloseSentinel, GnuplotInitError>
{
self.show_and_keep_running()?;
let mut gnuplot = self.gnuplot.take().expect("No gnuplot?");
{
let stdin = gnuplot.stdin.as_mut().expect("No stdin!?");
writeln!(stdin, "pause mouse close");
writeln!(stdin, "quit");
};
Ok(CloseSentinel::new(gnuplot))
}
pub fn save_to_png<P: AsRef<Path>>(
&mut self, filename: P, width_px: u32, height_px: u32,
) -> Result<(), GnuplotInitError>
{
let former_term = self.terminal.clone();
let former_output_file = self.output_file.clone();
self.terminal = format!("pngcairo size {},{}", width_px, height_px);
self.output_file = Some(filename.as_ref().into());
self.show()?;
self.terminal = former_term;
self.output_file = former_output_file;
Ok(())
}
pub fn save_to_svg<P: AsRef<Path>>(
&mut self, filename: P, width_px: u32, height_px: u32,
) -> Result<(), GnuplotInitError>
{
let former_term = self.terminal.clone();
let former_output_file = self.output_file.clone();
self.terminal = format!("svg size {},{}", width_px, height_px);
self.output_file = Some(filename.as_ref().into());
self.show()?;
self.terminal = former_term;
self.output_file = former_output_file;
Ok(())
}
pub fn save_to_pdf<P: AsRef<Path>>(
&mut self, filename: P, width_in: f32, height_in: f32,
) -> Result<(), GnuplotInitError>
{
let former_term = self.terminal.clone();
let former_output_file = self.output_file.clone();
self.terminal = format!("pdfcairo size {},{}", width_in, height_in);
self.output_file = Some(filename.as_ref().into());
self.show()?;
self.terminal = former_term;
self.output_file = former_output_file;
Ok(())
}
pub fn save_to_eps<P: AsRef<Path>>(
&mut self, filename: P, width_in: f32, height_in: f32,
) -> Result<(), GnuplotInitError>
{
let former_term = self.terminal.clone();
let former_output_file = self.output_file.clone();
self.terminal = format!("epscairo size {},{}", width_in, height_in);
self.output_file = Some(filename.as_ref().into());
self.show()?;
self.terminal = former_term;
self.output_file = former_output_file;
Ok(())
}
pub fn save_to_canvas<P: AsRef<Path>>(
&mut self, filename: P, width_px: u32, height_px: u32,
) -> Result<(), GnuplotInitError>
{
let former_term = self.terminal.clone();
let former_output_file = self.output_file.clone();
self.terminal = format!("canvas size {},{}", width_px, height_px);
self.output_file = Some(filename.as_ref().into());
self.show()?;
self.terminal = former_term;
self.output_file = former_output_file;
Ok(())
}
pub fn close(&mut self) -> &mut Figure
{
if self.gnuplot.is_none()
{
return self;
}
{
if let Some(p) = self.gnuplot.as_mut()
{
{
let stdin = p.stdin.as_mut().expect("No stdin!?");
writeln!(stdin, "quit");
}
p.wait();
};
self.gnuplot = None;
}
self
}
pub fn clear_axes(&mut self) -> &mut Figure
{
self.axes.clear();
self
}
pub fn echo<T: Writer>(&self, writer: &mut T) -> &Figure
{
let w = writer as &mut dyn Writer;
writeln!(w, "{}", &self.pre_commands);
if self.axes.is_empty()
{
return self;
}
writeln!(w, "set encoding utf8");
if !self.terminal.is_empty()
{
writeln!(w, "set terminal {}", self.terminal);
}
if let Some(ref output_file) = self.output_file
{
writeln!(
w,
"set output \"{}\"",
escape(output_file.to_str().unwrap())
);
}
writeln!(w, "set termoption dashed");
writeln!(
w,
"set termoption {}",
if self.enhanced_text
{
"enhanced"
}
else
{
"noenhanced"
}
);
let mut multiplot_options_string = "".to_string();
if let Some(m) = &self.multiplot_options
{
let fill_order = match m.fill_order
{
None => "",
Some(fo) => match fo
{
MultiplotFillOrder::RowsFirst => " rowsfirst",
MultiplotFillOrder::ColumnsFirst => " columnsfirst",
},
};
let fill_direction = match m.fill_direction
{
None => "",
Some(fd) => match fd
{
MultiplotFillDirection::Downwards => " downwards",
MultiplotFillDirection::Upwards => " upwards",
},
};
let title = m
.title
.as_ref()
.map_or("".to_string(), |t| format!(" title \"{}\"", escape(t)));
let scale = m.scale_x.map_or("".to_string(), |s| {
format!(" scale {},{}", s, m.scale_y.unwrap())
});
let offset = m.offset_x.map_or("".to_string(), |o| {
format!(" offset {},{}", o, m.offset_y.unwrap())
});
multiplot_options_string = format!(
" layout {},{}{}{}{}{}{}",
m.rows, m.columns, fill_order, fill_direction, title, scale, offset
);
}
writeln!(w, "set multiplot{}", multiplot_options_string);
let mut prev_e: Option<&AxesVariant> = None;
for e in self.axes.iter()
{
if let Some(prev_e) = prev_e
{
prev_e.reset_state(w);
}
e.write_out(
w,
self.multiplot_options.is_some(),
self.get_gnuplot_version(),
);
prev_e = Some(e);
}
writeln!(w, "unset multiplot");
writeln!(w, "{}", &self.post_commands);
self
}
pub fn echo_to_file<P: AsRef<Path>>(&self, filename: P) -> &Figure
{
if self.axes.is_empty()
{
return self;
}
let mut file = BufWriter::new(File::create(filename).unwrap());
self.echo(&mut file);
file.flush();
self
}
}
impl Drop for Figure
{
fn drop(&mut self)
{
self.close();
}
}
#[test]
fn flush_test()
{
use std::fs;
use tempfile::TempDir;
let tmp_path = TempDir::new().unwrap().into_path();
let filename = tmp_path.join("plot.png");
let mut fg = Figure::new();
fg.axes2d().boxes(0..5, 0..5, &[]);
fg.set_terminal("pngcairo", &*filename.to_string_lossy());
fg.show();
fs::read(filename).unwrap();
fs::remove_dir_all(&tmp_path);
}