use crossterm::{
event::{self, KeyCode},
execute,
terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode},
};
use ratatui::{
Terminal,
backend::CrosstermBackend,
layout::Rect,
style::{Color, Style},
symbols,
widgets::{Axis, Chart, Dataset},
};
use std::{
io::{Result, stdout},
process,
time::Duration,
};
use lorenz_cli::lorenz;
const SIGMA: f64 = 10.0;
const RHO: f64 = 28.0;
const BETA: f64 = 8.0 / 3.0;
fn main() -> Result<()> {
enable_raw_mode()?;
let mut stdout = stdout();
execute!(stdout, EnterAlternateScreen)?;
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
let axis_choice = std::env::args().nth(1).unwrap_or_else(|| "xy".to_string());
let dt = 0.01;
let num_steps = 10000;
'lorenz_attractor: loop {
let mut x = 6.0;
let mut y = 1.0;
let mut z = 1.05;
let mut points: Vec<(f64, f64)> = Vec::new();
let mut step = 0;
let mut x_min = f64::MAX;
let mut x_max = f64::MIN;
let mut y_min = f64::MAX;
let mut y_max = f64::MIN;
while step < num_steps {
let (dx, dy, dz) = lorenz(x, y, z, SIGMA, RHO, BETA).unwrap_or_else(|err| {
disable_raw_mode().ok();
execute!(terminal.backend_mut(), LeaveAlternateScreen).ok();
eprintln!("{:?}", err);
process::exit(1);
});
if dx.abs() > 1000.0 || dy.abs() > 1000.0 || dz.abs() > 1000.0 {
disable_raw_mode()?;
execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
eprintln!("Diverging Lorenz Attractor! Exiting...");
process::exit(1);
}
x += dx * dt;
y += dy * dt;
z += dz * dt;
let point = match axis_choice.as_str() {
"xy" => (x, y),
"xz" => (x, z),
"yz" => (y, z),
_ => (x, y),
};
x_min = x_min.min(point.0);
x_max = x_max.max(point.0);
y_min = y_min.min(point.1);
y_max = y_max.max(point.1);
points.push(point);
step += 1;
terminal.draw(|f| {
let size = f.area();
let chart_area = Rect {
x: 0,
y: 0,
width: size.width,
height: size.height,
};
let dataset = Dataset::default()
.marker(symbols::Marker::Braille)
.style(Style::default().fg(Color::Cyan))
.data(&points);
let chart = Chart::new(vec![dataset])
.x_axis(
Axis::default()
.bounds([x_min, x_max]),
)
.y_axis(
Axis::default()
.bounds([y_min, y_max]),
);
f.render_widget(chart, chart_area);
})?;
if event::poll(Duration::from_millis(10))? {
if let event::Event::Key(key) = event::read()? {
if key.code == KeyCode::Esc || key.code == KeyCode::Char('q') {
break 'lorenz_attractor;
}
}
}
}
}
disable_raw_mode()?;
execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
Ok(())
}