extern crate typed_arena;
use typed_arena::Arena;
#[macro_use]
extern crate json;
use json::JsonValue;
use std::io;
use std::io::Write;
pub struct FRange {
val: f64,
end: f64,
incr: f64
}
pub fn range(x1: f64, x2: f64, skip: f64) -> FRange {
FRange {val: x1, end: x2, incr: skip}
}
impl Iterator for FRange {
type Item = f64;
fn next(&mut self) -> Option<Self::Item> {
let res = self.val;
if res >= self.end {
None
} else {
self.val += self.incr;
Some(res)
}
}
}
pub fn zip<'a,I1,I2,T1,T2>(x: I1, y: I2) -> Box<Iterator<Item=(f64,f64)>+'a>
where I1: IntoIterator<Item=&'a T1>+'a, I2: IntoIterator<Item=&'a T2>+'a,
T1: Into<f64>+Copy+'a, T2: Into<f64>+Copy+'a
{
Box::new(x.into_iter().zip(y).map(|(&x,&y)| (x.into(),y.into())))
}
pub fn to_f64 <'a,I,T1,T2>(x: I) -> Box<Iterator<Item=(f64,f64)>+'a>
where I: IntoIterator<Item=&'a (T1,T2)>+'a,
T1: Into<f64>+Copy+'a, T2: Into<f64>+Copy+'a
{
Box::new(x.into_iter().map(|&(x,y)| (x.into(),y.into())))
}
pub fn valr<'a,I,T>(y: I) -> Box<Iterator<Item=(f64,f64)>+'a>
where I: IntoIterator<Item=&'a T>+'a,
T: Into<f64>+Copy+'a
{
Box::new((0..).into_iter().zip(y).map(|(x,&y)| (x.into(),y.into())))
}
pub fn mapr<'a,I,T,F>(x: I, f: F) -> Box<Iterator<Item=(f64,f64)>+'a>
where I: IntoIterator<Item=&'a T>+'a, F: Fn(f64)->f64 + 'a,
T: Into<f64>+Copy+'a
{
Box::new(x.into_iter().map(move |&x| { let fv = x.into(); (fv,f(fv))}))
}
pub fn mapv<'a,I,T,F>(x: I, f: F) -> Box<Iterator<Item=(f64,f64)>+'a>
where I: IntoIterator<Item=T>+'a, F: Fn(f64)->f64 + 'a,
T: Into<f64>+Copy+'a
{
Box::new(x.into_iter().map(move |x| { let fv = x.into(); (fv,f(fv))}))
}
enum PlotKind {
Lines,
Points,
Bars
}
impl PlotKind {
fn to_str(&self) -> &'static str {
match *self {
PlotKind::Lines => "lines",
PlotKind::Points => "points",
PlotKind::Bars => "bars"
}
}
}
pub struct Series {
data: JsonValue,
kind: PlotKind,
symbols: bool,
}
impl Series {
fn new<T>(kind: PlotKind, label: &str, data: T) -> Series
where T: IntoIterator<Item=(f64,f64)> {
let mut arr = JsonValue::new_array();
for p in data.into_iter() {
arr.push(array![p.0,p.1]).unwrap();
}
let jlbl = if label.is_empty() {JsonValue::Null} else {label.into()};
let mut data = object! {
"label" => jlbl,
"data" => arr
};
data[kind.to_str()] = object!{"show" => true};
Series {data: data, kind: kind, symbols: false}
}
fn kind_ref(&mut self) -> &mut JsonValue {
&mut self.data[self.kind.to_str()]
}
pub fn xaxis(&mut self, which: u32) -> &mut Self {
self.data["xaxis"] = which.into();
self
}
pub fn yaxis(&mut self, which: u32) -> &mut Self {
self.data["yaxis"] = which.into();
self
}
pub fn fill(&mut self, opacity: f32) -> &mut Self {
self.kind_ref()["fill"] = opacity.into();
self
}
pub fn fill_color(&mut self, color: &str) -> &mut Self {
self.kind_ref()["fillColor"] = color.into();
self
}
pub fn color(&mut self, color: &str) -> &mut Self {
self.data["color"] = color.into();
self
}
pub fn line_width(&mut self, size: u32) -> &mut Self {
self.kind_ref()["lineWidth"] = size.into();
self
}
pub fn radius(&mut self, size: u32) -> &mut Self {
match self.kind {
PlotKind::Points => self.kind_ref()["radius"] = size.into(),
_ => panic!("radius() only applies to points")
}
self
}
pub fn symbol(&mut self, name: &str) -> &mut Self {
match self.kind {
PlotKind::Points => {
self.symbols = true;
self.kind_ref()["symbol"] = name.into();
},
_ => panic!("symbol() only applies to points")
}
self
}
pub fn steps(&mut self) -> &mut Self {
match self.kind {
PlotKind::Lines => self.kind_ref()["steps"] = true.into(),
_ => panic!("steps() only applies to lines")
}
self
}
pub fn width(&mut self, width: f64) -> &mut Self {
match self.kind {
PlotKind::Bars => self.kind_ref()["barWidth"] = width.into(),
_ => panic!("bar_width() only applies to bars")
}
self
}
}
pub enum Corner {
None,
TopRight,
TopLeft,
BottomRight,
BottomLeft,
}
impl Corner {
fn to_str(&self) -> &'static str {
use Corner::*;
match *self {
None => "none",
TopRight => "ne",
TopLeft => "nw",
BottomRight => "sw",
BottomLeft => "se",
}
}
}
pub enum Side {
Right,
Left,
Bottom,
Top,
}
impl Side {
fn to_str(&self) -> &'static str {
use Side::*;
match *self {
Right => "right",
Left => "left",
Bottom => "bottom",
Top => "top",
}
}
}
pub struct Axis<'a> {
which: &'static str,
plot: &'a mut Plot,
idx: usize,
}
const TICK_FORMAT: &str = "v.toFixed(a.tickDecimals)";
impl <'a> Axis<'a> {
fn new(which: &'static str, plot: &'a mut Plot, idx: usize) -> Axis<'a> {
if plot.options[which].is_null() {
plot.options[which] = array![object!{}];
}
if idx > 1 {
plot.options[which].push(object!{}).unwrap();
}
Axis{which: which, plot: plot, idx: idx-1}
}
pub fn set_option(&mut self, key: &str, val: JsonValue) -> &mut Self {
self.plot.options[self.which][self.idx][key] = val;
self
}
fn axis_function(&mut self, var: &str, fun: &str) -> &mut Self {
self.plot.option_functions.push(format!("{}[{}].{} = {}",self.which,self.idx,var,fun));
self
}
pub fn transform(&mut self, expr_or_fun: &str) -> &mut Self {
let s;
self.axis_function("transform",if expr_or_fun.starts_with("function") {
expr_or_fun
} else {
s = format!("function (v) {{ return {}; }}",expr_or_fun);
&s
})
}
pub fn label_formatter(&mut self, fun: &str) -> &mut Self {
self.axis_function("tickFormatter",fun)
}
pub fn label_post(&mut self, s: &str) -> &mut Self {
self.label_formatter(
&format!("function(v,a) {{ return {} + {:?}; }}",TICK_FORMAT,s)
)
}
pub fn label_pre(&mut self, s: &str) -> &mut Self {
self.label_formatter(
&format!("function(v,a) {{ return {:?} + {}; }}",s,TICK_FORMAT)
)
}
pub fn min(&mut self, min: f64) -> &mut Self {
self.set_option("min",min.into());
self
}
pub fn max(&mut self, max: f64) -> &mut Self {
self.set_option("max",max.into());
self
}
pub fn position(&mut self, side: Side) -> &mut Self {
let pos = side.to_str();
if pos == "right" {
self.set_option("alignTicksWithAxis",1.into());
}
self.set_option("position",pos.into())
}
pub fn time(&mut self) -> &mut Self {
self.plot.time = true;
self.set_option("mode","time".into())
}
pub fn tick_values(&mut self, vv: &[f64]) -> &mut Self {
let mut arr = JsonValue::new_array();
for v in vv {
arr.push(JsonValue::from(*v)).unwrap();
}
self.set_option("ticks",arr)
}
pub fn tick_values_and_labels(&mut self, vv: &[(f64,&str)]) -> &mut Self {
let mut arr = JsonValue::new_array();
for p in vv {
arr.push(array![p.0,p.1]).unwrap();
}
self.set_option("ticks",arr)
}
}
pub struct Markings<'a> {
plot: &'a mut Plot,
}
impl <'a> Markings<'a> {
fn new(plot: &'a mut Plot) -> Markings<'a> {
plot.set_option("grid","markings",array![]);
Markings{plot: plot}
}
fn markings(&mut self) -> &mut JsonValue {
&mut self.plot.options["grid"]["markings"]
}
pub fn add_marking(&mut self, val: JsonValue) -> &mut Self {
self.markings().push(val).unwrap(); self
}
pub fn vertical_area(&mut self, p1: f64, p2: f64) -> &mut Self {
self.add_marking(object!{"xaxis" => object!{"from"=>p1,"to"=>p2 } })
}
pub fn horizontal_area(&mut self, p1: f64, p2: f64) -> &mut Self {
self.add_marking(object!{"yaxis" => object!{"from"=>p1,"to"=>p2 } })
}
pub fn vertical_line(&mut self, pos: f64) -> &mut Self {
self.vertical_area(pos,pos)
}
pub fn horizontal_line(&mut self, pos: f64) -> &mut Self {
self.horizontal_area(pos,pos)
}
pub fn area(&mut self, x1: f64, x2: f64, y1: f64, y2: f64) -> &mut Self {
self.add_marking(object!{
"xaxis" => object!{"from"=>x1,"to"=>x2 },
"yaxis" => object!{"from"=>y1,"to"=>y2 }
})
}
pub fn color(&mut self, color: &str) -> &mut Self {
{
let mut arr = self.markings();
let len = arr.len();
arr[len-1]["color"] = color.into();
}
self
}
}
pub struct Grid<'a> {
plot: &'a mut Plot,
}
impl <'a> Grid<'a> {
fn new(plot: &'a mut Plot) -> Grid<'a> {
if plot.options["grid"].is_null() {
plot.options["grid"] = object!{};
}
Grid{plot: plot}
}
pub fn set_option(&mut self, key: &str, val: JsonValue) -> &mut Self {
self.plot.options["grid"][key] = val;
self
}
pub fn hide(&mut self) -> &mut Self {
self.set_option("show",false.into())
}
pub fn color(&mut self, front: &str) -> &mut Self {
self.set_option("color",front.into())
}
pub fn background_color(&mut self, back: &str) -> &mut Self {
self.set_option("backgroundColor",back.into())
}
pub fn background_gradient(&mut self, bottom: &str, top: &str) -> &mut Self {
self.set_option("backgroundColor",object!{"colors" => array![bottom,top]})
}
}
pub struct Legend<'a> {
plot: &'a mut Plot,
}
impl <'a> Legend<'a> {
fn new(plot: &'a mut Plot) -> Legend<'a> {
if plot.options["legend"].is_null() {
plot.options["legend"] = object!{};
}
Legend{plot: plot}
}
pub fn set_option(&mut self, key: &str, val: JsonValue) -> &mut Self {
self.plot.options["legend"][key] = val;
self
}
pub fn pos(&mut self, pos: Corner) -> &mut Self {
match pos {
Corner::None => self.set_option("show",false.into()),
_ => self.set_option("position",pos.to_str().into())
}
}
}
pub struct Plot {
series: Arena<Series>,
placeholder: String,
options: JsonValue,
time: bool,
symbols: bool,
bounds: (u32,u32),
title: String,
option_functions: Vec<String>,
description: Vec<String>,
}
impl Plot {
fn new(name: &str, title: &str, bounds: (u32,u32)) -> Plot {
Plot {
series: Arena::new(),
placeholder: name.into(),
options: object!{},
time: false,
symbols: false,
bounds: bounds,
title: title.into(),
option_functions: Vec::new(),
description: Vec::new(),
}
}
pub fn text(&mut self, txt: &str) -> &mut Self {
let mut escaped = String::new();
for ch in txt.chars() {
match ch {
'<' => escaped.push_str("<"),
'>' => escaped.push_str(">"),
'&' => escaped.push_str("&"),
_ => escaped.push(ch)
}
}
self.description.push(escaped);
self
}
pub fn html(&mut self, txt: &str) -> &mut Self {
self.description.push(txt.into());
self
}
pub fn size(&mut self,width:u32,height:u32) -> &mut Self {
self.bounds = (width,height);
self
}
pub fn xaxis<'a>(&'a mut self) -> Axis<'a> {
Axis::new("xaxes",self,1)
}
pub fn yaxis<'a>(&'a mut self) -> Axis<'a> {
Axis::new("yaxes",self,1)
}
pub fn yaxis2<'a>(&'a mut self) -> Axis<'a> {
Axis::new("yaxes",self,2)
}
pub fn points<T>(&self, label: &str, data: T) -> &mut Series
where T: IntoIterator<Item=(f64,f64)> {
self.series.alloc(Series::new(PlotKind::Points,label,data))
}
pub fn lines<T>(&self, label: &str, data: T) -> &mut Series
where T: IntoIterator<Item=(f64,f64)> {
self.series.alloc(Series::new(PlotKind::Lines,label,data))
}
pub fn bars<T>(&self, label: &str, data: T) -> &mut Series
where T: IntoIterator<Item=(f64,f64)> {
self.series.alloc(Series::new(PlotKind::Bars,label,data))
}
pub fn legend_pos(&mut self, pos: Corner) -> &mut Self {
self.legend().pos(pos);
self
}
pub fn legend<'a>(&'a mut self) -> Legend<'a> {
Legend::new(self)
}
pub fn grid<'a>(&'a mut self) -> Grid<'a> {
Grid::new(self)
}
pub fn extra_symbols(&mut self) -> &mut Self {
self.symbols = true;
self
}
pub fn markings<'a>(&'a mut self) -> Markings<'a> {
Markings::new(self)
}
pub fn set_option(&mut self, key: &str, subkey: &str, val: JsonValue) -> &mut Self {
if self.options[key].is_null() {
self.options[key] = object!{};
}
self.options[key][subkey] = val;
self
}
fn render_placeholder(&self, f: &mut Write) -> io::Result<()> {
if ! self.title.is_empty() {
write!(f, "<h2 style='text-align: center;width:{}px'>{}</h2>\n"
,self.bounds.0,self.title)?;
}
write!(f, "<div id={:?} style=\"width:{}px;height:{}px\"></div>\n",
self.placeholder,self.bounds.0,self.bounds.1)?;
for s in &self.description {
write!(f, "<p style='width:{}px;margin-left:2em;margin-right:2em'>{}</p>",self.bounds.0,s)?;
}
Ok(())
}
fn render_script(self, f: &mut Write) -> io::Result<()> {
let series = self.series.into_vec();
let mut data = '['.to_string();
let basename = "plot";
let mut k = 1;
for s in &series {
let varname = format!("{}_{}",basename,k);
k += 1;
write!(f,"var {} = {};\n",varname,s.data)?;
data += &varname;
data.push(',');
}
data.pop();
data.push(']');
let option_var = format!("{}_options",basename);
write!(f,"var {} = {};\n",option_var,self.options)?;
for lf in &self.option_functions {
write!(f,"{}.{};\n",option_var,lf)?;
}
write!(f,"$.plot($(\"#{}\"),{},{});\n",self.placeholder,data,option_var)
}
}
use std::env;
use std::fs::File;
use std::mem;
use std::cell::Cell;
pub struct Page {
plots: Arena<Plot>,
count: Cell<usize>,
title: String,
bounds: (u32,u32),
}
fn script(base: &str, name: &str) -> String {
format!("<script language=\"javascript\" type=\"text/javascript\" src=\"{}/{}\"></script>",
base,name)
}
impl Page {
pub fn new(title: &str) -> Page {
Page {
plots: Arena::new(),
count: Cell::new(0),
title: title.into(),
bounds: (800,300),
}
}
pub fn plot(&self, title: &str) -> &mut Plot {
let count = &self.count;
count.set(count.get() + 1);
let name = format!("plot{}",self.count.get());
self.plots.alloc(Plot::new(&name,title,self.bounds))
}
pub fn size(&mut self,width:u32,height:u32) -> &mut Self {
self.bounds = (width,height);
self
}
pub fn render(&self, file: &str) -> io::Result<()> {
let mut nplots = Arena::new();
let self_ptr: *const Self = self;
let mut_self: &mut Self = unsafe { &mut * (self_ptr as *mut Self) };
mem::swap(&mut mut_self.plots, &mut nplots);
let plots = nplots.into_vec();
let (jquery,flot) = if let Ok(f) = env::var("FLOT") {
let local = format!("file://{}",f);
(local.clone(),local.clone())
} else {
(
"https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1".to_string(),
"https://cdnjs.cloudflare.com/ajax/libs/flot/0.8.3".to_string()
)
};
let mut f = File::create(file)?;
let header = format!("
<html>
<head>
<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">
<title>{}</title>
", if ! self.title.is_empty() {&self.title} else {"Flot"});
write!(f,"{}{}\n{}\n",header,
script(&jquery,"jquery.min.js"),
script(&flot,"jquery.flot.min.js"))?;
if plots.iter().any(|p| p.time) {
write!(f,"{}\n",script(&flot,"jquery.flot.time.min.js"))?;
}
if plots.iter().any(|p| p.symbols) {
write!(f,"{}\n",script(&flot,"jquery.flot.symbol.min.js"))?;
}
write!(f,"</head>\n</body>\n")?;
if ! self.title.is_empty() {
write!(f,"<h1>{}</h1>\n",self.title)?;
}
for p in &plots {
p.render_placeholder(&mut f)?;
}
write!(f,"<script type=\"text/javascript\">\n$(function () {{\n")?;
for p in plots {
p.render_script(&mut f)?;
}
write!(f,"}});\n</script>\n</body>\n</html>\n")
}
}