game/layer/
problems_diff.rs

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        // TODO Reinventing CompareCounts...
43        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                // Per trip, problems are counted in order, so stop after now.
144                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}