sampling/heatmap/
gnuplot.rs

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))]
14/// For labeling the gnuplot plots axis
15pub enum Labels{
16    /// construct the labels
17    FromValues{
18        /// minimum value for axis labels
19        min: f64,
20        /// maximum value for axis labels
21        max: f64,
22        ///number of tics, should be at least 2
23        tics: usize,
24    },
25    /// use labels 
26    FromStrings{
27        /// this are the labels
28        labels: Vec<String>
29    }
30}
31#[derive(Debug, Clone)]
32#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
33/// For labeling the gnuplot plots axis
34pub struct GnuplotAxis{
35    labels: Labels,
36    rotation: f32
37}
38
39impl GnuplotAxis{
40    /// Set the rotation value. 
41    /// Tics will be displayed rotated to the right by the requested amount
42    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    /// Create new GnuplotAxis::FromValues
92    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    /// Create new GnuplotAxis::Labels
102    /// - Vector contains labels used for axis
103    pub fn from_labels(labels: Vec<String>) -> Self
104    {
105        let labels = Labels::FromStrings { labels };
106        Self{labels, rotation: 0.0}
107    }
108
109    /// Similar to `from_labels`
110    /// * Slice of slice is converted to Vector of Strings and `Self::from_labels(vec)` is called
111    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))]
122/// # Settings for gnuplot
123/// * implements default
124/// * implements builder pattern for itself
125/// # **Safety**
126/// 
127/// **These gnuplot options are not meant for production!**
128/// If you allow arbitrary user input for this, the resulting gnuplot scripts can contain 
129/// **arbitrary system calls!** 
130/// 
131/// Thus calling the resulting gnuplot scripts is not safe, if you have not sanitized the inputs!
132/// 
133/// This is not an issue if you only create scripts for yourself, i.e., if you are your own user.
134pub struct GnuplotSettings{
135    /// x label for gnuplot
136    pub x_label: String,
137    /// how to format the labels of the x axis?
138    pub x_axis: Option<GnuplotAxis>,
139    /// y label for gnuplot
140    pub y_label: String,
141    /// how to format the labels of the y axis?
142    pub y_axis: Option<GnuplotAxis>,
143    /// title for gnuplot
144    pub title: String,
145    /// which terminal to use for gnuplot
146    pub terminal: GnuplotTerminal,
147
148    /// Color palette for heatmap
149    pub palette: GnuplotPalette,
150
151    /// Define the cb range if this option is set
152    pub cb_range: Option<(f64, f64)>,
153
154    /// # Size of the terminal
155    /// * Anything gnuplot accepts (e.g. "2cm, 2.9cm") is acceptable
156    /// # Note
157    /// the code does not check, if your input for `size` makes any sense
158    pub size: String,
159}
160
161impl GnuplotSettings {
162    /// # Builder pattern - set size of terminal
163    /// * Anything gnuplot accepts (e.g. "2cm, 2.9cm") is acceptable
164    /// # Note
165    /// the code does not check, if your input for `size` makes any sense
166    pub fn size<S: Into<String>>(&'_ mut self, size: S) -> &'_ mut Self
167    {
168        self.size = size.into();
169        self
170    }
171
172    /// # Builder pattern - set cb_range
173    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    /// # Builder pattern - remove cb_range
180    pub fn remove_cb_range(&'_ mut self) -> &'_ mut Self
181    {
182        self.cb_range = None;
183        self
184    }
185
186    /// # Builder pattern - set x_label
187    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    /// # Builder pattern - set y_label
206    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    /// # Builder pattern - set title
213    pub fn title<S: Into<String>>(&'_ mut self, title: S) -> &'_ mut Self
214    {
215        self.title = title.into();
216        self
217    }
218
219    /// # currently set title
220    pub fn get_title(&self) -> &str
221    {
222        &self.title
223    }
224
225    /// # Builder pattern - set terminal
226    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    /// # Builder pattern - set color palette
240    pub fn palette(&'_ mut self, palette: GnuplotPalette) -> &'_ mut Self
241    {
242        self.palette = palette;
243        self
244    }
245
246    /// Create new, default, GnuplotSettings
247    pub fn new() -> Self
248    {
249        Self::default()
250    }
251
252    /// Set x_axis - See GnuplotAxis or try it out
253    pub fn x_axis(&'_ mut self, axis: GnuplotAxis) -> &'_ mut Self
254    {
255        self.x_axis = Some(axis);
256        self
257    }
258
259    /// Remove x_axis
260    pub fn remove_x_axis(&'_ mut self) -> &'_ mut Self
261    {
262        self.x_axis = None;
263        self
264    }
265
266    /// Set y_axis - See GnuplotAxis or try it out
267    pub fn y_axis(&'_ mut self, axis: GnuplotAxis) -> &'_ mut Self
268    {
269        self.y_axis = Some(axis);
270        self
271    }
272
273    /// Remove y_axis
274    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    /// # Write a heatmap with the given gnuplot Settings
325    /// * `closure` has to write the heatmap. It must write `y_len` rows with `x_len` values each, where the latter values are separated by a space.
326    ///     This data will be used for the heatmap.
327    /// * `x_len`: The number of entries in each column, that you promise the `closure` will write
328    /// * `y_len`: The number of columns you promise that the `closure` will write
329    /// # **Safety**
330    /// 
331    /// **These gnuplot options are not meant for production!**
332    /// If you allow arbitrary user input for the gnuplot settings, the resulting gnuplot scripts can contain 
333    /// **arbitrary system calls!** 
334    /// 
335    /// Thus calling the resulting gnuplot scripts is not safe, if you have not sanitized the inputs!
336    /// 
337    /// This is not an issue if you only create scripts for yourself, i.e., if you are your own user.
338    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    /// Same as write_heatmap but it assumes that the heatmap 
364    /// matrix is available in the file "heatmap"
365    /// # **Safety**
366    /// 
367    /// **These gnuplot options are not meant for production!**
368    /// If you allow arbitrary user input for the gnuplot settings, the resulting gnuplot scripts can contain 
369    /// **arbitrary system calls!** 
370    /// 
371    /// Thus calling the resulting gnuplot scripts is not safe, if you have not sanitized the inputs!
372    /// 
373    /// This is not an issue if you only create scripts for yourself, i.e., if you are your own user.
374    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))]
419/// Implements color palett from <https://arxiv.org/abs/1108.5083>
420///
421/// What's so good about this palett? It is monotonically increasing in perceived brightness.
422/// That means, it is well suited for being printed in black and white. 
423///
424/// ```
425/// use sampling::heatmap::*;
426/// let mut params = CubeHelixParameter::default();
427/// params.rotation(1.3)
428///         .gamma(1.1)
429///         .start_color(0.3)
430///         .reverse(true);
431/// ```
432pub struct CubeHelixParameter{
433    hue: f32,       // hue intensity, valid values 0 <= hue <= 1
434    r:  f32,        // rotation in color space. Typical values: -1.5 <= r <= 1.5
435    s: f32,         // starting color, valid values 0 <= s <= 1
436    gamma: f32,     // gamma < 1 emphasises low intensity values, gamma > 1 high intensity ones
437    low: f32,       // lowest value for grayscale. 0 <= low < 1 and low < high
438    high: f32,      // highest value for grayscale. 0< high <= 1 and low < high
439    reverse: bool   // reverse cbrange?
440}
441
442fn valid(v: f32) -> bool
443{
444    (0.0..=1.0).contains(&v)
445}
446
447impl CubeHelixParameter {
448    /// # Builder pattern - set start color
449    /// Will panic if the following is false: 0.0 <= s <= 1.0
450    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    /// # Builder pattern - set gamma
460    /// 
461    /// |gamma| < 1 emphasises low intensity values, |gamma| > 1 high intensity ones
462    ///
463    /// gamma has to be finite - will panic otherwise
464    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    /// # Builder pattern - set reverse
475    /// reverse: Reverse the cbrange?
476    pub fn reverse(&mut self, reverse: bool) -> &mut Self
477    {
478        self.reverse = reverse;
479        self
480    }
481
482    /// # Builder pattern - set low and high value
483    /// default: low = 0.0, high = 1.0
484    ///
485    /// Maps grayscale range from [0.0, 1.0] -> [low, high].
486    /// These are the brightness values used for calculating the palette later on.
487    /// 
488    /// # Safety
489    /// will panic if
490    /// * `low` >= `high`
491    /// * `low` < 0
492    /// * `low` >= 1
493    /// * `high` <= 0
494    /// * `high` > 1
495    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    /// #Set hue intensity. Builder pattern
507    /// Valid values are 0.0 <= hue <= 1.0.
508    /// **Important** Will panic on invalid hue values!
509    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    /// #Set rotation. Builder pattern
520    /// Rotation in color space. The higher the value, the quicker the colors will change in the palett.
521    ///
522    /// Normally the range used is -1.5 <= rotation <= 1.5. Invalid values are Nan, or ±Infinity
523    /// **Important** Will panic on invalid rotation values!
524    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    /// Calculate color from gray value.
535    /// Gray value should be in the interval [0.0,1.0].
536    /// 
537    /// Will return `[red, green, blue]`, where red, green and blue are in [0.0, 1.0],
538    /// will return \[0,0,0\] for NAN gray value.
539    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;    // map [0,1] -> [low, high]
549        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,   // red
557            lg + a * (-0.29227 * c_phi - 0.90649 * s_phi),  // green
558            lg +  a * c_phi * 1.97294                       // blue
559        ]
560    }
561
562    /// * Calculate color from gray value.
563    /// * Gray value should be in the interval [0.0,1.0].
564    /// * will return `ColorRgb::new(0,0,0)` for NAN gray value
565    /// 
566    /// will return corresponding (approximate) [`ColorRgb`](crate::heatmap::ColorRGB)
567    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    /// Converts `self` into the corresponding enum of [`GnuplotPallet`](crate::heatmap::GnuplotPalette)
582    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/// # RGB value
605/// * stores a color in RGB space
606/// * default color is black `[0,0,0]`
607#[derive(Debug, Clone, Copy)]
608#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
609pub struct ColorRGB{
610    /// The red part
611    pub red: u8,
612    /// The green part
613    pub green: u8,
614    /// The blue part
615    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    /// # Create a new color
627    pub fn new(red: u8, green: u8, blue: u8) -> Self
628    {
629        Self{
630            red,
631            green, 
632            blue
633        }
634    }
635
636    /// # Create color from an array
637    /// * `color[0]` -> red
638    /// * `color[1]` -> green
639    /// * `color[2]` -> blue
640    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    /// # convert color to array,
650    /// will return `[red, green, blue]`
651    pub fn to_array(&self) -> [u8;3]
652    {
653        [self.red, self.green, self.blue]
654    }
655
656    /// # Turn into hex representation
657    /// ```
658    /// use sampling::heatmap::ColorRGB;
659    /// 
660    /// let color = ColorRGB::new(0,0,0);
661    /// let hex = color.to_hex();
662    /// assert_eq!(&hex, "#000000");
663    /// 
664    /// let color = ColorRGB::new_from_array(&[255,255,255]);
665    /// let hex = color.to_hex();
666    /// assert_eq!(&hex, "#FFFFFF");
667    /// ```
668    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    /// # Write hex representation to a fmt writer
677    /// * similar to [`to_hex`](crate::heatmap::ColorRGB::to_hex), but writes to fmt writer instead
678    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    /// # Write hex representation to a io writer
690    /// * similar to [`to_hex`](crate::heatmap::ColorRGB::to_hex), but writes to io writer instead
691    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/// # A color palette in RGB space
704/// * used for [GnuplotPalette](crate::heatmap::GnuplotPalette)
705#[derive(Debug, Clone)]
706#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
707pub struct PaletteRGB{
708    colors: Vec<ColorRGB>
709}
710
711impl PaletteRGB{
712    /// # Initialize Palette
713    /// Note: Palette needs at least two colors,
714    /// therefore `None` will be returned if the 
715    /// `colors` has a length less than 2
716    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    /// # add a color to the palette
730    pub fn add_color(&mut self, color: ColorRGB){
731        self.colors.push(color)
732    }
733
734    /// # write string to define this palette in gnuplot to fmt writer
735    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    /// # write string to define this palette in gnuplot to io writer
752    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    /// Converts `self` into the corresponding enum of [`GnuplotPallet`](crate::heatmap::GnuplotPalette)
769    pub fn into_gnuplot_palette(self) -> GnuplotPalette
770    {
771        self.into()
772    }
773}
774
775/// # Defines gnuplot point
776/// * Note that most of the fields are public and
777///     can be accessed directly
778#[derive(Debug, Clone)]
779#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
780pub struct GnuplotPointSettings
781{
782    /// Color of the point
783    pub color: ColorRGB,
784    size: f32,
785    /// should the point have a frame?
786    pub frame: bool,
787    /// Which color should the frame be?
788    pub frame_color: ColorRGB,
789    /// Entry for legend
790    legend: String
791}
792
793impl GnuplotPointSettings{
794    /// Create a new instance of GnuplotPointSettings
795    /// - same as GnuplotPointSettings::default()
796    pub fn new() -> Self
797    {
798        Self::default()
799    }
800
801    /// # Choose the color for the point
802    /// * default color is blue
803    pub fn color(&mut self, color: ColorRGB) -> &mut Self
804    {
805        self.color = color;
806        self
807    }
808
809    /// # Choose the size of the point
810    /// * size has to be finite
811    /// * size has to be >= 0.0
812    /// 
813    /// Otherwise the old size will be silently kept
814    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    /// # Get the point size
824    /// Note: allmost all other fields are public!
825    pub fn get_size(&self) -> f32
826    {
827        self.size
828    }
829
830    /// Should there be a frame around the point ?
831    /// This is good for better visibility if you do not know the color
832    /// of the background, or the background color changes
833    pub fn frame(&mut self, active: bool) -> &mut Self
834    {
835        self.frame = active;
836        self
837    }
838
839    /// # Which color should the frame have?
840    /// *default color is black
841    pub fn frame_color(&mut self, color: ColorRGB) -> &mut Self
842    {
843        self.frame_color = color;
844        self
845    }
846
847    /// # Change the legend entry
848    /// * This will be the title of the legend for this point(s)
849    /// * will be set to "Invalid character encountered" if it contains a " or newline character
850    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    /// # Get entry for legend
864    /// This will be the title of the legend for this point(s)
865    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))]
898/// defines presets for different color palettes
899pub enum GnuplotPalette{
900    /// Use preset HSV palette
901    PresetHSV,
902    /// Use preset RGB palette, i.e., the 
903    /// default palette of gnuplot
904    PresetRGB,
905    /// Use a CubeHelix palette
906    /// 
907    /// What makes this palette special is,
908    /// that, if it is converted to black and white,
909    /// it will be monotonically increasing in perceived brightness
910    /// (or monotonically decreasing, if you reverse the palette),
911    /// which is nice for heatmaps
912    ///
913    /// For more info see [`CubeHelixParameter`](crate::heatmap::CubeHelixParameter)
914    CubeHelix(CubeHelixParameter),
915    /// Define a palette in RGB space
916    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))]
987/// # Options for choosing gnuplot Terminal
988pub enum GnuplotTerminal{
989    /// # Use EpsLatex as terminal in gnuplot
990    /// * The String here is the output name, i.e., the filepath of the output of gnuplot (without the .tex)
991    ///   Only alphanumeric characters, space and underscore are allowed,
992    ///   all other characters will be ignored
993    /// * the created gnuplot script assumes, you have `latexmk` installed
994    /// * if you do not have latexmk, you can still use this, but you have to manually edit the 
995    ///     gnuplot scrip later on
996    /// * gnuplot script will create `.tex` file and `.pdf` file created from the tex file
997    /// 
998    /// ## WARNING
999    /// The created gnuplot script will contain a system call to latexmk, such that a pdf file is generated from a tex file
1000    /// without the need for calling latexmk afterwards. 
1001    EpsLatex(String),
1002    /// # Use pdf as gnuplot terminal
1003    /// * gnuplot script will create a `.pdf` file
1004    /// * The String here is the output name, i.e., the filepath of the output of gnuplot (without the .pdf)
1005    PDF(String),
1006    /// # Does not specify a terminal
1007    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}