mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-27 16:36:02 +03:00
break down delay by agent
This commit is contained in:
parent
e059ebdb36
commit
14c7518787
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ pub enum Event {
|
||||
BikeStoppedAtSidewalk(CarID, LaneID),
|
||||
|
||||
AgentEntersTraversable(AgentID, Traversable),
|
||||
IntersectionDelayMeasured(IntersectionID, Duration),
|
||||
IntersectionDelayMeasured(IntersectionID, Duration, TripMode),
|
||||
|
||||
TripFinished {
|
||||
trip: TripID,
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user