Flesh out the LTN impact tool -- calculate impact after, add tooltips

This commit is contained in:
Dustin Carlino 2022-01-19 14:55:37 +00:00
parent fc705318a9
commit c6e26e5e20
6 changed files with 152 additions and 45 deletions

View File

@ -3,7 +3,7 @@ use std::collections::HashMap;
use abstutil::{prettyprint_usize, Counter, Timer}; use abstutil::{prettyprint_usize, Counter, Timer};
use geom::{Duration, Polygon}; use geom::{Duration, Polygon};
use map_gui::colors::ColorSchemeChoice; use map_gui::colors::ColorSchemeChoice;
use map_gui::tools::ColorNetwork; use map_gui::tools::{cmp_count, ColorNetwork};
use map_gui::{AppLike, ID}; use map_gui::{AppLike, ID};
use map_model::{ use map_model::{
DirectedRoadID, Direction, PathRequest, RoadID, RoutingParams, Traversable, DirectedRoadID, Direction, PathRequest, RoadID, RoutingParams, Traversable,
@ -450,7 +450,7 @@ impl State<App> for AllRoutesExplorer {
let baseline = self.baseline_counts.get(r); let baseline = self.baseline_counts.get(r);
let current = self.current_counts.get(r); let current = self.current_counts.get(r);
let mut txt = Text::new(); let mut txt = Text::new();
txt.append_all(cmp_count(current, baseline)); cmp_count(&mut txt, baseline, current);
txt.add_line(format!("{} baseline", prettyprint_usize(baseline))); txt.add_line(format!("{} baseline", prettyprint_usize(baseline)));
txt.add_line(format!("{} now", prettyprint_usize(current))); txt.add_line(format!("{} now", prettyprint_usize(current)));
self.tooltip = Some(txt); self.tooltip = Some(txt);
@ -490,26 +490,6 @@ fn calculate_demand(app: &App, requests: &[PathRequest], timer: &mut Timer) -> C
counter counter
} }
fn cmp_count(after: usize, before: usize) -> Vec<TextSpan> {
match after.cmp(&before) {
std::cmp::Ordering::Equal => {
vec![Line("same")]
}
std::cmp::Ordering::Less => {
vec![
Line(prettyprint_usize(before - after)).fg(Color::GREEN),
Line(" less"),
]
}
std::cmp::Ordering::Greater => {
vec![
Line(prettyprint_usize(after - before)).fg(Color::RED),
Line(" more"),
]
}
}
}
/// Evaluate why an alternative path wasn't chosen, by showing the cost to reach every road from /// Evaluate why an alternative path wasn't chosen, by showing the cost to reach every road from
/// one start. /// one start.
pub struct PathCostDebugger { pub struct PathCostDebugger {

View File

@ -125,6 +125,10 @@ impl State<App> for BrowseNeighborhoods {
"Calculate" => { "Calculate" => {
return Transition::Push(super::impact::ShowResults::new_state(ctx, app)); return Transition::Push(super::impact::ShowResults::new_state(ctx, app));
} }
"Force recalculation" => {
app.session.impact = None;
return Transition::Push(super::impact::ShowResults::new_state(ctx, app));
}
_ => unreachable!(), _ => unreachable!(),
}, },
Outcome::Changed(_) => { Outcome::Changed(_) => {
@ -270,9 +274,16 @@ fn impact_widget(ctx: &EventCtx, app: &App) -> Widget {
Line(format!("We need to load a {} file", size)), Line(format!("We need to load a {} file", size)),
]) ])
.into_widget(ctx), .into_widget(ctx),
Widget::row(vec![
ctx.style() ctx.style()
.btn_solid_primary .btn_solid_primary
.text("Calculate") .text("Calculate")
.build_def(ctx), .build_def(ctx),
// TODO Bad UI! Detect edits and do this. I'm being lazy.
ctx.style()
.btn_solid_primary
.text("Force recalculation")
.build_def(ctx),
]),
]) ])
} }

View File

