mirror of
https://github.com/a-b-street/abstreet.git
synced 2025-01-08 07:41:57 +03:00
try showing delay measurements as a raw scatter plot
This commit is contained in:
parent
8d0981587a
commit
10bc4b9c32
@ -68,7 +68,7 @@ pub(crate) use crate::widgets::just_draw::JustDraw;
|
||||
pub use crate::widgets::line_plot::{LinePlot, PlotOptions, Series};
|
||||
pub(crate) use crate::widgets::menu::Menu;
|
||||
pub use crate::widgets::persistent_split::PersistentSplit;
|
||||
pub use crate::widgets::scatter_plot::ScatterPlot;
|
||||
pub use crate::widgets::scatter_plot::{ScatterPlot, ScatterPlotV2};
|
||||
pub use crate::widgets::slider::Slider;
|
||||
pub use crate::widgets::spinner::Spinner;
|
||||
pub(crate) use crate::widgets::text_box::TextBox;
|
||||
|
@ -106,7 +106,7 @@ impl<T: Yvalue<T>> LinePlot<T> {
|
||||
|
||||
// TODO Tuned to fit the info panel. Instead these should somehow stretch to fill their
|
||||
// container.
|
||||
let width = 0.25 * ctx.canvas.window_width;
|
||||
let width = 0.23 * ctx.canvas.window_width;
|
||||
let height = 0.2 * ctx.canvas.window_height;
|
||||
|
||||
let mut grid_batch = GeomBatch::new();
|
||||
@ -201,7 +201,7 @@ impl<T: Yvalue<T>> LinePlot<T> {
|
||||
draw_grid: ctx.upload(grid_batch),
|
||||
closest,
|
||||
max_x,
|
||||
max_y: max_y,
|
||||
max_y,
|
||||
|
||||
top_left: ScreenPt::new(0.0, 0.0),
|
||||
dims: ScreenDims::new(width, height),
|
||||
|
@ -1,8 +1,9 @@
|
||||
use crate::widgets::line_plot::Yvalue;
|
||||
use crate::{
|
||||
Color, Drawable, EventCtx, GeomBatch, GfxCtx, JustDraw, Line, ScreenDims, ScreenPt,
|
||||
ScreenRectangle, Text, TextExt, Widget, WidgetImpl, WidgetOutput,
|
||||
Color, Drawable, EventCtx, GeomBatch, GfxCtx, JustDraw, Line, PlotOptions, ScreenDims,
|
||||
ScreenPt, ScreenRectangle, Series, Text, TextExt, Widget, WidgetImpl, WidgetOutput,
|
||||
};
|
||||
use geom::{Angle, Circle, Distance, Duration, Pt2D};
|
||||
use geom::{Angle, Circle, Distance, Duration, PolyLine, Pt2D, Time};
|
||||
|
||||
// TODO This is tuned for the trip time comparison right now.
|
||||
// - Generic types for x and y axis
|
||||
@ -179,3 +180,150 @@ impl WidgetImpl for ScatterPlot {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Dedupe
|
||||
|
||||
// The X is always time
|
||||
pub struct ScatterPlotV2 {
|
||||
draw_data: Drawable,
|
||||
draw_grid: Drawable,
|
||||
|
||||
top_left: ScreenPt,
|
||||
dims: ScreenDims,
|
||||
}
|
||||
|
||||
impl ScatterPlotV2 {
|
||||
pub fn new<T: Yvalue<T>>(ctx: &EventCtx, data: Series<T>, opts: PlotOptions<T>) -> Widget {
|
||||
// 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
|
||||
.iter()
|
||||
.map(|(t, _)| *t)
|
||||
.max()
|
||||
.unwrap_or(Time::START_OF_DAY)
|
||||
});
|
||||
let max_y = opts.max_y.unwrap_or_else(|| {
|
||||
data.pts
|
||||
.iter()
|
||||
.map(|(_, value)| *value)
|
||||
.max()
|
||||
.unwrap_or(T::zero())
|
||||
});
|
||||
|
||||
// TODO Tuned to fit the info panel. Instead these should somehow stretch to fill their
|
||||
// container.
|
||||
let width = 0.23 * ctx.canvas.window_width;
|
||||
let height = 0.2 * ctx.canvas.window_height;
|
||||
|
||||
let mut grid_batch = GeomBatch::new();
|
||||
// Grid lines for the Y scale. Draw up to 10 lines max to cover the order of magnitude of
|
||||
// the range.
|
||||
// TODO This caps correctly, but if the max is 105, then suddenly we just have 2 grid
|
||||
// lines.
|
||||
{
|
||||
let order_of_mag = 10.0_f64.powf(max_y.to_f64().log10().ceil());
|
||||
for i in 0..10 {
|
||||
let y = max_y.from_f64(order_of_mag / 10.0 * (i as f64));
|
||||
let pct = y.to_percent(max_y);
|
||||
if pct > 1.0 {
|
||||
break;
|
||||
}
|
||||
grid_batch.push(
|
||||
Color::BLACK,
|
||||
PolyLine::new(vec![
|
||||
Pt2D::new(0.0, (1.0 - pct) * height),
|
||||
Pt2D::new(width, (1.0 - pct) * height),
|
||||
])
|
||||
.make_polygons(Distance::meters(5.0)),
|
||||
);
|
||||
}
|
||||
}
|
||||
// X axis grid
|
||||
if max_x != Time::START_OF_DAY {
|
||||
let order_of_mag = 10.0_f64.powf(max_x.inner_seconds().log10().ceil());
|
||||
for i in 0..10 {
|
||||
let x = Time::START_OF_DAY + Duration::seconds(order_of_mag / 10.0 * (i as f64));
|
||||
let pct = x.to_percent(max_x);
|
||||
if pct > 1.0 {
|
||||
break;
|
||||
}
|
||||
grid_batch.push(
|
||||
Color::BLACK,
|
||||
PolyLine::new(vec![
|
||||
Pt2D::new(pct * width, 0.0),
|
||||
Pt2D::new(pct * width, height),
|
||||
])
|
||||
.make_polygons(Distance::meters(5.0)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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 plot = ScatterPlotV2 {
|
||||
draw_data: ctx.upload(batch),
|
||||
draw_grid: ctx.upload(grid_batch),
|
||||
|
||||
top_left: ScreenPt::new(0.0, 0.0),
|
||||
dims: ScreenDims::new(width, height),
|
||||
};
|
||||
|
||||
let num_x_labels = 3;
|
||||
let mut row = Vec::new();
|
||||
for i in 0..num_x_labels {
|
||||
let percent_x = (i as f64) / ((num_x_labels - 1) as f64);
|
||||
let t = max_x.percent_of(percent_x);
|
||||
// TODO Need ticks now to actually see where this goes
|
||||
let mut batch = GeomBatch::new();
|
||||
for (color, poly) in Text::from(Line(t.to_string())).render_ctx(ctx).consume() {
|
||||
batch.fancy_push(color, poly.rotate(Angle::new_degs(-15.0)));
|
||||
}
|
||||
// The text is already scaled; don't use Widget::draw_batch and scale it again.
|
||||
row.push(JustDraw::wrap(ctx, batch.autocrop()));
|
||||
}
|
||||
let x_axis = Widget::row(row).padding(10);
|
||||
|
||||
let num_y_labels = 4;
|
||||
let mut col = Vec::new();
|
||||
for i in 0..num_y_labels {
|
||||
let percent_y = (i as f64) / ((num_y_labels - 1) as f64);
|
||||
col.push(max_y.from_percent(percent_y).prettyprint().draw_text(ctx));
|
||||
}
|
||||
col.reverse();
|
||||
let y_axis = Widget::col(col).padding(10);
|
||||
|
||||
// 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))]),
|
||||
x_axis.evenly_spaced(),
|
||||
])])
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetImpl for ScatterPlotV2 {
|
||||
fn get_dims(&self) -> ScreenDims {
|
||||
self.dims
|
||||
}
|
||||
|
||||
fn set_pos(&mut self, top_left: ScreenPt) {
|
||||
self.top_left = top_left;
|
||||
}
|
||||
|
||||
fn event(&mut self, _ctx: &mut EventCtx, _output: &mut WidgetOutput) {}
|
||||
|
||||
fn draw(&self, g: &mut GfxCtx) {
|
||||
g.redraw_at(self.top_left, &self.draw_grid);
|
||||
g.redraw_at(self.top_left, &self.draw_data);
|
||||
}
|
||||
}
|
||||
|
@ -2,12 +2,11 @@ use crate::app::App;
|
||||
use crate::info::{header_btns, make_tabs, throughput, DataOptions, Details, Tab};
|
||||
use abstutil::prettyprint_usize;
|
||||
use ezgui::{
|
||||
Color, EventCtx, GeomBatch, Line, LinePlot, PlotOptions, RewriteColor, Series, Text, Widget,
|
||||
Color, EventCtx, GeomBatch, Line, PlotOptions, RewriteColor, ScatterPlotV2, Series, Text,
|
||||
Widget,
|
||||
};
|
||||
use geom::{Angle, ArrowCap, Distance, PolyLine};
|
||||
use geom::{Duration, Statistic, Time};
|
||||
use map_model::{IntersectionID, IntersectionType};
|
||||
use sim::Analytics;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
pub fn info(ctx: &EventCtx, app: &App, details: &mut Details, id: IntersectionID) -> Vec<Widget> {
|
||||
@ -166,49 +165,35 @@ pub fn current_demand(
|
||||
rows
|
||||
}
|
||||
|
||||
// TODO a fan chart might be nicer
|
||||
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, Duration::hours(1)) {
|
||||
for (stat, pts) in series.iter_mut() {
|
||||
if distrib.count() == 0 {
|
||||
pts.push((t, Duration::ZERO));
|
||||
} else {
|
||||
pts.push((t, distrib.select(*stat)));
|
||||
}
|
||||
}
|
||||
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),
|
||||
}
|
||||
} 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),
|
||||
}
|
||||
series
|
||||
};
|
||||
|
||||
let mut all_series = Vec::new();
|
||||
for (idx, (stat, pts)) in get_data(app.primary.sim.get_analytics(), app.primary.sim.time())
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
{
|
||||
all_series.push(Series {
|
||||
label: stat.to_string(),
|
||||
color: app.cs.rotating_color_plot(idx),
|
||||
pts,
|
||||
});
|
||||
}
|
||||
if opts.show_before {
|
||||
for (idx, (stat, pts)) in get_data(app.prebaked(), app.primary.sim.get_end_of_day())
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
{
|
||||
all_series.push(Series {
|
||||
label: format!("{} (before changes)", stat),
|
||||
color: app.cs.rotating_color_plot(idx).alpha(0.3),
|
||||
pts,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
LinePlot::new(ctx, "delay", all_series, PlotOptions::new())
|
||||
ScatterPlotV2::new(ctx, series, PlotOptions::new())
|
||||
}
|
||||
|
||||
fn header(
|
||||
@ -242,7 +227,10 @@ fn header(
|
||||
),
|
||||
];
|
||||
if i.is_traffic_signal() {
|
||||
tabs.push(("Delay", Tab::IntersectionDelay(id, DataOptions::new(app))));
|
||||
tabs.push((
|
||||
"Delay",
|
||||
Tab::IntersectionDelay(id, DataOptions { show_before: false }),
|
||||
));
|
||||
tabs.push(("Current demand", Tab::IntersectionDemand(id)));
|
||||
}
|
||||
tabs
|
||||
|
Loading…
Reference in New Issue
Block a user