plotters_statistical/series/
qq_plot.rs1use plotters::element::{Drawable, PointCollection};
5use plotters::style::RGBColor;
6use plotters_backend::{BackendCoord, DrawingBackend, DrawingErrorKind};
7
8use crate::stats::{norm_ppf, quartiles, sorted_finite, StatsError};
9use crate::style::{stroke_style, translucent_fill};
10
11const DEFAULT_COLOR: RGBColor = RGBColor(0, 114, 178); const LINE_GRAY: RGBColor = RGBColor(120, 120, 120);
13
14#[derive(Debug, Clone)]
21pub struct QqPlot {
22 points: Vec<(f64, f64)>,
24 n: usize,
25 marker_radius: u32,
26 color: RGBColor,
27 line_width: u32,
28 show_line: bool,
29}
30
31impl QqPlot {
32 pub fn from_data(data: &[f64]) -> Result<Self, StatsError> {
40 let sorted = sorted_finite(data);
41 if sorted.is_empty() {
42 return Err(StatsError::EmptyInput);
43 }
44 let n = sorted.len();
45 let nf = n as f64;
46 let mut pts: Vec<(f64, f64)> = Vec::with_capacity(n);
47 for (i, &s) in sorted.iter().enumerate() {
48 let p = ((i as f64 + 1.0) - 0.375) / (nf + 0.25);
49 pts.push((norm_ppf(p), s));
50 }
51
52 let q = quartiles(&sorted)?;
54 let tx1 = norm_ppf(0.25);
55 let tx3 = norm_ppf(0.75);
56 let (slope, intercept) = if tx3 > tx1 {
57 let m = (q.q3 - q.q1) / (tx3 - tx1);
58 (m, q.q1 - m * tx1)
59 } else {
60 (1.0, 0.0)
61 };
62 let x_lo = pts.first().map(|p| p.0).unwrap_or(-3.0);
63 let x_hi = pts.last().map(|p| p.0).unwrap_or(3.0);
64 pts.push((x_lo, slope * x_lo + intercept));
65 pts.push((x_hi, slope * x_hi + intercept));
66
67 Ok(Self {
68 points: pts,
69 n,
70 marker_radius: 3,
71 color: DEFAULT_COLOR,
72 line_width: 1,
73 show_line: true,
74 })
75 }
76
77 pub fn reference_line(mut self, show: bool) -> Self {
79 self.show_line = show;
80 self
81 }
82
83 pub fn color(mut self, color: RGBColor) -> Self {
85 self.color = color;
86 self
87 }
88
89 pub fn marker_radius(mut self, radius: u32) -> Self {
91 self.marker_radius = radius;
92 self
93 }
94
95 pub fn line_width(mut self, width: u32) -> Self {
97 self.line_width = width;
98 self
99 }
100}
101
102impl<'a> PointCollection<'a, (f64, f64)> for &'a QqPlot {
103 type Point = &'a (f64, f64);
104 type IntoIter = &'a [(f64, f64)];
105 fn point_iter(self) -> &'a [(f64, f64)] {
106 &self.points
107 }
108}
109
110impl<DB: DrawingBackend> Drawable<DB> for QqPlot {
111 fn draw<I: Iterator<Item = BackendCoord>>(
112 &self,
113 points: I,
114 backend: &mut DB,
115 _parent_dim: (u32, u32),
116 ) -> Result<(), DrawingErrorKind<DB::ErrorType>> {
117 let pix: Vec<BackendCoord> = points.collect();
118 if pix.len() < self.n + 2 {
119 return Ok(());
120 }
121 if self.show_line {
122 backend.draw_line(
123 pix[self.n],
124 pix[self.n + 1],
125 &stroke_style(&LINE_GRAY, self.line_width),
126 )?;
127 }
128 let fill = translucent_fill(&self.color, 0.7);
129 for p in &pix[..self.n] {
130 backend.draw_circle(*p, self.marker_radius, &fill, true)?;
131 }
132 Ok(())
133 }
134}