Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโ€™ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hourly temperature visualization #1693

Merged
merged 8 commits into from
Sep 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion web/themes/new_weather_theme/assets/css/styles.css

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion web/themes/new_weather_theme/assets/css/styles.css.map

Large diffs are not rendered by default.

133 changes: 133 additions & 0 deletions web/themes/new_weather_theme/assets/js/charts/hourly-temperature.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/* global Chart ChartDataLabels */
(() => {
const styles = getComputedStyle(document.body);

const fontMono = styles.getPropertyValue("--font-family-mono");
const colors = {
base: styles.getPropertyValue("--color-base"),
baseLighter: styles.getPropertyValue("--color-base-lighter"),
baseLightest: styles.getPropertyValue("--color-base-lightest"),
primary: styles.getPropertyValue("--color-primary"),
primaryDark: styles.getPropertyValue("--color-primary-dark"),
primaryLight: styles.getPropertyValue("--color-primary-light"),
};

Chart.register(ChartDataLabels);

// These are applied globally to all charts. Unclear if that's okay, or if
// what we really want is to set them per-chart, but this is what I've got
// for now.
Chart.defaults.font = {
family: fontMono,
size: 12,
};

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),
);

// We don't need to keep a reference to the chart object. We only need the
// side-effects of creating it. This is not ideal, but it's how Chart.js
// works, so it's what we've got.
// eslint-disable-next-line no-new
new Chart(container.querySelector("canvas"), {
type: "line",
plugins: [ChartDataLabels],

options: {
animation: false,
responsive: true,
maintainAspectRatio: false,
interaction: {
intersect: false,
mode: "index",
},
plugins: {
legend: {
display: false,
},
},
scales: {
x: {
ticks: {
autoSkip: true,
maxRotation: 0,
color: colors.base,
},
grid: {
color: times.map((v) => {
if (v === "12 AM") {
return "black";
}

const even = Number.parseInt(v, 10) % 2 === 0;
if (even) {
return colors.baseLighter;
}
return colors.baseLightest;
}),
},
},
y: {
min: Math.min(
Math.round(Math.min(...temps) / 10) * 10 - 10,
Math.round(Math.min(...feelsLike) / 10) * 10 - 10,
),
max: Math.max(
Math.round(Math.max(...temps) / 10) * 10 + 10,
Math.round(Math.max(...feelsLike) / 10) * 10 + 10,
),
ticks: {
autoSkip: true,
color: colors.base,
maxTicksLimit: 6,
callback: (v) => `${v}ยฐ`,
},
},
},
},

data: {
labels: times,
datasets: [
{
label: "Temperature",
data: temps,
datalabels: {
align: ({ dataIndex }) =>
temps[dataIndex] >= feelsLike[dataIndex] ? "top" : "bottom",
color: colors.primaryDark,
},
backgroundColor: colors.primaryDark,
borderColor: colors.primaryDark,
borderWidth: 1.5,
},
{
label: "Feels like",
data: feelsLike,
datalabels: {
align: ({ dataIndex }) =>
temps[dataIndex] >= feelsLike[dataIndex] ? "bottom" : "top",
color: colors.primary,
display: ({ dataIndex }) =>
temps[dataIndex] !== feelsLike[dataIndex],
},
borderDash: [4],
backgroundColor: colors.primaryLight,
borderColor: colors.primaryLight,
borderWidth: 1.5,
},
],
},
});
}
})();
11 changes: 11 additions & 0 deletions web/themes/new_weather_theme/assets/sass/components/variables.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
@use "uswds-core" as *;

body {
--color-base: #{color("base")};
--color-base-lighter: #{color("base-lighter")};
--color-base-lightest: #{color("base-lightest")};
--color-primary: #{color("primary")};
--color-primary-dark: #{color("primary-dark")};
--color-primary-light: #{color("primary-light")};
--font-family-mono: #{font-family("mono")};
}
1 change: 1 addition & 0 deletions web/themes/new_weather_theme/assets/sass/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
@use "components/touchpoints";
@use "components/usa-identifier";
@use "components/utilities";
@use "components/variables";
@use "components/visual-page-headings";
@use "components/weather-story";
@use "components/wfo-page";
Expand Down
22 changes: 22 additions & 0 deletions web/themes/new_weather_theme/new_weather_theme.libraries.yml
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,25 @@ 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:
attributes:
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
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
{% 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 %}

<div class="grid-container padding-x-0 tablet:padding-x-2">
<ol class="usa-list--unstyled">
{# Attempt to render the period(s) associated with the current day #}
Expand All @@ -22,5 +21,6 @@
{% endfor %}
</ol>
</div>

{% endif %}
</div>
Original file line number Diff line number Diff line change
@@ -1,7 +1,33 @@
{{ 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 %}

<wx-hourly-table class="display-block position-relative margin-top-2 bg-white">
<h5 class="wx-visual-h3 font-heading-md text-normal text-primary-darker margin-top-0 margin-bottom-205">Hourly forecast</h5>

<div class="margin-bottom-205">
<span class="font-family-mono text-primary-dark">Temperature</span>
<div class="wx-hourly-temp-chart-container margin-y-105 position-relative grid-col-12" data-times="{{ times }}" data-temps="{{ temps }}" data-feels-like="{{ feelsLike }}">
<canvas>
</canvas>
</div>
<div class="display-flex flex-align-center font-mono-xs text-base">
<div class="width-3 border-bottom-2px border-primary-dark height-0 margin-right-1">
<div class="bg-primary-dark margin-left-1 radius-pill width-1 height-1" style="margin-top: -3px;"></div>
</div>
Temperature
<div class="margin-left-3 width-3 border-bottom-2px border-top-0 border-left-0 border-right-0 border-dotted border-primary-light height-0 margin-right-1">
<div class="bg-primary-light margin-left-1 radius-pill width-1 height-1" style="margin-top: -3px;"></div>
</div>
Feels like
</div>
</div>


<div class="display-flex flex-justify">
<div>
<button class="wx-scroll-button bg-gray-30 circle-5 border-0 text-white display-flex flex-align-center flex-justify-center padding-y-1px padding-x-05" data-direction="left" type="button" role="button">
Expand Down
Loading