use self::{ball::*, dashed_line::*};
use crate::*;
#[doc(no_inline)]
pub use plotters::{prelude::*, *};
use std::{
borrow::Cow,
cell::{Ref, RefCell},
rc::Rc,
};
mod ball;
mod dashed_line;
pub mod plot2d;
pub mod plot3d;
pub(crate) type PResult<T, B> = Result<T, DrawingAreaErrorKind<<B as DrawingBackend>::ErrorType>>;
pub(crate) type Canvas<B> = DrawingArea<B, coord::Shift>;
macro_rules! inner_opt {
($($(#[$meta:meta])+ fn $name:ident($ty:ty))+) => {$(
$(#[$meta])+
pub fn $name(mut self, $name: $ty) -> Self {
self.$name = $name;
self
}
)+};
}
pub(crate) fn formatter(v: &f64) -> String {
let mut s = format!("{v:.04}");
let sub = s.trim_end_matches('0');
s.truncate(sub.strip_suffix('.').unwrap_or(sub).len());
if s == "-0" {
s.remove(0);
}
s
}
pub struct ExtBound<const N: usize> {
pub min: [f64; N],
pub max: [f64; N],
}
impl<const N: usize> ExtBound<N> {
pub fn from_pts<'a, I>(iter: I) -> Self
where
I: IntoIterator<Item = &'a [f64; N]>,
{
let init = Self {
min: [f64::INFINITY; N],
max: [f64::NEG_INFINITY; N],
};
iter.into_iter().fold(init, |mut bound, p| {
p.iter()
.zip(&mut bound.min)
.zip(&mut bound.max)
.for_each(|((p, min), max)| {
*min = min.min(*p);
*max = max.max(*p);
});
bound
})
}
pub fn map_to<F, R>(self, f: F) -> [R; N]
where
F: Fn(f64, f64) -> R,
{
std::array::from_fn(|i| f(self.min[i], self.max[i]))
}
pub fn center(&self) -> [f64; N] {
std::array::from_fn(|i| (self.min[i] + self.max[i]) * 0.5)
}
pub fn to_square(mut self, margin: f64) -> Self {
let center = self.center();
let width = self
.min
.iter()
.zip(&self.max)
.map(|(min, max)| (max - min).abs())
.max_by(|a, b| a.partial_cmp(b).unwrap())
.unwrap()
* 0.5
* (1. + margin);
self.min
.iter_mut()
.zip(&mut self.max)
.zip(¢er)
.for_each(|((min, max), center)| {
*min = center - width;
*max = center + width;
});
self
}
}
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[derive(Clone, Copy, PartialEq, Eq, Default)]
#[non_exhaustive]
pub enum Style {
Line,
DashedLine,
#[default]
Circle,
Dot,
Triangle,
Cross,
Square,
}
impl Style {
pub const LIST: [Self; 7] = [
Self::Line,
Self::DashedLine,
Self::Circle,
Self::Dot,
Self::Triangle,
Self::Cross,
Self::Square,
];
pub const fn name(&self) -> &'static str {
match self {
Self::Line => "Line",
Self::DashedLine => "Dashed Line",
Self::Circle => "Circle",
Self::Dot => "Dot",
Self::Triangle => "Triangle",
Self::Cross => "Cross",
Self::Square => "Square",
}
}
pub(crate) fn draw<'a, DB, CT, I>(
&self,
chart: &mut ChartContext<'a, DB, CT>,
line: I,
color: ShapeStyle,
label: &str,
font: i32,
) -> PResult<(), DB>
where
DB: DrawingBackend + 'a,
CT: CoordTranslate,
CT::From: Clone + 'static,
I: Iterator<Item = CT::From> + Clone,
{
let dot_size = color.stroke_width * 2;
let gap = color.stroke_width as i32;
let has_label = !label.is_empty();
macro_rules! impl_marker {
($mk:ident) => {{
let line = line.into_iter().map(|c| $mk::new(c, dot_size, color));
let anno = chart.draw_series(line)?;
if has_label {
anno.label(label)
.legend(move |(x, y)| $mk::new((x + font / 2, y), dot_size, color));
}
}};
}
match self {
Self::Line => {
let line = LineSeries::new(line, color);
let anno = chart.draw_series(line)?;
if has_label {
anno.label(label).legend(move |(x, y)| {
PathElement::new([(x + gap, y), (x + font - gap, y)], color)
});
}
}
Self::DashedLine => {
let series = DashedPath::new(line, 10, 5, color).series();
let anno = chart.draw_series(series)?;
if has_label {
anno.label(label).legend(move |(x, y)| {
DashedPath::new([(x + gap, y), (x + font - gap, y)], 10, 5, color)
});
}
}
Self::Circle => impl_marker!(Circle),
Self::Dot => {
let color = color.filled();
let line = line.into_iter().map(|c| Circle::new(c, dot_size, color));
let anno = chart.draw_series(line)?;
if has_label {
anno.label(label).legend(move |c| {
EmptyElement::at(c)
+ Circle::new((gap, 0), dot_size, color)
+ Circle::new((font / 2, 0), dot_size, color)
+ Circle::new((font - gap, 0), dot_size, color)
});
}
}
Self::Triangle => impl_marker!(TriangleMarker),
Self::Cross => impl_marker!(Cross),
Self::Square => {
let r = color.stroke_width as i32;
let line = line
.into_iter()
.map(|c| EmptyElement::at(c) + Rectangle::new([(r, r), (-r, -r)], color));
let anno = chart.draw_series(line)?;
if has_label {
anno.label(label).legend(move |(x, y)| {
EmptyElement::at((x + font / 2, y))
+ Rectangle::new([(r, r), (-r, -r)], color)
});
}
}
}
Ok(())
}
}
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)]
pub enum LegendPos {
Hide,
UL,
ML,
LL,
UM,
MM,
LM,
#[default]
UR,
MR,
LR,
#[cfg_attr(feature = "clap", clap(skip))]
Coord(i32, i32),
}
impl LegendPos {
pub const LIST: [Self; 10] = [
Self::Hide,
Self::UL,
Self::ML,
Self::LL,
Self::UM,
Self::MM,
Self::LM,
Self::UR,
Self::MR,
Self::LR,
];
pub const fn name(&self) -> &'static str {
match self {
Self::Hide => "Hide",
Self::UL => "Upper Left",
Self::ML => "Middle Left",
Self::LL => "Lower Left",
Self::UM => "Upper Middle",
Self::MM => "Middle Middle",
Self::LM => "Lower Middle",
Self::UR => "Upper Right",
Self::MR => "Middle Right",
Self::LR => "Lower Right",
Self::Coord(_, _) => "Coordinate",
}
}
pub fn to_plotter_pos(&self) -> Option<SeriesLabelPosition> {
use SeriesLabelPosition::*;
Some(match self {
Self::Hide => None?,
Self::UL => UpperLeft,
Self::ML => MiddleLeft,
Self::LL => LowerLeft,
Self::UM => UpperMiddle,
Self::MM => MiddleMiddle,
Self::LM => LowerMiddle,
Self::UR => UpperRight,
Self::MR => MiddleRight,
Self::LR => LowerRight,
&Self::Coord(x, y) => Coordinate(x, y),
})
}
}
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(default)
)]
pub struct LineData<'a, C: Clone> {
pub label: Cow<'a, str>,
pub line: Cow<'a, [C]>,
pub style: Style,
pub color: [u8; 3],
pub filled: bool,
}
impl<'a, C: Clone> Default for LineData<'a, C> {
fn default() -> Self {
Self {
label: Cow::Borrowed(""),
line: Cow::Borrowed(&[]),
style: Style::default(),
color: [0; 3],
filled: false,
}
}
}
impl<'a, C: Clone> LineData<'a, C> {
pub(crate) fn color(&self) -> (RGBAColor, bool) {
let color = RGBAColor(self.color[0], self.color[1], self.color[2], 1.);
(color, self.filled)
}
}
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(default)
)]
#[derive(Clone)]
pub struct FigureBase<'a, 'b, M: Clone, C: Clone> {
pub fb: Option<Cow<'b, M>>,
pub angle: Option<f64>,
pub lines: Vec<Rc<RefCell<LineData<'a, C>>>>,
pub opt: Opt<'a>,
}
impl<M: Clone, C: Clone> Default for FigureBase<'_, '_, M, C> {
fn default() -> Self {
Self {
fb: None,
angle: None,
opt: Default::default(),
lines: Default::default(),
}
}
}
impl<'a, 'b, M: Clone, C: Clone> FigureBase<'a, 'b, M, C> {
pub fn new(fb: Option<M>) -> Self {
Self { fb: fb.map(Cow::Owned), ..Default::default() }
}
pub fn new_ref(fb: Option<&'b M>) -> Self {
Self { fb: fb.map(Cow::Borrowed), ..Default::default() }
}
pub fn with_fb(self, fb: M) -> Self {
FigureBase { fb: Some(Cow::Owned(fb)), ..self }
}
pub fn with_fb_ref(self, fb: &'b M) -> Self {
FigureBase { fb: Some(Cow::Borrowed(fb)), ..self }
}
pub fn remove_fb(self) -> Self {
Self { fb: None, ..self }
}
pub fn angle(self, angle: f64) -> Self {
Self { angle: Some(angle), ..self }
}
pub fn font_family(mut self, family: impl Into<Cow<'a, str>>) -> Self {
self.font_family.replace(family.into());
self
}
inner_opt! {
fn stroke(u32)
fn font(f64)
fn grid(bool)
fn axis(bool)
fn legend(LegendPos)
}
pub fn with_opt(self, opt: Opt<'a>) -> Self {
Self { opt, ..self }
}
pub fn add_line<S, L>(self, label: S, line: L, style: Style, color: RGBColor) -> Self
where
S: Into<Cow<'a, str>>,
L: Into<Cow<'a, [C]>>,
{
let color = ShapeStyle::from(color);
self.add_line_data(LineData {
label: label.into(),
line: line.into(),
style,
color: [color.color.0, color.color.1, color.color.2],
filled: color.filled,
})
}
pub fn add_line_data(mut self, data: LineData<'a, C>) -> Self {
self.push_line_data(data);
self
}
pub fn push_line<S, L>(&mut self, label: S, line: L, style: Style, color: RGBColor)
where
S: Into<Cow<'a, str>>,
L: Into<Cow<'a, [C]>>,
{
let color = ShapeStyle::from(color);
self.push_line_data(LineData {
label: label.into(),
line: line.into(),
style,
color: [color.color.0, color.color.1, color.color.2],
filled: color.filled,
});
}
pub fn push_line_data(&mut self, data: LineData<'a, C>) {
self.lines.push(Rc::new(RefCell::new(data)));
}
pub fn push_line_default<S, L>(&mut self, label: S, line: L)
where
S: Into<Cow<'a, str>>,
L: Into<Cow<'a, [C]>>,
{
self.push_line_data(LineData {
label: label.into(),
line: line.into(),
..Default::default()
});
}
pub fn lines(&self) -> impl Iterator<Item = Ref<LineData<'a, C>>> {
self.lines.iter().map(|packed| packed.borrow())
}
pub fn lines_mut(&mut self) -> &mut Vec<Rc<RefCell<LineData<'a, C>>>> {
&mut self.lines
}
#[inline]
pub(crate) fn check_empty<B: DrawingBackend>(&self) -> PResult<(), B> {
(!self.lines.is_empty() || self.fb.is_some())
.then_some(())
.ok_or(DrawingAreaErrorKind::LayoutError)
}
pub(crate) fn get_joints<D: efd::EfdDim>(&self) -> Option<[efd::Coord<D>; 5]>
where
M: fb::CurveGen<D>,
{
use std::f64::consts::TAU;
let fb = self.fb.as_deref()?;
let [start, end] = fb.angle_bound().to_value()?;
let end = if end > start { end } else { end + TAU };
let angle = match self.angle {
Some(angle) if (start..end).contains(&angle) => angle,
_ => start + (end - start) * 0.8,
};
fb.pos(angle)
}
pub(crate) fn get_dot_size(&self) -> (u32, u32) {
(self.stroke, (self.stroke as f32 * 1.5) as u32)
}
#[inline]
fn get_family(&self) -> &str {
const DEFAULT_FONT: &str = "Times New Roman";
self.font_family.as_deref().unwrap_or(DEFAULT_FONT)
}
pub(crate) fn get_font(&self) -> TextStyle {
(self.get_family(), self.font).into_font().color(&BLACK)
}
pub(crate) fn get_font3d(&self) -> TextStyle {
(self.get_family(), self.font * 1.3)
.into_font()
.color(&BLACK)
}
}
impl<'a, M: Clone, C: Clone> std::ops::Deref for FigureBase<'a, '_, M, C> {
type Target = Opt<'a>;
fn deref(&self) -> &Self::Target {
&self.opt
}
}
impl<'a, M: Clone, C: Clone> std::ops::DerefMut for FigureBase<'a, '_, M, C> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.opt
}
}
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(default)
)]
#[derive(Clone, PartialEq)]
pub struct Opt<'a> {
pub stroke: u32,
pub font: f64,
pub font_family: Option<Cow<'a, str>>,
pub grid: bool,
pub axis: bool,
pub legend: LegendPos,
}
impl Default for Opt<'_> {
fn default() -> Self {
Self {
stroke: 7,
font: 90.,
font_family: None,
grid: false,
axis: true,
legend: LegendPos::default(),
}
}
}