use crate::components::Rgb;
#[derive(Clone)]
pub struct Text {
pub content: String,
pub font: String,
pub size: usize,
pub color: Rgb,
pub x: f64,
pub y: f64,
}
impl Default for Text {
fn default() -> Self {
Text {
content: String::new(),
font: String::new(),
size: 12,
color: Rgb::default(),
x: 0.5,
y: 0.9,
}
}
}
impl Text {
pub fn from(content: impl Into<String>) -> Self {
Self {
content: content.into(),
..Default::default()
}
}
pub fn font(mut self, font: impl Into<String>) -> Self {
self.font = font.into();
self
}
pub fn size(mut self, size: usize) -> Self {
self.size = size;
self
}
pub fn color(mut self, color: Rgb) -> Self {
self.color = color;
self
}
pub fn x(mut self, x: f64) -> Self {
self.x = x;
self
}
pub fn y(mut self, y: f64) -> Self {
self.y = y;
self
}
pub fn has_custom_position(&self) -> bool {
const EPSILON: f64 = 1e-6;
(self.x - 0.5).abs() > EPSILON || (self.y - 0.9).abs() > EPSILON
}
pub fn with_plot_title_defaults(mut self) -> Self {
const EPSILON: f64 = 1e-6;
let y_is_default = (self.y - 0.9).abs() < EPSILON;
if y_is_default {
self.y = 0.95;
}
self
}
pub fn with_subplot_title_defaults(mut self) -> Self {
const EPSILON: f64 = 1e-6;
let y_is_default = (self.y - 0.9).abs() < EPSILON;
let y_is_plot_default = (self.y - 0.95).abs() < EPSILON;
if y_is_default || y_is_plot_default {
self.y = 1.1;
}
self
}
pub fn with_x_title_defaults(mut self) -> Self {
const EPSILON: f64 = 1e-6;
let y_is_default = (self.y - 0.9).abs() < EPSILON;
if y_is_default {
self.y = -0.15;
}
self
}
pub fn with_y_title_defaults(mut self) -> Self {
const EPSILON: f64 = 1e-6;
let x_is_default = (self.x - 0.5).abs() < EPSILON;
let y_is_default = (self.y - 0.9).abs() < EPSILON;
if x_is_default {
self.x = -0.08;
}
if y_is_default {
self.y = 0.5;
}
self
}
pub fn with_x_title_defaults_for_annotation(mut self) -> Self {
const EPSILON: f64 = 1e-6;
let x_is_default = (self.x - 0.5).abs() < EPSILON;
let y_is_default = (self.y - 0.9).abs() < EPSILON;
if x_is_default {
self.x = 0.5;
}
if y_is_default {
self.y = -0.15;
}
self
}
pub fn with_y_title_defaults_for_annotation(mut self) -> Self {
const EPSILON: f64 = 1e-6;
let x_is_default = (self.x - 0.5).abs() < EPSILON;
let y_is_default = (self.y - 0.9).abs() < EPSILON;
if x_is_default {
self.x = -0.08;
}
if y_is_default {
self.y = 0.5;
}
self
}
}
impl From<&str> for Text {
fn from(content: &str) -> Self {
Self::from(content.to_string())
}
}
impl From<String> for Text {
fn from(content: String) -> Self {
Self::from(content)
}
}
impl From<&String> for Text {
fn from(content: &String) -> Self {
Self::from(content)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::components::Rgb;
fn assert_float_eq(a: f64, b: f64) {
assert!(
(a - b).abs() < 1e-6,
"expected {b}, got {a} (diff {})",
(a - b).abs()
);
}
#[test]
fn test_from_str() {
let t = Text::from("hello");
assert_eq!(t.content, "hello");
assert_eq!(t.font, "");
assert_eq!(t.size, 12);
assert_eq!(t.color.0, 0);
assert_eq!(t.color.1, 0);
assert_eq!(t.color.2, 0);
assert_float_eq(t.x, 0.5);
assert_float_eq(t.y, 0.9);
}
#[test]
fn test_from_string() {
let t: Text = String::from("world").into();
assert_eq!(t.content, "world");
}
#[test]
fn test_from_ref_string() {
let s = String::from("ref");
let t: Text = (&s).into();
assert_eq!(t.content, "ref");
}
#[test]
fn test_default_values() {
let t = Text::default();
assert_eq!(t.content, "");
assert_eq!(t.font, "");
assert_eq!(t.size, 12);
assert_eq!(t.color.0, 0);
assert_eq!(t.color.1, 0);
assert_eq!(t.color.2, 0);
assert_float_eq(t.x, 0.5);
assert_float_eq(t.y, 0.9);
}
#[test]
fn test_font() {
let t = Text::from("x").font("Arial");
assert_eq!(t.font, "Arial");
assert_eq!(t.size, 12);
}
#[test]
fn test_size() {
let t = Text::from("x").size(20);
assert_eq!(t.size, 20);
assert_eq!(t.font, "");
}
#[test]
fn test_color() {
let t = Text::from("x").color(Rgb(1, 2, 3));
assert_eq!(t.color.0, 1);
assert_eq!(t.color.1, 2);
assert_eq!(t.color.2, 3);
}
#[test]
fn test_x() {
let t = Text::from("x").x(0.1);
assert_float_eq(t.x, 0.1);
assert_float_eq(t.y, 0.9);
}
#[test]
fn test_y() {
let t = Text::from("x").y(0.2);
assert_float_eq(t.y, 0.2);
assert_float_eq(t.x, 0.5);
}
#[test]
fn test_builder_chaining() {
let t = Text::from("chained")
.font("Courier")
.size(24)
.color(Rgb(10, 20, 30))
.x(0.3)
.y(0.7);
assert_eq!(t.content, "chained");
assert_eq!(t.font, "Courier");
assert_eq!(t.size, 24);
assert_eq!(t.color.0, 10);
assert_eq!(t.color.1, 20);
assert_eq!(t.color.2, 30);
assert_float_eq(t.x, 0.3);
assert_float_eq(t.y, 0.7);
}
#[test]
fn test_has_custom_position_default() {
let t = Text::default();
assert!(!t.has_custom_position());
}
#[test]
fn test_has_custom_position_x_changed() {
let t = Text::from("x").x(0.3);
assert!(t.has_custom_position());
}
#[test]
fn test_has_custom_position_epsilon() {
let t = Text::from("x").x(0.5 + 1e-7);
assert!(!t.has_custom_position());
}
#[test]
fn test_with_plot_title_defaults() {
let t = Text::from("title").with_plot_title_defaults();
assert_float_eq(t.y, 0.95);
let t2 = Text::from("title").y(0.7).with_plot_title_defaults();
assert_float_eq(t2.y, 0.7);
}
#[test]
fn test_with_x_title_defaults() {
let t = Text::from("x").with_x_title_defaults();
assert_float_eq(t.y, -0.15);
}
#[test]
fn test_with_y_title_defaults() {
let t = Text::from("y").with_y_title_defaults();
assert_float_eq(t.x, -0.08);
assert_float_eq(t.y, 0.5);
let t2 = Text::from("y").x(0.2).y(0.3).with_y_title_defaults();
assert_float_eq(t2.x, 0.2);
assert_float_eq(t2.y, 0.3);
}
}