break down delay by agent

This commit is contained in:
Dustin Carlino 2020-06-12 11:37:12 -07:00
parent e059ebdb36
commit 14c7518787
6 changed files with 154 additions and 60 deletions

View File

@ -374,9 +374,9 @@ data/system/maps/montlake.bin,79c11be1af5833a04c6cbb4746e04401,https://www.dropb
data/system/maps/mt_baker.bin,312bff6b9609305be22cce8c02190315,https://www.dropbox.com/s/cetje663p04cbgp/mt_baker.bin.zip?dl=0
data/system/maps/udistrict.bin,870d0e3b89a56a294a5ad6e559fe22b8,https://www.dropbox.com/s/zqt2je8fadssz5j/udistrict.bin.zip?dl=0
data/system/maps/west_seattle.bin,804ff03d1a8b2975a26340d4cf9dbc52,https://www.dropbox.com/s/5pp1ik9l40yj3wh/west_seattle.bin.zip?dl=0
data/system/prebaked_results/lakeslice/weekday.bin,84f52e9f99d9a006214d95a84d5b9bb3,https://www.dropbox.com/s/1c1sohvy50263wg/weekday.bin.zip?dl=0
data/system/prebaked_results/lakeslice/weekday.bin,6c3cf17d893cab5a10f2cf7e062d9bdc,https://www.dropbox.com/s/1c1sohvy50263wg/weekday.bin.zip?dl=0
data/system/prebaked_results/montlake/car vs bike contention.bin,8c1c883a5dcd6daa042740033c0c18c4,https://www.dropbox.com/s/jefg0ikjy9dsrdd/car%20vs%20bike%20contention.bin.zip?dl=0
data/system/prebaked_results/montlake/weekday.bin,a330ae5a97b0f2af203f4db21ce1c32c,https://www.dropbox.com/s/1aq7n9ow8tfqb5d/weekday.bin.zip?dl=0
data/system/prebaked_results/montlake/weekday.bin,580baa0d16f9275ae743303306996bf5,https://www.dropbox.com/s/1aq7n9ow8tfqb5d/weekday.bin.zip?dl=0
data/system/scenarios/ballard/weekday.bin,60d3eb1cdb8672e2d29cf3acf23ccabe,https://www.dropbox.com/s/67hys1v7m7oe979/weekday.bin.zip?dl=0
data/system/scenarios/downtown/weekday.bin,8821147124da650f975483ed1e5bbb69,https://www.dropbox.com/s/pstvu4p7xj3gaoi/weekday.bin.zip?dl=0
data/system/scenarios/huge_seattle/weekday.bin,31bfc23f39bb54bef939119f6cfbd2e2,https://www.dropbox.com/s/u3pmsshwnf13g83/weekday.bin.zip?dl=0

View File

