siplot 0.4.1

silx-style scientific plotting for egui, rendered with wgpu
Documentation
//! 3D scatter example — a value-coloured point cloud in a [`SceneWidget`].
//!
//! Mirrors the 3D-scatter item of silx `examples/plot3dSceneWindow.py` (and the
//! pygfx `13_3d_scatter` demo): random-looking points in the unit cube, coloured
//! through a colormap by a Gaussian value field and drawn as diamond markers.
//! The cloud is generated by a small deterministic LCG so the example is
//! reproducible without a `rand` dependency.
//!
//! Left-drag orbits, right-drag pans, wheel zooms.
//!
//! Run with: `cargo run --example scene3d_scatter`

use eframe::egui;
use siplot::{Colormap, ColormapName, PointMarker, Scatter3D, Scene3dGeometry, SceneWidget, Vec3};

const N: usize = 1000;

struct ScatterApp {
    scene: SceneWidget,
}

impl ScatterApp {
    fn new(cc: &eframe::CreationContext<'_>) -> Self {
        let rs = cc
            .wgpu_render_state
            .as_ref()
            .expect("eframe must use the wgpu renderer");

        let mut rng = Lcg::new(0x5eed_1234);
        let (mut xs, mut ys, mut zs, mut vs) = (vec![], vec![], vec![], vec![]);
        for _ in 0..N {
            let (x, y, z) = (rng.unit(), rng.unit(), rng.unit());
            // silx: values = exp(-11 * ((x-0.5)^2 + (y-0.5)^2 + (z-0.5)^2)).
            let r2 = (x - 0.5).powi(2) + (y - 0.5).powi(2) + (z - 0.5).powi(2);
            xs.push(x);
            ys.push(y);
            zs.push(z);
            vs.push((-11.0 * r2 as f64).exp());
        }

        let scatter = Scatter3D::new()
            .with_data(&xs, &ys, &zs, &vs)
            .with_colormap(Colormap::new(ColormapName::Magma, 0.0, 1.0))
            .with_marker(PointMarker::Diamond)
            .with_size(11.0);

        let mut geometry = Scene3dGeometry::new();
        scatter.append_to(&mut geometry);

        let mut scene = SceneWidget::new(rs, 0);
        scene.set_bounds(rs, (Vec3::ZERO, Vec3::new(1.0, 1.0, 1.0)));
        scene.set_geometry(rs, geometry);
        Self { scene }
    }
}

impl eframe::App for ScatterApp {
    fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
        egui::CentralPanel::default().show_inside(ui, |ui| {
            self.scene.show(ui);
        });
    }
}

/// A tiny linear-congruential generator (Knuth MMIX constants) for reproducible
/// pseudo-random coordinates without pulling in the `rand` crate.
struct Lcg(u64);

impl Lcg {
    fn new(seed: u64) -> Self {
        Lcg(seed)
    }

    /// The next `f32` uniformly in `[0, 1)`.
    fn unit(&mut self) -> f32 {
        self.0 = self
            .0
            .wrapping_mul(6364136223846793005)
            .wrapping_add(1442695040888963407);
        // Use the top 24 bits as a [0,1) float.
        ((self.0 >> 40) as f32) / (1u32 << 24) as f32
    }
}

fn main() -> eframe::Result {
    eframe::run_native(
        "siplot: 3D scatter",
        eframe::NativeOptions {
            renderer: eframe::Renderer::Wgpu,
            ..Default::default()
        },
        Box::new(|cc| Ok(Box::new(ScatterApp::new(cc)) as Box<dyn eframe::App>)),
    )
}