use image::{ImageBuffer, Rgba};
use serde::Deserialize;
use tracing::{trace, warn};
use crate::colours::Colour;
#[derive(Debug, Deserialize, Copy, Clone)]
#[allow(clippy::missing_docs_in_private_items)]
pub enum DataSymbol {
Cross,
Circle,
Triangle,
Square,
Point,
}
impl DataSymbol {
pub fn find_pixels(self, origin: (u32, u32), thickness: u32, radius: u32) -> Vec<(u32, u32)> {
let mut pixel_coords: Vec<(u32, u32)> = Vec::new();
match self {
DataSymbol::Cross => {
pixel_coords.push(origin);
let length = if (radius + 1) & 1 == 1 {
radius + 2
} else {
radius + 1
};
for i in 0..length {
pixel_coords.push((origin.0 + i, origin.1));
for n in 0..=thickness {
pixel_coords.push((origin.0 + i, origin.1 + n));
pixel_coords.push((origin.0 + i, origin.1 - n));
}
pixel_coords.push((origin.0 - i, origin.1));
for n in 0..=thickness {
pixel_coords.push((origin.0 - i, origin.1 + n));
pixel_coords.push((origin.0 - i, origin.1 - n));
}
pixel_coords.push((origin.0, origin.1 + i));
for n in 0..=thickness {
pixel_coords.push((origin.0 + n, origin.1 + i));
pixel_coords.push((origin.0 - n, origin.1 + i));
}
pixel_coords.push((origin.0, origin.1 - i));
for n in 0..=thickness {
pixel_coords.push((origin.0 + n, origin.1 - i));
pixel_coords.push((origin.0 - n, origin.1 - i));
}
}
}
DataSymbol::Circle => {
pixel_coords.push(origin);
for n in 0..=thickness {
let r = if (radius + 1) & 1 == 1 {
radius + 2 + n
} else {
radius + 1 + n
};
let scale_factor: u32 = 1000;
for angle_deg in 0..(360 * scale_factor) {
let y =
(angle_deg as f32 / scale_factor as f32).to_radians().sin() * r as f32;
let x =
(angle_deg as f32 / scale_factor as f32).to_radians().cos() * r as f32;
let delta_x = origin.0 as f32 + x;
let delta_y = origin.1 as f32 + y;
pixel_coords.push((delta_x as u32, delta_y as u32));
}
}
}
DataSymbol::Triangle => {
pixel_coords.push(origin);
for n in 0..=thickness {
let float_n = n as f32;
let side_length: f32 = if (radius + 1) & 1 == 1 {
(radius + 2) as f32 + float_n
} else {
(radius + 1) as f32 + float_n
};
let height: f32 = side_length * (3.0_f32.sqrt() / 2.0);
let incircle_radius: f32 = side_length / (2.0 * 3.0_f32.sqrt());
let top: (f32, f32) =
(origin.0 as f32, origin.1 as f32 + height - incircle_radius);
let left: (f32, f32) = (
origin.0 as f32 - (side_length / 2.0),
origin.1 as f32 - incircle_radius,
);
let right: (f32, f32) = (
origin.0 as f32 + (side_length / 2.0),
origin.1 as f32 - incircle_radius,
);
pixel_coords.push((top.0 as u32, top.1 as u32));
pixel_coords.push((left.0 as u32, left.1 as u32));
pixel_coords.push((right.0 as u32, right.1 as u32));
let x_distance_between_left_right = (right.0 - left.0) as u32;
for i in 0..x_distance_between_left_right {
pixel_coords.push((left.0 as u32 + i, left.1 as u32));
}
let gradient_l_t = (top.1 - left.1) / (top.0 - left.0);
let intercept_l_t = left.1 - (gradient_l_t * left.0);
let left_to_top_delta_x = (top.0 - left.0) as u32;
let scale_factor = 10000;
for i in 0..(left_to_top_delta_x * scale_factor) {
let x = left.0 + (i as f32 / scale_factor as f32);
let y = (gradient_l_t * x) + intercept_l_t;
pixel_coords.push((x as u32, y as u32));
}
let gradient_t_r = (top.1 - right.1) / (top.0 - right.0);
let intercept_t_r = right.1 - (gradient_t_r * right.0);
let top_to_right_delta_x = (right.0 - top.0) as u32;
let scale_factor = 10000;
for i in 0..(top_to_right_delta_x * scale_factor) {
let x = top.0 + (i as f32 / scale_factor as f32);
let y = (gradient_t_r * x) + intercept_t_r;
pixel_coords.push((x as u32, y as u32));
}
}
}
DataSymbol::Square => {
pixel_coords.push(origin);
let half_side_length = if (radius + 1) & 1 == 1 {
radius + 2
} else {
radius + 1
};
for i in 0..half_side_length {
for n in 0..=thickness {
pixel_coords.push((origin.0 + i + n, origin.1 - radius - n));
pixel_coords.push((origin.0 - i - n, origin.1 - radius - n));
pixel_coords.push((origin.0 + i + n, origin.1 + radius + n));
pixel_coords.push((origin.0 - i - n, origin.1 + radius + n));
pixel_coords.push((origin.0 - radius - n, origin.1 - i - n));
pixel_coords.push((origin.0 - radius - n, origin.1 + i + n));
pixel_coords.push((origin.0 + radius + n, origin.1 - i - n));
pixel_coords.push((origin.0 + radius + n, origin.1 + i + n));
}
}
}
DataSymbol::Point => {
pixel_coords.push(origin);
}
}
pixel_coords
}
}
#[derive(Debug, Deserialize, Copy, Clone)]
pub struct DataPoint {
pub x: f32,
pub ux: Option<f32>,
pub y: f32,
pub uy: Option<f32>,
pub colour: Colour,
pub symbol: DataSymbol,
pub symbol_radius: u32,
pub symbol_thickness: u32,
}
impl DataPoint {
pub fn draw_point(
self,
canvas: &mut ImageBuffer<Rgba<u8>, Vec<u8>>,
x_scale_factor: f32,
y_scale_factor: f32,
axes_origin: (u32, u32),
) {
trace!("Drawing point {:?}", self);
let rgba = Colour::get_pixel_colour(self.colour);
let x_pixel_corrected_pos = if self.x > 0.0 {
axes_origin.0 + (self.x * x_scale_factor) as u32
} else {
axes_origin.0 - (-self.x * x_scale_factor) as u32
};
let y_pixel_corrected_pos = if self.y > 0.0 {
axes_origin.1 - (self.y * y_scale_factor) as u32
} else {
axes_origin.1 + (-self.y * y_scale_factor) as u32
};
trace!(
"Plotting data point ({}, {}) with pixel position ({}, {})",
self.x,
self.y,
x_pixel_corrected_pos,
y_pixel_corrected_pos
);
let pixels_in_shape = self.symbol.find_pixels(
(x_pixel_corrected_pos, y_pixel_corrected_pos),
self.symbol_thickness,
self.symbol_radius,
);
for (px, py) in pixels_in_shape.iter() {
match canvas.get_pixel_mut_checked(*px, *py) {
Some(pixel) => *pixel = Rgba(rgba),
None => warn!(
"Cannot plot data point ({}, {}) with symbol pixel position ({}, {})",
self.x, self.y, x_pixel_corrected_pos, y_pixel_corrected_pos
),
}
}
match self.ux {
Some(value) => {
trace!("Drawing x uncertainty with size {}", value);
let upper_limit_pixel = axes_origin.0 + ((self.x + value) * x_scale_factor) as u32;
let lower_limit_pixel = axes_origin.0 + ((self.x - value) * x_scale_factor) as u32;
for px in lower_limit_pixel..=upper_limit_pixel {
match canvas.get_pixel_mut_checked(px, y_pixel_corrected_pos) {
Some(pixel) => *pixel = Rgba(rgba),
None => warn!(
"Cannot plot error bar point ({}, {}) with pixel position ({}, {})",
self.x, self.y, px, y_pixel_corrected_pos
),
}
}
let error_bar_length = (upper_limit_pixel - lower_limit_pixel) / 4;
for py in 0..=error_bar_length {
match canvas
.get_pixel_mut_checked(upper_limit_pixel, y_pixel_corrected_pos + py)
{
Some(pixel) => *pixel = Rgba(rgba),
None => warn!(
"Cannot plot error bar wing for point ({}, {}) with pixel position ({}, {})",
self.x, self.y, upper_limit_pixel, y_pixel_corrected_pos + py
),
}
match canvas
.get_pixel_mut_checked(lower_limit_pixel, y_pixel_corrected_pos + py)
{
Some(pixel) => *pixel = Rgba(rgba),
None => warn!(
"Cannot plot error bar wing for point ({}, {}) with pixel position ({}, {})",
self.x, self.y, lower_limit_pixel, y_pixel_corrected_pos - py
),
}
match canvas
.get_pixel_mut_checked(upper_limit_pixel, y_pixel_corrected_pos - py)
{
Some(pixel) => *pixel = Rgba(rgba),
None => warn!(
"Cannot plot error bar wing for point ({}, {}) with pixel position ({}, {})",
self.x, self.y, upper_limit_pixel, y_pixel_corrected_pos + py
),
}
match canvas
.get_pixel_mut_checked(lower_limit_pixel, y_pixel_corrected_pos - py)
{
Some(pixel) => *pixel = Rgba(rgba),
None => warn!(
"Cannot plot error bar wing for point ({}, {}) with pixel position ({}, {})",
self.x, self.y, lower_limit_pixel, y_pixel_corrected_pos - py
),
}
}
}
None => {}
}
match self.uy {
Some(value) => {
trace!("Drawing y uncertainty with size {}", value);
let upper_limit_pixel = axes_origin.1 - ((self.y - value) * y_scale_factor) as u32;
let lower_limit_pixel = axes_origin.1 - ((self.y + value) * y_scale_factor) as u32;
for py in lower_limit_pixel..=upper_limit_pixel {
match canvas.get_pixel_mut_checked(x_pixel_corrected_pos, py) {
Some(pixel) => *pixel = Rgba(rgba),
None => warn!(
"Cannot plot error bar point ({}, {}) with pixel position ({}, {})",
self.x, self.y, x_pixel_corrected_pos, py
),
}
}
let error_bar_length = (upper_limit_pixel - lower_limit_pixel) / 4;
for px in 0..=error_bar_length {
match canvas
.get_pixel_mut_checked(x_pixel_corrected_pos - px, upper_limit_pixel)
{
Some(pixel) => *pixel = Rgba(rgba),
None => warn!(
"Cannot plot error bar wing for point ({}, {}) with pixel position ({}, {})",
self.x, self.y, x_pixel_corrected_pos - px, upper_limit_pixel
),
}
match canvas
.get_pixel_mut_checked(x_pixel_corrected_pos - px, lower_limit_pixel)
{
Some(pixel) => *pixel = Rgba(rgba),
None => warn!(
"Cannot plot error bar wing for point ({}, {}) with pixel position ({}, {})",
self.x, self.y, x_pixel_corrected_pos - px, lower_limit_pixel
),
}
match canvas
.get_pixel_mut_checked(x_pixel_corrected_pos + px, upper_limit_pixel)
{
Some(pixel) => *pixel = Rgba(rgba),
None => warn!(
"Cannot plot error bar wing for point ({}, {}) with pixel position ({}, {})",
self.x, self.y, x_pixel_corrected_pos + px, upper_limit_pixel
),
}
match canvas
.get_pixel_mut_checked(x_pixel_corrected_pos + px, lower_limit_pixel)
{
Some(pixel) => *pixel = Rgba(rgba),
None => warn!(
"Cannot plot error bar wing for point ({}, {}) with pixel position ({}, {})",
self.x, self.y, x_pixel_corrected_pos + px, lower_limit_pixel
),
}
}
}
None => {}
}
}
}