rust-office-charts 0.1.7

Render Office Open XML charts to SVG from native Rust or WebAssembly
Documentation
use super::{line, text, text_width, Coords};
use std::collections::BTreeMap;

pub(super) type Hierarchy = BTreeMap<usize, BTreeMap<u32, String>>;

fn band_extent(labels: &BTreeMap<u32, String>, size: f64, horizontal: bool) -> f64 {
    if horizontal {
        size * 2.0
    } else {
        labels
            .values()
            .map(|s| text_width(s, size))
            .fold(0., f64::max)
            + size * 0.9
    }
}

pub(super) fn extent(hierarchy: &Hierarchy, size: f64, horizontal: bool) -> f64 {
    hierarchy
        .values()
        .map(|labels| band_extent(labels, size, horizontal))
        .sum()
}

pub(super) fn leaf_extent(
    labels: &BTreeMap<u32, String>,
    size: f64,
    rotation: f64,
    horizontal: bool,
) -> f64 {
    let width = labels
        .values()
        .map(|s| text_width(s, size))
        .fold(0., f64::max);
    if !horizontal {
        width + size * 0.9
    } else if rotation == 0. {
        size * 2.29
    } else {
        width * rotation.to_radians().abs().sin() + size * 1.4
    }
}

pub(super) fn draw(
    svg: &mut String,
    hierarchy: &Hierarchy,
    coords: &Coords,
    axis_position: f64,
    leaf_extent: f64,
    size: f64,
    face: &str,
    stroke: &str,
    side: &str,
) {
    let horizontal = !coords.horizontal;
    let direction = if side == "l" || side == "t" { -1. } else { 1. };
    let count = coords.count.min(u32::MAX as f64) as u32;
    let edge = |index: u32| {
        let (x, y) = coords.point(f64::from(index) - 0.5, 0.);
        if horizontal {
            x.clamp(coords.rect.left, coords.rect.right)
        } else {
            y.clamp(coords.rect.top, coords.rect.bottom)
        }
    };
    let mut offset = leaf_extent;
    for (&level, labels) in hierarchy {
        let band = band_extent(labels, size, horizontal);
        let inner = axis_position + direction * offset;
        let outer = inner + direction * band;
        for (&start, label) in labels.range(..count) {
            let end = hierarchy
                .range(level..)
                .filter_map(|(_, entries)| {
                    entries
                        .range((std::ops::Bound::Excluded(start), std::ops::Bound::Unbounded))
                        .next()
                        .map(|(&i, _)| i)
                })
                .min()
                .unwrap_or(count)
                .min(count);
            if end <= start {
                continue;
            }
            let a = edge(start);
            let b = edge(end);
            let center = (a + b) * 0.5;
            if horizontal {
                line(svg, a, axis_position, a, outer, stroke);
                line(svg, b, axis_position, b, outer, stroke);
                text(
                    svg,
                    center,
                    (inner + outer) * 0.5 + size * 0.31,
                    "middle",
                    size,
                    face,
                    label,
                    false,
                );
            } else {
                line(svg, axis_position, a, outer, a, stroke);
                line(svg, axis_position, b, outer, b, stroke);
                text(
                    svg,
                    (inner + outer) * 0.5,
                    center + size * 0.31,
                    "middle",
                    size,
                    face,
                    label,
                    false,
                );
            }
        }
        offset += band;
    }
}