This commit is contained in:
Matt Walsh
2026-04-05 23:58:50 -05:00
parent 778b7f4456
commit 9f6b90919c
16 changed files with 211 additions and 67 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

View File

@@ -5,10 +5,30 @@ import getHourlyData from './hourly.mjs';
import WeatherDisplay from './weatherdisplay.mjs'; import WeatherDisplay from './weatherdisplay.mjs';
import { registerDisplay, timeZone } from './navigation.mjs'; import { registerDisplay, timeZone } from './navigation.mjs';
import { DateTime } from '../vendor/auto/luxon.mjs'; import { DateTime } from '../vendor/auto/luxon.mjs';
import settings from './settings.mjs';
// get available space // set up spacing and scales
const availableWidth = 532; const scaling = () => {
const availableHeight = 285; const available = {
width: 532,
height: 285,
};
const dataLength = {
hours: 36,
xTicks: 4,
};
if (settings.wide?.value && settings.enhancedScreens?.value) {
available.width = available.width + 107 + 107;
available.height = 285;
dataLength.hours = 48;
dataLength.xTicks = 6;
}
return {
available,
dataLength,
};
};
class HourlyGraph extends WeatherDisplay { class HourlyGraph extends WeatherDisplay {
constructor(navId, elemId, defaultActive) { constructor(navId, elemId, defaultActive) {
@@ -46,28 +66,43 @@ class HourlyGraph extends WeatherDisplay {
skyCover, temperature, probabilityOfPrecipitation, temperatureUnit: data[0].temperatureUnit, dewpoint, skyCover, temperature, probabilityOfPrecipitation, temperatureUnit: data[0].temperatureUnit, dewpoint,
}; };
// get the data length for current settings
const { dataLength } = scaling();
// clamp down the data to the allowed size
Object.entries(this.data).forEach(([key, value]) => {
if (Array.isArray(value)) {
this.data[key] = value.slice(0, dataLength.hours);
}
});
this.setStatus(STATUS.loaded); this.setStatus(STATUS.loaded);
} }
drawCanvas() { drawCanvas() {
// get scaling parameters
const { dataLength, available } = scaling();
// get the image
if (!this.image) this.image = this.elem.querySelector('.chart img'); if (!this.image) this.image = this.elem.querySelector('.chart img');
this.image.width = availableWidth; // set up image
this.image.height = availableHeight; this.image.width = available.width;
this.image.height = available.height;
// get context // get context
const canvas = document.createElement('canvas'); const canvas = document.createElement('canvas');
canvas.width = availableWidth; canvas.width = available.width;
canvas.height = availableHeight; canvas.height = available.height;
const ctx = canvas.getContext('2d'); const ctx = canvas.getContext('2d');
ctx.imageSmoothingEnabled = false; ctx.imageSmoothingEnabled = false;
// calculate time scale // calculate time scale
const timeScale = calcScale(0, 5, this.data.temperature.length - 1, availableWidth); const timeScale = calcScale(0, 5, this.data.temperature.length - 1, available.width);
const timeStep = this.data.temperature.length / 4; const timeStep = this.data.temperature.length / (dataLength.xTicks);
const startTime = DateTime.now().startOf('hour'); const startTime = DateTime.now().startOf('hour');
let prevTime = startTime; let prevTime = startTime;
Array(5).fill().forEach((val, idx) => { Array(dataLength.xTicks + 1).fill().forEach((val, idx) => {
// track the previous label so a day of week can be added when it changes // track the previous label so a day of week can be added when it changes
const label = formatTime(startTime.plus({ hour: idx * timeStep }), prevTime); const label = formatTime(startTime.plus({ hour: idx * timeStep }), prevTime);
prevTime = label.ts; prevTime = label.ts;
@@ -77,7 +112,7 @@ class HourlyGraph extends WeatherDisplay {
// order is important last line drawn is on top // order is important last line drawn is on top
// clouds // clouds
const percentScale = calcScale(0, availableHeight - 10, 100, 10); const percentScale = calcScale(0, available.height - 10, 100, 10);
const cloud = createPath(this.data.skyCover, timeScale, percentScale); const cloud = createPath(this.data.skyCover, timeScale, percentScale);
drawPath(cloud, ctx, { drawPath(cloud, ctx, {
strokeStyle: 'lightgrey', strokeStyle: 'lightgrey',
@@ -97,7 +132,7 @@ class HourlyGraph extends WeatherDisplay {
const thirdScale = (maxScale - minScale) / 3; const thirdScale = (maxScale - minScale) / 3;
const midScale1 = Math.round(minScale + thirdScale); const midScale1 = Math.round(minScale + thirdScale);
const midScale2 = Math.round(minScale + (thirdScale * 2)); const midScale2 = Math.round(minScale + (thirdScale * 2));
const tempScale = calcScale(minScale, availableHeight - 10, maxScale, 10); const tempScale = calcScale(minScale, available.height - 10, maxScale, 10);
// dewpoint // dewpoint
const dewpointPath = createPath(this.data.dewpoint, timeScale, tempScale); const dewpointPath = createPath(this.data.dewpoint, timeScale, tempScale);

View File

@@ -238,7 +238,7 @@ const determineIcon = async (skyCover, weather, iceAccumulation, probabilityOfPr
}; };
// expand a set of values with durations to an hour-by-hour array // expand a set of values with durations to an hour-by-hour array
const expand = (data, maxHours = 36) => { const expand = (data, maxHours = 48) => {
const startOfHour = DateTime.utc().startOf('hour').toMillis(); const startOfHour = DateTime.utc().startOf('hour').toMillis();
const result = []; // resulting expanded values const result = []; // resulting expanded values
data.forEach((item) => { data.forEach((item) => {

View File

@@ -32,6 +32,25 @@ const wideScreenChange = (value) => {
window.dispatchEvent(new Event('resize')); window.dispatchEvent(new Event('resize'));
}; };
const enhancedScreenChange = (value) => {
const container = document.querySelector('#divTwc');
if (!container) {
// DOM not ready; defer enabling if set
if (value) {
deferredDomSettings.add('enhanced');
}
return;
}
if (value) {
container.classList.add('enhanced');
} else {
container.classList.remove('enhanced');
}
// Trigger resize to recalculate scaling for new width
window.dispatchEvent(new Event('resize'));
};
const kioskChange = (value) => { const kioskChange = (value) => {
const body = document.querySelector('body'); const body = document.querySelector('body');
if (!body) { if (!body) {
@@ -138,6 +157,7 @@ const init = () => {
settings.enhancedScreens = new Setting('enhancedScreens', { settings.enhancedScreens = new Setting('enhancedScreens', {
name: 'Enhanced Screens', name: 'Enhanced Screens',
defaultValue: false, defaultValue: false,
changeAction: enhancedScreenChange,
sticky: true, sticky: true,
}); });
settings.kiosk = new Setting('kiosk', { settings.kiosk = new Setting('kiosk', {

View File

@@ -1,8 +1,10 @@
@use 'shared/_colors' as c; @use 'shared/_colors'as c;
@use 'shared/_utils' as u; @use 'shared/_utils'as u;
@use 'shared/positions'as p;
.weather-display .main.current-weather { .weather-display .main.current-weather {
&.main { &.main {
width: calc(p.$standard-width - (2 * p.$blue-box-margin));
.col { .col {
height: 50px; height: 50px;
@@ -17,7 +19,6 @@
&.left { &.left {
font-family: 'Star4000 Extended'; font-family: 'Star4000 Extended';
font-size: 24pt; font-size: 24pt;
} }
&.right { &.right {
@@ -92,4 +93,4 @@
text-wrap: nowrap; text-wrap: nowrap;
} }
} }
} }

View File

@@ -4,6 +4,12 @@
#hourly-graph-html { #hourly-graph-html {
background-image: url(../images/backgrounds/1-chart.png); background-image: url(../images/backgrounds/1-chart.png);
// change background for wide-enhanced
.wide.enhanced & {
background-image: url(../images/backgrounds/1-chart-wide.png);
background-position-x: 0px;
}
.header { .header {
.right { .right {
position: absolute; position: absolute;
@@ -84,10 +90,51 @@
&.l-5 { &.l-5 {
left: calc(532px / 4 * 4); left: calc(532px / 4 * 4);
} }
// adjust when enhanced
.wide.enhanced & {
&.l-1 {
left: 0px;
}
&.l-2 {
left: calc(726px / 6 * 1);
}
&.l-3 {
left: calc(726px / 6 * 2);
}
&.l-4 {
left: calc(726px / 6 * 3);
}
&.l-5 {
left: calc(726px / 6 * 4);
}
&.l-6 {
left: calc(726px / 6 * 5);
}
&.l-7 {
left: calc(726px / 6 * 6);
}
}
// only in wide + enhanced
&.l-6,
&.l-7 {
display: none;
.wide.enhanced & {
display: block;
}
}
} }
} }
.chart { .chart {
@@ -97,6 +144,11 @@
img { img {
width: 532px; width: 532px;
height: 285px; height: 285px;
// wide and enhanced
.wide.enhanced & {
width: 746px;
}
} }
} }
@@ -128,32 +180,5 @@
} }
} }
.column-headers {
background-color: c.$column-header;
height: 20px;
position: absolute;
width: 100%;
}
.column-headers {
position: sticky;
top: 0px;
z-index: 5;
.temp {
left: 355px;
}
.like {
left: 435px;
}
.wind {
left: 535px;
}
}
} }
} }

View File

@@ -1,7 +1,14 @@
@use 'shared/_colors'as c; @use 'shared/_colors'as c;
@use 'shared/_utils'as u; @use 'shared/_utils'as u;
@use 'shared/positions'as p;
.weather-display .local-forecast { .weather-display .local-forecast {
// clamp width to standard
&.main {
width: calc(p.$standard-width - (2 * p.$blue-box-margin));
}
.container { .container {
position: relative; position: relative;
top: 15px; top: 15px;

View File

@@ -1,5 +1,6 @@
@use 'shared/_utils'as u; @use 'shared/_utils'as u;
@use 'shared/_colors'as c; @use 'shared/_colors'as c;
@use 'shared/positions'as p;
@font-face { @font-face {
font-family: "Star4000"; font-family: "Star4000";
@@ -345,8 +346,7 @@ body {
} }
.wide #container { .wide #container {
padding-left: 107px; width: p.$wide-width;
padding-right: 107px;
background: url(../images/backgrounds/1-wide.png); background: url(../images/backgrounds/1-wide.png);
background-repeat: no-repeat; background-repeat: no-repeat;
} }
@@ -368,6 +368,10 @@ body {
text-align: center; text-align: center;
justify-content: center; justify-content: center;
.wide & {
margin-left: p.$wide-margin;
}
.title { .title {
font-family: Star4000 Large; font-family: Star4000 Large;
font-size: 36px; font-size: 36px;

View File

@@ -1,11 +1,17 @@
@use 'shared/_colors' as c; @use 'shared/_colors'as c;
@use 'shared/_utils' as u; @use 'shared/_utils'as u;
@use 'shared/positions'as p;
.weather-display .progress { .weather-display .progress {
@include u.text-shadow(); @include u.text-shadow();
font-family: 'Star4000 Extended'; font-family: 'Star4000 Extended';
font-size: 19pt; font-size: 19pt;
// clamp width to standard
&.main {
width: calc(p.$standard-width - (2 * p.$blue-box-margin));
}
.container { .container {
position: relative; position: relative;
top: 15px; top: 15px;
@@ -118,4 +124,4 @@
transition: width 1s steps(6); transition: width 1s steps(6);
} }
} }
} }

View File

@@ -1,5 +1,6 @@
@use 'shared/_colors'as c; @use 'shared/_colors'as c;
@use 'shared/_utils'as u; @use 'shared/_utils'as u;
@use 'shared/positions'as p;
#spc-outlook-html.weather-display { #spc-outlook-html.weather-display {
background-image: url('../images/backgrounds/6.png'); background-image: url('../images/backgrounds/6.png');

View File

@@ -1,29 +1,43 @@
@use 'shared/_colors'as c; @use 'shared/_colors'as c;
@use 'shared/_utils'as u; @use 'shared/_utils'as u;
@use 'shared/positions'as p;
.weather-display { .weather-display {
width: 640px; width: p.$standard-width;
height: 480px; height: p.$standard-height;
overflow: hidden; overflow: hidden;
position: relative; position: relative;
background-image: url(../images/backgrounds/1.png); background-image: url(../images/backgrounds/1.png);
// adjust for wide
.wide & {
width: p.$wide-width;
background-position-x: p.$wide-margin;
background-repeat: no-repeat;
}
/* this method is required to hide blocks so they can be measured while off screen */ /* this method is required to hide blocks so they can be measured while off screen */
height: 0px; height: 0px;
&.show { &.show {
height: 480px; height: p.$standard-height;
} }
.template { .template {
display: none; display: none;
} }
.header { >.header {
width: 640px; width: p.$standard-width;
height: 60px; height: 60px;
position: relative;
padding-top: 30px; padding-top: 30px;
// adjust for wide
.wide & {
left: p.$wide-margin;
}
.title { .title {
color: c.$title-color; color: c.$title-color;
@include u.text-shadow(3px, 1.5px); @include u.text-shadow(3px, 1.5px);
@@ -92,8 +106,21 @@
.main { .main {
position: relative; position: relative;
// adjust for wide
.wide & {
left: p.$wide-margin;
}
// adjust for enhanced when possible
.wide.enhanced & {
&.can-enhance {
left: 0px;
width: p.$wide-width;
}
}
&.has-scroll { &.has-scroll {
width: 640px; width: p.$standard-width;
margin-top: 0; margin-top: 0;
height: 310px; height: 310px;
overflow: hidden; overflow: hidden;
@@ -117,7 +144,7 @@
#container>.scroll { #container>.scroll {
display: none; display: none;
@include u.text-shadow(3px, 1.5px); @include u.text-shadow(3px, 1.5px);
width: 640px; width: p.$standard-width;
height: 77px; height: 77px;
overflow: hidden; overflow: hidden;
margin-top: 3px; margin-top: 3px;
@@ -125,12 +152,17 @@
bottom: 0px; bottom: 0px;
z-index: 1; z-index: 1;
// adjust for wide
.wide & {
left: p.$wide-margin;
}
&.hazard { &.hazard {
background-color: rgb(112, 35, 35); background-color: rgb(112, 35, 35);
} }
.scroll-container { .scroll-container {
width: 640px; width: p.$standard-width;
.fixed, .fixed,
.scroll-header { .scroll-header {
@@ -156,7 +188,7 @@
position: relative; position: relative;
// the following added by js code as it is dependent on the content of the element // the following added by js code as it is dependent on the content of the element
// transition: left (x)s; // transition: left (x)s;
// left: calc((elem width) - 640px); // left: calc((elem width) - p.$standard-width);
} }
} }
} }
@@ -167,9 +199,9 @@
.wide #container>.scroll { .wide #container>.scroll {
width: 854px; width: 854px;
margin-left: -107px; margin-left: -1*p.$wide-margin;
.scroll-container { .scroll-container {
margin-left: 107px; margin-left: p.$wide-margin;
} }
} }

View File

@@ -0,0 +1,11 @@
// standard positioning
$standard-width: 640px;
$standard-height: 480px;
// blue box size
$blue-box-margin: 64px;
// wide screen positioning
$wide-padding: 107px;
$wide-margin: 107px;
$wide-width: 854px;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,8 +1,8 @@
<%- include('header.ejs', {title: 'Hourly Graph' , hasTime: false }) %> <%- include('header.ejs', {title: 'Hourly Graph' , hasTime: false }) %>
<div class="main has-scroll hourly-graph"> <div class="main has-scroll hourly-graph can-enhance">
<div class="top-right template "> <div class="top-right template ">
<div class="temperature">Temperature</div> <div class="temperature">Temperature</div>
<div class="dewpoint">Dewpoint</div> <div class="dewpoint">Dewpoint</div>
<div class="cloud">Cloud %</div> <div class="cloud">Cloud %</div>
<div class="rain">Precip %</div> <div class="rain">Precip %</div>
</div> </div>
@@ -10,7 +10,7 @@
<div class="label l-1">75</div> <div class="label l-1">75</div>
<div class="label l-2">65</div> <div class="label l-2">65</div>
<div class="label l-3">55</div> <div class="label l-3">55</div>
<div class="label l-4">45</div> <div class="label l-4">45</div>
</div> </div>
<div class="chart"> <div class="chart">
<img id="chart-area"></img> <img id="chart-area"></img>
@@ -21,5 +21,7 @@
<div class="label l-3">12p</div> <div class="label l-3">12p</div>
<div class="label l-4">6p</div> <div class="label l-4">6p</div>
<div class="label l-5">12a</div> <div class="label l-5">12a</div>
<div class="label l-6">6a</div>
<div class="label l-7">12p</div>
</div> </div>
</div> </div>