use std::{fmt::Display, slice};
use gnuplot::{
Axes2D,
AxesCommon,
BorderLocation2D,
ColorType,
DashType,
LabelOption,
PlotOption,
TickOption,
};
use num_traits::AsPrimitive;
use crate::plot::{
AxisFormat,
COLORS,
DEFAULT_FONT_FAMILY,
DEFAULT_FONT_SIZE,
Plot,
auto_option,
init_scaler,
};
#[derive(Default, Clone)]
pub struct ScatterPlot {
font_type: Option<String>,
font_size: Option<f64>,
title: Option<String>,
x_label: Option<String>,
y_label: Option<String>,
x_min: Option<f64>,
x_max: Option<f64>,
y_min: Option<f64>,
y_max: Option<f64>,
x_tick: Option<f64>,
y_tick: Option<f64>,
x_format: Option<AxisFormat>,
y_format: Option<AxisFormat>,
x_log_base: Option<f64>,
y_log_base: Option<f64>,
points: Vec<Point>,
}
#[derive(Clone)]
pub struct Point {
x: f64,
y: f64,
symbol: char,
size: f64,
color: ColorType,
}
impl Plot for ScatterPlot {
fn is_empty(&self) -> bool {
self.points.is_empty()
}
fn set_font_type<T>(&mut self, font_type: T)
where
T: AsRef<str>,
{
self.font_type = Some(font_type.as_ref().to_string());
}
fn with_font_type<T>(mut self, font_type: T) -> Self
where
T: AsRef<str>,
{
self.set_font_type(font_type);
self
}
fn set_font_size(&mut self, font_size: impl AsPrimitive<f64>) {
self.font_size = Some(font_size.as_());
}
fn with_font_size(mut self, font_size: impl AsPrimitive<f64>) -> Self {
self.set_font_size(font_size);
self
}
fn set_title<T>(&mut self, title: T)
where
T: Display,
{
self.title = Some(title.to_string());
}
fn with_title<T>(mut self, title: T) -> Self
where
T: Display,
{
self.set_title(title);
self
}
fn set_x_label<T>(&mut self, label: T)
where
T: Display,
{
self.x_label = Some(label.to_string());
}
fn with_x_label<T>(mut self, label: T) -> Self
where
T: Display,
{
self.set_x_label(label);
self
}
fn set_y_label<T>(&mut self, label: T)
where
T: Display,
{
self.y_label = Some(label.to_string());
}
fn with_y_label<T>(mut self, label: T) -> Self
where
T: Display,
{
self.set_y_label(label);
self
}
fn configure(&mut self, axes: &mut Axes2D) {
let font = LabelOption::Font(
self.font_type
.as_deref()
.unwrap_or(DEFAULT_FONT_FAMILY),
self.font_size.unwrap_or(DEFAULT_FONT_SIZE),
);
let x_scaler = init_scaler(self.x_format, self.max_x_value());
let y_scaler = init_scaler(self.y_format, self.max_y_value());
axes.set_border(
false,
&[
BorderLocation2D::Top,
BorderLocation2D::Right,
BorderLocation2D::Bottom,
BorderLocation2D::Left,
],
&[],
)
.set_x_range(
auto_option(self.x_min, x_scaler.as_ref()),
auto_option(self.x_max, x_scaler.as_ref()),
)
.set_y_range(
auto_option(self.y_min, y_scaler.as_ref()),
auto_option(self.y_max, y_scaler.as_ref()),
)
.set_x_ticks(
Some((auto_option(self.x_tick, x_scaler.as_ref()), 0)),
&[
TickOption::Mirror(false),
TickOption::Inward(false),
],
slice::from_ref(&font),
)
.set_y_ticks(
Some((auto_option(self.y_tick, y_scaler.as_ref()), 0)),
&[
TickOption::Mirror(false),
TickOption::Inward(false),
],
slice::from_ref(&font),
)
.set_grid_options(false, &[
PlotOption::Color(ColorType::RGBString("#bbbbbb")),
PlotOption::LineWidth(2.0),
PlotOption::LineStyle(DashType::Dot),
])
.set_x_grid(true)
.set_y_grid(true);
if let Some(title) = &self.title {
axes.set_title(title, slice::from_ref(&font));
}
if let Some(x_label) = &self.x_label {
axes.set_x_label(&x_scaler.apply_unit(x_label), slice::from_ref(&font));
}
if let Some(y_label) = &self.y_label {
axes.set_y_label(&y_scaler.apply_unit(y_label), &[font]);
}
if let Some(base) = self.x_log_base {
axes.set_x_log(Some(base));
}
if let Some(base) = self.y_log_base {
axes.set_y_log(Some(base));
}
for point in &self.points {
axes.points([x_scaler.scale(point.x)], [y_scaler.scale(point.y)], &[
PlotOption::PointSymbol(point.symbol),
PlotOption::PointSize(point.size),
PlotOption::Color(point.color.to_ref()),
]);
}
}
}
impl ScatterPlot {
pub fn set_x_min(&mut self, x_min: impl AsPrimitive<f64>) {
self.x_min = Some(x_min.as_());
}
pub fn with_x_min(mut self, x_min: impl AsPrimitive<f64>) -> Self {
self.set_x_min(x_min);
self
}
pub fn set_x_max(&mut self, x_max: impl AsPrimitive<f64>) {
self.x_max = Some(x_max.as_());
}
pub fn with_x_max(mut self, x_max: impl AsPrimitive<f64>) -> Self {
self.set_x_max(x_max);
self
}
pub fn set_y_min(&mut self, y_min: impl AsPrimitive<f64>) {
self.y_min = Some(y_min.as_());
}
pub fn with_y_min(mut self, y_min: impl AsPrimitive<f64>) -> Self {
self.set_y_min(y_min);
self
}
pub fn set_y_max(&mut self, y_max: impl AsPrimitive<f64>) {
self.y_max = Some(y_max.as_());
}
pub fn with_y_max(mut self, y_max: impl AsPrimitive<f64>) -> Self {
self.set_y_max(y_max);
self
}
pub fn set_x_tick(&mut self, x_tick: impl AsPrimitive<f64>) {
self.x_tick = Some(x_tick.as_());
}
pub fn with_x_tick(mut self, x_tick: impl AsPrimitive<f64>) -> Self {
self.set_x_tick(x_tick);
self
}
pub fn set_y_tick(&mut self, y_tick: impl AsPrimitive<f64>) {
self.y_tick = Some(y_tick.as_());
}
pub fn with_y_tick(mut self, y_tick: impl AsPrimitive<f64>) -> Self {
self.set_y_tick(y_tick);
self
}
pub fn set_x_format(&mut self, format_type: AxisFormat) {
if let AxisFormat::Log(base) = format_type {
self.x_log_base = Some(base);
return;
}
self.x_format = Some(format_type);
}
pub fn with_x_format(mut self, format_type: AxisFormat) -> Self {
self.set_x_format(format_type);
self
}
pub fn set_y_format(&mut self, format_type: AxisFormat) {
if let AxisFormat::Log(base) = format_type {
self.y_log_base = Some(base);
return;
}
self.y_format = Some(format_type);
}
pub fn with_y_format(mut self, format_type: AxisFormat) -> Self {
self.set_y_format(format_type);
self
}
pub fn point(&mut self, point: Point) {
self.points.push(point);
}
fn max_x_value(&self) -> f64 {
let mut max = self.x_max;
for point in &self.points {
if max.is_none_or(|value| value < point.x) {
max = Some(point.x);
}
}
max.unwrap_or(0.0)
}
fn max_y_value(&self) -> f64 {
let mut max = self.y_max;
for point in &self.points {
if max.is_none_or(|value| value < point.y) {
max = Some(point.y);
}
}
max.unwrap_or(0.0)
}
}
impl Point {
pub fn new(x: impl AsPrimitive<f64>, y: impl AsPrimitive<f64>) -> Self {
Point {
x: x.as_(),
y: y.as_(),
symbol: 'o',
size: 1.0,
color: COLORS[0].into(),
}
}
pub fn set_symbol(&mut self, symbol: char) {
self.symbol = symbol;
}
pub fn with_symbol(mut self, symbol: char) -> Self {
self.set_symbol(symbol);
self
}
pub fn set_size(&mut self, size: impl AsPrimitive<f64>) {
self.size = size.as_();
}
pub fn with_size(mut self, size: impl AsPrimitive<f64>) -> Self {
self.set_size(size);
self
}
pub fn set_color<T>(&mut self, color: T)
where
T: AsRef<str>,
{
self.color = color.as_ref().to_string().into();
}
pub fn with_color<T>(mut self, color: T) -> Self
where
T: AsRef<str>,
{
self.set_color(color);
self
}
}