mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-25 23:43:25 +03:00
unifying parking availability, intersection delay, thruput stats into one exclusive thing that consistently auto-updates
This commit is contained in:
parent
b40b26def8
commit
85ebb225cd
265
game/src/sandbox/analytics.rs
Normal file
265
game/src/sandbox/analytics.rs
Normal file
@ -0,0 +1,265 @@
|
||||
use crate::common::{ObjectColorer, ObjectColorerBuilder, RoadColorer, RoadColorerBuilder};
|
||||
use crate::game::{Transition, WizardState};
|
||||
use crate::helpers::ID;
|
||||
use crate::sandbox::SandboxMode;
|
||||
use crate::ui::UI;
|
||||
use abstutil::Counter;
|
||||
use ezgui::{Choice, Color, EventCtx, GfxCtx, ModalMenu};
|
||||
use geom::Duration;
|
||||
use map_model::{IntersectionID, RoadID, Traversable};
|
||||
use sim::{Event, ParkingSpot};
|
||||
use std::collections::HashSet;
|
||||
|
||||
pub enum Analytics {
|
||||
Inactive,
|
||||
ParkingAvailability(Duration, RoadColorer),
|
||||
IntersectionDelay(Duration, ObjectColorer),
|
||||
Throughput(Duration, ObjectColorer),
|
||||
}
|
||||
|
||||
impl Analytics {
|
||||
pub fn event(
|
||||
&mut self,
|
||||
ctx: &mut EventCtx,
|
||||
ui: &UI,
|
||||
menu: &mut ModalMenu,
|
||||
thruput_stats: &ThruputStats,
|
||||
) -> Option<Transition> {
|
||||
if menu.action("change analytics overlay") {
|
||||
return Some(Transition::Push(WizardState::new(Box::new(
|
||||
|wiz, ctx, _| {
|
||||
let (choice, _) =
|
||||
wiz.wrap(ctx).choose("Show which analytics overlay?", || {
|
||||
vec![
|
||||
Choice::new("none", ()),
|
||||
Choice::new("parking availability", ()),
|
||||
Choice::new("intersection delay", ()),
|
||||
Choice::new("cumulative throughput", ()),
|
||||
]
|
||||
})?;
|
||||
Some(Transition::PopWithData(Box::new(move |state, ui, ctx| {
|
||||
let mut sandbox = state.downcast_mut::<SandboxMode>().unwrap();
|
||||
sandbox.analytics =
|
||||
Analytics::recalc(&choice, &sandbox.thruput_stats, ui, ctx);
|
||||
})))
|
||||
},
|
||||
))));
|
||||
}
|
||||
|
||||
let (choice, time) = match self {
|
||||
Analytics::Inactive => {
|
||||
return None;
|
||||
}
|
||||
Analytics::ParkingAvailability(t, _) => ("parking availability", *t),
|
||||
Analytics::IntersectionDelay(t, _) => ("intersection delay", *t),
|
||||
Analytics::Throughput(t, _) => ("cumulative throughput", *t),
|
||||
};
|
||||
if time != ui.primary.sim.time() {
|
||||
*self = Analytics::recalc(choice, thruput_stats, ui, ctx);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
// True if active and should block normal drawing
|
||||
pub fn draw(&self, g: &mut GfxCtx, ui: &UI) -> bool {
|
||||
match self {
|
||||
Analytics::Inactive => false,
|
||||
Analytics::ParkingAvailability(_, ref heatmap) => {
|
||||
heatmap.draw(g, ui);
|
||||
true
|
||||
}
|
||||
Analytics::IntersectionDelay(_, ref heatmap)
|
||||
| Analytics::Throughput(_, ref heatmap) => {
|
||||
heatmap.draw(g, ui);
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn recalc(
|
||||
choice: &str,
|
||||
thruput_stats: &ThruputStats,
|
||||
ui: &UI,
|
||||
ctx: &mut EventCtx,
|
||||
) -> Analytics {
|
||||
let time = ui.primary.sim.time();
|
||||
match choice {
|
||||
"none" => Analytics::Inactive,
|
||||
"parking availability" => {
|
||||
Analytics::ParkingAvailability(time, calculate_parking_heatmap(ctx, ui))
|
||||
}
|
||||
"intersection delay" => {
|
||||
Analytics::IntersectionDelay(time, calculate_intersection_delay(ctx, ui))
|
||||
}
|
||||
"cumulative throughput" => {
|
||||
Analytics::Throughput(time, calculate_thruput(thruput_stats, ctx, ui))
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn calculate_parking_heatmap(ctx: &mut EventCtx, ui: &UI) -> RoadColorer {
|
||||
let awful = Color::BLACK;
|
||||
let bad = Color::RED;
|
||||
let meh = Color::YELLOW;
|
||||
let good = Color::GREEN;
|
||||
let mut colorer = RoadColorerBuilder::new(
|
||||
"parking availability",
|
||||
vec![
|
||||
("< 10%", awful),
|
||||
("< 30%", bad),
|
||||
("< 60%", meh),
|
||||
(">= 60%", good),
|
||||
],
|
||||
);
|
||||
|
||||
let lane = |spot| match spot {
|
||||
ParkingSpot::Onstreet(l, _) => l,
|
||||
ParkingSpot::Offstreet(b, _) => ui
|
||||
.primary
|
||||
.map
|
||||
.get_b(b)
|
||||
.parking
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.driving_pos
|
||||
.lane(),
|
||||
};
|
||||
|
||||
let mut filled = Counter::new();
|
||||
let mut avail = Counter::new();
|
||||
let mut keys = HashSet::new();
|
||||
let (filled_spots, avail_spots) = ui.primary.sim.get_all_parking_spots();
|
||||
for spot in filled_spots {
|
||||
let l = lane(spot);
|
||||
keys.insert(l);
|
||||
filled.inc(l);
|
||||
}
|
||||
for spot in avail_spots {
|
||||
let l = lane(spot);
|
||||
keys.insert(l);
|
||||
avail.inc(l);
|
||||
}
|
||||
|
||||
for l in keys {
|
||||
let open = avail.get(l);
|
||||
let closed = filled.get(l);
|
||||
let percent = (open as f64) / ((open + closed) as f64);
|
||||
let color = if percent >= 0.6 {
|
||||
good
|
||||
} else if percent > 0.3 {
|
||||
meh
|
||||
} else if percent > 0.1 {
|
||||
bad
|
||||
} else {
|
||||
awful
|
||||
};
|
||||
colorer.add(l, color, &ui.primary.map);
|
||||
}
|
||||
|
||||
colorer.build(ctx, &ui.primary.map)
|
||||
}
|
||||
|
||||
fn calculate_intersection_delay(ctx: &mut EventCtx, ui: &UI) -> ObjectColorer {
|
||||
let fast = Color::GREEN;
|
||||
let meh = Color::YELLOW;
|
||||
let slow = Color::RED;
|
||||
let mut colorer = ObjectColorerBuilder::new(
|
||||
"intersection delay (90%ile)",
|
||||
vec![("< 10s", fast), ("<= 60s", meh), ("> 60s", slow)],
|
||||
);
|
||||
|
||||
for i in ui.primary.map.all_intersections() {
|
||||
let delays = ui.primary.sim.get_intersection_delays(i.id);
|
||||
if let Some(d) = delays.percentile(90.0) {
|
||||
let color = if d < Duration::seconds(10.0) {
|
||||
fast
|
||||
} else if d <= Duration::seconds(60.0) {
|
||||
meh
|
||||
} else {
|
||||
slow
|
||||
};
|
||||
colorer.add(ID::Intersection(i.id), color);
|
||||
}
|
||||
}
|
||||
|
||||
colorer.build(ctx, &ui.primary.map)
|
||||
}
|
||||
|
||||
fn calculate_thruput(stats: &ThruputStats, ctx: &mut EventCtx, ui: &UI) -> ObjectColorer {
|
||||
let light = Color::GREEN;
|
||||
let medium = Color::YELLOW;
|
||||
let heavy = Color::RED;
|
||||
let mut colorer = ObjectColorerBuilder::new(
|
||||
"Throughput",
|
||||
vec![
|
||||
("< 50%ile", light),
|
||||
("< 90%ile", medium),
|
||||
(">= 90%ile", heavy),
|
||||
],
|
||||
);
|
||||
|
||||
// TODO If there are many duplicate counts, arbitrarily some will look heavier! Find the
|
||||
// disribution of counts instead.
|
||||
// TODO Actually display the counts at these percentiles
|
||||
// TODO Dump the data in debug mode
|
||||
{
|
||||
let roads = stats.count_per_road.sorted_asc();
|
||||
let p50_idx = ((roads.len() as f64) * 0.5) as usize;
|
||||
let p90_idx = ((roads.len() as f64) * 0.9) as usize;
|
||||
for (idx, r) in roads.into_iter().enumerate() {
|
||||
let color = if idx < p50_idx {
|
||||
light
|
||||
} else if idx < p90_idx {
|
||||
medium
|
||||
} else {
|
||||
heavy
|
||||
};
|
||||
colorer.add(ID::Road(*r), color);
|
||||
}
|
||||
}
|
||||
// TODO dedupe
|
||||
{
|
||||
let intersections = stats.count_per_intersection.sorted_asc();
|
||||
let p50_idx = ((intersections.len() as f64) * 0.5) as usize;
|
||||
let p90_idx = ((intersections.len() as f64) * 0.9) as usize;
|
||||
for (idx, i) in intersections.into_iter().enumerate() {
|
||||
let color = if idx < p50_idx {
|
||||
light
|
||||
} else if idx < p90_idx {
|
||||
medium
|
||||
} else {
|
||||
heavy
|
||||
};
|
||||
colorer.add(ID::Intersection(*i), color);
|
||||
}
|
||||
}
|
||||
|
||||
colorer.build(ctx, &ui.primary.map)
|
||||
}
|
||||
|
||||
pub struct ThruputStats {
|
||||
count_per_road: Counter<RoadID>,
|
||||
count_per_intersection: Counter<IntersectionID>,
|
||||
}
|
||||
|
||||
impl ThruputStats {
|
||||
pub fn new() -> ThruputStats {
|
||||
ThruputStats {
|
||||
count_per_road: Counter::new(),
|
||||
count_per_intersection: Counter::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn record(&mut self, ui: &mut UI) {
|
||||
for ev in ui.primary.sim.collect_events() {
|
||||
if let Event::AgentEntersTraversable(_, to) = ev {
|
||||
match to {
|
||||
Traversable::Lane(l) => self.count_per_road.inc(ui.primary.map.get_l(l).parent),
|
||||
Traversable::Turn(t) => self.count_per_intersection.inc(t.parent),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,36 +1,31 @@
|
||||
mod analytics;
|
||||
mod score;
|
||||
mod spawner;
|
||||
mod thruput_stats;
|
||||
mod time_travel;
|
||||
mod trip_stats;
|
||||
|
||||
use crate::common::{
|
||||
time_controls, AgentTools, CommonState, ObjectColorer, ObjectColorerBuilder, RoadColorer,
|
||||
RoadColorerBuilder, RouteExplorer, SpeedControls, TripExplorer,
|
||||
time_controls, AgentTools, CommonState, RouteExplorer, SpeedControls, TripExplorer,
|
||||
};
|
||||
use crate::debug::DebugMode;
|
||||
use crate::edit::EditMode;
|
||||
use crate::game::{State, Transition, WizardState};
|
||||
use crate::helpers::ID;
|
||||
use crate::ui::{PerMapUI, ShowEverything, UI};
|
||||
use abstutil::Counter;
|
||||
use crate::ui::{ShowEverything, UI};
|
||||
use ezgui::{
|
||||
hotkey, lctrl, Choice, Color, EventCtx, EventLoopMode, GfxCtx, Key, Line, ModalMenu, Text,
|
||||
Wizard,
|
||||
hotkey, lctrl, Choice, EventCtx, EventLoopMode, GfxCtx, Key, Line, ModalMenu, Text, Wizard,
|
||||
};
|
||||
use geom::Duration;
|
||||
use sim::{ParkingSpot, Sim};
|
||||
use std::collections::HashSet;
|
||||
use sim::Sim;
|
||||
|
||||
pub struct SandboxMode {
|
||||
speed: SpeedControls,
|
||||
agent_tools: AgentTools,
|
||||
pub time_travel: time_travel::InactiveTimeTravel,
|
||||
trip_stats: trip_stats::TripStats,
|
||||
thruput_stats: thruput_stats::ThruputStats,
|
||||
thruput_stats: analytics::ThruputStats,
|
||||
analytics: analytics::Analytics,
|
||||
common: CommonState,
|
||||
parking_heatmap: Option<(Duration, RoadColorer)>,
|
||||
intersection_delay_heatmap: Option<(Duration, ObjectColorer)>,
|
||||
menu: ModalMenu,
|
||||
}
|
||||
|
||||
@ -43,10 +38,9 @@ impl SandboxMode {
|
||||
trip_stats: trip_stats::TripStats::new(
|
||||
ui.primary.current_flags.sim_flags.opts.record_stats,
|
||||
),
|
||||
thruput_stats: thruput_stats::ThruputStats::new(),
|
||||
thruput_stats: analytics::ThruputStats::new(),
|
||||
analytics: analytics::Analytics::Inactive,
|
||||
common: CommonState::new(),
|
||||
parking_heatmap: None,
|
||||
intersection_delay_heatmap: None,
|
||||
menu: ModalMenu::new(
|
||||
"Sandbox Mode",
|
||||
vec![
|
||||
@ -68,12 +62,10 @@ impl SandboxMode {
|
||||
],
|
||||
vec![
|
||||
// TODO Strange to always have this. Really it's a case of stacked modal?
|
||||
(hotkey(Key::A), "show/hide parking availability"),
|
||||
(hotkey(Key::I), "show/hide intersection delay"),
|
||||
(hotkey(Key::T), "start time traveling"),
|
||||
(hotkey(Key::Q), "scoreboard"),
|
||||
(None, "trip stats"),
|
||||
(None, "throughput stats"),
|
||||
(hotkey(Key::L), "change analytics overlay"),
|
||||
],
|
||||
vec![
|
||||
(hotkey(Key::Escape), "quit"),
|
||||
@ -110,6 +102,12 @@ impl State for SandboxMode {
|
||||
if let Some(t) = self.common.event(ctx, ui, &mut self.menu) {
|
||||
return t;
|
||||
}
|
||||
if let Some(t) = self
|
||||
.analytics
|
||||
.event(ctx, ui, &mut self.menu, &self.thruput_stats)
|
||||
{
|
||||
return t;
|
||||
}
|
||||
|
||||
if let Some(new_state) = spawner::AgentSpawner::new(ctx, ui, &mut self.menu) {
|
||||
return Transition::Push(new_state);
|
||||
@ -137,55 +135,6 @@ impl State for SandboxMode {
|
||||
println!("No trip stats available");
|
||||
}
|
||||
}
|
||||
if self.menu.action("throughput stats") {
|
||||
return Transition::Push(Box::new(thruput_stats::ShowStats::new(
|
||||
&self.thruput_stats,
|
||||
ui,
|
||||
ctx,
|
||||
)));
|
||||
}
|
||||
if self.menu.action("show/hide parking availability") {
|
||||
if self.parking_heatmap.is_some() {
|
||||
self.parking_heatmap = None;
|
||||
} else {
|
||||
self.parking_heatmap = Some((
|
||||
ui.primary.sim.time(),
|
||||
calculate_parking_heatmap(ctx, &ui.primary),
|
||||
));
|
||||
}
|
||||
}
|
||||
if self
|
||||
.parking_heatmap
|
||||
.as_ref()
|
||||
.map(|(t, _)| *t != ui.primary.sim.time())
|
||||
.unwrap_or(false)
|
||||
{
|
||||
self.parking_heatmap = Some((
|
||||
ui.primary.sim.time(),
|
||||
calculate_parking_heatmap(ctx, &ui.primary),
|
||||
));
|
||||
}
|
||||
if self.menu.action("show/hide intersection delay") {
|
||||
if self.intersection_delay_heatmap.is_some() {
|
||||
self.intersection_delay_heatmap = None;
|
||||
} else {
|
||||
self.intersection_delay_heatmap = Some((
|
||||
ui.primary.sim.time(),
|
||||
calculate_intersection_delay(ctx, &ui.primary),
|
||||
));
|
||||
}
|
||||
}
|
||||
if self
|
||||
.intersection_delay_heatmap
|
||||
.as_ref()
|
||||
.map(|(t, _)| *t != ui.primary.sim.time())
|
||||
.unwrap_or(false)
|
||||
{
|
||||
self.intersection_delay_heatmap = Some((
|
||||
ui.primary.sim.time(),
|
||||
calculate_intersection_delay(ctx, &ui.primary),
|
||||
));
|
||||
}
|
||||
|
||||
if self.menu.action("quit") {
|
||||
return Transition::Pop;
|
||||
@ -293,11 +242,8 @@ impl State for SandboxMode {
|
||||
}
|
||||
|
||||
fn draw(&self, g: &mut GfxCtx, ui: &UI) {
|
||||
// TODO Oh no, these're actually exclusive, represent that better.
|
||||
if let Some((_, ref c)) = self.parking_heatmap {
|
||||
c.draw(g, ui);
|
||||
} else if let Some((_, ref c)) = self.intersection_delay_heatmap {
|
||||
c.draw(g, ui);
|
||||
if self.analytics.draw(g, ui) {
|
||||
// Don't draw agent tools!
|
||||
} else {
|
||||
ui.draw(
|
||||
g,
|
||||
@ -305,9 +251,9 @@ impl State for SandboxMode {
|
||||
&ui.primary.sim,
|
||||
&ShowEverything::new(),
|
||||
);
|
||||
self.agent_tools.draw(g, ui);
|
||||
}
|
||||
self.common.draw(g, ui);
|
||||
self.agent_tools.draw(g, ui);
|
||||
self.menu.draw(g);
|
||||
self.speed.draw(g);
|
||||
}
|
||||
@ -330,90 +276,3 @@ fn load_savestate(wiz: &mut Wizard, ctx: &mut EventCtx, ui: &mut UI) -> Option<T
|
||||
});
|
||||
Some(Transition::Pop)
|
||||
}
|
||||
|
||||
fn calculate_parking_heatmap(ctx: &mut EventCtx, primary: &PerMapUI) -> RoadColorer {
|
||||
let awful = Color::BLACK;
|
||||
let bad = Color::RED;
|
||||
let meh = Color::YELLOW;
|
||||
let good = Color::GREEN;
|
||||
let mut colorer = RoadColorerBuilder::new(
|
||||
"parking availability",
|
||||
vec![
|
||||
("< 10%", awful),
|
||||
("< 30%", bad),
|
||||
("< 60%", meh),
|
||||
(">= 60%", good),
|
||||
],
|
||||
);
|
||||
|
||||
let lane = |spot| match spot {
|
||||
ParkingSpot::Onstreet(l, _) => l,
|
||||
ParkingSpot::Offstreet(b, _) => primary
|
||||
.map
|
||||
.get_b(b)
|
||||
.parking
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.driving_pos
|
||||
.lane(),
|
||||
};
|
||||
|
||||
let mut filled = Counter::new();
|
||||
let mut avail = Counter::new();
|
||||
let mut keys = HashSet::new();
|
||||
let (filled_spots, avail_spots) = primary.sim.get_all_parking_spots();
|
||||
for spot in filled_spots {
|
||||
let l = lane(spot);
|
||||
keys.insert(l);
|
||||
filled.inc(l);
|
||||
}
|
||||
for spot in avail_spots {
|
||||
let l = lane(spot);
|
||||
keys.insert(l);
|
||||
avail.inc(l);
|
||||
}
|
||||
|
||||
for l in keys {
|
||||
let open = avail.get(l);
|
||||
let closed = filled.get(l);
|
||||
let percent = (open as f64) / ((open + closed) as f64);
|
||||
let color = if percent >= 0.6 {
|
||||
good
|
||||
} else if percent > 0.3 {
|
||||
meh
|
||||
} else if percent > 0.1 {
|
||||
bad
|
||||
} else {
|
||||
awful
|
||||
};
|
||||
colorer.add(l, color, &primary.map);
|
||||
}
|
||||
|
||||
colorer.build(ctx, &primary.map)
|
||||
}
|
||||
|
||||
fn calculate_intersection_delay(ctx: &mut EventCtx, primary: &PerMapUI) -> ObjectColorer {
|
||||
let fast = Color::GREEN;
|
||||
let meh = Color::YELLOW;
|
||||
let slow = Color::RED;
|
||||
let mut colorer = ObjectColorerBuilder::new(
|
||||
"intersection delay (90%ile)",
|
||||
vec![("< 10s", fast), ("<= 60s", meh), ("> 60s", slow)],
|
||||
);
|
||||
|
||||
for i in primary.map.all_intersections() {
|
||||
let delays = primary.sim.get_intersection_delays(i.id);
|
||||
if let Some(d) = delays.percentile(90.0) {
|
||||
let color = if d < Duration::seconds(10.0) {
|
||||
fast
|
||||
} else if d <= Duration::seconds(60.0) {
|
||||
meh
|
||||
} else {
|
||||
slow
|
||||
};
|
||||
colorer.add(ID::Intersection(i.id), color);
|
||||
}
|
||||
}
|
||||
|
||||
colorer.build(ctx, &primary.map)
|
||||
}
|
||||
|
@ -1,123 +0,0 @@
|
||||
use crate::common::{ObjectColorer, ObjectColorerBuilder};
|
||||
use crate::game::{State, Transition};
|
||||
use crate::helpers::ID;
|
||||
use crate::ui::UI;
|
||||
use abstutil::Counter;
|
||||
use ezgui::{hotkey, Color, EventCtx, GfxCtx, Key, ModalMenu};
|
||||
use map_model::{IntersectionID, RoadID, Traversable};
|
||||
use sim::Event;
|
||||
|
||||
pub struct ThruputStats {
|
||||
count_per_road: Counter<RoadID>,
|
||||
count_per_intersection: Counter<IntersectionID>,
|
||||
}
|
||||
|
||||
impl ThruputStats {
|
||||
pub fn new() -> ThruputStats {
|
||||
ThruputStats {
|
||||
count_per_road: Counter::new(),
|
||||
count_per_intersection: Counter::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn record(&mut self, ui: &mut UI) {
|
||||
for ev in ui.primary.sim.collect_events() {
|
||||
if let Event::AgentEntersTraversable(_, to) = ev {
|
||||
match to {
|
||||
Traversable::Lane(l) => self.count_per_road.inc(ui.primary.map.get_l(l).parent),
|
||||
Traversable::Turn(t) => self.count_per_intersection.inc(t.parent),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ShowStats {
|
||||
menu: ModalMenu,
|
||||
heatmap: ObjectColorer,
|
||||
}
|
||||
|
||||
impl State for ShowStats {
|
||||
fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> Transition {
|
||||
ctx.canvas.handle_event(ctx.input);
|
||||
if ctx.redo_mouseover() {
|
||||
ui.recalculate_current_selection(ctx);
|
||||
}
|
||||
|
||||
self.menu.handle_event(ctx, None);
|
||||
if self.menu.action("quit") {
|
||||
return Transition::Pop;
|
||||
}
|
||||
Transition::Keep
|
||||
}
|
||||
|
||||
fn draw_default_ui(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn draw(&self, g: &mut GfxCtx, ui: &UI) {
|
||||
self.heatmap.draw(g, ui);
|
||||
self.menu.draw(g);
|
||||
}
|
||||
}
|
||||
|
||||
impl ShowStats {
|
||||
pub fn new(stats: &ThruputStats, ui: &UI, ctx: &mut EventCtx) -> ShowStats {
|
||||
let light = Color::GREEN;
|
||||
let medium = Color::YELLOW;
|
||||
let heavy = Color::RED;
|
||||
let mut colorer = ObjectColorerBuilder::new(
|
||||
"Throughput",
|
||||
vec![
|
||||
("< 50%ile", light),
|
||||
("< 90%ile", medium),
|
||||
(">= 90%ile", heavy),
|
||||
],
|
||||
);
|
||||
|
||||
// TODO If there are many duplicate counts, arbitrarily some will look heavier! Find the
|
||||
// disribution of counts instead.
|
||||
// TODO Actually display the counts at these percentiles
|
||||
// TODO Dump the data in debug mode
|
||||
{
|
||||
let roads = stats.count_per_road.sorted_asc();
|
||||
let p50_idx = ((roads.len() as f64) * 0.5) as usize;
|
||||
let p90_idx = ((roads.len() as f64) * 0.9) as usize;
|
||||
for (idx, r) in roads.into_iter().enumerate() {
|
||||
let color = if idx < p50_idx {
|
||||
light
|
||||
} else if idx < p90_idx {
|
||||
medium
|
||||
} else {
|
||||
heavy
|
||||
};
|
||||
colorer.add(ID::Road(*r), color);
|
||||
}
|
||||
}
|
||||
// TODO dedupe
|
||||
{
|
||||
let intersections = stats.count_per_intersection.sorted_asc();
|
||||
let p50_idx = ((intersections.len() as f64) * 0.5) as usize;
|
||||
let p90_idx = ((intersections.len() as f64) * 0.9) as usize;
|
||||
for (idx, i) in intersections.into_iter().enumerate() {
|
||||
let color = if idx < p50_idx {
|
||||
light
|
||||
} else if idx < p90_idx {
|
||||
medium
|
||||
} else {
|
||||
heavy
|
||||
};
|
||||
colorer.add(ID::Intersection(*i), color);
|
||||
}
|
||||
}
|
||||
|
||||
ShowStats {
|
||||
menu: ModalMenu::new(
|
||||
"Thruput Stats",
|
||||
vec![vec![(hotkey(Key::Escape), "quit")]],
|
||||
ctx,
|
||||
),
|
||||
heatmap: colorer.build(ctx, &ui.primary.map),
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user