use crate::build::{PlotIterator, PlotRes, Point};
use super::*;
mod render_base;
mod render_plot;
#[derive(Clone)]
pub struct RenderFrameBuilder {
num_css_classes: Option<usize>,
preserve_aspect: bool,
dim: Option<[f64; 2]>,
xtick_lines: bool,
ytick_lines: bool,
precision: usize,
bar_width: f64,
}
impl Default for RenderFrameBuilder {
fn default() -> Self {
RenderFrameBuilder {
num_css_classes: Some(8),
preserve_aspect: false,
dim: None,
xtick_lines: false,
ytick_lines: false,
precision: 2,
bar_width: 20.0,
}
}
}
impl RenderFrameBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn with_viewbox(&mut self, dim: [f64; 2]) -> &mut Self {
self.dim = Some(dim);
self
}
pub fn with_tick_lines(&mut self, a: [bool; 2]) -> &mut Self {
self.xtick_lines = a[0];
self.ytick_lines = a[1];
self
}
pub fn num_css_class(&mut self, a: Option<usize>) -> &mut Self {
self.num_css_classes = a;
self
}
pub fn with_precision(&mut self, precision: usize) -> &mut Self {
self.precision = precision;
self
}
pub fn preserve_aspect(&mut self) -> &mut Self {
self.preserve_aspect = true;
self
}
pub fn bar_width(&mut self, val: f64) -> &mut Self {
self.bar_width = val;
self
}
#[deprecated]
pub fn move_into(&mut self) -> Self {
self.clone()
}
pub fn build(&self) -> RenderFrame {
let (width, height) = if let Some([x, y]) = self.dim {
(x, y)
} else {
let [x, y] = Header::new().get_viewbox();
(x, y)
};
let ideal_dash_size = 20.0;
let padding = 150.0;
let paddingy = 100.0;
let (scalex, scaley) = if self.preserve_aspect {
if width > height {
(height - paddingy * 2.0, height - paddingy * 2.0)
} else {
(width - padding * 2.0, width - padding * 2.0)
}
} else {
(width - padding * 2.0, height - paddingy * 2.0)
};
let distancex_min_to_max = scalex;
let distancey_min_to_max = scaley;
let (xaspect_offset, yaspect_offset) = if self.preserve_aspect {
if width > height {
(-padding + width / 2.0 - distancey_min_to_max / 2.0, 0.0)
} else {
(
0.0,
-height + paddingy + height / 2.0 + distancey_min_to_max / 2.0,
)
}
} else {
(0.0, 0.0)
};
let ideal_xtick_spacing = 80.0;
let ideal_ytick_spacing = 60.0;
let ideal_num_xsteps = (distancex_min_to_max / ideal_xtick_spacing).floor() as u32;
let ideal_num_ysteps = (distancey_min_to_max / ideal_ytick_spacing).floor() as u32;
let ideal_num_xsteps = ideal_num_xsteps.max(2);
let ideal_num_ysteps = ideal_num_ysteps.max(2);
let spacing = padding / 3.0;
let legendx1 = width - padding / 1.2 + padding / 30.0;
RenderFrame {
boundx: ticks::RenderFrameBound {
ideal_num_steps: ideal_num_xsteps,
ideal_dash_size,
max: scalex,
axis: Axis::X,
},
boundy: ticks::RenderFrameBound {
ideal_num_steps: ideal_num_ysteps,
ideal_dash_size,
max: scaley,
axis: Axis::Y,
},
width,
height,
padding,
paddingy,
xaspect_offset,
yaspect_offset,
spacing,
legendx1,
num_css_classes: self.num_css_classes,
xtick_lines: self.xtick_lines,
ytick_lines: self.ytick_lines,
precision: self.precision,
bar_width: self.bar_width,
}
}
}
#[derive(Clone)]
pub struct RenderFrame {
boundx: ticks::RenderFrameBound,
boundy: ticks::RenderFrameBound,
width: f64,
height: f64,
padding: f64,
paddingy: f64,
xaspect_offset: f64,
yaspect_offset: f64,
spacing: f64,
legendx1: f64,
num_css_classes: Option<usize>,
xtick_lines: bool,
ytick_lines: bool,
precision: usize,
bar_width: f64,
}
impl RenderFrame {
pub fn data<X: PlotNum, Y: PlotNum, L: Point<X = X, Y = Y>, J: build::PlotIterator<L = L>>(
self,
plots: J,
) -> Stage1<PlotRes<J::P, L>, X::DefaultTicks, Y::DefaultTicks>
where
X: HasDefaultTicks,
Y: HasDefaultTicks,
{
render::Stage1::from_parts(plots, X::default_ticks(), Y::default_ticks(), self)
}
}
#[deprecated]
pub fn render_opt() -> RenderFrameBuilder {
RenderFrameBuilder::default()
}
pub struct Stage1<P: PlotIterator, TX, TY> {
opt: RenderFrame,
tickx: TX,
ticky: TY,
plots: P,
boundx: DataBound<<P::L as Point>::X>,
boundy: DataBound<<P::L as Point>::Y>,
}
impl<X: PlotNum, Y: PlotNum, L: Point<X = X, Y = Y>, P: build::PlotIterator<L = L>>
Stage1<P, X::DefaultTicks, Y::DefaultTicks>
where
X: HasDefaultTicks,
Y: HasDefaultTicks,
{
pub fn new(plots: P) -> Stage1<PlotRes<P::P, L>, X::DefaultTicks, Y::DefaultTicks> {
Self::from_parts(
plots,
X::default_ticks(),
Y::default_ticks(),
RenderFrameBuilder::new().build(),
)
}
}
impl<
X: PlotNum,
Y: PlotNum,
L: Point<X = X, Y = Y>,
P: build::PlotIterator<L = L>,
TX: TickDistGen<X>,
TY: TickDistGen<Y>,
> Stage1<P, TX, TY>
{
pub fn from_parts(
plots: P,
tickx: TX,
ticky: TY,
opt: RenderFrame,
) -> Stage1<PlotRes<P::P, L>, TX, TY> {
let PlotRes {
area,
it,
num_plots,
} = plots.unpack();
let (boundx, boundy) = area.build();
Stage1 {
opt,
plots: PlotRes {
it,
area,
num_plots,
},
ticky,
tickx,
boundx,
boundy,
}
}
#[deprecated]
pub fn map_opt<F: FnOnce(RenderFrame) -> RenderFrame>(self, func: F) -> Self {
Stage1 {
opt: func(self.opt),
tickx: self.tickx,
ticky: self.ticky,
plots: self.plots,
boundx: self.boundx,
boundy: self.boundy,
}
}
pub fn map_xticks<TTT: TickDistGen<X>, F: FnOnce(TX) -> TTT>(
self,
func: F,
) -> Stage1<P, TTT, TY> {
let tickx = func(self.tickx);
Stage1 {
opt: self.opt,
tickx,
ticky: self.ticky,
plots: self.plots,
boundx: self.boundx,
boundy: self.boundy,
}
}
pub fn map_yticks<TTT: TickDistGen<Y>, F: FnOnce(TY) -> TTT>(
self,
func: F,
) -> Stage1<P, TX, TTT> {
let ticky = func(self.ticky);
Stage1 {
opt: self.opt,
tickx: self.tickx,
ticky,
plots: self.plots,
boundx: self.boundx,
boundy: self.boundy,
}
}
pub fn build_map<F: FnOnce(Stage2<P, TX::Res, TY::Res>) -> K, K>(self, func: F) -> K {
let k = self.build();
func(k)
}
pub fn build(self) -> Stage2<P, TX::Res, TY::Res> {
let mut index_counter = 0;
let data = self;
let opt = data.opt;
let xticks = data.tickx.generate(
&data.boundx,
&opt.boundx,
IndexRequester::new(&mut index_counter),
);
let yticks = data.ticky.generate(
&data.boundy,
&opt.boundy,
IndexRequester::new(&mut index_counter),
);
Stage2 {
opt,
xticks,
yticks,
boundx: data.boundx,
boundy: data.boundy,
plots: data.plots,
}
}
pub fn build_and_label<Fmt: BaseFmt>(self, fmt: Fmt) -> Stage3<P, TX::Res, TY::Res, Fmt> {
self.build().label(fmt)
}
}
pub struct Stage2<P: PlotIterator, A, B> {
opt: RenderFrame,
xticks: A,
yticks: B,
plots: P,
boundx: DataBound<<P::L as Point>::X>,
boundy: DataBound<<P::L as Point>::Y>,
}
impl<
X: PlotNum,
Y: PlotNum,
L: Point<X = X, Y = Y>,
P: PlotIterator<L = L>,
A: TickDist<Num = X>,
B: TickDist<Num = Y>,
> Stage2<P, A, B>
{
pub fn label<Fmt: BaseFmt>(self, fmt: Fmt) -> Stage3<P, A, B, Fmt> {
Stage3 {
data: self,
base: fmt,
}
}
pub fn boundx(&self) -> &DataBound<X> {
&self.boundx
}
pub fn boundy(&self) -> &DataBound<Y> {
&self.boundy
}
pub fn xticks(&self) -> &A {
&self.xticks
}
pub fn yticks(&self) -> &B {
&self.yticks
}
}
pub struct Stage3<P: PlotIterator, A, B, BB> {
data: Stage2<P, A, B>,
base: BB,
}
impl<X: PlotNum, Y: PlotNum, L: Point<X = X, Y = Y>, P, A, B, BB> Stage3<P, A, B, BB>
where
P: PlotIterator<L = L>,
A: crate::ticks::TickDist<Num = X>,
B: crate::ticks::TickDist<Num = Y>,
BB: BaseFmt,
{
pub fn append_to<E: Elem>(self, elem: E) -> Stage4<elem::Append<E, Self>> {
Stage4(elem.append(self))
}
pub fn headless(self) -> Stage4<Self> {
Stage4(self)
}
}
use tagu::stack::*;
impl<X: PlotNum, Y: PlotNum, L: Point<X = X, Y = Y>, P, A, B, BB> ElemOuter for Stage3<P, A, B, BB>
where
P: PlotIterator<L = L>,
A: crate::ticks::TickDist<Num = X>,
B: crate::ticks::TickDist<Num = Y>,
BB: BaseFmt,
{
fn render<'a>(
mut self,
mut writer: ElemStack<'a, Sentinel>,
) -> Result<ElemStack<'a, Sentinel>, fmt::Error> {
writer.put(hbuild::single("circle").with(attrs!(
("r", "1e5"),
("class", "poloto_background"),
("fill", "white")
)))?;
let writer = render::render_plot::render_plot(
writer,
&self.data.boundx,
&self.data.boundy,
&self.data.opt,
self.data.plots,
)?;
render::render_base::render_base(
writer,
self.data.xticks,
self.data.yticks,
&self.data.boundx,
&self.data.boundy,
&mut self.base,
&self.data.opt,
)
}
}
impl<A, B, C> BaseFmt for (A, B, C)
where
A: Display,
B: Display,
C: Display,
{
fn write_title(&self, writer: &mut dyn fmt::Write) -> fmt::Result {
write!(writer, "{}", self.0)
}
fn write_xname(&self, writer: &mut dyn fmt::Write) -> fmt::Result {
write!(writer, "{}", self.1)
}
fn write_yname(&self, writer: &mut dyn fmt::Write) -> fmt::Result {
write!(writer, "{}", self.2)
}
}
pub struct Stage4<R>(R);
impl<R: Elem + Locked> Stage4<R> {
pub fn render_stdout(self) {
tagu::render(self.0, tagu::stdout_fmt()).unwrap()
}
pub fn render_fmt_write<T: fmt::Write>(self, w: T) -> fmt::Result {
tagu::render(self.0, w)
}
pub fn render_io_write<T: std::io::Write>(self, w: T) -> std::fmt::Result {
tagu::render(self.0, tagu::tools::upgrade_write(w))
}
pub fn render_string(self) -> Result<String, fmt::Error> {
let mut s = String::new();
tagu::render(self.0, &mut s)?;
Ok(s)
}
}
impl<R> Locked for Stage4<R> {}
impl<R: Elem> Elem for Stage4<R> {
type Tail = R::Tail;
fn render_head(self, w: elem::ElemWrite) -> Result<Self::Tail, fmt::Error> {
self.0.render_head(w)
}
}
#[derive(Copy, Clone)]
pub struct Header<A> {
dim: [f64; 2],
viewbox: [f64; 2],
attr: A,
}
impl Default for Header<()> {
fn default() -> Self {
Self::new()
}
}
use tagu::{attr::Attr, elem::Locked};
impl Header<()> {
pub fn new() -> Self {
let a = [800.0, 500.0];
Header {
dim: a,
viewbox: a,
attr: (),
}
}
}
impl<A: Attr> Header<A> {
pub fn with<AA: Attr>(self, attr: AA) -> Header<AA> {
Header {
dim: self.dim,
viewbox: self.viewbox,
attr,
}
}
pub fn with_viewbox_width(self, width: f64) -> Self {
let [xx, yy] = self.dim;
let vh = width * (yy / xx);
Header {
dim: self.dim,
viewbox: [width, vh],
attr: self.attr,
}
}
pub fn get_viewbox(&self) -> [f64; 2] {
self.viewbox
}
pub fn with_dim(self, dim: [f64; 2]) -> Self {
Header {
dim,
viewbox: self.viewbox,
attr: self.attr,
}
}
pub fn with_viewbox(self, viewbox: [f64; 2]) -> Self {
Header {
dim: self.dim,
viewbox,
attr: self.attr,
}
}
pub fn light_theme(self) -> elem::Append<Self, Theme<'static>> {
self.append(Theme::light())
}
pub fn dark_theme(self) -> elem::Append<Self, Theme<'static>> {
self.append(Theme::dark())
}
}
impl<A> Locked for Header<A> {}
impl<A: Attr> Elem for Header<A> {
type Tail = tagu::elem::ElementTail<&'static str>;
fn render_head(self, w: elem::ElemWrite) -> Result<Self::Tail, fmt::Error> {
let elem = tagu::build::elem("svg").with(attrs!(
("class", "poloto"),
("width", self.dim[0]),
("height", self.dim[1]),
(
"viewBox",
format_move!("{} {} {} {}", 0, 0, self.viewbox[0], self.viewbox[1])
),
("xmlns", "http://www.w3.org/2000/svg"),
self.attr
));
elem.render_head(w)
}
}
#[derive(Copy, Clone)]
pub struct Theme<'a> {
styles: &'a str,
}
impl Theme<'static> {
pub const fn light() -> Theme<'static> {
const STYLE_CONFIG_LIGHT_DEFAULT: &str = ".poloto{
stroke-linecap:round;
stroke-linejoin:round;
font-family:Roboto,sans-serif;
font-size:16px;
}
.poloto_background{fill:AliceBlue;}
.poloto_scatter{stroke-width:7}
.poloto_line{stroke-width:2}
.poloto_text{fill: black;}
.poloto_name{font-size:24px;dominant-baseline:auto;text-anchor:middle;}
.poloto_where{dominant-baseline:middle;text-anchor:start}
.poloto_text.poloto_legend{font-size:20px;dominant-baseline:middle;text-anchor:start;}
.poloto_text.poloto_ticks.poloto_y{dominant-baseline:middle;text-anchor:end}
.poloto_text.poloto_ticks.poloto_x{dominant-baseline:auto;text-anchor:middle}
.poloto_imgs.poloto_ticks{stroke: black;stroke-width:3;fill:none;stroke-dasharray:none}
.poloto_grid{stroke:gray;stroke-width:0.5}
.poloto0.poloto_stroke{stroke:blue;}
.poloto1.poloto_stroke{stroke:red;}
.poloto2.poloto_stroke{stroke:green;}
.poloto3.poloto_stroke{stroke:gold;}
.poloto4.poloto_stroke{stroke:aqua;}
.poloto5.poloto_stroke{stroke:lime;}
.poloto6.poloto_stroke{stroke:orange;}
.poloto7.poloto_stroke{stroke:chocolate;}
.poloto0.poloto_fill{fill:blue;}
.poloto1.poloto_fill{fill:red;}
.poloto2.poloto_fill{fill:green;}
.poloto3.poloto_fill{fill:gold;}
.poloto4.poloto_fill{fill:aqua;}
.poloto5.poloto_fill{fill:lime;}
.poloto6.poloto_fill{fill:orange;}
.poloto7.poloto_fill{fill:chocolate;}";
Theme {
styles: STYLE_CONFIG_LIGHT_DEFAULT,
}
}
pub const fn dark() -> Theme<'static> {
const STYLE_CONFIG_DARK_DEFAULT: &str = ".poloto{
stroke-linecap:round;
stroke-linejoin:round;
font-family:Roboto,sans-serif;
font-size:16px;
}
.poloto_background{fill:#262626;}
.poloto_scatter{stroke-width:7}
.poloto_line{stroke-width:2}
.poloto_text{fill: white;}
.poloto_name{font-size:24px;dominant-baseline:auto;text-anchor:middle;}
.poloto_where{dominant-baseline:middle;text-anchor:start}
.poloto_text.poloto_legend{font-size:20px;dominant-baseline:middle;text-anchor:start;}
.poloto_text.poloto_ticks.poloto_y{dominant-baseline:middle;text-anchor:end}
.poloto_text.poloto_ticks.poloto_x{dominant-baseline:auto;text-anchor:middle}
.poloto_imgs.poloto_ticks{stroke: white;stroke-width:3;fill:none;stroke-dasharray:none}
.poloto_grid{stroke:gray;stroke-width:0.5}
.poloto0.poloto_stroke{stroke:blue;}
.poloto1.poloto_stroke{stroke:red;}
.poloto2.poloto_stroke{stroke:green;}
.poloto3.poloto_stroke{stroke:gold;}
.poloto4.poloto_stroke{stroke:aqua;}
.poloto5.poloto_stroke{stroke:lime;}
.poloto6.poloto_stroke{stroke:orange;}
.poloto7.poloto_stroke{stroke:chocolate;}
.poloto0.poloto_fill{fill:blue;}
.poloto1.poloto_fill{fill:red;}
.poloto2.poloto_fill{fill:green;}
.poloto3.poloto_fill{fill:gold;}
.poloto4.poloto_fill{fill:aqua;}
.poloto5.poloto_fill{fill:lime;}
.poloto6.poloto_fill{fill:orange;}
.poloto7.poloto_fill{fill:chocolate;}";
Theme {
styles: STYLE_CONFIG_DARK_DEFAULT,
}
}
pub const fn get_str(&self) -> &'static str {
self.styles
}
}
impl<'a> Locked for Theme<'a> {}
impl<'a> Elem for Theme<'a> {
type Tail = tagu::elem::ElementTail<&'static str>;
fn render_head(self, w: elem::ElemWrite) -> Result<Self::Tail, fmt::Error> {
let k = tagu::build::elem("style");
let k = k.append(tagu::build::raw(self.styles));
k.render_head(w)
}
}
#[derive(Copy, Clone)]
pub struct FloatFmt {
precision: usize,
}
impl FloatFmt {
pub fn new(precision: usize) -> Self {
FloatFmt { precision }
}
pub fn disp(&self, num: f64) -> impl Display {
let precision = self.precision;
util::disp_const(move |f| write!(f, "{:.*}", precision, num))
}
}