plotters_statistical/series/
calibration_curve.rs1use plotters::element::{Drawable, PointCollection};
5use plotters::style::RGBColor;
6use plotters_backend::{BackendCoord, DrawingBackend, DrawingErrorKind};
7
8use crate::stats::{calibration_curve, StatsError};
9use crate::style::{stroke_style, translucent_fill};
10
11const DEFAULT_COLOR: RGBColor = RGBColor(0, 158, 115); const DIAGONAL_GRAY: RGBColor = RGBColor(150, 150, 150);
13
14#[derive(Debug, Clone)]
17pub struct CalibrationCurve {
18 points: Vec<(f64, f64)>,
20 n_pts: usize,
21 color: RGBColor,
22 stroke_width: u32,
23 marker_radius: u32,
24 show_markers: bool,
25 show_diagonal: bool,
26}
27
28impl CalibrationCurve {
29 pub fn from_scores(scores: &[f64], labels: &[bool], n_bins: usize) -> Result<Self, StatsError> {
32 let bins = calibration_curve(scores, labels, n_bins)?;
33 let mut points: Vec<(f64, f64)> = bins
34 .iter()
35 .map(|b| (b.mean_predicted, b.observed_freq))
36 .collect();
37 let n_pts = points.len();
38 points.push((0.0, 0.0));
39 points.push((1.0, 1.0));
40 Ok(Self {
41 points,
42 n_pts,
43 color: DEFAULT_COLOR,
44 stroke_width: 2,
45 marker_radius: 4,
46 show_markers: true,
47 show_diagonal: true,
48 })
49 }
50
51 pub fn diagonal(mut self, show: bool) -> Self {
53 self.show_diagonal = show;
54 self
55 }
56
57 pub fn markers(mut self, show: bool) -> Self {
59 self.show_markers = show;
60 self
61 }
62
63 pub fn color(mut self, color: RGBColor) -> Self {
65 self.color = color;
66 self
67 }
68
69 pub fn stroke_width(mut self, width: u32) -> Self {
71 self.stroke_width = width;
72 self
73 }
74}
75
76impl<'a> PointCollection<'a, (f64, f64)> for &'a CalibrationCurve {
77 type Point = &'a (f64, f64);
78 type IntoIter = &'a [(f64, f64)];
79 fn point_iter(self) -> &'a [(f64, f64)] {
80 &self.points
81 }
82}
83
84impl<DB: DrawingBackend> Drawable<DB> for CalibrationCurve {
85 fn draw<I: Iterator<Item = BackendCoord>>(
86 &self,
87 points: I,
88 backend: &mut DB,
89 _parent_dim: (u32, u32),
90 ) -> Result<(), DrawingErrorKind<DB::ErrorType>> {
91 let pix: Vec<BackendCoord> = points.collect();
92 if pix.len() < self.n_pts + 2 {
93 return Ok(());
94 }
95 if self.show_diagonal {
96 backend.draw_line(
97 pix[self.n_pts],
98 pix[self.n_pts + 1],
99 &stroke_style(&DIAGONAL_GRAY, 1),
100 )?;
101 }
102 let curve = &pix[..self.n_pts];
103 if curve.len() >= 2 {
104 backend.draw_path(
105 curve.iter().copied(),
106 &stroke_style(&self.color, self.stroke_width),
107 )?;
108 }
109 if self.show_markers {
110 let fill = translucent_fill(&self.color, 0.9);
111 for p in curve {
112 backend.draw_circle(*p, self.marker_radius, &fill, true)?;
113 }
114 }
115 Ok(())
116 }
117}