From 088aa17002c2e6e2f1e0ea1a51420eb982303222 Mon Sep 17 00:00:00 2001 From: Greg Walker Date: Tue, 27 Aug 2024 14:23:12 -0500 Subject: [PATCH 1/7] chartjs for daily and hourly temps --- .../js/charts/daily-temperature-range.js | 105 ++++++++++++++++++ .../assets/js/charts/hourly-temperature.js | 91 +++++++++++++++ .../new_weather_theme.libraries.yml | 30 +++++ ...block--weathergov-daily-forecast.html.twig | 42 +++++++ .../templates/partials/hourly-table.html.twig | 15 +++ 5 files changed, 283 insertions(+) create mode 100644 web/themes/new_weather_theme/assets/js/charts/daily-temperature-range.js create mode 100644 web/themes/new_weather_theme/assets/js/charts/hourly-temperature.js diff --git a/web/themes/new_weather_theme/assets/js/charts/daily-temperature-range.js b/web/themes/new_weather_theme/assets/js/charts/daily-temperature-range.js new file mode 100644 index 000000000..e978b0107 --- /dev/null +++ b/web/themes/new_weather_theme/assets/js/charts/daily-temperature-range.js @@ -0,0 +1,105 @@ +/* global Chart ChartDataLabels */ +(() => { + Chart.register(ChartDataLabels); + + const container = document.querySelector( + ".wx-daily-temp-range-chart-container", + ); + + const data = JSON.parse(container.dataset.days); + + const d2 = JSON.parse(JSON.stringify(data)); + + const chart = new Chart(container.querySelector("canvas"), { + plugins: [ChartDataLabels], + + options: { + animation: false, + responsive: true, + maintainAspectRatio: false, + showLine: false, + plugins: { + legend: { + display: false, + }, + }, + layout: { + padding: { + right: 30, + }, + }, + scales: { + x: { + grid: { + display: false, + }, + }, + y: { + min: + Math.floor( + Math.min(...data.map(({ temps }) => Math.min(...temps))) / 10, + ) * 10, + max: + Math.ceil( + Math.max(...data.map(({ temps }) => Math.max(...temps))) / 10, + ) * 10, + }, + }, + }, + + data: { + labels: data.map(({ day }) => day), + + datasets: [ + { + type: "bar", + backgroundColor: "transparent", + borderColor: "transparent", + data: data.map(({ temps }) => Math.min(...temps)), + options: { scales: { x: { stacked: true } } }, + stack: "diff", + datalabels: { + display: false, + }, + }, + + { + type: "bar", + backgroundColor: "rgb(11,71,120)", + borderColor: "rgb(11,71,120)", + data: data.map(({ temps }) => + Math.abs(temps.reduce((p, n) => n - p, 0)), + ), + barThickness: 1, + options: { + scales: { x: { stacked: true } }, + }, + stack: "diff", + datalabels: { + display: false, + }, + }, + { + type: "line", + backgroundColor: "rgb(11,71,120)", + borderColor: "rgb(11,71,120)", + data: data.map(({ temps }) => temps.pop()), + datalabels: { + align: "right", + formatter: (v) => `${v}℉`, + }, + }, + { + type: "line", + backgroundColor: "rgb(11,71,120)", + borderColor: "rgb(11,71,120)", + data: data.map(({ temps }) => temps.pop()), + datalabels: { + align: "right", + formatter: (v) => `${v}℉`, + }, + }, + ], + }, + }); +})(); diff --git a/web/themes/new_weather_theme/assets/js/charts/hourly-temperature.js b/web/themes/new_weather_theme/assets/js/charts/hourly-temperature.js new file mode 100644 index 000000000..c828378cc --- /dev/null +++ b/web/themes/new_weather_theme/assets/js/charts/hourly-temperature.js @@ -0,0 +1,91 @@ +/* global Chart ChartDataLabels */ +(() => { + Chart.register(ChartDataLabels); + + const chartContainers = Array.from( + document.querySelectorAll(".wx-hourly-temp-chart-container"), + ); + + for (const container of chartContainers) { + const times = JSON.parse(container.dataset.times); + const temps = JSON.parse(container.dataset.temps).map((v) => + Number.parseInt(v, 10), + ); + const feelsLike = JSON.parse(container.dataset.feelsLike).map((v) => + Number.parseInt(v, 10), + ); + + const chart = new Chart(container.querySelector("canvas"), { + type: "line", + plugins: [ChartDataLabels], + + options: { + animation: false, + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { + align: "start", + position: "bottom", + }, + }, + scales: { + x: { + ticks: { maxRotation: 0 }, + grid: { + color: times.map((v) => { + if (v === "12 AM") { + return "black"; + } + + const even = Number.parseInt(v, 10) % 2 === 0; + if (even) { + return "#ccc"; + } + return "#eee"; + }), + }, + }, + y: { + min: Math.min( + 0, + Math.min(...temps) - 15, + Math.min(...feelsLike) - 15, + ), + max: Math.max( + 100, + Math.max(...temps) + 15, + Math.max(...feelsLike) + 15, + ), + ticks: { + autoSkip: true, + maxTicksLimit: 6, + callback: (v) => `${v}℉`, + }, + }, + }, + }, + + data: { + labels: times.map((v) => (Number.parseInt(v, 10) % 2 === 0 ? v : "")), + datasets: [ + { + label: "Temperature", + data: temps, + datalabels: { + align: (_, i) => (temps[i] > feelsLike[i] ? "top" : "bottom"), + }, + }, + { + label: "Feels like", + data: feelsLike, + borderDash: [4], + datalabels: { + align: (_, i) => (temps[i] > feelsLike[i] ? "bottom" : "top"), + }, + }, + ], + }, + }); + } +})(); diff --git a/web/themes/new_weather_theme/new_weather_theme.libraries.yml b/web/themes/new_weather_theme/new_weather_theme.libraries.yml index 3af3e0626..f754cb418 100644 --- a/web/themes/new_weather_theme/new_weather_theme.libraries.yml +++ b/web/themes/new_weather_theme/new_weather_theme.libraries.yml @@ -98,3 +98,33 @@ afd-selector: version: 2024-08-16 js: assets/js/components/afd-selector.js: { preprocess: false } + +chartjs: + version: 4.4.1 + header: true + js: + https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.min.js: + # https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.min.js: + attributes: + # integrity: "sha512-L0Shl7nXXzIlBSUUPpxrokqq4ojqgZFQczTYlGjzONGTDAcLremjwaWv5A+EDLnxhQzY5xUZPWLOLqYRkY0Cbw==", + integrity: "sha512-CQBWl4fJHWbryGE+Pc7UAxWMUMNMWzWxF4SQo9CgkJIN1kx6djDQZjh3Y8SZ1d+6I+1zze6Z7kHXO7q3UyZAWw==" + crossorigin: "anonymous" + referrerpolicy: "no-referrer" + https://cdnjs.cloudflare.com/ajax/libs/chartjs-plugin-datalabels/2.2.0/chartjs-plugin-datalabels.min.js: + attributes: + integrity: sha512-JPcRR8yFa8mmCsfrw4TNte1ZvF1e3+1SdGMslZvmrzDYxS69J7J49vkFL8u6u8PlPJK+H3voElBtUCzaXj+6ig== + crossorigin: anonymous + referrerpolicy: no-referrer + +chart-hourly-temperature: + version: 2024-08-27 + js: + assets/js/charts/hourly-temperature.js: + attributes: + defer: true +chart-daily-temperature-range: + version: 2024-08-27 + js: + assets/js/charts/daily-temperature-range.js: + attributes: + defer: true \ No newline at end of file diff --git a/web/themes/new_weather_theme/templates/block/block--weathergov-daily-forecast.html.twig b/web/themes/new_weather_theme/templates/block/block--weathergov-daily-forecast.html.twig index e14e3b768..be45459f4 100644 --- a/web/themes/new_weather_theme/templates/block/block--weathergov-daily-forecast.html.twig +++ b/web/themes/new_weather_theme/templates/block/block--weathergov-daily-forecast.html.twig @@ -1,11 +1,52 @@
{{ attach_library('new_weather_theme/hourly-toggle') }} + {{ attach_library('new_weather_theme/chartjs') }} + {{ attach_library('new_weather_theme/chart-daily-temperature-range') }} {% if content.error %} {% set message = "There was an error loading the daily forecast." | t %} {% include '@new_weather_theme/partials/uswds-alert.html.twig' with { 'level': "error", body: message } %} {% else %} + {% set days = [] %} + {% if content.today %} + {% set temps = content.today | filter(v => v.isOvernight != true) | map(v => v.temperature) %} + {% set dayLetter = content.today.0.dayName | slice(0, 1) %} + + {% set high = max(temps) %} + {% set low = min(temps) %} + + {% if high != low %} + {% set days = days | merge([{"day": dayLetter, "temps": [high, low] }]) %} + {% else %} + {% set days = days | merge([{"day": dayLetter, "temps": [high] }]) %} + {% endif %} + {% endif %} + + {% for day in content.detailed %} + {% set temps = [day.daytime.temperature, day.nighttime.temperature] %} + {% set dayLetter = day.daytime.dayName | slice(0, 1) %} + + {% set high = max(temps) %} + {% set low = min(temps) %} + + {% if high != low %} + {% set days = days | merge([{"day": dayLetter, "temps": [high, low] }]) %} + {% else %} + {% set days = days | merge([{"day": dayLetter, "temps": [high] }]) %} + {% endif %} + {% endfor %} + +
+
+ Temperature +
+ + +
+
+
+
    {# Attempt to render the period(s) associated with the current day #} @@ -22,5 +63,6 @@ {% endfor %}
+ {% endif %}
diff --git a/web/themes/new_weather_theme/templates/partials/hourly-table.html.twig b/web/themes/new_weather_theme/templates/partials/hourly-table.html.twig index d9cc376ef..c7d92d0f0 100644 --- a/web/themes/new_weather_theme/templates/partials/hourly-table.html.twig +++ b/web/themes/new_weather_theme/templates/partials/hourly-table.html.twig @@ -1,7 +1,22 @@ {{ attach_library('new_weather_theme/hourly-table') }} +{{ attach_library('new_weather_theme/chartjs') }} +{{ attach_library('new_weather_theme/chart-hourly-temperature') }} + +{% set times = hours | map(h => h.time) | json_encode %} +{% set temps = hours | map(h => h.temperature) | json_encode %} +{% set feelsLike = hours | map(h => h.apparentTemperature) | json_encode %} +
Hourly forecast
+
+ Temperature +
+ + +
+
+
+
+
+
+
+ Temperature +
+
+
+ Feels like +
+