From 9604aef6ba70bfddc20160a6d1b57d6cb4fa1425 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Jaenisch?= Date: Thu, 10 Oct 2024 16:27:23 +0200 Subject: [PATCH] feat: show hourly forecast MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This took me quite some trial and error to figure out. Signed-off-by: André Jaenisch --- weather-api-widget/README.md | 3 +- weather-api-widget/weather.lua | 194 ++++++++++++++++++++++++++++++++- 2 files changed, 190 insertions(+), 7 deletions(-) diff --git a/weather-api-widget/README.md b/weather-api-widget/README.md index f752dbb..9bfc5bc 100644 --- a/weather-api-widget/README.md +++ b/weather-api-widget/README.md @@ -18,7 +18,8 @@ following config parameters: | units | `metric` | `metric` for celsius, `imperial` for fahrenheit | | icon_pack_name | `weather-underground-icons` | Name of the icon pack, could be `weather-underground-icon` or `VitalyGorbachev` or create your own, more details below | | icons_extension | `.png` | File extension of icons in the pack | -| show_forecast | false | Show forecast for next three days | +| show_daily_forecast | false | Show forecast for next three days | +| show_hourly_forecast | false | Show hourly forecast section | | timeout | 120 | How often in seconds the widget refreshes | ### Icons: diff --git a/weather-api-widget/weather.lua b/weather-api-widget/weather.lua index 34a542f..6f9d601 100644 --- a/weather-api-widget/weather.lua +++ b/weather-api-widget/weather.lua @@ -171,14 +171,16 @@ local function worker(user_args) local api_key = args.api_key local font_name = args.font_name or beautiful.font:gsub("%s%d+$", "") local units = args.units or 'metric' + local time_format_12h = args.time_format_12h local both_units_widget = args.both_units_widget or false local icon_pack_name = args.icons or 'weather-underground-icons' local icons_extension = args.icons_extension or '.png' - local show_forecast = args.show_forecast or false + local show_daily_forecast = args.show_daily_forecast or false + local show_hourly_forecast = args.show_hourly_forecast or false local timeout = args.timeout or 120 local ICONS_DIR = WIDGET_DIR .. '/icons/' .. icon_pack_name .. '/' - -- Forecast endpoint includes current. I could map show_forecast to days here. + -- Forecast endpoint includes current. I could map show_daily_forecast to days here. -- Currently overfetching but only showing when opting in. local weather_api = ('https://api.weatherapi.com/v1/forecast.json' .. @@ -304,7 +306,7 @@ local function worker(user_args) end } - local forecast_widget = { + local daily_forecast_widget = { forced_width = 300, layout = wibox.layout.flex.horizontal, update = function(self, forecast) @@ -364,6 +366,181 @@ local function worker(user_args) end } + local hourly_forecast_graph = wibox.widget { + step_width = 12, + color = '#EBCB8B', + background_color = beautiful.bg_normal, + forced_height = 100, + forced_width = 300, + widget = wibox.widget.graph, + set_max_value = function(self, new_max_value) + self.max_value = new_max_value + end, + set_min_value = function(self, new_min_value) + self.min_value = new_min_value + end, + } + + local hourly_forecast_negative_graph = wibox.widget { + step_width = 12, + color = '#5E81AC', + background_color = beautiful.bg_normal, + forced_height = 100, + forced_width = 300, + widget = wibox.widget.graph, + set_max_value = function(self, new_max_value) + self.max_value = new_max_value + end, + set_min_value = function(self, new_min_value) + self.min_value = new_min_value + end, + } + + local hourly_forecast_widget = { + layout = wibox.layout.fixed.vertical, + update = function(self, hourly) + local hours_below = { + id = 'hours', + forced_width = 300, + layout = wibox.layout.flex.horizontal + } + local temp_below = { + id = 'temp', + forced_width = 300, + layout = wibox.layout.flex.horizontal + } + + local max_temp = -1000 + local min_temp = 1000 + local values= {} + + -- Yeah, this looks weird. I would expect to have to use ipairs + for i, hour in pairs(hourly) do + if i > 25 then + break + end + + values[i] = hour.temp_c + + if max_temp < hour.temp_c then + max_temp = hour.temp_c + end + + if min_temp > hour.temp_c then + min_temp = hour.temp_c + end + + if (i - 1) % 5 == 0 then + table.insert(hours_below, wibox.widget { + text = os.date(time_format_12h and '%I%p' or '%H:00', tonumber(hour.time_epoch)), + align = 'center', + font = font_name .. ' 9', + widget = wibox.widget.textbox + }) + + table.insert(temp_below, wibox.widget { + markup = ' 0 and '#2E3440' or '#ECEFF4') .. '">' + .. string.format('%.0f', hour.temp_c) .. '°' .. '', + align = 'center', + font = font_name .. ' 9', + widget = wibox.widget.textbox + }) + end + end + + hourly_forecast_graph:set_max_value(math.max(max_temp, math.abs(min_temp))) + hourly_forecast_graph:set_min_value(min_temp > 0 and min_temp * 0.7 or 0) -- move graph a bit up + + hourly_forecast_negative_graph:set_max_value(math.abs(min_temp)) + hourly_forecast_negative_graph:set_min_value(max_temp < 0 and math.abs(max_temp) * 0.7 or 0) + + for _, value in ipairs(values) do + if value >= 0 then + hourly_forecast_graph:add_value(value) + hourly_forecast_negative_graph:add_value(0) + else + hourly_forecast_graph:add_value(0) + hourly_forecast_negative_graph:add_value(math.abs(value)) + end + end + + local count = #self + for i = 0, count do + self[i] = nil + end + + -- all temperatures are positive + if min_temp > 0 then + table.insert(self, wibox.widget { + { + hourly_forecast_graph, + reflection = { horizontal = true }, + widget = wibox.container.mirror + }, + { + temp_below, + valign = 'bottom', + widget = wibox.container.place + }, + id = 'graph', + layout = wibox.layout.stack + }) + table.insert(self, hours_below) + + -- all temperatures are negative + elseif max_temp < 0 then + table.insert(self, hours_below) + table.insert(self, wibox.widget { + { + hourly_forecast_negative_graph, + reflection = { horizontal = true, vertical = true }, + widget = wibox.container.mirror + }, + { + temp_below, + valign = 'top', + widget = wibox.container.place + }, + id = 'graph', + layout = wibox.layout.stack + }) + + -- mixed temperatures + else + table.insert(self, wibox.widget { + { + hourly_forecast_graph, + reflection = { horizontal = true }, + widget = wibox.container.mirror + }, + { + temp_below, + valign = 'bottom', + widget = wibox.container.place + }, + id = 'graph', + layout = wibox.layout.stack + }) + + table.insert(self, wibox.widget { + { + hourly_forecast_negative_graph, + reflection = { horizontal = true, vertical = true }, + widget = wibox.container.mirror + }, + { + temp_below, + valign = 'top', + widget = wibox.container.place + }, + id = 'graph', + layout = wibox.layout.stack + }) + end + end + } + local function update_widget(widget, stdout, stderr) if stderr ~= '' then if not warning_shown then @@ -411,9 +588,14 @@ local function worker(user_args) } - if show_forecast then - forecast_widget:update(result.forecast.forecastday) - table.insert(final_widget, forecast_widget) + if show_hourly_forecast then + hourly_forecast_widget:update(result.forecast.forecastday[1].hour) + table.insert(final_widget, hourly_forecast_widget) + end + + if show_daily_forecast then + daily_forecast_widget:update(result.forecast.forecastday) + table.insert(final_widget, daily_forecast_widget) end weather_popup:setup({