eulumdat_ui/widgets/
cartesian.rs

1//! Cartesian diagram widget for egui
2
3use crate::Theme;
4use egui::{pos2, vec2, Pos2, Rect, Sense, Stroke};
5use eulumdat::{diagram::CartesianDiagram, Eulumdat};
6
7/// Cartesian diagram widget
8pub struct CartesianWidget;
9
10impl CartesianWidget {
11    /// Show the cartesian diagram
12    pub fn show(ui: &mut egui::Ui, ldt: &Eulumdat, theme: &Theme) {
13        let available_size = ui.available_size();
14        let width = available_size.x.min(800.0);
15        let height = (width * 0.6).min(available_size.y - 50.0);
16
17        let (response, painter) = ui.allocate_painter(vec2(width, height), Sense::hover());
18        let rect = response.rect;
19
20        // Margins for axes
21        let margin = vec2(60.0, 40.0);
22        let plot_rect = Rect::from_min_max(rect.min + margin, rect.max - vec2(20.0, 30.0));
23
24        // Background
25        painter.rect_filled(rect, 0.0, theme.background);
26
27        // Generate diagram data (max 8 curves)
28        let cartesian = CartesianDiagram::from_eulumdat(ldt, width as f64, height as f64, 8);
29
30        // Draw grid
31        Self::draw_grid(&painter, plot_rect, &cartesian, theme);
32
33        // Draw curves
34        for curve in cartesian.curves.iter() {
35            let color = theme.c_plane_color(curve.c_angle, cartesian.curves.len());
36            Self::draw_curve(&painter, plot_rect, curve, &cartesian, color);
37        }
38
39        // Draw axes labels
40        Self::draw_axes(&painter, plot_rect, &cartesian, theme);
41
42        // Legend
43        Self::draw_legend(ui, &cartesian, theme);
44    }
45
46    fn draw_grid(
47        painter: &egui::Painter,
48        rect: Rect,
49        _cartesian: &CartesianDiagram,
50        theme: &Theme,
51    ) {
52        let grid_stroke = Stroke::new(1.0, theme.grid);
53
54        // Vertical grid lines (gamma angles)
55        for gamma in (0..=180).step_by(30) {
56            let x = rect.left() + (gamma as f32 / 180.0) * rect.width();
57            painter.line_segment([pos2(x, rect.top()), pos2(x, rect.bottom())], grid_stroke);
58        }
59
60        // Horizontal grid lines (intensity)
61        let num_lines = 5;
62        for i in 0..=num_lines {
63            let y = rect.bottom() - (i as f32 / num_lines as f32) * rect.height();
64            painter.line_segment([pos2(rect.left(), y), pos2(rect.right(), y)], grid_stroke);
65        }
66    }
67
68    fn draw_curve(
69        painter: &egui::Painter,
70        rect: Rect,
71        curve: &eulumdat::diagram::CartesianCurve,
72        cartesian: &CartesianDiagram,
73        color: egui::Color32,
74    ) {
75        if curve.points.is_empty() {
76            return;
77        }
78
79        let stroke = Stroke::new(2.0, color);
80        let max_intensity = cartesian.scale.scale_max;
81
82        let screen_points: Vec<Pos2> = curve
83            .points
84            .iter()
85            .map(|p| {
86                let x = rect.left() + (p.gamma as f32 / 180.0) * rect.width();
87                let y = rect.bottom() - (p.intensity as f32 / max_intensity as f32) * rect.height();
88                pos2(x, y.max(rect.top()))
89            })
90            .collect();
91
92        for pair in screen_points.windows(2) {
93            painter.line_segment([pair[0], pair[1]], stroke);
94        }
95    }
96
97    fn draw_axes(painter: &egui::Painter, rect: Rect, cartesian: &CartesianDiagram, theme: &Theme) {
98        let axis_stroke = Stroke::new(1.5, theme.axis);
99
100        // X axis
101        painter.line_segment(
102            [
103                pos2(rect.left(), rect.bottom()),
104                pos2(rect.right(), rect.bottom()),
105            ],
106            axis_stroke,
107        );
108
109        // Y axis
110        painter.line_segment(
111            [
112                pos2(rect.left(), rect.top()),
113                pos2(rect.left(), rect.bottom()),
114            ],
115            axis_stroke,
116        );
117
118        // X axis labels (gamma)
119        for gamma in (0..=180).step_by(30) {
120            let x = rect.left() + (gamma as f32 / 180.0) * rect.width();
121            painter.text(
122                pos2(x, rect.bottom() + 15.0),
123                egui::Align2::CENTER_TOP,
124                format!("{}°", gamma),
125                egui::FontId::proportional(10.0),
126                theme.text,
127            );
128        }
129
130        // Y axis labels (intensity)
131        let num_labels = 5;
132        for i in 0..=num_labels {
133            let value = cartesian.scale.scale_max * (i as f64 / num_labels as f64);
134            let y = rect.bottom() - (i as f32 / num_labels as f32) * rect.height();
135            painter.text(
136                pos2(rect.left() - 10.0, y),
137                egui::Align2::RIGHT_CENTER,
138                format!("{:.0}", value),
139                egui::FontId::proportional(10.0),
140                theme.text,
141            );
142        }
143
144        // Axis titles
145        painter.text(
146            pos2(rect.center().x, rect.bottom() + 30.0),
147            egui::Align2::CENTER_TOP,
148            "Gamma (°)",
149            egui::FontId::proportional(12.0),
150            theme.text,
151        );
152    }
153
154    fn draw_legend(ui: &mut egui::Ui, cartesian: &CartesianDiagram, theme: &Theme) {
155        ui.horizontal_wrapped(|ui| {
156            for curve in &cartesian.curves {
157                let color = theme.c_plane_color(curve.c_angle, cartesian.curves.len());
158                let (rect, _) = ui.allocate_exact_size(vec2(15.0, 3.0), Sense::hover());
159                ui.painter().rect_filled(rect, 0.0, color);
160                ui.label(format!("C{:.0}°", curve.c_angle));
161                ui.add_space(10.0);
162            }
163        });
164    }
165}