Functional Weave
Code in TypeScript

charts.layout@1.0.1

impl/rust/legend_rows.rs

2,310 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::math_round_float::round_float;  ← from math.round-float ^1.0.0 · built alongside by fune

// Space between the swatch and its text.
const SWATCH_PADDING: f64 = 4.0;

/// Legend items left to right, wrapping when the next would pass max_width.
/// Text width is estimated as code points times char_width; no font is
/// measured, so the answer is the same in every language.
///
/// # Panics
/// Panics if max_width is not positive or any other size is negative.
pub fn legend_rows(
    labels: &[String],
    max_width: f64,
    char_width: f64,
    swatch_size: f64,
    gap: f64,
    row_height: f64,
) -> Vec<LegendItem> {
    if !(max_width > 0.0) {
        panic!("maxWidth must be greater than zero, received {}", max_width);
    }
    if !(char_width >= 0.0 && swatch_size >= 0.0 && gap >= 0.0 && row_height >= 0.0) {
        panic!("legend sizes must not be negative");
    }
    let mut items = Vec::new();
    let mut row: i64 = 0;
    let mut x = 0.0;
    let mut on_row = 0;
    for label in labels {
        let width = swatch_size + SWATCH_PADDING + label.chars().count() as f64 * char_width;
        if on_row > 0 && x + width > max_width {
            row += 1;
            x = 0.0;
            on_row = 0;
        }
        items.push(LegendItem {
            label: label.clone(),
            row,
            x: round_float(x, 2),
            y: round_float(row as f64 * row_height, 2),
            width: round_float(width, 2),
            text_x: round_float(x + swatch_size + SWATCH_PADDING, 2),
        });
        x = x + width + gap;
        on_row += 1;
    }
    items
}

pub fn legend_item_to_value(item: &LegendItem) -> Value {
    Value::obj(vec![
        ("label", Value::str(&item.label)),
        ("row", Value::Int(item.row)),
        ("x", Value::Float(item.x)),
        ("y", Value::Float(item.y)),
        ("width", Value::Float(item.width)),
        ("textX", Value::Float(item.text_x)),
    ])
}

pub fn fune_vector(args: &[Value]) -> Value {
    let labels: Vec<String> = args[0].as_arr().iter().map(|v| v.as_str().to_string()).collect();
    let items = legend_rows(
        &labels,
        args[1].as_f64(),
        args[2].as_f64(),
        args[3].as_f64(),
        args[4].as_f64(),
        args[5].as_f64(),
    );
    Value::Arr(items.iter().map(legend_item_to_value).collect())
}