1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
use std::collections::BTreeMap;

use geom::{ArrowCap, Distance, Duration, PolyLine, Pt2D};
use sim::{AgentID, DelayCause};
use widgetry::{
    Btn, Color, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Line, Outcome, Panel,
    State, Text, VerticalAlignment, Widget,
};

use crate::app::App;
use crate::app::Transition;
use crate::common::CommonState;

/// Visualize the graph of what agents are blocked by others.
pub struct Viewer {
    panel: Panel,
    graph: BTreeMap<AgentID, (Duration, DelayCause)>,
    arrows: Drawable,
}

impl Viewer {
    pub fn new(ctx: &mut EventCtx, app: &App) -> Box<dyn State<App>> {
        let graph = app.primary.sim.get_blocked_by_graph(&app.primary.map);
        let agent_positions: BTreeMap<AgentID, Pt2D> = app
            .primary
            .sim
            .get_unzoomed_agents(&app.primary.map)
            .into_iter()
            .map(|a| (a.id, a.pos))
            .collect();

        let mut arrows = GeomBatch::new();
        for (id, (_, cause)) in &graph {
            let (to, color) = match cause {
                DelayCause::Agent(a) => {
                    if let Some(pos) = agent_positions.get(a) {
                        (*pos, Color::RED)
                    } else {
                        warn!("{} blocked by {}, but they're gone?", id, a);
                        continue;
                    }
                }
                DelayCause::Intersection(i) => {
                    (app.primary.map.get_i(*i).polygon.center(), Color::BLUE)
                }
            };
            let arrow = PolyLine::must_new(vec![agent_positions[id], to])
                .make_arrow(Distance::meters(0.5), ArrowCap::Triangle);
            arrows.push(color.alpha(0.5), arrow);
        }

        Box::new(Viewer {
            graph,
            arrows: ctx.upload(arrows),
            panel: Panel::new(
                Widget::row(vec![
                    Line("What agents are blocked by others?")
                        .small_heading()
                        .draw(ctx),
                    Btn::close(ctx),
                ]),
                // TODO info about cycles
            )
            .aligned(HorizontalAlignment::Center, VerticalAlignment::Top)
            .build(ctx),
        })
    }
}

impl State<App> for Viewer {
    fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
        ctx.canvas_movement();
        if ctx.redo_mouseover() {
            app.recalculate_current_selection(ctx);
        }

        match self.panel.event(ctx) {
            Outcome::Clicked(x) => match x.as_ref() {
                "close" => {
                    return Transition::Pop;
                }
                _ => unreachable!(),
            },
            _ => {}
        }

        Transition::Keep
    }

    fn draw(&self, g: &mut GfxCtx, app: &App) {
        self.panel.draw(g);
        CommonState::draw_osd(g, app);
        g.redraw(&self.arrows);

        if let Some(id) = app
            .primary
            .current_selection
            .as_ref()
            .and_then(|id| id.agent_id())
        {
            if let Some((delay, _)) = self.graph.get(&id) {
                g.draw_mouse_tooltip(Text::from(Line(format!("Waiting {}", delay))));
            }
        }
    }
}