use serde::{Serialize, Deserialize};
use std::default::Default;
use std::fmt;
use std::collections::HashMap;
use std::ops::Range;
use std::str::FromStr;
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub enum Adjustment {
Tight,
Round,
Off
}
impl FromStr for Adjustment {
type Err = ();
fn from_str(s : &str) -> Result<Self, ()> {
match s {
"tight" => Ok(Self::Tight),
"round" => Ok(Self::Round),
"off" => Ok(Self::Off),
_ => Err(())
}
}
}
impl Default for Adjustment {
fn default() -> Self {
Self::Off
}
}
pub enum MappingType {
Line,
Scatter,
Bar,
Area,
Text,
Interval
}
impl MappingType {
pub fn from_str(name : &str) -> Option<Self> {
match name {
"line" => Some(MappingType::Line),
"scatter" => Some(MappingType::Scatter),
"bar" => Some(MappingType::Bar),
"area" => Some(MappingType::Area),
"text" => Some(MappingType::Text),
"interval" => Some(MappingType::Interval),
_ => None
}
}
pub fn default_hash(&self) -> HashMap<String, String> {
let mut hash = HashMap::new();
hash.insert(String::from("color"), String::from("#000000"));
hash.insert(String::from("x"), String::from("None"));
hash.insert(String::from("y"), String::from("None"));
hash.insert(String::from("source"), String::from("None"));
match self {
MappingType::Line => {
hash.insert(String::from("width"), String::from("1"));
hash.insert(String::from("dash"), String::from("1"));
},
MappingType::Scatter => {
hash.insert(String::from("radius"), String::from("1"));
},
MappingType::Bar => {
hash.insert(String::from("center"), String::from("false"));
hash.insert(String::from("horizontal"), String::from("false"));
hash.insert(String::from("width"), String::from("None"));
hash.insert(String::from("height"), String::from("None"));
hash.insert(String::from("width"), String::from("100"));
hash.insert(String::from("origin_x"), String::from("0"));
hash.insert(String::from("origin_y"), String::from("0"));
hash.insert(String::from("spacing"), String::from("1"));
},
MappingType::Area => {
hash.insert(String::from("ymax"), String::from("None"));
hash.insert(String::from("opacity"), String::from("1.0"));
},
MappingType::Text => {
hash.insert(String::from("font"), String::from("Monospace Regular 12"));
hash.insert(String::from("text"), String::from("None"));
},
MappingType::Interval => {
unimplemented!()
}
}
hash
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Design {
pub bgcolor : String,
pub fgcolor : String,
pub width : i32,
pub font : String
}
#[derive(Debug, Default)]
pub struct DesignBuilder(Design);
impl DesignBuilder {
pub fn build(self) -> Design {
self.0
}
pub fn bgcolor(mut self, s : &str) -> Self {
self.0.bgcolor = s.to_string();
self
}
pub fn fgcolor(mut self, s : &str) -> Self {
self.0.fgcolor = s.to_string();
self
}
pub fn font(mut self, s : &str) -> Self {
self.0.font = s.to_string();
self
}
pub fn width(mut self, width : i32) -> Self {
self.0.width = width;
self
}
}
#[derive(Debug, thiserror::Error)]
pub enum DesignError {
#[error("Invalid grid width")]
InvalidGridWidth,
#[error("Invalid grid color")]
InvalidGridColor,
#[error("Invalid background color")]
InvalidBackgroundColor
}
impl Design {
pub fn validate(&self) -> Result<(), DesignError> {
if self.width < 0 || self.width > 50 {
Err(DesignError::InvalidGridWidth)?;
}
if !crate::model::validate_color(&self.fgcolor) {
Err(DesignError::InvalidGridColor)?;
}
if !crate::model::validate_color(&self.bgcolor) {
Err(DesignError::InvalidBackgroundColor)?;
}
Ok(())
}
pub fn new() -> Self {
Self::default()
}
pub fn builder() -> DesignBuilder {
DesignBuilder(Design::default())
}
}
impl Default for Design {
fn default() -> Self {
Self {
bgcolor : String::from("#ffffff"),
fgcolor : String::from("#d3d7cf"),
width : 1,
font : String::from("Monospace Regular 12")
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Layout {
pub width : i32,
pub height : i32,
pub hratio : f64,
pub vratio : f64,
pub split : Option<String>
}
impl Layout {
pub fn new() -> Self {
Self::default()
}
pub fn builder() -> LayoutBuilder {
LayoutBuilder(Layout::default())
}
}
pub struct LayoutBuilder(Layout);
impl LayoutBuilder {
pub fn build(self) -> Layout {
self.0
}
pub fn width(mut self, width : i32) -> Self {
self.0.width = width;
self
}
pub fn height(mut self, height : i32) -> Self {
self.0.height = height;
self
}
pub fn hratio(mut self, ratio : f64) -> Self {
self.0.hratio = ratio;
self
}
pub fn vratio(mut self, ratio : f64) -> Self {
self.0.vratio = ratio;
self
}
pub fn split(mut self, split : &str) -> Self {
self.0.split = Some(split.to_string());
self
}
}
impl Default for Layout {
fn default() -> Self {
Self {
width : 800,
height : 600,
hratio : 0.5,
vratio : 0.5,
split : None
}
}
}
#[derive(Debug, thiserror::Error)]
pub enum ScaleError {
#[error("Inverted range")]
InvertedRange,
#[error("Number of steps")]
StepNumber,
#[error("Invalid offset")]
InvalidOffset,
#[error("Invalid adjustment")]
InvalidAdjustment
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Scale {
pub label : String,
pub precision : i32,
pub from : f64,
pub to : f64,
pub intervals : i32,
pub log : bool,
pub invert : bool,
pub offset : i32,
pub adjust : Option<String>
}
impl Scale {
pub fn validate(&self) -> Result<(), ScaleError> {
if self.from > self.to {
Err(ScaleError::InvertedRange)?;
}
if self.offset < 0 || self.offset > 100 {
Err(ScaleError::InvalidOffset)?;
}
if let Some(adj) = &self.adjust {
if Adjustment::from_str(adj).is_err() {
Err(ScaleError::InvalidAdjustment)?;
}
}
Ok(())
}
pub fn new() -> Self {
Scale::default()
}
pub fn builder() -> ScaleBuilder {
ScaleBuilder(Scale::default())
}
}
impl Default for Scale {
fn default() -> Self {
Self {
label : String::new(),
precision : 2,
from : 0.0,
to : 1.0,
intervals : 5,
log : false,
invert : false,
offset : 0,
adjust : Some(String::from("tight"))
}
}
}
pub struct ScaleBuilder(Scale);
impl ScaleBuilder {
pub fn build(self) -> Scale {
self.0
}
pub fn label(mut self, label : &str) -> Self {
self.0.label = label.to_string();
self
}
pub fn adjust(mut self, adjust : &str) -> Self {
self.0.adjust = Some(adjust.to_string());
self
}
pub fn precision(mut self, precision : i32) -> Self {
self.0.precision = precision;
self
}
pub fn intervals(mut self, intervals : i32) -> Self {
self.0.intervals = intervals;
self
}
pub fn offset(mut self, offset : i32) -> Self {
self.0.offset = offset;
self
}
pub fn from(mut self, from : f64) -> Self {
self.0.from = from;
self
}
pub fn to(mut self, to : f64) -> Self {
self.0.to = to;
self
}
pub fn log(mut self, log : bool) -> Self {
self.0.log = log;
self
}
pub fn invert(mut self, invert : bool) -> Self {
self.0.invert = invert;
self
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Map {
pub x : Option<Vec<f64>>,
pub y : Option<Vec<f64>>,
pub z : Option<Vec<f64>>,
pub text : Option<Vec<String>>
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Line {
pub map : Map,
pub width : f64,
pub spacing : i32,
pub color : String
}
pub struct LineBuilder(Line);
impl LineBuilder {
pub fn build(self) -> Line {
self.0
}
pub fn map(mut self, x : Vec<f64>, y : Vec<f64>) -> Self {
self.0.map = Map { x : Some(x), y : Some(y), z : None, text : None };
self
}
pub fn width(mut self, width : f64) -> Self {
self.0.width = width;
self
}
pub fn spacing(mut self, spacing : i32) -> Self {
self.0.spacing = spacing;
self
}
pub fn color(mut self, color : &str) -> Self {
self.0.color = color.to_string();
self
}
}
impl Line {
pub fn new() -> Self {
Line::default()
}
pub fn builder() -> LineBuilder {
LineBuilder(Self::default())
}
}
impl Default for Line {
fn default() -> Self {
Line {
map : Map::default(),
width : 1.0,
spacing : 1,
color : String::from("#000000")
}
}
}
impl From<Line> for Mapping {
fn from(line : Line) -> Self {
let Line { map, width, spacing, color, .. } = line;
Mapping { kind : String::from("line"), map : Some(map), width : Some(width), spacing : Some(spacing), color : Some(color), ..Default::default() }
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Scatter {
pub map : Map,
pub radius : f64,
pub color : String
}
pub struct ScatterBuilder(Scatter);
impl ScatterBuilder {
pub fn build(self) -> Scatter {
self.0
}
pub fn map(mut self, x : Vec<f64>, y : Vec<f64>) -> Self {
self.0.map = Map { x : Some(x), y : Some(y), z : None, text : None };
self
}
pub fn radius(mut self, radius : f64) -> Self {
self.0.radius = radius;
self
}
pub fn color(mut self, color : &str) -> Self {
self.0.color = color.to_string();
self
}
}
impl Scatter {
pub fn new() -> Self {
Scatter::default()
}
pub fn builder() -> ScatterBuilder {
ScatterBuilder(Self::default())
}
}
impl Default for Scatter {
fn default() -> Self {
Scatter {
map : Map::default(),
radius : 1.0,
color : String::from("#000000")
}
}
}
impl From<Scatter> for Mapping {
fn from(scatter : Scatter) -> Self {
let Scatter { map, radius, color, .. } = scatter;
Mapping { kind : String::from("scatter"), map : Some(map), radius : Some(radius), color : Some(color), ..Default::default() }
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Interval {
pub map : Map,
pub width : f64,
pub limits : f64,
pub spacing : f64,
pub vertical : bool,
pub color : String
}
pub struct IntervalBuilder(Interval);
impl IntervalBuilder {
pub fn width(mut self, width : f64) -> Self {
self.0.width = width;
self
}
pub fn spacing(mut self, spacing : f64) -> Self {
self.0.spacing = spacing;
self
}
pub fn vertical(mut self, vertical : bool) -> Self {
self.0.vertical = vertical;
self
}
pub fn limits(mut self, limits : f64) -> Self {
self.0.limits = limits;
self
}
pub fn build(self) -> Interval {
self.0
}
pub fn map(mut self, pos : Vec<f64>, min : Vec<f64>, max : Vec<f64>) -> Self {
self.0.map = Map { x : Some(pos), y : Some(min), z : Some(max), text : None };
self
}
pub fn color(mut self, color : &str) -> Self {
self.0.color = color.to_string();
self
}
}
impl Interval {
pub fn new() -> Self {
Interval::default()
}
pub fn builder() -> IntervalBuilder {
IntervalBuilder(Self::default())
}
}
impl Default for Interval {
fn default() -> Self {
Interval {
map : Map::default(),
color : String::from("#000000"),
width : 1.0,
spacing : 1.0,
limits : 1.0,
vertical : true
}
}
}
impl From<Interval> for Mapping {
fn from(interval : Interval) -> Self {
let Interval { map, color, width, vertical, limits, .. } = interval;
Mapping {
kind : String::from("interval"),
map : Some(map),
color : Some(color),
limits : Some(limits),
vertical : Some(vertical),
width : Some(width),
..Default::default()
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Label {
pub map : Map,
pub color : String,
pub font : String,
}
pub struct LabelBuilder(Label);
impl LabelBuilder {
pub fn build(self) -> Label {
self.0
}
pub fn map(mut self, x : Vec<f64>, y : Vec<f64>, text : Vec<String>) -> Self {
self.0.map = Map { x : Some(x), y : Some(y), z : None, text : Some(text) };
self
}
pub fn font(mut self, font : String) -> Self {
self.0.font = font;
self
}
pub fn color(mut self, color : &str) -> Self {
self.0.color = color.to_string();
self
}
}
impl Label {
pub fn new() -> Label {
Label::default()
}
pub fn builder() -> LabelBuilder {
LabelBuilder(Self::default())
}
}
impl Default for Label {
fn default() -> Label {
Label {
map : Map::default(),
color : String::from("#000000"),
font : String::from("Monospace Regular 12")
}
}
}
impl From<Label> for Mapping {
fn from(label : Label) -> Self {
let Label { map, color, font, .. } = label;
Mapping { kind : String::from("text"), map : Some(map), color : Some(color), font : Some(font), ..Default::default() }
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Bar {
pub map : Map,
pub color : String,
pub width : f64,
pub spacing : f64,
pub origin : f64,
pub center : bool,
pub vertical : bool,
}
pub struct BarBuilder(Bar);
impl BarBuilder {
pub fn build(self) -> Bar {
self.0
}
pub fn map(mut self, x : Vec<f64>) -> Self {
self.0.map = Map { x : Some(x), y : None, z : None, text : None };
self
}
pub fn width(mut self, width : f64) -> Self {
self.0.width = width;
self
}
pub fn spacing(mut self, spacing : f64) -> Self {
self.0.spacing = spacing;
self
}
pub fn origin(mut self, origin : f64) -> Self {
self.0.origin = origin;
self
}
pub fn center(mut self, center : bool) -> Self {
self.0.center = center;
self
}
pub fn vertical(mut self, vertical : bool) -> Self {
self.0.vertical = vertical;
self
}
}
impl Bar {
pub fn new() -> Bar {
Bar::default()
}
pub fn builder() -> BarBuilder {
BarBuilder(Self::default())
}
}
impl Default for Bar {
fn default() -> Bar {
Bar {
map : Map::default(),
color : String::from("#000000"),
width : 1.0,
spacing : 1.0,
origin : 0.,
center : false,
vertical : true,
}
}
}
impl From<Bar> for Mapping {
fn from(bar : Bar) -> Self {
let Bar { map, color, width, spacing, origin, center, vertical, .. } = bar;
Mapping {
kind : String::from("bar"),
map : Some(map),
color : Some(color),
origin : Some(origin),
center : Some(center),
vertical : Some(vertical),
width : Some(width),
spacing : Some(spacing as i32),
..Default::default()
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Mapping {
pub kind : String,
pub color : Option<String>,
pub map : Option<Map>,
pub width : Option<f64>,
pub spacing : Option<i32>,
pub vertical : Option<bool>,
pub font : Option<String>,
pub radius : Option<f64>,
pub limits : Option<f64>,
pub center : Option<bool>,
pub origin : Option<f64>,
}
fn hex_byte_at(full : &str, pos : Range<usize>) -> bool {
if let Some(sub) = full.get(pos) {
u8::from_str_radix(sub, 16).is_ok()
} else {
false
}
}
pub(crate) fn validate_color(s : &str) -> bool {
let has_rgb = s.starts_with("#") && hex_byte_at(s, 1..3) && hex_byte_at(s, 3..5) && hex_byte_at(s, 5..7);
match s.len() {
7 => has_rgb,
9 => has_rgb && hex_byte_at(s, 7..9),
_ => false
}
}
impl Mapping {
pub fn has_shared_property(&self) -> bool {
self.width.is_some() || self.spacing.is_some()
}
pub fn has_specific_property(&self, kind : MappingType) -> bool {
match kind {
MappingType::Bar => {
self.center.is_some() || self.vertical.is_some() ||
self.width.is_some() || self.spacing.is_some() || self.origin.is_some()
},
MappingType::Interval => {
self.limits.is_some() || self.vertical.is_some()
},
MappingType::Text => {
self.font.is_some()
},
MappingType::Scatter => {
self.radius.is_some()
},
_ => false
}
}
pub fn validate(&self) -> Result<(), MappingError> {
let ty = MappingType::from_str(&self.kind).ok_or(MappingError::InvalidKind)?;
if let Some(map) = &self.map {
let n = map.x.as_ref().map(|data| data.len() ).unwrap_or(0);
if let Some(y) = &map.y {
if y.len() != n {
Err(MappingError::DataLength)?;
}
}
if let Some(z) = &map.z {
if z.len() != n {
Err(MappingError::DataLength)?;
}
}
if let Some(t) = &map.text {
if t.len() != n {
Err(MappingError::DataLength)?;
}
}
}
if let Some(color) = &self.color {
if !validate_color(&color[..]) {
Err(MappingError::InvalidColor)?;
}
}
match ty {
MappingType::Line => {
if self.has_specific_property(MappingType::Scatter) || self.has_specific_property(MappingType::Text) ||
self.has_specific_property(MappingType::Bar) || self.has_specific_property(MappingType::Interval) {
Err(MappingError::InvalidProperty)?;
}
},
MappingType::Scatter => {
if self.has_specific_property(MappingType::Text) || self.has_specific_property(MappingType::Bar) ||
self.has_specific_property(MappingType::Interval) {
Err(MappingError::InvalidProperty)?;
}
if self.has_shared_property() {
Err(MappingError::InvalidProperty)?;
}
},
MappingType::Text => {
if self.has_specific_property(MappingType::Scatter) || self.has_specific_property(MappingType::Bar) ||
self.has_specific_property(MappingType::Interval) {
Err(MappingError::InvalidProperty)?;
}
if self.has_shared_property() {
Err(MappingError::InvalidProperty)?;
}
},
MappingType::Area => {
if self.has_specific_property(MappingType::Scatter) || self.has_specific_property(MappingType::Text) ||
self.has_specific_property(MappingType::Bar) || self.has_specific_property(MappingType::Interval) {
Err(MappingError::InvalidProperty)?;
}
if self.has_shared_property() {
Err(MappingError::InvalidProperty)?;
}
},
MappingType::Interval => {
if self.has_specific_property(MappingType::Scatter) || self.has_specific_property(MappingType::Text) ||
self.has_specific_property(MappingType::Bar) {
Err(MappingError::InvalidProperty)?;
}
},
MappingType::Bar => {
if self.has_specific_property(MappingType::Scatter) || self.has_specific_property(MappingType::Text) ||
self.has_specific_property(MappingType::Interval) {
Err(MappingError::InvalidProperty)?;
}
}
}
Ok(())
}
}
#[derive(thiserror::Error, Debug)]
pub enum MappingError {
#[error("Invalid kind")]
InvalidKind,
#[error("Data length")]
DataLength,
#[error("Invalid property")]
InvalidProperty,
#[error("Invalid RGB/RGBA color")]
InvalidColor
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Plot {
pub mappings : Vec<Mapping>,
pub x : Scale,
pub y : Scale,
pub design : Option<Design>,
pub layout : Option<Layout>
}
impl Plot {
pub fn new() -> Self {
Self::default()
}
pub fn builder() -> PlotBuilder {
PlotBuilder(Self::default())
}
}
impl fmt::Display for Plot {
fn fmt(&self, f : &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", serde_json::to_string(self).unwrap() )
}
}
pub struct PlotBuilder(Plot);
impl PlotBuilder {
pub fn build(self) -> Plot {
self.0
}
pub fn mapping<M>(mut self, mapping : M) -> Self
where
M : Into<Mapping>
{
self.0.mappings.push(mapping.into());
self
}
pub fn mappings<M, const U : usize>(mut self, mappings : [M; U]) -> Self
where
M : Into<Mapping>
{
for m in mappings {
self.0.mappings.push(m.into());
}
self
}
pub fn x(mut self, scale : Scale) -> Self {
self.0.x = scale;
self
}
pub fn y(mut self, scale : Scale) -> Self {
self.0.y = scale;
self
}
pub fn design(mut self, design : Design) -> Self {
self.0.design = Some(design);
self
}
pub fn layout(mut self, layout : Layout) -> Self {
self.0.layout = Some(layout);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Panel {
pub plots : Vec<Plot>,
pub design : Option<Design>,
pub layout : Option<Layout>
}
impl Default for Panel {
fn default() -> Self {
Panel {
plots : Vec::new(),
design : Some(Design::default()),
layout : Some(Layout::default())
}
}
}
impl Panel {
pub fn new() -> Self {
Self::default()
}
pub fn builder() -> PanelBuilder {
PanelBuilder(Panel::default())
}
}
pub struct PanelBuilder(Panel);
impl PanelBuilder {
pub fn build(self) -> Panel {
self.0
}
pub fn plots<const U : usize>(mut self, plots : [Plot; U]) -> Self {
self.0.plots = Vec::from(plots);
self
}
pub fn design(mut self, design : Design) -> Self {
self.0.design = Some(design);
self
}
pub fn layout(mut self, layout : Layout) -> Self {
self.0.layout = Some(layout);
self
}
}
impl fmt::Display for Panel {
fn fmt(&self, f : &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", serde_json::to_string(self).unwrap() )
}
}