diff --git a/website/package-lock.json b/website/package-lock.json index de615086..97140425 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -14,6 +14,7 @@ "@types/mapbox__sphericalmercator": "^1.2.3", "bits-ui": "^0.21.12", "chart.js": "^4.4.3", + "chartjs-plugin-zoom": "^2.0.1", "clsx": "^2.1.1", "dexie": "^4.0.7", "gpx": "file:../gpx", @@ -2686,6 +2687,17 @@ "pnpm": ">=8" } }, + "node_modules/chartjs-plugin-zoom": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/chartjs-plugin-zoom/-/chartjs-plugin-zoom-2.0.1.tgz", + "integrity": "sha512-ogOmLu6e+Q7E1XWOCOz9YwybMslz9qNfGV2a+qjfmqJYpsw5ZMoRHZBUyW+NGhkpQ5PwwPA/+rikHpBZb7PZuA==", + "dependencies": { + "hammerjs": "^2.0.8" + }, + "peerDependencies": { + "chart.js": ">=3.2.0" + } + }, "node_modules/cheap-ruler": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/cheap-ruler/-/cheap-ruler-3.0.2.tgz", @@ -3958,6 +3970,14 @@ "resolved": "https://registry.npmjs.org/grid-index/-/grid-index-1.1.0.tgz", "integrity": "sha512-HZRwumpOGUrHyxO5bqKZL0B0GlUpwtCAzZ42sgxUPniu33R1LSFH5yrIcBCHjkctCAh3mtWKcKd9J4vDDdeVHA==" }, + "node_modules/hammerjs": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz", + "integrity": "sha512-tSQXBXS/MWQOn/RKckawJ61vvsDpCom87JgxiYdGwHdOa0ht0vzUWDlfioofFCRU0L+6NGDt6XzbgoJvZkMeRQ==", + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/hard-rejection": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", diff --git a/website/package.json b/website/package.json index a5444d93..94dc6267 100644 --- a/website/package.json +++ b/website/package.json @@ -53,6 +53,7 @@ "@types/mapbox__sphericalmercator": "^1.2.3", "bits-ui": "^0.21.12", "chart.js": "^4.4.3", + "chartjs-plugin-zoom": "^2.0.1", "clsx": "^2.1.1", "dexie": "^4.0.7", "gpx": "file:../gpx", @@ -69,4 +70,4 @@ "tailwind-merge": "^2.3.0", "tailwind-variants": "^0.2.1" } -} \ No newline at end of file +} diff --git a/website/src/lib/components/ElevationProfile.svelte b/website/src/lib/components/ElevationProfile.svelte index 03c30a56..e752eca8 100644 --- a/website/src/lib/components/ElevationProfile.svelte +++ b/website/src/lib/components/ElevationProfile.svelte @@ -2,6 +2,7 @@ import * as ToggleGroup from '$lib/components/ui/toggle-group'; import Tooltip from '$lib/components/Tooltip.svelte'; import Chart from 'chart.js/auto'; + import zoomPlugin from 'chartjs-plugin-zoom'; import mapboxgl from 'mapbox-gl'; import { map } from '$lib/stores'; import { onDestroy, onMount } from 'svelte'; @@ -68,11 +69,13 @@ let overlay: HTMLCanvasElement; let chart: Chart; + Chart.register(zoomPlugin); Chart.defaults.font.family = 'ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"'; // Tailwind CSS font let marker: mapboxgl.Marker | null = null; let dragging = false; + let panning = false; let options = { animation: false, @@ -119,7 +122,7 @@ enabled: true }, tooltip: { - enabled: () => !dragging, + enabled: () => !dragging && !panning, callbacks: { title: function () { return ''; @@ -177,6 +180,48 @@ return labels; } } + }, + zoom: { + pan: { + enabled: true, + mode: 'x', + modifierKey: 'shift', + onPanStart: function () { + // hide tooltip + panning = true; + $slicedGPXStatistics = undefined; + }, + onPanComplete: function () { + panning = false; + } + }, + zoom: { + wheel: { + enabled: true + }, + mode: 'x', + onZoomStart: function ({ chart, event }: { chart: Chart; event: any }) { + if ( + event.deltaY < 0 && + Math.abs( + chart.getInitialScaleBounds().x.max / chart.options.plugins.zoom.limits.x.minRange - + chart.getZoomLevel() + ) < 0.01 + ) { + // Disable wheel pan if zoomed in to the max, and zooming in + return false; + } + + $slicedGPXStatistics = undefined; + } + }, + limits: { + x: { + min: 'original', + max: 'original', + minRange: 1 + } + } } }, stacked: false, @@ -313,6 +358,10 @@ let dragStarted = false; function onMouseDown(evt) { + if (evt.shiftKey) { + // Panning interaction + return; + } dragStarted = true; canvas.style.cursor = 'col-resize'; startIndex = getIndex(evt); diff --git a/website/src/lib/docs/en/files-and-stats.mdx b/website/src/lib/docs/en/files-and-stats.mdx index aba9aae9..451f9474 100644 --- a/website/src/lib/docs/en/files-and-stats.mdx +++ b/website/src/lib/docs/en/files-and-stats.mdx @@ -65,9 +65,12 @@ The size of the elevation profile can be adjusted by dragging the separator betw ### Interactive statistics When hovering over the elevation profile, a tooltip will show statistics at the cursor position. + To get the statistics for a specific section of the elevation profile, you can drag a selection rectangle on the profile. Click on the profile to reset the selection. +You can also use the mouse wheel to zoom in and out on the elevation profile, and pan by dragging the profile while holding the Shift key. + ### Additional data Using the buttons on the right of the elevation profile, you can optionally color the elevation profile by: diff --git a/website/src/routes/+layout.js b/website/src/routes/+layout.js index ed90b52f..ed97adf6 100644 --- a/website/src/routes/+layout.js +++ b/website/src/routes/+layout.js @@ -1,4 +1,5 @@ export const prerender = true; +export const ssr = false; import { languages } from '$lib/languages'; import { register, init } from 'svelte-i18n';