fallback and 404

This commit is contained in:
Vincent van der Wal
2026-07-19 22:47:05 +02:00
parent baa4840b38
commit cd1a47b6c3
11 changed files with 580 additions and 212 deletions
@@ -1,6 +1,8 @@
<script lang="ts">
import { fade } from 'svelte/transition';
import { storedVariablePrefs } from '$lib/stores/settings';
import { formatZoned, getRelativeDayLabel } from '$lib/utils/date';
import { ChartContainer, ChartToolbar } from '$lib/components/charts';
@@ -68,6 +70,12 @@
{ label: 'All', apply: () => resetZoom() }
];
// Soft band drawn on every chart marking the currently selected day
let selectedDayHighlight = $derived.by(() => {
const start = dayStartSec(selectedDay);
return start == null ? undefined : { start, end: start + SECONDS_PER_DAY };
});
// ─── Series Building ────────────────────────────────────────────────────────
let tempUnit = $derived(getTempUnit(units));
@@ -90,17 +98,14 @@
if (!data) return [];
const { hourly } = data;
// Variables the user disabled in the sidebar are left out entirely
const on = (key: string): boolean => $storedVariablePrefs.charts?.[key] ?? true;
const defs: ChartDef[] = [];
const tempChart: ChartDef = {
title: 'Temperature & Cloud Cover',
unit: tempUnit,
// Hidden inverted right axis (0-250) so cloud cover hangs from the top,
// occupying at most the upper 40% of the plot
yMinRight: 0,
yMaxRight: 250,
invertRight: true,
series: [
{
if (on('temperature') || on('cloud_cover')) {
const series: ChartSeries[] = [];
if (on('cloud_cover')) {
series.push({
name: 'Cloud Cover',
type: 'line',
color: 'rgb(150, 150, 150)',
@@ -110,8 +115,10 @@
fillOpacity: 0.25,
axis: 'right',
format: (v) => `${v.toFixed(0)}%`
},
{
});
}
if (on('temperature')) {
series.push({
name: 'Temperature',
type: 'line',
color: '#ef6c00',
@@ -120,26 +127,35 @@
fill: true,
fillOpacity: 0.2,
format: (v) => `${v.toFixed(1)} ${tempUnit}`
}
]
};
});
}
defs.push({
title: [on('temperature') && 'Temperature', on('cloud_cover') && 'Cloud Cover']
.filter(Boolean)
.join(' & '),
unit: tempUnit,
// Hidden inverted right axis (0-250) so cloud cover hangs from the top,
// occupying at most the upper 40% of the plot
yMinRight: 0,
yMaxRight: 250,
invertRight: true,
series
});
}
const precipChart: ChartDef = {
title: 'Precipitation & Probability',
unit: precipUnit,
unitRight: '%',
yMin: 0,
yMinRight: 0,
yMaxRight: 100,
series: [
{
if (on('precipitation') || on('precipitation_probability')) {
const series: ChartSeries[] = [];
if (on('precipitation')) {
series.push({
name: 'Precipitation',
type: 'bar',
color: 'rgba(30, 136, 229, 0.8)',
data: hourly.precipitation,
format: (v) => `${v.toFixed(1)} ${precipUnit}`
},
{
});
}
if (on('precipitation_probability')) {
series.push({
name: 'Precip. Probability',
type: 'line',
color: '#5c6bc0',
@@ -148,20 +164,28 @@
dashed: true,
axis: 'right',
format: (v) => `${v.toFixed(0)}%`
}
]
};
});
}
defs.push({
title: [
on('precipitation') && 'Precipitation',
on('precipitation_probability') && 'Probability'
]
.filter(Boolean)
.join(' & '),
unit: precipUnit,
unitRight: on('precipitation_probability') ? '%' : undefined,
yMin: 0,
yMinRight: 0,
yMaxRight: 100,
series
});
}
const windChart: ChartDef = {
title: 'Wind Speed & Humidity',
unit: windUnit,
unitRight: '%',
yMin: 0,
yMinRight: 0,
yMaxRight: 100,
showCredit: true,
series: [
{
if (on('wind') || on('humidity')) {
const series: ChartSeries[] = [];
if (on('wind')) {
series.push({
name: 'Wind Speed',
type: 'line',
color: '#26a69a',
@@ -174,8 +198,10 @@
const dirLabel = dir != null && !isNaN(dir) ? ` (${getWindDirectionLabel(dir)})` : '';
return `${v.toFixed(0)} ${windUnit}${dirLabel}`;
}
},
{
});
}
if (on('humidity')) {
series.push({
name: 'Humidity',
type: 'line',
color: '#8d6e63',
@@ -184,11 +210,24 @@
dashed: true,
axis: 'right',
format: (v) => `${v.toFixed(0)}%`
}
]
};
});
}
defs.push({
title: [on('wind') && 'Wind Speed', on('humidity') && 'Humidity']
.filter(Boolean)
.join(' & '),
unit: windUnit,
unitRight: on('humidity') ? '%' : undefined,
yMin: 0,
yMinRight: 0,
yMaxRight: 100,
series
});
}
return [tempChart, precipChart, windChart];
if (defs.length > 0) defs[defs.length - 1].showCredit = true;
return defs;
});
</script>
@@ -230,30 +269,39 @@
</div>
</div>
<ChartContainer {loading} chartCount={3} chartHeight={300}>
{#each chartDefs as def, i (def.title)}
<CanvasChart
bind:this={chartComponents[i]}
timestamps={timestampsSec}
timezone={data.timezone}
series={def.series}
bands={data.daylightBands}
unit={def.unit}
unitRight={def.unitRight}
yMin={def.yMin}
yMinRight={def.yMinRight}
yMaxRight={def.yMaxRight}
invertRight={def.invertRight}
title={def.title}
showCredit={def.showCredit}
showLegend
height={300}
group={CHART_GROUP}
/>
{/each}
</ChartContainer>
{#if chartDefs.length > 0}
<ChartContainer {loading} chartCount={chartDefs.length} chartHeight={300}>
{#each chartDefs as def, i (def.title)}
<CanvasChart
bind:this={chartComponents[i]}
timestamps={timestampsSec}
timezone={data.timezone}
series={def.series}
bands={data.daylightBands}
highlight={selectedDayHighlight}
unit={def.unit}
unitRight={def.unitRight}
yMin={def.yMin}
yMinRight={def.yMinRight}
yMaxRight={def.yMaxRight}
invertRight={def.invertRight}
title={def.title}
showCredit={def.showCredit}
showLegend
height={300}
group={CHART_GROUP}
/>
{/each}
</ChartContainer>
<div class="mt-6 md:mt-10">
<ChartToolbar charts={liveCharts} fileName="week-forecast" />
</div>
<div class="mt-6 md:mt-10">
<ChartToolbar charts={liveCharts} fileName="week-forecast" />
</div>
{:else}
<div
class="rounded-xl border border-dashed border-border px-4 py-10 text-center text-sm text-muted-foreground"
>
All chart variables are hidden — enable some under “Variables”.
</div>
{/if}
</section>