Fix weather plot, close #5
This commit is contained in:
parent
e8f5f00386
commit
d537c6f7be
2 changed files with 130 additions and 22 deletions
|
@ -28,7 +28,7 @@ pub struct WeatherData {
|
||||||
#[serde(skip_deserializing)]
|
#[serde(skip_deserializing)]
|
||||||
minutely: Vec<()>,
|
minutely: Vec<()>,
|
||||||
/// Weather Information for the coming hours
|
/// Weather Information for the coming hours
|
||||||
hourly: Vec<HourlyWeather>,
|
pub hourly: Vec<HourlyWeather>,
|
||||||
/// Weather information for the next days _(unused at the moment)_
|
/// Weather information for the next days _(unused at the moment)_
|
||||||
#[serde(skip_deserializing)]
|
#[serde(skip_deserializing)]
|
||||||
daily: Vec<()>,
|
daily: Vec<()>,
|
||||||
|
@ -95,6 +95,12 @@ pub struct HourlyWeather {
|
||||||
pub(self) pop: f32,
|
pub(self) pop: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl HourlyWeather {
|
||||||
|
pub fn temperature(&self) -> f32 {
|
||||||
|
self.data.temp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Common weather information shared among different forecast types.
|
/// Common weather information shared among different forecast types.
|
||||||
///
|
///
|
||||||
/// This struct is not instantiated by the API but merely introduced by the `Deserializer`.
|
/// This struct is not instantiated by the API but merely introduced by the `Deserializer`.
|
||||||
|
|
|
@ -1,22 +1,129 @@
|
||||||
|
use crate::error::display_no_data;
|
||||||
|
use embedded_graphics::mono_font::MonoTextStyleBuilder;
|
||||||
use embedded_graphics::{
|
use embedded_graphics::{
|
||||||
draw_target::DrawTarget,
|
draw_target::DrawTarget, image::Image, mono_font::MonoTextStyle, prelude::*, text::Text,
|
||||||
image::Image,
|
|
||||||
mono_font::MonoTextStyle,
|
|
||||||
prelude::*,
|
|
||||||
text::Text
|
|
||||||
};
|
};
|
||||||
use embedded_plots::{axis::Scale, curve::Curve, single_plot::SinglePlot};
|
use embedded_plots::{
|
||||||
use profont::{
|
axis::{Axis, Placement, Scale},
|
||||||
PROFONT_14_POINT, PROFONT_24_POINT, PROFONT_7_POINT, PROFONT_9_POINT,
|
curve::{Curve, PlotPoint},
|
||||||
|
single_plot::SinglePlot,
|
||||||
};
|
};
|
||||||
|
use profont::{PROFONT_14_POINT, PROFONT_24_POINT, PROFONT_7_POINT, PROFONT_9_POINT};
|
||||||
use ssd1675::Color;
|
use ssd1675::Color;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use crate::error::display_no_data;
|
use std::ops::Range;
|
||||||
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
mod api;
|
mod api;
|
||||||
mod data;
|
mod data;
|
||||||
mod secret;
|
|
||||||
mod icons;
|
mod icons;
|
||||||
|
mod secret;
|
||||||
|
|
||||||
|
/// Draws the 24hr weather data in a graph with 2 x axes, one for today and one for tomorrow.
|
||||||
|
fn draw_weather_plot<D>(
|
||||||
|
wtr: &data::WeatherData,
|
||||||
|
display: &mut D,
|
||||||
|
top_left: Point,
|
||||||
|
bottom_right: Point,
|
||||||
|
) where
|
||||||
|
D: DrawTarget<Color = Color>,
|
||||||
|
D::Error: Debug,
|
||||||
|
{
|
||||||
|
let local_time =
|
||||||
|
OffsetDateTime::now_local().expect("Failed to retrieve current time from system clock");
|
||||||
|
let now_hour = local_time.hour();
|
||||||
|
|
||||||
|
// Split the plot data into two sets: one for today's data (time up to 23:00), one for tomorrow
|
||||||
|
// (0:00 onwards). This is necessary because if all data were plotted in one graph, it would
|
||||||
|
// show the delta in hours to the current time. If you replaced the delta with the actual hour,
|
||||||
|
// the plot would be re-ordered, leading to confusion.
|
||||||
|
// NOTE(feliix42): We're doing a +1 here to actually also take the 24 for a better overlap
|
||||||
|
let num_entries_today = 24 - now_hour + 1;
|
||||||
|
let num_entries_tomorrow = 24 - num_entries_today;
|
||||||
|
|
||||||
|
let plot_data = wtr.forecast_plot_data();
|
||||||
|
|
||||||
|
// X coordinate where to split between axis 1 and 2
|
||||||
|
let cutoff_point = top_left.x + ((bottom_right.x - top_left.x) / 24) * num_entries_today as i32;
|
||||||
|
|
||||||
|
// computation of scales. We want 5 (plus x origin) labels on the x axis. So we compute as
|
||||||
|
// follows: num_labels % 6 = num labels per section
|
||||||
|
// Note, that the origin of x2 will be printed anyways as well
|
||||||
|
let num_labels_in_x1: u8 = num_entries_today / 6;
|
||||||
|
let x1_labels = if num_labels_in_x1 > 0 {
|
||||||
|
num_labels_in_x1
|
||||||
|
} else {
|
||||||
|
1
|
||||||
|
};
|
||||||
|
let mut x2_labels = 5 - x1_labels;
|
||||||
|
if x2_labels == 0 {
|
||||||
|
x2_labels = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let x1_scale = Scale::RangeFraction(x1_labels.into());
|
||||||
|
let x2_scale = Scale::RangeFraction(x2_labels.into());
|
||||||
|
let y_scale = Scale::RangeFraction(2);
|
||||||
|
let thickness = 2;
|
||||||
|
let axis_thickness = thickness;
|
||||||
|
let text_style = MonoTextStyleBuilder::new().text_color(Color::Black).build();
|
||||||
|
|
||||||
|
let curve = [(Curve::from_data(&plot_data), Color::Black)];
|
||||||
|
|
||||||
|
let x1_range: Range<i32> = (now_hour as i32)..((now_hour + num_entries_today) as i32);
|
||||||
|
let x2_range: Range<i32> = 0..(num_entries_tomorrow as i32);
|
||||||
|
let y_range = curve[0].0.y_range.clone();
|
||||||
|
|
||||||
|
Axis::new(x1_range)
|
||||||
|
.set_title("")
|
||||||
|
.set_scale(x1_scale)
|
||||||
|
.into_drawable_axis(Placement::X {
|
||||||
|
x1: top_left.x,
|
||||||
|
x2: cutoff_point,
|
||||||
|
y: bottom_right.y,
|
||||||
|
})
|
||||||
|
.set_color(Color::Black)
|
||||||
|
.set_text_style(text_style)
|
||||||
|
.set_tick_size(2)
|
||||||
|
.set_thickness(axis_thickness)
|
||||||
|
.draw(display)
|
||||||
|
.expect("Failed to draw the temperature plot");
|
||||||
|
Axis::new(x2_range)
|
||||||
|
.set_title("t")
|
||||||
|
.set_scale(x2_scale)
|
||||||
|
.into_drawable_axis(Placement::X {
|
||||||
|
x1: cutoff_point,
|
||||||
|
x2: bottom_right.x,
|
||||||
|
y: bottom_right.y,
|
||||||
|
})
|
||||||
|
.set_color(Color::Black)
|
||||||
|
.set_text_style(text_style)
|
||||||
|
.set_tick_size(2)
|
||||||
|
.set_thickness(axis_thickness)
|
||||||
|
.draw(display)
|
||||||
|
.expect("Failed to draw the temperature plot");
|
||||||
|
Axis::new(y_range)
|
||||||
|
.set_title("C")
|
||||||
|
.set_scale(y_scale)
|
||||||
|
.into_drawable_axis(Placement::Y {
|
||||||
|
y1: top_left.y,
|
||||||
|
y2: bottom_right.y,
|
||||||
|
x: top_left.x,
|
||||||
|
})
|
||||||
|
.set_color(Color::Black)
|
||||||
|
.set_text_style(text_style)
|
||||||
|
.set_tick_size(2)
|
||||||
|
.set_thickness(axis_thickness)
|
||||||
|
.draw(display)
|
||||||
|
.expect("Failed to draw the temperature plot");
|
||||||
|
|
||||||
|
curve[0]
|
||||||
|
.0
|
||||||
|
.into_drawable_curve(&top_left, &bottom_right)
|
||||||
|
.set_color(curve[0].1)
|
||||||
|
.set_thickness(thickness)
|
||||||
|
.draw(display)
|
||||||
|
.expect("Failed to draw the temperature plot");
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_weather<D>(display: &mut D)
|
pub fn get_weather<D>(display: &mut D)
|
||||||
where
|
where
|
||||||
|
@ -107,15 +214,10 @@ where
|
||||||
.expect("error drawing text");
|
.expect("error drawing text");
|
||||||
|
|
||||||
// Plot the forecast
|
// Plot the forecast
|
||||||
// TODO(feliix42): Annotate the plot better??
|
draw_weather_plot(
|
||||||
let data_points = weather_data.forecast_plot_data();
|
&weather_data,
|
||||||
let curve = [(Curve::from_data(&data_points), Color::Black)];
|
display,
|
||||||
|
Point::new(100, 54),
|
||||||
let plot = SinglePlot::new(&curve, Scale::RangeFraction(4), Scale::RangeFraction(2))
|
Point::new(206, 94),
|
||||||
.into_drawable(Point::new(100, 54), Point::new(206, 94))
|
);
|
||||||
.set_color(Color::Black);
|
|
||||||
|
|
||||||
plot.draw(display)
|
|
||||||
.expect("Failed to draw the temperature plot");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue