use malevich::{Frame, Line, Plot, Theme};
use super::{html, render};
use crate::{Element, Field};
fn norms<E: Element>(field: &Field<E>) -> Vec<f64>
where
f64: From<E>,
{
field
.payloads()
.iter()
.map(render::euclidean_norm)
.collect()
}
fn summary(label: &str, norms: &[f64]) -> String {
let finite: Vec<f64> = norms
.iter()
.copied()
.filter(|norm| norm.is_finite())
.collect();
let largest = finite.iter().copied().fold(0.0_f64, f64::max);
let mean = if finite.is_empty() {
0.0
} else {
finite.iter().sum::<f64>() / finite.len() as f64
};
let unusual = norms.len() - finite.len();
let mut parts = vec![
label.to_string(),
format!("{} nodes", norms.len()),
format!("max norm {}", render::number(largest)),
format!("mean norm {}", render::number(mean)),
];
if unusual > 0 {
parts.push(format!("{unusual} non-finite"));
}
parts.join(" \u{b7} ")
}
pub(crate) fn profile_card<E: Element>(theme: Theme, label: &str, field: &Field<E>) -> String
where
f64: From<E>,
{
let norms = norms(field);
let header = html::escape(&summary(label, &norms));
if norms.len() < 2 {
let only = norms.first().copied().unwrap_or(0.0);
let body = format!(
"<div style=\"font-size:20px\">{}</div>",
html::escape(&render::number(only))
);
return html::card(theme, &header, &body);
}
let mut frame = Frame::plain(72, 18);
frame.theme = theme;
let plot = Plot::new()
.layer(Line::y(&norms[..]).label("norm"))
.x_label("node");
html::card(theme, &header, &plot.to_html(&frame))
}
pub(crate) fn profile_text<E: Element>(label: &str, field: &Field<E>) -> String
where
f64: From<E>,
{
let norms = norms(field);
let header = summary(label, &norms);
if norms.len() < 2 {
let only = norms.first().copied().unwrap_or(0.0);
return format!("{header}\n{}", render::number(only));
}
let plot = Plot::new()
.layer(Line::y(&norms[..]).label("norm"))
.x_label("node");
format!("{header}\n{}", plot.render(&Frame::plain(72, 18)))
}
impl<E: Element> Field<E>
where
f64: From<E>,
{
pub fn to_html(&self, theme: Theme) -> String {
profile_card(theme, "field", self)
}
pub fn evcxr_display(&self) {
html::show(&self.to_html(Theme::detect()), &profile_text("field", self));
}
}
#[cfg(test)]
#[path = "tests/field_tests.rs"]
mod tests;