@ -1,21 +1,23 @@
use abstio::MapName; use abstio::MapName;
use abstutil::{Counter, Timer}; use abstutil::{prettyprint_usize, Counter, Timer};
use map_gui::load::FileLoader; use map_gui::load::FileLoader;
use map_gui::tools::ColorNetwork; use map_gui::tools::{cmp_count, ColorNetwork};
use map_gui::ID;
use map_model::{PathRequest, PathStepV2, RoadID}; use map_model::{PathRequest, PathStepV2, RoadID};
use sim::{Scenario, TripEndpoint, TripMode}; use sim::{Scenario, TripEndpoint, TripMode};
use widgetry::mapspace::ToggleZoomed; use widgetry::mapspace::ToggleZoomed;
use widgetry::{ use widgetry::{
EventCtx, GfxCtx, HorizontalAlignment, Panel, SimpleState, State, TextExt, VerticalAlignment, Choice, EventCtx, GfxCtx, HorizontalAlignment, Line, Panel, SimpleState, State, Text, TextExt,
Widget, VerticalAlignment, Widget,
}; };
use crate::{App, Transition}; use crate::{App, Transition};
// TODO Tooltips
// TODO Intersections // TODO Intersections
// TODO Toggle before/after / compare directly // TODO Configurable main road penalty, like in the pathfinding tool
// TODO Don't allow crossing filters at all -- don't just disincentivize
// TODO Share structure or pieces with Ungap's predict mode // TODO Share structure or pieces with Ungap's predict mode
// ... can't we just produce data of a certain shape, and have a UI pretty tuned for that?
pub struct Results { pub struct Results {
map: MapName, map: MapName,
@ -66,12 +68,15 @@ impl Results {
fn recalculate_impact(&mut self, ctx: &mut EventCtx, app: &App, timer: &mut Timer) { fn recalculate_impact(&mut self, ctx: &mut EventCtx, app: &App, timer: &mut Timer) {
self.before_counts = Counter::new(); self.before_counts = Counter::new();
self.after_counts = Counter::new(); self.after_counts = Counter::new();
let map = &app.map; let map = &app.map;
// Before the filters
for path in timer for path in timer
.parallelize("calculate routes", self.all_driving_trips.clone(), |req| { .parallelize(
map.pathfind_v2(req) "calculate routes before filters",
}) self.all_driving_trips.clone(),
|req| map.pathfind_v2(req),
)
.into_iter() .into_iter()
.flatten() .flatten()
{ {
@ -82,14 +87,46 @@ impl Results {
} }
} }
} }
let mut colorer = ColorNetwork::no_fading(app); let mut colorer = ColorNetwork::no_fading(app);
colorer.ranked_roads(self.before_counts.clone(), &app.cs.good_to_bad_red); colorer.ranked_roads(self.before_counts.clone(), &app.cs.good_to_bad_red);
self.before_draw_heatmap = colorer.build(ctx); self.before_draw_heatmap = colorer.build(ctx);
// After the filters
let mut params = map.routing_params().clone();
app.session.modal_filters.update_routing_params(&mut params);
let cache_custom = true;
for path in timer
.parallelize(
"calculate routes after filters",
self.all_driving_trips.clone(),
|req| map.pathfind_v2_with_params(req, &params, cache_custom),
)
.into_iter()
.flatten()
{
for step in path.get_steps() {
// No Contraflow steps for driving paths
if let PathStepV2::Along(dr) = step {
self.after_counts.inc(dr.road);
}
}
}
let mut colorer = ColorNetwork::no_fading(app);
colorer.ranked_roads(self.after_counts.clone(), &app.cs.good_to_bad_red);
self.after_draw_heatmap = colorer.build(ctx);
} }
} }
pub struct ShowResults; #[derive(Clone, Copy, Debug, PartialEq)]
enum Layer {
Before,
After,
}
pub struct ShowResults {
layer: Layer,
tooltip: Option<Text>,
}
impl ShowResults { impl ShowResults {
pub fn new_state(ctx: &mut EventCtx, app: &App) -> Box<dyn State<App>> { pub fn new_state(ctx: &mut EventCtx, app: &App) -> Box<dyn State<App>> {
@ -115,35 +152,89 @@ impl ShowResults {
); );
} }
let layer = Layer::Before;
let panel = Panel::new_builder(Widget::col(vec![ let panel = Panel::new_builder(Widget::col(vec![
map_gui::tools::app_header(ctx, app, "Low traffic neighborhoods"), map_gui::tools::app_header(ctx, app, "Low traffic neighborhoods"),
Widget::row(vec![ Widget::row(vec![
"Impact prediction".text_widget(ctx), "Impact prediction".text_widget(ctx),
ctx.style().btn_close_widget(ctx), ctx.style().btn_close_widget(ctx),
]), ]),
"This shows how many driving trips cross each road".text_widget(ctx),
Widget::row(vec![
"Show what?".text_widget(ctx).centered_vert(),
Widget::dropdown(
ctx,
"layer",
layer,
vec![
Choice::new("before", Layer::Before),
Choice::new("after", Layer::After),
],
),
]),
])) ]))
.aligned(HorizontalAlignment::Left, VerticalAlignment::Top) .aligned(HorizontalAlignment::Left, VerticalAlignment::Top)
.build(ctx); .build(ctx);
<dyn SimpleState<_>>::new_state(panel, Box::new(ShowResults)) <dyn SimpleState<_>>::new_state(
panel,
Box::new(ShowResults {
layer,
tooltip: None,
}),
)
} }
} }
impl SimpleState<App> for ShowResults { impl SimpleState<App> for ShowResults {
fn on_click(&mut self, ctx: &mut EventCtx, app: &mut App, x: &str, _: &Panel) -> Transition { fn on_click(&mut self, _: &mut EventCtx, _: &mut App, x: &str, _: &Panel) -> Transition {
if x == "close" { if x == "close" {
return Transition::Pop; return Transition::Pop;
} }
unreachable!() unreachable!()
} }
// TODO Or on_mouseover? fn on_mouseover(&mut self, ctx: &mut EventCtx, app: &mut App) {
fn other_event(&mut self, ctx: &mut EventCtx, _: &mut App) -> Transition {
ctx.canvas_movement(); ctx.canvas_movement();
Transition::Keep self.tooltip = None;
if let Some(r) = match app.mouseover_unzoomed_roads_and_intersections(ctx) {
Some(ID::Road(r)) => Some(r),
Some(ID::Lane(l)) => Some(l.road),
_ => None,
} {
let impact = app.session.impact.as_ref().unwrap();
let before = impact.before_counts.get(r);
let after = impact.after_counts.get(r);
let mut txt = Text::from_multiline(vec![
Line(format!("Before: {}", prettyprint_usize(before))),
Line(format!("After: {}", prettyprint_usize(after))),
]);
cmp_count(&mut txt, before, after);
self.tooltip = Some(txt);
}
}
fn panel_changed(
&mut self,
_: &mut EventCtx,
_: &mut App,
panel: &mut Panel,
) -> Option<Transition> {
self.layer = panel.dropdown_value("layer");
None
} }
fn draw(&self, g: &mut GfxCtx, app: &App) { fn draw(&self, g: &mut GfxCtx, app: &App) {
let impact = app.session.impact.as_ref().unwrap(); let impact = app.session.impact.as_ref().unwrap();
match self.layer {
Layer::Before => {
impact.before_draw_heatmap.draw(g); impact.before_draw_heatmap.draw(g);
} }
Layer::After => {
impact.after_draw_heatmap.draw(g);
}
}
if let Some(ref txt) = self.tooltip {
g.draw_mouse_tooltip(txt.clone());
}
}
} }