@ -1,7 +1,7 @@
use crate::widgets::line_plot::Yvalue;
use crate::{
Color, Drawable, EventCtx, GeomBatch, GfxCtx, JustDraw, Line, PlotOptions, ScreenDims,
ScreenPt, ScreenRectangle, Series, Text, TextExt, Widget, WidgetImpl, WidgetOutput,
Checkbox, Color, Drawable, EventCtx, GeomBatch, GfxCtx, JustDraw, Line, PlotOptions,
ScreenDims, ScreenPt, ScreenRectangle, Series, Text, TextExt, Widget, WidgetImpl, WidgetOutput,
};
use geom::{Angle, Circle, Distance, Duration, PolyLine, Pt2D, Time};
@ -178,31 +178,89 @@ impl WidgetImpl for ScatterPlot {
}
}
// TODO Dedupe
// TODO Dedupe. This one is more like LinePlot now.
// The X is always time
pub struct ScatterPlotV2 {
draw_data: Drawable,
series: Vec<SeriesState>,
draw_grid: Drawable,
top_left: ScreenPt,
dims: ScreenDims,
}
struct SeriesState {
label: String,
enabled: bool,
draw: Drawable,
}
impl ScatterPlotV2 {
pub fn new<T: Yvalue<T>>(ctx: &EventCtx, data: Series<T>, opts: PlotOptions<T>) -> Widget {
// id must be unique in a Composite
pub fn new<T: Yvalue<T>>(
ctx: &EventCtx,
id: &str,
series: Vec<Series<T>>,
opts: PlotOptions<T>,
) -> Widget {
let legend = if series.len() == 1 {
let radius = 15.0;
// Can't hide if there's just one series
Widget::row(vec![
Widget::draw_batch(
ctx,
GeomBatch::from(vec![(
series[0].color,
Circle::new(Pt2D::new(radius, radius), Distance::meters(radius))
.to_polygon(),
)]),
)
.margin(5),
series[0].label.clone().draw_text(ctx),
])
} else {
let mut row = Vec::new();
for s in &series {
row.push(Widget::row(vec![
Widget::new(Box::new(
Checkbox::colored(ctx, &s.label, s.color, true)
.take_checkbox()
.callback_to_plot(id, &s.label),
))
// TODO Messy! We have to remember to repeat what Checkbox::text does,
// because we used take_checkbox
.named(&s.label)
.margin_right(8),
Line(&s.label).draw(ctx),
]));
}
Widget::row(row).flex_wrap(ctx, 24)
};
// Assume min_x is Time::START_OF_DAY and min_y is T::zero()
let max_x = opts.max_x.unwrap_or_else(|| {
data.pts
series
.iter()
.map(|(t, _)| *t)
.map(|s| {
s.pts
.iter()
.map(|(t, _)| *t)
.max()
.unwrap_or(Time::START_OF_DAY)
})
.max()
.unwrap_or(Time::START_OF_DAY)
});
let max_y = opts.max_y.unwrap_or_else(|| {
data.pts
series
.iter()
.map(|(_, value)| *value)
.map(|s| {
s.pts
.iter()
.map(|(_, value)| *value)
.max()
.unwrap_or(T::zero())
})
.max()
.unwrap_or(T::zero())
});
@ -255,20 +313,28 @@ impl ScatterPlotV2 {
}
}
let mut batch = GeomBatch::new();
let circle = Circle::new(Pt2D::new(0.0, 0.0), Distance::meters(4.0)).to_polygon();
for (t, y) in data.pts {
let percent_x = t.to_percent(max_x);
let percent_y = y.to_percent(max_y);
// Y inversion
batch.push(
data.color,
circle.translate(percent_x * width, (1.0 - percent_y) * height),
);
let mut series_state = Vec::new();
for s in series {
let mut batch = GeomBatch::new();
for (t, y) in s.pts {
let percent_x = t.to_percent(max_x);
let percent_y = y.to_percent(max_y);
// Y inversion
batch.push(
s.color,
circle.translate(percent_x * width, (1.0 - percent_y) * height),
);
}
series_state.push(SeriesState {
label: s.label,
enabled: true,
draw: batch.upload(ctx),
});
}
let plot = ScatterPlotV2 {
draw_data: ctx.upload(batch),
series: series_state,
draw_grid: ctx.upload(grid_batch),
top_left: ScreenPt::new(0.0, 0.0),
@ -301,8 +367,11 @@ impl ScatterPlotV2 {
// Don't let the x-axis fill the parent container
Widget::row(vec![Widget::col(vec![
Line(data.label).draw(ctx),
Widget::row(vec![y_axis.evenly_spaced(), Widget::new(Box::new(plot))]),
legend,
Widget::row(vec![
y_axis.evenly_spaced(),
Widget::new(Box::new(plot)).named(id),
]),
x_axis.evenly_spaced(),
])])
}
@ -321,6 +390,30 @@ impl WidgetImpl for ScatterPlotV2 {
fn draw(&self, g: &mut GfxCtx) {
g.redraw_at(self.top_left, &self.draw_grid);
g.redraw_at(self.top_left, &self.draw_data);
for series in &self.series {
if series.enabled {
g.redraw_at(self.top_left, &series.draw);
}
}
}
fn update_series(&mut self, label: String, enabled: bool) {
for series in &mut self.series {
if series.label == label {
series.enabled = enabled;
return;
}
}
panic!("ScatterPlotV2 doesn't have a series {}", label);
}
fn can_restore(&self) -> bool {
true
}
fn restore(&mut self, _: &mut EventCtx, prev: &Box<dyn WidgetImpl>) {
let prev = prev.downcast_ref::<ScatterPlotV2>().unwrap();
for (s1, s2) in self.series.iter_mut().zip(prev.series.iter()) {
s1.enabled = s2.enabled;
}
}
}

View File

@ -1,10 +1,12 @@
use crate::app::App;
use crate::helpers::color_for_mode;
use crate::info::{header_btns, make_tabs, throughput, DataOptions, Details, Tab};
use abstutil::prettyprint_usize;
use ezgui::{Color, EventCtx, GeomBatch, Line, PlotOptions, ScatterPlotV2, Series, Text, Widget};
use geom::{ArrowCap, Distance, PolyLine};
use geom::{ArrowCap, Distance, Duration, PolyLine, Time};
use map_model::{IntersectionID, IntersectionType};
use std::collections::BTreeSet;
use sim::TripMode;
use std::collections::{BTreeMap, BTreeSet};
pub fn info(ctx: &EventCtx, app: &App, details: &mut Details, id: IntersectionID) -> Vec<Widget> {
let mut rows = header(ctx, app, details, id, Tab::IntersectionInfo(id));
@ -157,33 +159,29 @@ pub fn current_demand(
// TODO a fan chart might be nicer
fn delay_plot(ctx: &EventCtx, app: &App, i: IntersectionID, opts: &DataOptions) -> Widget {
let series = if opts.show_before {
Series {
label: "Delay through intersection (before changes)".to_string(),
color: Color::BLUE.alpha(0.9),
pts: app
.prebaked()
.intersection_delays
.get(&i)
.cloned()
.unwrap_or_else(Vec::new),
}
let data = if opts.show_before {
app.prebaked()
} else {
Series {
label: "Delay through intersection (after changes)".to_string(),
color: Color::RED.alpha(0.9),
pts: app
.primary
.sim
.get_analytics()
.intersection_delays
.get(&i)
.cloned()
.unwrap_or_else(Vec::new),
}
app.primary.sim.get_analytics()
};
ScatterPlotV2::new(ctx, series, PlotOptions::new())
let mut by_mode: BTreeMap<TripMode, Vec<(Time, Duration)>> = TripMode::all()
.into_iter()
.map(|m| (m, Vec::new()))
.collect();
if let Some(list) = data.intersection_delays.get(&i) {
for (t, dt, mode) in list {
by_mode.get_mut(mode).unwrap().push((*t, *dt));
}
}
let series: Vec<Series<Duration>> = by_mode
.into_iter()
.map(|(mode, pts)| Series {
label: mode.ongoing_verb().to_string(),
color: color_for_mode(app, mode),
pts,
})
.collect();
ScatterPlotV2::new(ctx, "delay", series, PlotOptions::new())
}
fn header(

View File

@ -23,7 +23,7 @@ pub struct Analytics {
pub finished_trips: Vec<(Time, TripID, Option<TripMode>, Duration)>,
// TODO This subsumes finished_trips
pub trip_log: Vec<(Time, TripID, Option<PathRequest>, TripPhaseType)>,
pub intersection_delays: BTreeMap<IntersectionID, Vec<(Time, Duration)>>,
pub intersection_delays: BTreeMap<IntersectionID, Vec<(Time, Duration, TripMode)>>,
// Per parking lane or lot, when does a spot become filled (true) or free (false)
pub parking_lane_changes: BTreeMap<LaneID, Vec<(Time, bool)>>,
pub parking_lot_changes: BTreeMap<ParkingLotID, Vec<(Time, bool)>>,
@ -115,11 +115,11 @@ impl Analytics {
}
// Intersection delays
if let Event::IntersectionDelayMeasured(id, delay) = ev {
if let Event::IntersectionDelayMeasured(id, delay, mode) = ev {
self.intersection_delays
.entry(id)
.or_insert_with(Vec::new)
.push((time, delay));
.push((time, delay, mode));
}
// Parking spot changes
@ -236,7 +236,7 @@ impl Analytics {
for (i, list1) in &self.intersection_delays {
if let Some(list2) = before.intersection_delays.get(i) {
let mut sum1 = Duration::ZERO;
for (t, dt) in list1 {
for (t, dt, _) in list1 {
if *t > now {
break;
}
@ -244,7 +244,7 @@ impl Analytics {
}
let mut sum2 = Duration::ZERO;
for (t, dt) in list2 {
for (t, dt, _) in list2 {
if *t > now {
break;
}

View File

@ -30,7 +30,7 @@ pub enum Event {
BikeStoppedAtSidewalk(CarID, LaneID),
AgentEntersTraversable(AgentID, Traversable),
IntersectionDelayMeasured(IntersectionID, Duration),
IntersectionDelayMeasured(IntersectionID, Duration, TripMode),
TripFinished {
trip: TripID,

View File

@ -1,6 +1,6 @@
use crate::mechanics::car::Car;
use crate::mechanics::Queue;
use crate::{AgentID, AlertLocation, CarID, Command, Event, Scheduler, Speed};
use crate::{AgentID, AlertLocation, CarID, Command, Event, Scheduler, Speed, TripMode};
use abstutil::{deserialize_btreemap, retain_btreeset, serialize_btreemap};
use geom::{Duration, Time};
use map_model::{
@ -291,8 +291,11 @@ impl IntersectionSimState {
let state = self.state.get_mut(&turn.parent).unwrap();
let delay = now - state.waiting.remove(&req).unwrap();
if map.maybe_get_traffic_signal(state.id).is_some() {
self.events
.push(Event::IntersectionDelayMeasured(turn.parent, delay));
self.events.push(Event::IntersectionDelayMeasured(
turn.parent,
delay,
TripMode::from_agent(agent),
));
}
state.accepted.insert(req);
if self.break_turn_conflict_cycles {