mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2025-09-01 08:12:32 +00:00
drag-select range elevation profile
This commit is contained in:
@@ -6,7 +6,7 @@
|
|||||||
import Chart from 'chart.js/auto';
|
import Chart from 'chart.js/auto';
|
||||||
import mapboxgl from 'mapbox-gl';
|
import mapboxgl from 'mapbox-gl';
|
||||||
|
|
||||||
import { map, gpxStatistics } from '$lib/stores';
|
import { map, gpxStatistics, slicedGPXStatistics } from '$lib/stores';
|
||||||
import { settings } from '$lib/db';
|
import { settings } from '$lib/db';
|
||||||
|
|
||||||
import { onDestroy, onMount } from 'svelte';
|
import { onDestroy, onMount } from 'svelte';
|
||||||
@@ -42,8 +42,10 @@
|
|||||||
getVelocityWithUnits,
|
getVelocityWithUnits,
|
||||||
secondsToHHMMSS
|
secondsToHHMMSS
|
||||||
} from '$lib/units';
|
} from '$lib/units';
|
||||||
|
import { get } from 'svelte/store';
|
||||||
|
|
||||||
let canvas: HTMLCanvasElement;
|
let canvas: HTMLCanvasElement;
|
||||||
|
let overlay: HTMLCanvasElement;
|
||||||
let chart: Chart;
|
let chart: Chart;
|
||||||
|
|
||||||
Chart.defaults.font.family =
|
Chart.defaults.font.family =
|
||||||
@@ -53,6 +55,7 @@
|
|||||||
let additionalDatasets: string[];
|
let additionalDatasets: string[];
|
||||||
|
|
||||||
let marker: mapboxgl.Marker | null = null;
|
let marker: mapboxgl.Marker | null = null;
|
||||||
|
let dragging = false;
|
||||||
|
|
||||||
let { distanceUnits, velocityUnits, temperatureUnits } = settings;
|
let { distanceUnits, velocityUnits, temperatureUnits } = settings;
|
||||||
|
|
||||||
@@ -101,6 +104,7 @@
|
|||||||
enabled: true
|
enabled: true
|
||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
|
enabled: () => !dragging,
|
||||||
callbacks: {
|
callbacks: {
|
||||||
title: function () {
|
title: function () {
|
||||||
return '';
|
return '';
|
||||||
@@ -109,8 +113,12 @@
|
|||||||
let point = context.raw;
|
let point = context.raw;
|
||||||
if (context.datasetIndex === 0) {
|
if (context.datasetIndex === 0) {
|
||||||
if ($map && marker) {
|
if ($map && marker) {
|
||||||
marker.addTo($map);
|
if (dragging) {
|
||||||
marker.setLngLat(point.coordinates);
|
marker.remove();
|
||||||
|
} else {
|
||||||
|
marker.addTo($map);
|
||||||
|
marker.setLngLat(point.coordinates);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return `${$_('quantities.elevation')}: ${getElevationWithUnits(point.y, false)}`;
|
return `${$_('quantities.elevation')}: ${getElevationWithUnits(point.y, false)}`;
|
||||||
} else if (context.datasetIndex === 1) {
|
} 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: {
|
let datasets: {
|
||||||
@@ -227,11 +239,101 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Map marker to show on hover
|
||||||
let element = document.createElement('div');
|
let element = document.createElement('div');
|
||||||
element.className = 'h-4 w-4 rounded-full bg-cyan-500 border-2 border-white';
|
element.className = 'h-4 w-4 rounded-full bg-cyan-500 border-2 border-white';
|
||||||
marker = new mapboxgl.Marker({
|
marker = new mapboxgl.Marker({
|
||||||
element
|
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) {
|
$: if (chart && $distanceUnits && $velocityUnits && $temperatureUnits) {
|
||||||
@@ -246,7 +348,8 @@
|
|||||||
y: point.ele ? getConvertedElevation(point.ele) : 0,
|
y: point.ele ? getConvertedElevation(point.ele) : 0,
|
||||||
slope: data.local.slope[index],
|
slope: data.local.slope[index],
|
||||||
surface: point.getSurface(),
|
surface: point.getSurface(),
|
||||||
coordinates: point.getCoordinates()
|
coordinates: point.getCoordinates(),
|
||||||
|
index: index
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
normalized: true,
|
normalized: true,
|
||||||
@@ -402,6 +505,9 @@
|
|||||||
chart.update();
|
chart.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: if ($slicedGPXStatistics) {
|
||||||
|
}
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
if (chart) {
|
if (chart) {
|
||||||
chart.destroy();
|
chart.destroy();
|
||||||
@@ -411,7 +517,8 @@
|
|||||||
|
|
||||||
<div class="h-full grow min-w-0 flex flex-row gap-4 items-center py-2 pr-4">
|
<div class="h-full grow min-w-0 flex flex-row gap-4 items-center py-2 pr-4">
|
||||||
<div class="h-full grow min-w-0">
|
<div class="h-full grow min-w-0">
|
||||||
<canvas bind:this={canvas} class="w-full h-full"> </canvas>
|
<canvas bind:this={overlay} class="absolute pointer-events-none"></canvas>
|
||||||
|
<canvas bind:this={canvas} class="w-full h-full"></canvas>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-fit flex flex-col border rounded">
|
<div class="w-fit flex flex-col border rounded">
|
||||||
<ToggleGroup.Root class="flex-col gap-0" type="single" bind:value={elevationFill}>
|
<ToggleGroup.Root class="flex-col gap-0" type="single" bind:value={elevationFill}>
|
||||||
|
@@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
let statistics: GPXStatistics;
|
let statistics: GPXStatistics;
|
||||||
|
|
||||||
$: if ($currentTool === Tool.SCISSORS) {
|
$: if ($slicedGPXStatistics !== undefined) {
|
||||||
statistics = $slicedGPXStatistics;
|
statistics = $slicedGPXStatistics;
|
||||||
} else {
|
} else {
|
||||||
statistics = $gpxStatistics;
|
statistics = $gpxStatistics;
|
||||||
|
@@ -27,7 +27,7 @@ export class StartEndMarkers {
|
|||||||
|
|
||||||
update() {
|
update() {
|
||||||
let tool = get(currentTool);
|
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) {
|
if (statistics.local.points.length > 0 && tool !== Tool.ROUTING) {
|
||||||
this.start.setLngLat(statistics.local.points[0].getCoordinates()).addTo(this.map);
|
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);
|
this.end.setLngLat(statistics.local.points[statistics.local.points.length - 1].getCoordinates()).addTo(this.map);
|
||||||
|
@@ -18,7 +18,7 @@
|
|||||||
import { gpxStatistics, slicedGPXStatistics, splitAs } from '$lib/stores';
|
import { gpxStatistics, slicedGPXStatistics, splitAs } from '$lib/stores';
|
||||||
import { get } from 'svelte/store';
|
import { get } from 'svelte/store';
|
||||||
import { _ } from 'svelte-i18n';
|
import { _ } from 'svelte-i18n';
|
||||||
import { tick } from 'svelte';
|
import { onDestroy, tick } from 'svelte';
|
||||||
import { Crop } from 'lucide-svelte';
|
import { Crop } from 'lucide-svelte';
|
||||||
import { dbUtils } from '$lib/db';
|
import { dbUtils } from '$lib/db';
|
||||||
|
|
||||||
@@ -62,6 +62,10 @@
|
|||||||
let splitType = splitTypes[0];
|
let splitType = splitTypes[0];
|
||||||
|
|
||||||
$: splitAs.set(splitType.value);
|
$: splitAs.set(splitType.value);
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
slicedGPXStatistics.set(undefined);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-col gap-3 max-w-80">
|
<div class="flex flex-col gap-3 max-w-80">
|
||||||
|
@@ -16,7 +16,7 @@ export const map = writable<mapboxgl.Map | null>(null);
|
|||||||
export const selectFiles = writable<{ [key: string]: (fileId?: string) => void }>({});
|
export const selectFiles = writable<{ [key: string]: (fileId?: string) => void }>({});
|
||||||
|
|
||||||
export const gpxStatistics: Writable<GPXStatistics> = writable(new GPXStatistics());
|
export const gpxStatistics: Writable<GPXStatistics> = writable(new GPXStatistics());
|
||||||
export const slicedGPXStatistics: Writable<GPXStatistics> = writable(new GPXStatistics());
|
export const slicedGPXStatistics: Writable<GPXStatistics | undefined> = writable(undefined);
|
||||||
|
|
||||||
function updateGPXData() {
|
function updateGPXData() {
|
||||||
let statistics = new GPXStatistics();
|
let statistics = new GPXStatistics();
|
||||||
|
Reference in New Issue
Block a user