Skip to main content

eulumdat_ui/widgets/
polar.rs

1//! Polar diagram widget for egui
2
3use crate::Theme;
4use egui::{vec2, Color32, Pos2, Sense, Stroke, Vec2};
5use eulumdat::{diagram::PolarDiagram, Eulumdat};
6
7/// Polar diagram widget
8pub struct PolarWidget;
9
10impl PolarWidget {
11    /// Show the polar diagram
12    pub fn show(ui: &mut egui::Ui, ldt: &Eulumdat, theme: &Theme) {
13        let available_size = ui.available_size();
14        let size = available_size.min_elem().min(600.0);
15        let (response, painter) = ui.allocate_painter(Vec2::splat(size), Sense::hover());
16
17        let rect = response.rect;
18        let center = rect.center();
19        let radius = (size / 2.0) * 0.85;
20
21        // Background
22        painter.rect_filled(rect, 0.0, theme.background);
23
24        // Generate diagram data
25        let polar = PolarDiagram::from_eulumdat(ldt);
26
27        // Draw grid circles
28        Self::draw_grid(&painter, center, radius, &polar, theme);
29
30        // Draw angle labels
31        Self::draw_angle_labels(&painter, center, radius, theme);
32
33        // Draw intensity curves
34        Self::draw_curve(
35            &painter,
36            center,
37            radius,
38            &polar.c0_c180_curve.points,
39            polar.scale.scale_max,
40            theme.primary_curve,
41        );
42
43        if polar.show_c90_c270() {
44            Self::draw_curve(
45                &painter,
46                center,
47                radius,
48                &polar.c90_c270_curve.points,
49                polar.scale.scale_max,
50                theme.secondary_curve,
51            );
52        }
53
54        // Legend
55        Self::draw_legend(ui, &polar, theme);
56    }
57
58    fn draw_grid(
59        painter: &egui::Painter,
60        center: Pos2,
61        radius: f32,
62        polar: &PolarDiagram,
63        theme: &Theme,
64    ) {
65        let grid_stroke = Stroke::new(1.0, theme.grid);
66        let axis_stroke = Stroke::new(1.5, theme.axis);
67
68        // Concentric circles for intensity scale
69        for &value in polar.scale.grid_values.iter() {
70            let r = radius * (value / polar.scale.scale_max) as f32;
71            painter.circle_stroke(center, r, grid_stroke);
72
73            // Label on right side
74            let label_pos = center + vec2(r + 5.0, 0.0);
75            painter.text(
76                label_pos,
77                egui::Align2::LEFT_CENTER,
78                format!("{:.0}", value),
79                egui::FontId::proportional(10.0),
80                theme.text,
81            );
82        }
83
84        // Radial lines for angles (every 30°)
85        for angle_deg in (0..360).step_by(30) {
86            let angle_rad = (angle_deg as f32 - 90.0).to_radians();
87            let outer = center + radius * vec2(angle_rad.cos(), angle_rad.sin());
88            painter.line_segment([center, outer], grid_stroke);
89        }
90
91        // Horizontal and vertical axes (stronger)
92        painter.line_segment(
93            [center - vec2(radius, 0.0), center + vec2(radius, 0.0)],
94            axis_stroke,
95        );
96        painter.line_segment(
97            [center - vec2(0.0, radius), center + vec2(0.0, radius)],
98            axis_stroke,
99        );
100    }
101
102    fn draw_angle_labels(painter: &egui::Painter, center: Pos2, radius: f32, theme: &Theme) {
103        let labels = [
104            (0, "0°", egui::Align2::CENTER_BOTTOM),
105            (90, "90°", egui::Align2::LEFT_CENTER),
106            (180, "180°", egui::Align2::CENTER_TOP),
107            (270, "90°", egui::Align2::RIGHT_CENTER),
108        ];
109
110        for (angle_deg, label, align) in labels {
111            let angle_rad = (angle_deg as f32 - 90.0).to_radians();
112            let pos = center + (radius + 15.0) * vec2(angle_rad.cos(), angle_rad.sin());
113            painter.text(
114                pos,
115                align,
116                label,
117                egui::FontId::proportional(12.0),
118                theme.text,
119            );
120        }
121    }
122
123    fn draw_curve(
124        painter: &egui::Painter,
125        center: Pos2,
126        radius: f32,
127        points: &[eulumdat::diagram::PolarPoint],
128        scale_max: f64,
129        color: Color32,
130    ) {
131        if points.is_empty() {
132            return;
133        }
134
135        let stroke = Stroke::new(2.0, color);
136        let screen_points: Vec<Pos2> = points
137            .iter()
138            .map(|p| {
139                // Convert from diagram coordinates to screen coordinates
140                // In polar diagram: gamma 0° = down (nadir), gamma 180° = up (zenith)
141                let r = (p.intensity / scale_max) as f32 * radius;
142                let angle_rad = (p.gamma as f32 - 90.0).to_radians();
143                center + r * vec2(angle_rad.cos(), angle_rad.sin())
144            })
145            .collect();
146
147        // Draw as connected line segments
148        for pair in screen_points.windows(2) {
149            painter.line_segment([pair[0], pair[1]], stroke);
150        }
151    }
152
153    fn draw_legend(ui: &mut egui::Ui, polar: &PolarDiagram, theme: &Theme) {
154        ui.horizontal(|ui| {
155            // C0-C180 legend
156            let (rect, _) = ui.allocate_exact_size(vec2(20.0, 3.0), Sense::hover());
157            ui.painter().rect_filled(rect, 0.0, theme.primary_curve);
158            ui.label("C0-C180");
159
160            if polar.show_c90_c270() {
161                ui.add_space(20.0);
162                let (rect, _) = ui.allocate_exact_size(vec2(20.0, 3.0), Sense::hover());
163                ui.painter().rect_filled(rect, 0.0, theme.secondary_curve);
164                ui.label("C90-C270");
165            }
166
167            ui.add_space(20.0);
168            ui.label(format!("Max: {:.0} cd/klm", polar.scale.max_intensity));
169        });
170    }
171}