1use bytemuck::{Pod, Zeroable};
2
3#[cfg(feature = "python")]
4use pyo3::prelude::*;
5
6#[repr(C)]
8#[derive(Debug, Clone, Copy, Pod, Zeroable)]
9#[cfg_attr(feature = "python", pyo3::pyclass(get_all, set_all))]
10pub struct Point2D {
11 pub x: f32,
12 pub y: f32,
13}
14
15impl Point2D {
16 pub fn new(x: f32, y: f32) -> Self {
17 Self { x, y }
18 }
19}
20
21#[cfg(feature = "python")]
23#[pymethods]
24impl Point2D {
25 #[new]
26 fn py_new(x: f32, y: f32) -> Self {
27 Self::new(x, y)
28 }
29}
30
31#[repr(C)]
33#[derive(Debug, Clone, Copy, Pod, Zeroable)]
34#[cfg_attr(feature = "python", pyo3::pyclass(name = "Color", get_all, set_all))]
35pub struct Color {
36 pub r: f32,
37 pub g: f32,
38 pub b: f32,
39 pub a: f32,
40}
41
42impl Color {
43 pub fn new(r: f32, g: f32, b: f32, a: f32) -> Self {
44 Self { r, g, b, a }
45 }
46
47 pub fn from_hex(hex: &str) -> Self {
48 let hex = hex.trim_start_matches('#');
49 let r = u8::from_str_radix(&hex[0..2], 16).unwrap_or(0) as f32 / 255.0;
50 let g = u8::from_str_radix(&hex[2..4], 16).unwrap_or(0) as f32 / 255.0;
51 let b = u8::from_str_radix(&hex[4..6], 16).unwrap_or(0) as f32 / 255.0;
52 let a = if hex.len() >= 8 {
53 u8::from_str_radix(&hex[6..8], 16).unwrap_or(255) as f32 / 255.0
54 } else {
55 1.0
56 };
57 Self { r, g, b, a }
58 }
59}
60
61#[cfg(feature = "python")]
63#[pymethods]
64impl Color {
65 #[new]
66 #[pyo3(signature = (r, g, b, a=1.0))]
67 fn py_new(r: f32, g: f32, b: f32, a: f32) -> Self {
68 Self::new(r, g, b, a)
69 }
70
71 #[staticmethod]
73 #[pyo3(name = "from_hex")]
74 fn from_hex_py(hex: &str) -> Self {
75 Self::from_hex(hex)
76 }
77}
78
79impl Default for Color {
80 fn default() -> Self {
81 Self::new(0.0, 0.5, 1.0, 1.0) }
83}
84
85#[repr(C)]
87#[derive(Debug, Clone, Copy, Pod, Zeroable)]
88pub struct Vertex {
89 pub position: [f32; 2],
90 pub color: [f32; 4],
91 pub size: f32,
92 pub _padding: [f32; 3], }
94
95impl Vertex {
96 pub fn new(position: Point2D, color: Color, size: f32) -> Self {
97 Self {
98 position: [position.x, position.y],
99 color: [color.r, color.g, color.b, color.a],
100 size,
101 _padding: [0.0; 3],
102 }
103 }
104
105 pub fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
106 wgpu::VertexBufferLayout {
107 array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
108 step_mode: wgpu::VertexStepMode::Vertex,
109 attributes: &[
110 wgpu::VertexAttribute {
112 offset: 0,
113 shader_location: 0,
114 format: wgpu::VertexFormat::Float32x2,
115 },
116 wgpu::VertexAttribute {
118 offset: std::mem::size_of::<[f32; 2]>() as wgpu::BufferAddress,
119 shader_location: 1,
120 format: wgpu::VertexFormat::Float32x4,
121 },
122 wgpu::VertexAttribute {
124 offset: std::mem::size_of::<[f32; 6]>() as wgpu::BufferAddress,
125 shader_location: 2,
126 format: wgpu::VertexFormat::Float32,
127 },
128 ],
129 }
130 }
131}
132
133pub struct ChartData {
135 pub vertices: Vec<Vertex>,
136 pub viewport_width: f32,
137 pub viewport_height: f32,
138}
139
140impl ChartData {
141 pub fn new(width: f32, height: f32) -> Self {
142 Self {
143 vertices: Vec::new(),
144 viewport_width: width,
145 viewport_height: height,
146 }
147 }
148
149 pub fn add_point(&mut self, point: Point2D, color: Color, size: f32) {
156 self.vertices.push(Vertex::new(point, color, size));
157 }
158
159 pub fn from_scatter(
175 x: &[f32],
176 y: &[f32],
177 color: Option<Color>,
178 size: Option<f32>,
179 width: f32,
180 height: f32,
181 ) -> Self {
182 Self::from_scatter_with_range(x, y, color, size, width, height, None, None)
183 }
184
185 pub fn from_scatter_with_range(
218 x: &[f32],
219 y: &[f32],
220 color: Option<Color>,
221 size: Option<f32>,
222 width: f32,
223 height: f32,
224 x_range: Option<(f32, f32)>,
225 y_range: Option<(f32, f32)>,
226 ) -> Self {
227 let mut data = Self::new(width, height);
228 let color = color.unwrap_or_default();
229 let size = size.unwrap_or(2.0);
230
231 let (x_out_min, x_out_max) = x_range.unwrap_or((-1.0, 1.0));
233 let (y_out_min, y_out_max) = y_range.unwrap_or((-1.0, 1.0));
234
235 let x_min = x.iter().cloned().fold(f32::INFINITY, f32::min);
237 let x_max = x.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
238 let y_min = y.iter().cloned().fold(f32::INFINITY, f32::min);
239 let y_max = y.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
240
241 let x_in_range = x_max - x_min;
242 let y_in_range = y_max - y_min;
243 let x_out_range = x_out_max - x_out_min;
244 let y_out_range = y_out_max - y_out_min;
245
246 for i in 0..x.len().min(y.len()) {
248 let norm_x = ((x[i] - x_min) / x_in_range) * x_out_range + x_out_min;
249 let norm_y = ((y[i] - y_min) / y_in_range) * y_out_range + y_out_min;
250
251 data.add_point(Point2D::new(norm_x, norm_y), color, size);
252 }
253
254 data
255 }
256}