review fixes
This commit is contained in:
@@ -1265,21 +1265,6 @@
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Line series: draw fill and stroke per contiguous non-null run
|
|
||||||
// (points outside the view are handled by the clip rect). Each point
|
|
||||||
// is [x, y, yBand, sourceIndex] — yBand only used when s.bandTo is set.
|
|
||||||
const runs: Array<Array<[number, number, number, number]>> = [];
|
|
||||||
let run: Array<[number, number, number, number]> = [];
|
|
||||||
for (let i = 0; i < timestamps.length; i++) {
|
|
||||||
const v = s.data[i];
|
|
||||||
const b = s.bandTo?.[i];
|
|
||||||
const bandInvalid = s.bandTo != null && (b === null || b === undefined || !isFinite(b));
|
|
||||||
if (v === null || v === undefined || !isFinite(v) || bandInvalid) {
|
|
||||||
if (run.length > 0) runs.push(run);
|
|
||||||
run = [];
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (s.type === 'point') {
|
if (s.type === 'point') {
|
||||||
const radius = s.pointRadius ?? 3;
|
const radius = s.pointRadius ?? 3;
|
||||||
ctx.fillStyle = s.color;
|
ctx.fillStyle = s.color;
|
||||||
@@ -1297,6 +1282,21 @@
|
|||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Line series: draw fill and stroke per contiguous non-null run
|
||||||
|
// (points outside the view are handled by the clip rect). Each point
|
||||||
|
// is [x, y, yBand, sourceIndex] — yBand only used when s.bandTo is set.
|
||||||
|
const runs: Array<Array<[number, number, number, number]>> = [];
|
||||||
|
let run: Array<[number, number, number, number]> = [];
|
||||||
|
for (let i = 0; i < timestamps.length; i++) {
|
||||||
|
const v = s.data[i];
|
||||||
|
const b = s.bandTo?.[i];
|
||||||
|
const bandInvalid = s.bandTo != null && (b === null || b === undefined || !isFinite(b));
|
||||||
|
if (v === null || v === undefined || !isFinite(v) || bandInvalid) {
|
||||||
|
if (run.length > 0) runs.push(run);
|
||||||
|
run = [];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
run.push([xPix(timestamps[i]), yPix(v, axis), s.bandTo ? yPix(b as number, axis) : 0, i]);
|
run.push([xPix(timestamps[i]), yPix(v, axis), s.bandTo ? yPix(b as number, axis) : 0, i]);
|
||||||
}
|
}
|
||||||
if (run.length > 0) runs.push(run);
|
if (run.length > 0) runs.push(run);
|
||||||
|
|||||||
@@ -563,6 +563,11 @@ export async function fetchModelComparison(
|
|||||||
{ signal: options.signal }
|
{ signal: options.signal }
|
||||||
);
|
);
|
||||||
if (responses.length === 0) throw new Error('The weather service returned no model data.');
|
if (responses.length === 0) throw new Error('The weather service returned no model data.');
|
||||||
|
if (responses.length !== params.models.length) {
|
||||||
|
throw new Error(
|
||||||
|
`The weather service returned ${responses.length} model responses for ${params.models.length} requested models.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// With multiple models, we get one response per model
|
// With multiple models, we get one response per model
|
||||||
const firstResponse = responses[0];
|
const firstResponse = responses[0];
|
||||||
@@ -604,9 +609,11 @@ export async function fetchModelComparison(
|
|||||||
const modelHourly = response.hourly();
|
const modelHourly = response.hourly();
|
||||||
if (!modelHourly) continue;
|
if (!modelHourly) continue;
|
||||||
|
|
||||||
// Keep the requested id as the stable UI key. The concrete id reported by
|
// The multi-model endpoint preserves request order and returns one response
|
||||||
// Open-Meteo is metadata only: seamless and best-match requests may resolve
|
// per requested model, including unavailable regional models. Keep that
|
||||||
// to a different underlying domain.
|
// requested id as the stable UI key. The concrete id reported by Open-Meteo
|
||||||
|
// is metadata only: seamless and best-match requests may resolve to a
|
||||||
|
// different underlying domain.
|
||||||
const modelEnum = response.model();
|
const modelEnum = response.model();
|
||||||
const resolvedModelId = Model[modelEnum] ?? `model_${modelEnum}`;
|
const resolvedModelId = Model[modelEnum] ?? `model_${modelEnum}`;
|
||||||
const modelId = params.models[responseIndex] ?? resolvedModelId;
|
const modelId = params.models[responseIndex] ?? resolvedModelId;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onDestroy, onMount } from 'svelte';
|
import { onDestroy, onMount } from 'svelte';
|
||||||
import { SvelteSet } from 'svelte/reactivity';
|
|
||||||
import { get } from 'svelte/store';
|
import { get } from 'svelte/store';
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
|
|
||||||
@@ -93,7 +92,6 @@
|
|||||||
let chartComponents: CanvasChart[] = $state([]);
|
let chartComponents: CanvasChart[] = $state([]);
|
||||||
let pictogramExporter: ExportableChart | null = $state(null);
|
let pictogramExporter: ExportableChart | null = $state(null);
|
||||||
let showLegend = $state(true);
|
let showLegend = $state(true);
|
||||||
const hiddenModels = new SvelteSet<string>();
|
|
||||||
|
|
||||||
let isMobile = $state(false);
|
let isMobile = $state(false);
|
||||||
let comparisonPlotInsetLeft = $derived(isMobile ? 92 : 116);
|
let comparisonPlotInsetLeft = $derived(isMobile ? 92 : 116);
|
||||||
@@ -150,12 +148,14 @@
|
|||||||
|
|
||||||
let variablesDirty = $derived(!hasSameVariables(params.hourly, appliedHourly));
|
let variablesDirty = $derived(!hasSameVariables(params.hourly, appliedHourly));
|
||||||
let modelsDirty = $derived(!hasSameOrder(params.models, appliedModels));
|
let modelsDirty = $derived(!hasSameOrder(params.models, appliedModels));
|
||||||
let displayedVariablesStale = $derived(
|
let displayedVariablesStale = $derived.by(() => {
|
||||||
fetchedData !== null && !hasSameVariables(fetchedData.selection.hourly, appliedHourly)
|
const current: FetchedData | null = fetchedData;
|
||||||
);
|
return current !== null && !hasSameVariables(current.selection.hourly, appliedHourly);
|
||||||
let displayedModelsStale = $derived(
|
});
|
||||||
fetchedData !== null && !hasSameOrder(fetchedData.selection.models, appliedModels)
|
let displayedModelsStale = $derived.by(() => {
|
||||||
);
|
const current: FetchedData | null = fetchedData;
|
||||||
|
return current !== null && !hasSameOrder(current.selection.models, appliedModels);
|
||||||
|
});
|
||||||
let comparisonSelectionDirty = $derived(variablesDirty || modelsDirty);
|
let comparisonSelectionDirty = $derived(variablesDirty || modelsDirty);
|
||||||
let comparisonMuted = $derived(
|
let comparisonMuted = $derived(
|
||||||
comparisonSelectionDirty || displayedVariablesStale || displayedModelsStale
|
comparisonSelectionDirty || displayedVariablesStale || displayedModelsStale
|
||||||
@@ -384,7 +384,6 @@
|
|||||||
: (model.variables[variable] ?? []),
|
: (model.variables[variable] ?? []),
|
||||||
width: 2,
|
width: 2,
|
||||||
pointRadius: 3.25,
|
pointRadius: 3.25,
|
||||||
hidden: hiddenModels.has(model.modelId),
|
|
||||||
format: direction
|
format: direction
|
||||||
? (value) => `${value.toFixed(0)}° ${cardinalDirection(value)}`
|
? (value) => `${value.toFixed(0)}° ${cardinalDirection(value)}`
|
||||||
: undefined
|
: undefined
|
||||||
@@ -422,7 +421,7 @@
|
|||||||
variable === 'precipitation'
|
variable === 'precipitation'
|
||||||
? {
|
? {
|
||||||
points: precipitationAgreement(
|
points: precipitationAgreement(
|
||||||
result.models.filter((model) => !hiddenModels.has(model.modelId)),
|
result.models,
|
||||||
variable,
|
variable,
|
||||||
result.timestamps.length,
|
result.timestamps.length,
|
||||||
wetThreshold
|
wetThreshold
|
||||||
@@ -488,7 +487,6 @@
|
|||||||
|
|
||||||
let exportLegend = $derived.by((): ExportLegendItem[] => [
|
let exportLegend = $derived.by((): ExportLegendItem[] => [
|
||||||
...displayedModels
|
...displayedModels
|
||||||
.filter((model) => !hiddenModels.has(model.modelId))
|
|
||||||
.map((model) => ({
|
.map((model) => ({
|
||||||
name: modelLabel(model.modelId),
|
name: modelLabel(model.modelId),
|
||||||
color: modelColor(model.modelId, fetchedData?.selection.models),
|
color: modelColor(model.modelId, fetchedData?.selection.models),
|
||||||
@@ -501,11 +499,6 @@
|
|||||||
: [])
|
: [])
|
||||||
]);
|
]);
|
||||||
|
|
||||||
function toggleModel(modelId: string): void {
|
|
||||||
if (hiddenModels.has(modelId)) hiddenModels.delete(modelId);
|
|
||||||
else hiddenModels.add(modelId);
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleSelection(list: 'models' | 'hourly', value: string): void {
|
function toggleSelection(list: 'models' | 'hourly', value: string): void {
|
||||||
const values = params[list];
|
const values = params[list];
|
||||||
const nextValues = values.includes(value)
|
const nextValues = values.includes(value)
|
||||||
@@ -534,9 +527,6 @@
|
|||||||
const variablesChanged = !hasSameVariables(params.hourly, appliedHourly);
|
const variablesChanged = !hasSameVariables(params.hourly, appliedHourly);
|
||||||
const modelsChanged = !hasSameOrder(params.models, appliedModels);
|
const modelsChanged = !hasSameOrder(params.models, appliedModels);
|
||||||
if (modelsChanged) {
|
if (modelsChanged) {
|
||||||
for (const modelId of params.models) {
|
|
||||||
if (!appliedModels.includes(modelId)) hiddenModels.delete(modelId);
|
|
||||||
}
|
|
||||||
appliedModels = [...params.models];
|
appliedModels = [...params.models];
|
||||||
}
|
}
|
||||||
if (variablesChanged) appliedHourly = [...params.hourly];
|
if (variablesChanged) appliedHourly = [...params.hourly];
|
||||||
@@ -795,22 +785,13 @@
|
|||||||
transition:fade={{ duration: 150 }}
|
transition:fade={{ duration: 150 }}
|
||||||
>
|
>
|
||||||
{#each displayedModels as model (model.modelId)}
|
{#each displayedModels as model (model.modelId)}
|
||||||
<button
|
<span class="flex min-h-8 shrink-0 items-center gap-1.5 text-xs">
|
||||||
type="button"
|
|
||||||
class="flex min-h-8 shrink-0 items-center gap-1.5 text-xs {hiddenModels.has(
|
|
||||||
model.modelId
|
|
||||||
)
|
|
||||||
? 'opacity-40'
|
|
||||||
: ''}"
|
|
||||||
onclick={() => toggleModel(model.modelId)}
|
|
||||||
aria-pressed={!hiddenModels.has(model.modelId)}
|
|
||||||
>
|
|
||||||
<span
|
<span
|
||||||
class="size-2.5 rounded-full"
|
class="size-2.5 rounded-full"
|
||||||
style:background-color={modelColor(model.modelId, fetchedData.selection.models)}
|
style:background-color={modelColor(model.modelId, fetchedData.selection.models)}
|
||||||
></span>
|
></span>
|
||||||
{modelLabel(model.modelId)}
|
{modelLabel(model.modelId)}
|
||||||
</button>
|
</span>
|
||||||
{/each}
|
{/each}
|
||||||
{#if chartDefs.some( (def) => def.series.some((series) => series.name === m.compare_model_mean()) )}
|
{#if chartDefs.some( (def) => def.series.some((series) => series.name === m.compare_model_mean()) )}
|
||||||
<span class="flex shrink-0 items-center gap-1.5 text-xs">
|
<span class="flex shrink-0 items-center gap-1.5 text-xs">
|
||||||
|
|||||||
Reference in New Issue
Block a user