mirror of
https://github.com/netbymatt/ws4kp.git
synced 2026-04-23 12:09:30 -07:00
portrait hourly graph #167
This commit is contained in:
BIN
server/images/backgrounds/1-chart-portrait.png
Normal file
BIN
server/images/backgrounds/1-chart-portrait.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 115 KiB |
BIN
server/images/gimp/1-chart-portrait.xcf
Normal file
BIN
server/images/gimp/1-chart-portrait.xcf
Normal file
Binary file not shown.
@@ -7,6 +7,12 @@ 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';
|
import settings from './settings.mjs';
|
||||||
|
|
||||||
|
// two chart areas
|
||||||
|
const chartAreas = [
|
||||||
|
'.top',
|
||||||
|
'.bottom',
|
||||||
|
];
|
||||||
|
|
||||||
// set up spacing and scales
|
// set up spacing and scales
|
||||||
const scaling = () => {
|
const scaling = () => {
|
||||||
const available = {
|
const available = {
|
||||||
@@ -24,6 +30,11 @@ const scaling = () => {
|
|||||||
dataLength.hours = 48;
|
dataLength.hours = 48;
|
||||||
dataLength.xTicks = 6;
|
dataLength.xTicks = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (settings.portrait?.value && settings.enhanced?.value) {
|
||||||
|
available.height = 450;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
available,
|
available,
|
||||||
dataLength,
|
dataLength,
|
||||||
@@ -85,10 +96,13 @@ class HourlyGraph extends WeatherDisplay {
|
|||||||
|
|
||||||
// get the image
|
// get the image
|
||||||
if (!this.image) this.image = this.elem.querySelector('.chart img');
|
if (!this.image) this.image = this.elem.querySelector('.chart img');
|
||||||
|
if (!this.portraitImage) this.portraitImage = this.elem.querySelector('.bottom .chart img');
|
||||||
|
|
||||||
// set up image
|
// set up images
|
||||||
this.image.width = available.width;
|
this.image.width = available.width;
|
||||||
this.image.height = available.height;
|
this.image.height = available.height;
|
||||||
|
this.portraitImage.width = available.width;
|
||||||
|
this.portraitImage.height = available.height;
|
||||||
|
|
||||||
// get context
|
// get context
|
||||||
const canvas = document.createElement('canvas');
|
const canvas = document.createElement('canvas');
|
||||||
@@ -97,31 +111,57 @@ class HourlyGraph extends WeatherDisplay {
|
|||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
ctx.imageSmoothingEnabled = false;
|
ctx.imageSmoothingEnabled = false;
|
||||||
|
|
||||||
|
// set the canvas for each graph to the top one by default
|
||||||
|
const contexts = [
|
||||||
|
ctx,
|
||||||
|
ctx,
|
||||||
|
ctx,
|
||||||
|
ctx,
|
||||||
|
];
|
||||||
|
|
||||||
|
// if in portrait-enhanced, change out the second two contexts with a second canvas
|
||||||
|
let portraitCanvas;
|
||||||
|
if (settings.portrait?.value && settings.enhanced?.value) {
|
||||||
|
portraitCanvas = document.createElement('canvas');
|
||||||
|
portraitCanvas.width = available.width;
|
||||||
|
portraitCanvas.height = available.height;
|
||||||
|
const portraitCtx = portraitCanvas.getContext('2d');
|
||||||
|
portraitCtx.imageSmoothingEnabled = false;
|
||||||
|
|
||||||
|
contexts[2] = portraitCtx;
|
||||||
|
contexts[3] = portraitCtx;
|
||||||
|
}
|
||||||
|
|
||||||
// calculate time scale
|
// calculate time scale
|
||||||
const timeScale = calcScale(0, 5, this.data.temperature.length - 1, available.width);
|
const timeScale = calcScale(0, 5, this.data.temperature.length - 1, available.width);
|
||||||
const timeStep = this.data.temperature.length / (dataLength.xTicks);
|
const timeStep = this.data.temperature.length / (dataLength.xTicks);
|
||||||
const startTime = DateTime.now().startOf('hour');
|
const startTime = DateTime.now().startOf('hour');
|
||||||
|
|
||||||
|
// there are two x axes in portrait
|
||||||
|
chartAreas.forEach((area) => {
|
||||||
let prevTime = startTime;
|
let prevTime = startTime;
|
||||||
|
const elem = this.elem.querySelector(area);
|
||||||
Array(dataLength.xTicks + 1).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;
|
||||||
// write to page
|
// write to page
|
||||||
document.querySelector(`.x-axis .l-${idx + 1}`).innerHTML = label.formatted;
|
elem.querySelector(`.x-axis .l-${idx + 1}`).innerHTML = label.formatted;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// order is important last line drawn is on top
|
// order is important last line drawn is on top
|
||||||
// clouds
|
// clouds
|
||||||
const percentScale = calcScale(0, available.height - 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, contexts[3], {
|
||||||
strokeStyle: 'lightgrey',
|
strokeStyle: 'lightgrey',
|
||||||
lineWidth: 3,
|
lineWidth: 3,
|
||||||
});
|
});
|
||||||
|
|
||||||
// precip
|
// precip
|
||||||
const precip = createPath(this.data.probabilityOfPrecipitation, timeScale, percentScale);
|
const precip = createPath(this.data.probabilityOfPrecipitation, timeScale, percentScale);
|
||||||
drawPath(precip, ctx, {
|
drawPath(precip, contexts[2], {
|
||||||
strokeStyle: 'aqua',
|
strokeStyle: 'aqua',
|
||||||
lineWidth: 3,
|
lineWidth: 3,
|
||||||
});
|
});
|
||||||
@@ -136,14 +176,14 @@ class HourlyGraph extends WeatherDisplay {
|
|||||||
|
|
||||||
// dewpoint
|
// dewpoint
|
||||||
const dewpointPath = createPath(this.data.dewpoint, timeScale, tempScale);
|
const dewpointPath = createPath(this.data.dewpoint, timeScale, tempScale);
|
||||||
drawPath(dewpointPath, ctx, {
|
drawPath(dewpointPath, contexts[1], {
|
||||||
strokeStyle: 'green',
|
strokeStyle: 'green',
|
||||||
lineWidth: 3,
|
lineWidth: 3,
|
||||||
});
|
});
|
||||||
|
|
||||||
// temperature
|
// temperature
|
||||||
const tempPath = createPath(this.data.temperature, timeScale, tempScale);
|
const tempPath = createPath(this.data.temperature, timeScale, tempScale);
|
||||||
drawPath(tempPath, ctx, {
|
drawPath(tempPath, contexts[0], {
|
||||||
strokeStyle: 'red',
|
strokeStyle: 'red',
|
||||||
lineWidth: 3,
|
lineWidth: 3,
|
||||||
});
|
});
|
||||||
@@ -151,6 +191,8 @@ class HourlyGraph extends WeatherDisplay {
|
|||||||
// temperature axis labels
|
// temperature axis labels
|
||||||
// limited to 3 characters, sacraficing degree character
|
// limited to 3 characters, sacraficing degree character
|
||||||
const degree = String.fromCharCode(176);
|
const degree = String.fromCharCode(176);
|
||||||
|
|
||||||
|
// only fill the upper chart with temperatures
|
||||||
this.elem.querySelector('.y-axis .l-1').innerHTML = (maxScale + degree).substring(0, 3);
|
this.elem.querySelector('.y-axis .l-1').innerHTML = (maxScale + degree).substring(0, 3);
|
||||||
this.elem.querySelector('.y-axis .l-2').innerHTML = (midScale2 + degree).substring(0, 3);
|
this.elem.querySelector('.y-axis .l-2').innerHTML = (midScale2 + degree).substring(0, 3);
|
||||||
this.elem.querySelector('.y-axis .l-3').innerHTML = (midScale1 + degree).substring(0, 3);
|
this.elem.querySelector('.y-axis .l-3').innerHTML = (midScale1 + degree).substring(0, 3);
|
||||||
@@ -159,6 +201,11 @@ class HourlyGraph extends WeatherDisplay {
|
|||||||
// set the image source
|
// set the image source
|
||||||
this.image.src = canvas.toDataURL();
|
this.image.src = canvas.toDataURL();
|
||||||
|
|
||||||
|
// if a portrait canvas was created set that image as well
|
||||||
|
if (portraitCanvas) {
|
||||||
|
this.portraitImage.src = portraitCanvas.toDataURL();
|
||||||
|
}
|
||||||
|
|
||||||
// change the units in the header
|
// change the units in the header
|
||||||
this.elem.querySelector('.temperature').innerHTML = `Temperature ${String.fromCharCode(176)}${this.data.temperatureUnit}`;
|
this.elem.querySelector('.temperature').innerHTML = `Temperature ${String.fromCharCode(176)}${this.data.temperatureUnit}`;
|
||||||
this.elem.querySelector('.dewpoint').innerHTML = `Dewpoint ${String.fromCharCode(176)}${this.data.temperatureUnit}`;
|
this.elem.querySelector('.dewpoint').innerHTML = `Dewpoint ${String.fromCharCode(176)}${this.data.temperatureUnit}`;
|
||||||
|
|||||||
@@ -7,22 +7,6 @@
|
|||||||
width: calc(p.$standard-width - (2 * p.$blue-box-margin));
|
width: calc(p.$standard-width - (2 * p.$blue-box-margin));
|
||||||
@include u.text-shadow();
|
@include u.text-shadow();
|
||||||
|
|
||||||
.portrait-only {
|
|
||||||
display: none;
|
|
||||||
|
|
||||||
.portrait.enhanced & {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.standard-only {
|
|
||||||
display: block;
|
|
||||||
|
|
||||||
.portrait.enhanced & {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.portrait-location {
|
.portrait-location {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-top: 175px;
|
margin-top: 175px;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
#hourly-graph-html {
|
#hourly-graph-html {
|
||||||
background-image: url(../images/backgrounds/1-chart.png);
|
background-image: url(../images/backgrounds/1-chart.png);
|
||||||
@@ -10,10 +11,22 @@
|
|||||||
background-position-x: 0px;
|
background-position-x: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
// change background for portrait
|
||||||
.right {
|
.portrait.enhanced & {
|
||||||
|
background-image: url(../images/backgrounds/1-chart-portrait.png);
|
||||||
|
background-position-x: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend {
|
||||||
|
.right & {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
||||||
|
.header & {
|
||||||
top: 35px;
|
top: 35px;
|
||||||
|
}
|
||||||
|
|
||||||
right: 60px;
|
right: 60px;
|
||||||
width: 360px;
|
width: 360px;
|
||||||
font-family: 'Star4000 Small';
|
font-family: 'Star4000 Small';
|
||||||
@@ -27,6 +40,13 @@
|
|||||||
|
|
||||||
.temperature {
|
.temperature {
|
||||||
color: red;
|
color: red;
|
||||||
|
|
||||||
|
// shift down when only 2 legend elements are shown in portrait
|
||||||
|
.header & {
|
||||||
|
.portrait.enhanced & {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.dewpoint {
|
.dewpoint {
|
||||||
@@ -42,15 +62,30 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.weather-display .main.hourly-graph {
|
.weather-display .main.hourly-graph {
|
||||||
|
|
||||||
&.main {
|
&.main {
|
||||||
|
|
||||||
|
.chart-container {
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
height: p.$standard-scroll-height;
|
||||||
|
|
||||||
|
&.portrait-only {
|
||||||
|
top: 495px;
|
||||||
|
}
|
||||||
|
|
||||||
>div {
|
>div {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.portrait.enhanced & {
|
||||||
|
height: 470px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
font-family: 'Star4000 Small';
|
font-family: 'Star4000 Small';
|
||||||
font-size: 24pt;
|
font-size: 24pt;
|
||||||
@@ -149,6 +184,10 @@
|
|||||||
.wide.enhanced & {
|
.wide.enhanced & {
|
||||||
width: 746px;
|
width: 746px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.portrait.enhanced & {
|
||||||
|
height: 450px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,6 +197,10 @@
|
|||||||
width: 50px;
|
width: 50px;
|
||||||
height: 285px;
|
height: 285px;
|
||||||
|
|
||||||
|
.portrait.enhanced & {
|
||||||
|
height: 450px;
|
||||||
|
}
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
right: 0px;
|
right: 0px;
|
||||||
@@ -177,6 +220,62 @@
|
|||||||
&.l-4 {
|
&.l-4 {
|
||||||
bottom: 0px;
|
bottom: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.portrait.enhanced & {
|
||||||
|
&.l-1 {
|
||||||
|
top: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.l-2 {
|
||||||
|
top: calc(445px / 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.l-3 {
|
||||||
|
bottom: calc(445px / 3 - 11px);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.l-4 {
|
||||||
|
bottom: 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.bottom {
|
||||||
|
.y-axis .label {
|
||||||
|
.portrait.enhanced & {
|
||||||
|
&.l-1 {
|
||||||
|
top: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.l-2 {
|
||||||
|
top: calc(445px / 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.l-3 {
|
||||||
|
top: calc(445px / 2 - 11px);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.l-4 {
|
||||||
|
bottom: calc(445px / 4 - 11px);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.l-5 {
|
||||||
|
bottom: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-container.bottom {
|
||||||
|
|
||||||
|
.right {
|
||||||
|
width: p.$standard-width;
|
||||||
|
position: absolute;
|
||||||
|
top: -25px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -851,3 +851,19 @@ body.kiosk #loading .instructions {
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.portrait-only {
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
.portrait.enhanced & {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.standard-only {
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
.portrait.enhanced & {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
2
server/styles/ws.min.css
vendored
2
server/styles/ws.min.css
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,11 +1,12 @@
|
|||||||
<%- include('header.ejs', {title: 'Hourly Graph' , hasTime: false }) %>
|
<%- include('header.ejs', {title: 'Hourly Graph' , hasTime: false }) %>
|
||||||
<div class="main has-scroll hourly-graph can-enhance">
|
<div class="main has-scroll hourly-graph can-enhance">
|
||||||
<div class="top-right template ">
|
<div class="top-right legend 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 standard-only">Cloud %</div>
|
||||||
<div class="rain">Precip %</div>
|
<div class="rain standard-only">Precip %</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="top chart-container">
|
||||||
<div class="y-axis">
|
<div class="y-axis">
|
||||||
<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>
|
||||||
@@ -25,3 +26,32 @@
|
|||||||
<div class="label l-7">12p</div>
|
<div class="label l-7">12p</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="bottom chart-container portrait-only">
|
||||||
|
<div class="right">
|
||||||
|
<div class="legend portrait-only">
|
||||||
|
<div class="cloud">Cloud %</div>
|
||||||
|
<div class="rain">Precip %</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="y-axis">
|
||||||
|
<div class="label l-1">100</div>
|
||||||
|
<div class="label l-2">75%</div>
|
||||||
|
<div class="label l-3">50%</div>
|
||||||
|
<div class="label l-4">25%</div>
|
||||||
|
<div class="label l-5">0%</div>
|
||||||
|
</div>
|
||||||
|
<div class="chart">
|
||||||
|
<img id="chart-area"></img>
|
||||||
|
</div>
|
||||||
|
<div class="x-axis">
|
||||||
|
<div class="label l-1">12a</div>
|
||||||
|
<div class="label l-2">6a</div>
|
||||||
|
<div class="label l-3">12p</div>
|
||||||
|
<div class="label l-4">6p</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>
|
||||||
Reference in New Issue
Block a user