1use std::collections::HashSet;
2
3use crate::ID;
4use abstutil::{prettyprint_usize, Counter};
5use geom::Time;
6use map_gui::tools::ColorNetwork;
7use map_model::{IntersectionID, RoadID, Traversable};
8use sim::{Problem, ProblemType};
9use widgetry::mapspace::ToggleZoomed;
10use widgetry::tools::DivergingScale;
11use widgetry::{Color, EventCtx, GfxCtx, Outcome, Panel, Text, Toggle, Widget};
12
13use crate::app::App;
14use crate::layer::{header, problems, Layer, LayerOutcome, PANEL_PLACEMENT};
15
16pub struct RelativeProblemMap {
17 time: Time,
18 opts: Options,
19 draw: ToggleZoomed,
20 panel: Panel,
21
22 before_road: Counter<RoadID>,
23 before_intersection: Counter<IntersectionID>,
24 after_road: Counter<RoadID>,
25 after_intersection: Counter<IntersectionID>,
26 tooltip: Option<Text>,
27}
28
29impl Layer for RelativeProblemMap {
30 fn name(&self) -> Option<&'static str> {
31 Some("problem map")
32 }
33 fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Option<LayerOutcome> {
34 let mut recalc_tooltip = false;
35 if app.primary.sim.time() != self.time {
36 let mut new = Self::new(ctx, app, self.opts.clone());
37 new.panel.restore(ctx, &self.panel);
38 *self = new;
39 recalc_tooltip = true;
40 }
41
42 if ctx.redo_mouseover() || recalc_tooltip {
44 self.tooltip = None;
45 if let Some((before, after)) = match app.mouseover_unzoomed_roads_and_intersections(ctx)
46 {
47 Some(ID::Road(r)) => Some((self.before_road.get(r), self.after_road.get(r))),
48 Some(ID::Intersection(i)) => Some((
49 self.before_intersection.get(i),
50 self.after_intersection.get(i),
51 )),
52 _ => None,
53 } {
54 self.tooltip = Some(Text::from(format!(
55 "{} before, {} after{}",
56 prettyprint_usize(before),
57 prettyprint_usize(after),
58 if before != 0 {
59 format!(" ({:.1}x)", (after as f64) / (before as f64))
60 } else {
61 String::new()
62 },
63 )));
64 }
65 } else {
66 self.tooltip = None;
67 }
68
69 match self.panel.event(ctx) {
70 Outcome::Clicked(x) => match x.as_ref() {
71 "close" => {
72 return Some(LayerOutcome::Close);
73 }
74 _ => unreachable!(),
75 },
76 Outcome::Changed(x) => {
77 if x == "Compare before proposal" {
78 let mut opts = problems::Options::new(app);
79 opts.types = self.opts.clone();
80 return Some(LayerOutcome::Replace(Box::new(problems::ProblemMap::new(
81 ctx, app, opts,
82 ))));
83 }
84
85 let new_opts = ProblemTypes::from_controls(&self.panel);
86 if self.opts != new_opts {
87 *self = Self::new(ctx, app, new_opts);
88 }
89 }
90 _ => {}
91 }
92 None
93 }
94 fn draw(&self, g: &mut GfxCtx, _: &App) {
95 self.panel.draw(g);
96 self.draw.draw(g);
97 if let Some(ref txt) = self.tooltip {
98 g.draw_mouse_tooltip(txt.clone());
99 }
100 }
101 fn draw_minimap(&self, g: &mut GfxCtx) {
102 g.redraw(&self.draw.unzoomed);
103 }
104}
105
106impl RelativeProblemMap {
107 pub fn new(ctx: &mut EventCtx, app: &App, opts: Options) -> Self {
108 let after = app.primary.sim.get_analytics();
109 let before = app.prebaked();
110 let now = app.primary.sim.time();
111
112 let mut after_road = Counter::new();
113 let mut before_road = Counter::new();
114 let mut after_intersection = Counter::new();
115 let mut before_intersection = Counter::new();
116
117 let update_count =
118 |problem: &Problem,
119 roads: &mut Counter<RoadID>,
120 intersections: &mut Counter<IntersectionID>| {
121 match problem {
122 Problem::IntersectionDelay(i, _) | Problem::ComplexIntersectionCrossing(i) => {
123 intersections.inc(*i);
124 }
125 Problem::OvertakeDesired(on) | Problem::PedestrianOvercrowding(on) => {
126 match on {
127 Traversable::Lane(l) => {
128 roads.inc(l.road);
129 }
130 Traversable::Turn(t) => {
131 intersections.inc(t.parent);
132 }
133 }
134 }
135 Problem::ArterialIntersectionCrossing(t) => {
136 intersections.inc(t.parent);
137 }
138 }
139 };
140
141 for (_, problems) in &before.problems_per_trip {
142 for (time, problem) in problems {
143 if *time > now {
145 break;
146 }
147 if opts.show(problem) {
148 update_count(problem, &mut before_road, &mut before_intersection);
149 }
150 }
151 }
152
153 for (_, problems) in &after.problems_per_trip {
154 for (_, problem) in problems {
155 if opts.show(problem) {
156 update_count(problem, &mut after_road, &mut after_intersection);
157 }
158 }
159 }
160
161 let mut colorer = ColorNetwork::new(app);
162
163 let scale = DivergingScale::new(Color::hex("#5D9630"), Color::WHITE, Color::hex("#A32015"))
164 .range(0.0, 2.0)
165 .ignore(0.7, 1.3);
166
167 for (r, before, after) in before_road.clone().compare(after_road.clone()) {
168 if let Some(c) = scale.eval((after as f64) / (before as f64)) {
169 colorer.add_r(r, c);
170 }
171 }
172 for (i, before, after) in before_intersection
173 .clone()
174 .compare(after_intersection.clone())
175 {
176 if let Some(c) = scale.eval((after as f64) / (before as f64)) {
177 colorer.add_i(i, c);
178 }
179 }
180
181 let legend = scale.make_legend(ctx, vec!["less problems", "same", "more"]);
182 let controls = make_controls(ctx, &opts, legend);
183 Self {
184 time: app.primary.sim.time(),
185 opts,
186 draw: colorer.build(ctx),
187 panel: controls,
188 tooltip: None,
189 before_road,
190 before_intersection,
191 after_road,
192 after_intersection,
193 }
194 }
195}
196
197fn make_controls(ctx: &mut EventCtx, opts: &Options, legend: Widget) -> Panel {
198 Panel::new_builder(Widget::col(vec![
199 header(ctx, "Change in Problems encountered"),
200 Toggle::switch(ctx, "Compare before proposal", None, true),
201 opts.to_controls(ctx),
202 legend,
203 ]))
204 .aligned_pair(PANEL_PLACEMENT)
205 .build(ctx)
206}
207
208pub type Options = ProblemTypes;
209
210#[derive(Clone, PartialEq)]
211pub struct ProblemTypes {
212 disabled_types: HashSet<ProblemType>,
213}
214
215impl ProblemTypes {
216 pub fn new() -> Self {
217 Self {
218 disabled_types: HashSet::new(),
219 }
220 }
221
222 pub fn show(&self, problem: &Problem) -> bool {
223 !self.disabled_types.contains(&ProblemType::from(problem))
224 }
225
226 pub fn to_controls(&self, ctx: &mut EventCtx) -> Widget {
227 let mut col = Vec::new();
228 for pt in ProblemType::all() {
229 col.push(Toggle::checkbox(
230 ctx,
231 &format!("show {}", pt.name()),
232 None,
233 !self.disabled_types.contains(&pt),
234 ));
235 }
236 Widget::col(col)
237 }
238
239 pub fn from_controls(panel: &Panel) -> Self {
240 let mut types = Self::new();
241 for pt in ProblemType::all() {
242 if !panel.is_checked(&format!("show {}", pt.name())) {
243 types.disabled_types.insert(pt);
244 }
245 }
246 types
247 }
248}