feat: canvas to echarts (#5)

Co-authored-by: terraputix <terraputix@mailbox.org>
Reviewed-on: useweb/open-meteo-blue#5
This commit was merged in pull request #5.
This commit is contained in:
2026-02-16 00:33:58 +01:00
co-authored by terraputix
parent 2fe8cf92e1
commit 7a6cca5dad
19 changed files with 2449 additions and 1149 deletions
+30 -49
View File
@@ -7,14 +7,9 @@
import {
buildAverageSeries,
buildCurrentTimeSeries,
buildDaylightMarkAreas,
buildDaylightSeries,
buildSpreadSeries,
calculateAverage,
calculateSpread,
composeChartOption,
convertTimestamps,
findUnit,
getThemeColors
} from '$lib/utils/echarts';
@@ -23,6 +18,12 @@
import { Label } from '$lib/components/ui/label';
import { Switch } from '$lib/components/ui/switch';
import {
type EnsembleForecastResult,
type MarkArea,
fetchEnsembleForecast
} from '$lib/services/weather';
import { defaultParameters } from '../options';
import type * as echarts from 'echarts';
@@ -52,11 +53,10 @@
// ─── Cached API Response ────────────────────────────────────────────────────
interface FetchedData {
hourly: Record<string, unknown>;
hourly_units: Record<string, string>;
utc_offset_seconds: number;
markAreas: Array<[{ xAxis: number; itemStyle: { color: string } }, { xAxis: number }]>;
ensembleResult: EnsembleForecastResult;
timestamps: number[];
utc_offset_seconds: number;
markAreas: MarkArea[];
}
let fetchedData: FetchedData | null = $state(null);
@@ -92,35 +92,22 @@
chartInstances = [];
chartComponents = [];
const [dataDaily, dataReq] = await Promise.all([
fetch(
`https://api.open-meteo.com/v1/forecast?latitude=${location.latitude}&longitude=${location.longitude}&timeformat=unixtime&daily=sunset,sunrise&forecast_days=14`
),
fetch(
`https://ensemble-api.open-meteo.com/v1/ensemble?latitude=${location.latitude}&longitude=${location.longitude}&hourly=${hourlyVars.join(',')}&models=${modelList.join(',')}&timeformat=unixtime&forecast_days=14`
)
]);
const [wd, data] = await Promise.all([dataDaily.json(), dataReq.json()]);
let markAreas: FetchedData['markAreas'] = [];
if ('daily' in wd && 'sunrise' in wd.daily && 'sunset' in wd.daily) {
markAreas = buildDaylightMarkAreas(
wd.daily.sunrise,
wd.daily.sunset,
data.utc_offset_seconds
);
}
const timestamps = convertTimestamps(data.hourly.time, data.utc_offset_seconds);
const result: EnsembleForecastResult = await fetchEnsembleForecast({
latitude: location.latitude!,
longitude: location.longitude!,
hourlyVariables: hourlyVars,
models: modelList,
forecast_days: 14,
temperature_unit: params.temperature_unit as 'celsius' | 'fahrenheit',
wind_speed_unit: params.wind_speed_unit as 'kmh' | 'ms' | 'mph' | 'kn',
precipitation_unit: params.precipitation_unit as 'mm' | 'inch'
});
fetchedData = {
hourly: data.hourly,
hourly_units: data.hourly_units,
utc_offset_seconds: data.utc_offset_seconds,
markAreas,
timestamps
ensembleResult: result,
timestamps: result.timestamps,
utc_offset_seconds: result.utcOffsetSeconds,
markAreas: result.markAreas
};
loading = false;
@@ -134,32 +121,26 @@
$effect(() => {
if (!fetchedData) return;
const {
hourly: hourlyData,
hourly_units,
utc_offset_seconds,
markAreas,
timestamps
} = fetchedData;
const { ensembleResult, timestamps, utc_offset_seconds, markAreas } = fetchedData;
const _showLegend = showLegend;
const colors = getThemeColors();
const variableCount = params.hourly?.length || 0;
const timeLength = (hourlyData.time as number[]).length;
const newOptions: Array<Record<string, unknown>> = [];
for (let vi = 0; vi < variableCount; vi++) {
const variable = params.hourly![vi];
const unit = findUnit(hourly_units, hourlyData, variable);
const varData = ensembleResult.variables[variable];
if (!varData) continue;
const { average } = calculateAverage(hourlyData, variable, timeLength);
const { minValues, maxValues } = calculateSpread(hourlyData, variable, timeLength);
const unit = varData.unit;
const { average, min: minValues, max: maxValues } = varData;
const series: Array<Record<string, unknown>> = [];
const spreadData: Array<[number, number, number]> = minValues.map(
(min, index) =>
[timestamps[index], min ?? 0, maxValues[index] ?? 0] as [number, number, number]
(minVal, index) =>
[timestamps[index], minVal ?? 0, maxValues[index] ?? 0] as [number, number, number]
);
series.push(...buildSpreadSeries({ variable, spreadData }));