View File

@ -126,6 +126,8 @@ impl RoutePlanner {
let mut draw = ToggleZoomed::builder(); let mut draw = ToggleZoomed::builder();
// First the route respecting the filters // First the route respecting the filters
// TODO Like in rat_runs, we need to actually enforce this, not just penalize... though,
// should it matter except for when a cell is disconnected?
let (total_time_after, total_dist_after) = { let (total_time_after, total_dist_after) = {
let mut params = map.routing_params().clone(); let mut params = map.routing_params().clone();
app.session.modal_filters.update_routing_params(&mut params); app.session.modal_filters.update_routing_params(&mut params);

View File

@ -19,7 +19,8 @@ pub use self::title_screen::{Executable, TitleScreen};
pub use self::trip_files::{TripManagement, TripManagementState}; pub use self::trip_files::{TripManagement, TripManagementState};
pub use self::turn_explorer::TurnExplorer; pub use self::turn_explorer::TurnExplorer;
pub use self::ui::{ pub use self::ui::{
cmp_dist, cmp_duration, percentage_bar, ChooseSomething, FilePicker, PopupMsg, PromptInput, cmp_count, cmp_dist, cmp_duration, percentage_bar, ChooseSomething, FilePicker, PopupMsg,
PromptInput,
}; };
pub use self::url::URLManager; pub use self::url::URLManager;
pub use self::waypoints::{InputWaypoints, WaypointID}; pub use self::waypoints::{InputWaypoints, WaypointID};

View File

@ -4,6 +4,7 @@ use std::cmp::Ordering;
use anyhow::Result; use anyhow::Result;
use abstutil::prettyprint_usize;
use geom::{Distance, Duration, Polygon}; use geom::{Distance, Duration, Polygon};
use widgetry::{ use widgetry::{
hotkeys, Choice, Color, DrawBaselayer, EventCtx, GeomBatch, GfxCtx, Key, Line, Menu, Outcome, hotkeys, Choice, Color, DrawBaselayer, EventCtx, GeomBatch, GfxCtx, Key, Line, Menu, Outcome,
@ -316,3 +317,24 @@ pub fn cmp_duration(
Ordering::Equal => {} Ordering::Equal => {}
} }
} }
/// Less is better
pub fn cmp_count(txt: &mut Text, before: usize, after: usize) {
match after.cmp(&before) {
std::cmp::Ordering::Equal => {
txt.add_line(Line("same"));
}
std::cmp::Ordering::Less => {
txt.add_appended(vec![
Line(prettyprint_usize(before - after)).fg(Color::GREEN),
Line(" less"),
]);
}
std::cmp::Ordering::Greater => {
txt.add_appended(vec![
Line(prettyprint_usize(after - before)).fg(Color::RED),
Line(" more"),
]);
}
}
}