1use crate::render::Cell;
26use crate::style::Color;
27use crate::widget::traits::{RenderContext, View, WidgetProps};
28use crate::{impl_props_builders, impl_styled_view};
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
32pub enum WaveStyle {
33 #[default]
35 Line,
36 Filled,
38 Mirrored,
40 Bars,
42 Dots,
44 Smooth,
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
50pub enum Interpolation {
51 #[default]
53 Linear,
54 Bezier,
56 CatmullRom,
58 Step,
60}
61
62#[derive(Debug, Clone)]
64pub struct Waveline {
65 data: Vec<f64>,
67 style: WaveStyle,
69 interpolation: Interpolation,
71 color: Color,
73 gradient_color: Option<Color>,
75 baseline: f64,
77 amplitude: f64,
79 show_baseline: bool,
81 baseline_color: Color,
83 bg_color: Option<Color>,
85 height: Option<u16>,
87 max_points: Option<usize>,
89 label: Option<String>,
91 props: WidgetProps,
93}
94
95impl Default for Waveline {
96 fn default() -> Self {
97 Self::new(Vec::new())
98 }
99}
100
101impl Waveline {
102 pub fn new(data: Vec<f64>) -> Self {
104 Self {
105 data,
106 style: WaveStyle::Line,
107 interpolation: Interpolation::Linear,
108 color: Color::CYAN,
109 gradient_color: None,
110 baseline: 0.5,
111 amplitude: 1.0,
112 show_baseline: false,
113 baseline_color: Color::rgb(80, 80, 80),
114 bg_color: None,
115 height: None,
116 max_points: None,
117 label: None,
118 props: WidgetProps::new(),
119 }
120 }
121
122 pub fn data(mut self, data: Vec<f64>) -> Self {
124 self.data = data;
125 self
126 }
127
128 pub fn style(mut self, style: WaveStyle) -> Self {
130 self.style = style;
131 self
132 }
133
134 pub fn interpolation(mut self, method: Interpolation) -> Self {
136 self.interpolation = method;
137 self
138 }
139
140 pub fn color(mut self, color: Color) -> Self {
142 self.color = color;
143 self
144 }
145
146 pub fn gradient(mut self, start: Color, end: Color) -> Self {
148 self.color = start;
149 self.gradient_color = Some(end);
150 self
151 }
152
153 pub fn baseline(mut self, position: f64) -> Self {
155 self.baseline = position.clamp(0.0, 1.0);
156 self
157 }
158
159 pub fn amplitude(mut self, amp: f64) -> Self {
161 self.amplitude = amp;
162 self
163 }
164
165 pub fn show_baseline(mut self, show: bool) -> Self {
167 self.show_baseline = show;
168 self
169 }
170
171 pub fn baseline_color(mut self, color: Color) -> Self {
173 self.baseline_color = color;
174 self
175 }
176
177 pub fn bg(mut self, color: Color) -> Self {
179 self.bg_color = Some(color);
180 self
181 }
182
183 pub fn height(mut self, height: u16) -> Self {
185 self.height = Some(height);
186 self
187 }
188
189 pub fn max_points(mut self, max: usize) -> Self {
191 self.max_points = Some(max);
192 self
193 }
194
195 pub fn label(mut self, label: impl Into<String>) -> Self {
197 self.label = Some(label.into());
198 self
199 }
200
201 fn get_color(&self, ratio: f64) -> Color {
202 if let Some(end) = self.gradient_color {
203 let r = (self.color.r as f64 * (1.0 - ratio) + end.r as f64 * ratio) as u8;
204 let g = (self.color.g as f64 * (1.0 - ratio) + end.g as f64 * ratio) as u8;
205 let b = (self.color.b as f64 * (1.0 - ratio) + end.b as f64 * ratio) as u8;
206 Color::rgb(r, g, b)
207 } else {
208 self.color
209 }
210 }
211
212 fn get_interpolated_value(&self, data: &[f64], x: usize, width: usize) -> f64 {
213 if data.is_empty() {
214 return 0.0;
215 }
216 let ratio = x as f64 / (width - 1).max(1) as f64;
217 let idx = ratio * (data.len() - 1) as f64;
218 let idx_floor = idx.floor() as usize;
219 let idx_ceil = (idx_floor + 1).min(data.len() - 1);
220 let t = idx - idx_floor as f64;
221
222 match self.interpolation {
223 Interpolation::Linear => data[idx_floor] * (1.0 - t) + data[idx_ceil] * t,
224 Interpolation::Step => data[idx_floor],
225 Interpolation::Bezier | Interpolation::CatmullRom => {
226 let p0_idx = idx_floor.saturating_sub(1);
227 let p3_idx = (idx_ceil + 1).min(data.len() - 1);
228
229 let p0 = data[p0_idx];
230 let p1 = data[idx_floor];
231 let p2 = data[idx_ceil];
232 let p3 = data[p3_idx];
233
234 let t2 = t * t;
235 let t3 = t2 * t;
236
237 0.5 * ((2.0 * p1)
238 + (-p0 + p2) * t
239 + (2.0 * p0 - 5.0 * p1 + 4.0 * p2 - p3) * t2
240 + (-p0 + 3.0 * p1 - 3.0 * p2 + p3) * t3)
241 }
242 }
243 }
244}
245
246impl View for Waveline {
247 fn render(&self, ctx: &mut RenderContext) {
248 let area = ctx.area;
249 let height = self.height.unwrap_or(area.height);
250
251 if area.width < 2 || height < 1 {
252 return;
253 }
254
255 let mut chart_y = area.y;
256 let mut chart_height = height.min(area.height);
257
258 if let Some(bg) = self.bg_color {
260 for y in area.y..area.y + chart_height {
261 for x in area.x..area.x + area.width {
262 let mut cell = Cell::new(' ');
263 cell.bg = Some(bg);
264 ctx.buffer.set(x, y, cell);
265 }
266 }
267 }
268
269 if let Some(ref label) = self.label {
271 ctx.buffer
272 .put_str_styled(area.x, chart_y, label, Some(Color::WHITE), self.bg_color);
273 chart_y += 1;
274 chart_height = chart_height.saturating_sub(1);
275 }
276
277 if chart_height < 1 || self.data.is_empty() {
278 return;
279 }
280
281 let data = if let Some(max) = self.max_points {
283 if self.data.len() > max {
284 &self.data[self.data.len() - max..]
285 } else {
286 &self.data[..]
287 }
288 } else {
289 &self.data[..]
290 };
291
292 let width = area.width as usize;
293
294 if self.show_baseline {
296 let baseline_row = ((1.0 - self.baseline) * (chart_height - 1) as f64) as u16;
297 let y = chart_y + baseline_row;
298 for x in area.x..area.x + area.width {
299 let mut cell = Cell::new('─');
300 cell.fg = Some(self.baseline_color);
301 ctx.buffer.set(x, y, cell);
302 }
303 }
304
305 match self.style {
306 WaveStyle::Line | WaveStyle::Smooth => {
307 for x in 0..width {
308 let val = (self.get_interpolated_value(data, x, width) * self.amplitude)
309 .clamp(-1.0, 1.0);
310 let y_ratio = self.baseline + val * (1.0 - self.baseline);
311 let y = chart_y + ((1.0 - y_ratio) * (chart_height - 1) as f64) as u16;
312
313 if y >= chart_y && y < chart_y + chart_height {
314 let screen_x = area.x + x as u16;
315 let mut cell = Cell::new('●');
316 cell.fg = Some(self.get_color(y_ratio));
317 ctx.buffer.set(screen_x, y, cell);
318 }
319 }
320 }
321 WaveStyle::Filled => {
322 let baseline_row = ((1.0 - self.baseline) * (chart_height - 1) as f64) as u16;
323
324 for x in 0..width {
325 let val = (self.get_interpolated_value(data, x, width) * self.amplitude)
326 .clamp(-1.0, 1.0);
327 let y_ratio = self.baseline + val * (1.0 - self.baseline);
328 let y = ((1.0 - y_ratio) * (chart_height - 1) as f64) as u16;
329
330 let screen_x = area.x + x as u16;
331
332 let (start_y, end_y) = if y <= baseline_row {
333 (y, baseline_row)
334 } else {
335 (baseline_row, y)
336 };
337
338 for dy in start_y..=end_y {
339 if dy < chart_height {
340 let screen_y = chart_y + dy;
341 let ch = if dy == y { '█' } else { '▓' };
342 let ratio = 1.0 - dy as f64 / (chart_height - 1) as f64;
343 let mut cell = Cell::new(ch);
344 cell.fg = Some(self.get_color(ratio));
345 ctx.buffer.set(screen_x, screen_y, cell);
346 }
347 }
348 }
349 }
350 WaveStyle::Mirrored => {
351 let center_y = chart_height / 2;
352
353 for x in 0..width {
354 let val = (self.get_interpolated_value(data, x, width).abs() * self.amplitude)
355 .clamp(0.0, 1.0);
356 let half_height = (val * center_y as f64) as u16;
357
358 let screen_x = area.x + x as u16;
359
360 for dy in 0..=half_height {
362 let screen_y = chart_y + center_y.saturating_sub(dy);
363 if screen_y >= chart_y {
364 let intensity = 1.0 - dy as f64 / center_y as f64;
365 let ch = if dy == half_height { '▀' } else { '█' };
366 let mut cell = Cell::new(ch);
367 cell.fg = Some(self.get_color(0.5 + intensity * 0.5));
368 ctx.buffer.set(screen_x, screen_y, cell);
369 }
370 }
371
372 for dy in 0..=half_height {
374 let screen_y = chart_y + center_y + dy;
375 if screen_y < chart_y + chart_height {
376 let intensity = 1.0 - dy as f64 / center_y as f64;
377 let ch = if dy == half_height { '▄' } else { '█' };
378 let mut cell = Cell::new(ch);
379 cell.fg = Some(self.get_color(0.5 + intensity * 0.5));
380 ctx.buffer.set(screen_x, screen_y, cell);
381 }
382 }
383 }
384 }
385 WaveStyle::Bars => {
386 let baseline_row = ((1.0 - self.baseline) * (chart_height - 1) as f64) as u16;
387 let bar_chars = ['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'];
388
389 for x in 0..width {
390 let val = (self.get_interpolated_value(data, x, width) * self.amplitude)
391 .clamp(-1.0, 1.0);
392 let y_ratio = self.baseline + val * (1.0 - self.baseline);
393 let target_y = ((1.0 - y_ratio) * (chart_height - 1) as f64) as u16;
394
395 let screen_x = area.x + x as u16;
396
397 if val >= 0.0 {
398 for dy in target_y..=baseline_row {
399 if dy < chart_height {
400 let screen_y = chart_y + dy;
401 let ch = if dy == target_y {
402 let frac = (y_ratio * 8.0).fract();
403 bar_chars[(frac * 8.0) as usize % 8]
404 } else {
405 '█'
406 };
407 let mut cell = Cell::new(ch);
408 cell.fg = Some(self.get_color(y_ratio));
409 ctx.buffer.set(screen_x, screen_y, cell);
410 }
411 }
412 } else {
413 for dy in baseline_row..=target_y {
414 if dy < chart_height {
415 let screen_y = chart_y + dy;
416 let ch = if dy == target_y {
417 let frac = 1.0 - (y_ratio * 8.0).fract();
418 bar_chars[(frac * 8.0) as usize % 8]
419 } else {
420 '█'
421 };
422 let mut cell = Cell::new(ch);
423 cell.fg = Some(self.get_color(y_ratio));
424 ctx.buffer.set(screen_x, screen_y, cell);
425 }
426 }
427 }
428 }
429 }
430 WaveStyle::Dots => {
431 for x in 0..width {
432 let val = (self.get_interpolated_value(data, x, width) * self.amplitude)
433 .clamp(-1.0, 1.0);
434 let y_ratio = self.baseline + val * (1.0 - self.baseline);
435 let y = chart_y + ((1.0 - y_ratio) * (chart_height - 1) as f64) as u16;
436
437 if y >= chart_y && y < chart_y + chart_height {
438 let screen_x = area.x + x as u16;
439 let mut cell = Cell::new('⣿');
440 cell.fg = Some(self.get_color(y_ratio));
441 ctx.buffer.set(screen_x, y, cell);
442 }
443 }
444 }
445 }
446 }
447
448 crate::impl_view_meta!("Waveline");
449}
450
451impl_styled_view!(Waveline);
452impl_props_builders!(Waveline);
453
454pub fn waveline(data: Vec<f64>) -> Waveline {
458 Waveline::new(data)
459}
460
461pub fn audio_waveform(samples: Vec<f64>) -> Waveline {
463 Waveline::new(samples)
464 .style(WaveStyle::Mirrored)
465 .color(Color::CYAN)
466 .gradient(Color::BLUE, Color::CYAN)
467}
468
469pub fn signal_wave(data: Vec<f64>) -> Waveline {
471 Waveline::new(data)
472 .style(WaveStyle::Line)
473 .interpolation(Interpolation::CatmullRom)
474 .color(Color::GREEN)
475 .show_baseline(true)
476}
477
478pub fn area_wave(data: Vec<f64>) -> Waveline {
480 Waveline::new(data)
481 .style(WaveStyle::Filled)
482 .color(Color::MAGENTA)
483 .baseline(1.0)
484}
485
486pub fn spectrum(data: Vec<f64>) -> Waveline {
488 Waveline::new(data)
489 .style(WaveStyle::Bars)
490 .color(Color::YELLOW)
491 .baseline(1.0)
492}
493
494pub fn sine_wave(samples: usize, frequency: f64, amplitude: f64) -> Vec<f64> {
496 (0..samples)
497 .map(|i| {
498 let t = i as f64 / samples as f64 * std::f64::consts::PI * 2.0 * frequency;
499 t.sin() * amplitude
500 })
501 .collect()
502}
503
504pub fn square_wave(samples: usize, frequency: f64, amplitude: f64) -> Vec<f64> {
506 (0..samples)
507 .map(|i| {
508 let t = i as f64 / samples as f64 * frequency;
509 if t.fract() < 0.5 {
510 amplitude
511 } else {
512 -amplitude
513 }
514 })
515 .collect()
516}
517
518pub fn sawtooth_wave(samples: usize, frequency: f64, amplitude: f64) -> Vec<f64> {
520 (0..samples)
521 .map(|i| {
522 let t = i as f64 / samples as f64 * frequency;
523 (t.fract() * 2.0 - 1.0) * amplitude
524 })
525 .collect()
526}
527
528#[cfg(test)]
529mod tests {
530 use super::*;
531
532 #[test]
533 fn test_waveline_creation() {
534 let data = vec![0.0, 0.5, 1.0, 0.5, 0.0];
535 let wave = waveline(data.clone());
536
537 assert_eq!(wave.data, data);
538 }
539
540 #[test]
541 fn test_waveline_styles() {
542 let data = vec![0.5; 10];
543 let wave = waveline(data)
544 .style(WaveStyle::Mirrored)
545 .color(Color::RED)
546 .amplitude(0.8);
547
548 assert_eq!(wave.style, WaveStyle::Mirrored);
549 assert_eq!(wave.color, Color::RED);
550 assert_eq!(wave.amplitude, 0.8);
551 }
552
553 #[test]
554 fn test_sine_wave_generation() {
555 let data = sine_wave(100, 2.0, 1.0);
556 assert_eq!(data.len(), 100);
557 assert!(data.iter().all(|&v| v >= -1.0 && v <= 1.0));
558 }
559
560 #[test]
561 fn test_interpolation() {
562 let wave = waveline(vec![0.0, 1.0, 0.0]).interpolation(Interpolation::CatmullRom);
563
564 assert_eq!(wave.interpolation, Interpolation::CatmullRom);
565 }
566}