1use crate::error_types::*;
6
7use self::AxesVariant::*;
8use crate::axes2d::*;
9use crate::axes3d::*;
10
11use crate::options::{GnuplotVersion, MultiplotFillDirection, MultiplotFillOrder};
12use crate::util::escape;
13use crate::writer::Writer;
14use std::fs::File;
15use std::io::{BufWriter, Write};
16use std::path::{Path, PathBuf};
17use std::process::{Child, Command, Stdio};
18use std::str;
19use tempfile;
20
21enum AxesVariant
22{
23 Axes2DType(Axes2D),
24 Axes3DType(Axes3D),
25 NewPage,
26}
27
28impl AxesVariant
29{
30 fn write_out(
31 &self, data_directory: Option<&str>, writer: &mut dyn Writer, auto_layout: bool,
32 version: GnuplotVersion,
33 )
34 {
35 match *self
36 {
37 Axes2DType(ref a) => a.write_out(data_directory, writer, auto_layout, version),
38 Axes3DType(ref a) => a.write_out(data_directory, writer, auto_layout, version),
39 NewPage =>
40 {
41 writeln!(writer, "unset multiplot");
42 writeln!(writer, "set multiplot");
43 }
44 }
45 }
46
47 fn reset_state(&self, writer: &mut dyn Writer)
48 {
49 match *self
50 {
51 Axes2DType(ref a) => a.reset_state(writer),
52 Axes3DType(ref a) => a.reset_state(writer),
53 _ => (),
54 }
55 }
56}
57
58struct MultiplotOptions
60{
61 rows: usize,
62 columns: usize,
63 title: Option<String>,
64 scale_x: Option<f32>,
65 scale_y: Option<f32>,
66 offset_x: Option<f32>,
67 offset_y: Option<f32>,
68 fill_order: Option<MultiplotFillOrder>,
69 fill_direction: Option<MultiplotFillDirection>,
70}
71
72impl MultiplotOptions
73{
74 pub fn new() -> MultiplotOptions
75 {
76 MultiplotOptions {
77 rows: 1,
78 columns: 1,
79 title: None,
80 scale_x: None,
81 scale_y: None,
82 offset_x: None,
83 offset_y: None,
84 fill_order: None,
85 fill_direction: None,
86 }
87 }
88}
89
90pub struct CloseSentinel
92{
93 gnuplot: Child,
94}
95
96impl CloseSentinel
97{
98 fn new(gnuplot: Child) -> Self
99 {
100 CloseSentinel { gnuplot }
101 }
102
103 pub fn wait(&mut self) -> std::io::Result<std::process::ExitStatus>
105 {
106 self.gnuplot.wait()
107 }
108
109 pub fn try_wait(&mut self) -> std::io::Result<Option<std::process::ExitStatus>>
112 {
113 self.gnuplot.try_wait()
114 }
115}
116
117impl Drop for CloseSentinel
118{
119 fn drop(&mut self)
120 {
121 self.wait().unwrap();
122 }
123}
124
125pub struct Figure
127{
128 axes: Vec<AxesVariant>,
129 terminal: String,
130 enhanced_text: bool,
131 output_file: Option<PathBuf>,
132 post_commands: String,
133 pre_commands: String,
134 gnuplot: Option<Child>,
136 version: Option<GnuplotVersion>,
137 multiplot_options: Option<MultiplotOptions>,
138 data_directory: Option<String>,
139 data_tempdir: Option<tempfile::TempDir>,
140}
141
142impl Default for GnuplotVersion
143{
144 fn default() -> GnuplotVersion
145 {
146 GnuplotVersion { major: 5, minor: 0 }
147 }
148}
149
150impl Default for Figure
151{
152 fn default() -> Self
153 {
154 Self::new()
155 }
156}
157
158impl Figure
159{
160 pub fn new() -> Figure
162 {
163 let data_tempdir = tempfile::tempdir().ok();
164 Figure {
165 axes: Vec::new(),
166 terminal: "".into(),
167 enhanced_text: true,
168 output_file: None,
169 gnuplot: None,
170 post_commands: "".into(),
171 pre_commands: "".into(),
172 version: None,
173 multiplot_options: None,
174 data_directory: data_tempdir
175 .as_ref()
176 .and_then(|d| d.path().to_str())
177 .map(|s| s.into()),
178 data_tempdir: data_tempdir,
179 }
180 }
181
182 pub fn set_data_directory(&mut self, data_directory: Option<String>) -> &mut Self
192 {
193 self.data_directory = data_directory;
194 if self
195 .data_directory
196 .as_ref()
197 .map(|s| s == "")
198 .unwrap_or(false)
199 {
200 self.data_directory = self
201 .data_tempdir
202 .as_ref()
203 .and_then(|d| d.path().to_str())
204 .map(|s| s.into())
205 }
206 self
207 }
208
209 pub fn set_terminal<'l>(&'l mut self, terminal: &str, output_file: &str) -> &'l mut Figure
224 {
225 self.terminal = terminal.into();
226 self.output_file = if output_file.is_empty()
227 {
228 None
229 }
230 else
231 {
232 Some(output_file.into())
233 };
234 self
235 }
236
237 pub fn set_enhanced_text(&mut self, enhanced: bool) -> &mut Figure
239 {
240 self.enhanced_text = enhanced;
241 self
242 }
243
244 pub fn set_post_commands(&mut self, post_commands: &str) -> &mut Figure
246 {
247 self.post_commands = post_commands.into();
248 self
249 }
250
251 pub fn set_pre_commands(&mut self, pre_commands: &str) -> &mut Figure
253 {
254 self.pre_commands = pre_commands.into();
255 self
256 }
257
258 pub fn set_gnuplot_version(&mut self, version: Option<GnuplotVersion>) -> &mut Figure
263 {
264 self.version = version;
265 self
266 }
267
268 pub fn get_gnuplot_version(&self) -> GnuplotVersion
270 {
271 self.version.unwrap_or_default()
272 }
273
274 pub fn set_multiplot_layout(&mut self, rows: usize, columns: usize) -> &mut Self
279 {
280 let multiplot_options = self
281 .multiplot_options
282 .get_or_insert(MultiplotOptions::new());
283 multiplot_options.rows = rows;
284 multiplot_options.columns = columns;
285
286 self
287 }
288
289 pub fn set_title(&mut self, title: &str) -> &mut Self
293 {
294 let multiplot_options = self
295 .multiplot_options
296 .get_or_insert(MultiplotOptions::new());
297 multiplot_options.title = Some(title.into());
298
299 self
300 }
301
302 pub fn set_scale(&mut self, scale_x: f32, scale_y: f32) -> &mut Self
307 {
308 let multiplot_options = self
309 .multiplot_options
310 .get_or_insert(MultiplotOptions::new());
311 multiplot_options.scale_x = Some(scale_x);
312 multiplot_options.scale_y = Some(scale_y);
313
314 self
315 }
316
317 pub fn set_offset(&mut self, offset_x: f32, offset_y: f32) -> &mut Self
322 {
323 let multiplot_options = self
324 .multiplot_options
325 .get_or_insert(MultiplotOptions::new());
326 multiplot_options.offset_x = Some(offset_x);
327 multiplot_options.offset_y = Some(offset_y);
328
329 self
330 }
331
332 pub fn set_multiplot_fill_order(
337 &mut self, order: MultiplotFillOrder, direction: MultiplotFillDirection,
338 ) -> &mut Self
339 {
340 let multiplot_options = self
341 .multiplot_options
342 .get_or_insert(MultiplotOptions::new());
343 multiplot_options.fill_order = Some(order);
344 multiplot_options.fill_direction = Some(direction);
345
346 self
347 }
348
349 pub fn axes2d(&mut self) -> &mut Axes2D
351 {
352 self.axes.push(Axes2DType(Axes2D::new()));
353 let l = self.axes.len();
354 match self.axes[l - 1]
355 {
356 Axes2DType(ref mut a) => a,
357 _ => unreachable!(),
358 }
359 }
360
361 pub fn axes3d(&mut self) -> &mut Axes3D
363 {
364 self.axes.push(Axes3DType(Axes3D::new()));
365 let l = self.axes.len();
366 match self.axes[l - 1]
367 {
368 Axes3DType(ref mut a) => a,
369 _ => unreachable!(),
370 }
371 }
372
373 pub fn new_page(&mut self) -> &mut Figure
381 {
382 self.axes.push(NewPage);
383 self
384 }
385
386 pub fn show_and_keep_running(&mut self) -> Result<&mut Figure, GnuplotInitError>
393 {
394 if self.axes.is_empty()
395 {
396 return Ok(self);
397 }
398
399 if self.version.is_none()
400 {
401 let output = Command::new("gnuplot").arg("--version").output()?;
402
403 if let Ok(version_string) = str::from_utf8(&output.stdout)
404 {
405 let parts: Vec<_> = version_string.split(|c| c == ' ' || c == '.').collect();
406 if parts.len() > 2 && parts[0] == "gnuplot"
407 {
408 if let (Ok(major), Ok(minor)) =
409 (parts[1].parse::<i32>(), parts[2].parse::<i32>())
410 {
411 self.version = Some(GnuplotVersion { major, minor });
412 }
413 }
414 }
415 }
416
417 if self.gnuplot.is_none()
418 {
419 self.gnuplot = Some(
420 Command::new("gnuplot")
421 .arg("-p")
422 .stdin(Stdio::piped())
423 .spawn()
424 .expect(
425 "Couldn't spawn gnuplot. Make sure it is installed and available in PATH.",
426 ),
427 );
428 }
429
430 {
431 let mut gnuplot = self.gnuplot.take();
432 if let Some(p) = gnuplot.as_mut()
433 {
434 let stdin = p.stdin.as_mut().expect("No stdin!?");
435 self.echo(stdin);
436 stdin.flush();
437 };
438 self.gnuplot = gnuplot;
439 }
440
441 Ok(self)
442 }
443
444 pub fn show(&mut self) -> Result<CloseSentinel, GnuplotInitError>
451 {
452 self.show_and_keep_running()?;
453 let mut gnuplot = self.gnuplot.take().expect("No gnuplot?");
454 {
455 let stdin = gnuplot.stdin.as_mut().expect("No stdin!?");
456 writeln!(stdin, "pause mouse close");
457 writeln!(stdin, "quit");
458 };
459 Ok(CloseSentinel::new(gnuplot))
460 }
461
462 pub fn save_to_png<P: AsRef<Path>>(
469 &mut self, filename: P, width_px: u32, height_px: u32,
470 ) -> Result<(), GnuplotInitError>
471 {
472 let former_term = self.terminal.clone();
473 let former_output_file = self.output_file.clone();
474 self.terminal = format!("pngcairo size {},{}", width_px, height_px);
475 self.output_file = Some(filename.as_ref().into());
476 self.show()?;
477 self.terminal = former_term;
478 self.output_file = former_output_file;
479
480 Ok(())
481 }
482
483 pub fn save_to_svg<P: AsRef<Path>>(
490 &mut self, filename: P, width_px: u32, height_px: u32,
491 ) -> Result<(), GnuplotInitError>
492 {
493 let former_term = self.terminal.clone();
494 let former_output_file = self.output_file.clone();
495 self.terminal = format!("svg size {},{}", width_px, height_px);
496 self.output_file = Some(filename.as_ref().into());
497 self.show()?;
498 self.terminal = former_term;
499 self.output_file = former_output_file;
500
501 Ok(())
502 }
503
504 pub fn save_to_pdf<P: AsRef<Path>>(
511 &mut self, filename: P, width_in: f32, height_in: f32,
512 ) -> Result<(), GnuplotInitError>
513 {
514 let former_term = self.terminal.clone();
515 let former_output_file = self.output_file.clone();
516 self.terminal = format!("pdfcairo size {},{}", width_in, height_in);
517 self.output_file = Some(filename.as_ref().into());
518 self.show()?;
519 self.terminal = former_term;
520 self.output_file = former_output_file;
521
522 Ok(())
523 }
524
525 pub fn save_to_eps<P: AsRef<Path>>(
532 &mut self, filename: P, width_in: f32, height_in: f32,
533 ) -> Result<(), GnuplotInitError>
534 {
535 let former_term = self.terminal.clone();
536 let former_output_file = self.output_file.clone();
537 self.terminal = format!("epscairo size {},{}", width_in, height_in);
538 self.output_file = Some(filename.as_ref().into());
539 self.show()?;
540 self.terminal = former_term;
541 self.output_file = former_output_file;
542
543 Ok(())
544 }
545
546 pub fn save_to_canvas<P: AsRef<Path>>(
553 &mut self, filename: P, width_px: u32, height_px: u32,
554 ) -> Result<(), GnuplotInitError>
555 {
556 let former_term = self.terminal.clone();
557 let former_output_file = self.output_file.clone();
558 self.terminal = format!("canvas size {},{}", width_px, height_px);
559 self.output_file = Some(filename.as_ref().into());
560 self.show()?;
561 self.terminal = former_term;
562 self.output_file = former_output_file;
563
564 Ok(())
565 }
566
567 pub fn close(&mut self) -> &mut Figure
572 {
573 if self.gnuplot.is_none()
574 {
575 return self;
576 }
577
578 {
579 if let Some(p) = self.gnuplot.as_mut()
580 {
581 {
582 let stdin = p.stdin.as_mut().expect("No stdin!?");
583 writeln!(stdin, "quit");
584 }
585 p.wait();
586 };
587 self.gnuplot = None;
588 }
589
590 self
591 }
592
593 pub fn clear_axes(&mut self) -> &mut Figure
595 {
596 self.axes.clear();
597 self
598 }
599
600 pub fn echo<T: Writer>(&self, writer: &mut T) -> &Figure
604 {
605 let w = writer as &mut dyn Writer;
606 writeln!(w, "{}", &self.pre_commands);
607
608 if self.axes.is_empty()
609 {
610 return self;
611 }
612
613 writeln!(w, "set encoding utf8");
614 if !self.terminal.is_empty()
615 {
616 writeln!(w, "set terminal {}", self.terminal);
617 }
618
619 if let Some(ref output_file) = self.output_file
620 {
621 writeln!(
622 w,
623 "set output \"{}\"",
624 escape(output_file.to_str().unwrap())
625 );
626 }
627
628 writeln!(w, "set termoption dashed");
629 writeln!(
630 w,
631 "set termoption {}",
632 if self.enhanced_text
633 {
634 "enhanced"
635 }
636 else
637 {
638 "noenhanced"
639 }
640 );
641
642 if self.axes.len() > 1 || self.multiplot_options.is_some()
643 {
644 let mut multiplot_options_string = "".to_string();
645 if let Some(m) = &self.multiplot_options
646 {
647 let fill_order = match m.fill_order
648 {
649 None => "",
650 Some(fo) => match fo
651 {
652 MultiplotFillOrder::RowsFirst => " rowsfirst",
653 MultiplotFillOrder::ColumnsFirst => " columnsfirst",
654 },
655 };
656
657 let fill_direction = match m.fill_direction
658 {
659 None => "",
660 Some(fd) => match fd
661 {
662 MultiplotFillDirection::Downwards => " downwards",
663 MultiplotFillDirection::Upwards => " upwards",
664 },
665 };
666
667 let title = m
668 .title
669 .as_ref()
670 .map_or("".to_string(), |t| format!(" title \"{}\"", escape(t)));
671 let scale = m.scale_x.map_or("".to_string(), |s| {
672 format!(" scale {},{}", s, m.scale_y.unwrap())
673 });
674 let offset = m.offset_x.map_or("".to_string(), |o| {
675 format!(" offset {},{}", o, m.offset_y.unwrap())
676 });
677
678 multiplot_options_string = format!(
679 " layout {},{}{}{}{}{}{}",
680 m.rows, m.columns, fill_order, fill_direction, title, scale, offset
681 );
682 }
683
684 writeln!(w, "set multiplot{}", multiplot_options_string);
685 }
686
687 let mut prev_e: Option<&AxesVariant> = None;
688 for (i, e) in self.axes.iter().enumerate()
689 {
690 if let Some(prev_e) = prev_e
691 {
692 prev_e.reset_state(w);
693 }
694 let out_path = self.data_directory.as_ref().and_then(|d| {
695 Path::new(&d)
696 .join(i.to_string())
697 .to_str()
698 .map(|s| s.to_string())
699 });
700 if let Some(out_path) = out_path.as_ref()
701 {
702 std::fs::create_dir_all(out_path).ok();
703 }
704 e.write_out(
705 out_path.as_deref(),
706 w,
707 self.multiplot_options.is_some(),
708 self.get_gnuplot_version(),
709 );
710 prev_e = Some(e);
711 }
712
713 if self.axes.len() > 1 || self.multiplot_options.is_some()
714 {
715 writeln!(w, "unset multiplot");
716 }
717 writeln!(w, "{}", &self.post_commands);
718 self
719 }
720
721 pub fn echo_to_file<P: AsRef<Path>>(&self, filename: P) -> &Figure
725 {
726 if self.axes.is_empty()
727 {
728 return self;
729 }
730
731 let mut file = BufWriter::new(File::create(filename).unwrap());
732 self.echo(&mut file);
733 file.flush();
734 self
735 }
736}
737
738impl Drop for Figure
739{
740 fn drop(&mut self)
741 {
742 self.close();
743 }
744}
745
746#[test]
747fn flush_test()
748{
749 use std::fs;
750 use tempfile::TempDir;
751
752 let tmp_path = TempDir::new().unwrap().into_path();
753 let filename = tmp_path.join("plot.png");
754 let mut fg = Figure::new();
755 fg.axes2d().boxes(0..5, 0..5, &[]);
756 fg.set_terminal("pngcairo", &*filename.to_string_lossy());
757 fg.show();
758 fs::read(filename).unwrap();
759 fs::remove_dir_all(&tmp_path);
760}