Add a line plot showing the elevation of the route taken by pedestrians

and cyclists. Add support for Distance as the Y axis of a LinePlot. #82
This commit is contained in:
Dustin Carlino 2021-03-26 10:16:28 -07:00
parent 00323925f3
commit 73a31d1633
2 changed files with 47 additions and 31 deletions

View File

@ -749,9 +749,7 @@ fn make_trip_details(
for (idx, p) in phases.into_iter().enumerate() {
let color = color_for_trip_phase(app, p.phase_type).alpha(0.7);
if let Some(path) = &p.path {
if app.opts.dev
&& (p.phase_type == TripPhaseType::Walking || p.phase_type == TripPhaseType::Biking)
{
if p.phase_type == TripPhaseType::Walking || p.phase_type == TripPhaseType::Biking {
elevation.push(make_elevation(
ctx,
color,
@ -852,10 +850,7 @@ fn make_trip_details(
if path_impossible {
col.push("Map edits have disconnected the path taken before".text_widget(ctx));
}
// TODO This just needs too much more work
if false {
col.extend(elevation);
}
col.extend(elevation);
highlight_slow_intersections(ctx, app, details, trip_id);
highlight_slow_lanes(ctx, app, details, trip_id);
Widget::col(col)
@ -870,7 +865,6 @@ fn make_elevation(ctx: &EventCtx, color: Color, walking: bool, path: &Path, map:
}
dist += step.as_traversable().length(map);
}
// TODO Plot needs to support Distance as both X and Y axis. :P
// TODO Show roughly where we are in the trip; use distance covered by current path for this
LinePlot::new(
ctx,
@ -884,12 +878,7 @@ fn make_elevation(ctx: &EventCtx, color: Color, walking: bool, path: &Path, map:
color,
pts: pts
.into_iter()
.map(|(x, y)| {
(
Time::START_OF_DAY + Duration::seconds(x.inner_meters()),
y.inner_meters() as usize,
)
})
.map(|(x, y)| (Time::START_OF_DAY + Duration::seconds(x.inner_meters()), y))
.collect(),
}],
PlotOptions::fixed(),

View File

@ -12,27 +12,27 @@ use crate::{
};
// The X is always time
pub struct LinePlot<T: Yvalue<T>> {
pub struct LinePlot<Y: Yvalue<Y>> {
draw: Drawable,
// The geometry here is in screen-space.
max_x: Time,
max_y: T,
max_y: Y,
closest: FindClosest<String>,
top_left: ScreenPt,
dims: ScreenDims,
}
pub struct PlotOptions<T: Yvalue<T>> {
pub struct PlotOptions<Y: Yvalue<Y>> {
pub filterable: bool,
pub max_x: Option<Time>,
pub max_y: Option<T>,
pub max_y: Option<Y>,
pub disabled: HashSet<String>,
}
impl<T: Yvalue<T>> PlotOptions<T> {
pub fn filterable() -> PlotOptions<T> {
impl<Y: Yvalue<Y>> PlotOptions<Y> {
pub fn filterable() -> PlotOptions<Y> {
PlotOptions {
filterable: true,
max_x: None,
@ -41,7 +41,7 @@ impl<T: Yvalue<T>> PlotOptions<T> {
}
}
pub fn fixed() -> PlotOptions<T> {
pub fn fixed() -> PlotOptions<Y> {
PlotOptions {
filterable: false,
max_x: None,
@ -51,8 +51,8 @@ impl<T: Yvalue<T>> PlotOptions<T> {
}
}
impl<T: Yvalue<T>> LinePlot<T> {
pub fn new(ctx: &EventCtx, mut series: Vec<Series<T>>, opts: PlotOptions<T>) -> Widget {
impl<Y: Yvalue<Y>> LinePlot<Y> {
pub fn new(ctx: &EventCtx, mut series: Vec<Series<Y>>, opts: PlotOptions<Y>) -> Widget {
let legend = make_legend(ctx, &series, &opts);
series.retain(|s| !opts.disabled.contains(&s.label));
@ -78,10 +78,10 @@ impl<T: Yvalue<T>> LinePlot<T> {
.iter()
.map(|(_, value)| *value)
.max()
.unwrap_or(T::zero())
.unwrap_or(Y::zero())
})
.max()
.unwrap_or(T::zero())
.unwrap_or(Y::zero())
});
// TODO Tuned to fit the info panel. Instead these should somehow stretch to fill their
@ -203,7 +203,7 @@ impl<T: Yvalue<T>> LinePlot<T> {
}
}
impl<T: Yvalue<T>> WidgetImpl for LinePlot<T> {
impl<Y: Yvalue<Y>> WidgetImpl for LinePlot<Y> {
fn get_dims(&self) -> ScreenDims {
self.dims
}
@ -310,18 +310,45 @@ impl Yvalue<Duration> for Duration {
Duration::ZERO
}
}
impl Yvalue<Distance> for Distance {
fn from_percent(&self, percent: f64) -> Distance {
*self * percent
}
fn to_percent(self, max: Distance) -> f64 {
if max == Distance::ZERO {
0.0
} else {
self / max
}
}
fn prettyprint(self) -> String {
self.to_string(&UnitFmt {
metric: false,
round_durations: true,
})
}
fn to_f64(self) -> f64 {
self.inner_meters() as f64
}
fn from_f64(&self, x: f64) -> Distance {
Distance::meters(x as f64)
}
fn zero() -> Distance {
Distance::ZERO
}
}
pub struct Series<T> {
pub struct Series<Y> {
pub label: String,
pub color: Color,
// X-axis is time. Assume this is sorted by X.
pub pts: Vec<(Time, T)>,
pub pts: Vec<(Time, Y)>,
}
pub fn make_legend<T: Yvalue<T>>(
pub fn make_legend<Y: Yvalue<Y>>(
ctx: &EventCtx,
series: &Vec<Series<T>>,
opts: &PlotOptions<T>,
series: &Vec<Series<Y>>,
opts: &PlotOptions<Y>,
) -> Widget {
let mut row = Vec::new();
let mut seen = HashSet::new();