use ruviz::prelude::*;
fn main() -> Result<()> {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("failed to create current-thread Tokio runtime for interactive example")
.block_on(async_main())
}
async fn async_main() -> Result<()> {
println!("Starting data brushing example...");
println!("Controls:");
println!(" - Mouse wheel: Zoom in/out");
println!(" - Left click + drag: Pan");
println!(" - Right click: Context menu");
println!(" - Right click + drag: Box zoom");
println!(" - Escape: Reset view");
println!(" - Cmd/Ctrl+S: Save PNG");
println!(" - Cmd/Ctrl+C: Copy image");
let n_points = 500;
let data = generate_correlated_data(n_points);
println!("Generated {} correlated data points", n_points);
println!("Created 4 derived plots for the brushing workflow");
#[cfg(feature = "interactive")]
{
println!("Opening interactive data brushing demo...");
println!("Note: Full multi-plot brushing requires subplot layout implementation");
let interactive_plot = create_brushing_demo_plot(&data)?;
show_interactive(interactive_plot).await?;
}
#[cfg(not(feature = "interactive"))]
{
println!("Interactive features not enabled.");
println!("To enable: cargo run --features interactive --example data_brushing");
let time_plot = Plot::new()
.line(&data.time, &data.values)
.scatter(&data.time, &data.values)
.title("Time Series View")
.xlabel("Time")
.ylabel("Value")
.legend(Position::TopLeft);
let phase_plot = Plot::new()
.scatter(&data.values, &data.derivatives)
.title("Phase Space View")
.xlabel("Value")
.ylabel("Derivative")
.legend(Position::TopRight);
let correlation_plot = Plot::new()
.scatter(&data.values, &data.noise)
.title("Value vs Noise Correlation")
.xlabel("Value")
.ylabel("Noise Component")
.legend(Position::BottomRight);
let histogram_plot = Plot::new()
.scatter(&data.histogram_bins, &data.histogram_counts)
.title("Value Distribution")
.xlabel("Value Bins")
.ylabel("Frequency")
.legend(Position::TopLeft);
time_plot.save("generated/examples/data_brushing_time_series.png")?;
phase_plot.save("generated/examples/data_brushing_phase_space.png")?;
correlation_plot.save("generated/examples/data_brushing_correlation.png")?;
histogram_plot.save("generated/examples/data_brushing_histogram.png")?;
println!("Saved static versions:");
println!(" - generated/examples/data_brushing_time_series.png");
println!(" - generated/examples/data_brushing_phase_space.png");
println!(" - generated/examples/data_brushing_correlation.png");
println!(" - generated/examples/data_brushing_histogram.png");
}
println!("Data brushing example completed!");
Ok(())
}
#[cfg_attr(feature = "interactive", allow(dead_code))]
struct CorrelatedData {
time: Vec<f64>,
values: Vec<f64>,
derivatives: Vec<f64>,
noise: Vec<f64>,
histogram_bins: Vec<f64>,
histogram_counts: Vec<f64>,
}
fn generate_correlated_data(n_points: usize) -> CorrelatedData {
let mut time = Vec::with_capacity(n_points);
let mut values = Vec::with_capacity(n_points);
let mut derivatives = Vec::with_capacity(n_points);
let mut noise = Vec::with_capacity(n_points);
for i in 0..n_points {
let t = i as f64 * 0.1;
time.push(t);
let signal = (t * 0.5).sin() * 2.0 + (t * 0.2).cos() * 1.5 + t * 0.01;
let noise_component = (t * 0.8).sin() * 0.3 + (i as f64 * 0.01).cos() * 0.2;
let value = signal + noise_component;
values.push(value);
noise.push(noise_component);
if i > 0 {
let derivative = (value - values[i - 1]) / 0.1;
derivatives.push(derivative);
} else {
derivatives.push(0.0);
}
}
let histogram_bins: Vec<f64> = (-30..30).map(|i| i as f64 * 0.2).collect();
let mut histogram_counts = vec![0.0; histogram_bins.len()];
for &value in &values {
let bin_index = ((value + 6.0) / 0.2) as usize;
if bin_index < histogram_counts.len() {
histogram_counts[bin_index] += 1.0;
}
}
CorrelatedData {
time,
values,
derivatives,
noise,
histogram_bins,
histogram_counts,
}
}
fn create_brushing_demo_plot(data: &CorrelatedData) -> Result<Plot> {
let plot: Plot = Plot::new()
.line(&data.time, &data.values)
.scatter(&data.time, &data.values)
.title("Interactive Data Brushing Demo\n(Multi-plot brushing coming soon)")
.xlabel("Time")
.ylabel("Value")
.legend(Position::TopLeft)
.into();
Ok(plot)
}
#[cfg_attr(not(test), allow(dead_code))]
fn simulate_brushing_selection(
data: &CorrelatedData,
selection_region: (f64, f64, f64, f64),
) -> Vec<usize> {
let (min_x, min_y, max_x, max_y) = selection_region;
let mut selected_indices = Vec::new();
for (i, (&x, &y)) in data.time.iter().zip(data.values.iter()).enumerate() {
if x >= min_x && x <= max_x && y >= min_y && y <= max_y {
selected_indices.push(i);
}
}
selected_indices
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_data_generation() {
let data = generate_correlated_data(100);
assert_eq!(data.time.len(), 100);
assert_eq!(data.values.len(), 100);
assert_eq!(data.derivatives.len(), 100);
assert_eq!(data.noise.len(), 100);
for i in 1..data.time.len() {
assert!(data.time[i] > data.time[i - 1]);
}
let total_count: f64 = data.histogram_counts.iter().sum();
assert!(total_count > 0.0);
}
#[test]
fn test_brushing_selection() {
let data = generate_correlated_data(50);
let selection = simulate_brushing_selection(&data, (0.0, -2.0, 5.0, 2.0));
assert!(!selection.is_empty());
for &index in &selection {
let x = data.time[index];
let y = data.values[index];
assert!(x >= 0.0 && x <= 5.0);
assert!(y >= -2.0 && y <= 2.0);
}
}
#[tokio::test]
async fn test_plot_creation() {
let data = generate_correlated_data(10);
let plot = create_brushing_demo_plot(&data);
assert!(plot.is_ok());
}
}