plotlib 0.3.0

Plotting data structures and tools
Documentation
use svg::Node;
use svg::node;

use histogram;
use scatter;
use function;
use axis;
use style;
use utils::PairWise;

fn value_to_face_offset(value: f64, axis: &axis::Axis, face_size: f64) -> f64 {
    let range = axis.max() - axis.min();
    (face_size * (value - axis.min())) / range
}

pub fn draw_x_axis(a: &axis::Axis, face_width: f64) -> node::element::Group {
    let axis_line = node::element::Line::new()
        .set("x1", 0)
        .set("y1", 0)
        .set("x2", face_width)
        .set("y2", 0)
        .set("stroke", "black")
        .set("stroke-width", 1);

    let mut ticks = node::element::Group::new();
    let mut labels = node::element::Group::new();

    for &tick in a.ticks().iter() {
        let tick_pos = value_to_face_offset(tick, a, face_width);
        let tick_mark = node::element::Line::new()
            .set("x1", tick_pos)
            .set("y1", 0)
            .set("x2", tick_pos)
            .set("y2", 10)
            .set("stroke", "black")
            .set("stroke-width", 1);
        ticks.append(tick_mark);

        let tick_label = node::element::Text::new()
            .set("x", tick_pos)
            .set("y", 20)
            .set("text-anchor", "middle")
            .set("font-size", 12)
            .add(node::Text::new(tick.to_string()));
        labels.append(tick_label);
    }

    let label = node::element::Text::new()
        .set("x", face_width / 2.)
        .set("y", 30)
        .set("text-anchor", "middle")
        .set("font-size", 12)
        .add(node::Text::new(a.get_label()));

    node::element::Group::new()
        .add(ticks)
        .add(axis_line)
        .add(labels)
        .add(label)
}

pub fn draw_y_axis(a: &axis::Axis, face_height: f64) -> node::element::Group {
    let axis_line = node::element::Line::new()
        .set("x1", 0)
        .set("y1", 0)
        .set("x2", 0)
        .set("y2", -face_height)
        .set("stroke", "black")
        .set("stroke-0", 1);

    let mut ticks = node::element::Group::new();
    let mut labels = node::element::Group::new();

    for &tick in a.ticks().iter() {
        let tick_pos = value_to_face_offset(tick, a, face_height);
        let tick_mark = node::element::Line::new()
            .set("x1", 0)
            .set("y1", -tick_pos)
            .set("x2", -10)
            .set("y2", -tick_pos)
            .set("stroke", "black")
            .set("stroke-width", 1);
        ticks.append(tick_mark);

        let tick_label = node::element::Text::new()
            .set("x", -15)
            .set("y", -tick_pos)
            .set("text-anchor", "end")
            .set("dominant-baseline", "middle")
            .set("font-size", 12)
            .add(node::Text::new(tick.to_string()));
        labels.append(tick_label);
    }

    let label = node::element::Text::new()
        .set("x", -30)
        .set("y", -(face_height / 2.))
        .set("text-anchor", "middle")
        .set("font-size", 12)
        .set(
            "transform",
            format!("rotate(-90 {} {})", -30, -(face_height / 2.)),
        )
        .add(node::Text::new(a.get_label()));

    node::element::Group::new()
        .add(ticks)
        .add(axis_line)
        .add(labels)
        .add(label)
}

