1use std::{
2 fmt,
3 io::Write,
4 convert::From,
5 borrow::*,
6 path::Path
7 };
8
9#[cfg(feature = "serde_support")]
10use serde::{Serialize, Deserialize};
11
12#[derive(Debug, Clone)]
13#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
14pub enum Labels{
16 FromValues{
18 min: f64,
20 max: f64,
22 tics: usize,
24 },
25 FromStrings{
27 labels: Vec<String>
29 }
30}
31#[derive(Debug, Clone)]
32#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
33pub struct GnuplotAxis{
35 labels: Labels,
36 rotation: f32
37}
38
39impl GnuplotAxis{
40 pub fn set_rotation(&mut self, rotation_degrees: f32)
43 {
44 self.rotation = rotation_degrees;
45 }
46
47 pub(crate) fn write_tics<W: Write>(&self, mut w: W, num_bins: usize, axis: &str) -> std::io::Result<()>
48 {
49 match &self.labels {
50 Labels::FromValues{min, max, tics} => {
51 if min.is_nan() || max.is_nan() || *tics < 2 || num_bins < 2 {
52 Ok(())
53 } else {
54 let t_m1 = tics - 1;
55 let difference = (max - min) / t_m1 as f64;
56
57 let bin_dif = (num_bins - 1) as f64 / t_m1 as f64;
58 write!(w, "set {}tics ( ", axis)?;
59 for i in 0..t_m1 {
60
61 let val = min + i as f64 * difference;
62 let pos = i as f64 * bin_dif;
63 write!(w, "\"{:#}\" {:e}, ", val, pos)?;
64 }
65 writeln!(w, "\"{:#}\" {:e} ) rotate by {} right", max, num_bins - 1, self.rotation)
66 }
67 },
68 Labels::FromStrings{labels} => {
69 let tics = labels.len();
70 match tics {
71 0 => Ok(()),
72 1 => {
73 writeln!(w, "set {}tics ( \"{}\" 0 )", axis, labels[0])
74 },
75 _ => {
76 write!(w, "set {}tics ( ", axis)?;
77 let t_m1 = tics - 1;
78 let bin_dif = (num_bins - 1) as f64 / t_m1 as f64;
79 for (i, lab) in labels.iter().enumerate(){
80 let pos = i as f64 * bin_dif;
81 write!(w, "\"{}\" {:e}, ", lab, pos)?;
82 }
83 writeln!(w, " ) rotate by {} right", self.rotation)
84 }
85 }
86 }
87 }
88
89 }
90
91 pub fn new(min: f64, max: f64, tics: usize) -> Self {
93 let labels = Labels::FromValues{
94 min,
95 max,
96 tics
97 };
98 Self { labels, rotation: 0.0 }
99 }
100
101 pub fn from_labels(labels: Vec<String>) -> Self
104 {
105 let labels = Labels::FromStrings { labels };
106 Self{labels, rotation: 0.0}
107 }
108
109 pub fn from_slice(labels: &[&str]) -> Self {
112 let vec = labels.iter()
113 .map(|&s| s.into())
114 .collect();
115
116 Self::from_labels(vec)
117 }
118}
119
120#[derive(Debug, Clone)]
121#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
122pub struct GnuplotSettings{
135 pub x_label: String,
137 pub x_axis: Option<GnuplotAxis>,
139 pub y_label: String,
141 pub y_axis: Option<GnuplotAxis>,
143 pub title: String,
145 pub terminal: GnuplotTerminal,
147
148 pub palette: GnuplotPalette,
150
151 pub cb_range: Option<(f64, f64)>,
153
154 pub size: String,
159}
160
161impl GnuplotSettings {
162 pub fn size<S: Into<String>>(&'_ mut self, size: S) -> &'_ mut Self
167 {
168 self.size = size.into();
169 self
170 }
171
172 pub fn cb_range(&'_ mut self, range_start: f64, range_end: f64) -> &'_ mut Self
174 {
175 self.cb_range = Some((range_start, range_end));
176 self
177 }
178
179 pub fn remove_cb_range(&'_ mut self) -> &'_ mut Self
181 {
182 self.cb_range = None;
183 self
184 }
185
186 pub fn x_label<S: Into<String>>(&'_ mut self, x_label: S) -> &'_ mut Self
188 {
189 self.x_label = x_label.into();
190 self
191 }
192
193 pub(crate) fn write_label<W: Write>(&self, mut writer: W) -> std::io::Result<()>
194 {
195 if !self.x_label.is_empty(){
196 writeln!(writer, "set xlabel \"{}\"", self.x_label)?;
197 }
198 if !self.y_label.is_empty(){
199 writeln!(writer, "set ylabel \"{}\"", self.y_label)
200 } else {
201 Ok(())
202 }
203 }
204
205 pub fn y_label<S: Into<String>>(&'_ mut self, y_label: S) -> &'_ mut Self
207 {
208 self.y_label = y_label.into();
209 self
210 }
211
212 pub fn title<S: Into<String>>(&'_ mut self, title: S) -> &'_ mut Self
214 {
215 self.title = title.into();
216 self
217 }
218
219 pub fn get_title(&self) -> &str
221 {
222 &self.title
223 }
224
225 pub fn terminal(&'_ mut self, terminal: GnuplotTerminal) -> &'_ mut Self
227 {
228 self.terminal = terminal;
229 self
230 }
231
232 pub(crate) fn write_terminal<W: Write>(
233 &self,
234 writer: W
235 ) -> std::io::Result<()> {
236 self.terminal.write_terminal(writer, &self.size)
237 }
238
239 pub fn palette(&'_ mut self, palette: GnuplotPalette) -> &'_ mut Self
241 {
242 self.palette = palette;
243 self
244 }
245
246 pub fn new() -> Self
248 {
249 Self::default()
250 }
251
252 pub fn x_axis(&'_ mut self, axis: GnuplotAxis) -> &'_ mut Self
254 {
255 self.x_axis = Some(axis);
256 self
257 }
258
259 pub fn remove_x_axis(&'_ mut self) -> &'_ mut Self
261 {
262 self.x_axis = None;
263 self
264 }
265
266 pub fn y_axis(&'_ mut self, axis: GnuplotAxis) -> &'_ mut Self
268 {
269 self.y_axis = Some(axis);
270 self
271 }
272
273 pub fn remove_y_axis(&'_ mut self) -> &'_ mut Self
275 {
276 self.y_axis = None;
277 self
278 }
279
280 pub(crate) fn write_axis<W: Write>(&self, mut w: W, num_bins_x: usize, num_bins_y: usize) -> std::io::Result<()>
281 {
282 if let Some(ax) = self.x_axis.as_ref() {
283 ax.write_tics(&mut w, num_bins_x, "x")?;
284 }
285 if let Some(ax) = self.y_axis.as_ref() {
286 ax.write_tics(w, num_bins_y, "y")?;
287 }
288 Ok(())
289 }
290
291 pub(crate) fn write_heatmap_helper1<W>(
292 &self,
293 mut writer: W,
294 x_len: usize,
295 y_len: usize
296 ) -> std::io::Result<()>
297 where W: Write
298 {
299 self.write_terminal(&mut writer)?;
300
301 self.write_label(&mut writer)?;
302
303 writeln!(writer, "set xrange[-0.5:{}]", x_len as f64 - 0.5)?;
304 writeln!(writer, "set yrange[-0.5:{}]", y_len as f64 - 0.5)?;
305 if let Some((range_start, range_end)) = self.cb_range{
306 writeln!(writer, "set cbrange [{range_start:e}:{range_end:e}]")?;
307 }
308 if !self.title.is_empty(){
309 writeln!(writer, "set title '{}'", self.title)?;
310 }
311
312 self.write_axis(
313 &mut writer,
314 x_len,
315 y_len
316 )?;
317
318 self.palette.write_palette(&mut writer)?;
319 writeln!(writer, "set view map")?;
320
321 writeln!(writer, "set rmargin screen 0.8125\nset lmargin screen 0.175")
322 }
323
324 pub fn write_heatmap<F, W>(
339 &self,
340 mut writer: W,
341 closure: F,
342 x_len: usize,
343 y_len: usize
344 ) -> std::io::Result<()>
345 where W: Write,
346 F: FnOnce (&mut W) -> std::io::Result<()>
347 {
348 self.write_heatmap_helper1(
349 &mut writer,
350 x_len,
351 y_len
352 )?;
353 writeln!(writer, "$data << EOD")?;
354 closure(&mut writer)?;
355
356 writeln!(writer, "EOD")?;
357
358 writeln!(writer, "splot $data matrix with image t \"{}\" ", &self.title)?;
359
360 self.terminal.finish(&mut writer)
361 }
362
363 pub fn write_heatmap_external_matrix<W, P>(
375 &self,
376 mut writer: W,
377 matrix_width: usize,
378 matrix_height: usize,
379 matrix_path: P
380 ) -> std::io::Result<()>
381 where W: Write,
382 P: AsRef<Path>
383 {
384 self.write_heatmap_helper1(
385 &mut writer,
386 matrix_width,
387 matrix_height
388 )?;
389
390 writeln!(
391 writer,
392 "splot \"{}\" matrix with image t \"{}\" ",
393 matrix_path.as_ref().to_string_lossy(),
394 &self.title
395 )?;
396
397 self.terminal.finish(&mut writer)
398 }
399}
400
401impl Default for GnuplotSettings{
402 fn default() -> Self {
403 Self{
404 x_label: "".to_owned(),
405 y_label: "".to_owned(),
406 title: "".to_owned(),
407 terminal: GnuplotTerminal::Empty,
408 palette: GnuplotPalette::PresetHSV,
409 x_axis: None,
410 y_axis: None,
411 size: "7.4cm, 5cm".into(),
412 cb_range: None
413 }
414 }
415}
416
417#[derive(Debug,Clone, Copy)]
418#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
419pub struct CubeHelixParameter{
433 hue: f32, r: f32, s: f32, gamma: f32, low: f32, high: f32, reverse: bool }
441
442fn valid(v: f32) -> bool
443{
444 (0.0..=1.0).contains(&v)
445}
446
447impl CubeHelixParameter {
448 pub fn start_color(&mut self, s: f32) -> &mut Self
451 {
452 if valid(s) {
453 self.s = s;
454 return self;
455 }
456 panic!("Invalid value for starting color! The following has to be true: 0.0 <= s <= 1.0 - you used: s={}", s)
457 }
458
459 pub fn gamma(&mut self, gamma: f32) -> &mut Self
465 {
466 if gamma.is_finite(){
467 self.gamma = gamma.abs();
468 return self;
469 }
470 panic!("Gamma has to be finite. You used: {}", gamma)
471 }
472
473
474 pub fn reverse(&mut self, reverse: bool) -> &mut Self
477 {
478 self.reverse = reverse;
479 self
480 }
481
482 pub fn low_high(&mut self, low: f32, high: f32) -> &mut Self
496 {
497 if low < high && valid(low) && valid(high) {
498 self.low = low;
499 self.high = high;
500 return self;
501 }
502 panic!("Invalid values of low and high. The following has to be true: 0.0 <= low < high <= 1.0. You used: low {} high {}", low, high)
503 }
504
505
506 pub fn hue(&mut self, hue: f32) -> &mut Self
510 {
511 if valid(hue) {
512 self.hue = hue;
513 } else {
514 panic!("Invalid hue value! Hue value has to be 0.0 <= hue <= 1.0, you used {}", hue)
515 }
516 self
517 }
518
519 pub fn rotation(&mut self, rotation: f32) -> &mut Self
525 {
526 if rotation.is_finite(){
527 self.r = rotation;
528 }else {
529 panic!("Invalid rotation value! Rotation value has to be finite, you used {}", rotation)
530 }
531 self
532 }
533
534 pub fn rgb_from_gray(&self, gray: f32) -> [f32; 3]
540 {
541 if gray.is_nan() {
542 return [0.0, 0.0, 0.0];
543 }
544 let mut lambda = gray.clamp(0.0, 1.0);
545 if self.reverse {
546 lambda = 1.0 - lambda;
547 }
548 lambda = self.low + (self.high - self.low) * lambda; let lg = lambda.powf(self.gamma);
550 let phi = 2.0 * (self.s / 3.0 + self.r * lambda) * std::f32::consts::PI;
551 let a = self.hue * lg * (1.0 - lg) * 0.5;
552
553 let (s_phi, c_phi) = phi.sin_cos();
554
555 [
556 a * (-0.14861 * c_phi + 1.78277 * s_phi)+ lg, lg + a * (-0.29227 * c_phi - 0.90649 * s_phi), lg + a * c_phi * 1.97294 ]
560 }
561
562 pub fn approximate_color_rgb(&self, gray: f32) -> ColorRGB
568 {
569 let color = self.rgb_from_gray(gray);
570 let color = color
571 .map(
572 |val|
573 (val * 255.0)
574 .clamp(0.0,255.0)
575 .floor() as u8
576 );
577
578 ColorRGB::new_from_array(&color)
579 }
580
581 pub fn into_gnuplot_palette(self) -> GnuplotPalette
583 {
584 self.into()
585 }
586}
587
588impl Default for CubeHelixParameter {
589 fn default() -> Self
590 {
591 Self{
592 hue: 1.0,
593 r: 1.2,
594 low: 0.0,
595 high: 1.0,
596 reverse: false,
597 gamma: 1.0,
598 s: 0.1
599 }
600 }
601}
602
603
604#[derive(Debug, Clone, Copy)]
608#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
609pub struct ColorRGB{
610 pub red: u8,
612 pub green: u8,
614 pub blue: u8
616}
617
618impl Default for ColorRGB{
619 fn default() -> Self
620 {
621 Self::new(0,0,0)
622 }
623}
624
625impl ColorRGB{
626 pub fn new(red: u8, green: u8, blue: u8) -> Self
628 {
629 Self{
630 red,
631 green,
632 blue
633 }
634 }
635
636 pub fn new_from_array(color: &[u8;3]) -> Self
641 {
642 Self{
643 red: color[0],
644 green: color[1],
645 blue: color[2]
646 }
647 }
648
649 pub fn to_array(&self) -> [u8;3]
652 {
653 [self.red, self.green, self.blue]
654 }
655
656 pub fn to_hex(&self) -> String
669 {
670 let mut s = String::new();
671 self.fmt_hex(&mut s)
672 .unwrap();
673 s
674 }
675
676 pub fn fmt_hex<W: fmt::Write>(&self, mut writer: W) -> Result<(), fmt::Error>
679 {
680 write!(
681 writer,
682 "#{:02X?}{:02X?}{:02X?}",
683 self.red,
684 self.green,
685 self.blue
686 )
687 }
688
689 pub fn write_hex<W: Write>(&self, mut writer: W) -> Result<(), std::io::Error>
692 {
693 write!(
694 writer,
695 "#{:02X?}{:02X?}{:02X?}",
696 self.red,
697 self.green,
698 self.blue
699 )
700 }
701}
702
703#[derive(Debug, Clone)]
706#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
707pub struct PaletteRGB{
708 colors: Vec<ColorRGB>
709}
710
711impl PaletteRGB{
712 pub fn new(colors: Vec<ColorRGB>) -> Option<Self>
717 {
718 if colors.len() < 2
719 {
720 return None;
721 }
722 Some(
723 Self{
724 colors
725 }
726 )
727 }
728
729 pub fn add_color(&mut self, color: ColorRGB){
731 self.colors.push(color)
732 }
733
734 pub fn fmt_palette<W: fmt::Write>(&self, mut writer: W) -> Result<(), fmt::Error>
736 {
737 write!(writer, "set palette defined ( 0 \"")?;
738 self.colors[0].fmt_hex(&mut writer)?;
739 write!(writer, "\"")?;
740
741 for (color, index) in self.colors.iter().skip(1).zip(1..)
742 {
743 write!(writer, ", {} \"", index)?;
744 color.fmt_hex(&mut writer)?;
745 write!(writer, "\"")?;
746
747 }
748 write!(writer, " )")
749 }
750
751 pub fn write_palette<W: std::io::Write>(&self, mut writer: W) -> Result<(), std::io::Error>
753 {
754 write!(writer, "set palette defined ( 0 \"")?;
755 self.colors[0].write_hex(&mut writer)?;
756 write!(writer, "\"")?;
757
758 for (color, index) in self.colors.iter().skip(1).zip(1..)
759 {
760 write!(writer, ", {} \"", index)?;
761 color.write_hex(&mut writer)?;
762 write!(writer, "\"")?;
763
764 }
765 write!(writer, " )")
766 }
767
768 pub fn into_gnuplot_palette(self) -> GnuplotPalette
770 {
771 self.into()
772 }
773}
774
775#[derive(Debug, Clone)]
779#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
780pub struct GnuplotPointSettings
781{
782 pub color: ColorRGB,
784 size: f32,
785 pub frame: bool,
787 pub frame_color: ColorRGB,
789 legend: String
791}
792
793impl GnuplotPointSettings{
794 pub fn new() -> Self
797 {
798 Self::default()
799 }
800
801 pub fn color(&mut self, color: ColorRGB) -> &mut Self
804 {
805 self.color = color;
806 self
807 }
808
809 pub fn size(&mut self, size: f32) -> &mut Self
815 {
816 if size.is_finite() && size >= 0.0
817 {
818 self.size = size;
819 }
820 self
821 }
822
823 pub fn get_size(&self) -> f32
826 {
827 self.size
828 }
829
830 pub fn frame(&mut self, active: bool) -> &mut Self
834 {
835 self.frame = active;
836 self
837 }
838
839 pub fn frame_color(&mut self, color: ColorRGB) -> &mut Self
842 {
843 self.frame_color = color;
844 self
845 }
846
847 pub fn legend<S: Into<String>>(&mut self, legend: S) -> &mut Self
851 {
852 let s = legend.into();
853 if s.contains('\"') || s.contains('\n')
854 {
855 self.legend = "Invalid character encountered".to_owned();
856 self
857 } else {
858 self.legend = s;
859 self
860 }
861 }
862
863 pub fn get_legend(&self) -> &str
866 {
867 &self.legend
868 }
869
870 #[allow(dead_code)]
871 pub(crate) fn frame_size(&self) -> f32
872 {
873 let size = self.size * 1.14;
874 if size < 0.01 {
875 0.01
876 } else {
877 size
878 }
879 }
880}
881
882impl Default for GnuplotPointSettings
883{
884 fn default() -> Self
885 {
886 Self{
887 color: ColorRGB::new(0,0,255),
888 size: 0.5,
889 frame: true,
890 frame_color: ColorRGB::default(),
891 legend: "".into()
892 }
893 }
894}
895
896#[derive(Debug, Clone)]
897#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
898pub enum GnuplotPalette{
900 PresetHSV,
902 PresetRGB,
905 CubeHelix(CubeHelixParameter),
915 RGB(PaletteRGB),
917}
918
919impl From<PaletteRGB> for GnuplotPalette{
920 fn from(palette: PaletteRGB) -> Self
921 {
922 GnuplotPalette::RGB(palette)
923 }
924}
925
926impl From<CubeHelixParameter> for GnuplotPalette
927{
928 fn from(parameter: CubeHelixParameter) -> Self
929 {
930 GnuplotPalette::CubeHelix(parameter)
931 }
932}
933
934impl GnuplotPalette{
935 pub(crate) fn write_palette<W: Write>(&self, mut writer: W) -> std::io::Result<()>
936 {
937 match self {
938 Self::PresetHSV => {
939 writeln!(writer, "set palette model HSV")?;
940 writeln!(writer, "set palette negative defined ( 0 0 1 0, 2.8 0.4 0.6 0.8, 5.5 0.83 0 1 )")
941 },
942 Self::PresetRGB => Ok(()),
943 Self::CubeHelix(helix) => {
944 writeln!(writer, "# Parameter for color palett")?;
945 writeln!(writer, "hue={} # hue intensity, valid values 0 <= hue <= 1", helix.hue)?;
946 writeln!(writer, "r={} # rotation in color space. Typical values: -1.5 <= r <= 1.5", helix.r)?;
947 writeln!(writer, "s={} # starting color, valid values 0 <= s <= 1", helix.s)?;
948 writeln!(writer, "gamma={} # gamma < 1 emphasizes low intensity values, gamma > 1 high intensity ones", helix.gamma)?;
949 writeln!(writer, "low={} # lowest value for grayscale. 0 <= low < 1 and low < high", helix.low)?;
950 writeln!(writer, "high={} # highest value for grayscale. 0< high <= 1 and low < high", helix.high)?;
951 let s = if helix.reverse {
952 "1"
953 } else {
954 "0"
955 };
956 writeln!(writer, "reverse={} # set to 1 for reverse cbrange, set to 0 for original cbrange", s)?;
957 writeln!(writer, "\n\nlg(lambda)=lambda**gamma
958phi(lambda)=2.0 * (s/3.0 + r * lambda) * pi
959a(lambda)=hue*lg(lambda)*(1.0-lg(lambda)) * 0.5
960
961red(lambda)=a(lambda)*(-0.14861*cos(phi(lambda)) + 1.78277 * sin(phi(lambda))) + lg(lambda)
962green(lambda)=lg(lambda) + a(lambda)*(-0.29227 * cos(phi(lambda)) - 0.90649 * sin(phi(lambda)))
963blue(lambda)=lg(lambda) + a(lambda) * cos(phi(lambda)) * 1.97294
964
965rev(x)=reverse?1-x:x # reverse grayscale
966
967map(x)=low+(high-low)*rev(x)
968
969set palette functions red(map(gray)), green(map(gray)), blue(map(gray))\n")
970 },
971 Self::RGB(palette) => {
972 {
973 palette.write_palette(&mut writer)?;
974 writeln!(writer)
975 }
976 }
977 }
978
979 }
980}
981
982
983
984
985#[derive(Debug, Clone)]
986#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
987pub enum GnuplotTerminal{
989 EpsLatex(String),
1002 PDF(String),
1006 Empty,
1008}
1009
1010fn get_valid_filename(name: &str) -> String
1011{
1012 name.chars()
1013 .filter(
1014 |c|
1015 {
1016 c.is_alphabetic() || *c == ' ' || *c == '_'
1017 }
1018 ).take(255)
1019 .collect()
1020}
1021
1022impl GnuplotTerminal{
1023 pub(crate) fn write_terminal<W: Write>(
1024 &self,
1025 mut writer: W,
1026 size: &str
1027 ) -> std::io::Result<()>
1028 {
1029 writeln!(writer, "reset session")?;
1030 writeln!(writer, "set encoding utf8")?;
1031
1032 let size = if size.is_empty(){
1033 size.to_owned()
1034 } else {
1035 format!(" size {}", size)
1036 };
1037
1038 match self{
1039 Self::EpsLatex(name) => {
1040 let name = get_valid_filename(name);
1041 writeln!(writer, "set t epslatex 9 standalone color{} header \"\\\\usepackage{{amsmath}}\\n\"\nset font \",9\"", size)?;
1042 writeln!(writer, "set output \"{name}.tex\"")
1043 },
1044 Self::PDF(name) => {
1045 let name = get_valid_filename(name);
1046 writeln!(writer, "set t pdf {}", size)?;
1047 writeln!(writer, "set output \"{name}.pdf\"")
1048 },
1049 Self::Empty => Ok(())
1050 }
1051 }
1052
1053 pub(crate) fn finish<W: Write>(&self, mut w: W) -> std::io::Result<()>
1054 {
1055 match self {
1056 Self::EpsLatex(name) => {
1057 let name = get_valid_filename(name);
1058 writeln!(w, "set output")?;
1059 write!(w, "system('latexmk {name}.tex")?;
1060 writeln!(w, " -pdf -f')")
1061 },
1062 Self::PDF(_) => {
1063 writeln!(w, "set output")
1064 },
1065 _ => Ok(())
1066 }
1067 }
1068}