reservoirs/
plot.rs

1use crate::utils;
2use plotters::prelude::*;
3
4/// Boundary box delineating extent of plotting data.
5pub struct Bbox {
6    xmin: f64,
7    xmax: f64,
8    ymin: f64,
9    ymax: f64,
10}
11
12impl Bbox {
13    fn new(x: &[f64], y: &[f64]) -> Self {
14        let xmin: f64 = x.iter().cloned().fold(f64::NAN, |m, v| v.min(m));
15        let xmax: f64 = x.iter().cloned().fold(f64::NAN, |m, v| v.max(m));
16        let ymin: f64 = y.iter().cloned().fold(f64::NAN, |m, v| v.min(m));
17        let ymax: f64 = y.iter().cloned().fold(f64::NAN, |m, v| v.max(m));
18        Bbox {
19            xmin,
20            xmax,
21            ymin,
22            ymax,
23        }
24    }
25}
26
27/// Generic function for plotting results on the fly.
28pub fn xy(x: &[f64], y: &[f64], path: &str) -> Result<(), Box<dyn std::error::Error>> {
29    let bbox = Bbox::new(x, y);
30    let xy: Vec<(f64, f64)> = x.iter().cloned().zip(y.iter().cloned()).collect();
31    let root = BitMapBackend::new(path, (640, 480)).into_drawing_area();
32    root.fill(&WHITE)?;
33    root.margin(10, 10, 10, 10);
34    let mut chart = ChartBuilder::on(&root)
35        // .caption("Title", ("sans-serif", 16).into_font())
36        .x_label_area_size(40)
37        .y_label_area_size(60)
38        .build_cartesian_2d(bbox.xmin..bbox.xmax, bbox.ymin..bbox.ymax)?;
39    chart
40        .configure_mesh()
41        // .x_labels(5)
42        // .y_labels(5)
43        .y_label_formatter(&|x| format!("{:.2}", x))
44        .x_label_formatter(&|x| format!("{:.2}", x))
45        // .x_desc("Value")
46        // .y_desc("CDF")
47        .draw()?;
48    chart.draw_series(xy.iter().map(|x| Circle::new((x.0, x.1), 2, BLUE.filled())))?;
49
50    Ok(())
51}
52
53/// Compare the CDF of two accumulation records.
54pub fn comp_cdf(a: &[f64], b: &[f64], title: &str) -> Result<(), Box<dyn std::error::Error>> {
55    let a = utils::cdf(a);
56    let b = utils::cdf(b);
57    let mut ab = a.clone();
58    let mut c = b.clone();
59    ab.append(&mut c);
60
61    let ymin = ab.iter().map(|xi| xi.1).fold(0.0, f64::min);
62    let ymax = ab.iter().map(|xi| xi.1).fold(0.0, f64::max);
63    let xmin = ab.iter().map(|xi| xi.0).fold(0.0, f64::min);
64    let xmax = ab.iter().map(|xi| xi.0).fold(0.0, f64::max);
65    let root = BitMapBackend::new(title, (640, 480)).into_drawing_area();
66    root.fill(&WHITE)?;
67    root.margin(10, 10, 10, 10);
68    // construct a chart context
69    let mut chart = ChartBuilder::on(&root)
70        // Set the caption of the chart
71        //        .caption("Title", ("sans-serif", 40).into_font())
72        // Set the size of the label region
73        .x_label_area_size(40)
74        .y_label_area_size(60)
75        // Finally attach a coordinate on the drawing area and make a chart context
76        .build_cartesian_2d(xmin..xmax, ymin..ymax)?;
77
78    // Then we can draw a mesh
79    chart
80        .configure_mesh()
81        // We can customize the maximum number of labels allowed for each axis
82        .x_labels(5)
83        .y_labels(5)
84        // We can also change the format of the label text
85        .y_label_formatter(&|x| format!("{:.2}", x))
86        .x_label_formatter(&|x| format!("{:.2}", x))
87        .x_desc("Value")
88        .y_desc("CDF")
89        .draw()?;
90
91    // And we can draw something in the drawing area
92    chart
93        .draw_series(LineSeries::new(a.clone(), &BLACK))?
94        .label("synthetic")
95        .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], &BLUE));
96    // Similarly, we can draw point series
97    chart.draw_series(PointSeries::of_element(a, 2, &BLUE, &|c, s, st| {
98        return EmptyElement::at(c)    // We want to construct a composed element on-the-fly
99                + Circle::new((0, 0), s, st.filled()); // At this point, the new pixel coordinate is established
100                                                       //                + Text::new(format!("{:?}", c), (10, 0), ("sans-serif", 10).into_font());
101    }))?;
102
103    chart
104        .draw_series(LineSeries::new(b.clone(), &BLACK))?
105        .label("observed")
106        .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], &GREEN));
107    chart.draw_series(PointSeries::of_element(b, 2, &GREEN, &|c, s, st| {
108        return EmptyElement::at(c)    // We want to construct a composed element on-the-fly
109                + Circle::new((0, 0), s, st.filled()); // At this point, the new pixel coordinate is established
110                                                       //                + Text::new(format!("{:?}", c), (10, 0), ("sans-serif", 10).into_font());
111    }))?;
112    chart
113        .configure_series_labels()
114        .background_style(WHITE.filled())
115        .draw()?;
116    Ok(())
117}
118
119/// Scatter plot for comparing goodness-of-fit statistics from model runs.
120pub fn scatter(x: &[f64], y: &[f64], title: &str) -> Result<(), Box<dyn std::error::Error>> {
121    let xy: Vec<(f64, f64)> = x.iter().cloned().zip(y.iter().cloned()).collect();
122
123    let ymin = xy.iter().map(|xi| xi.1).fold(1.0, f64::min);
124    let ymax = xy.iter().map(|xi| xi.1).fold(0.0, f64::max);
125    let xmin = xy.iter().map(|xi| xi.0).fold(1.0, f64::min);
126    let xmax = xy.iter().map(|xi| xi.0).fold(0.0, f64::max);
127    let root = BitMapBackend::new(title, (640, 480)).into_drawing_area();
128    root.fill(&WHITE)?;
129    root.margin(10, 10, 10, 10);
130    let mut chart = ChartBuilder::on(&root)
131        .x_label_area_size(40)
132        .y_label_area_size(60)
133        .build_cartesian_2d(xmin..xmax, ymin..ymax)?;
134
135    chart
136        .configure_mesh()
137        .x_labels(5)
138        .y_labels(5)
139        .y_label_formatter(&|x| format!("{:.2}", x))
140        .x_label_formatter(&|x| format!("{:.2}", x))
141        .x_desc("Rate")
142        .y_desc("Fit")
143        .draw()?;
144    chart.draw_series(xy.iter().map(|x| Circle::new((x.0, x.1), 2, BLUE.filled())))?;
145
146    Ok(())
147}
148
149/// Experimental box-and-whisker plot of transit times.
150pub fn whisker_for_facies(
151    df: &plotters::data::Quartiles,
152    ff: &plotters::data::Quartiles,
153    fg: &plotters::data::Quartiles,
154    title: &str,
155) -> Result<(), Box<dyn std::error::Error>> {
156    let values_range = plotters::data::fitting_range(
157        df.values()
158            .iter()
159            .chain(ff.values().iter().chain(fg.values().iter())),
160    );
161
162    let facies_axis = ["debris flows", "fines", "gravels"];
163    let root = BitMapBackend::new(title, (640, 480)).into_drawing_area();
164    root.fill(&WHITE)?;
165    root.margin(10, 10, 10, 10);
166    // construct a chart context
167    let mut chart = ChartBuilder::on(&root)
168        // Set the caption of the chart
169        //        .caption("Title", ("sans-serif", 40).into_font())
170        // Set the size of the label region
171        .x_label_area_size(40)
172        .y_label_area_size(60)
173        // Finally attach a coordinate on the drawing area and make a chart context
174        .build_cartesian_2d(facies_axis[..].into_segmented(), values_range)?;
175
176    // Then we can draw a mesh
177    chart
178        .configure_mesh()
179        // We can customize the maximum number of labels allowed for each axis
180        .y_labels(5)
181        // We can also change the format of the label text
182        .y_label_formatter(&|x| format!("{:.2}", x))
183        .x_desc("Facies")
184        .y_desc("Transit Time")
185        .draw()?;
186
187    // And we can draw something in the drawing area
188
189    chart.draw_series(vec![
190        Boxplot::new_vertical(SegmentValue::CenterOf(&"debris flows"), df),
191        Boxplot::new_vertical(SegmentValue::CenterOf(&"fines"), ff),
192        Boxplot::new_vertical(SegmentValue::CenterOf(&"gravels"), fg),
193    ])?;
194
195    chart
196        .configure_series_labels()
197        .background_style(WHITE.filled())
198        .draw()?;
199    Ok(())
200}