change settings live for traffic/delay graphs [rebuild]

This commit is contained in:
Dustin Carlino 2020-03-31 12:31:40 -07:00
parent 6809330dda
commit 7be5b3f34a
6 changed files with 164 additions and 47 deletions

View File

@ -664,7 +664,7 @@ impl Composite {
self.find::<TextBox>(name).get_line() self.find::<TextBox>(name).get_line()
} }
pub fn dropdown_value<T: 'static + PartialEq + Clone>(&mut self, name: &str) -> T { pub fn dropdown_value<T: 'static + PartialEq + Clone>(&self, name: &str) -> T {
self.find::<Dropdown<T>>(name).current_value() self.find::<Dropdown<T>>(name).current_value()
} }

View File

@ -45,7 +45,7 @@ impl<T: 'static + Ord + PartialEq + Copy + core::fmt::Debug + Yvalue<T>> Plot<T>
let height = 0.2 * ctx.canvas.window_height; let height = 0.2 * ctx.canvas.window_height;
let radius = 15.0; let radius = 15.0;
let legend = Widget::col( let legend = Widget::row(
series series
.iter() .iter()
.map(|s| { .map(|s| {
@ -63,7 +63,8 @@ impl<T: 'static + Ord + PartialEq + Copy + core::fmt::Debug + Yvalue<T>> Plot<T>
]) ])
}) })
.collect(), .collect(),
); )
.flex_wrap(ctx, 24);
// Assume min_x is Time::START_OF_DAY and min_y is y_zero // Assume min_x is Time::START_OF_DAY and min_y is y_zero
let max_x = opts.max_x.unwrap_or_else(|| { let max_x = opts.max_x.unwrap_or_else(|| {

View File

@ -1,8 +1,8 @@
use crate::app::App; use crate::app::App;
use crate::helpers::rotating_color_map; use crate::helpers::rotating_color_map;
use crate::info::{header_btns, make_tabs, throughput, Details, Tab}; use crate::info::{header_btns, make_tabs, throughput, DataOptions, Details, Tab};
use abstutil::prettyprint_usize; use abstutil::prettyprint_usize;
use ezgui::{EventCtx, Line, Plot, PlotOptions, Series, Text, TextExt, Widget}; use ezgui::{EventCtx, Line, Plot, PlotOptions, Series, Text, Widget};
use geom::{Duration, Statistic, Time}; use geom::{Duration, Statistic, Time};
use map_model::{IntersectionID, IntersectionType}; use map_model::{IntersectionID, IntersectionType};
use sim::Analytics; use sim::Analytics;
@ -27,12 +27,19 @@ pub fn info(ctx: &EventCtx, app: &App, details: &mut Details, id: IntersectionID
} }
pub fn traffic( pub fn traffic(
ctx: &EventCtx, ctx: &mut EventCtx,
app: &App, app: &App,
details: &mut Details, details: &mut Details,
id: IntersectionID, id: IntersectionID,
opts: &DataOptions,
) -> Vec<Widget> { ) -> Vec<Widget> {
let mut rows = header(ctx, app, details, id, Tab::IntersectionTraffic(id)); let mut rows = header(
ctx,
app,
details,
id,
Tab::IntersectionTraffic(id, opts.clone()),
);
let mut txt = Text::new(); let mut txt = Text::new();
@ -47,38 +54,54 @@ pub fn traffic(
.get(id) .get(id)
) )
))); )));
txt.add(Line(format!("In 20 minute buckets:")));
rows.push(txt.draw(ctx)); rows.push(txt.draw(ctx));
rows.push(opts.to_controls(ctx, app));
rows.push( rows.push(
throughput(ctx, app, move |a, t| { throughput(
a.throughput_intersection(t, id, Duration::minutes(20)) ctx,
}) app,
move |a, t| a.throughput_intersection(t, id, opts.bucket_size),
opts.show_baseline,
)
.margin(10), .margin(10),
); );
rows rows
} }
pub fn delay(ctx: &EventCtx, app: &App, details: &mut Details, id: IntersectionID) -> Vec<Widget> { pub fn delay(
let mut rows = header(ctx, app, details, id, Tab::IntersectionDelay(id)); ctx: &mut EventCtx,
app: &App,
details: &mut Details,
id: IntersectionID,
opts: &DataOptions,
) -> Vec<Widget> {
let mut rows = header(
ctx,
app,
details,
id,
Tab::IntersectionDelay(id, opts.clone()),
);
let i = app.primary.map.get_i(id); let i = app.primary.map.get_i(id);
assert!(i.is_traffic_signal()); assert!(i.is_traffic_signal());
rows.push("In 20 minute buckets".draw_text(ctx)); rows.push(opts.to_controls(ctx, app));
rows.push(delay_plot(ctx, app, id, Duration::minutes(20)).margin(10)); rows.push(delay_plot(ctx, app, id, opts).margin(10));
rows rows
} }
fn delay_plot(ctx: &EventCtx, app: &App, i: IntersectionID, bucket: Duration) -> Widget { fn delay_plot(ctx: &EventCtx, app: &App, i: IntersectionID, opts: &DataOptions) -> Widget {
let get_data = |a: &Analytics, t: Time| { let get_data = |a: &Analytics, t: Time| {
let mut series: Vec<(Statistic, Vec<(Time, Duration)>)> = Statistic::all() let mut series: Vec<(Statistic, Vec<(Time, Duration)>)> = Statistic::all()
.into_iter() .into_iter()
.map(|stat| (stat, Vec::new())) .map(|stat| (stat, Vec::new()))
.collect(); .collect();
for (t, distrib) in a.intersection_delays_bucketized(t, i, bucket) { for (t, distrib) in a.intersection_delays_bucketized(t, i, opts.bucket_size) {
for (stat, pts) in series.iter_mut() { for (stat, pts) in series.iter_mut() {
if distrib.count() == 0 { if distrib.count() == 0 {
pts.push((t, Duration::ZERO)); pts.push((t, Duration::ZERO));
@ -101,7 +124,7 @@ fn delay_plot(ctx: &EventCtx, app: &App, i: IntersectionID, bucket: Duration) ->
pts, pts,
}); });
} }
if app.has_prebaked().is_some() { if opts.show_baseline {
for (idx, (stat, pts)) in get_data(app.prebaked(), Time::END_OF_DAY) for (idx, (stat, pts)) in get_data(app.prebaked(), Time::END_OF_DAY)
.into_iter() .into_iter()
.enumerate() .enumerate()
@ -142,10 +165,13 @@ fn header(
rows.push(make_tabs(ctx, &mut details.hyperlinks, tab, { rows.push(make_tabs(ctx, &mut details.hyperlinks, tab, {
let mut tabs = vec![ let mut tabs = vec![
("Info", Tab::IntersectionInfo(id)), ("Info", Tab::IntersectionInfo(id)),
("Traffic", Tab::IntersectionTraffic(id)), (
"Traffic",
Tab::IntersectionTraffic(id, DataOptions::new(app)),
),
]; ];
if i.is_traffic_signal() { if i.is_traffic_signal() {
tabs.push(("Delay", Tab::IntersectionDelay(id))); tabs.push(("Delay", Tab::IntersectionDelay(id, DataOptions::new(app))));
} }
tabs tabs
})); }));

View File

@ -1,8 +1,7 @@
use crate::app::App; use crate::app::App;
use crate::info::{header_btns, make_table, make_tabs, throughput, Details, Tab}; use crate::info::{header_btns, make_table, make_tabs, throughput, DataOptions, Details, Tab};
use abstutil::prettyprint_usize; use abstutil::prettyprint_usize;
use ezgui::{EventCtx, Line, Text, TextExt, Widget}; use ezgui::{EventCtx, Line, Text, TextExt, Widget};
use geom::Duration;
use map_model::LaneID; use map_model::LaneID;
pub fn info(ctx: &EventCtx, app: &App, details: &mut Details, id: LaneID) -> Vec<Widget> { pub fn info(ctx: &EventCtx, app: &App, details: &mut Details, id: LaneID) -> Vec<Widget> {
@ -93,8 +92,14 @@ pub fn debug(ctx: &EventCtx, app: &App, details: &mut Details, id: LaneID) -> Ve
rows rows
} }
pub fn traffic(ctx: &EventCtx, app: &App, details: &mut Details, id: LaneID) -> Vec<Widget> { pub fn traffic(
let mut rows = header(ctx, app, details, id, Tab::LaneTraffic(id)); ctx: &mut EventCtx,
app: &App,
details: &mut Details,
id: LaneID,
opts: &DataOptions,
) -> Vec<Widget> {
let mut rows = header(ctx, app, details, id, Tab::LaneTraffic(id, opts.clone()));
let map = &app.primary.map; let map = &app.primary.map;
let l = map.get_l(id); let l = map.get_l(id);
let r = map.get_r(l.parent); let r = map.get_r(l.parent);
@ -112,14 +117,18 @@ pub fn traffic(ctx: &EventCtx, app: &App, details: &mut Details, id: LaneID) ->
.get(r.id) .get(r.id)
) )
))); )));
txt.add(Line(format!("In 20 minute buckets:")));
rows.push(txt.draw(ctx)); rows.push(txt.draw(ctx));
rows.push(opts.to_controls(ctx, app));
let r = map.get_l(id).parent; let r = map.get_l(id).parent;
rows.push( rows.push(
throughput(ctx, app, move |a, t| { throughput(
a.throughput_road(t, r, Duration::minutes(20)) ctx,
}) app,
move |a, t| a.throughput_road(t, r, opts.bucket_size),
opts.show_baseline,
)
.margin(10), .margin(10),
); );
@ -148,7 +157,7 @@ fn header(ctx: &EventCtx, app: &App, details: &mut Details, id: LaneID, tab: Tab
tab, tab,
vec![ vec![
("Info", Tab::LaneInfo(id)), ("Info", Tab::LaneInfo(id)),
("Traffic", Tab::LaneTraffic(id)), ("Traffic", Tab::LaneTraffic(id, DataOptions::new(app))),
("Debug", Tab::LaneDebug(id)), ("Debug", Tab::LaneDebug(id)),
], ],
)); ));

