From 9ad16ac294bc82edce7c281557ceb03ad9fded95 Mon Sep 17 00:00:00 2001 From: vcoppe Date: Wed, 12 Jun 2024 12:13:30 +0200 Subject: [PATCH] drag-select range elevation profile --- .../lib/components/ElevationProfile.svelte | 119 +++++++++++++++++- .../src/lib/components/GPXStatistics.svelte | 2 +- .../components/gpx-layer/StartEndMarkers.ts | 2 +- .../components/toolbar/tools/Scissors.svelte | 6 +- website/src/lib/stores.ts | 2 +- 5 files changed, 121 insertions(+), 10 deletions(-) diff --git a/website/src/lib/components/ElevationProfile.svelte b/website/src/lib/components/ElevationProfile.svelte index 74c9b1b5..174dae6b 100644 --- a/website/src/lib/components/ElevationProfile.svelte +++ b/website/src/lib/components/ElevationProfile.svelte @@ -6,7 +6,7 @@ import Chart from 'chart.js/auto'; import mapboxgl from 'mapbox-gl'; - import { map, gpxStatistics } from '$lib/stores'; + import { map, gpxStatistics, slicedGPXStatistics } from '$lib/stores'; import { settings } from '$lib/db'; import { onDestroy, onMount } from 'svelte'; @@ -42,8 +42,10 @@ getVelocityWithUnits, secondsToHHMMSS } from '$lib/units'; + import { get } from 'svelte/store'; let canvas: HTMLCanvasElement; + let overlay: HTMLCanvasElement; let chart: Chart; Chart.defaults.font.family = @@ -53,6 +55,7 @@ let additionalDatasets: string[]; let marker: mapboxgl.Marker | null = null; + let dragging = false; let { distanceUnits, velocityUnits, temperatureUnits } = settings; @@ -101,6 +104,7 @@ enabled: true }, tooltip: { + enabled: () => !dragging, callbacks: { title: function () { return ''; @@ -109,8 +113,12 @@ let point = context.raw; if (context.datasetIndex === 0) { if ($map && marker) { - marker.addTo($map); - marker.setLngLat(point.coordinates); + if (dragging) { + marker.remove(); + } else { + marker.addTo($map); + marker.setLngLat(point.coordinates); + } } return `${$_('quantities.elevation')}: ${getElevationWithUnits(point.y, false)}`; } else if (context.datasetIndex === 1) { @@ -141,7 +149,11 @@ } } }, - stacked: false + stacked: false, + onResize: function (chart, size) { + overlay.width = size.width; + overlay.height = size.height; + } }; let datasets: { @@ -227,11 +239,101 @@ } ] }); + + // Map marker to show on hover let element = document.createElement('div'); element.className = 'h-4 w-4 rounded-full bg-cyan-500 border-2 border-white'; marker = new mapboxgl.Marker({ element }); + + // Overlay canvas to create a selection rectangle + overlay.width = canvas.width; + overlay.height = canvas.height; + + let selectionContext = overlay.getContext('2d'); + let selectionRect = { + w: 0, + startX: 0, + startY: 0 + }; + let startIndex = 0, + endIndex = 0; + canvas.addEventListener('pointerdown', (evt) => { + dragging = true; + canvas.style.cursor = 'col-resize'; + + const points = chart.getElementsAtEventForMode( + evt, + 'index', + { + intersect: false + }, + true + ); + startIndex = points.find((point) => point.datasetIndex === 0)?.element.raw.index ?? 0; + + const rect = canvas.getBoundingClientRect(); + selectionRect.startX = Math.min( + Math.max(evt.clientX - rect.left, chart.chartArea.left), + chart.chartArea.right + ); + selectionRect.startY = chart.chartArea.top; + }); + canvas.addEventListener('pointermove', (evt) => { + if (dragging) { + const rect = canvas.getBoundingClientRect(); + selectionRect.w = + Math.min(Math.max(evt.clientX - rect.left, chart.chartArea.left), chart.chartArea.right) - + selectionRect.startX; + if (selectionContext) { + selectionContext.globalAlpha = 0.2; + selectionContext.clearRect(0, 0, canvas.width, canvas.height); + selectionContext.fillRect( + Math.max(selectionRect.startX, chart.chartArea.left), + selectionRect.startY, + selectionRect.w, + chart.chartArea.bottom - chart.chartArea.top + ); + } + + const points = chart.getElementsAtEventForMode( + evt, + 'index', + { + intersect: false + }, + true + ); + endIndex = points.find((point) => point.datasetIndex === 0)?.element.raw.index ?? 0; + + if (startIndex !== endIndex) { + slicedGPXStatistics.set( + get(gpxStatistics).slice(Math.min(startIndex, endIndex), Math.max(startIndex, endIndex)) + ); + } + } + }); + canvas.addEventListener('pointerup', (evt) => { + dragging = false; + canvas.style.cursor = ''; + + const points = chart.getElementsAtEventForMode( + evt, + 'index', + { + intersect: false + }, + true + ); + endIndex = points.find((point) => point.datasetIndex === 0)?.element.raw.index ?? 0; + + if (startIndex === endIndex) { + if (selectionContext) { + selectionContext.clearRect(0, 0, canvas.width, canvas.height); + } + } + }); }); $: if (chart && $distanceUnits && $velocityUnits && $temperatureUnits) { @@ -246,7 +348,8 @@ y: point.ele ? getConvertedElevation(point.ele) : 0, slope: data.local.slope[index], surface: point.getSurface(), - coordinates: point.getCoordinates() + coordinates: point.getCoordinates(), + index: index }; }), normalized: true, @@ -402,6 +505,9 @@ chart.update(); } + $: if ($slicedGPXStatistics) { + } + onDestroy(() => { if (chart) { chart.destroy(); @@ -411,7 +517,8 @@
- + +
diff --git a/website/src/lib/components/GPXStatistics.svelte b/website/src/lib/components/GPXStatistics.svelte index 432966b4..2c6bb68c 100644 --- a/website/src/lib/components/GPXStatistics.svelte +++ b/website/src/lib/components/GPXStatistics.svelte @@ -15,7 +15,7 @@ let statistics: GPXStatistics; - $: if ($currentTool === Tool.SCISSORS) { + $: if ($slicedGPXStatistics !== undefined) { statistics = $slicedGPXStatistics; } else { statistics = $gpxStatistics; diff --git a/website/src/lib/components/gpx-layer/StartEndMarkers.ts b/website/src/lib/components/gpx-layer/StartEndMarkers.ts index a1338f99..8e60750d 100644 --- a/website/src/lib/components/gpx-layer/StartEndMarkers.ts +++ b/website/src/lib/components/gpx-layer/StartEndMarkers.ts @@ -27,7 +27,7 @@ export class StartEndMarkers { update() { let tool = get(currentTool); - let statistics = tool === Tool.SCISSORS ? get(slicedGPXStatistics) : get(gpxStatistics); + let statistics = get(slicedGPXStatistics) ?? get(gpxStatistics); if (statistics.local.points.length > 0 && tool !== Tool.ROUTING) { this.start.setLngLat(statistics.local.points[0].getCoordinates()).addTo(this.map); this.end.setLngLat(statistics.local.points[statistics.local.points.length - 1].getCoordinates()).addTo(this.map); diff --git a/website/src/lib/components/toolbar/tools/Scissors.svelte b/website/src/lib/components/toolbar/tools/Scissors.svelte index e23e7427..e2bcdab0 100644 --- a/website/src/lib/components/toolbar/tools/Scissors.svelte +++ b/website/src/lib/components/toolbar/tools/Scissors.svelte @@ -18,7 +18,7 @@ import { gpxStatistics, slicedGPXStatistics, splitAs } from '$lib/stores'; import { get } from 'svelte/store'; import { _ } from 'svelte-i18n'; - import { tick } from 'svelte'; + import { onDestroy, tick } from 'svelte'; import { Crop } from 'lucide-svelte'; import { dbUtils } from '$lib/db'; @@ -62,6 +62,10 @@ let splitType = splitTypes[0]; $: splitAs.set(splitType.value); + + onDestroy(() => { + slicedGPXStatistics.set(undefined); + });
diff --git a/website/src/lib/stores.ts b/website/src/lib/stores.ts index 03a76719..ce3cdc74 100644 --- a/website/src/lib/stores.ts +++ b/website/src/lib/stores.ts @@ -16,7 +16,7 @@ export const map = writable(null); export const selectFiles = writable<{ [key: string]: (fileId?: string) => void }>({}); export const gpxStatistics: Writable = writable(new GPXStatistics()); -export const slicedGPXStatistics: Writable = writable(new GPXStatistics()); +export const slicedGPXStatistics: Writable = writable(undefined); function updateGPXData() { let statistics = new GPXStatistics();