use serde::{Deserialize, Serialize};
use std::sync::Arc;
use crate::plot::ArrayElement;
mod asinh;
mod bool;
mod date;
mod datetime;
mod exp;
mod identity;
mod integer;
mod log;
mod pseudo_log;
mod sqrt;
mod square;
mod string;
mod time;
pub use self::asinh::Asinh;
pub use self::bool::Bool;
pub use self::date::Date;
pub use self::datetime::DateTime;
pub use self::exp::Exp;
pub use self::identity::Identity;
pub use self::integer::Integer;
pub use self::log::Log;
pub use self::pseudo_log::PseudoLog;
pub use self::sqrt::Sqrt;
pub use self::square::Square;
pub use self::string::String as StringTransform;
pub use self::time::Time;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TransformKind {
Identity,
Log10,
Log2,
Log,
Sqrt,
Square,
Exp10,
Exp2,
Exp,
Asinh,
PseudoLog,
Date,
DateTime,
Time,
String,
Bool,
Integer,
}
impl TransformKind {
pub fn is_temporal(&self) -> bool {
matches!(
self,
TransformKind::Date | TransformKind::DateTime | TransformKind::Time
)
}
}
impl std::fmt::Display for TransformKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let name = match self {
TransformKind::Identity => "identity",
TransformKind::Log10 => "log",
TransformKind::Log2 => "log2",
TransformKind::Log => "ln",
TransformKind::Sqrt => "sqrt",
TransformKind::Square => "square",
TransformKind::Exp10 => "exp10",
TransformKind::Exp2 => "exp2",
TransformKind::Exp => "exp",
TransformKind::Asinh => "asinh",
TransformKind::PseudoLog => "pseudo_log",
TransformKind::Date => "date",
TransformKind::DateTime => "datetime",
TransformKind::Time => "time",
TransformKind::String => "string",
TransformKind::Bool => "bool",
TransformKind::Integer => "integer",
};
write!(f, "{}", name)
}
}
pub trait TransformTrait: std::fmt::Debug + std::fmt::Display + Send + Sync {
fn transform_kind(&self) -> TransformKind;
fn name(&self) -> &'static str;
fn allowed_domain(&self) -> (f64, f64);
fn calculate_breaks(&self, min: f64, max: f64, n: usize, pretty: bool) -> Vec<f64>;
fn calculate_minor_breaks(
&self,
major_breaks: &[f64],
n: usize,
range: Option<(f64, f64)>,
) -> Vec<f64>;
fn default_minor_break_count(&self) -> usize {
1 }
fn transform(&self, value: f64) -> f64;
fn inverse(&self, value: f64) -> f64;
fn wrap_numeric(&self, value: f64) -> ArrayElement {
ArrayElement::Number(value)
}
fn parse_value(&self, elem: &ArrayElement) -> ArrayElement {
match elem {
ArrayElement::Number(n) => self.wrap_numeric(*n),
other => other.clone(),
}
}
}
#[derive(Clone)]
pub struct Transform(Arc<dyn TransformTrait>);
impl Transform {
pub fn identity() -> Self {
Self(Arc::new(Identity))
}
pub fn log() -> Self {
Self(Arc::new(Log::base10()))
}
pub fn log2() -> Self {
Self(Arc::new(Log::base2()))
}
pub fn ln() -> Self {
Self(Arc::new(Log::natural()))
}
pub fn sqrt() -> Self {
Self(Arc::new(Sqrt))
}
pub fn square() -> Self {
Self(Arc::new(Square))
}
pub fn exp10() -> Self {
Self(Arc::new(Exp::base10()))
}
pub fn exp2() -> Self {
Self(Arc::new(Exp::base2()))
}
pub fn exp() -> Self {
Self(Arc::new(Exp::natural()))
}
pub fn asinh() -> Self {
Self(Arc::new(Asinh))
}
pub fn pseudo_log() -> Self {
Self(Arc::new(PseudoLog::base10()))
}
pub fn pseudo_log2() -> Self {
Self(Arc::new(PseudoLog::base2()))
}
pub fn pseudo_ln() -> Self {
Self(Arc::new(PseudoLog::natural()))
}
pub fn date() -> Self {
Self(Arc::new(Date))
}
pub fn datetime() -> Self {
Self(Arc::new(DateTime))
}
pub fn time() -> Self {
Self(Arc::new(Time))
}
pub fn string() -> Self {
Self(Arc::new(StringTransform))
}
pub fn bool() -> Self {
Self(Arc::new(Bool))
}
pub fn integer() -> Self {
Self(Arc::new(Integer))
}
pub fn from_name(name: &str) -> Option<Self> {
match name {
"identity" | "linear" => Some(Self::identity()),
"log" | "log10" => Some(Self::log()),
"log2" => Some(Self::log2()),
"ln" => Some(Self::ln()),
"sqrt" => Some(Self::sqrt()),
"square" | "pow2" => Some(Self::square()),
"exp10" => Some(Self::exp10()),
"exp2" => Some(Self::exp2()),
"exp" => Some(Self::exp()),
"asinh" => Some(Self::asinh()),
"pseudo_log" | "pseudo_log10" => Some(Self::pseudo_log()),
"pseudo_log2" => Some(Self::pseudo_log2()),
"pseudo_ln" => Some(Self::pseudo_ln()),
"date" => Some(Self::date()),
"datetime" => Some(Self::datetime()),
"time" => Some(Self::time()),
"string" | "str" | "varchar" => Some(Self::string()),
"bool" | "boolean" => Some(Self::bool()),
"integer" | "int" | "bigint" => Some(Self::integer()),
_ => None,
}
}
pub fn from_kind(kind: TransformKind) -> Self {
match kind {
TransformKind::Identity => Self::identity(),
TransformKind::Log10 => Self::log(),
TransformKind::Log2 => Self::log2(),
TransformKind::Log => Self::ln(),
TransformKind::Sqrt => Self::sqrt(),
TransformKind::Square => Self::square(),
TransformKind::Exp10 => Self::exp10(),
TransformKind::Exp2 => Self::exp2(),
TransformKind::Exp => Self::exp(),
TransformKind::Asinh => Self::asinh(),
TransformKind::PseudoLog => Self::pseudo_log(),
TransformKind::Date => Self::date(),
TransformKind::DateTime => Self::datetime(),
TransformKind::Time => Self::time(),
TransformKind::String => Self::string(),
TransformKind::Bool => Self::bool(),
TransformKind::Integer => Self::integer(),
}
}
pub fn transform_kind(&self) -> TransformKind {
self.0.transform_kind()
}
pub fn name(&self) -> &'static str {
self.0.name()
}
pub fn allowed_domain(&self) -> (f64, f64) {
self.0.allowed_domain()
}
pub fn calculate_breaks(&self, min: f64, max: f64, n: usize, pretty: bool) -> Vec<f64> {
self.0.calculate_breaks(min, max, n, pretty)
}
pub fn calculate_minor_breaks(
&self,
major_breaks: &[f64],
n: usize,
range: Option<(f64, f64)>,
) -> Vec<f64> {
self.0.calculate_minor_breaks(major_breaks, n, range)
}
pub fn default_minor_break_count(&self) -> usize {
self.0.default_minor_break_count()
}
pub fn transform(&self, value: f64) -> f64 {
self.0.transform(value)
}
pub fn inverse(&self, value: f64) -> f64 {
self.0.inverse(value)
}
pub fn wrap_numeric(&self, value: f64) -> ArrayElement {
self.0.wrap_numeric(value)
}
pub fn parse_value(&self, elem: &ArrayElement) -> ArrayElement {
self.0.parse_value(elem)
}
pub fn is_identity(&self) -> bool {
self.transform_kind() == TransformKind::Identity
}
pub fn is_temporal(&self) -> bool {
self.transform_kind().is_temporal()
}
pub fn target_type(&self) -> crate::plot::ArrayElementType {
use crate::plot::ArrayElementType;
match self.transform_kind() {
TransformKind::Date => ArrayElementType::Date,
TransformKind::DateTime => ArrayElementType::DateTime,
TransformKind::Time => ArrayElementType::Time,
TransformKind::String => ArrayElementType::String,
TransformKind::Bool => ArrayElementType::Boolean,
_ => ArrayElementType::Number,
}
}
pub fn format_as_iso(&self, value: f64) -> Option<String> {
use chrono::{DateTime as ChronoDateTime, NaiveDate, NaiveTime};
const UNIX_EPOCH_CE_DAYS: i32 = 719163;
match self.transform_kind() {
TransformKind::Date => {
let days = value as i32;
NaiveDate::from_num_days_from_ce_opt(days + UNIX_EPOCH_CE_DAYS)
.map(|d| d.format("%Y-%m-%d").to_string())
}
TransformKind::DateTime => {
let micros = value as i64;
ChronoDateTime::from_timestamp_micros(micros)
.map(|dt| dt.format("%Y-%m-%dT%H:%M:%S").to_string())
}
TransformKind::Time => {
let nanos = value as i64;
let secs = (nanos / 1_000_000_000) as u32;
let nano_part = (nanos % 1_000_000_000) as u32;
NaiveTime::from_num_seconds_from_midnight_opt(secs, nano_part)
.map(|t| t.format("%H:%M:%S").to_string())
}
_ => None,
}
}
}
impl std::fmt::Debug for Transform {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Transform::{:?}", self.transform_kind())
}
}
impl std::fmt::Display for Transform {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl PartialEq for Transform {
fn eq(&self, other: &Self) -> bool {
self.transform_kind() == other.transform_kind()
}
}
impl Eq for Transform {}
impl Serialize for Transform {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.transform_kind().serialize(serializer)
}
}
impl<'de> Deserialize<'de> for Transform {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let kind = TransformKind::deserialize(deserializer)?;
Ok(Transform::from_kind(kind))
}
}
impl Default for Transform {
fn default() -> Self {
Self::identity()
}
}
pub const ALL_TRANSFORM_NAMES: &[&str] = &[
"identity",
"linear", "log",
"log10", "log2",
"ln",
"sqrt",
"square",
"pow2", "exp10",
"exp2",
"exp",
"asinh",
"pseudo_log",
"pseudo_log10", "pseudo_log2",
"pseudo_ln",
"date",
"datetime",
"time",
"string",
"str", "varchar", "bool",
"boolean", "integer",
"int", "bigint", ];
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_transform_creation() {
let identity = Transform::identity();
assert_eq!(identity.transform_kind(), TransformKind::Identity);
assert_eq!(identity.name(), "identity");
let log = Transform::log();
assert_eq!(log.transform_kind(), TransformKind::Log10);
assert_eq!(log.name(), "log");
let ln = Transform::ln();
assert_eq!(ln.transform_kind(), TransformKind::Log);
assert_eq!(ln.name(), "ln");
}
#[test]
fn test_transform_from_name() {
assert!(Transform::from_name("identity").is_some());
assert!(Transform::from_name("log").is_some());
assert!(Transform::from_name("log10").is_some()); assert!(Transform::from_name("log2").is_some());
assert!(Transform::from_name("ln").is_some());
assert!(Transform::from_name("sqrt").is_some());
assert!(Transform::from_name("asinh").is_some());
assert!(Transform::from_name("pseudo_log").is_some());
assert!(Transform::from_name("pseudo_log10").is_some()); assert!(Transform::from_name("pseudo_log2").is_some());
assert!(Transform::from_name("pseudo_ln").is_some());
assert!(Transform::from_name("unknown").is_none());
assert_eq!(Transform::from_name("log").unwrap().name(), "log");
assert_eq!(Transform::from_name("log10").unwrap().name(), "log");
assert_eq!(Transform::from_name("log2").unwrap().name(), "log2");
assert_eq!(Transform::from_name("ln").unwrap().name(), "ln");
assert_eq!(
Transform::from_name("pseudo_log").unwrap().name(),
"pseudo_log"
);
assert_eq!(
Transform::from_name("pseudo_log10").unwrap().name(),
"pseudo_log"
);
assert_eq!(
Transform::from_name("pseudo_log2").unwrap().name(),
"pseudo_log2"
);
assert_eq!(
Transform::from_name("pseudo_ln").unwrap().name(),
"pseudo_ln"
);
}
#[test]
fn test_transform_from_kind() {
let t = Transform::from_kind(TransformKind::Log10);
assert_eq!(t.transform_kind(), TransformKind::Log10);
}
#[test]
fn test_transform_equality() {
let log_a = Transform::log();
let log_b = Transform::log();
let log2 = Transform::log2();
assert_eq!(log_a, log_b);
assert_ne!(log_a, log2);
}
#[test]
fn test_transform_display() {
assert_eq!(format!("{}", Transform::identity()), "identity");
assert_eq!(format!("{}", Transform::log()), "log");
assert_eq!(format!("{}", Transform::ln()), "ln");
assert_eq!(format!("{}", Transform::sqrt()), "sqrt");
}
#[test]
fn test_transform_serialization() {
let log = Transform::log();
let json = serde_json::to_string(&log).unwrap();
assert_eq!(json, "\"log10\"");
let deserialized: Transform = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.transform_kind(), TransformKind::Log10);
}
#[test]
fn test_transform_is_identity() {
assert!(Transform::identity().is_identity());
assert!(!Transform::log().is_identity());
assert!(!Transform::sqrt().is_identity());
}
#[test]
fn test_transform_default() {
let default = Transform::default();
assert!(default.is_identity());
}
#[test]
fn test_transform_kind_display() {
assert_eq!(format!("{}", TransformKind::Identity), "identity");
assert_eq!(format!("{}", TransformKind::Log10), "log");
assert_eq!(format!("{}", TransformKind::Log), "ln");
assert_eq!(format!("{}", TransformKind::PseudoLog), "pseudo_log");
}
#[test]
fn test_transform_target_type() {
use crate::plot::ArrayElementType;
assert_eq!(Transform::date().target_type(), ArrayElementType::Date);
assert_eq!(
Transform::datetime().target_type(),
ArrayElementType::DateTime
);
assert_eq!(Transform::time().target_type(), ArrayElementType::Time);
assert_eq!(
Transform::identity().target_type(),
ArrayElementType::Number
);
assert_eq!(Transform::log().target_type(), ArrayElementType::Number);
assert_eq!(Transform::log2().target_type(), ArrayElementType::Number);
assert_eq!(Transform::ln().target_type(), ArrayElementType::Number);
assert_eq!(Transform::sqrt().target_type(), ArrayElementType::Number);
assert_eq!(Transform::asinh().target_type(), ArrayElementType::Number);
assert_eq!(
Transform::pseudo_log().target_type(),
ArrayElementType::Number
);
assert_eq!(Transform::string().target_type(), ArrayElementType::String);
assert_eq!(Transform::bool().target_type(), ArrayElementType::Boolean);
}
#[test]
fn test_transform_string_creation() {
let string = Transform::string();
assert_eq!(string.transform_kind(), TransformKind::String);
assert_eq!(string.name(), "string");
}
#[test]
fn test_transform_bool_creation() {
let bool_t = Transform::bool();
assert_eq!(bool_t.transform_kind(), TransformKind::Bool);
assert_eq!(bool_t.name(), "bool");
}
#[test]
fn test_transform_from_name_string_aliases() {
assert_eq!(
Transform::from_name("string").unwrap().transform_kind(),
TransformKind::String
);
assert_eq!(
Transform::from_name("str").unwrap().transform_kind(),
TransformKind::String
);
assert_eq!(
Transform::from_name("varchar").unwrap().transform_kind(),
TransformKind::String
);
}
#[test]
fn test_transform_from_name_bool_aliases() {
assert_eq!(
Transform::from_name("bool").unwrap().transform_kind(),
TransformKind::Bool
);
assert_eq!(
Transform::from_name("boolean").unwrap().transform_kind(),
TransformKind::Bool
);
}
#[test]
fn test_transform_from_kind_string_bool() {
let string = Transform::from_kind(TransformKind::String);
assert_eq!(string.transform_kind(), TransformKind::String);
let bool_t = Transform::from_kind(TransformKind::Bool);
assert_eq!(bool_t.transform_kind(), TransformKind::Bool);
}
#[test]
fn test_transform_kind_display_string_bool() {
assert_eq!(format!("{}", TransformKind::String), "string");
assert_eq!(format!("{}", TransformKind::Bool), "bool");
}
#[test]
fn test_transform_integer_creation() {
let integer = Transform::integer();
assert_eq!(integer.transform_kind(), TransformKind::Integer);
assert_eq!(integer.name(), "integer");
}
#[test]
fn test_transform_from_name_integer_aliases() {
assert_eq!(
Transform::from_name("integer").unwrap().transform_kind(),
TransformKind::Integer
);
assert_eq!(
Transform::from_name("int").unwrap().transform_kind(),
TransformKind::Integer
);
assert_eq!(
Transform::from_name("bigint").unwrap().transform_kind(),
TransformKind::Integer
);
}
#[test]
fn test_transform_from_kind_integer() {
let integer = Transform::from_kind(TransformKind::Integer);
assert_eq!(integer.transform_kind(), TransformKind::Integer);
}
#[test]
fn test_transform_kind_display_integer() {
assert_eq!(format!("{}", TransformKind::Integer), "integer");
}
#[test]
fn test_transform_integer_target_type() {
use crate::plot::ArrayElementType;
assert_eq!(Transform::integer().target_type(), ArrayElementType::Number);
}
#[test]
fn test_transform_square_creation() {
let square = Transform::square();
assert_eq!(square.transform_kind(), TransformKind::Square);
assert_eq!(square.name(), "square");
}
#[test]
fn test_transform_square_transform() {
let sq = Transform::square();
assert!((sq.transform(3.0) - 9.0).abs() < 1e-10);
assert!((sq.transform(-3.0) - 9.0).abs() < 1e-10);
assert!((sq.transform(0.0) - 0.0).abs() < 1e-10);
}
#[test]
fn test_transform_square_inverse() {
let sq = Transform::square();
assert!((sq.inverse(9.0) - 3.0).abs() < 1e-10);
assert!((sq.inverse(4.0) - 2.0).abs() < 1e-10);
assert!((sq.inverse(0.0) - 0.0).abs() < 1e-10);
}
#[test]
fn test_transform_from_name_square_aliases() {
assert_eq!(
Transform::from_name("square").unwrap().transform_kind(),
TransformKind::Square
);
assert_eq!(
Transform::from_name("pow2").unwrap().transform_kind(),
TransformKind::Square
);
}
#[test]
fn test_transform_from_kind_square() {
let square = Transform::from_kind(TransformKind::Square);
assert_eq!(square.transform_kind(), TransformKind::Square);
}
#[test]
fn test_transform_kind_display_square() {
assert_eq!(format!("{}", TransformKind::Square), "square");
}
#[test]
fn test_transform_square_is_inverse_of_sqrt() {
let sqrt = Transform::sqrt();
let square = Transform::square();
for &val in &[0.0, 1.0, 2.0, 5.0, 10.0] {
let result = sqrt.transform(square.transform(val));
assert!(
(result - val).abs() < 1e-10,
"sqrt(square({})) != {}",
val,
val
);
}
}
#[test]
fn test_transform_exp10_creation() {
let exp10 = Transform::exp10();
assert_eq!(exp10.transform_kind(), TransformKind::Exp10);
assert_eq!(exp10.name(), "exp10");
}
#[test]
fn test_transform_exp10_transform() {
let exp10 = Transform::exp10();
assert!((exp10.transform(0.0) - 1.0).abs() < 1e-10);
assert!((exp10.transform(1.0) - 10.0).abs() < 1e-10);
assert!((exp10.transform(2.0) - 100.0).abs() < 1e-10);
}
#[test]
fn test_transform_exp10_inverse() {
let exp10 = Transform::exp10();
assert!((exp10.inverse(1.0) - 0.0).abs() < 1e-10);
assert!((exp10.inverse(10.0) - 1.0).abs() < 1e-10);
assert!((exp10.inverse(100.0) - 2.0).abs() < 1e-10);
}
#[test]
fn test_transform_exp10_is_inverse_of_log10() {
let log10 = Transform::log();
let exp10 = Transform::exp10();
for &val in &[-1.0, 0.0, 1.0, 2.0, 3.0] {
let result = log10.transform(exp10.transform(val));
if val == 0.0 {
assert!(
(result - val).abs() < 1e-10,
"log10(exp10({})) != {}",
val,
val
);
} else {
assert!(
(result - val).abs() / val.abs() < 1e-10,
"log10(exp10({})) != {}",
val,
val
);
}
}
}
#[test]
fn test_transform_exp2_creation() {
let exp2 = Transform::exp2();
assert_eq!(exp2.transform_kind(), TransformKind::Exp2);
assert_eq!(exp2.name(), "exp2");
}
#[test]
fn test_transform_exp2_transform() {
let exp2 = Transform::exp2();
assert!((exp2.transform(0.0) - 1.0).abs() < 1e-10);
assert!((exp2.transform(1.0) - 2.0).abs() < 1e-10);
assert!((exp2.transform(3.0) - 8.0).abs() < 1e-10);
}
#[test]
fn test_transform_exp2_is_inverse_of_log2() {
let log2 = Transform::log2();
let exp2 = Transform::exp2();
for &val in &[-1.0, 0.0, 1.0, 2.0, 3.0] {
let result = log2.transform(exp2.transform(val));
if val == 0.0 {
assert!(
(result - val).abs() < 1e-10,
"log2(exp2({})) != {}",
val,
val
);
} else {
assert!(
(result - val).abs() / val.abs() < 1e-10,
"log2(exp2({})) != {}",
val,
val
);
}
}
}
#[test]
fn test_transform_exp_creation() {
let exp = Transform::exp();
assert_eq!(exp.transform_kind(), TransformKind::Exp);
assert_eq!(exp.name(), "exp");
}
#[test]
fn test_transform_exp_transform() {
use std::f64::consts::E;
let exp = Transform::exp();
assert!((exp.transform(0.0) - 1.0).abs() < 1e-10);
assert!((exp.transform(1.0) - E).abs() < 1e-10);
}
#[test]
fn test_transform_exp_is_inverse_of_ln() {
let ln = Transform::ln();
let exp = Transform::exp();
for &val in &[-1.0, 0.0, 1.0, 2.0] {
let result = ln.transform(exp.transform(val));
if val == 0.0 {
assert!((result - val).abs() < 1e-10, "ln(exp({})) != {}", val, val);
} else {
assert!(
(result - val).abs() / val.abs() < 1e-10,
"ln(exp({})) != {}",
val,
val
);
}
}
}
#[test]
fn test_transform_from_name_exp_variants() {
assert_eq!(
Transform::from_name("exp10").unwrap().transform_kind(),
TransformKind::Exp10
);
assert_eq!(
Transform::from_name("exp2").unwrap().transform_kind(),
TransformKind::Exp2
);
assert_eq!(
Transform::from_name("exp").unwrap().transform_kind(),
TransformKind::Exp
);
}
#[test]
fn test_transform_from_kind_exp_variants() {
assert_eq!(
Transform::from_kind(TransformKind::Exp10).transform_kind(),
TransformKind::Exp10
);
assert_eq!(
Transform::from_kind(TransformKind::Exp2).transform_kind(),
TransformKind::Exp2
);
assert_eq!(
Transform::from_kind(TransformKind::Exp).transform_kind(),
TransformKind::Exp
);
}
#[test]
fn test_transform_kind_display_exp_variants() {
assert_eq!(format!("{}", TransformKind::Exp10), "exp10");
assert_eq!(format!("{}", TransformKind::Exp2), "exp2");
assert_eq!(format!("{}", TransformKind::Exp), "exp");
}
#[test]
fn test_transform_square_exp_target_type() {
use crate::plot::ArrayElementType;
assert_eq!(Transform::square().target_type(), ArrayElementType::Number);
assert_eq!(Transform::exp10().target_type(), ArrayElementType::Number);
assert_eq!(Transform::exp2().target_type(), ArrayElementType::Number);
assert_eq!(Transform::exp().target_type(), ArrayElementType::Number);
}
}