View File

@ -13,10 +13,11 @@ use crate::game::Transition;
use crate::helpers::ID; use crate::helpers::ID;
use crate::render::{ExtraShapeID, MIN_ZOOM_FOR_DETAIL}; use crate::render::{ExtraShapeID, MIN_ZOOM_FOR_DETAIL};
use ezgui::{ use ezgui::{
hotkey, Btn, Color, Composite, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key, hotkey, Btn, Choice, Color, Composite, Drawable, EventCtx, GeomBatch, GfxCtx,
Line, Outcome, Plot, PlotOptions, Series, Text, TextExt, VerticalAlignment, Widget, HorizontalAlignment, Key, Line, Outcome, Plot, PlotOptions, Series, Text, TextExt,
VerticalAlignment, Widget,
}; };
use geom::{Circle, Distance, Time}; use geom::{Circle, Distance, Duration, Time};
use map_model::{AreaID, BuildingID, BusStopID, IntersectionID, LaneID}; use map_model::{AreaID, BuildingID, BusStopID, IntersectionID, LaneID};
use sim::{AgentID, Analytics, CarID, PedestrianID, PersonID, PersonState, TripMode, VehicleType}; use sim::{AgentID, Analytics, CarID, PedestrianID, PersonID, PersonState, TripMode, VehicleType};
use std::collections::{BTreeMap, HashMap}; use std::collections::{BTreeMap, HashMap};
@ -58,12 +59,12 @@ pub enum Tab {
ExtraShape(ExtraShapeID), ExtraShape(ExtraShapeID),
IntersectionInfo(IntersectionID), IntersectionInfo(IntersectionID),
IntersectionTraffic(IntersectionID), IntersectionTraffic(IntersectionID, DataOptions),
IntersectionDelay(IntersectionID), IntersectionDelay(IntersectionID, DataOptions),
LaneInfo(LaneID), LaneInfo(LaneID),
LaneDebug(LaneID), LaneDebug(LaneID),
LaneTraffic(LaneID), LaneTraffic(LaneID, DataOptions),
} }
impl Tab { impl Tab {
@ -118,10 +119,27 @@ impl Tab {
Tab::Crowd(members) => Some(ID::PedCrowd(members)), Tab::Crowd(members) => Some(ID::PedCrowd(members)),
Tab::Area(a) => Some(ID::Area(a)), Tab::Area(a) => Some(ID::Area(a)),
Tab::ExtraShape(es) => Some(ID::ExtraShape(es)), Tab::ExtraShape(es) => Some(ID::ExtraShape(es)),
Tab::IntersectionInfo(i) | Tab::IntersectionTraffic(i) | Tab::IntersectionDelay(i) => { Tab::IntersectionInfo(i)
Some(ID::Intersection(i)) | Tab::IntersectionTraffic(i, _)
| Tab::IntersectionDelay(i, _) => Some(ID::Intersection(i)),
Tab::LaneInfo(l) | Tab::LaneDebug(l) | Tab::LaneTraffic(l, _) => Some(ID::Lane(l)),
}
}
fn changed_settings(&self, c: &Composite) -> Option<Tab> {
let mut new_tab = self.clone();
match new_tab {
Tab::IntersectionTraffic(_, ref mut opts)
| Tab::IntersectionDelay(_, ref mut opts)
| Tab::LaneTraffic(_, ref mut opts) => {
*opts = DataOptions::from_controls(c);
} }
Tab::LaneInfo(l) | Tab::LaneDebug(l) | Tab::LaneTraffic(l) => Some(ID::Lane(l)), _ => {}
}
if &new_tab == self {
None
} else {
Some(new_tab)
} }
} }
} }
@ -172,13 +190,18 @@ impl InfoPanel {
Tab::Area(a) => (debug::area(ctx, app, &mut details, a), true), Tab::Area(a) => (debug::area(ctx, app, &mut details, a), true),
Tab::ExtraShape(es) => (debug::extra_shape(ctx, app, &mut details, es), true), Tab::ExtraShape(es) => (debug::extra_shape(ctx, app, &mut details, es), true),
Tab::IntersectionInfo(i) => (intersection::info(ctx, app, &mut details, i), true), Tab::IntersectionInfo(i) => (intersection::info(ctx, app, &mut details, i), true),
Tab::IntersectionTraffic(i) => { Tab::IntersectionTraffic(i, ref opts) => (
(intersection::traffic(ctx, app, &mut details, i), false) intersection::traffic(ctx, app, &mut details, i, opts),
false,
),
Tab::IntersectionDelay(i, ref opts) => {
(intersection::delay(ctx, app, &mut details, i, opts), false)
} }
Tab::IntersectionDelay(i) => (intersection::delay(ctx, app, &mut details, i), false),
Tab::LaneInfo(l) => (lane::info(ctx, app, &mut details, l), true), Tab::LaneInfo(l) => (lane::info(ctx, app, &mut details, l), true),
Tab::LaneDebug(l) => (lane::debug(ctx, app, &mut details, l), false), Tab::LaneDebug(l) => (lane::debug(ctx, app, &mut details, l), false),
Tab::LaneTraffic(l) => (lane::traffic(ctx, app, &mut details, l), false), Tab::LaneTraffic(l, ref opts) => {
(lane::traffic(ctx, app, &mut details, l, opts), false)
}
}; };
let maybe_id = tab.clone().to_id(app); let maybe_id = tab.clone().to_id(app);
let mut cached_actions = Vec::new(); let mut cached_actions = Vec::new();
@ -358,7 +381,15 @@ impl InfoPanel {
(close_panel, Some(t)) (close_panel, Some(t))
} }
} }
None => (false, None), None => {
// Maybe a non-click action should change the tab. Aka, checkboxes/dropdowns/etc on
// a tab.
if let Some(new_tab) = self.tab.changed_settings(&self.composite) {
*self = InfoPanel::new(ctx, app, new_tab, ctx_actions);
}
(false, None)
}
} }
} }
@ -408,6 +439,7 @@ fn throughput<F: Fn(&Analytics, Time) -> BTreeMap<TripMode, Vec<(Time, usize)>>>
ctx: &EventCtx, ctx: &EventCtx,
app: &App, app: &App,
get_data: F, get_data: F,
show_baseline: bool,
) -> Widget { ) -> Widget {
let mut series = get_data(app.primary.sim.get_analytics(), app.primary.sim.time()) let mut series = get_data(app.primary.sim.get_analytics(), app.primary.sim.time())
.into_iter() .into_iter()
@ -417,7 +449,7 @@ fn throughput<F: Fn(&Analytics, Time) -> BTreeMap<TripMode, Vec<(Time, usize)>>>
pts, pts,
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
if app.has_prebaked().is_some() { if show_baseline {
// TODO Ahh these colors don't show up differently at all. // TODO Ahh these colors don't show up differently at all.
for (m, pts) in get_data(app.prebaked(), Time::END_OF_DAY) { for (m, pts) in get_data(app.prebaked(), Time::END_OF_DAY) {
series.push(Series { series.push(Series {
@ -482,3 +514,51 @@ pub trait ContextualActions {
close_panel: &mut bool, close_panel: &mut bool,
) -> Transition; ) -> Transition;
} }
#[derive(Clone, PartialEq)]
pub struct DataOptions {
pub show_baseline: bool,
pub bucket_size: Duration,
}
impl DataOptions {
pub fn new(app: &App) -> DataOptions {
DataOptions {
show_baseline: app.has_prebaked().is_some(),
bucket_size: Duration::minutes(20),
}
}
pub fn to_controls(&self, ctx: &mut EventCtx, app: &App) -> Widget {
Widget::col(vec![
Widget::row(vec![
"In".draw_text(ctx),
Widget::dropdown(
ctx,
"bucket size",
self.bucket_size,
vec![
Choice::new("20 minute", Duration::minutes(20)),
Choice::new("1 hour", Duration::hours(1)),
Choice::new("6 hour", Duration::hours(6)),
],
)
.margin(3),
"buckets".draw_text(ctx),
]),
if app.has_prebaked().is_some() {
// TODO Change the wording of this
Widget::checkbox(ctx, "Show baseline data", None, self.show_baseline)
} else {
Widget::nothing()
},
])
}
pub fn from_controls(c: &Composite) -> DataOptions {
DataOptions {
show_baseline: c.has_widget("Show baseline data") && c.is_checked("Show baseline data"),
bucket_size: c.dropdown_value("bucket size"),
}
}
}

View File

@ -149,10 +149,11 @@ impl std::fmt::Display for Duration {
write!(f, "{}m", minutes)?; write!(f, "{}m", minutes)?;
} }
if remainder != 0 { if remainder != 0 {
write!(f, "{}.{:01}s", seconds, remainder) write!(f, "{}.{:01}s", seconds, remainder)?;
} else { } else if seconds != 0 {
write!(f, "{}s", seconds) write!(f, "{}s", seconds)?;
} }
Ok(())
} }
} }