diff --git a/src/weather/data.rs b/src/weather/data.rs index 1442a77..5357328 100644 --- a/src/weather/data.rs +++ b/src/weather/data.rs @@ -28,7 +28,7 @@ pub struct WeatherData { #[serde(skip_deserializing)] minutely: Vec<()>, /// Weather Information for the coming hours - hourly: Vec, + pub hourly: Vec, /// Weather information for the next days _(unused at the moment)_ #[serde(skip_deserializing)] daily: Vec<()>, @@ -95,6 +95,12 @@ pub struct HourlyWeather { pub(self) pop: f32, } +impl HourlyWeather { + pub fn temperature(&self) -> f32 { + self.data.temp + } +} + /// Common weather information shared among different forecast types. /// /// This struct is not instantiated by the API but merely introduced by the `Deserializer`. diff --git a/src/weather/mod.rs b/src/weather/mod.rs index 2e338ee..4401d8c 100644 --- a/src/weather/mod.rs +++ b/src/weather/mod.rs @@ -1,22 +1,129 @@ +use crate::error::display_no_data; +use embedded_graphics::mono_font::MonoTextStyleBuilder; use embedded_graphics::{ - draw_target::DrawTarget, - image::Image, - mono_font::MonoTextStyle, - prelude::*, - text::Text + draw_target::DrawTarget, image::Image, mono_font::MonoTextStyle, prelude::*, text::Text, }; -use embedded_plots::{axis::Scale, curve::Curve, single_plot::SinglePlot}; -use profont::{ - PROFONT_14_POINT, PROFONT_24_POINT, PROFONT_7_POINT, PROFONT_9_POINT, +use embedded_plots::{ + axis::{Axis, Placement, Scale}, + curve::{Curve, PlotPoint}, + single_plot::SinglePlot, }; +use profont::{PROFONT_14_POINT, PROFONT_24_POINT, PROFONT_7_POINT, PROFONT_9_POINT}; use ssd1675::Color; use std::fmt::Debug; -use crate::error::display_no_data; +use std::ops::Range; +use time::OffsetDateTime; mod api; mod data; -mod secret; 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( + wtr: &data::WeatherData, + display: &mut D, + top_left: Point, + bottom_right: Point, +) where + D: DrawTarget, + 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 = (now_hour as i32)..((now_hour + num_entries_today) as i32); + let x2_range: Range = 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(display: &mut D) where @@ -107,15 +214,10 @@ where .expect("error drawing text"); // Plot the forecast - // TODO(feliix42): Annotate the plot better?? - let data_points = weather_data.forecast_plot_data(); - let curve = [(Curve::from_data(&data_points), Color::Black)]; - - let plot = SinglePlot::new(&curve, Scale::RangeFraction(4), Scale::RangeFraction(2)) - .into_drawable(Point::new(100, 54), Point::new(206, 94)) - .set_color(Color::Black); - - plot.draw(display) - .expect("Failed to draw the temperature plot"); + draw_weather_plot( + &weather_data, + display, + Point::new(100, 54), + Point::new(206, 94), + ); } -