Skip to content

Commit

Permalink
Draw precipitation histogram
Browse files Browse the repository at this point in the history
Refs: #20
  • Loading branch information
orontee committed Sep 5, 2023
1 parent 49d8e1d commit ccd8aeb
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 13 deletions.
3 changes: 3 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

### Added

- Draw precipitation histogram
[#20](https://github.com/orontee/argos/issues/20)

- Sub-menu to select unit system
[#4](https://github.com/orontee/argos/issues/4)

Expand Down
80 changes: 71 additions & 9 deletions src/hourlyforecastbox.h
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,10 @@ class HourlyForecastBox : public Widget {
const auto bar_center_x =
(bar_index + 1.0 / 2) * this->adjusted_bar_width;

if (this->forecast_offset + bar_index <
this->model->hourly_forecast.size()) {
const auto forecast =
this->model->hourly_forecast[this->forecast_offset + bar_index];
const auto forecast_index = this->forecast_offset + bar_index;
if (forecast_index < this->model->hourly_forecast.size()) {
const auto forecast = this->model->hourly_forecast[forecast_index];

SetFont(tiny_font.get(), BLACK);

std::string time_text{"?????"};
Expand All @@ -131,11 +131,10 @@ class HourlyForecastBox : public Widget {
DrawString(bar_center_x - StringWidth(wind_speed_text.c_str()) / 2.0,
wind_speed_y, wind_speed_text.c_str());

std::stringstream humidity_text;
humidity_text << static_cast<int>(forecast.humidity) << "%";
DrawString(bar_center_x -
StringWidth(humidity_text.str().c_str()) / 2.0,
humidity_y, humidity_text.str().c_str());
std::string humidity_text =
std::to_string(static_cast<int>(forecast.humidity)) + "%";
DrawString(bar_center_x - StringWidth(humidity_text.c_str()) / 2.0,
humidity_y, humidity_text.c_str());

SetFont(small_bold_font.get(), BLACK);

Expand All @@ -155,6 +154,8 @@ class HourlyForecastBox : public Widget {
const auto curve_y_offset = temperature_y - this->vertical_padding;
const auto curve_height =
temperature_y - icon_y - this->icon_size - 2 * this->vertical_padding;

this->draw_precipitation_histogram(curve_y_offset, curve_height);
this->draw_curve(curve_y_offset, curve_height);
}

Expand Down Expand Up @@ -207,6 +208,67 @@ class HourlyForecastBox : public Widget {
}
}

void draw_precipitation_histogram(const int y_offset, const int height) {
auto tiny_font = this->fonts->get_tiny_font();
const auto min_bar_height = tiny_font.get()->height + 10;
// 2 pixels of padding in bar
const auto max_bar_height = height - tiny_font.get()->height;
// 1 pixel of padding between bar top and
// probability_of_precipitation text

if (max_bar_height <= min_bar_height) {
return;
}

const auto normalized_rain = normalize_rain(this->model->hourly_forecast,
min_bar_height, max_bar_height);

const auto bar_width = this->adjusted_bar_width;
const Units units{this->model};

SetFont(tiny_font.get(), DGRAY);

for (size_t bar_index = 0; bar_index < this->visible_bars; ++bar_index) {
const auto forecast_index = this->forecast_offset + bar_index;
if (forecast_index < this->model->hourly_forecast.size()) {
const auto forecast = this->model->hourly_forecast[forecast_index];
if (std::isnan(forecast.rain)) {
continue;
}
const auto bar_center_x = (bar_index + 1.0 / 2) * bar_width;
const auto x_screen = this->bounding_box.x + bar_index * bar_width;
const auto bar_height = normalized_rain.at(forecast_index);
const auto y_screen = y_offset - bar_height;
FillArea(x_screen, y_screen, bar_width, bar_height, LGRAY);
DrawRect(x_screen, y_screen, bar_width, bar_height, 0x777777);

const std::string rain_text =
units.format_precipitation(forecast.rain, true);

SetFont(tiny_font.get(), BLACK);
DrawString(bar_center_x - StringWidth(rain_text.c_str()) / 2.0,
y_screen, rain_text.c_str());

const auto probability_of_precipitation =
forecast.probability_of_precipitation;
if (not std::isnan(probability_of_precipitation)) {

const std::string probability_of_precipitation_text =
std::to_string(
static_cast<int>(probability_of_precipitation * 100)) +
"%";

SetFont(tiny_font.get(), DGRAY);
DrawString(
bar_center_x -
StringWidth(probability_of_precipitation_text.c_str()) / 2.0,
y_screen - tiny_font.get()->height - 2,
probability_of_precipitation_text.c_str());
}
}
}
}

void increase_forecast_offset() {
const size_t max_forecast_offset{24 - this->visible_bars};
const auto updated_forecast_offset = std::min(
Expand Down
50 changes: 48 additions & 2 deletions src/util.cc
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
#include "util.h"

#include <algorithm>
#include <cassert>
#include <cmath>

std::vector<double>
taranis::normalize_temperatures(std::vector<Condition> conditions,
int amplitude) {
taranis::normalize_temperatures(const std::vector<Condition> &conditions,
const int amplitude) {
if (conditions.size() < 2) {
return {};
}
Expand Down Expand Up @@ -32,3 +34,47 @@ taranis::normalize_temperatures(std::vector<Condition> conditions,
}
return normalized;
}

std::vector<double>
taranis::normalize_rain(const std::vector<Condition> &conditions,
const int lower_bound, const int upper_bound) {
assert(lower_bound < upper_bound);

const long double rain_threshold = 60;
// The scale is computed to reach upper_bound for precipitations
// >60mm
//
// Note that 305mm is the record of rain precipitation in one hour,
// Holt, Missouri, United States, 22 June 1947!

double min = NAN;
double max = rain_threshold;
;
for (const auto &condition : conditions) {
if (std::isnan(condition.rain)) {
continue;
}
const auto value = std::min(condition.rain, rain_threshold);
if (std::isnan(min) or value < min) {
min = value;
}
}
if (std::isnan(min)) {
return std::vector<double>(conditions.size(), NAN);
} else if (min == max) {
return std::vector<double>(conditions.size(), upper_bound);
}

std::vector<double> normalized;
normalized.reserve(conditions.size());
for (const auto &condition : conditions) {
if (std::isnan(condition.rain)) {
normalized.push_back(NAN);
continue;
}
normalized.push_back((upper_bound - lower_bound) / (max - min) *
(condition.rain - min) +
lower_bound);
}
return normalized;
}
9 changes: 7 additions & 2 deletions src/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,13 @@
#include "model.h"

namespace taranis {
std::vector<double> normalize_temperatures(std::vector<Condition> conditions,
int amplitude);
std::vector<double>
normalize_temperatures(const std::vector<Condition> &conditions,
const int amplitude);

std::vector<double> normalize_rain(const std::vector<Condition> &conditions,
const int lower_bound,
const int upper_bound);

static inline void ltrim(std::string &s) {
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](char ch) {
Expand Down

0 comments on commit ccd8aeb

Please sign in to comment.