pub fn draw_face_points<S>(
    s: &scatter::Scatter,
    x_axis: &axis::Axis,
    y_axis: &axis::Axis,
    face_width: f64,
    face_height: f64,
    style: &S,
) -> node::element::Group
where
    S: style::Point,
{
    let mut group = node::element::Group::new();

    for &(x, y) in &s.data {
        let x_pos = value_to_face_offset(x, x_axis, face_width);
        let y_pos = -value_to_face_offset(y, y_axis, face_height);
        let radius = style.get_size().clone().unwrap_or(5.) as f64;
        match style.get_marker().clone().unwrap_or(style::Marker::Circle) {
            style::Marker::Circle => {
                group.append(
                    node::element::Circle::new()
                        .set("cx", x_pos)
                        .set("cy", y_pos)
                        .set("r", radius)
                        .set(
                            "fill",
                            style.get_colour().clone().unwrap_or_else(|| "".into()),
                        ),
                );
            }
            style::Marker::Square => {
                group.append(
                    node::element::Rectangle::new()
                        .set("x", x_pos - radius)
                        .set("y", y_pos - radius)
                        .set("width", 2. * radius)
                        .set("height", 2. * radius)
                        .set(
                            "fill",
                            style.get_colour().clone().unwrap_or_else(|| "".into()),
                        ),
                );
            }
            style::Marker::Cross => {
                let path = node::element::path::Data::new()
                    .move_to((x_pos - radius, y_pos - radius))
                    .line_by((radius * 2., radius * 2.))
                    .move_by((-radius * 2., 0))
                    .line_by((radius * 2., -radius * 2.))
                    .close();
                group.append(
                    node::element::Path::new()
                        .set("fill", "none")
                        .set(
                            "stroke",
                            style.get_colour().clone().unwrap_or_else(|| "".into()),
                        )
                        .set("stroke-width", 2)
                        .set("d", path),
                );
            }
        };
    }

    group
}

pub fn draw_face_bars<S>(
    h: &histogram::Histogram,
    x_axis: &axis::Axis,
    y_axis: &axis::Axis,
    face_width: f64,
    face_height: f64,
    style: &S,
) -> node::element::Group
where
    S: style::Bar,
{
    let mut group = node::element::Group::new();

    for ((&l, &u), &count) in h.bin_bounds.pairwise().zip(h.bin_counts.iter()) {
        let l_pos = value_to_face_offset(l, x_axis, face_width);
        let u_pos = value_to_face_offset(u, x_axis, face_width);
        let width = u_pos - l_pos;
        let count_scaled = value_to_face_offset(count as f64, y_axis, face_height);
        let rect = node::element::Rectangle::new()
            .set("x", l_pos)
            .set("y", -count_scaled)
            .set("width", width)
            .set("height", count_scaled)
            .set(
                "fill",
                style
                    .get_fill()
                    .clone()
                    .unwrap_or_else(|| "burlywood".into()),
            )
            .set("stroke", "black");
        group.append(rect);
    }

    group
}

pub fn draw_face_line<S>(
    s: &function::Function,
    x_axis: &axis::Axis,
    y_axis: &axis::Axis,
    face_width: f64,
    face_height: f64,
    style: &S,
) -> node::element::Group
where
    S: style::Line,
{
    let mut group = node::element::Group::new();

    let mut d: Vec<node::element::path::Command> = vec![];
    let &(first_x, first_y) = s.data.first().unwrap();
    let first_x_pos = value_to_face_offset(first_x, x_axis, face_width);
    let first_y_pos = -value_to_face_offset(first_y, y_axis, face_height);
    d.push(node::element::path::Command::Move(
        node::element::path::Position::Absolute,
        (first_x_pos, first_y_pos).into(),
    ));
    for &(x, y) in &s.data {
        let x_pos = value_to_face_offset(x, x_axis, face_width);
        let y_pos = -value_to_face_offset(y, y_axis, face_height);
        d.push(node::element::path::Command::Line(
            node::element::path::Position::Absolute,
            (x_pos, y_pos).into(),
        ));
    }

    let path = node::element::path::Data::from(d);

    group.append(
        node::element::Path::new()
            .set("fill", "none")
            .set(
                "stroke",
                style.get_colour().clone().unwrap_or_else(|| "".into()),
            )
            .set("stroke-width", style.get_width().clone().unwrap_or(2.))
            .set("d", path),
    );

    group
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_value_to_face_offset() {
        let axis = axis::Axis::new(-2., 5.);
        assert_eq!(value_to_face_offset(-2.0, &axis, 14.0), 0.0);
        assert_eq!(value_to_face_offset(5.0, &axis, 14.0), 14.0);
        assert_eq!(value_to_face_offset(0.0, &axis, 14.0), 4.0);
        assert_eq!(value_to_face_offset(-4.0, &axis, 14.0), -4.0);
        assert_eq!(value_to_face_offset(7.0, &axis, 14.0), 18.0);
    }
}