From 506831cf889b5bc8f3bf45b8c903b747f0501ba2 Mon Sep 17 00:00:00 2001 From: terraputix Date: Mon, 3 Aug 2026 23:14:23 +0200 Subject: [PATCH] mobile improvements mostly --- messages/de.json | 3 + messages/en.json | 3 + messages/es.json | 3 + messages/fr.json | 3 + messages/it.json | 3 + src/lib/charts/CanvasChart.svelte | 35 ++-- .../components/charts/downloadChartsPng.ts | 17 +- .../weather/compare/[location]/+page.svelte | 150 +++++++++++------- .../ComparisonSelectionPanel.svelte | 56 ++----- .../[location]/ModelPictogramTimeline.svelte | 38 ++++- 10 files changed, 183 insertions(+), 128 deletions(-) diff --git a/messages/de.json b/messages/de.json index 0103ab7..56e29ee 100644 --- a/messages/de.json +++ b/messages/de.json @@ -293,6 +293,7 @@ "compare_selected_models": "Ausgewählte Modelle", "compare_selection_pending": "Auswahl geändert", "compare_apply_selection": "Anwenden & Diagramme neu laden", + "compare_discard_changes": "Änderungen verwerfen", "compare_customize_models": "Modelle anpassen", "compare_edit_models": "Modelle bearbeiten", "compare_edit_variables": "Variablen bearbeiten", @@ -300,6 +301,8 @@ "compare_edit_variables_description": "Variablen suchen oder ganze Themengruppen auswählen.", "compare_search_models": "Modelle suchen", "compare_search_variables": "Variablen suchen", + "compare_show_model_names": "Modellnamen anzeigen", + "compare_hide_model_names": "Modellnamen ausblenden", "compare_only_selected": "Nur ausgewählte", "compare_select_group": "Alle auswählen", "compare_clear_group": "Alle abwählen", diff --git a/messages/en.json b/messages/en.json index 7c35009..162b91a 100644 --- a/messages/en.json +++ b/messages/en.json @@ -293,6 +293,7 @@ "compare_selected_models": "Selected models", "compare_selection_pending": "Selection changed", "compare_apply_selection": "Apply & reload charts", + "compare_discard_changes": "Discard changes", "compare_customize_models": "Customize models", "compare_edit_models": "Edit models", "compare_edit_variables": "Edit variables", @@ -300,6 +301,8 @@ "compare_edit_variables_description": "Search variables or select complete thematic groups.", "compare_search_models": "Search models", "compare_search_variables": "Search variables", + "compare_show_model_names": "Show model names", + "compare_hide_model_names": "Hide model names", "compare_only_selected": "Only selected", "compare_select_group": "Select all", "compare_clear_group": "Clear all", diff --git a/messages/es.json b/messages/es.json index 4388189..52c1847 100644 --- a/messages/es.json +++ b/messages/es.json @@ -293,6 +293,7 @@ "compare_selected_models": "Modelos seleccionados", "compare_selection_pending": "Selección modificada", "compare_apply_selection": "Aplicar y recargar gráficos", + "compare_discard_changes": "Descartar cambios", "compare_customize_models": "Personalizar modelos", "compare_edit_models": "Editar modelos", "compare_edit_variables": "Editar variables", @@ -300,6 +301,8 @@ "compare_edit_variables_description": "Busca variables o selecciona grupos temáticos completos.", "compare_search_models": "Buscar modelos", "compare_search_variables": "Buscar variables", + "compare_show_model_names": "Mostrar nombres de modelos", + "compare_hide_model_names": "Ocultar nombres de modelos", "compare_only_selected": "Solo seleccionados", "compare_select_group": "Seleccionar todo", "compare_clear_group": "Deseleccionar todo", diff --git a/messages/fr.json b/messages/fr.json index 0adf192..2e04997 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -293,6 +293,7 @@ "compare_selected_models": "Modèles sélectionnés", "compare_selection_pending": "Sélection modifiée", "compare_apply_selection": "Appliquer et recharger les graphiques", + "compare_discard_changes": "Annuler les modifications", "compare_customize_models": "Personnaliser les modèles", "compare_edit_models": "Modifier les modèles", "compare_edit_variables": "Modifier les variables", @@ -300,6 +301,8 @@ "compare_edit_variables_description": "Recherchez des variables ou sélectionnez des groupes thématiques entiers.", "compare_search_models": "Rechercher des modèles", "compare_search_variables": "Rechercher des variables", + "compare_show_model_names": "Afficher les noms des modèles", + "compare_hide_model_names": "Masquer les noms des modèles", "compare_only_selected": "Sélection uniquement", "compare_select_group": "Tout sélectionner", "compare_clear_group": "Tout désélectionner", diff --git a/messages/it.json b/messages/it.json index c65a859..3d16c06 100644 --- a/messages/it.json +++ b/messages/it.json @@ -293,6 +293,7 @@ "compare_selected_models": "Modelli selezionati", "compare_selection_pending": "Selezione modificata", "compare_apply_selection": "Applica e ricarica i grafici", + "compare_discard_changes": "Annulla modifiche", "compare_customize_models": "Personalizza modelli", "compare_edit_models": "Modifica modelli", "compare_edit_variables": "Modifica variabili", @@ -300,6 +301,8 @@ "compare_edit_variables_description": "Cerca variabili o seleziona interi gruppi tematici.", "compare_search_models": "Cerca modelli", "compare_search_variables": "Cerca variabili", + "compare_show_model_names": "Mostra nomi dei modelli", + "compare_hide_model_names": "Nascondi nomi dei modelli", "compare_only_selected": "Solo selezionati", "compare_select_group": "Seleziona tutto", "compare_clear_group": "Deseleziona tutto", diff --git a/src/lib/charts/CanvasChart.svelte b/src/lib/charts/CanvasChart.svelte index 327b4cf..a82d53e 100644 --- a/src/lib/charts/CanvasChart.svelte +++ b/src/lib/charts/CanvasChart.svelte @@ -209,6 +209,8 @@ yTicks?: number[]; /** Custom left-axis tick formatter. */ yTickFormat?: (value: number) => string; + /** Use abbreviated y-axis labels and omit verbose gutter labels. */ + compactYAxis?: boolean; /** Force the derived left axis to include zero (default true) */ zeroBaseLeft?: boolean; /** Fixed right-axis minimum (default 0) */ @@ -260,6 +262,7 @@ yMax, yTicks, yTickFormat, + compactYAxis = false, yPadTop, yPadBottom, zeroBaseLeft = true, @@ -497,7 +500,7 @@ let iconRows = $derived(isNarrow ? ownIconRows : Math.max(ownIconRows, reserveTopRows)); // tighter top/bottom gutters on mobile so charts don't waste vertical space const iconRowH = $derived(isNarrow ? 30 : ICON_ROW_H); - let padTop = $derived((title ? (subtitle ? 66 : 46) : isNarrow ? 14 : 28) + iconRows * iconRowH); + let padTop = $derived((title ? (subtitle ? 66 : 46) : isNarrow ? 22 : 28) + iconRows * iconRowH); let agreementStripOffset = $derived( agreementStrip ? AGREEMENT_STRIP_HEIGHT + AGREEMENT_STRIP_GAP + 2 : 0 ); @@ -1087,7 +1090,13 @@ ctx.lineTo(plotRight, y); ctx.stroke(); ctx.fillStyle = textColor; - ctx.fillText(yTickFormat?.(v) ?? v.toFixed(tickDecimals(leftScale.step)), padLeft - 8, y); + ctx.fillText( + compactYAxis + ? v.toFixed(tickDecimals(leftScale.step)) + : (yTickFormat?.(v) ?? v.toFixed(tickDecimals(leftScale.step))), + padLeft - 8, + y + ); } // Right axis labels in the gutter (desktop only). On mobile there is no @@ -1543,16 +1552,18 @@ ctx.strokeStyle = withAlpha(gridColor, 0.8); ctx.lineWidth = 1; ctx.strokeRect(padLeft, stripTop + 0.5, plotW, AGREEMENT_STRIP_HEIGHT - 1); - ctx.fillStyle = textColor; - ctx.font = '600 9px system-ui, sans-serif'; - ctx.textAlign = 'right'; - ctx.textBaseline = 'middle'; - ctx.fillText( - agreementStrip.label, - padLeft - 6, - stripTop + AGREEMENT_STRIP_HEIGHT / 2, - Math.max(24, padLeft - 10) - ); + if (!compactYAxis) { + ctx.fillStyle = textColor; + ctx.font = '600 9px system-ui, sans-serif'; + ctx.textAlign = 'right'; + ctx.textBaseline = 'middle'; + ctx.fillText( + agreementStrip.label, + padLeft - 6, + stripTop + AGREEMENT_STRIP_HEIGHT / 2, + Math.max(24, padLeft - 10) + ); + } } // Axis unit labels diff --git a/src/lib/components/charts/downloadChartsPng.ts b/src/lib/components/charts/downloadChartsPng.ts index 7459df5..d12fc90 100644 --- a/src/lib/components/charts/downloadChartsPng.ts +++ b/src/lib/components/charts/downloadChartsPng.ts @@ -108,10 +108,9 @@ export async function downloadChartsPng( } if (row.length > 0) legendRows.push(row); } - const headerHeight = - (options.title ? 34 * dpr : 0) + - (legendRows.length > 0 ? legendRows.length * rowHeight + 8 * dpr : 0); - const totalHeight = headerHeight + canvases.reduce((sum, c) => sum + c.height, 0); + const titleHeight = options.title ? 34 * dpr : 0; + const legendHeight = legendRows.length > 0 ? legendRows.length * rowHeight + 8 * dpr : 0; + const totalHeight = titleHeight + canvases.reduce((sum, c) => sum + c.height, 0) + legendHeight; const canvas = document.createElement('canvas'); canvas.width = maxWidth; @@ -134,7 +133,12 @@ export async function downloadChartsPng( ctx.fillText(options.title, padding, 17 * dpr); y += 34 * dpr; } + for (const c of canvases) { + ctx.drawImage(c, 0, y); + y += c.height; + } if (legendRows.length > 0) { + y += 8 * dpr; ctx.font = `${12 * dpr}px system-ui, -apple-system, sans-serif`; ctx.textBaseline = 'middle'; for (const row of legendRows) { @@ -165,11 +169,6 @@ export async function downloadChartsPng( } y += rowHeight; } - y += 8 * dpr; - } - for (const c of canvases) { - ctx.drawImage(c, 0, y); - y += c.height; } triggerDownload(canvas.toDataURL('image/png'), `${fileName}.png`); diff --git a/src/routes/weather/compare/[location]/+page.svelte b/src/routes/weather/compare/[location]/+page.svelte index e86fe13..1080eb1 100644 --- a/src/routes/weather/compare/[location]/+page.svelte +++ b/src/routes/weather/compare/[location]/+page.svelte @@ -1,7 +1,6 @@ - {m.page_compare_title()} | Drizz.li + Drizz.li | {m.page_compare_title()} @@ -689,6 +712,7 @@ yMax={def.yMax} yTicks={def.yTicks} yTickFormat={def.yTickFormat} + {compactYAxis} plotInsetLeft={comparisonPlotInsetLeft} plotInsetRight={comparisonPlotInsetRight} showCredit={i === chartDefs.length - 1} @@ -711,14 +735,6 @@ {m.compare_selection_pending()} - {/if} @@ -748,6 +764,8 @@ group={CHART_GROUP} plotInsetLeft={comparisonPlotInsetLeft} plotInsetRight={comparisonPlotInsetRight} + {showModelNames} + onToggleModelNames={() => (showModelNames = !showModelNames)} registerExporter={(exporter) => (pictogramExporter = exporter)} /> @@ -759,36 +777,25 @@ ? 'pointer-events-none opacity-40' : ''}" > -
-

