1use geom::{Angle, Circle, Distance, Duration, Pt2D};
2
3use crate::{
4 Color, Drawable, EventCtx, GeomBatch, GfxCtx, Line, ScreenDims, ScreenPt, ScreenRectangle,
5 Text, TextExt, Widget, WidgetImpl, WidgetOutput,
6};
7
8pub struct CompareTimes {
16 draw: Drawable,
17
18 max: Duration,
19
20 top_left: ScreenPt,
21 dims: ScreenDims,
22}
23
24impl CompareTimes {
25 pub fn new_widget<I: AsRef<str>>(
26 ctx: &mut EventCtx,
27 x_name: I,
28 y_name: I,
29 points: Vec<(Duration, Duration)>,
30 ) -> Widget {
31 if points.is_empty() {
32 return Widget::nothing();
33 }
34
35 let actual_max = *points.iter().map(|(b, a)| a.max(b)).max().unwrap();
36 let num_labels = 5;
38 let (max, labels) = actual_max.make_intervals_for_max(num_labels);
39
40 let width = 500.0;
42 let height = width;
43
44 let mut batch = GeomBatch::new();
45 batch.autocrop_dims = false;
46
47 let thickness = Distance::meters(2.0);
49 for i in 1..num_labels {
50 let x = (i as f64) / (num_labels as f64) * width;
51 let y = (i as f64) / (num_labels as f64) * height;
52 batch.push(
54 Color::grey(0.5),
55 geom::Line::must_new(Pt2D::new(0.0, y), Pt2D::new(width, y))
56 .make_polygons(thickness),
57 );
58 batch.push(
60 Color::grey(0.5),
61 geom::Line::must_new(Pt2D::new(x, 0.0), Pt2D::new(x, height))
62 .make_polygons(thickness),
63 );
64 }
65 batch.push(
67 Color::grey(0.5),
68 geom::Line::must_new(Pt2D::new(0.0, height), Pt2D::new(width, 0.0))
69 .make_polygons(thickness),
70 );
71
72 let circle = Circle::new(Pt2D::new(0.0, 0.0), Distance::meters(4.0)).to_polygon();
73 for (b, a) in points {
74 let pt = Pt2D::new((b / max) * width, (1.0 - (a / max)) * height);
75 let color = match a.cmp(&b) {
77 std::cmp::Ordering::Equal => Color::YELLOW.alpha(0.5),
78 std::cmp::Ordering::Less => Color::GREEN.alpha(0.9),
79 std::cmp::Ordering::Greater => Color::RED.alpha(0.9),
80 };
81 batch.push(color, circle.translate(pt.x(), pt.y()));
82 }
83 let plot = Widget::new(Box::new(CompareTimes {
84 dims: batch.get_dims(),
85 draw: ctx.upload(batch),
86 max,
87 top_left: ScreenPt::new(0.0, 0.0),
88 }));
89
90 let y_axis = Widget::custom_col(
91 labels
92 .iter()
93 .rev()
94 .map(|x| {
95 Line(x.num_minutes_rounded_up().to_string())
96 .small()
97 .into_widget(ctx)
98 })
99 .collect(),
100 )
101 .evenly_spaced();
102 let mut y_label = Text::from(format!("{} (minutes)", y_name.as_ref()))
103 .render(ctx)
104 .rotate(Angle::degrees(90.0));
105 y_label.autocrop_dims = true;
106 let y_label = y_label
107 .autocrop()
108 .into_widget(ctx)
109 .centered_vert()
110 .margin_right(5);
111
112 let x_axis = Widget::custom_row(
113 labels
114 .iter()
115 .map(|x| {
116 Line(x.num_minutes_rounded_up().to_string())
117 .small()
118 .into_widget(ctx)
119 })
120 .collect(),
121 )
122 .evenly_spaced();
123 let x_label = format!("{} (minutes)", x_name.as_ref())
124 .text_widget(ctx)
125 .centered_horiz();
126
127 let plot_width = plot.get_width_for_forcing();
129 Widget::custom_col(vec![
130 Widget::custom_row(vec![y_label, y_axis, plot]),
131 Widget::custom_col(vec![x_axis, x_label])
132 .force_width(plot_width)
133 .align_right(),
134 ])
135 .container()
136 }
137}
138
139impl WidgetImpl for CompareTimes {
140 fn get_dims(&self) -> ScreenDims {
141 self.dims
142 }
143
144 fn set_pos(&mut self, top_left: ScreenPt) {
145 self.top_left = top_left;
146 }
147
148 fn event(&mut self, _: &mut EventCtx, _: &mut WidgetOutput) {}
149
150 fn draw(&self, g: &mut GfxCtx) {
151 g.redraw_at(self.top_left, &self.draw);
152
153 if let Some(cursor) = g.canvas.get_cursor_in_screen_space() {
154 let rect = ScreenRectangle::top_left(self.top_left, self.dims);
155 if let Some((pct_x, pct_y)) = rect.pt_to_percent(cursor) {
156 let thickness = Distance::meters(2.0);
157 let mut batch = GeomBatch::new();
158 if let Ok(l) = geom::Line::new(Pt2D::new(rect.x1, cursor.y), cursor.to_pt()) {
160 batch.push(Color::WHITE, l.make_polygons(thickness));
161 }
162 if let Ok(l) = geom::Line::new(Pt2D::new(cursor.x, rect.y2), cursor.to_pt()) {
164 batch.push(Color::WHITE, l.make_polygons(thickness));
165 }
166
167 g.fork_screenspace();
168 let draw = g.upload(batch);
169 g.redraw(&draw);
170 let before = pct_x * self.max;
172 let after = (1.0 - pct_y) * self.max;
173 if after <= before {
174 g.draw_mouse_tooltip(Text::from_multiline(vec![
175 Line(format!("Before: {}", before)),
176 Line(format!("After: {}", after)),
177 Line(format!(
178 "{} faster (-{:.1}%)",
179 before - after,
180 100.0 * (1.0 - after / before)
181 ))
182 .fg(Color::hex("#72CE36")),
183 ]));
184 } else {
185 g.draw_mouse_tooltip(Text::from_multiline(vec![
186 Line(format!("Before: {}", before)),
187 Line(format!("After: {}", after)),
188 Line(format!(
189 "{} slower (+{:.1}%)",
190 after - before,
191 100.0 * (after / before - 1.0)
192 ))
193 .fg(Color::hex("#EB3223")),
194 ]));
195 }
196 g.unfork();
197 }
198 }
199 }
200}