map_gui/tools/
colors.rs

1use std::collections::HashMap;
2
3use abstutil::Counter;
4use geom::{Circle, Distance};
5use map_model::{BuildingID, IntersectionID, LaneID, Map, ParkingLotID, RoadID, TransitStopID};
6use widgetry::mapspace::{ToggleZoomed, ToggleZoomedBuilder};
7use widgetry::tools::{ColorLegend, ColorScale};
8use widgetry::{Color, EventCtx, GeomBatch, Widget};
9
10use crate::AppLike;
11
12// TODO Tooltips would almost be nice, for cases like pedestrian crowding
13pub struct ColorDiscrete<'a> {
14    map: &'a Map,
15    // pub so callers can add stuff in before building
16    pub draw: ToggleZoomedBuilder,
17    // Store both, so we can build the legend in the original order later
18    pub categories: Vec<(String, Color)>,
19    colors: HashMap<String, Color>,
20}
21
22impl<'a> ColorDiscrete<'a> {
23    pub fn new<I: Into<String>>(
24        app: &'a dyn AppLike,
25        categories: Vec<(I, Color)>,
26    ) -> ColorDiscrete<'a> {
27        let mut draw = ToggleZoomed::builder();
28        draw.unzoomed.push(
29            app.cs().fade_map_dark,
30            app.map().get_boundary_polygon().clone(),
31        );
32        let categories: Vec<(String, Color)> =
33            categories.into_iter().map(|(k, v)| (k.into(), v)).collect();
34        ColorDiscrete {
35            map: app.map(),
36            draw,
37            colors: categories.iter().cloned().collect(),
38            categories,
39        }
40    }
41
42    pub fn no_fading<I: Into<String>>(
43        app: &'a dyn AppLike,
44        categories: Vec<(I, Color)>,
45    ) -> ColorDiscrete<'a> {
46        let mut c = ColorDiscrete::new(app, categories);
47        c.draw.unzoomed = GeomBatch::new();
48        c
49    }
50
51    pub fn add_l<I: AsRef<str>>(&mut self, l: LaneID, category: I) {
52        let color = self.colors[category.as_ref()];
53        self.draw
54            .unzoomed
55            .push(color, self.map.get_parent(l).get_thick_polygon());
56        let lane = self.map.get_l(l);
57        self.draw
58            .zoomed
59            .push(color.alpha(0.4), lane.get_thick_polygon());
60    }
61
62    pub fn add_r<I: AsRef<str>>(&mut self, r: RoadID, category: I) {
63        let color = self.colors[category.as_ref()];
64        self.draw
65            .unzoomed
66            .push(color, self.map.get_r(r).get_thick_polygon());
67        self.draw
68            .zoomed
69            .push(color.alpha(0.4), self.map.get_r(r).get_thick_polygon());
70    }
71
72    pub fn add_i<I: AsRef<str>>(&mut self, i: IntersectionID, category: I) {
73        let color = self.colors[category.as_ref()];
74        self.draw
75            .unzoomed
76            .push(color, self.map.get_i(i).polygon.clone());
77        self.draw
78            .zoomed
79            .push(color.alpha(0.4), self.map.get_i(i).polygon.clone());
80    }
81
82    pub fn add_b<I: AsRef<str>>(&mut self, b: BuildingID, category: I) {
83        let color = self.colors[category.as_ref()];
84        self.draw
85            .unzoomed
86            .push(color, self.map.get_b(b).polygon.clone());
87        self.draw
88            .zoomed
89            .push(color.alpha(0.4), self.map.get_b(b).polygon.clone());
90    }
91
92    pub fn add_ts<I: AsRef<str>>(&mut self, ts: TransitStopID, category: I) {
93        let color = self.colors[category.as_ref()];
94        let pt = self.map.get_ts(ts).sidewalk_pos.pt(self.map);
95        self.draw.zoomed.push(
96            color.alpha(0.4),
97            Circle::new(pt, Distance::meters(5.0)).to_polygon(),
98        );
99        self.draw
100            .unzoomed
101            .push(color, Circle::new(pt, Distance::meters(15.0)).to_polygon());
102    }
103
104    pub fn build(self, ctx: &EventCtx) -> (ToggleZoomed, Widget) {
105        let legend = self
106            .categories
107            .into_iter()
108            .map(|(name, color)| ColorLegend::row(ctx, color, name))
109            .collect();
110        (self.draw.build(ctx), Widget::col(legend))
111    }
112}
113
114// TODO Bad name
115pub struct ColorNetwork<'a> {
116    map: &'a Map,
117    pub draw: ToggleZoomedBuilder,
118}
119
120impl<'a> ColorNetwork<'a> {
121    pub fn new(app: &'a dyn AppLike) -> ColorNetwork {
122        let mut draw = ToggleZoomed::builder();
123        draw.unzoomed.push(
124            app.cs().fade_map_dark,
125            app.map().get_boundary_polygon().clone(),
126        );
127        ColorNetwork {
128            map: app.map(),
129            draw,
130        }
131    }
132
133    pub fn no_fading(app: &'a dyn AppLike) -> ColorNetwork {
134        ColorNetwork {
135            map: app.map(),
136            draw: ToggleZoomed::builder(),
137        }
138    }
139
140    pub fn add_l(&mut self, l: LaneID, color: Color) {
141        self.draw
142            .unzoomed
143            .push(color, self.map.get_parent(l).get_thick_polygon());
144        let lane = self.map.get_l(l);
145        self.draw
146            .zoomed
147            .push(color.alpha(0.4), lane.get_thick_polygon());
148    }
149
150    pub fn add_r(&mut self, r: RoadID, color: Color) {
151        self.draw
152            .unzoomed
153            .push(color, self.map.get_r(r).get_thick_polygon());
154        self.draw
155            .zoomed
156            .push(color.alpha(0.4), self.map.get_r(r).get_thick_polygon());
157    }
158
159    pub fn add_i(&mut self, i: IntersectionID, color: Color) {
160        self.draw
161            .unzoomed
162            .push(color, self.map.get_i(i).polygon.clone());
163        self.draw
164            .zoomed
165            .push(color.alpha(0.4), self.map.get_i(i).polygon.clone());
166    }
167
168    pub fn add_b(&mut self, b: BuildingID, color: Color) {
169        self.draw
170            .unzoomed
171            .push(color, self.map.get_b(b).polygon.clone());
172        self.draw
173            .zoomed
174            .push(color.alpha(0.4), self.map.get_b(b).polygon.clone());
175    }
176
177    pub fn add_pl(&mut self, pl: ParkingLotID, color: Color) {
178        self.draw
179            .unzoomed
180            .push(color, self.map.get_pl(pl).polygon.clone());
181        self.draw
182            .zoomed
183            .push(color.alpha(0.4), self.map.get_pl(pl).polygon.clone());
184    }
185
186    // Order the roads by count, then interpolate a color based on position in that ordering.
187    pub fn ranked_roads(&mut self, counter: Counter<RoadID>, scale: &ColorScale) {
188        let roads = counter.sorted_asc();
189        let len = roads.len() as f64;
190        for (idx, list) in roads.into_iter().enumerate() {
191            let color = scale.eval((idx as f64) / len);
192            for r in list {
193                self.add_r(r, color);
194            }
195        }
196    }
197    pub fn ranked_intersections(&mut self, counter: Counter<IntersectionID>, scale: &ColorScale) {
198        let intersections = counter.sorted_asc();
199        let len = intersections.len() as f64;
200        for (idx, list) in intersections.into_iter().enumerate() {
201            let color = scale.eval((idx as f64) / len);
202            for i in list {
203                self.add_i(i, color);
204            }
205        }
206    }
207
208    // Interpolate a color for each road based on the max count.
209    pub fn pct_roads(&mut self, counter: Counter<RoadID>, scale: &ColorScale) {
210        let max = counter.max() as f64;
211        for (r, cnt) in counter.consume() {
212            self.add_r(r, scale.eval((cnt as f64) / max));
213        }
214    }
215    // Interpolate a color for each intersection based on the max count.
216    pub fn pct_intersections(&mut self, counter: Counter<IntersectionID>, scale: &ColorScale) {
217        let max = counter.max() as f64;
218        for (i, cnt) in counter.consume() {
219            self.add_i(i, scale.eval((cnt as f64) / max));
220        }
221    }
222
223    pub fn build(self, ctx: &EventCtx) -> ToggleZoomed {
224        self.draw.build(ctx)
225    }
226}