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()
}
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()
}

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

View File

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

View File

@ -1,8 +1,7 @@
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 ezgui::{EventCtx, Line, Text, TextExt, Widget};
use geom::Duration;
use map_model::LaneID;
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
}
pub fn traffic(ctx: &EventCtx, app: &App, details: &mut Details, id: LaneID) -> Vec<Widget> {
let mut rows = header(ctx, app, details, id, Tab::LaneTraffic(id));
pub fn traffic(
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 l = map.get_l(id);
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)
)
)));
txt.add(Line(format!("In 20 minute buckets:")));
rows.push(txt.draw(ctx));
rows.push(opts.to_controls(ctx, app));
let r = map.get_l(id).parent;
rows.push(
throughput(ctx, app, move |a, t| {
a.throughput_road(t, r, Duration::minutes(20))
})
throughput(
ctx,
app,
move |a, t| a.throughput_road(t, r, opts.bucket_size),
opts.show_baseline,
)
.margin(10),
);
@ -148,7 +157,7 @@ fn header(ctx: &EventCtx, app: &App, details: &mut Details, id: LaneID, tab: Tab
tab,
vec![
("Info", Tab::LaneInfo(id)),
("Traffic", Tab::LaneTraffic(id)),
("Traffic", Tab::LaneTraffic(id, DataOptions::new(app))),
("Debug", Tab::LaneDebug(id)),
],
));

View File

@ -13,10 +13,11 @@ use crate::game::Transition;
use crate::helpers::ID;
use crate::render::{ExtraShapeID, MIN_ZOOM_FOR_DETAIL};
use ezgui::{
hotkey, Btn, Color, Composite, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key,
Line, Outcome, Plot, PlotOptions, Series, Text, TextExt, VerticalAlignment, Widget,
hotkey, Btn, Choice, Color, Composite, Drawable, EventCtx, GeomBatch, GfxCtx,
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 sim::{AgentID, Analytics, CarID, PedestrianID, PersonID, PersonState, TripMode, VehicleType};
use std::collections::{BTreeMap, HashMap};
@ -58,12 +59,12 @@ pub enum Tab {
ExtraShape(ExtraShapeID),
IntersectionInfo(IntersectionID),
IntersectionTraffic(IntersectionID),
IntersectionDelay(IntersectionID),
IntersectionTraffic(IntersectionID, DataOptions),
IntersectionDelay(IntersectionID, DataOptions),
LaneInfo(LaneID),
LaneDebug(LaneID),
LaneTraffic(LaneID),
LaneTraffic(LaneID, DataOptions),
}
impl Tab {
@ -118,10 +119,27 @@ impl Tab {
Tab::Crowd(members) => Some(ID::PedCrowd(members)),
Tab::Area(a) => Some(ID::Area(a)),
Tab::ExtraShape(es) => Some(ID::ExtraShape(es)),
Tab::IntersectionInfo(i) | Tab::IntersectionTraffic(i) | Tab::IntersectionDelay(i) => {
Some(ID::Intersection(i))
Tab::IntersectionInfo(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::ExtraShape(es) => (debug::extra_shape(ctx, app, &mut details, es), true),
Tab::IntersectionInfo(i) => (intersection::info(ctx, app, &mut details, i), true),
Tab::IntersectionTraffic(i) => {
(intersection::traffic(ctx, app, &mut details, i), false)
Tab::IntersectionTraffic(i, ref opts) => (
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::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 mut cached_actions = Vec::new();
@ -358,7 +381,15 @@ impl InfoPanel {
(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,
app: &App,
get_data: F,
show_baseline: bool,
) -> Widget {
let mut series = get_data(app.primary.sim.get_analytics(), app.primary.sim.time())
.into_iter()
@ -417,7 +449,7 @@ fn throughput<F: Fn(&Analytics, Time) -> BTreeMap<TripMode, Vec<(Time, usize)>>>
pts,
})
.collect::<Vec<_>>();
if app.has_prebaked().is_some() {
if show_baseline {
// TODO Ahh these colors don't show up differently at all.
for (m, pts) in get_data(app.prebaked(), Time::END_OF_DAY) {
series.push(Series {
@ -482,3 +514,51 @@ pub trait ContextualActions {
close_panel: &mut bool,
) -> 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)?;
}
if remainder != 0 {
write!(f, "{}.{:01}s", seconds, remainder)
} else {
write!(f, "{}s", seconds)
write!(f, "{}.{:01}s", seconds, remainder)?;
} else if seconds != 0 {
write!(f, "{}s", seconds)?;
}
Ok(())
}
}