Functional Weave
Code in TypeScript

charts.sparkline@1.0.0

impl/rust.rs

2,557 bytes · the Rust implementation · view raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

use super::funejson::Value;  ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::charts_extent::extent;  ← from charts.extent ^1.0.0 · built alongside by fune
use super::charts_scale_linear_scale::linear_scale;
use super::charts_shape_line_path::line_path;
use super::charts_shape_types::Point;
use super::math_round_float::round_float;  ← from math.round-float ^1.0.0 · built alongside by fune

fn check_size(value: f64, what: &str) {
    if !value.is_finite() || value < 0.0 {
        panic!("{} must be a finite number of zero or more, received {}", what, value);
    }
}

/// A sparkline in a box; see the README for flat and single-value series.
///
/// # Panics
/// Panics on a negative or non-finite size, or a box too small for its padding.
pub fn sparkline(values: &[Option<f64>], width: f64, height: f64, padding: f64) -> Sparkline {
    check_size(width, "width");
    check_size(height, "height");
    check_size(padding, "padding");
    if width - 2.0 * padding <= 0.0 || height - 2.0 * padding <= 0.0 {
        panic!(
            "the box is too small for its padding: {} by {} with {} on each side",
            width, height, padding
        );
    }
    let range = match extent(values) {
        None => return Sparkline { path: String::new(), end: None },
        Some(r) => r,
    };
    let (lo, hi) = (range[0], range[1]);
    let n = values.len();
    let points: Vec<Point> = values
        .iter()
        .enumerate()
        .map(|(i, v)| {
            let x = if n == 1 {
                width - padding
            } else {
                linear_scale(&[0.0, (n - 1) as f64], &[padding, width - padding], i as f64, false)
            };
            let y = v.map(|v| if lo == hi { height / 2.0 } else { linear_scale(&[lo, hi], &[height - padding, padding], v, false) });
            Point { x, y }
        })
        .collect();
    let end = points
        .iter()
        .filter_map(|p| p.y.map(|y| SparkPoint { x: round_float(p.x, 2), y: round_float(y, 2) }))
        .last();
    Sparkline { path: line_path(&points), end }
}

pub fn sparkline_to_value(s: &Sparkline) -> Value {
    Value::obj(vec![
        ("path", Value::str(&s.path)),
        (
            "end",
            match &s.end {
                None => Value::Null,
                Some(p) => Value::obj(vec![("x", Value::Float(p.x)), ("y", Value::Float(p.y))]),
            },
        ),
    ])
}

pub fn fune_vector(args: &[Value]) -> Value {
    let values: Vec<Option<f64>> = args[0]
        .as_arr()
        .iter()
        .map(|v| if v.is_null() { None } else { Some(v.as_f64()) })
        .collect();
    sparkline_to_value(&sparkline(&values, args[1].as_f64(), args[2].as_f64(), args[3].as_f64()))
}