move intersection demand layer to info panel

This commit is contained in:
Dustin Carlino 2020-05-19 11:55:28 -07:00
parent 977d78775d
commit 2120035d26
6 changed files with 87 additions and 136 deletions

View File

@ -88,7 +88,7 @@ impl State for RouteSelect {
let roads = if let Some(roads) = pathfind(&app.primary.map, i1, i2) {
for r in &roads {
batch.push(
Color::RED,
Color::RED.alpha(0.5),
app.primary
.map
.get_r(*r)
@ -219,7 +219,7 @@ impl BulkEdit {
.align_right(),
])
.margin_below(5),
Btn::text_fg("Quit").build_def(ctx, None),
Btn::text_fg("Quit").build_def(ctx, hotkey(Key::Escape)),
])
.bg(app.cs.panel_bg)
.padding(10),
@ -374,7 +374,7 @@ impl State for PaintSelect {
let mut batch = GeomBatch::new();
for r in &self.roads {
batch.push(
Color::BLUE,
Color::BLUE.alpha(0.5),
app.primary
.map
.get_r(*r)

View File

@ -1,7 +1,10 @@
use crate::app::App;
use crate::info::{header_btns, make_tabs, throughput, DataOptions, Details, Tab};
use abstutil::prettyprint_usize;
use ezgui::{EventCtx, Line, LinePlot, PlotOptions, Series, Text, Widget};
use ezgui::{
Color, EventCtx, GeomBatch, Line, LinePlot, PlotOptions, RewriteColor, Series, Text, Widget,
};
use geom::{Angle, ArrowCap, Distance, PolyLine};
use geom::{Duration, Statistic, Time};
use map_model::{IntersectionID, IntersectionType};
use sim::Analytics;
@ -96,6 +99,71 @@ pub fn delay(
rows
}
pub fn current_demand(
ctx: &mut EventCtx,
app: &App,
details: &mut Details,
id: IntersectionID,
) -> Vec<Widget> {
let mut rows = header(ctx, app, details, id, Tab::IntersectionDemand(id));
let mut total_demand = 0;
let mut demand_per_group: Vec<(&PolyLine, usize)> = Vec::new();
for g in app.primary.map.get_traffic_signal(id).turn_groups.values() {
let demand = app
.primary
.sim
.get_analytics()
.demand
.get(&g.id)
.cloned()
.unwrap_or(0);
if demand > 0 {
total_demand += demand;
demand_per_group.push((&g.geom, demand));
}
}
let mut batch = GeomBatch::new();
let polygon = app.primary.map.get_i(id).polygon.clone();
let bounds = polygon.get_bounds();
// Pick a zoom so that we fit a fixed width in pixels
let zoom = 300.0 / bounds.width();
batch.push(app.cs.normal_intersection, polygon);
for (pl, demand) in demand_per_group {
let percent = (demand as f64) / (total_demand as f64);
batch.push(
Color::RED,
pl.make_arrow(percent * Distance::meters(3.0), ArrowCap::Triangle)
.unwrap(),
);
batch.add_transformed(
Text::from(Line(prettyprint_usize(demand))).render_ctx(ctx),
pl.middle(),
0.15,
Angle::ZERO,
RewriteColor::NoOp,
);
}
let mut transformed_batch = GeomBatch::new();
for (color, poly) in batch.consume() {
transformed_batch.fancy_push(
color,
poly.translate(-bounds.min_x, -bounds.min_y).scale(zoom),
);
}
let mut txt = Text::from(Line(format!(
"How many active trips will cross this intersection?"
)));
txt.add(Line(format!("Total: {}", prettyprint_usize(total_demand))).secondary());
rows.push(txt.draw(ctx));
rows.push(Widget::draw_batch(ctx, transformed_batch));
rows
}
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()
@ -173,6 +241,7 @@ fn header(
];
if i.is_traffic_signal() {
tabs.push(("Delay", Tab::IntersectionDelay(id, DataOptions::new(app))));
tabs.push(("Current demand", Tab::IntersectionDemand(id)));
}
tabs
}));

View File

@ -67,6 +67,7 @@ pub enum Tab {
IntersectionInfo(IntersectionID),
IntersectionTraffic(IntersectionID, DataOptions),
IntersectionDelay(IntersectionID, DataOptions),
IntersectionDemand(IntersectionID),
LaneInfo(LaneID),
LaneDebug(LaneID),
@ -134,7 +135,8 @@ impl Tab {
Tab::Area(a) => Some(ID::Area(a)),
Tab::IntersectionInfo(i)
| Tab::IntersectionTraffic(i, _)
| Tab::IntersectionDelay(i, _) => Some(ID::Intersection(i)),
| Tab::IntersectionDelay(i, _)
| Tab::IntersectionDemand(i) => Some(ID::Intersection(i)),
Tab::LaneInfo(l) | Tab::LaneDebug(l) | Tab::LaneTraffic(l, _) => Some(ID::Lane(l)),
}
}
@ -209,6 +211,10 @@ impl InfoPanel {
Tab::IntersectionDelay(i, ref opts) => {
(intersection::delay(ctx, app, &mut details, i, opts), false)
}
Tab::IntersectionDemand(i) => (
intersection::current_demand(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, ref opts) => {

View File

@ -28,9 +28,9 @@ pub trait Layer {
fn draw_minimap(&self, g: &mut GfxCtx);
}
// TODO Just return a bool for closed? Less readable...
pub enum LayerOutcome {
Close,
Transition(Transition),
}
pub struct PickLayer {
@ -50,10 +50,6 @@ impl PickLayer {
app.layer = None;
return None;
}
Some(LayerOutcome::Transition(t)) => {
app.layer = Some(layer);
return Some(t);
}
None => {}
}
app.layer = Some(layer);

View File

@ -1,16 +1,13 @@
use crate::app::App;
use crate::common::{ColorLegend, Colorer, Warping};
use crate::game::Transition;
use crate::helpers::ID;
use crate::common::{ColorLegend, Colorer};
use crate::layer::{Layer, LayerOutcome};
use abstutil::{prettyprint_usize, Counter};
use abstutil::Counter;
use ezgui::{
hotkey, Btn, Checkbox, Color, Composite, Drawable, EventCtx, GeomBatch, GfxCtx,
HorizontalAlignment, Key, Line, Outcome, RewriteColor, Text, TextExt, VerticalAlignment,
Widget,
hotkey, Btn, Checkbox, Color, Composite, Drawable, EventCtx, GfxCtx, HorizontalAlignment, Key,
Outcome, TextExt, VerticalAlignment, Widget,
};
use geom::{Angle, ArrowCap, Distance, Duration, PolyLine, Time};
use map_model::{IntersectionID, Traversable};
use geom::{Duration, Time};
use map_model::Traversable;
pub struct Dynamic {
time: Time,
@ -465,113 +462,3 @@ impl Throughput {
}
}
}
// TODO Does this make sense as a layer? Should it be an info panel tab?
pub struct IntersectionDemand {
time: Time,
i: IntersectionID,
draw: Drawable,
composite: Composite,
}
impl Layer for IntersectionDemand {
fn name(&self) -> Option<&'static str> {
None
}
fn event(
&mut self,
ctx: &mut EventCtx,
app: &mut App,
minimap: &Composite,
) -> Option<LayerOutcome> {
if app.primary.sim.time() != self.time {
*self = IntersectionDemand::new(ctx, app, self.i);
}
self.composite.align_above(ctx, minimap);
match self.composite.event(ctx) {
Some(Outcome::Clicked(x)) => match x.as_ref() {
"intersection demand" => {
let id = ID::Intersection(self.i);
return Some(LayerOutcome::Transition(Transition::Push(Warping::new(
ctx,
id.canonical_point(&app.primary).unwrap(),
Some(10.0),
Some(id.clone()),
&mut app.primary,
))));
}
"X" => {
return Some(LayerOutcome::Close);
}
_ => unreachable!(),
},
None => {}
}
None
}
fn draw(&self, g: &mut GfxCtx, _: &App) {
self.composite.draw(g);
g.redraw(&self.draw);
}
fn draw_minimap(&self, _: &mut GfxCtx) {}
}
impl IntersectionDemand {
pub fn new(ctx: &mut EventCtx, app: &App, i: IntersectionID) -> IntersectionDemand {
let mut batch = GeomBatch::new();
let mut total_demand = 0;
let mut demand_per_group: Vec<(&PolyLine, usize)> = Vec::new();
for g in app.primary.map.get_traffic_signal(i).turn_groups.values() {
let demand = app
.primary
.sim
.get_analytics()
.demand
.get(&g.id)
.cloned()
.unwrap_or(0);
if demand > 0 {
total_demand += demand;
demand_per_group.push((&g.geom, demand));
}
}
for (pl, demand) in demand_per_group {
let percent = (demand as f64) / (total_demand as f64);
batch.push(
Color::RED,
pl.make_arrow(percent * Distance::meters(5.0), ArrowCap::Triangle)
.unwrap(),
);
batch.add_transformed(
Text::from(Line(prettyprint_usize(demand))).render_ctx(ctx),
pl.middle(),
0.08,
Angle::ZERO,
RewriteColor::NoOp,
);
}
let col = vec![
Widget::row(vec![
"intersection demand".draw_text(ctx),
Btn::svg_def("../data/system/assets/tools/location.svg")
.build(ctx, "intersection demand", None)
.margin(5),
Btn::text_fg("X").build_def(ctx, None).align_right(),
]),
ColorLegend::row(ctx, Color::RED, "current demand"),
];
IntersectionDemand {
time: app.primary.sim.time(),
i,
draw: batch.upload(ctx),
composite: Composite::new(Widget::col(col).bg(app.cs.panel_bg))
.aligned(HorizontalAlignment::Right, VerticalAlignment::Center)
.build(ctx),
}
}
}

View File

@ -383,7 +383,6 @@ impl ContextualActions for Actions {
ID::Intersection(i) => {
if app.primary.map.get_i(i).is_traffic_signal() {
actions.push((Key::F, "explore traffic signal details".to_string()));
actions.push((Key::C, "show current demand".to_string()));
actions.push((Key::E, "edit traffic signal".to_string()));
}
if app.primary.map.get_i(i).is_stop_sign() {
@ -423,12 +422,6 @@ impl ContextualActions for Actions {
(ID::Intersection(i), "explore traffic signal details") => {
Transition::Push(ShowTrafficSignal::new(ctx, app, i))
}
(ID::Intersection(i), "show current demand") => {
app.layer = Some(Box::new(crate::layer::traffic::IntersectionDemand::new(
ctx, app, i,
)));
Transition::Keep
}
(ID::Intersection(i), "edit traffic signal") => Transition::PushTwice(
Box::new(EditMode::new(ctx, app, self.gameplay.clone())),
Box::new(TrafficSignalEditor::new(i, ctx, app)),