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
12pub struct ColorDiscrete<'a> {
14 map: &'a Map,
15 pub draw: ToggleZoomedBuilder,
17 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
114pub 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 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 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 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}