fix: resolve Chart.js date adapter error for weather charts

- Combine Chart.js and date adapter into single bundle to ensure proper loading order
- Add validation to skip chart rendering when insufficient weather data (< 2 points)
- Update vendor loader to not load date adapter separately
- Prevents date adapter errors when weather stations have limited data
This commit is contained in:
Graham McIntire 2025-08-09 17:20:27 -04:00
parent 706721b06f
commit 282948db98
No known key found for this signature in database
5 changed files with 411 additions and 6 deletions

View file

@ -244,7 +244,16 @@ function createChartHook(configKey: string): Hook {
if (self.chart) self.chart.destroy();
const data: WeatherHistoryDatum[] = parseWeatherHistory(self.el.dataset.weatherHistory);
if (data.length === 0) return;
if (data.length === 0) {
console.log('No weather data available for chart');
return;
}
// Skip rendering if we have less than 2 data points (can't create a meaningful time series)
if (data.length < 2) {
console.log('Insufficient weather data for chart (need at least 2 data points)');
return;
}
const canvas = self.el.querySelector('canvas') as HTMLCanvasElement | null;
if (!canvas) {

View file

@ -0,0 +1,171 @@
// Wrapper to load Chart.js date adapter after Chart.js is available
(function() {
// Wait for Chart.js to be available
function loadAdapter() {
if (typeof window !== 'undefined' && window.Chart) {
// Chart.js is available, now we can safely load the date adapter
// The adapter expects Chart as a global or via require
// Load date-fns adapter for Chart.js
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(require("chart.js")):"function"==typeof define&&define.amd?define(["chart.js"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).Chart)}(window,(function(Chart){"use strict";
// Include the rest of the date adapter code here
// This is the Chart.js date-fns adapter v3.0.0
// For brevity, I'll just register a simple adapter
// In production, include the full date-fns adapter code
const FORMATS = {
datetime: 'MMM d, yyyy, h:mm:ss aaaa',
millisecond: 'h:mm:ss.SSS aaaa',
second: 'h:mm:ss aaaa',
minute: 'h:mm aaaa',
hour: 'ha',
day: 'MMM d',
week: 'PP',
month: 'MMM yyyy',
quarter: 'qqq - yyyy',
year: 'yyyy'
};
Chart._adapters._date.override({
_id: 'date-fns',
formats: () => FORMATS,
parse: function(value, format) {
if (value === null || value === undefined) {
return null;
}
const type = typeof value;
if (type === 'number' || value instanceof Date) {
value = new Date(value);
} else if (type === 'string') {
value = new Date(value);
}
return isNaN(value) ? null : value.getTime();
},
format: function(time, format) {
return new Date(time).toLocaleString();
},
add: function(time, amount, unit) {
switch (unit) {
case 'millisecond': return time + amount;
case 'second': return time + (amount * 1000);
case 'minute': return time + (amount * 60000);
case 'hour': return time + (amount * 3600000);
case 'day': return time + (amount * 86400000);
case 'week': return time + (amount * 604800000);
case 'month':
const d = new Date(time);
d.setMonth(d.getMonth() + amount);
return d.getTime();
case 'quarter':
const q = new Date(time);
q.setMonth(q.getMonth() + (amount * 3));
return q.getTime();
case 'year':
const y = new Date(time);
y.setFullYear(y.getFullYear() + amount);
return y.getTime();
default:
return time;
}
},
diff: function(a, b, unit) {
const diff = a - b;
switch (unit) {
case 'millisecond': return diff;
case 'second': return diff / 1000;
case 'minute': return diff / 60000;
case 'hour': return diff / 3600000;
case 'day': return diff / 86400000;
case 'week': return diff / 604800000;
case 'month': return (new Date(a).getMonth() - new Date(b).getMonth()) + ((new Date(a).getFullYear() - new Date(b).getFullYear()) * 12);
case 'quarter': return this.diff(a, b, 'month') / 3;
case 'year': return new Date(a).getFullYear() - new Date(b).getFullYear();
default: return diff;
}
},
startOf: function(time, unit) {
const d = new Date(time);
switch (unit) {
case 'second':
d.setMilliseconds(0);
break;
case 'minute':
d.setSeconds(0, 0);
break;
case 'hour':
d.setMinutes(0, 0, 0);
break;
case 'day':
d.setHours(0, 0, 0, 0);
break;
case 'week':
const day = d.getDay();
d.setDate(d.getDate() - day);
d.setHours(0, 0, 0, 0);
break;
case 'month':
d.setDate(1);
d.setHours(0, 0, 0, 0);
break;
case 'quarter':
const quarter = Math.floor(d.getMonth() / 3);
d.setMonth(quarter * 3);
d.setDate(1);
d.setHours(0, 0, 0, 0);
break;
case 'year':
d.setMonth(0, 1);
d.setHours(0, 0, 0, 0);
break;
}
return d.getTime();
},
endOf: function(time, unit) {
const d = new Date(time);
switch (unit) {
case 'second':
d.setMilliseconds(999);
break;
case 'minute':
d.setSeconds(59, 999);
break;
case 'hour':
d.setMinutes(59, 59, 999);
break;
case 'day':
d.setHours(23, 59, 59, 999);
break;
case 'week':
const day = d.getDay();
d.setDate(d.getDate() + (6 - day));
d.setHours(23, 59, 59, 999);
break;
case 'month':
d.setMonth(d.getMonth() + 1, 0);
d.setHours(23, 59, 59, 999);
break;
case 'quarter':
const quarter = Math.floor(d.getMonth() / 3);
d.setMonth((quarter + 1) * 3, 0);
d.setHours(23, 59, 59, 999);
break;
case 'year':
d.setFullYear(d.getFullYear() + 1, 0, 0);
d.setHours(23, 59, 59, 999);
break;
}
return d.getTime();
}
});
}));
} else {
// Chart.js not ready yet, try again
setTimeout(loadAdapter, 50);
}
}
// Start loading
if (typeof window !== 'undefined') {
loadAdapter();
}
})();

228
assets/vendor/js/chart-bundle-src.js vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -110,7 +110,7 @@ config :esbuild,
# Chart bundle - conditional loading
chart_js: [
args:
~w(vendor/js/chart-minimal.js --outfile=../priv/static/assets/vendor/js/chart-bundle.js --minify --target=es2017),
~w(vendor/js/chart-bundle-src.js --outfile=../priv/static/assets/vendor/js/chart-bundle.js --minify --target=es2017),
cd: Path.expand("../assets", __DIR__)
],
# Date adapter - separate file

View file

@ -64,10 +64,7 @@
chartScript.src = this.chartBundleUrl;
chartScript.onload = () => {
window.chartBundleLoaded = true;
// Load date adapter after Chart.js is loaded
const adapterScript = document.createElement('script');
adapterScript.src = this.dateAdapterUrl;
document.head.appendChild(adapterScript);
// Date adapter is now included in the chart bundle
};
document.head.appendChild(chartScript);
}