{m.compare_model_colors()}

-
- - -
+

{m.compare_model_colors()}

+
+ {#each displayedModels as model (model.modelId)} + + + {modelLabel(model.modelId)} + + {/each} + {#if chartDefs.some( (def) => def.series.some((series) => series.name === m.compare_model_mean()) )} + + + {m.compare_model_mean()} + + {/if}
- {#if showLegend} -
- {#each displayedModels as model (model.modelId)} - - - {modelLabel(model.modelId)} - - {/each} - {#if chartDefs.some( (def) => def.series.some((series) => series.name === m.compare_model_mean()) )} - - - {m.compare_model_mean()} - - {/if} -
- {/if}
toggleSelection('hourly', value)} onToggleGroup={(values, select) => toggleGroupSelection('hourly', values, select)} - onApply={applyComparisonSelection} onRestoreDefaults={restoreStandardVariables} /> @@ -853,9 +858,46 @@ mode="models" groups={modelSelectionGroups} selected={params.models} - dirty={modelsDirty} onToggle={(value) => toggleSelection('models', value)} onToggleGroup={(values, select) => toggleGroupSelection('models', values, select)} - onApply={applyComparisonSelection} /> + +{#if comparisonSelectionDirty} + +
+
+
+

{m.compare_selection_pending()}

+
+ {#if variablesDirty} + {m.compare_variables_heading()}: {pendingVariableChanges} + {/if} + {#if modelsDirty} + {m.compare_models_heading()}: {pendingModelChanges} + {/if} +
+
+
+ + +
+
+
+{/if} diff --git a/src/routes/weather/compare/[location]/ComparisonSelectionPanel.svelte b/src/routes/weather/compare/[location]/ComparisonSelectionPanel.svelte index 8b660da..ef6196f 100644 --- a/src/routes/weather/compare/[location]/ComparisonSelectionPanel.svelte +++ b/src/routes/weather/compare/[location]/ComparisonSelectionPanel.svelte @@ -21,23 +21,12 @@ mode: 'models' | 'variables'; groups: OptionGroup[]; selected: string[]; - dirty: boolean; onToggle: (value: string) => void; onToggleGroup: (values: string[], select: boolean) => void; - onApply: () => void; onRestoreDefaults?: () => void; } - let { - mode, - groups, - selected, - dirty, - onToggle, - onToggleGroup, - onApply, - onRestoreDefaults - }: Props = $props(); + let { mode, groups, selected, onToggle, onToggleGroup, onRestoreDefaults }: Props = $props(); let search = $state(''); let onlySelected = $state(false); @@ -118,17 +107,6 @@ {selection.count}/{fullGroup.options.length} -
{#each visibleGroup.options as option (option.value)} @@ -164,27 +142,15 @@ {/if}
-
-
- {#if mode === 'variables' && onRestoreDefaults} - - {/if} + {#if mode === 'variables' && onRestoreDefaults} +
+
- -
+ {/if}
diff --git a/src/routes/weather/compare/[location]/ModelPictogramTimeline.svelte b/src/routes/weather/compare/[location]/ModelPictogramTimeline.svelte index d458aa1..cf16bf3 100644 --- a/src/routes/weather/compare/[location]/ModelPictogramTimeline.svelte +++ b/src/routes/weather/compare/[location]/ModelPictogramTimeline.svelte @@ -26,6 +26,8 @@ group: string; plotInsetLeft: number; plotInsetRight: number; + showModelNames: boolean; + onToggleModelNames: () => void; registerExporter?: (exporter: TimelineExporter | null) => void; } @@ -68,6 +70,8 @@ group, plotInsetLeft, plotInsetRight, + showModelNames, + onToggleModelNames, registerExporter }: Props = $props(); @@ -600,9 +604,16 @@ context.beginPath(); context.arc(9, rowCenter, 4, 0, Math.PI * 2); context.fill(); - context.fillStyle = foreground; - context.textAlign = 'left'; - context.fillText(modelLabel(model.modelId), 18, rowCenter, Math.max(20, plotInsetLeft - 24)); + if (showModelNames) { + context.fillStyle = foreground; + context.textAlign = 'left'; + context.fillText( + modelLabel(model.modelId), + 18, + rowCenter, + Math.max(20, plotInsetLeft - 24) + ); + } context.save(); context.beginPath(); @@ -659,6 +670,7 @@ void daySeparators; void sunlightSegments; void displayModels; + void showModelNames; if (!target || currentWidth <= 0 || currentHeight <= 0) return; const version = ++screenRenderVersion; @@ -684,11 +696,21 @@ class="mt-7 -mx-3 border-y border-border/70 bg-card shadow-sm lg:mx-0 lg:rounded-2xl lg:border" aria-labelledby="model-pictogram-title" > -
-

- {m.compare_timeline_title()} -

-

{m.compare_timeline_hint()}

+
+
+

+ {m.compare_timeline_title()} +

+

{m.compare_timeline_hint()}

+
+