use std::iter::FusedIterator;
use super::*;
use crate::build::*;
struct SinglePlotIterator<I> {
it: I,
size_hint: (usize, Option<usize>),
finished: bool,
}
impl<I: Iterator<Item = PlotTag<L, D>>, L: Point, D: Display> SinglePlotIterator<I> {
fn new(mut it: I) -> Option<(Self, D, PlotMetaType)> {
if let Some(o) = it.next() {
match o {
PlotTag::Start {
name,
typ,
size_hint,
} => Some((
Self {
it,
size_hint,
finished: false,
},
name,
typ,
)),
PlotTag::Plot(_) => panic!("expected start"),
PlotTag::Finish() => panic!("expected start"),
}
} else {
None
}
}
}
impl<I: ExactSizeIterator<Item = PlotTag<L, D>>, L: Point, D: Display> ExactSizeIterator
for SinglePlotIterator<I>
{
}
impl<I: Iterator<Item = PlotTag<L, D>>, L: Point, D: Display> FusedIterator
for SinglePlotIterator<I>
{
}
impl<I: Iterator<Item = PlotTag<L, D>>, L: Point, D: Display> Iterator for SinglePlotIterator<I> {
type Item = L;
fn next(&mut self) -> Option<Self::Item> {
if self.finished {
return None;
}
let o = self.it.next().unwrap();
match o {
PlotTag::Start { .. } => panic!("did not expect start"),
PlotTag::Plot(a) => Some(a),
PlotTag::Finish() => {
self.finished = true;
None
}
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.size_hint
}
}
pub(super) fn render_plot<
'a,
X: PlotNum,
Y: PlotNum,
L: Point<X = X, Y = Y>,
P: build::PlotIterator<L = L>,
>(
mut writer: ElemStack<'a, Sentinel>,
boundx: &ticks::DataBound<X>,
boundy: &ticks::DataBound<Y>,
canvas: &RenderFrame,
plots_all: P,
) -> Result<ElemStack<'a, Sentinel>, fmt::Error> {
let RenderFrame {
width,
height,
padding,
paddingy,
xaspect_offset,
yaspect_offset,
spacing,
num_css_classes,
..
} = canvas;
let scalex = canvas.boundx.max;
let scaley = canvas.boundy.max;
let boundx = [boundx.min, boundx.max];
let boundy = [boundy.min, boundy.max];
let [minx, maxx] = boundx;
let [miny, maxy] = boundy;
let mut color_iter = {
let max = if let Some(nn) = *num_css_classes {
nn
} else {
usize::MAX
};
(0..max).cycle()
};
let mut color_iter2 = color_iter.clone();
let mut names = vec![];
let PlotRes {
mut it, num_plots, ..
} = plots_all.unpack();
for i in 0..num_plots {
let (mut it, label, typ) = SinglePlotIterator::new(&mut it).unwrap();
let mut name = String::new();
use std::fmt::Write;
write!(&mut name, "{}", label).unwrap();
let name_exists = !name.is_empty();
if name_exists {
names.push((typ, name, i));
}
let aa = minx.scale(&[minx, maxx], scalex);
let bb = miny.scale(&[miny, maxy], scaley);
match typ {
PlotMetaType::Text => {
assert_eq!((&mut it).count(), 0);
}
PlotMetaType::Plot(p_type) => {
let colori = color_iter.next().unwrap();
let rangex_ii = &[minx, maxx];
let rangey_ii = &[miny, maxy];
let basex_ii = xaspect_offset + padding - aa;
let basey_ii = yaspect_offset + height - paddingy + bb;
let maxx_ii = scalex;
let maxy_ii = scaley;
let it = it.map(move |l| {
let (x, y) = l.get();
[
basex_ii + x.scale(rangex_ii, maxx_ii),
basey_ii - y.scale(rangey_ii, maxy_ii),
]
});
let precision = canvas.precision;
render(
&mut writer,
it,
PlotRenderInfo {
canvas,
p_type,
colori,
precision,
bar_width: canvas.bar_width,
},
)?;
}
}
}
assert!(SinglePlotIterator::new(&mut it).is_none());
if !names.is_empty() {
for (typ, name, i) in names.iter() {
match typ {
PlotMetaType::Text => {
}
&PlotMetaType::Plot(p_type) => {
let colori = color_iter2.next().unwrap();
let legendy1 =
paddingy - yaspect_offset - padding / 8.0 + (*i as f64) * spacing;
if !name.is_empty() {
render_label(
&mut writer,
PlotRenderInfo2 {
canvas,
p_type,
colori,
legendy1,
},
)?;
}
}
}
}
let bb = hbuild::from_iter(names.iter().map(|(typ, name, i)| {
let class = match typ {
PlotMetaType::Plot(e) => match e {
PlotType::Scatter => "poloto_scatter",
PlotType::Line => "poloto_line",
PlotType::Histo => "poloto_histo",
PlotType::LineFill => "poloto_linefill",
PlotType::LineFillRaw => "poloto_linefillraw",
PlotType::Bars => "poloto_bars",
},
PlotMetaType::Text => "",
};
let text = hbuild::elem("text")
.with(attrs!(
(
"class",
format_move!("poloto_legend poloto_text {} poloto{}", class, i)
),
("x", width - padding / 1.2),
("y", paddingy - yaspect_offset + (*i as f64) * spacing)
))
.inline();
text.append(hbuild::raw(name))
}));
writer.put(bb)?;
}
Ok(writer)
}
struct PlotRenderInfo2<'a> {
canvas: &'a RenderFrame,
p_type: PlotType,
colori: usize,
legendy1: f64,
}
struct PlotRenderInfo<'a> {
canvas: &'a RenderFrame,
p_type: PlotType,
colori: usize,
precision: usize,
bar_width: f64,
}
fn render_label<T>(writer: &mut ElemStack<T>, info: PlotRenderInfo2) -> fmt::Result {
let PlotRenderInfo2 {
canvas,
p_type,
colori,
legendy1,
..
} = info;
let RenderFrame {
padding, legendx1, ..
} = *canvas;
match p_type {
PlotType::Line => {
let g = hbuild::elem("g").with((
"class",
format_move!(
"poloto_legend poloto_imgs poloto_line poloto{} poloto_stroke",
colori
),
));
let g = g.append(hbuild::single("line").with(attrs!(
("x1", legendx1),
("x2", legendx1 + padding / 3.0),
("y1", legendy1),
("y2", legendy1)
)));
writer.put(g.inline())?;
}
PlotType::Scatter => {
let g = hbuild::elem("g").with((
"class",
format_move!(
"poloto_legend poloto_imgs poloto_scatter poloto{} poloto_stroke",
colori,
),
));
let g = g.append(hbuild::single("line").with(attrs!(
("x1", legendx1 + padding / 30.0),
("x2", legendx1 + padding / 30.0),
("y1", legendy1),
("y2", legendy1)
)));
writer.put(g.inline())?;
}
PlotType::Histo => {
let g = hbuild::elem("g").with((
"class",
format_move!(
"poloto_legend poloto_imgs poloto_histo poloto{} poloto_fill",
colori,
),
));
let g = g.append(hbuild::single("rect").with(attrs!(
("x", legendx1),
("y", legendy1 - padding / 30.0),
("width", padding / 3.0),
("height", padding / 20.0),
("rx", padding / 30.0),
("ry", padding / 30.0)
)));
writer.put(g.inline())?;
}
PlotType::LineFill => {
let g = hbuild::elem("g").with((
"class",
format_move!(
"poloto_legend poloto_imgs poloto_linefill poloto{} poloto_fill",
colori,
),
));
let g = g.append(hbuild::single("rect").with(attrs!(
("x", legendx1),
("y", legendy1 - padding / 30.0),
("width", padding / 3.0),
("height", padding / 20.0),
("rx", padding / 30.0),
("ry", padding / 30.0)
)));
writer.put(g.inline())?;
}
PlotType::LineFillRaw => {
let g = hbuild::elem("g").with((
"class",
format_move!(
"poloto_legend poloto_imgs poloto_linefillraw poloto{} poloto_fill",
colori,
),
));
let g = g.append(hbuild::single("rect").with(attrs!(
("x", legendx1),
("y", legendy1 - padding / 30.0),
("width", padding / 3.0),
("height", padding / 20.0),
("rx", padding / 30.0),
("ry", padding / 30.0)
)));
writer.put(g.inline())?;
}
PlotType::Bars => {
let g = hbuild::elem("g").with((
"class",
format_move!(
"poloto_legend poloto_imgs poloto_bars poloto{} poloto_fill",
colori
),
));
let g = g.append(hbuild::single("rect").with(attrs!(
("x", legendx1),
("y", legendy1 - padding / 30.0),
("width", padding / 3.0),
("height", padding / 20.0),
("rx", padding / 30.0),
("ry", padding / 30.0)
)));
writer.put(g.inline())?;
}
};
Ok(())
}
fn render<T>(
writer: &mut ElemStack<T>,
it: impl Iterator<Item = [f64; 2]>,
info: PlotRenderInfo,
) -> fmt::Result {
let PlotRenderInfo {
canvas,
p_type,
colori,
precision,
bar_width,
..
} = info;
let RenderFrame {
height,
padding,
paddingy,
..
} = *canvas;
let ffmt = FloatFmt::new(precision);
match p_type {
PlotType::Line => {
let g = hbuild::elem("g").with(attrs!(
("id", format_move!("poloto_plot{}", colori)),
(
"class",
format_move!(
"poloto_plot poloto_imgs poloto_line poloto{} poloto_stroke",
colori
)
),
("fill", "none"),
("stroke", "black")
));
let j = hbuild::single("path").with(attrs!(Line::new(it, ffmt)));
writer.put(g.append(j))?;
}
PlotType::Scatter => {
let g = hbuild::elem("g").with(attrs!(
("id", format_move!("poloto_plot{}", colori)),
(
"class",
format_move!(
"poloto_plot poloto_imgs poloto_scatter poloto{} poloto_stroke",
colori
),
)
));
let j = hbuild::single("path").with(attrs!(hbuild::path_from_closure(move |w| {
let mut w = w.start();
use tagu::attr::PathCommand::*;
for [x, y] in it.filter(|&[x, y]| x.is_finite() && y.is_finite()) {
w.put(M(ffmt.disp(x), ffmt.disp(y)))?;
w.put(H_(ffmt.disp(0.0)))?;
}
Ok(())
})));
writer.put(g.append(j))?;
}
PlotType::Histo => {
let g = hbuild::elem("g").with(attrs!(
("id", format_move!("poloto_plot{}", colori)),
(
"class",
format_move!(
"poloto_plot poloto_imgs poloto_histo poloto{} poloto_fill",
colori
),
)
));
let h = hbuild::from_stack(move |mut w| {
let mut last = None;
for [x, y] in it.filter(|&[x, y]| x.is_finite() && y.is_finite()) {
if let Some((lx, ly)) = last {
w.put(hbuild::single("rect").with(attrs!(
("x", ffmt.disp(lx)),
("y", ffmt.disp(ly)),
("width", (padding * 0.02).max((x - lx) - (padding * 0.02))),
("height", height - paddingy - ly)
)))?;
}
last = Some((x, y))
}
Ok(w)
});
writer.put(g.append(h))?;
}
PlotType::LineFill => {
let g = hbuild::elem("g").with(attrs!(
("id", format_move!("poloto_plot{}", colori)),
(
"class",
format_move!(
"poloto_plot poloto_imgs poloto_linefill poloto{} poloto_fill",
colori
),
)
));
let j = hbuild::single("path").with(attrs!(LineFill::new(
it,
ffmt,
height - paddingy,
true
)));
writer.put(g.append(j))?;
}
PlotType::LineFillRaw => {
let g = hbuild::elem("g").with(attrs!(
("id", format_move!("poloto_plot{}", colori)),
(
"class",
format_move!(
"poloto_plot poloto_imgs poloto_linefill poloto{} poloto_fill",
colori
),
)
));
let j = hbuild::single("path").with(attrs!(LineFill::new(
it,
ffmt,
height - paddingy,
false
)));
writer.put(g.append(j))?;
}
PlotType::Bars => {
let g = hbuild::elem("g").with(attrs!(
("id", format_move!("poloto_plot{}", colori)),
(
"class",
format_move!(
"poloto_plot poloto_imgs poloto_histo poloto{} poloto_fill",
colori
),
)
));
let h = hbuild::from_stack(move |mut w| {
for [x, y] in it.filter(|&[x, y]| x.is_finite() && y.is_finite()) {
w.put(hbuild::single("rect").with(attrs!(
("x", ffmt.disp(padding)),
("y", ffmt.disp(y - bar_width / 2.0)),
("width", x - padding),
("height", bar_width)
)))?;
}
Ok(w)
});
writer.put(g.append(h))?;
}
};
Ok(())
}
struct LineFill<I> {
it: I,
fmt: FloatFmt,
base_line: f64,
add_start_end_base: bool,
}
impl<I: Iterator<Item = [f64; 2]>> LineFill<I> {
pub fn new(it: I, fmt: FloatFmt, base_line: f64, add_start_end_base: bool) -> Self {
LineFill {
it,
fmt,
base_line,
add_start_end_base,
}
}
}
impl<I: Iterator<Item = [f64; 2]>> attr::Attr for LineFill<I> {
fn render(self, w: &mut attr::AttrWrite) -> fmt::Result {
let LineFill {
mut it,
fmt,
base_line,
add_start_end_base,
} = self;
w.render(tagu::build::path_from_closure(|w| {
let mut w = w.start();
if let Some([startx, starty]) = it.next() {
use tagu::attr::PathCommand::*;
let mut last = [startx, starty];
let mut last_finite = None;
let mut first = true;
for [newx, newy] in it {
match (
newx.is_finite() && newy.is_finite(),
last[0].is_finite() && last[1].is_finite(),
) {
(true, true) => {
if first {
if add_start_end_base {
w.put(M(fmt.disp(last[0]), fmt.disp(base_line)))?;
w.put(L(fmt.disp(last[0]), fmt.disp(last[1])))?;
} else {
w.put(M(fmt.disp(last[0]), fmt.disp(last[1])))?;
}
first = false;
}
last_finite = Some([newx, newy]);
w.put(L(fmt.disp(newx), fmt.disp(newy)))?;
}
(true, false) => {
w.put(M(fmt.disp(newx), fmt.disp(newy)))?;
}
(false, true) => {
w.put(L(fmt.disp(last[0]), fmt.disp(base_line)))?;
}
_ => {}
};
last = [newx, newy];
}
if let Some([x, _]) = last_finite {
if add_start_end_base {
w.put(L(fmt.disp(x), fmt.disp(base_line)))?;
}
w.put(Z())?;
}
}
Ok(())
}))
}
}
struct Line<I> {
it: I,
fmt: FloatFmt,
}
impl<I: Iterator<Item = [f64; 2]>> Line<I> {
pub fn new(it: I, fmt: FloatFmt) -> Self {
Line { it, fmt }
}
}
impl<I: Iterator<Item = [f64; 2]>> attr::Attr for Line<I> {
fn render(self, w: &mut attr::AttrWrite) -> fmt::Result {
let Line { mut it, fmt } = self;
w.render(tagu::build::path_from_closure(|w| {
let mut w = w.start();
if let Some([startx, starty]) = it.next() {
use tagu::attr::PathCommand::*;
let mut last = [startx, starty];
let mut first = true;
for [newx, newy] in it {
match (
newx.is_finite() && newy.is_finite(),
last[0].is_finite() && last[1].is_finite(),
) {
(true, true) => {
if first {
w.put(M(fmt.disp(last[0]), fmt.disp(last[1])))?;
first = false;
}
w.put(L(fmt.disp(newx), fmt.disp(newy)))?;
}
(true, false) => {
w.put(M(fmt.disp(newx), fmt.disp(newy)))?;
}
_ => {}
};
last = [newx, newy];
}
}
Ok(())
}))
}
}