use crate::TextCanvas;
#[inline]
fn min_of(arr: &[f64]) -> Option<f64> {
arr.iter().copied().min_by(f64::total_cmp)
}
#[inline]
fn max_of(arr: &[f64]) -> Option<f64> {
arr.iter().copied().max_by(f64::total_cmp)
}
#[derive(Copy, Clone, Eq, PartialEq)]
enum PlotType {
Line,
Scatter,
Bars,
}
pub struct Plot;
impl Plot {
pub fn stroke_xy_axes(canvas: &mut TextCanvas, x: &[f64], y: &[f64]) {
Self::stroke_x_axis(canvas, y);
Self::stroke_y_axis(canvas, x);
}
pub fn stroke_x_axis(canvas: &mut TextCanvas, y: &[f64]) {
Self::stroke_line_at_y(canvas, 0.0, y);
}
pub fn stroke_y_axis(canvas: &mut TextCanvas, x: &[f64]) {
Self::stroke_line_at_x(canvas, 0.0, x);
}
pub fn stroke_line_at_x(canvas: &mut TextCanvas, value: f64, x: &[f64]) {
let Some(x) = Self::compute_screen_x(canvas, value, x) else {
return;
};
canvas.stroke_line(x, 0, x, canvas.h());
}
pub fn stroke_line_at_y(canvas: &mut TextCanvas, value: f64, y: &[f64]) {
let Some(y) = Self::compute_screen_y(canvas, value, y) else {
return;
};
canvas.stroke_line(0, y, canvas.w(), y);
}
#[allow(clippy::cast_possible_truncation, clippy::missing_panics_doc)]
#[must_use]
pub fn compute_screen_x(canvas: &TextCanvas, value: f64, x: &[f64]) -> Option<i32> {
if x.is_empty() {
return None;
}
let min_x = min_of(x).expect("cannot be empty");
let max_x = max_of(x).expect("cannot be empty");
let range_x = max_x - min_x;
let scale_x = canvas.fw() / range_x;
if scale_x.is_infinite() {
return Some(canvas.cx());
}
let x = ((value - min_x) * scale_x).trunc() as i32;
Some(x)
}
#[allow(clippy::cast_possible_truncation, clippy::missing_panics_doc)]
#[must_use]
pub fn compute_screen_y(canvas: &TextCanvas, value: f64, y: &[f64]) -> Option<i32> {
if y.is_empty() {
return None;
}
let min_y = min_of(y).expect("cannot be empty");
let max_y = max_of(y).expect("cannot be empty");
let range_y = max_y - min_y;
let scale_y = canvas.fh() / range_y;
if scale_y.is_infinite() {
return Some(canvas.cy());
}
let mut y = ((value - min_y) * scale_y).trunc() as i32;
y = canvas.h() - y;
Some(y)
}
pub fn stroke_xy_axes_of_function(
canvas: &mut TextCanvas,
from_x: f64,
to_x: f64,
f: &impl Fn(f64) -> f64,
) {
let nb_values = canvas.screen.fwidth();
let (x, y) = Self::compute_function(from_x, to_x, nb_values, f);
Self::stroke_x_axis(canvas, &y);
Self::stroke_y_axis(canvas, &x);
}
pub fn stroke_x_axis_of_function(
canvas: &mut TextCanvas,
from_x: f64,
to_x: f64,
f: &impl Fn(f64) -> f64,
) {
Self::stroke_line_at_y_of_function(canvas, 0.0, from_x, to_x, f);
}
pub fn stroke_y_axis_of_function(
canvas: &mut TextCanvas,
from_x: f64,
to_x: f64,
f: &impl Fn(f64) -> f64,
) {
Self::stroke_line_at_x_of_function(canvas, 0.0, from_x, to_x, f);
}
pub fn stroke_line_at_x_of_function(
canvas: &mut TextCanvas,
value: f64,
from_x: f64,
to_x: f64,
f: &impl Fn(f64) -> f64,
) {
let nb_values = canvas.screen.fwidth();
let (x, _) = Self::compute_function(from_x, to_x, nb_values, f);
Self::stroke_line_at_x(canvas, value, &x);
}
pub fn stroke_line_at_y_of_function(
canvas: &mut TextCanvas,
value: f64,
from_x: f64,
to_x: f64,
f: &impl Fn(f64) -> f64,
) {
let nb_values = canvas.screen.fwidth();
let (_, y) = Self::compute_function(from_x, to_x, nb_values, f);
Self::stroke_line_at_y(canvas, value, &y);
}
pub fn compute_screen_x_of_function(
canvas: &TextCanvas,
value: f64,
from_x: f64,
to_x: f64,
f: &impl Fn(f64) -> f64,
) -> Option<i32> {
let nb_values = canvas.screen.fwidth();
let (x, _) = Self::compute_function(from_x, to_x, nb_values, f);
Self::compute_screen_x(canvas, value, &x)
}
pub fn compute_screen_y_of_function(
canvas: &TextCanvas,
value: f64,
from_x: f64,
to_x: f64,
f: &impl Fn(f64) -> f64,
) -> Option<i32> {
let nb_values = canvas.screen.fwidth();
let (_, y) = Self::compute_function(from_x, to_x, nb_values, f);
Self::compute_screen_y(canvas, value, &y)
}
pub fn line(canvas: &mut TextCanvas, x: &[f64], y: &[f64]) {
Self::plot(canvas, x, y, PlotType::Line);
}
pub fn scatter(canvas: &mut TextCanvas, x: &[f64], y: &[f64]) {
Self::plot(canvas, x, y, PlotType::Scatter);
}
pub fn bars(canvas: &mut TextCanvas, x: &[f64], y: &[f64]) {
Self::plot(canvas, x, y, PlotType::Bars);
}
#[allow(clippy::cast_possible_truncation)]
fn plot(canvas: &mut TextCanvas, x: &[f64], y: &[f64], plot_type: PlotType) {
if x.is_empty() || y.is_empty() {
return;
}
let mut points: Vec<(f64, f64)> = x.iter().copied().zip(y.iter().copied()).collect();
if plot_type == PlotType::Line {
points.sort_by(|a, b| f64::total_cmp(&a.0, &b.0));
}
let min_x = min_of(x).expect("cannot be empty");
let max_x = max_of(x).expect("cannot be empty");
let range_x = max_x - min_x;
let scale_x = canvas.fw() / range_x;
let min_y = min_of(y).expect("cannot be empty");
let max_y = max_of(y).expect("cannot be empty");
let range_y = max_y - min_y;
let scale_y = canvas.fh() / range_y;
if scale_x.is_infinite() || scale_y.is_infinite() {
return Self::handle_axes_without_range(
canvas,
x,
y,
plot_type,
scale_x.is_infinite(),
scale_y.is_infinite(),
);
}
let mut previous: Option<(i32, i32)> = None; for (x, y) in points {
let mut x = x;
x = (x - min_x) * scale_x;
let x = x.trunc() as i32;
let mut y = y;
y = (y - min_y) * scale_y;
y = canvas.fh() - y; let y = y.trunc() as i32;
match plot_type {
PlotType::Line => {
let pair = (x, y);
if let Some(previous) = previous {
canvas.stroke_line(previous.0, previous.1, pair.0, pair.1);
}
previous = Some(pair);
}
PlotType::Scatter => {
canvas.set_pixel(x, y, true);
}
PlotType::Bars => {
canvas.stroke_line(x, y, x, canvas.h());
}
}
}
}
fn handle_axes_without_range(
canvas: &mut TextCanvas,
x: &[f64],
y: &[f64],
plot_type: PlotType,
x_has_no_range: bool,
y_has_no_range: bool,
) {
let x_has_range_but_not_y = !x_has_no_range && y_has_no_range;
let y_has_range_but_not_x = x_has_no_range && !y_has_no_range;
let both_have_no_range = x_has_no_range && y_has_no_range;
if x_has_range_but_not_y {
Self::draw_horizontally_centered_line(canvas, x, plot_type);
} else if y_has_range_but_not_x {
Self::draw_vertically_centered_line(canvas, y, plot_type);
} else if both_have_no_range {
canvas.set_pixel(canvas.cx(), canvas.cy(), true);
if plot_type == PlotType::Bars {
canvas.stroke_line(canvas.cx(), canvas.cy(), canvas.cx(), canvas.h());
}
}
}
fn draw_horizontally_centered_line(canvas: &mut TextCanvas, x: &[f64], plot_type: PlotType) {
match plot_type {
PlotType::Line => {
canvas.stroke_line(0, canvas.cy(), canvas.w(), canvas.cy());
}
PlotType::Scatter => {
for &x_val in x {
if let Some(x) = Self::compute_screen_x(canvas, x_val, x) {
canvas.set_pixel(x, canvas.cy(), true);
}
}
}
PlotType::Bars => {
for &x_val in x {
if let Some(x) = Self::compute_screen_x(canvas, x_val, x) {
canvas.stroke_line(x, canvas.cy(), x, canvas.h());
}
}
}
}
}
fn draw_vertically_centered_line(canvas: &mut TextCanvas, y: &[f64], plot_type: PlotType) {
match plot_type {
PlotType::Line | PlotType::Bars => {
canvas.stroke_line(canvas.cx(), 0, canvas.cx(), canvas.h());
}
PlotType::Scatter => {
for &y_val in y {
if let Some(y) = Self::compute_screen_y(canvas, y_val, y) {
canvas.set_pixel(canvas.cx(), y, true);
}
}
}
}
}
pub fn function(canvas: &mut TextCanvas, from_x: f64, to_x: f64, f: &impl Fn(f64) -> f64) {
let nb_values = canvas.screen.fwidth();
let (x, y) = Self::compute_function(from_x, to_x, nb_values, f);
Self::line(canvas, &x, &y);
}
pub fn function_filled(
canvas: &mut TextCanvas,
from_x: f64,
to_x: f64,
f: &impl Fn(f64) -> f64,
) {
let nb_values = canvas.screen.fwidth();
let nb_values = nb_values * 1.07;
let (x, y) = Self::compute_function(from_x, to_x, nb_values, f);
Self::bars(canvas, &x, &y);
}
pub fn compute_function<T>(
from_x: f64,
to_x: f64,
nb_values: f64,
f: &impl Fn(f64) -> T,
) -> (Vec<f64>, Vec<T>) {
let range = to_x - from_x;
let step = range / (nb_values - 1.0);
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
let nb_values = nb_values.ceil() as usize;
let mut px: Vec<f64> = Vec::with_capacity(nb_values);
let mut py: Vec<T> = Vec::with_capacity(nb_values);
let mut x = from_x;
for _ in 0..(nb_values - 1) {
px.push(x);
py.push(f(x));
x += step;
}
px.push(to_x);
py.push(f(to_x));
debug_assert_eq!(px.len(), nb_values);
debug_assert_eq!(px.len(), py.len());
(px, py)
}
}
pub struct Chart;
impl Chart {
const MARGIN_TOP: i32 = 1;
const MARGIN_RIGHT: i32 = 2;
const MARGIN_BOTTOM: i32 = 2;
const MARGIN_LEFT: i32 = 10;
const HORIZONTAL_MARGIN: i32 = Self::MARGIN_LEFT + Self::MARGIN_RIGHT;
const VERTICAL_MARGIN: i32 = Self::MARGIN_TOP + Self::MARGIN_BOTTOM;
pub fn line(canvas: &mut TextCanvas, x: &[f64], y: &[f64]) {
Self::chart(canvas, x, y, PlotType::Line);
}
pub fn scatter(canvas: &mut TextCanvas, x: &[f64], y: &[f64]) {
Self::chart(canvas, x, y, PlotType::Scatter);
}
pub fn bars(canvas: &mut TextCanvas, x: &[f64], y: &[f64]) {
Self::chart(canvas, x, y, PlotType::Bars);
}
fn chart(canvas: &mut TextCanvas, x: &[f64], y: &[f64], plot_type: PlotType) {
if x.is_empty() || y.is_empty() {
return;
}
Self::check_canvas_size(canvas);
Self::plot_values(canvas, x, y, plot_type);
Self::stroke_plot_border(canvas);
Self::draw_min_and_max_values(canvas, x, y);
}
fn check_canvas_size(canvas: &TextCanvas) {
let width = canvas.output.width();
let height = canvas.output.height();
let min_width = Self::HORIZONTAL_MARGIN + 1;
let min_height = Self::VERTICAL_MARGIN + 1;
assert!(
width >= min_width && height >= min_height,
"Canvas size is {width}×{height}, but must be at least {min_width}×{min_height} to accommodate for plot."
);
}
fn plot_values(canvas: &mut TextCanvas, x: &[f64], y: &[f64], plot_type: PlotType) {
let width = canvas.output.width() - Self::HORIZONTAL_MARGIN;
let height = canvas.output.height() - Self::VERTICAL_MARGIN;
let mut plot = TextCanvas::new(width, height);
match plot_type {
PlotType::Line => {
Plot::line(&mut plot, x, y);
}
PlotType::Scatter => {
Plot::scatter(&mut plot, x, y);
}
PlotType::Bars => {
Plot::bars(&mut plot, x, y);
}
}
canvas.draw_canvas(&plot, Self::MARGIN_LEFT * 2, Self::MARGIN_TOP * 4);
}
fn stroke_plot_border(canvas: &mut TextCanvas) {
let top = (Self::MARGIN_TOP - 1) * 4 + 2;
let right = canvas.w() - (Self::MARGIN_RIGHT - 1) * 2;
let bottom = canvas.h() - ((Self::MARGIN_BOTTOM - 1) * 4 + 2);
let left = (Self::MARGIN_LEFT - 1) * 2;
canvas.stroke_line(left, top, right, top);
canvas.stroke_line(right, top, right, bottom);
canvas.stroke_line(right, bottom, left, bottom);
canvas.stroke_line(left, bottom, left, top);
}
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
fn draw_min_and_max_values(canvas: &mut TextCanvas, x: &[f64], y: &[f64]) {
let min_x = Self::format_number(min_of(x).expect("cannot be empty"));
let max_x = Self::format_number(max_of(x).expect("cannot be empty"));
let min_y = Self::format_number(min_of(y).expect("cannot be empty"));
let max_y = Self::format_number(max_of(y).expect("cannot be empty"));
canvas.draw_text(
&min_x,
Self::MARGIN_LEFT - (min_x.len() as i32),
canvas.output.height() - Self::MARGIN_TOP,
);
canvas.draw_text(
&max_x,
canvas.output.width() - Self::MARGIN_RIGHT + 2 - (max_x.len() as i32),
canvas.output.height() - Self::MARGIN_TOP,
);
canvas.draw_text(
&min_y,
Self::MARGIN_LEFT - 2 - (min_y.len() as i32),
canvas.output.height() - Self::MARGIN_TOP - 1,
);
canvas.draw_text(
&max_y,
Self::MARGIN_LEFT - 2 - (max_y.len() as i32),
Self::MARGIN_TOP - 1,
);
}
fn format_number(mut number: f64) -> String {
let mut precision = 1;
let mut suffix = "";
if number.abs() >= 1_000_000_000_000.0 {
number /= 1_000_000_000_000.0;
suffix = "T";
} else if number.abs() >= 1_000_000_000.0 {
number /= 1_000_000_000.0;
suffix = "B";
} else if number.abs() >= 1_000_000.0 {
number /= 1_000_000.0;
suffix = "M";
} else if number.abs() >= 10_000.0 {
number /= 1000.0;
suffix = "K";
} else if (number - number.round()).abs() < 0.001 {
precision = 0; if number.abs() < 0.000_1 {
number = 0.0; }
} else if number.abs() < 1.0 {
precision = 4; }
format!("{number:.precision$}{suffix}")
}
pub fn function(canvas: &mut TextCanvas, from_x: f64, to_x: f64, f: &impl Fn(f64) -> f64) {
let nb_values = f64::from((canvas.output.width() - Self::HORIZONTAL_MARGIN) * 2);
let (x, y) = Plot::compute_function(from_x, to_x, nb_values, f);
Self::line(canvas, &x, &y);
}
pub fn function_filled(
canvas: &mut TextCanvas,
from_x: f64,
to_x: f64,
f: &impl Fn(f64) -> f64,
) {
let nb_values = f64::from((canvas.output.width() - Self::HORIZONTAL_MARGIN) * 2);
let nb_values = nb_values * 1.07;
let (x, y) = Plot::compute_function(from_x, to_x, nb_values, f);
Self::bars(canvas, &x, &y);
}
}
pub struct Resampling;
impl Resampling {
#[must_use]
pub fn downsample_mean(x: &[f64], y: &[f64], max_nb_points: usize) -> (Vec<f64>, Vec<f64>) {
let points: Vec<(f64, f64)> = x.iter().copied().zip(y.iter().copied()).collect();
let points = Self::downsample_points_mean(&points, max_nb_points);
let (x, y) = points.into_iter().unzip();
(x, y)
}
#[allow(clippy::cast_precision_loss)]
#[must_use]
pub fn downsample_points_mean(points: &[(f64, f64)], max_nb_points: usize) -> Vec<(f64, f64)> {
assert!(
max_nb_points >= 2,
"minimum two points are required as output"
);
if points.len() <= max_nb_points {
return points.to_owned();
}
if max_nb_points - 2 == 0 {
return vec![points[0], points[points.len() - 1]];
}
let nb_points = (points.len() - 2) as f64;
let nb_buckets = (max_nb_points - 2) as f64;
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
let bucket_size = (nb_points / nb_buckets).ceil() as usize;
let mut downsampled_points = Vec::with_capacity(max_nb_points);
downsampled_points.push(points.first().copied().expect("min 2 points"));
for bucket in points[1..points.len() - 1].chunks(bucket_size) {
let mean_x = bucket.iter().fold(0.0, |acc, &(x, _)| acc + x) / (bucket.len() as f64);
let mean_y = bucket.iter().fold(0.0, |acc, &(_, y)| acc + y) / (bucket.len() as f64);
downsampled_points.push((mean_x, mean_y));
}
downsampled_points.push(points.last().copied().expect("min 2 points"));
downsampled_points
}
#[must_use]
pub fn downsample_min_max(x: &[f64], y: &[f64], max_nb_points: usize) -> (Vec<f64>, Vec<f64>) {
let points: Vec<(f64, f64)> = x.iter().copied().zip(y.iter().copied()).collect();
let points = Self::downsample_points_min_max(&points, max_nb_points);
let (x, y) = points.into_iter().unzip();
(x, y)
}
#[allow(clippy::cast_precision_loss)]
#[must_use]
pub fn downsample_points_min_max(
points: &[(f64, f64)],
max_nb_points: usize,
) -> Vec<(f64, f64)> {
assert!(
max_nb_points >= 2,
"minimum two points are required as output"
);
assert_eq!(max_nb_points % 2, 0, "number of output points must be even");
if points.len() <= max_nb_points {
return points.to_owned();
}
if max_nb_points - 2 == 0 {
return vec![points[0], points[points.len() - 1]];
}
let nb_points = (points.len() - 2) as f64;
let nb_buckets = (max_nb_points - 2) as f64 / 2.0;
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
let bucket_size = (nb_points / nb_buckets).ceil() as usize;
let mut downsampled_points = Vec::with_capacity(max_nb_points);
downsampled_points.push(points.first().copied().expect("min 2 points"));
for bucket in points[1..points.len() - 1].chunks(bucket_size) {
let mut bucket = bucket.iter();
let &first_point = bucket.next().expect("bucket is non-empty");
let (mut min, mut max) = (first_point, first_point);
for &point in bucket {
if point.1 < min.1 {
min = point;
}
if point.1 > max.1 {
max = point;
}
}
if min.0 <= max.0 {
downsampled_points.extend([min, max]);
} else {
#[allow(clippy::tuple_array_conversions)]
downsampled_points.extend([max, min]);
}
}
downsampled_points.push(points.last().copied().expect("min 2 points"));
downsampled_points
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn stroke_x_and_y_axes() {
let mut canvas = TextCanvas::new(15, 5);
let x: Vec<f64> = (-5..=5).map(f64::from).collect();
let y: Vec<f64> = (-5..=5).map(f64::from).collect();
Plot::stroke_xy_axes(&mut canvas, &x, &y);
assert_eq!(
canvas.to_string(),
"\
⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀
⠤⠤⠤⠤⠤⠤⠤⡧⠤⠤⠤⠤⠤⠤⠤
⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀
"
);
}
#[test]
fn stroke_x_axis_at_top_boundary() {
let mut canvas = TextCanvas::new(15, 5);
let y: Vec<f64> = (-5..=0).map(f64::from).collect();
Plot::stroke_x_axis(&mut canvas, &y);
assert_eq!(
canvas.to_string(),
"\
⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
"
);
}
#[test]
fn stroke_x_axis_at_bottom_boundary() {
let mut canvas = TextCanvas::new(15, 5);
let y: Vec<f64> = (0..=5).map(f64::from).collect();
Plot::stroke_x_axis(&mut canvas, &y);
assert_eq!(
canvas.to_string(),
"\
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀
"
);
}
#[test]
fn stroke_y_axis_at_left_boundary() {
let mut canvas = TextCanvas::new(15, 5);
let x: Vec<f64> = (0..=5).map(f64::from).collect();
Plot::stroke_y_axis(&mut canvas, &x);
assert_eq!(
canvas.to_string(),
"\
⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
"
);
}
#[test]
fn stroke_y_axis_at_right_boundary() {
let mut canvas = TextCanvas::new(15, 5);
let x: Vec<f64> = (-5..=0).map(f64::from).collect();
Plot::stroke_y_axis(&mut canvas, &x);
assert_eq!(
canvas.to_string(),
"\
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸
"
);
}
#[test]
fn stroke_line_at_x() {
let mut canvas = TextCanvas::new(15, 5);
let x: Vec<f64> = (-5..=5).map(f64::from).collect();
Plot::stroke_line_at_x(&mut canvas, -5.0, &x);
Plot::stroke_line_at_x(&mut canvas, -2.5, &x);
Plot::stroke_line_at_x(&mut canvas, 0.0, &x);
Plot::stroke_line_at_x(&mut canvas, 2.5, &x);
Plot::stroke_line_at_x(&mut canvas, 5.0, &x);
assert_eq!(
canvas.to_string(),
"\
⡇⠀⠀⢸⠀⠀⠀⡇⠀⠀⢸⠀⠀⠀⢸
⡇⠀⠀⢸⠀⠀⠀⡇⠀⠀⢸⠀⠀⠀⢸
⡇⠀⠀⢸⠀⠀⠀⡇⠀⠀⢸⠀⠀⠀⢸
⡇⠀⠀⢸⠀⠀⠀⡇⠀⠀⢸⠀⠀⠀⢸
⡇⠀⠀⢸⠀⠀⠀⡇⠀⠀⢸⠀⠀⠀⢸
"
);
}
#[test]
fn stroke_line_at_x_ignore_empty_values() {
let mut canvas = TextCanvas::new(15, 5);
let x: Vec<f64> = vec![];
Plot::stroke_line_at_x(&mut canvas, 0.0, &x);
assert_eq!(
canvas.to_string(),
"\
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
"
);
}
#[test]
fn stroke_line_at_y() {
let mut canvas = TextCanvas::new(15, 5);
let y: Vec<f64> = (-5..=5).map(f64::from).collect();
Plot::stroke_line_at_y(&mut canvas, -5.0, &y);
Plot::stroke_line_at_y(&mut canvas, -2.5, &y);
Plot::stroke_line_at_y(&mut canvas, 0.0, &y);
Plot::stroke_line_at_y(&mut canvas, 2.5, &y);
Plot::stroke_line_at_y(&mut canvas, 5.0, &y);
assert_eq!(
canvas.to_string(),
"\
⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉
⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒
⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤
⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀
⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀
"
);
}
#[test]
fn stroke_line_at_y_ignore_empty_values() {
let mut canvas = TextCanvas::new(15, 5);
let y: Vec<f64> = vec![];
Plot::stroke_line_at_y(&mut canvas, 0.0, &y);
assert_eq!(
canvas.to_string(),
"\
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
"
);
}
#[test]
fn compute_screen_x() {
let canvas = TextCanvas::new(15, 5);
let x: Vec<f64> = (-10..=10).map(f64::from).collect();
assert_eq!(0, Plot::compute_screen_x(&canvas, -10.0, &x).unwrap());
assert_eq!(29, Plot::compute_screen_x(&canvas, 10.0, &x).unwrap());
assert_eq!(14, Plot::compute_screen_x(&canvas, 0.0, &x).unwrap());
}
#[test]
fn compute_screen_x_input_size_1() {
let canvas = TextCanvas::new(15, 5);
let x: Vec<f64> = vec![3.0];
assert_eq!(15, Plot::compute_screen_x(&canvas, 0.0, &x).unwrap());
}
#[test]
fn compute_screen_x_empty_input() {
let canvas = TextCanvas::new(15, 5);
let x: Vec<f64> = vec![];
assert!(Plot::compute_screen_x(&canvas, 0.0, &x).is_none());
}
#[test]
fn compute_screen_y() {
let canvas = TextCanvas::new(15, 5);
let y: Vec<f64> = (-10..=10).map(f64::from).collect();
assert_eq!(19, Plot::compute_screen_y(&canvas, -10.0, &y).unwrap());
assert_eq!(0, Plot::compute_screen_y(&canvas, 10.0, &y).unwrap());
assert_eq!(10, Plot::compute_screen_y(&canvas, 0.0, &y).unwrap());
}
#[test]
fn compute_screen_y_input_size_1() {
let canvas = TextCanvas::new(15, 5);
let y: Vec<f64> = vec![3.0];
assert_eq!(10, Plot::compute_screen_y(&canvas, 0.0, &y).unwrap());
}
#[test]
fn compute_screen_y_empty_input() {
let canvas = TextCanvas::new(15, 5);
let y: Vec<f64> = vec![];
assert!(Plot::compute_screen_y(&canvas, 0.0, &y).is_none());
}
#[test]
fn stroke_x_and_y_axes_of_function() {
let mut canvas = TextCanvas::new(15, 5);
let f = |x| x;
Plot::stroke_xy_axes_of_function(&mut canvas, -5.0, 5.0, &f);
assert_eq!(
canvas.to_string(),
"\
⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀
⠤⠤⠤⠤⠤⠤⠤⡧⠤⠤⠤⠤⠤⠤⠤
⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀
"
);
}
#[test]
fn stroke_x_axis_of_function_at_top_boundary() {
let mut canvas = TextCanvas::new(15, 5);
let f = |x| x;
Plot::stroke_x_axis_of_function(&mut canvas, -5.0, 0.0, &f);
assert_eq!(
canvas.to_string(),
"\
⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
"
);
}
#[test]
fn stroke_x_axis_of_function_at_bottom_boundary() {
let mut canvas = TextCanvas::new(15, 5);
let f = |x| x;
Plot::stroke_x_axis_of_function(&mut canvas, 0.0, 5.0, &f);
assert_eq!(
canvas.to_string(),
"\
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀
"
);
}
#[test]
fn stroke_y_axis_of_function_at_left_boundary() {
let mut canvas = TextCanvas::new(15, 5);
let f = |x| x;
Plot::stroke_y_axis_of_function(&mut canvas, 0.0, 5.0, &f);
assert_eq!(
canvas.to_string(),
"\
⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
"
);
}
#[test]
fn stroke_y_axis_of_function_at_right_boundary() {
let mut canvas = TextCanvas::new(15, 5);
let f = |x| x;
Plot::stroke_y_axis_of_function(&mut canvas, -5.0, 0.0, &f);
assert_eq!(
canvas.to_string(),
"\
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸
"
);
}
#[test]
fn stroke_line_at_x_of_function() {
let mut canvas = TextCanvas::new(15, 5);
let f = |x| x;
Plot::stroke_line_at_x_of_function(&mut canvas, -5.0, -5.0, 5.0, &f);
Plot::stroke_line_at_x_of_function(&mut canvas, -2.5, -5.0, 5.0, &f);
Plot::stroke_line_at_x_of_function(&mut canvas, 0.0, -5.0, 5.0, &f);
Plot::stroke_line_at_x_of_function(&mut canvas, 2.5, -5.0, 5.0, &f);
Plot::stroke_line_at_x_of_function(&mut canvas, 5.0, -5.0, 5.0, &f);
assert_eq!(
canvas.to_string(),
"\
⡇⠀⠀⢸⠀⠀⠀⡇⠀⠀⢸⠀⠀⠀⢸
⡇⠀⠀⢸⠀⠀⠀⡇⠀⠀⢸⠀⠀⠀⢸
⡇⠀⠀⢸⠀⠀⠀⡇⠀⠀⢸⠀⠀⠀⢸
⡇⠀⠀⢸⠀⠀⠀⡇⠀⠀⢸⠀⠀⠀⢸
⡇⠀⠀⢸⠀⠀⠀⡇⠀⠀⢸⠀⠀⠀⢸
"
);
}
#[test]
fn stroke_line_at_x_of_function_value_out_of_bounds() {
let mut canvas = TextCanvas::new(15, 5);
let f = |x| x;
Plot::stroke_line_at_x_of_function(&mut canvas, -100.0, -5.0, 5.0, &f);
assert_eq!(
canvas.to_string(),
"\
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
"
);
}
#[test]
fn stroke_line_at_y_of_function() {
let mut canvas = TextCanvas::new(15, 5);
let f = |x| x;
Plot::stroke_line_at_y_of_function(&mut canvas, -5.0, -5.0, 5.0, &f);
Plot::stroke_line_at_y_of_function(&mut canvas, -2.5, -5.0, 5.0, &f);
Plot::stroke_line_at_y_of_function(&mut canvas, 0.0, -5.0, 5.0, &f);
Plot::stroke_line_at_y_of_function(&mut canvas, 2.5, -5.0, 5.0, &f);
Plot::stroke_line_at_y_of_function(&mut canvas, 5.0, -5.0, 5.0, &f);
assert_eq!(
canvas.to_string(),
"\
⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉
⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒
⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤
⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀
⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀
"
);
}
#[test]
fn stroke_line_at_y_of_function_value_out_of_bounds() {
let mut canvas = TextCanvas::new(15, 5);
let f = |x| x;
Plot::stroke_line_at_y_of_function(&mut canvas, -100.0, -5.0, 5.0, &f);
assert_eq!(
canvas.to_string(),
"\
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
"
);
}
#[test]
fn compute_screen_x_of_function() {
let canvas = TextCanvas::new(15, 5);
let f = |x| x;
assert_eq!(
0,
Plot::compute_screen_x_of_function(&canvas, -10.0, -10.0, 10.0, &f).unwrap()
);
assert_eq!(
14,
Plot::compute_screen_x_of_function(&canvas, 0.0, -10.0, 10.0, &f).unwrap()
);
assert_eq!(
29,
Plot::compute_screen_x_of_function(&canvas, 10.0, -10.0, 10.0, &f).unwrap()
);
}
#[test]
fn compute_screen_x_of_function_range_0() {
let canvas = TextCanvas::new(15, 5);
let f = |x| x;
assert_eq!(
15,
Plot::compute_screen_x_of_function(&canvas, -10.0, 0.0, 0.0, &f).unwrap()
);
assert_eq!(
15,
Plot::compute_screen_x_of_function(&canvas, 0.0, 0.0, 0.0, &f).unwrap()
);
assert_eq!(
15,
Plot::compute_screen_x_of_function(&canvas, 10.0, 0.0, 0.0, &f).unwrap()
);
}
#[test]
fn compute_screen_x_of_function_canvas_size_1x1() {
let canvas = TextCanvas::new(1, 1);
let f = |x| x;
assert_eq!(
0,
Plot::compute_screen_x_of_function(&canvas, -10.0, -10.0, 10.0, &f).unwrap()
);
assert_eq!(
0,
Plot::compute_screen_x_of_function(&canvas, 0.0, -10.0, 10.0, &f).unwrap()
);
assert_eq!(
1,
Plot::compute_screen_x_of_function(&canvas, 10.0, -10.0, 10.0, &f).unwrap()
);
}
#[test]
fn compute_screen_y_of_function() {
let canvas = TextCanvas::new(15, 5);
let f = |x| x;
assert_eq!(
19,
Plot::compute_screen_y_of_function(&canvas, -10.0, -10.0, 10.0, &f).unwrap()
);
assert_eq!(
10,
Plot::compute_screen_y_of_function(&canvas, 0.0, -10.0, 10.0, &f).unwrap()
);
assert_eq!(
0,
Plot::compute_screen_y_of_function(&canvas, 10.0, -10.0, 10.0, &f).unwrap()
);
}
#[test]
fn compute_screen_y_of_function_range_0() {
let canvas = TextCanvas::new(15, 5);
let f = |x| x;
assert_eq!(
10,
Plot::compute_screen_y_of_function(&canvas, -10.0, 0.0, 0.0, &f).unwrap()
);
assert_eq!(
10,
Plot::compute_screen_y_of_function(&canvas, 0.0, 0.0, 0.0, &f).unwrap()
);
assert_eq!(
10,
Plot::compute_screen_y_of_function(&canvas, 10.0, 0.0, 0.0, &f).unwrap()
);
}
#[test]
fn compute_screen_y_of_function_canvas_size_1x1() {
let canvas = TextCanvas::new(1, 1);
let f = |x| x;
assert_eq!(
3,
Plot::compute_screen_y_of_function(&canvas, -10.0, -10.0, 10.0, &f).unwrap()
);
assert_eq!(
2,
Plot::compute_screen_y_of_function(&canvas, 0.0, -10.0, 10.0, &f).unwrap()
);
assert_eq!(
0,
Plot::compute_screen_y_of_function(&canvas, 10.0, -10.0, 10.0, &f).unwrap()
);
}
#[test]
fn plot_line() {
let mut canvas = TextCanvas::new(15, 5);
let x: Vec<f64> = (-5..=5).map(f64::from).collect();
let y: Vec<f64> = (-5..=5).map(f64::from).collect();
Plot::stroke_xy_axes(&mut canvas, &x, &y);
Plot::line(&mut canvas, &x, &y);
assert_eq!(
canvas.to_string(),
"\
⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⢀⠤⠒⠉
⠀⠀⠀⠀⠀⠀⠀⡇⢀⠤⠊⠁⠀⠀⠀
⠤⠤⠤⠤⠤⢤⠤⡯⠥⠤⠤⠤⠤⠤⠤
⠀⠀⢀⠤⠊⠁⠀⡇⠀⠀⠀⠀⠀⠀⠀
⡠⠊⠁⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀
"
);
}
#[test]
fn plot_line_with_empty_x() {
let mut canvas = TextCanvas::new(15, 5);
let x: Vec<f64> = vec![];
let y: Vec<f64> = (-5..=5).map(f64::from).collect();
Plot::stroke_xy_axes(&mut canvas, &x, &y);
Plot::line(&mut canvas, &x, &y);
assert_eq!(
canvas.to_string(),
"\
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
"
);
}
#[test]
fn plot_line_with_empty_y() {
let mut canvas = TextCanvas::new(15, 5);
let x: Vec<f64> = (-5..=5).map(f64::from).collect();
let y: Vec<f64> = vec![];
Plot::stroke_xy_axes(&mut canvas, &x, &y);
Plot::line(&mut canvas, &x, &y);
assert_eq!(
canvas.to_string(),
"\
⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀
"
);
}
#[test]
fn plot_line_sorts_elements_by_x_before_plotting() {
let mut canvas = TextCanvas::new(15, 5);
let x: Vec<f64> = vec![-5.0, 5.0, -2.5];
let y: Vec<f64> = vec![5.0, 2.5, -2.5];
Plot::stroke_xy_axes(&mut canvas, &x, &y);
Plot::line(&mut canvas, &x, &y);
assert_eq!(
canvas.to_string(),
"\
⢣⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀
⠈⢆⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⢀⡠
⠀⠘⡄⠀⠀⠀⠀⡇⠀⠀⣀⠤⠊⠁⠀
⠒⠒⠳⡒⠒⠒⢒⡷⠖⠛⠒⠒⠒⠒⠒
⠀⠀⠀⢣⠤⠒⠁⡇⠀⠀⠀⠀⠀⠀⠀
"
);
}
#[test]
fn plot_line_with_single_value() {
let mut canvas = TextCanvas::new(15, 5);
let x: Vec<f64> = vec![0.0];
let y: Vec<f64> = vec![0.0];
Plot::line(&mut canvas, &x, &y);
assert_eq!(
canvas.to_string(),
"\
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠠⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
"
);
}
#[test]
fn plot_line_with_range_xy_zero() {
let mut canvas = TextCanvas::new(15, 5);
let x: Vec<f64> = (-5..=5).map(|_| 0.0).collect();
let y: Vec<f64> = (-5..=5).map(|_| 0.0).collect();
Plot::line(&mut canvas, &x, &y);
assert_eq!(
canvas.to_string(),
"\
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠠⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
"
);
}
#[test]
fn plot_line_with_range_x_zero() {
let mut canvas = TextCanvas::new(15, 5);
let x: Vec<f64> = (-5..=5).map(|_| 0.0).collect();
let y: Vec<f64> = (-5..=5).map(f64::from).collect();
Plot::line(&mut canvas, &x, &y);
assert_eq!(
canvas.to_string(),
"\
⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀
"
);
}
#[test]
fn plot_line_with_range_y_zero() {
let mut canvas = TextCanvas::new(15, 5);
let x: Vec<f64> = (-5..=5).map(f64::from).collect();
let y: Vec<f64> = (-5..=5).map(|_| 0.0).collect();
Plot::line(&mut canvas, &x, &y);
assert_eq!(
canvas.to_string(),
"\
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
"
);
}
#[test]
fn plot_line_with_x_and_y_of_different_lengths_more_x() {
let mut canvas = TextCanvas::new(15, 5);
let x: Vec<f64> = (-10..=10).map(f64::from).collect();
let y: Vec<f64> = (-5..=5).map(f64::from).collect();
Plot::stroke_xy_axes(&mut canvas, &x, &y);
Plot::line(&mut canvas, &x, &y);
assert_eq!(
canvas.to_string(),
"\
⠀⠀⠀⠀⠀⢀⠔⡇⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⡠⠊⠀⡇⠀⠀⠀⠀⠀⠀⠀
⠤⠤⢤⠴⠥⠤⠤⡧⠤⠤⠤⠤⠤⠤⠤
⠀⡠⠊⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀
⡰⠁⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀
"
);
}
#[test]
fn plot_line_with_x_and_y_of_different_lengths_more_y() {
let mut canvas = TextCanvas::new(15, 5);
let x: Vec<f64> = (-5..=5).map(f64::from).collect();
let y: Vec<f64> = (-10..=10).map(f64::from).collect();
Plot::stroke_xy_axes(&mut canvas, &x, &y);
Plot::line(&mut canvas, &x, &y);
assert_eq!(
canvas.to_string(),
"\
⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀
⠤⠤⠤⠤⠤⠤⠤⡧⠤⠤⠤⣤⡤⠤⠶
⠀⠀⠀⠀⠀⣀⡠⡧⠒⠊⠉⠀⠀⠀⠀
⡠⠤⠒⠊⠉⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀
"
);
}
#[test]
fn plot_scatter() {
let mut canvas = TextCanvas::new(15, 5);
let x: Vec<f64> = (-5..=5).map(f64::from).collect();
let y: Vec<f64> = (-5..=5).map(f64::from).collect();
Plot::stroke_xy_axes(&mut canvas, &x, &y);
Plot::scatter(&mut canvas, &x, &y);
assert_eq!(
canvas.to_string(),
"\
⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⢀⠀⠂⠈
⠀⠀⠀⠀⠀⠀⠀⡇⢀⠀⠂⠀⠀⠀⠀
⠤⠤⠤⠤⠤⢤⠤⡧⠤⠤⠤⠤⠤⠤⠤
⠀⠀⢀⠀⠂⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀
⡀⠂⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀
"
);
}
#[test]
fn plot_scatter_with_empty_x() {
let mut canvas = TextCanvas::new(15, 5);
let x: Vec<f64> = vec![];
let y: Vec<f64> = (-5..=5).map(f64::from).collect();
Plot::stroke_xy_axes(&mut canvas, &x, &y);
Plot::scatter(&mut canvas, &x, &y);
assert_eq!(
canvas.to_string(),
"\
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
"
);
}
#[test]
fn plot_scatter_with_empty_y() {
let mut canvas = TextCanvas::new(15, 5);
let x: Vec<f64> = (-5..=5).map(f64::from).collect();
let y: Vec<f64> = vec![];
Plot::stroke_xy_axes(&mut canvas, &x, &y);
Plot::scatter(&mut canvas, &x, &y);
assert_eq!(
canvas.to_string(),
"\
⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀
"
);
}
#[test]
fn plot_scatter_with_single_value() {
let mut canvas = TextCanvas::new(15, 5);
let x: Vec<f64> = vec![0.0];
let y: Vec<f64> = vec![0.0];
Plot::scatter(&mut canvas, &x, &y);
assert_eq!(
canvas.to_string(),
"\
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠠⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
"
);
}
#[test]
fn plot_scatter_with_range_xy_zero() {
let mut canvas = TextCanvas::new(15, 5);
let x: Vec<f64> = (-5..=5).map(|_| 0.0).collect();
let y: Vec<f64> = (-5..=5).map(|_| 0.0).collect();
Plot::scatter(&mut canvas, &x, &y);
assert_eq!(
canvas.to_string(),
"\
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠠⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
"
);
}
#[test]
fn plot_scatter_with_range_x_zero() {
let mut canvas = TextCanvas::new(15, 5);
let x: Vec<f64> = (-5..=5).map(|_| 0.0).collect();
let y: Vec<f64> = (-5..=5).map(f64::from).collect();
Plot::scatter(&mut canvas, &x, &y);
assert_eq!(
canvas.to_string(),
"\
⠀⠀⠀⠀⠀⠀⠀⠨⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠨⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠨⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠨⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⢨⠀⠀⠀⠀⠀⠀⠀
"
);
}
#[test]
fn plot_scatter_with_range_y_zero() {
let mut canvas = TextCanvas::new(15, 5);
let x: Vec<f64> = (-5..=5).map(f64::from).collect();
let y: Vec<f64> = (-5..=5).map(|_| 0.0).collect();
Plot::scatter(&mut canvas, &x, &y);
assert_eq!(
canvas.to_string(),
"\
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠄⠄⠠⠀⠄⠠⠀⠄⠠⠀⠄⠠⠀⠄⠠
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
"
);
}
#[test]
fn plot_scatter_with_x_and_y_of_different_lengths_more_x() {
let mut canvas = TextCanvas::new(15, 5);
let x: Vec<f64> = (-10..=10).map(f64::from).collect();
let y: Vec<f64> = (-5..=5).map(f64::from).collect();
Plot::stroke_xy_axes(&mut canvas, &x, &y);
Plot::scatter(&mut canvas, &x, &y);
assert_eq!(
canvas.to_string(),
"\
⠀⠀⠀⠀⠀⢀⠐⡇⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⡀⠂⠀⡇⠀⠀⠀⠀⠀⠀⠀
⠤⠤⢤⠴⠤⠤⠤⡧⠤⠤⠤⠤⠤⠤⠤
⠀⡀⠂⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀
⡐⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀
"
);
}
#[test]
fn plot_scatter_with_x_and_y_of_different_lengths_more_y() {
let mut canvas = TextCanvas::new(15, 5);
let x: Vec<f64> = (-5..=5).map(f64::from).collect();
let y: Vec<f64> = (-10..=10).map(f64::from).collect();
Plot::stroke_xy_axes(&mut canvas, &x, &y);
Plot::scatter(&mut canvas, &x, &y);
assert_eq!(
canvas.to_string(),
"\
⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀
⠤⠤⠤⠤⠤⠤⠤⡧⠤⠤⠤⢤⠤⠤⠴
⠀⠀⠀⠀⠀⢀⠀⡇⠐⠀⠁⠀⠀⠀⠀
⡀⠄⠐⠀⠁⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀
"
);
}
#[test]
fn plot_bars() {
let mut canvas = TextCanvas::new(15, 5);
let x: Vec<f64> = (-5..=5).map(f64::from).collect();
let y: Vec<f64> = (-5..=5).map(f64::from).collect();
Plot::stroke_xy_axes(&mut canvas, &x, &y);
Plot::bars(&mut canvas, &x, &y);
assert_eq!(
canvas.to_string(),
"\
⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⢀⠀⡆⢸
⠀⠀⠀⠀⠀⠀⠀⡇⢀⠀⡆⢸⠀⡇⢸
⠤⠤⠤⠤⠤⢤⠤⡧⢼⠤⡧⢼⠤⡧⢼
⠀⠀⢀⠀⡆⢸⠀⡇⢸⠀⡇⢸⠀⡇⢸
⡀⡆⢸⠀⡇⢸⠀⡇⢸⠀⡇⢸⠀⡇⢸
"
);
}
#[test]
fn plot_bars_with_empty_x() {
let mut canvas = TextCanvas::new(15, 5);
let x: Vec<f64> = vec![];
let y: Vec<f64> = (-5..=5).map(f64::from).collect();
Plot::stroke_xy_axes(&mut canvas, &x, &y);
Plot::bars(&mut canvas, &x, &y);
assert_eq!(
canvas.to_string(),
"\
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
"
);
}
#[test]
fn plot_bars_with_empty_y() {
let mut canvas = TextCanvas::new(15, 5);
let x: Vec<f64> = (-5..=5).map(f64::from).collect();
let y: Vec<f64> = vec![];
Plot::stroke_xy_axes(&mut canvas, &x, &y);
Plot::bars(&mut canvas, &x, &y);
assert_eq!(
canvas.to_string(),
"\
⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀
"
);
}
#[test]
fn plot_bars_with_single_value() {
let mut canvas = TextCanvas::new(15, 5);
let x: Vec<f64> = vec![0.0];
let y: Vec<f64> = vec![0.0];
Plot::bars(&mut canvas, &x, &y);
assert_eq!(
canvas.to_string(),
"\
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⢠⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀
"
);
}
#[test]
fn plot_bars_with_range_xy_zero() {
let mut canvas = TextCanvas::new(15, 5);
let x: Vec<f64> = (-5..=5).map(|_| 0.0).collect();
let y: Vec<f64> = (-5..=5).map(|_| 0.0).collect();
Plot::bars(&mut canvas, &x, &y);
assert_eq!(
canvas.to_string(),
"\
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⢠⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀
"
);
}
#[test]
fn plot_bars_with_range_x_zero() {
let mut canvas = TextCanvas::new(15, 5);
let x: Vec<f64> = (-5..=5).map(|_| 0.0).collect();
let y: Vec<f64> = (-5..=5).map(f64::from).collect();
Plot::bars(&mut canvas, &x, &y);
assert_eq!(
canvas.to_string(),
"\
⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀
"
);
}
#[test]
fn plot_bars_with_range_y_zero() {
let mut canvas = TextCanvas::new(15, 5);
let x: Vec<f64> = (-5..=5).map(f64::from).collect();
let y: Vec<f64> = (-5..=5).map(|_| 0.0).collect();
Plot::bars(&mut canvas, &x, &y);
assert_eq!(
canvas.to_string(),
"\
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⡄⡄⢠⠀⡄⢠⠀⡄⢠⠀⡄⢠⠀⡄⢠
⡇⡇⢸⠀⡇⢸⠀⡇⢸⠀⡇⢸⠀⡇⢸
⡇⡇⢸⠀⡇⢸⠀⡇⢸⠀⡇⢸⠀⡇⢸
"
);
}
#[test]
fn plot_bars_with_x_and_y_of_different_lengths_more_x() {
let mut canvas = TextCanvas::new(15, 5);
let x: Vec<f64> = (-10..=10).map(f64::from).collect();
let y: Vec<f64> = (-5..=5).map(f64::from).collect();
Plot::stroke_xy_axes(&mut canvas, &x, &y);
Plot::bars(&mut canvas, &x, &y);
assert_eq!(
canvas.to_string(),
"\
⠀⠀⠀⠀⠀⢀⢰⡇⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⡀⣾⢸⡇⠀⠀⠀⠀⠀⠀⠀
⠤⠤⢤⢴⡧⣿⢼⡧⠤⠤⠤⠤⠤⠤⠤
⠀⡀⣾⢸⡇⣿⢸⡇⠀⠀⠀⠀⠀⠀⠀
⣰⡇⣿⢸⡇⣿⢸⡇⠀⠀⠀⠀⠀⠀⠀
"
);
}
#[test]
fn plot_bars_with_x_and_y_of_different_lengths_more_y() {
let mut canvas = TextCanvas::new(15, 5);
let x: Vec<f64> = (-5..=5).map(f64::from).collect();
let y: Vec<f64> = (-10..=10).map(f64::from).collect();
Plot::stroke_xy_axes(&mut canvas, &x, &y);
Plot::bars(&mut canvas, &x, &y);
assert_eq!(
canvas.to_string(),
"\
⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀
⠤⠤⠤⠤⠤⠤⠤⡧⠤⠤⠤⢤⠤⡤⢴
⠀⠀⠀⠀⠀⢀⠀⡇⢰⠀⡇⢸⠀⡇⢸
⡀⡄⢰⠀⡇⢸⠀⡇⢸⠀⡇⢸⠀⡇⢸
"
);
}
#[test]
fn plot_function() {
let mut canvas = TextCanvas::new(15, 5);
let f = |x| x * x;
Plot::stroke_xy_axes_of_function(&mut canvas, -10.0, 10.0, &f);
Plot::function(&mut canvas, -10.0, 10.0, &f);
assert_eq!(
canvas.to_string(),
"\
⠱⡀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⡜
⠀⢣⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⡜⠀
⠀⠀⠣⡀⠀⠀⠀⡇⠀⠀⠀⠀⡔⠁⠀
⠀⠀⠀⠑⡄⠀⠀⡇⠀⠀⢀⠎⠀⠀⠀
⣀⣀⣀⣀⣈⣒⣤⣇⣤⣒⣁⣀⣀⣀⣀
"
);
}
#[test]
fn plot_function_with_single_value() {
let mut canvas = TextCanvas::new(15, 5);
let f = |_| 0.0;
Plot::function(&mut canvas, 0.0, 0.0, &f);
assert_eq!(
canvas.to_string(),
"\
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠠⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
"
);
}
#[test]
fn plot_function_with_range_zero() {
let mut canvas = TextCanvas::new(15, 5);
let f = |_| 0.0;
Plot::function(&mut canvas, -10.0, 10.0, &f);
assert_eq!(
canvas.to_string(),
"\
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
"
);
}
#[test]
fn plot_function_filled() {
let mut canvas = TextCanvas::new(15, 5);
let f = |x| x * x;
Plot::stroke_xy_axes_of_function(&mut canvas, -10.0, 10.0, &f);
Plot::function_filled(&mut canvas, -10.0, 10.0, &f);
assert_eq!(
canvas.to_string(),
"\
⡇⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⢠⣿
⣿⡄⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⣼⣿
⣿⣿⣄⠀⠀⠀⠀⡇⠀⠀⠀⢀⣼⣿⣿
⣿⣿⣿⣦⡀⠀⠀⡇⠀⠀⣠⣾⣿⣿⣿
⣿⣿⣿⣿⣿⣦⣤⣧⣤⣾⣿⣿⣿⣿⣿
"
);
}
#[test]
fn plot_function_filled_with_single_value() {
let mut canvas = TextCanvas::new(15, 5);
let f = |_| 0.0;
Plot::function_filled(&mut canvas, 0.0, 0.0, &f);
assert_eq!(
canvas.to_string(),
"\
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⢠⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀
"
);
}
#[test]
fn plot_function_filled_with_range_zero() {
let mut canvas = TextCanvas::new(15, 5);
let f = |_| 0.0;
Plot::function_filled(&mut canvas, -10.0, 10.0, &f);
assert_eq!(
canvas.to_string(),
"\
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
"
);
}
#[test]
fn compute_function_works_with_structs() {
#[derive(Debug, PartialEq)]
struct Mock {
foo: f64,
bar: f64,
}
let f = |x: f64| Mock { foo: x, bar: -x };
let (x, y) = Plot::compute_function(-5.0, 5.0, 5.0, &f);
assert_eq!(x, vec![-5.0, -2.5, 0.0, 2.5, 5.0]);
assert_eq!(
y,
vec![
Mock {
foo: -5.0,
bar: 5.0
},
Mock {
foo: -2.5,
bar: 2.5
},
Mock {
foo: 0.0,
bar: -0.0
},
Mock {
foo: 2.5,
bar: -2.5
},
Mock {
foo: 5.0,
bar: -5.0
}
]
);
let y_foo: Vec<f64> = y.iter().map(|mock| mock.foo).collect();
let y_bar: Vec<f64> = y.iter().map(|mock| mock.bar).collect();
assert_eq!(y_foo, vec![-5.0, -2.5, 0.0, 2.5, 5.0]);
assert_eq!(y_bar, vec![5.0, 2.5, -0.0, -2.5, -5.0]);
}
#[test]
fn chart_function_x_squared() {
let mut canvas = TextCanvas::new(71, 19);
let f = |x| x * x;
Chart::function(&mut canvas, -10.0, 10.0, &f);
println!("{canvas}");
assert_eq!(
canvas.to_string(),
"\
⠀⠀⠀⠀⠀100⠀⡤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⢤⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⢇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠊⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠈⢆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠃⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠈⢆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠃⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠈⢆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡜⠀⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠈⢢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠎⠀⠀⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⢣⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠎⠀⠀⠀⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠱⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠃⠀⠀⠀⠀⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠑⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡰⠁⠀⠀⠀⠀⠀⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠈⢆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠜⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠣⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠔⠊⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⢄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⠜⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⢄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠎⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠢⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⠔⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⢄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠⠊⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠒⢄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡠⠒⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠒⠢⠤⠤⢄⡠⠤⠤⠴⠒⠋⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀
⠀⠀0.0073⠀⠓⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠚⠀
⠀⠀⠀⠀⠀⠀⠀-10⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀10
"
);
}
#[test]
fn chart_function_polynomial() {
let mut canvas = TextCanvas::new(71, 19);
let f = |x: f64| x.powi(3) - 2.0 * x.powi(2) + 3.0 * x;
Chart::function(&mut canvas, -5.0, 5.0, &f);
println!("{canvas}");
assert_eq!(
canvas.to_string(),
"\
⠀⠀⠀⠀⠀⠀90⠀⡤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⢤⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠔⠉⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠔⠁⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡠⠒⠁⠀⠀⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⡠⠔⠊⠁⠀⠀⠀⠀⠀⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣀⣀⣀⣀⣀⣀⣀⡠⠤⠤⠔⠒⠒⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⡠⠤⠒⠒⠒⠉⠉⠉⠉⠉⠉⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡠⠔⠚⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡠⠒⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠔⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⢀⠔⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⡔⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⢠⠊⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⡰⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⢀⠜⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⢀⠎⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⡎⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀
⠀⠀⠀⠀-190⠀⠓⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠚⠀
⠀⠀⠀⠀⠀⠀⠀⠀-5⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀5
"
);
}
#[test]
fn chart_function_cos() {
let mut canvas = TextCanvas::new(71, 19);
let f = |x: f64| x.cos();
Chart::function(&mut canvas, 0.0, 5.0, &f);
println!("{canvas}");
assert_eq!(
canvas.to_string(),
"\
⠀⠀⠀⠀⠀⠀⠀1⠀⡤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⢤⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠉⠉⠉⠒⠢⢄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠙⢄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠈⠢⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⢄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⢄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠤⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠣⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠⠊⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⢄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠔⠁⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠣⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠⠊⠀⠀⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⢄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠔⠁⠀⠀⠀⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠣⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠⠊⠀⠀⠀⠀⠀⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠜⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⢄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠔⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠱⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠔⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠒⢄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡠⠒⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠑⠢⠤⠤⢄⠤⠤⠔⠊⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀-1⠀⠓⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠚⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀0⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀5
"
);
}
#[test]
fn chart_function_filled_x_squared() {
let mut canvas = TextCanvas::new(71, 19);
let f = |x| x * x;
Chart::function_filled(&mut canvas, -10.0, 10.0, &f);
println!("{canvas}");
assert_eq!(
canvas.to_string(),
"\
⠀⠀⠀⠀⠀100⠀⡤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⢤⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⣇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣿⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⣿⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣿⣿⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⣿⣿⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣼⣿⣿⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⣿⣿⣿⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣾⣿⣿⣿⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⣿⣿⣿⣿⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣾⣿⣿⣿⣿⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⣿⣿⣿⣿⣿⣷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣿⣿⣿⣿⣿⣿⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⣿⣿⣿⣿⣿⣿⣷⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣴⣿⣿⣿⣿⣿⣿⣿⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⣿⣿⣿⣿⣿⣿⣿⣷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣾⣿⣿⣿⣿⣿⣿⣿⣿⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⣿⣿⣿⣿⣿⣿⣿⣿⣿⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣤⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣦⣤⣤⣤⣠⣤⣤⣤⣶⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢸⠀
⠀⠀0.0035⠀⠓⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠚⠀
⠀⠀⠀⠀⠀⠀⠀-10⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀10
"
);
}
#[test]
fn chart_function_filled_polynomial() {
let mut canvas = TextCanvas::new(71, 19);
let f = |x: f64| x.powi(3) - 2.0 * x.powi(2) + 3.0 * x;
Chart::function_filled(&mut canvas, -5.0, 5.0, &f);
println!("{canvas}");
assert_eq!(
canvas.to_string(),
"\
⠀⠀⠀⠀⠀⠀90⠀⡤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⢤⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣿⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣿⣿⣿⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣤⣾⣿⣿⣿⣿⣿⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣠⣴⣾⣿⣿⣿⣿⣿⣿⣿⣿⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣀⣀⣀⣀⣀⣀⣀⣠⣤⣤⣴⣶⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣠⣤⣴⣶⣶⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⣴⣶⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⢀⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⣠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⢀⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⢀⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢸⠀
⠀⠀⠀⠀-190⠀⠓⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠚⠀
⠀⠀⠀⠀⠀⠀⠀⠀-5⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀5
"
);
}
#[test]
fn chart_function_filled_cos() {
let mut canvas = TextCanvas::new(71, 19);
let f = |x: f64| x.cos();
Chart::function_filled(&mut canvas, 0.0, 5.0, &f);
println!("{canvas}");
assert_eq!(
canvas.to_string(),
"\
⠀⠀⠀⠀⠀⠀⠀1⠀⡤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⢤⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⣿⣿⣿⣶⣤⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⣿⣿⣿⣿⣿⣿⣿⣦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣤⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣾⣿⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣾⣿⣿⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣼⣿⣿⣿⣿⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⣿⣿⣿⣿⣿⣿⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣾⣿⣿⣿⣿⣿⣿⣿⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣸⣿⣿⣿⣿⣿⣿⣿⣿⣿⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣶⣤⣤⣄⣤⣤⣤⣶⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢸⠀
⠀⠀⠀⠀⠀⠀-1⠀⠓⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠚⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀0⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀5
"
);
}
#[test]
fn chart_line() {
let mut canvas = TextCanvas::new(35, 10);
let x: Vec<f64> = (-5..=5).map(f64::from).collect();
let y: Vec<f64> = (-5..=5).map(f64::from).collect();
Chart::line(&mut canvas, &x, &y);
println!("{canvas}");
assert_eq!(
canvas.to_string(),
"\
⠀⠀⠀⠀⠀⠀⠀5⠀⡤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⢤⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⠤⠒⠉⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⠤⠊⠀⠀⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡠⠒⠉⠀⠀⠀⠀⠀⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠤⠊⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⢀⡠⠔⠊⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⢀⡠⠔⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⡠⠒⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀-5⠀⠓⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠚⠀
⠀⠀⠀⠀⠀⠀⠀⠀-5⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀5
"
);
}
#[test]
fn chart_scatter() {
let mut canvas = TextCanvas::new(35, 10);
let x: Vec<f64> = (-5..=5).map(f64::from).collect();
let y: Vec<f64> = (-5..=5).map(f64::from).collect();
Chart::scatter(&mut canvas, &x, &y);
println!("{canvas}");
assert_eq!(
canvas.to_string(),
"\
⠀⠀⠀⠀⠀⠀⠀5⠀⡤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⢤⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠄⠀⠈⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠂⠀⠀⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠠⠀⠈⠀⠀⠀⠀⠀⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠠⠀⠀⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠐⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⡀⠀⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀
⠀⠀⠀⠀⠀⠀-5⠀⠓⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠚⠀
⠀⠀⠀⠀⠀⠀⠀⠀-5⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀5
"
);
}
#[test]
fn chart_bars() {
let mut canvas = TextCanvas::new(35, 10);
let x: Vec<f64> = (-5..=5).map(f64::from).collect();
let y: Vec<f64> = (-5..=5).map(f64::from).collect();
Chart::bars(&mut canvas, &x, &y);
println!("{canvas}");
assert_eq!(
canvas.to_string(),
"\
⠀⠀⠀⠀⠀⠀⠀5⠀⡤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⢤⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡄⠀⢸⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡆⠀⡇⠀⢸⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠀⢸⠀⠀⡇⠀⡇⠀⢸⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡆⠀⢸⠀⢸⠀⠀⡇⠀⡇⠀⢸⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⢠⠀⠀⡇⠀⡇⠀⢸⠀⢸⠀⠀⡇⠀⡇⠀⢸⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⢰⠀⢸⠀⠀⡇⠀⡇⠀⢸⠀⢸⠀⠀⡇⠀⡇⠀⢸⢸⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⡀⠀⡇⠀⢸⠀⢸⠀⠀⡇⠀⡇⠀⢸⠀⢸⠀⠀⡇⠀⡇⠀⢸⢸⠀
⠀⠀⠀⠀⠀⠀-5⠀⠓⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠚⠀
⠀⠀⠀⠀⠀⠀⠀⠀-5⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀5
"
);
}
#[test]
fn chart_empty() {
let mut canvas = TextCanvas::new(35, 10);
let x: Vec<f64> = vec![];
let y: Vec<f64> = vec![];
Chart::line(&mut canvas, &x, &y);
Chart::scatter(&mut canvas, &x, &y);
println!("{canvas}");
assert_eq!(
canvas.to_string(),
"\
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
"
);
}
#[test]
#[should_panic(
expected = "Canvas size is 12×3, but must be at least 13×4 to accommodate for plot."
)]
fn chart_canvas_too_small_both_horizontally_and_vertically() {
let mut canvas = TextCanvas::new(Chart::HORIZONTAL_MARGIN, Chart::VERTICAL_MARGIN);
let x: Vec<f64> = (-5..=5).map(f64::from).collect();
let y: Vec<f64> = (-5..=5).map(f64::from).collect();
Chart::scatter(&mut canvas, &x, &y);
}
#[test]
#[should_panic(
expected = "Canvas size is 12×4, but must be at least 13×4 to accommodate for plot."
)]
fn chart_canvas_too_small_horizontally() {
let mut canvas = TextCanvas::new(Chart::HORIZONTAL_MARGIN, Chart::VERTICAL_MARGIN + 1);
let x: Vec<f64> = (-5..=5).map(f64::from).collect();
let y: Vec<f64> = (-5..=5).map(f64::from).collect();
Chart::line(&mut canvas, &x, &y);
}
#[test]
#[should_panic(
expected = "Canvas size is 13×3, but must be at least 13×4 to accommodate for plot."
)]
fn chart_canvas_too_small_vertically() {
let mut canvas = TextCanvas::new(Chart::HORIZONTAL_MARGIN + 1, Chart::VERTICAL_MARGIN);
let x: Vec<f64> = (-5..=5).map(f64::from).collect();
let y: Vec<f64> = (-5..=5).map(f64::from).collect();
Chart::line(&mut canvas, &x, &y);
}
#[test]
#[allow(clippy::cognitive_complexity)]
fn chart_pretty_number() {
assert_eq!(Chart::format_number(1_570_000_000_000.0), "1.6T");
assert_eq!(Chart::format_number(1_000_000_000_000.0), "1.0T");
assert_eq!(Chart::format_number(1_570_000_000.0), "1.6B");
assert_eq!(Chart::format_number(1_000_000_000.0), "1.0B");
assert_eq!(Chart::format_number(1_570_000.0), "1.6M");
assert_eq!(Chart::format_number(1_000_000.0), "1.0M");
assert_eq!(Chart::format_number(100_000.0), "100.0K");
assert_eq!(Chart::format_number(10_570.0), "10.6K");
assert_eq!(Chart::format_number(10_000.0), "10.0K");
assert_eq!(Chart::format_number(1_570.0), "1570");
assert_eq!(Chart::format_number(1_000.0), "1000");
assert_eq!(Chart::format_number(1.0009), "1");
assert_eq!(Chart::format_number(-1.0009), "-1");
assert_eq!(Chart::format_number(0.010_57), "0.0106");
assert_eq!(Chart::format_number(0.010_00), "0.0100");
assert_eq!(Chart::format_number(0.000_001_57), "0");
assert_eq!(Chart::format_number(0.000_001_00), "0");
assert_eq!(Chart::format_number(-0.000_001_57), "0");
assert_eq!(Chart::format_number(-0.000_001_00), "0");
assert_eq!(Chart::format_number(-0.001_57), "-0.0016");
assert_eq!(Chart::format_number(-0.001_00), "-0.0010");
assert_eq!(Chart::format_number(-1_570.0), "-1570");
assert_eq!(Chart::format_number(-1_000.0), "-1000");
assert_eq!(Chart::format_number(-10_570.0), "-10.6K");
assert_eq!(Chart::format_number(-10_000.0), "-10.0K");
assert_eq!(Chart::format_number(-100_000.0), "-100.0K");
assert_eq!(Chart::format_number(-1_570_000.0), "-1.6M");
assert_eq!(Chart::format_number(-1_000_000.0), "-1.0M");
assert_eq!(Chart::format_number(-1_570_000_000.0), "-1.6B");
assert_eq!(Chart::format_number(-1_000_000_000.0), "-1.0B");
assert_eq!(Chart::format_number(-1_570_000_000_000.0), "-1.6T");
assert_eq!(Chart::format_number(-1_000_000_000_000.0), "-1.0T");
}
#[test]
fn downsample_mean_regular() {
let points = [
(0.0, 0.0),
(1.0, 3.0),
(2.0, -1.0),
(3.0, -4.0),
(4.0, 6.0),
(5.0, 1.0),
(6.0, 7.0),
(7.0, -4.0),
(8.0, -2.0),
(9.0, 2.5),
(10.0, 0.0),
];
let (x, y): (Vec<f64>, Vec<f64>) = points.iter().copied().unzip();
let res = Resampling::downsample_mean(&x, &y, 4);
let res_points = Resampling::downsample_points_mean(&points, 4);
let res_points: (Vec<f64>, Vec<f64>) = res_points.into_iter().unzip();
assert_eq!(res, res_points);
}
#[test]
fn downsample_points_mean_regular() {
let points = [
(0.0, 0.0),
(1.0, 3.0),
(2.0, -1.0),
(3.0, -4.0),
(4.0, 6.0),
(5.0, 1.0),
(6.0, 7.0),
(7.0, -4.0),
(8.0, -2.0),
(9.0, 2.5),
(10.0, 0.0),
];
let res = Resampling::downsample_points_mean(&points, 4);
assert_eq!(
res,
[
(0.0, 0.0),
(3.0, 1.0),
(7.5, 0.875),
(10.0, 0.0),
]
);
}
#[test]
fn downsample_points_mean_no_op_nb_points_lt_max_nb_points() {
let points = [(0.0, 0.0), (1.0, 0.0), (2.0, 0.0), (3.0, 0.0), (4.0, 0.0)];
let res = Resampling::downsample_points_mean(&points, 6);
assert_eq!(res, points);
}
#[test]
fn downsample_points_mean_keep_only_first_and_last() {
let points = [(0.0, 0.0), (1.0, 0.0), (2.0, 0.0), (3.0, 0.0)];
let res = Resampling::downsample_points_mean(&points, 2);
assert_eq!(res, [(0.0, 0.0), (3.0, 0.0)]);
}
#[test]
#[should_panic(expected = "minimum two points are required as output")]
fn downsample_points_mean_error_max_nb_points_lt_2() {
_ = Resampling::downsample_points_mean(&[], 2); _ = Resampling::downsample_points_mean(&[], 1);
}
#[test]
fn plot_data_with_downsampling_mean() {
let f = |x: f64| x.sin();
let (x, y) = Plot::compute_function(0.0, std::f64::consts::TAU, 1000.0, &f);
let mut canvas = TextCanvas::new(15, 5);
Plot::scatter(&mut canvas, &x, &y);
assert_eq!(x.len(), 1000);
assert_eq!(
canvas.to_string(),
"\
⠀⣠⠞⠉⠙⢦⠀⠀⠀⠀⠀⠀⠀⠀⠀
⣰⠃⠀⠀⠀⠈⢧⠀⠀⠀⠀⠀⠀⠀⠀
⠃⠀⠀⠀⠀⠀⠈⢧⠀⠀⠀⠀⠀⢀⡖
⠀⠀⠀⠀⠀⠀⠀⠈⢧⠀⠀⠀⢀⡞⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠈⠳⢤⠴⠋⠀⠀
"
);
let mut canvas_downsampled = TextCanvas::new(15, 5);
let points: Vec<(f64, f64)> = x.iter().copied().zip(y).collect();
let points = Resampling::downsample_points_mean(&points, 30);
let (x, y): (Vec<f64>, Vec<f64>) = points.into_iter().unzip();
Plot::scatter(&mut canvas_downsampled, &x, &y);
assert_eq!(x.len(), 30);
assert_eq!(
canvas_downsampled.to_string(),
"\
⠀⠠⠊⠉⠑⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠠⠁⠀⠀⠀⠀⠡⠀⠀⠀⠀⠀⠀⠀⠀
⠃⠀⠀⠀⠀⠀⠀⠡⠀⠀⠀⠀⠀⠀⠔
⠀⠀⠀⠀⠀⠀⠀⠀⠡⠀⠀⠀⢀⠌⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⢄⠤⠂⠀⠀
"
);
}
#[test]
fn downsample_min_max_regular() {
let points = [
(0.0, 0.0),
(1.0, 3.0),
(2.0, -1.0),
(3.0, -4.0),
(4.0, 6.0),
(5.0, 1.0),
(6.0, 7.0),
(7.0, -4.0),
(8.0, -2.0),
(9.0, 2.5),
(10.0, 0.0),
];
let (x, y): (Vec<f64>, Vec<f64>) = points.iter().copied().unzip();
let res = Resampling::downsample_min_max(&x, &y, 4);
let res_points = Resampling::downsample_points_min_max(&points, 4);
let res_points: (Vec<f64>, Vec<f64>) = res_points.into_iter().unzip();
assert_eq!(res, res_points);
}
#[test]
fn downsample_points_min_max_regular() {
let points = [
(0.0, 0.0),
(1.0, 3.0),
(2.0, -1.0),
(3.0, -4.0),
(4.0, 6.0),
(5.0, 1.0),
(6.0, 7.0),
(7.0, -4.0),
(8.0, -2.0),
(9.0, 2.5),
(10.0, 0.0),
];
let res = Resampling::downsample_points_min_max(&points, 6);
assert_eq!(
res,
[
(0.0, 0.0),
(3.0, -4.0),
(4.0, 6.0),
(6.0, 7.0),
(7.0, -4.0),
(10.0, 0.0),
]
);
}
#[test]
fn downsample_points_min_max_no_op_nb_points_lt_max_nb_points() {
let points = [(0.0, 0.0), (1.0, 0.0), (2.0, 0.0), (3.0, 0.0), (4.0, 0.0)];
let res = Resampling::downsample_points_min_max(&points, 6);
assert_eq!(res, points);
}
#[test]
fn downsample_points_min_max_keep_only_first_and_last() {
let points = [(0.0, 0.0), (1.0, 0.0), (2.0, 0.0), (3.0, 0.0)];
let res = Resampling::downsample_points_min_max(&points, 2);
assert_eq!(res, [(0.0, 0.0), (3.0, 0.0)]);
}
#[test]
#[should_panic(expected = "minimum two points are required as output")]
fn downsample_points_min_max_error_max_nb_points_lt_2() {
_ = Resampling::downsample_points_min_max(&[], 2); _ = Resampling::downsample_points_min_max(&[], 1);
}
#[test]
#[should_panic(expected = "number of output points must be even")]
fn downsample_points_min_max_error_max_nb_points_is_odd() {
_ = Resampling::downsample_points_min_max(&[], 3);
}
#[test]
fn plot_data_with_downsampling_min_max() {
let f = |x: f64| x.sin();
let (x, y) = Plot::compute_function(0.0, std::f64::consts::TAU, 1000.0, &f);
let mut canvas = TextCanvas::new(15, 5);
Plot::scatter(&mut canvas, &x, &y);
assert_eq!(x.len(), 1000);
assert_eq!(
canvas.to_string(),
"\
⠀⣠⠞⠉⠙⢦⠀⠀⠀⠀⠀⠀⠀⠀⠀
⣰⠃⠀⠀⠀⠈⢧⠀⠀⠀⠀⠀⠀⠀⠀
⠃⠀⠀⠀⠀⠀⠈⢧⠀⠀⠀⠀⠀⢀⡖
⠀⠀⠀⠀⠀⠀⠀⠈⢧⠀⠀⠀⢀⡞⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠈⠳⢤⠴⠋⠀⠀
"
);
let mut canvas_downsampled = TextCanvas::new(15, 5);
let points: Vec<(f64, f64)> = x.iter().copied().zip(y).collect();
let points = Resampling::downsample_points_min_max(&points, 60);
let (x, y): (Vec<f64>, Vec<f64>) = points.into_iter().unzip();
Plot::scatter(&mut canvas_downsampled, &x, &y);
assert_eq!(x.len(), 60);
assert_eq!(
canvas_downsampled.to_string(),
"\
⠀⢀⠔⠉⠉⢂⠀⠀⠀⠀⠀⠀⠀⠀⠀
⢀⠂⠀⠀⠀⠀⠡⠀⠀⠀⠀⠀⠀⠀⠀
⠂⠀⠀⠀⠀⠀⠀⢁⠀⠀⠀⠀⠀⠀⠖
⠀⠀⠀⠀⠀⠀⠀⠀⠢⠀⠀⠀⠀⠌⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⢤⠤⠊⠀⠀
"
);
}
}