plotters_statistical/series/
ecdf.rs1use plotters::element::{Drawable, PointCollection};
5use plotters::style::RGBColor;
6use plotters_backend::{BackendCoord, DrawingBackend, DrawingErrorKind};
7
8use crate::stats::{dkw_epsilon, ecdf, StatsError};
9use crate::style::{stroke_style, translucent_fill};
10
11const DEFAULT_COLOR: RGBColor = RGBColor(0, 114, 178); #[derive(Debug, Clone)]
16pub struct Ecdf {
17 ex: Vec<f64>,
18 ep: Vec<f64>,
19 n: usize,
20 complementary: bool,
21 ci_alpha: Option<f64>,
22 points: Vec<(f64, f64)>,
25 n_step: usize,
26 has_band: bool,
27 color: RGBColor,
28 stroke_width: u32,
29 band_opacity: f64,
30 marker_radius: u32,
31 show_markers: bool,
32}
33
34impl Ecdf {
35 pub fn from_data(data: &[f64]) -> Result<Self, StatsError> {
37 let e = ecdf(data)?;
38 let mut this = Self {
39 ex: e.x,
40 ep: e.p,
41 n: e.n,
42 complementary: false,
43 ci_alpha: None,
44 points: Vec::new(),
45 n_step: 0,
46 has_band: false,
47 color: DEFAULT_COLOR,
48 stroke_width: 2,
49 band_opacity: 0.15,
50 marker_radius: 3,
51 show_markers: false,
52 };
53 this.rebuild();
54 Ok(this)
55 }
56
57 pub fn complementary(mut self, yes: bool) -> Self {
59 self.complementary = yes;
60 self.rebuild();
61 self
62 }
63
64 pub fn confidence_band(mut self, alpha: f64) -> Self {
67 self.ci_alpha = Some(alpha);
68 self.rebuild();
69 self
70 }
71
72 pub fn markers(mut self, show: bool) -> Self {
74 self.show_markers = show;
75 self
76 }
77
78 pub fn color(mut self, color: RGBColor) -> Self {
80 self.color = color;
81 self
82 }
83
84 pub fn stroke_width(mut self, width: u32) -> Self {
86 self.stroke_width = width;
87 self
88 }
89
90 fn py(&self, i: usize) -> f64 {
91 if self.complementary {
92 1.0 - self.ep[i]
93 } else {
94 self.ep[i]
95 }
96 }
97
98 fn rebuild(&mut self) {
99 let m = self.ex.len();
100 let base = if self.complementary { 1.0 } else { 0.0 };
101 let mut verts: Vec<(f64, f64)> = Vec::with_capacity(2 * m);
102 verts.push((self.ex[0], base));
103 verts.push((self.ex[0], self.py(0)));
104 for i in 1..m {
105 verts.push((self.ex[i], self.py(i - 1)));
106 verts.push((self.ex[i], self.py(i)));
107 }
108 self.n_step = verts.len();
109
110 self.points = verts.clone();
111 self.has_band = false;
112 if let Some(alpha) = self.ci_alpha {
113 let eps = dkw_epsilon(self.n, alpha);
114 let mut band: Vec<(f64, f64)> = Vec::with_capacity(2 * self.n_step);
115 for &(x, y) in &verts {
116 band.push((x, (y + eps).clamp(0.0, 1.0)));
117 }
118 for &(x, y) in verts.iter().rev() {
119 band.push((x, (y - eps).clamp(0.0, 1.0)));
120 }
121 self.points.extend(band);
122 self.has_band = true;
123 }
124 }
125}
126
127impl<'a> PointCollection<'a, (f64, f64)> for &'a Ecdf {
128 type Point = &'a (f64, f64);
129 type IntoIter = &'a [(f64, f64)];
130 fn point_iter(self) -> &'a [(f64, f64)] {
131 &self.points
132 }
133}
134
135impl<DB: DrawingBackend> Drawable<DB> for Ecdf {
136 fn draw<I: Iterator<Item = BackendCoord>>(
137 &self,
138 points: I,
139 backend: &mut DB,
140 _parent_dim: (u32, u32),
141 ) -> Result<(), DrawingErrorKind<DB::ErrorType>> {
142 let pix: Vec<BackendCoord> = points.collect();
143 if pix.len() < self.n_step {
144 return Ok(());
145 }
146 if self.has_band && pix.len() >= self.n_step * 3 {
147 let band = &pix[self.n_step..self.n_step * 3];
148 backend.fill_polygon(
149 band.iter().copied(),
150 &translucent_fill(&self.color, self.band_opacity),
151 )?;
152 }
153 let step = &pix[..self.n_step];
154 backend.draw_path(
155 step.iter().copied(),
156 &stroke_style(&self.color, self.stroke_width),
157 )?;
158 if self.show_markers {
159 let fill = translucent_fill(&self.color, 0.9);
160 for k in (1..self.n_step).step_by(2) {
161 backend.draw_circle(step[k], self.marker_radius, &fill, true)?;
162 }
163 }
164 Ok(())
165 }
166}