use plotters::element::{Drawable, PointCollection};
use plotters::style::RGBColor;
use plotters_backend::{BackendCoord, DrawingBackend, DrawingErrorKind};
use crate::stats::{calibration_curve, StatsError};
use crate::style::{stroke_style, translucent_fill};
const DEFAULT_COLOR: RGBColor = RGBColor(0, 158, 115); const DIAGONAL_GRAY: RGBColor = RGBColor(150, 150, 150);
#[derive(Debug, Clone)]
pub struct CalibrationCurve {
points: Vec<(f64, f64)>,
n_pts: usize,
color: RGBColor,
stroke_width: u32,
marker_radius: u32,
show_markers: bool,
show_diagonal: bool,
}
impl CalibrationCurve {
pub fn from_scores(scores: &[f64], labels: &[bool], n_bins: usize) -> Result<Self, StatsError> {
let bins = calibration_curve(scores, labels, n_bins)?;
let mut points: Vec<(f64, f64)> = bins
.iter()
.map(|b| (b.mean_predicted, b.observed_freq))
.collect();
let n_pts = points.len();
points.push((0.0, 0.0));
points.push((1.0, 1.0));
Ok(Self {
points,
n_pts,
color: DEFAULT_COLOR,
stroke_width: 2,
marker_radius: 4,
show_markers: true,
show_diagonal: true,
})
}
pub fn diagonal(mut self, show: bool) -> Self {
self.show_diagonal = show;
self
}
pub fn markers(mut self, show: bool) -> Self {
self.show_markers = show;
self
}
pub fn color(mut self, color: RGBColor) -> Self {
self.color = color;
self
}
pub fn stroke_width(mut self, width: u32) -> Self {
self.stroke_width = width;
self
}
}
impl<'a> PointCollection<'a, (f64, f64)> for &'a CalibrationCurve {
type Point = &'a (f64, f64);
type IntoIter = &'a [(f64, f64)];
fn point_iter(self) -> &'a [(f64, f64)] {
&self.points
}
}
impl<DB: DrawingBackend> Drawable<DB> for CalibrationCurve {
fn draw<I: Iterator<Item = BackendCoord>>(
&self,
points: I,
backend: &mut DB,
_parent_dim: (u32, u32),
) -> Result<(), DrawingErrorKind<DB::ErrorType>> {
let pix: Vec<BackendCoord> = points.collect();
if pix.len() < self.n_pts + 2 {
return Ok(());
}
if self.show_diagonal {
backend.draw_line(
pix[self.n_pts],
pix[self.n_pts + 1],
&stroke_style(&DIAGONAL_GRAY, 1),
)?;
}
let curve = &pix[..self.n_pts];
if curve.len() >= 2 {
backend.draw_path(
curve.iter().copied(),
&stroke_style(&self.color, self.stroke_width),
)?;
}
if self.show_markers {
let fill = translucent_fill(&self.color, 0.9);
for p in curve {
backend.draw_circle(*p, self.marker_radius, &fill, true)?;
}
}
Ok(())
}
}