maelstrom_plot/
transform.rs1use crate::PlotPoint;
2use egui::{pos2, remap, Pos2, Rect, Vec2};
3use std::ops::RangeInclusive;
4
5#[derive(Clone, Copy, PartialEq, Debug)]
8pub struct PlotBounds {
9 pub(crate) min: [f64; 2],
10 pub(crate) max: [f64; 2],
11}
12
13impl PlotBounds {
14 pub const NOTHING: Self = Self {
15 min: [f64::INFINITY; 2],
16 max: [-f64::INFINITY; 2],
17 };
18
19 pub fn from_min_max(min: [f64; 2], max: [f64; 2]) -> Self {
20 Self { min, max }
21 }
22
23 pub fn min(&self) -> [f64; 2] {
24 self.min
25 }
26
27 pub fn max(&self) -> [f64; 2] {
28 self.max
29 }
30
31 pub(crate) fn new_symmetrical(half_extent: f64) -> Self {
32 Self {
33 min: [-half_extent; 2],
34 max: [half_extent; 2],
35 }
36 }
37
38 pub fn is_finite(&self) -> bool {
39 self.min[0].is_finite()
40 && self.min[1].is_finite()
41 && self.max[0].is_finite()
42 && self.max[1].is_finite()
43 }
44
45 pub fn is_finite_x(&self) -> bool {
46 self.min[0].is_finite() && self.max[0].is_finite()
47 }
48
49 pub fn is_finite_y(&self) -> bool {
50 self.min[1].is_finite() && self.max[1].is_finite()
51 }
52
53 pub fn is_valid(&self) -> bool {
54 self.is_finite() && self.width() > 0.0 && self.height() > 0.0
55 }
56
57 pub fn is_valid_x(&self) -> bool {
58 self.is_finite_x() && self.width() > 0.0
59 }
60
61 pub fn is_valid_y(&self) -> bool {
62 self.is_finite_y() && self.height() > 0.0
63 }
64
65 pub fn width(&self) -> f64 {
66 self.max[0] - self.min[0]
67 }
68
69 pub fn height(&self) -> f64 {
70 self.max[1] - self.min[1]
71 }
72
73 pub fn center(&self) -> PlotPoint {
74 [
75 (self.min[0] + self.max[0]) / 2.0,
76 (self.min[1] + self.max[1]) / 2.0,
77 ]
78 .into()
79 }
80
81 pub(crate) fn extend_with(&mut self, value: &PlotPoint) {
83 self.extend_with_x(value.x);
84 self.extend_with_y(value.y);
85 }
86
87 pub(crate) fn extend_with_x(&mut self, x: f64) {
89 self.min[0] = self.min[0].min(x);
90 self.max[0] = self.max[0].max(x);
91 }
92
93 pub(crate) fn extend_with_y(&mut self, y: f64) {
95 self.min[1] = self.min[1].min(y);
96 self.max[1] = self.max[1].max(y);
97 }
98
99 pub(crate) fn expand_x(&mut self, pad: f64) {
100 self.min[0] -= pad;
101 self.max[0] += pad;
102 }
103
104 pub(crate) fn expand_y(&mut self, pad: f64) {
105 self.min[1] -= pad;
106 self.max[1] += pad;
107 }
108
109 pub(crate) fn merge_x(&mut self, other: &PlotBounds) {
110 self.min[0] = self.min[0].min(other.min[0]);
111 self.max[0] = self.max[0].max(other.max[0]);
112 }
113
114 pub(crate) fn merge_y(&mut self, other: &PlotBounds) {
115 self.min[1] = self.min[1].min(other.min[1]);
116 self.max[1] = self.max[1].max(other.max[1]);
117 }
118
119 pub(crate) fn set_x(&mut self, other: &PlotBounds) {
120 self.min[0] = other.min[0];
121 self.max[0] = other.max[0];
122 }
123
124 pub(crate) fn set_y(&mut self, other: &PlotBounds) {
125 self.min[1] = other.min[1];
126 self.max[1] = other.max[1];
127 }
128
129 pub(crate) fn translate_x(&mut self, delta: f64) {
130 self.min[0] += delta;
131 self.max[0] += delta;
132 }
133
134 pub(crate) fn translate_y(&mut self, delta: f64) {
135 self.min[1] += delta;
136 self.max[1] += delta;
137 }
138
139 pub(crate) fn translate(&mut self, delta: Vec2) {
140 self.translate_x(delta.x as f64);
141 self.translate_y(delta.y as f64);
142 }
143
144 pub(crate) fn add_relative_margin_x(&mut self, margin_fraction: Vec2) {
145 let width = self.width().max(0.0);
146 self.expand_x(margin_fraction.x as f64 * width);
147 }
148
149 pub(crate) fn add_relative_margin_y(&mut self, margin_fraction: Vec2) {
150 let height = self.height().max(0.0);
151 self.expand_y(margin_fraction.y as f64 * height);
152 }
153
154 pub(crate) fn range_x(&self) -> RangeInclusive<f64> {
155 self.min[0]..=self.max[0]
156 }
157
158 pub(crate) fn range_y(&self) -> RangeInclusive<f64> {
159 self.min[1]..=self.max[1]
160 }
161
162 pub(crate) fn make_x_symmetrical(&mut self) {
163 let x_abs = self.min[0].abs().max(self.max[0].abs());
164 self.min[0] = -x_abs;
165 self.max[0] = x_abs;
166 }
167
168 pub(crate) fn make_y_symmetrical(&mut self) {
169 let y_abs = self.min[1].abs().max(self.max[1].abs());
170 self.min[1] = -y_abs;
171 self.max[1] = y_abs;
172 }
173}
174
175#[derive(Clone, Copy, Debug)]
177pub struct PlotTransform {
178 frame: Rect,
180
181 bounds: PlotBounds,
183
184 x_centered: bool,
186
187 y_centered: bool,
189}
190
191impl PlotTransform {
192 pub fn new(frame: Rect, mut bounds: PlotBounds, x_centered: bool, y_centered: bool) -> Self {
193 if !bounds.is_valid_x() {
195 bounds.set_x(&PlotBounds::new_symmetrical(1.0));
196 }
197 if !bounds.is_valid_y() {
198 bounds.set_y(&PlotBounds::new_symmetrical(1.0));
199 }
200
201 if x_centered {
203 bounds.make_x_symmetrical();
204 };
205 if y_centered {
206 bounds.make_y_symmetrical();
207 };
208
209 Self {
210 frame,
211 bounds,
212 x_centered,
213 y_centered,
214 }
215 }
216
217 pub fn frame(&self) -> &Rect {
219 &self.frame
220 }
221
222 pub fn bounds(&self) -> &PlotBounds {
224 &self.bounds
225 }
226
227 pub(crate) fn set_bounds(&mut self, bounds: PlotBounds) {
228 self.bounds = bounds;
229 }
230
231 pub(crate) fn translate_bounds(&mut self, mut delta_pos: Vec2) {
232 if self.x_centered {
233 delta_pos.x = 0.;
234 }
235 if self.y_centered {
236 delta_pos.y = 0.;
237 }
238 delta_pos.x *= self.dvalue_dpos()[0] as f32;
239 delta_pos.y *= self.dvalue_dpos()[1] as f32;
240 self.bounds.translate(delta_pos);
241 }
242
243 pub(crate) fn zoom(&mut self, zoom_factor: Vec2, center: Pos2) {
245 let center = self.value_from_position(center);
246
247 let mut new_bounds = self.bounds;
248 new_bounds.min[0] = center.x + (new_bounds.min[0] - center.x) / (zoom_factor.x as f64);
249 new_bounds.max[0] = center.x + (new_bounds.max[0] - center.x) / (zoom_factor.x as f64);
250 new_bounds.min[1] = center.y + (new_bounds.min[1] - center.y) / (zoom_factor.y as f64);
251 new_bounds.max[1] = center.y + (new_bounds.max[1] - center.y) / (zoom_factor.y as f64);
252
253 if new_bounds.is_valid() {
254 self.bounds = new_bounds;
255 }
256 }
257
258 pub fn position_from_point_x(&self, value: f64) -> f32 {
259 remap(
260 value,
261 self.bounds.min[0]..=self.bounds.max[0],
262 (self.frame.left() as f64)..=(self.frame.right() as f64),
263 ) as f32
264 }
265
266 pub fn position_from_point_y(&self, value: f64) -> f32 {
267 remap(
268 value,
269 self.bounds.min[1]..=self.bounds.max[1],
270 (self.frame.bottom() as f64)..=(self.frame.top() as f64), ) as f32
272 }
273
274 pub fn position_from_point(&self, value: &PlotPoint) -> Pos2 {
276 pos2(
277 self.position_from_point_x(value.x),
278 self.position_from_point_y(value.y),
279 )
280 }
281
282 pub fn value_from_position(&self, pos: Pos2) -> PlotPoint {
284 let x = remap(
285 pos.x as f64,
286 (self.frame.left() as f64)..=(self.frame.right() as f64),
287 self.bounds.min[0]..=self.bounds.max[0],
288 );
289 let y = remap(
290 pos.y as f64,
291 (self.frame.bottom() as f64)..=(self.frame.top() as f64), self.bounds.min[1]..=self.bounds.max[1],
293 );
294 PlotPoint::new(x, y)
295 }
296
297 pub fn rect_from_values(&self, value1: &PlotPoint, value2: &PlotPoint) -> Rect {
302 let pos1 = self.position_from_point(value1);
303 let pos2 = self.position_from_point(value2);
304
305 let mut rect = Rect::NOTHING;
306 rect.extend_with(pos1);
307 rect.extend_with(pos2);
308 rect
309 }
310
311 pub fn dpos_dvalue_x(&self) -> f64 {
313 self.frame.width() as f64 / self.bounds.width()
314 }
315
316 pub fn dpos_dvalue_y(&self) -> f64 {
318 -self.frame.height() as f64 / self.bounds.height() }
320
321 pub fn dpos_dvalue(&self) -> [f64; 2] {
323 [self.dpos_dvalue_x(), self.dpos_dvalue_y()]
324 }
325
326 pub fn dvalue_dpos(&self) -> [f64; 2] {
328 [1.0 / self.dpos_dvalue_x(), 1.0 / self.dpos_dvalue_y()]
329 }
330
331 fn aspect(&self) -> f64 {
333 let rw = self.frame.width() as f64;
334 let rh = self.frame.height() as f64;
335 (self.bounds.width() / rw) / (self.bounds.height() / rh)
336 }
337
338 pub(crate) fn set_aspect_by_expanding(&mut self, aspect: f64) {
342 let current_aspect = self.aspect();
343
344 let epsilon = 1e-5;
345 if (current_aspect - aspect).abs() < epsilon {
346 return;
348 }
349
350 if current_aspect < aspect {
351 self.bounds
352 .expand_x((aspect / current_aspect - 1.0) * self.bounds.width() * 0.5);
353 } else {
354 self.bounds
355 .expand_y((current_aspect / aspect - 1.0) * self.bounds.height() * 0.5);
356 }
357 }
358
359 pub(crate) fn set_aspect_by_changing_axis(&mut self, aspect: f64, change_x: bool) {
361 let current_aspect = self.aspect();
362
363 let epsilon = 1e-5;
364 if (current_aspect - aspect).abs() < epsilon {
365 return;
367 }
368
369 if change_x {
370 self.bounds
371 .expand_x((aspect / current_aspect - 1.0) * self.bounds.width() * 0.5);
372 } else {
373 self.bounds
374 .expand_y((current_aspect / aspect - 1.0) * self.bounds.height() * 0.5);
375 }
376 }
377}