From 2d47b7bdbbf5f70a5b9a0e2c89ef73b9812b444d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20de=20las=20Pozas=20=C3=81lvarez?= Date: Wed, 15 Nov 2023 00:39:59 +0100 Subject: [PATCH] Day & night background coloring in forecast tab. --- AboutDialog.cpp | 2 +- CMakeLists.txt | 4 +-- ConfigurationDialog.cpp | 2 +- Utils.cpp | 39 ++++++++++++++++++++++++++ Utils.h | 12 ++++++-- WeatherDialog.cpp | 62 +++++++++++++++++++++++++++++++++++++++-- WeatherDialog.h | 7 +++++ readme.md | 12 ++++---- 8 files changed, 126 insertions(+), 14 deletions(-) diff --git a/AboutDialog.cpp b/AboutDialog.cpp index 4ee233e..2ca67fd 100644 --- a/AboutDialog.cpp +++ b/AboutDialog.cpp @@ -26,7 +26,7 @@ #include #include -const QString AboutDialog::VERSION{"1.26.1"}; +const QString AboutDialog::VERSION{"1.27.0"}; const QString COPYRIGHT{"Copyright (c) 2016-%1 Félix de las Pozas Álvarez"}; //----------------------------------------------------------------- diff --git a/CMakeLists.txt b/CMakeLists.txt index ddd6b9a..d19fa39 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,8 +7,8 @@ cmake_minimum_required (VERSION 3.0.0) # Version Number set (TRAYWEATHER_VERSION_MAJOR 1) -set (TRAYWEATHER_VERSION_MINOR 26) -set (TRAYWEATHER_VERSION_PATCH 1) +set (TRAYWEATHER_VERSION_MINOR 27) +set (TRAYWEATHER_VERSION_PATCH 0) set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) diff --git a/ConfigurationDialog.cpp b/ConfigurationDialog.cpp index 3f61a21..79dfe3f 100644 --- a/ConfigurationDialog.cpp +++ b/ConfigurationDialog.cpp @@ -1359,7 +1359,7 @@ QPixmap ConfigurationDialog::generateTemperatureIconPixmap(QFont &font) QPixmap pixmap(384,384); pixmap.fill(Qt::transparent); QPainter painter(&pixmap); - font.setPixelSize(200); + font.setPixelSize(150); painter.setFont(font); QColor color; diff --git a/Utils.cpp b/Utils.cpp index 97fac9e..82f6b84 100644 --- a/Utils.cpp +++ b/Utils.cpp @@ -40,12 +40,14 @@ #include // C++ +#define _USE_MATH_DEFINES #include #include #include #include #include #include +#include static const QString INI_FILENAME = QString("TrayWeather.ini"); @@ -1154,3 +1156,40 @@ QRect computeDrawnRect(const QImage &image) const auto maxY = lastY-firstY; return QRect(firstX, firstY, maxX, maxY); } + +//-------------------------------------------------------------------- +std::pair computeSunriseSunset(const ForecastData &data, const double longitude, const double latitude) +{ + // convert to day of year + struct tm t; + unixTimeStampToDate(t, data.dt); + + // From http://edwilliams.org/sunrise_sunset_algorithm.htm + const auto N1 = std::floor(275 * t.tm_mon / 9); + const auto N2 = std::floor((t.tm_mon + 9) / 12); + const auto N3 = (1 + std::floor((t.tm_year - 4 * std::floor(t.tm_year / 4) + 2) / 3)); + const auto dayOfYear = N1 - (N2 * N3) + t.tm_mday - 30; + + // From https://codepal.ai/code-generator/query/GDQrUujP/c-function-sunrise-sunset-time + // Calculate the solar declination + const double solarDeclination = 0.4093 * std::sin(2 * M_PI * (dayOfYear - 81) / 365); + + // Calculate the hour angle + const double hourAngle = std::acos(-std::tan(latitude * M_PI / 180) * std::tan(solarDeclination)); + + // Calculate the sunrise and sunset times + const auto sunriseTime = 12 - (hourAngle * 180 / M_PI) / 15; + const auto sunsetTime = 12 + (hourAngle * 180 / M_PI) / 15; + + t.tm_hour = static_cast(sunriseTime); + t.tm_min = static_cast((sunriseTime - t.tm_hour) * 60.0); + t.tm_sec = static_cast((((sunriseTime - t.tm_hour) * 60.0) - t.tm_min) * 60.0); + const unsigned long long unixSunrise = std::mktime(&t); + + t.tm_hour = static_cast(sunsetTime); + t.tm_min = static_cast((sunsetTime - t.tm_hour) * 60.0); + t.tm_sec = static_cast((((sunsetTime - t.tm_hour) * 60.0) - t.tm_min) * 60.0); + const unsigned long long unixSunset = std::mktime(&t); + + return std::make_pair(unixSunrise, unixSunset); +} diff --git a/Utils.h b/Utils.h index 9267656..19d0879 100644 --- a/Utils.h +++ b/Utils.h @@ -108,7 +108,7 @@ struct LanguageData * \param[in] f Filename of the translation file without extension. * */ - LanguageData(const QString n, const QString i, const QString f, const QString a): name{n}, icon{i}, file{f}, author{a} {}; + LanguageData(const QString n, const QString i, const QString f, const QString a): name(n), icon(i), file(f), author(a) {}; }; /** Translations @@ -235,7 +235,7 @@ struct Configuration , minimumValue {-10} , maximumValue {45} , update {Update::WEEKLY} - , lastCheck {QDateTime::currentDateTime()} + , lastCheck (QDateTime::currentDateTime()) , autostart {false} , lastTab {0} , lastLayer {"temperature"} @@ -586,6 +586,14 @@ QPixmap createIconsSummary(const unsigned int theme, const int size, const QColo */ QRect computeDrawnRect(const QImage &image); +/** \brief Computes and returns the sunrise and sunset time in unix time for the given ForecastData date and coordinates. + * \param[in] data ForecastData struct. + * \param[in] longitude + * \param[in] latitude + * + */ +std::pair computeSunriseSunset (const ForecastData &data, const double longitude, const double latitude); + /** \class CustomComboBox * \brief ComboBox that uses rich text for selected item. * diff --git a/WeatherDialog.cpp b/WeatherDialog.cpp index 4c6e2e9..94a16e1 100644 --- a/WeatherDialog.cpp +++ b/WeatherDialog.cpp @@ -52,7 +52,7 @@ using namespace QtCharts; //-------------------------------------------------------------------- WeatherDialog::WeatherDialog(QWidget* parent, Qt::WindowFlags flags) -: QDialog {parent, flags} +: QDialog (parent, flags) , m_forecast {nullptr} , m_config {nullptr} , m_weatherTooltip {nullptr} @@ -478,8 +478,15 @@ void WeatherDialog::setWeatherData(const ForecastData ¤t, const Forecast & } }; - for(auto &entry: data) + QLinearGradient plotAreaGradient; + plotAreaGradient.setStart(QPointF(0, 0)); + plotAreaGradient.setFinalStop(QPointF(1, 0)); + plotAreaGradient.setCoordinateMode(QGradient::ObjectBoundingMode); + + for(int i = 0; i < data.size(); ++i) { + const auto &entry = data.at(i); + unixTimeStampToDate(t, entry.dt); dtTime = QDateTime{QDate{t.tm_year + 1900, t.tm_mon + 1, t.tm_mday}, QTime{t.tm_hour, t.tm_min, t.tm_sec}}; @@ -501,8 +508,16 @@ void WeatherDialog::setWeatherData(const ForecastData ¤t, const Forecast & snowMin = std::min(snowMin, value); snowMax = std::max(snowMax, value); + + const auto sunTimes = computeSunriseSunset(entry, m_config->longitude, m_config->latitude); + const auto entryDt = static_cast(entry.dt); + const bool isDay = entryDt > sunTimes.first && entryDt < sunTimes.second; + plotAreaGradient.setColorAt(static_cast(i)/(data.size()-1), isDay ? Qt::white:Qt::lightGray); } + forecastChart->setPlotAreaBackgroundBrush(plotAreaGradient); + forecastChart->setPlotAreaBackgroundVisible(true); + axisYTemp->setProperty("axisType", "temp"); // Bar series need to be added first so they don't hide the line series. @@ -574,6 +589,8 @@ void WeatherDialog::setWeatherData(const ForecastData ¤t, const Forecast & connect(axisX, SIGNAL(rangeChanged(QDateTime, QDateTime)), this, SLOT(onAreaChanged())); + connect(axisX, SIGNAL(rangeChanged(QDateTime, QDateTime)), + this, SLOT(onForecastAreaChanged(QDateTime, QDateTime))); if(oldChart) { @@ -582,6 +599,9 @@ void WeatherDialog::setWeatherData(const ForecastData ¤t, const Forecast & { disconnect(axis, SIGNAL(rangeChanged(QDateTime, QDateTime)), this, SLOT(onAreaChanged())); + disconnect(axisX, SIGNAL(rangeChanged(QDateTime, QDateTime)), + this, SLOT(onForecastAreaChanged(QDateTime, QDateTime))); + } delete oldChart; @@ -1675,3 +1695,41 @@ void WeatherDialog::updateAxesRanges(QtCharts::QChart *chart) axis->setVisible(timesUsed > 0); } } + +//-------------------------------------------------------------------- +void WeatherDialog::onForecastAreaChanged (QDateTime begin, QDateTime end) +{ + QLinearGradient plotAreaGradient; + plotAreaGradient.setStart(QPointF(0, 0)); + plotAreaGradient.setFinalStop(QPointF(1, 0)); + plotAreaGradient.setCoordinateMode(QGradient::ObjectBoundingMode); + + auto interpolateDt = [&begin, &end](const long long int dt) + { + return static_cast(dt-begin.toMSecsSinceEpoch())/(end.toMSecsSinceEpoch()-begin.toMSecsSinceEpoch()); + }; + + struct tm t; + for(int i = 0; i < m_forecast->size(); ++i) + { + const auto &entry = m_forecast->at(i); + const auto sunTimes = computeSunriseSunset(entry, m_config->longitude, m_config->latitude); + const auto entryDt = static_cast(entry.dt); + const bool isDay = entryDt > sunTimes.first && entryDt < sunTimes.second; + + unixTimeStampToDate(t, entry.dt); + const auto dtTime = QDateTime{QDate{t.tm_year + 1900, t.tm_mon + 1, t.tm_mday}, QTime{t.tm_hour, t.tm_min, t.tm_sec}}; + const auto msec = dtTime.toMSecsSinceEpoch(); + + double entryTime = interpolateDt(msec); + if(msec < begin.toMSecsSinceEpoch()) entryTime = 0; + if(msec > end.toMSecsSinceEpoch()) entryTime = 1; + + plotAreaGradient.setColorAt(entryTime, isDay ? Qt::white:Qt::lightGray); + } + + auto chart = m_weatherChart->chart(); + chart->setPlotAreaBackgroundBrush(plotAreaGradient); + chart->setPlotAreaBackgroundVisible(true); + chart->update(); +} diff --git a/WeatherDialog.h b/WeatherDialog.h index 0eb808a..e110e18 100644 --- a/WeatherDialog.h +++ b/WeatherDialog.h @@ -183,6 +183,13 @@ class WeatherDialog */ void onUVAreaChanged(QDateTime begin, QDateTime end); + /** \brief Updates the background of the forecast chart on zoom. + * \param[in] begin Begin point in X axis. + * \param[in] end End point in X axis. + * + */ + void onForecastAreaChanged(QDateTime begin, QDateTime end); + private: /** \brief Returns the color of the given aqi value. * \param[in] aqiValue aqi value in [1,5]. diff --git a/readme.md b/readme.md index c2a558c..946d4a0 100644 --- a/readme.md +++ b/readme.md @@ -80,10 +80,10 @@ Weather dialog, showing the current weather tab. ![weather](https://user-images.githubusercontent.com/12167134/127046991-e2eb1e5c-73d7-4ece-b9c4-dfff8dd1648e.png) -Weather forecast for the next days. If the user puts the mouse over a point in the temperature line or a bar a tooltip will provide the weather conditions for that day and hour. +Weather forecast for the next days. If the user puts the mouse over a point in the temperature line or a bar a tooltip will provide the weather conditions for that day and hour. Background is colored to day/night. The graph can be zoomed by selecting the area to zoom with the mouse and resetted to the initial state by using the reset button below the graph. Data series can be hidden and shown again by clicking on its legend text. -![forecast_graph](https://user-images.githubusercontent.com/12167134/109207324-4b36e800-77a9-11eb-9891-291c907d0aef.png) +![forecast_graph](https://user-images.githubusercontent.com/12167134/282960962-93cc1a0a-cd26-4dc1-a13f-267bf62361a6.png) Pollution forecast can be obtained in the third tab, showing the projections for the next days. The chart can be zoomed in the X axis and resetted by using the reset button below. The pollution chart also has a tooltip with detailed information for each point of the lines and @@ -148,7 +148,7 @@ To the translation in your language. For example in Spanish it is: # Repository information -**Version**: 1.26.1 +**Version**: 1.27.0 **Status**: finished. @@ -156,8 +156,8 @@ To the translation in your language. For example in Spanish it is: | Language |files |blank |comment |code | |:-----------------------------|--------------:|------------:|-----------------:|-----:| -| C++ | 10 | 1037 | 393 | 5120 | -| C/C++ Header | 10 | 283 | 829 | 933 | +| C++ | 10 | 1057 | 401 | 5189 | +| C/C++ Header | 10 | 285 | 840 | 935 | | HTML | 1 | 33 | 0 | 150 | | CMake | 1 | 19 | 11 | 125 | -| **Total** | **22** | **1372** | **1233** | **6328** | +| **Total** | **22** | **1394** | **1252** | **6399** |