mirror of
https://github.com/alemidev/scope-tui.git
synced 2024-11-23 14:14:48 +01:00
feat: implemented spectroscope
it's not perfect and lower frequency tend to have way higher amplitudes, which is suspicious, but it works!
This commit is contained in:
parent
1b67bfbf75
commit
6e1871d953
6 changed files with 258 additions and 159 deletions
|
@ -19,4 +19,4 @@ libpulse-simple-binding = "2.25"
|
|||
clap = { version = "4.0.32", features = ["derive"] }
|
||||
derive_more = "0.99.17"
|
||||
thiserror = "1.0.48"
|
||||
easyfft = "0.4.0"
|
||||
rustfft = "6.1.0"
|
||||
|
|
288
src/app.rs
288
src/app.rs
|
@ -27,143 +27,6 @@ pub struct App {
|
|||
mode: CurrentDisplayMode,
|
||||
}
|
||||
|
||||
impl App {
|
||||
pub fn run<T : Backend>(&mut self, mut source: impl DataSource, terminal: &mut Terminal<T>) -> Result<(), io::Error> {
|
||||
// prepare globals
|
||||
let fmt = Signed16PCM{}; // TODO some way to choose this?
|
||||
|
||||
let mut fps = 0;
|
||||
let mut framerate = 0;
|
||||
let mut last_poll = Instant::now();
|
||||
let mut channels = vec![];
|
||||
|
||||
loop {
|
||||
let data = source.recv().unwrap();
|
||||
|
||||
if !self.pause {
|
||||
channels = fmt.oscilloscope(data, self.channels);
|
||||
}
|
||||
|
||||
fps += 1;
|
||||
|
||||
if last_poll.elapsed().as_secs() >= 1 {
|
||||
framerate = fps;
|
||||
fps = 0;
|
||||
last_poll = Instant::now();
|
||||
}
|
||||
|
||||
{
|
||||
let display = match self.mode {
|
||||
CurrentDisplayMode::Oscilloscope => &self.oscilloscope as &dyn DisplayMode,
|
||||
CurrentDisplayMode::Vectorscope => &self.vectorscope as &dyn DisplayMode,
|
||||
CurrentDisplayMode::Spectroscope => &self.spectroscope as &dyn DisplayMode,
|
||||
};
|
||||
|
||||
let mut datasets = Vec::new();
|
||||
if self.graph.references {
|
||||
datasets.append(&mut display.references(&self.graph));
|
||||
}
|
||||
datasets.append(&mut display.process(&self.graph, &channels));
|
||||
terminal.draw(|f| {
|
||||
let mut size = f.size();
|
||||
if self.graph.show_ui {
|
||||
f.render_widget(
|
||||
make_header(&self.graph, &display.header(&self.graph), framerate, self.pause),
|
||||
Rect { x: size.x, y: size.y, width: size.width, height:1 } // a 1px line at the top
|
||||
);
|
||||
size.height -= 1;
|
||||
size.y += 1;
|
||||
}
|
||||
let chart = Chart::new(datasets.iter().map(|x| x.into()).collect())
|
||||
.x_axis(display.axis(&self.graph, Dimension::X)) // TODO allow to have axis sometimes?
|
||||
.y_axis(display.axis(&self.graph, Dimension::Y));
|
||||
f.render_widget(chart, size)
|
||||
}).unwrap();
|
||||
}
|
||||
|
||||
while event::poll(Duration::from_millis(0))? { // process all enqueued events
|
||||
let event = event::read()?;
|
||||
|
||||
if self.process_events(event.clone())? { return Ok(()); }
|
||||
self.current_display().handle(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn current_display(&mut self) -> &mut dyn DisplayMode {
|
||||
match self.mode {
|
||||
CurrentDisplayMode::Oscilloscope => &mut self.oscilloscope as &mut dyn DisplayMode,
|
||||
CurrentDisplayMode::Vectorscope => &mut self.vectorscope as &mut dyn DisplayMode,
|
||||
CurrentDisplayMode::Spectroscope => &mut self.spectroscope as &mut dyn DisplayMode,
|
||||
}
|
||||
}
|
||||
|
||||
fn process_events(&mut self, event: Event) -> Result<bool, io::Error> {
|
||||
let mut quit = false;
|
||||
if let Event::Key(key) = event {
|
||||
if let KeyModifiers::CONTROL = key.modifiers {
|
||||
match key.code { // mimic other programs shortcuts to quit, for user friendlyness
|
||||
KeyCode::Char('c') | KeyCode::Char('q') | KeyCode::Char('w') => quit = true,
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
let magnitude = match key.modifiers {
|
||||
KeyModifiers::SHIFT => 10.0,
|
||||
KeyModifiers::CONTROL => 5.0,
|
||||
KeyModifiers::ALT => 0.2,
|
||||
_ => 1.0,
|
||||
};
|
||||
match key.code {
|
||||
KeyCode::Up => update_value_i(&mut self.graph.scale, true, 250, magnitude, 0..32768), // inverted to act as zoom
|
||||
KeyCode::Down => update_value_i(&mut self.graph.scale, false, 250, magnitude, 0..32768), // inverted to act as zoom
|
||||
KeyCode::Right => update_value_i(&mut self.graph.samples, true, 25, magnitude, 0..self.graph.width),
|
||||
KeyCode::Left => update_value_i(&mut self.graph.samples, false, 25, magnitude, 0..self.graph.width),
|
||||
KeyCode::Char('q') => quit = true,
|
||||
KeyCode::Char(' ') => self.pause = !self.pause,
|
||||
KeyCode::Char('s') => self.graph.scatter = !self.graph.scatter,
|
||||
KeyCode::Char('h') => self.graph.show_ui = !self.graph.show_ui,
|
||||
KeyCode::Char('r') => self.graph.references = !self.graph.references,
|
||||
KeyCode::Tab => { // switch modes
|
||||
match self.mode {
|
||||
CurrentDisplayMode::Oscilloscope => self.mode = CurrentDisplayMode::Vectorscope,
|
||||
CurrentDisplayMode::Vectorscope => self.mode = CurrentDisplayMode::Spectroscope,
|
||||
CurrentDisplayMode::Spectroscope => self.mode = CurrentDisplayMode::Oscilloscope,
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
};
|
||||
|
||||
Ok(quit)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_value_f(val: &mut f64, base: f64, magnitude: f64, range: Range<f64>) {
|
||||
let delta = base * magnitude;
|
||||
if *val + delta > range.end {
|
||||
*val = range.end
|
||||
} else if *val + delta < range.start {
|
||||
*val = range.start
|
||||
} else {
|
||||
*val += delta;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_value_i(val: &mut u32, inc: bool, base: u32, magnitude: f64, range: Range<u32>) {
|
||||
let delta = (base as f64 * magnitude) as u32;
|
||||
if inc {
|
||||
if range.end - delta < *val {
|
||||
*val = range.end
|
||||
} else {
|
||||
*val += delta
|
||||
}
|
||||
} else if range.start + delta > *val {
|
||||
*val = range.start
|
||||
} else {
|
||||
*val -= delta
|
||||
}
|
||||
}
|
||||
|
||||
impl From::<&crate::Args> for App {
|
||||
fn from(args: &crate::Args) -> Self {
|
||||
let graph = GraphConfig {
|
||||
|
@ -191,8 +54,13 @@ impl From::<&crate::Args> for App {
|
|||
peaks: args.show_peaks,
|
||||
};
|
||||
|
||||
let vectorscope = Vectorscope {};
|
||||
let spectroscope = Spectroscope {};
|
||||
let vectorscope = Vectorscope::default();
|
||||
let spectroscope = Spectroscope {
|
||||
sampling_rate: args.sample_rate,
|
||||
buffer_size: graph.width,
|
||||
average: 1,
|
||||
buf: Vec::new(),
|
||||
};
|
||||
|
||||
App {
|
||||
graph, oscilloscope, vectorscope, spectroscope,
|
||||
|
@ -203,6 +71,148 @@ impl From::<&crate::Args> for App {
|
|||
}
|
||||
}
|
||||
|
||||
impl App {
|
||||
pub fn run<T : Backend>(&mut self, mut source: impl DataSource, terminal: &mut Terminal<T>) -> Result<(), io::Error> {
|
||||
// prepare globals
|
||||
let fmt = Signed16PCM{}; // TODO some way to choose this?
|
||||
|
||||
let mut fps = 0;
|
||||
let mut framerate = 0;
|
||||
let mut last_poll = Instant::now();
|
||||
let mut channels = vec![];
|
||||
|
||||
loop {
|
||||
let data = source.recv().unwrap();
|
||||
|
||||
if !self.pause {
|
||||
channels = fmt.oscilloscope(data, self.channels);
|
||||
}
|
||||
|
||||
fps += 1;
|
||||
|
||||
if last_poll.elapsed().as_secs() >= 1 {
|
||||
framerate = fps;
|
||||
fps = 0;
|
||||
last_poll = Instant::now();
|
||||
}
|
||||
|
||||
{
|
||||
let mut datasets = Vec::new();
|
||||
let graph = self.graph.clone(); // TODO cheap fix...
|
||||
if self.graph.references {
|
||||
datasets.append(&mut self.current_display_mut().references(&graph));
|
||||
}
|
||||
datasets.append(&mut self.current_display_mut().process(&graph, &channels));
|
||||
terminal.draw(|f| {
|
||||
let mut size = f.size();
|
||||
if self.graph.show_ui {
|
||||
f.render_widget(
|
||||
make_header(&self.graph, &self.current_display().header(&self.graph), framerate, self.pause),
|
||||
Rect { x: size.x, y: size.y, width: size.width, height:1 } // a 1px line at the top
|
||||
);
|
||||
size.height -= 1;
|
||||
size.y += 1;
|
||||
}
|
||||
let chart = Chart::new(datasets.iter().map(|x| x.into()).collect())
|
||||
.x_axis(self.current_display().axis(&self.graph, Dimension::X)) // TODO allow to have axis sometimes?
|
||||
.y_axis(self.current_display().axis(&self.graph, Dimension::Y));
|
||||
f.render_widget(chart, size)
|
||||
}).unwrap();
|
||||
}
|
||||
|
||||
while event::poll(Duration::from_millis(0))? { // process all enqueued events
|
||||
let event = event::read()?;
|
||||
|
||||
if self.process_events(event.clone())? { return Ok(()); }
|
||||
self.current_display_mut().handle(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn current_display_mut(&mut self) -> &mut dyn DisplayMode {
|
||||
match self.mode {
|
||||
CurrentDisplayMode::Oscilloscope => &mut self.oscilloscope as &mut dyn DisplayMode,
|
||||
CurrentDisplayMode::Vectorscope => &mut self.vectorscope as &mut dyn DisplayMode,
|
||||
CurrentDisplayMode::Spectroscope => &mut self.spectroscope as &mut dyn DisplayMode,
|
||||
}
|
||||
}
|
||||
|
||||
fn current_display(&self) -> &dyn DisplayMode {
|
||||
match self.mode {
|
||||
CurrentDisplayMode::Oscilloscope => &self.oscilloscope as &dyn DisplayMode,
|
||||
CurrentDisplayMode::Vectorscope => &self.vectorscope as &dyn DisplayMode,
|
||||
CurrentDisplayMode::Spectroscope => &self.spectroscope as &dyn DisplayMode,
|
||||
}
|
||||
}
|
||||
|
||||
fn process_events(&mut self, event: Event) -> Result<bool, io::Error> {
|
||||
let mut quit = false;
|
||||
if let Event::Key(key) = event {
|
||||
if let KeyModifiers::CONTROL = key.modifiers {
|
||||
match key.code { // mimic other programs shortcuts to quit, for user friendlyness
|
||||
KeyCode::Char('c') | KeyCode::Char('q') | KeyCode::Char('w') => quit = true,
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
let magnitude = match key.modifiers {
|
||||
KeyModifiers::SHIFT => 10.0,
|
||||
KeyModifiers::CONTROL => 5.0,
|
||||
KeyModifiers::ALT => 0.2,
|
||||
_ => 1.0,
|
||||
};
|
||||
match key.code {
|
||||
KeyCode::Up => update_value_i(&mut self.graph.scale, true, 250, magnitude, 0..32768), // inverted to act as zoom
|
||||
KeyCode::Down => update_value_i(&mut self.graph.scale, false, 250, magnitude, 0..32768), // inverted to act as zoom
|
||||
KeyCode::Right => update_value_i(&mut self.graph.samples, true, 25, magnitude, 0..self.graph.width*10),
|
||||
KeyCode::Left => update_value_i(&mut self.graph.samples, false, 25, magnitude, 0..self.graph.width*10),
|
||||
KeyCode::Char('q') => quit = true,
|
||||
KeyCode::Char(' ') => self.pause = !self.pause,
|
||||
KeyCode::Char('s') => self.graph.scatter = !self.graph.scatter,
|
||||
KeyCode::Char('h') => self.graph.show_ui = !self.graph.show_ui,
|
||||
KeyCode::Char('r') => self.graph.references = !self.graph.references,
|
||||
KeyCode::Tab => { // switch modes
|
||||
match self.mode {
|
||||
CurrentDisplayMode::Oscilloscope => self.mode = CurrentDisplayMode::Vectorscope,
|
||||
CurrentDisplayMode::Vectorscope => self.mode = CurrentDisplayMode::Spectroscope,
|
||||
CurrentDisplayMode::Spectroscope => self.mode = CurrentDisplayMode::Oscilloscope,
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
};
|
||||
|
||||
Ok(quit)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO can these be removed or merged somewhere else?
|
||||
|
||||
pub fn update_value_f(val: &mut f64, base: f64, magnitude: f64, range: Range<f64>) {
|
||||
let delta = base * magnitude;
|
||||
if *val + delta > range.end {
|
||||
*val = range.end
|
||||
} else if *val + delta < range.start {
|
||||
*val = range.start
|
||||
} else {
|
||||
*val += delta;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_value_i(val: &mut u32, inc: bool, base: u32, magnitude: f64, range: Range<u32>) {
|
||||
let delta = (base as f64 * magnitude) as u32;
|
||||
if inc {
|
||||
if range.end - delta < *val {
|
||||
*val = range.end
|
||||
} else {
|
||||
*val += delta
|
||||
}
|
||||
} else if range.start + delta > *val {
|
||||
*val = range.start
|
||||
} else {
|
||||
*val -= delta
|
||||
}
|
||||
}
|
||||
|
||||
fn make_header<'a>(cfg: &GraphConfig, module_header: &'a str, fps: usize, pause: bool) -> Table<'a> {
|
||||
Table::new(
|
||||
vec![
|
||||
|
|
|
@ -9,6 +9,7 @@ pub enum Dimension {
|
|||
X, Y
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GraphConfig {
|
||||
pub samples: u32,
|
||||
pub scale: u32,
|
||||
|
@ -32,7 +33,7 @@ impl GraphConfig {
|
|||
pub trait DisplayMode {
|
||||
// MUST define
|
||||
fn axis(&self, cfg: &GraphConfig, dimension: Dimension) -> Axis; // TODO simplify this
|
||||
fn process(&self, cfg: &GraphConfig, data: &Vec<Vec<f64>>) -> Vec<DataSet>;
|
||||
fn process(&mut self, cfg: &GraphConfig, data: &Vec<Vec<f64>>) -> Vec<DataSet>;
|
||||
|
||||
// SHOULD override
|
||||
fn handle(&mut self, _event: Event) {}
|
||||
|
|
|
@ -5,6 +5,7 @@ use crate::app::update_value_f;
|
|||
|
||||
use super::{DisplayMode, GraphConfig, DataSet, Dimension};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Oscilloscope {
|
||||
pub triggering: bool,
|
||||
pub falling_edge: bool,
|
||||
|
@ -47,7 +48,7 @@ impl DisplayMode for Oscilloscope {
|
|||
a.style(Style::default().fg(cfg.axis_color)).bounds(bounds)
|
||||
}
|
||||
|
||||
fn process(&self, cfg: &GraphConfig, data: &Vec<Vec<f64>>) -> Vec<DataSet> {
|
||||
fn process(&mut self, cfg: &GraphConfig, data: &Vec<Vec<f64>>) -> Vec<DataSet> {
|
||||
let mut out = Vec::new();
|
||||
|
||||
let mut trigger_offset = 0;
|
||||
|
|
|
@ -1,24 +1,53 @@
|
|||
use std::collections::VecDeque;
|
||||
|
||||
use crossterm::event::{Event, KeyCode};
|
||||
use ratatui::{widgets::{Axis, GraphType}, style::Style, text::Span};
|
||||
|
||||
use crate::app::update_value_i;
|
||||
|
||||
use super::{DisplayMode, GraphConfig, DataSet, Dimension};
|
||||
|
||||
use easyfft::prelude::*;
|
||||
use rustfft::{FftPlanner, num_complex::Complex};
|
||||
|
||||
pub struct Spectroscope {}
|
||||
#[derive(Default)]
|
||||
pub struct Spectroscope {
|
||||
pub sampling_rate: u32,
|
||||
pub buffer_size: u32,
|
||||
pub average: u32,
|
||||
pub buf: Vec<VecDeque<Vec<f64>>>,
|
||||
}
|
||||
|
||||
fn complex_to_magnitude(c: Complex<f64>) -> f64 {
|
||||
let squared = (c.re * c.re) + (c.im * c.im);
|
||||
squared.sqrt()
|
||||
}
|
||||
|
||||
impl DisplayMode for Spectroscope {
|
||||
fn channel_name(&self, index: usize) -> String {
|
||||
format!("{}", index)
|
||||
match index {
|
||||
0 => "L".into(),
|
||||
1 => "R".into(),
|
||||
_ => format!("{}", index),
|
||||
}
|
||||
}
|
||||
|
||||
fn header(&self, _: &GraphConfig) -> String {
|
||||
"live".into()
|
||||
if self.average <= 1 {
|
||||
format!("live -- {:.3}Hz buckets", self.sampling_rate as f64 / self.buffer_size as f64)
|
||||
} else {
|
||||
format!(
|
||||
"{}x average ({:.1}s) -- {:.3}Hz buckets",
|
||||
self.average,
|
||||
(self.average * self.buffer_size) as f64 / self.sampling_rate as f64,
|
||||
self.sampling_rate as f64 / (self.buffer_size * self.average) as f64
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn axis(&self, cfg: &GraphConfig, dimension: Dimension) -> Axis {
|
||||
let (name, bounds) = match dimension {
|
||||
Dimension::X => ("frequency -", [-(cfg.scale as f64), cfg.scale as f64]),
|
||||
Dimension::Y => ("| level", [-(cfg.scale as f64), cfg.scale as f64]),
|
||||
Dimension::X => ("frequency -", [20.0f64.ln(), ((cfg.samples as f64 / cfg.width as f64) * 20000.0).ln()]),
|
||||
Dimension::Y => ("| level", [0.0, cfg.scale as f64 / 10.0]), // TODO super arbitraty! wtf
|
||||
};
|
||||
let mut a = Axis::default();
|
||||
if cfg.show_ui { // TODO don't make it necessary to check show_ui inside here
|
||||
|
@ -27,21 +56,31 @@ impl DisplayMode for Spectroscope {
|
|||
a.style(Style::default().fg(cfg.axis_color)).bounds(bounds)
|
||||
}
|
||||
|
||||
fn references(&self, cfg: &GraphConfig) -> Vec<DataSet> {
|
||||
vec![
|
||||
DataSet::new("".into(), vec![(0.0, 0.0), (20000.0, 0.0)], cfg.marker_type, GraphType::Line, cfg.axis_color),
|
||||
DataSet::new("".into(), vec![(0.0, 0.0), (0.0, cfg.scale as f64)], cfg.marker_type, GraphType::Line, cfg.axis_color),
|
||||
]
|
||||
}
|
||||
fn process(&mut self, cfg: &GraphConfig, data: &Vec<Vec<f64>>) -> Vec<DataSet> {
|
||||
for (i, chan) in data.iter().enumerate() {
|
||||
if self.buf.len() <= i {
|
||||
self.buf.push(VecDeque::new());
|
||||
}
|
||||
self.buf[i].push_back(chan.clone());
|
||||
while self.buf[i].len() > self.average as usize {
|
||||
self.buf[i].pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
fn process(&self, cfg: &GraphConfig, data: &Vec<Vec<f64>>) -> Vec<DataSet> {
|
||||
let mut out = Vec::new();
|
||||
let mut planner: FftPlanner<f64> = FftPlanner::new();
|
||||
let sample_len = self.buffer_size * self.average;
|
||||
let resolution = self.sampling_rate as f64 / sample_len as f64;
|
||||
let fft = planner.plan_fft_forward(sample_len as usize);
|
||||
|
||||
for (n, chunk) in data.iter().enumerate() {
|
||||
let tmp = chunk.real_fft().iter().map(|x| (x.re, x.im)).collect();
|
||||
for (n, chan_queue) in self.buf.iter().enumerate().rev() {
|
||||
let chunk = chan_queue.iter().flatten().collect::<Vec<&f64>>();
|
||||
let max_val = chunk.iter().max_by(|a, b| a.total_cmp(b)).expect("empty dataset?");
|
||||
let mut tmp : Vec<Complex<f64>> = chunk.iter().map(|x| Complex { re: *x / *max_val, im: 0.0 }).collect();
|
||||
fft.process(tmp.as_mut_slice());
|
||||
out.push(DataSet::new(
|
||||
self.channel_name(n),
|
||||
tmp,
|
||||
tmp[..=tmp.len() / 2].iter().enumerate().map(|(i,x)| ((i as f64 * resolution).ln(), complex_to_magnitude(*x))).collect(),
|
||||
cfg.marker_type,
|
||||
if cfg.scatter { GraphType::Scatter } else { GraphType::Line },
|
||||
cfg.palette(n),
|
||||
|
@ -50,4 +89,51 @@ impl DisplayMode for Spectroscope {
|
|||
|
||||
out
|
||||
}
|
||||
|
||||
fn handle(&mut self, event: Event) {
|
||||
if let Event::Key(key) = event {
|
||||
match key.code {
|
||||
KeyCode::PageUp => update_value_i(&mut self.average, true, 1, 1., 1..65535),
|
||||
KeyCode::PageDown => update_value_i(&mut self.average, false, 1, 1., 1..65535),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn references(&self, cfg: &GraphConfig) -> Vec<DataSet> {
|
||||
let s = cfg.scale as f64 / 10.0;
|
||||
vec![
|
||||
DataSet::new("".into(), vec![(0.0, 0.0), ((cfg.samples as f64).ln(), 0.0)], cfg.marker_type, GraphType::Line, cfg.axis_color),
|
||||
|
||||
// TODO can we auto generate these? lol...
|
||||
DataSet::new("".into(), vec![(20.0f64.ln(), 0.0), (20.0f64.ln(), s)], cfg.marker_type, GraphType::Line, cfg.axis_color),
|
||||
DataSet::new("".into(), vec![(30.0f64.ln(), 0.0), (30.0f64.ln(), s)], cfg.marker_type, GraphType::Line, cfg.axis_color),
|
||||
DataSet::new("".into(), vec![(40.0f64.ln(), 0.0), (40.0f64.ln(), s)], cfg.marker_type, GraphType::Line, cfg.axis_color),
|
||||
DataSet::new("".into(), vec![(50.0f64.ln(), 0.0), (50.0f64.ln(), s)], cfg.marker_type, GraphType::Line, cfg.axis_color),
|
||||
DataSet::new("".into(), vec![(60.0f64.ln(), 0.0), (60.0f64.ln(), s)], cfg.marker_type, GraphType::Line, cfg.axis_color),
|
||||
DataSet::new("".into(), vec![(70.0f64.ln(), 0.0), (70.0f64.ln(), s)], cfg.marker_type, GraphType::Line, cfg.axis_color),
|
||||
DataSet::new("".into(), vec![(80.0f64.ln(), 0.0), (80.0f64.ln(), s)], cfg.marker_type, GraphType::Line, cfg.axis_color),
|
||||
DataSet::new("".into(), vec![(90.0f64.ln(), 0.0), (90.0f64.ln(), s)], cfg.marker_type, GraphType::Line, cfg.axis_color),
|
||||
DataSet::new("".into(), vec![(100.0f64.ln(), 0.0), (100.0f64.ln(), s)], cfg.marker_type, GraphType::Line, cfg.axis_color),
|
||||
DataSet::new("".into(), vec![(200.0f64.ln(), 0.0), (200.0f64.ln(), s)], cfg.marker_type, GraphType::Line, cfg.axis_color),
|
||||
DataSet::new("".into(), vec![(300.0f64.ln(), 0.0), (300.0f64.ln(), s)], cfg.marker_type, GraphType::Line, cfg.axis_color),
|
||||
DataSet::new("".into(), vec![(400.0f64.ln(), 0.0), (400.0f64.ln(), s)], cfg.marker_type, GraphType::Line, cfg.axis_color),
|
||||
DataSet::new("".into(), vec![(500.0f64.ln(), 0.0), (500.0f64.ln(), s)], cfg.marker_type, GraphType::Line, cfg.axis_color),
|
||||
DataSet::new("".into(), vec![(600.0f64.ln(), 0.0), (600.0f64.ln(), s)], cfg.marker_type, GraphType::Line, cfg.axis_color),
|
||||
DataSet::new("".into(), vec![(700.0f64.ln(), 0.0), (700.0f64.ln(), s)], cfg.marker_type, GraphType::Line, cfg.axis_color),
|
||||
DataSet::new("".into(), vec![(800.0f64.ln(), 0.0), (800.0f64.ln(), s)], cfg.marker_type, GraphType::Line, cfg.axis_color),
|
||||
DataSet::new("".into(), vec![(900.0f64.ln(), 0.0), (900.0f64.ln(), s)], cfg.marker_type, GraphType::Line, cfg.axis_color),
|
||||
DataSet::new("".into(), vec![(1000.0f64.ln(), 0.0), (1000.0f64.ln(), s)], cfg.marker_type, GraphType::Line, cfg.axis_color),
|
||||
DataSet::new("".into(), vec![(2000.0f64.ln(), 0.0), (2000.0f64.ln(), s)], cfg.marker_type, GraphType::Line, cfg.axis_color),
|
||||
DataSet::new("".into(), vec![(3000.0f64.ln(), 0.0), (3000.0f64.ln(), s)], cfg.marker_type, GraphType::Line, cfg.axis_color),
|
||||
DataSet::new("".into(), vec![(4000.0f64.ln(), 0.0), (4000.0f64.ln(), s)], cfg.marker_type, GraphType::Line, cfg.axis_color),
|
||||
DataSet::new("".into(), vec![(5000.0f64.ln(), 0.0), (5000.0f64.ln(), s)], cfg.marker_type, GraphType::Line, cfg.axis_color),
|
||||
DataSet::new("".into(), vec![(6000.0f64.ln(), 0.0), (6000.0f64.ln(), s)], cfg.marker_type, GraphType::Line, cfg.axis_color),
|
||||
DataSet::new("".into(), vec![(7000.0f64.ln(), 0.0), (7000.0f64.ln(), s)], cfg.marker_type, GraphType::Line, cfg.axis_color),
|
||||
DataSet::new("".into(), vec![(8000.0f64.ln(), 0.0), (8000.0f64.ln(), s)], cfg.marker_type, GraphType::Line, cfg.axis_color),
|
||||
DataSet::new("".into(), vec![(9000.0f64.ln(), 0.0), (9000.0f64.ln(), s)], cfg.marker_type, GraphType::Line, cfg.axis_color),
|
||||
DataSet::new("".into(), vec![(10000.0f64.ln(), 0.0), (10000.0f64.ln(), s)], cfg.marker_type, GraphType::Line, cfg.axis_color),
|
||||
DataSet::new("".into(), vec![(20000.0f64.ln(), 0.0), (20000.0f64.ln(), s)], cfg.marker_type, GraphType::Line, cfg.axis_color),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ use ratatui::{widgets::{Axis, GraphType}, style::Style, text::Span};
|
|||
|
||||
use super::{DisplayMode, GraphConfig, DataSet, Dimension};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Vectorscope {}
|
||||
|
||||
impl DisplayMode for Vectorscope {
|
||||
|
@ -32,7 +33,7 @@ impl DisplayMode for Vectorscope {
|
|||
]
|
||||
}
|
||||
|
||||
fn process(&self, cfg: &GraphConfig, data: &Vec<Vec<f64>>) -> Vec<DataSet> {
|
||||
fn process(&mut self, cfg: &GraphConfig, data: &Vec<Vec<f64>>) -> Vec<DataSet> {
|
||||
let mut out = Vec::new();
|
||||
|
||||
for (n, chunk) in data.chunks(2).enumerate() {
|
||||
|
|
Loading…
Reference in a new issue