mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-26 07:52:05 +03:00
change settings live for traffic/delay graphs [rebuild]
This commit is contained in:
parent
6809330dda
commit
7be5b3f34a
@ -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()
|
||||
}
|
||||
|
||||
|
@ -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(|| {
|
||||
|
@ -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
|
||||
}));
|
||||
|
@ -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)),
|
||||
],
|
||||
));
|
||||
|
@ -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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user