mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2025-12-27 13:39:59 +00:00
update chartjs
This commit is contained in:
8
website/package-lock.json
generated
8
website/package-lock.json
generated
@@ -14,7 +14,7 @@
|
|||||||
"@mapbox/sphericalmercator": "^2.0.1",
|
"@mapbox/sphericalmercator": "^2.0.1",
|
||||||
"@mapbox/tilebelt": "^2.0.2",
|
"@mapbox/tilebelt": "^2.0.2",
|
||||||
"@types/mapbox__sphericalmercator": "^1.2.3",
|
"@types/mapbox__sphericalmercator": "^1.2.3",
|
||||||
"chart.js": "^4.4.9",
|
"chart.js": "^4.5.1",
|
||||||
"chartjs-plugin-zoom": "^2.2.0",
|
"chartjs-plugin-zoom": "^2.2.0",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"dexie": "^4.0.11",
|
"dexie": "^4.0.11",
|
||||||
@@ -3664,9 +3664,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/chart.js": {
|
"node_modules/chart.js": {
|
||||||
"version": "4.4.9",
|
"version": "4.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.9.tgz",
|
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz",
|
||||||
"integrity": "sha512-EyZ9wWKgpAU0fLJ43YAEIF8sr5F2W3LqbS40ZJyHIner2lY14ufqv2VMp69MAiZ2rpwxEUxEhIH/0U3xyRynxg==",
|
"integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kurkle/color": "^0.3.0"
|
"@kurkle/color": "^0.3.0"
|
||||||
|
|||||||
@@ -66,7 +66,7 @@
|
|||||||
"@mapbox/sphericalmercator": "^2.0.1",
|
"@mapbox/sphericalmercator": "^2.0.1",
|
||||||
"@mapbox/tilebelt": "^2.0.2",
|
"@mapbox/tilebelt": "^2.0.2",
|
||||||
"@types/mapbox__sphericalmercator": "^1.2.3",
|
"@types/mapbox__sphericalmercator": "^1.2.3",
|
||||||
"chart.js": "^4.4.9",
|
"chart.js": "^4.5.1",
|
||||||
"chartjs-plugin-zoom": "^2.2.0",
|
"chartjs-plugin-zoom": "^2.2.0",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"dexie": "^4.0.11",
|
"dexie": "^4.0.11",
|
||||||
|
|||||||
@@ -14,7 +14,12 @@ import {
|
|||||||
getTemperatureWithUnits,
|
getTemperatureWithUnits,
|
||||||
getVelocityWithUnits,
|
getVelocityWithUnits,
|
||||||
} from '$lib/units';
|
} from '$lib/units';
|
||||||
import Chart from 'chart.js/auto';
|
import Chart, {
|
||||||
|
type ChartEvent,
|
||||||
|
type ChartOptions,
|
||||||
|
type ScriptableLineSegmentContext,
|
||||||
|
type TooltipItem,
|
||||||
|
} from 'chart.js/auto';
|
||||||
import mapboxgl from 'mapbox-gl';
|
import mapboxgl from 'mapbox-gl';
|
||||||
import { get, type Readable, type Writable } from 'svelte/store';
|
import { get, type Readable, type Writable } from 'svelte/store';
|
||||||
import { map } from '$lib/components/map/map';
|
import { map } from '$lib/components/map/map';
|
||||||
@@ -27,6 +32,20 @@ const { distanceUnits, velocityUnits, temperatureUnits } = settings;
|
|||||||
Chart.defaults.font.family =
|
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
|
'ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"'; // Tailwind CSS font
|
||||||
|
|
||||||
|
interface ElevationProfilePoint {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
time?: Date;
|
||||||
|
slope: {
|
||||||
|
at: number;
|
||||||
|
segment: number;
|
||||||
|
length: number;
|
||||||
|
};
|
||||||
|
extensions: Record<string, any>;
|
||||||
|
coordinates: [number, number];
|
||||||
|
index: number;
|
||||||
|
}
|
||||||
|
|
||||||
export class ElevationProfile {
|
export class ElevationProfile {
|
||||||
private _chart: Chart | null = null;
|
private _chart: Chart | null = null;
|
||||||
private _canvas: HTMLCanvasElement;
|
private _canvas: HTMLCanvasElement;
|
||||||
@@ -90,7 +109,7 @@ export class ElevationProfile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
initialize() {
|
initialize() {
|
||||||
let options = {
|
let options: ChartOptions<'line'> = {
|
||||||
animation: false,
|
animation: false,
|
||||||
parsing: false,
|
parsing: false,
|
||||||
maintainAspectRatio: false,
|
maintainAspectRatio: false,
|
||||||
@@ -98,8 +117,8 @@ export class ElevationProfile {
|
|||||||
x: {
|
x: {
|
||||||
type: 'linear',
|
type: 'linear',
|
||||||
ticks: {
|
ticks: {
|
||||||
callback: function (value: number) {
|
callback: function (value: number | string) {
|
||||||
return `${value.toFixed(1).replace(/\.0+$/, '')} ${getDistanceUnits()}`;
|
return `${(value as number).toFixed(1).replace(/\.0+$/, '')} ${getDistanceUnits()}`;
|
||||||
},
|
},
|
||||||
align: 'inner',
|
align: 'inner',
|
||||||
maxRotation: 0,
|
maxRotation: 0,
|
||||||
@@ -108,8 +127,8 @@ export class ElevationProfile {
|
|||||||
y: {
|
y: {
|
||||||
type: 'linear',
|
type: 'linear',
|
||||||
ticks: {
|
ticks: {
|
||||||
callback: function (value: number) {
|
callback: function (value: number | string) {
|
||||||
return getElevationWithUnits(value, false);
|
return getElevationWithUnits(value as number, false);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -140,8 +159,8 @@ export class ElevationProfile {
|
|||||||
title: () => {
|
title: () => {
|
||||||
return '';
|
return '';
|
||||||
},
|
},
|
||||||
label: (context: Chart.TooltipContext) => {
|
label: (context: TooltipItem<'line'>) => {
|
||||||
let point = context.raw;
|
let point = context.raw as ElevationProfilePoint;
|
||||||
if (context.datasetIndex === 0) {
|
if (context.datasetIndex === 0) {
|
||||||
const map_ = get(map);
|
const map_ = get(map);
|
||||||
if (map_ && this._marker) {
|
if (map_ && this._marker) {
|
||||||
@@ -165,10 +184,10 @@ export class ElevationProfile {
|
|||||||
return `${i18n._('quantities.power')}: ${getPowerWithUnits(point.y)}`;
|
return `${i18n._('quantities.power')}: ${getPowerWithUnits(point.y)}`;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
afterBody: (contexts: Chart.TooltipContext[]) => {
|
afterBody: (contexts: TooltipItem<'line'>[]) => {
|
||||||
let context = contexts.filter((context) => context.datasetIndex === 0);
|
let context = contexts.filter((context) => context.datasetIndex === 0);
|
||||||
if (context.length === 0) return;
|
if (context.length === 0) return;
|
||||||
let point = context[0].raw;
|
let point = context[0].raw as ElevationProfilePoint;
|
||||||
let slope = {
|
let slope = {
|
||||||
at: point.slope.at.toFixed(1),
|
at: point.slope.at.toFixed(1),
|
||||||
segment: point.slope.segment.toFixed(1),
|
segment: point.slope.segment.toFixed(1),
|
||||||
@@ -227,6 +246,7 @@ export class ElevationProfile {
|
|||||||
onPanStart: () => {
|
onPanStart: () => {
|
||||||
this._panning = true;
|
this._panning = true;
|
||||||
this._slicedGPXStatistics.set(undefined);
|
this._slicedGPXStatistics.set(undefined);
|
||||||
|
return true;
|
||||||
},
|
},
|
||||||
onPanComplete: () => {
|
onPanComplete: () => {
|
||||||
this._panning = false;
|
this._panning = false;
|
||||||
@@ -238,13 +258,13 @@ export class ElevationProfile {
|
|||||||
},
|
},
|
||||||
mode: 'x',
|
mode: 'x',
|
||||||
onZoomStart: ({ chart, event }: { chart: Chart; event: any }) => {
|
onZoomStart: ({ chart, event }: { chart: Chart; event: any }) => {
|
||||||
|
if (!this._chart) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const maxZoom = this._chart.getInitialScaleBounds()?.x?.max ?? 0;
|
||||||
if (
|
if (
|
||||||
event.deltaY < 0 &&
|
event.deltaY < 0 &&
|
||||||
Math.abs(
|
Math.abs(maxZoom / this._chart.getZoomLevel()) < 0.01
|
||||||
this._chart.getInitialScaleBounds().x.max /
|
|
||||||
this._chart.options.plugins.zoom.limits.x.minRange -
|
|
||||||
this._chart.getZoomLevel()
|
|
||||||
) < 0.01
|
|
||||||
) {
|
) {
|
||||||
// Disable wheel pan if zoomed in to the max, and zooming in
|
// Disable wheel pan if zoomed in to the max, and zooming in
|
||||||
return false;
|
return false;
|
||||||
@@ -262,7 +282,6 @@ export class ElevationProfile {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
stacked: false,
|
|
||||||
onResize: () => {
|
onResize: () => {
|
||||||
this.updateOverlay();
|
this.updateOverlay();
|
||||||
},
|
},
|
||||||
@@ -270,7 +289,7 @@ export class ElevationProfile {
|
|||||||
|
|
||||||
let datasets: string[] = ['speed', 'hr', 'cad', 'atemp', 'power'];
|
let datasets: string[] = ['speed', 'hr', 'cad', 'atemp', 'power'];
|
||||||
datasets.forEach((id) => {
|
datasets.forEach((id) => {
|
||||||
options.scales[`y${id}`] = {
|
options.scales![`y${id}`] = {
|
||||||
type: 'linear',
|
type: 'linear',
|
||||||
position: 'right',
|
position: 'right',
|
||||||
grid: {
|
grid: {
|
||||||
@@ -291,7 +310,7 @@ export class ElevationProfile {
|
|||||||
{
|
{
|
||||||
id: 'toggleMarker',
|
id: 'toggleMarker',
|
||||||
events: ['mouseout'],
|
events: ['mouseout'],
|
||||||
afterEvent: (chart: Chart, args: { event: Chart.ChartEvent }) => {
|
afterEvent: (chart: Chart, args: { event: ChartEvent }) => {
|
||||||
if (args.event.type === 'mouseout') {
|
if (args.event.type === 'mouseout') {
|
||||||
const map_ = get(map);
|
const map_ = get(map);
|
||||||
if (map_ && this._marker) {
|
if (map_ && this._marker) {
|
||||||
@@ -305,7 +324,7 @@ export class ElevationProfile {
|
|||||||
|
|
||||||
let startIndex = 0;
|
let startIndex = 0;
|
||||||
let endIndex = 0;
|
let endIndex = 0;
|
||||||
const getIndex = (evt) => {
|
const getIndex = (evt: PointerEvent) => {
|
||||||
if (!this._chart) {
|
if (!this._chart) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@@ -329,16 +348,16 @@ export class ElevationProfile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let point = points.find((point) => point.element.raw);
|
const point = points.find((point) => (point.element as any).raw);
|
||||||
if (point) {
|
if (point) {
|
||||||
return point.element.raw.index;
|
return (point.element as any).raw.index;
|
||||||
} else {
|
} else {
|
||||||
return points[0].index;
|
return points[0].index;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let dragStarted = false;
|
let dragStarted = false;
|
||||||
const onMouseDown = (evt) => {
|
const onMouseDown = (evt: PointerEvent) => {
|
||||||
if (evt.shiftKey) {
|
if (evt.shiftKey) {
|
||||||
// Panning interaction
|
// Panning interaction
|
||||||
return;
|
return;
|
||||||
@@ -347,7 +366,7 @@ export class ElevationProfile {
|
|||||||
this._canvas.style.cursor = 'col-resize';
|
this._canvas.style.cursor = 'col-resize';
|
||||||
startIndex = getIndex(evt);
|
startIndex = getIndex(evt);
|
||||||
};
|
};
|
||||||
const onMouseMove = (evt) => {
|
const onMouseMove = (evt: PointerEvent) => {
|
||||||
if (dragStarted) {
|
if (dragStarted) {
|
||||||
this._dragging = true;
|
this._dragging = true;
|
||||||
endIndex = getIndex(evt);
|
endIndex = getIndex(evt);
|
||||||
@@ -367,7 +386,7 @@ export class ElevationProfile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const onMouseUp = (evt) => {
|
const onMouseUp = (evt: PointerEvent) => {
|
||||||
dragStarted = false;
|
dragStarted = false;
|
||||||
this._dragging = false;
|
this._dragging = false;
|
||||||
this._canvas.style.cursor = '';
|
this._canvas.style.cursor = '';
|
||||||
@@ -463,8 +482,8 @@ export class ElevationProfile {
|
|||||||
normalized: true,
|
normalized: true,
|
||||||
yAxisID: 'ypower',
|
yAxisID: 'ypower',
|
||||||
};
|
};
|
||||||
this._chart.options.scales.x['min'] = 0;
|
this._chart.options.scales!.x!['min'] = 0;
|
||||||
this._chart.options.scales.x['max'] = getConvertedDistance(data.global.distance.total);
|
this._chart.options.scales!.x!['max'] = getConvertedDistance(data.global.distance.total);
|
||||||
|
|
||||||
this.setVisibility();
|
this.setVisibility();
|
||||||
this.setFill();
|
this.setFill();
|
||||||
@@ -513,21 +532,24 @@ export class ElevationProfile {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const elevationFill = get(this._elevationFill);
|
const elevationFill = get(this._elevationFill);
|
||||||
|
const dataset = this._chart.data.datasets[0];
|
||||||
|
let segment: any = {};
|
||||||
if (elevationFill === 'slope') {
|
if (elevationFill === 'slope') {
|
||||||
this._chart.data.datasets[0]['segment'] = {
|
segment = {
|
||||||
backgroundColor: this.slopeFillCallback,
|
backgroundColor: this.slopeFillCallback,
|
||||||
};
|
};
|
||||||
} else if (elevationFill === 'surface') {
|
} else if (elevationFill === 'surface') {
|
||||||
this._chart.data.datasets[0]['segment'] = {
|
segment = {
|
||||||
backgroundColor: this.surfaceFillCallback,
|
backgroundColor: this.surfaceFillCallback,
|
||||||
};
|
};
|
||||||
} else if (elevationFill === 'highway') {
|
} else if (elevationFill === 'highway') {
|
||||||
this._chart.data.datasets[0]['segment'] = {
|
segment = {
|
||||||
backgroundColor: this.highwayFillCallback,
|
backgroundColor: this.highwayFillCallback,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
this._chart.data.datasets[0]['segment'] = {};
|
segment = {};
|
||||||
}
|
}
|
||||||
|
Object.assign(dataset, { segment });
|
||||||
}
|
}
|
||||||
|
|
||||||
updateOverlay() {
|
updateOverlay() {
|
||||||
@@ -575,19 +597,22 @@ export class ElevationProfile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
slopeFillCallback(context) {
|
slopeFillCallback(context: ScriptableLineSegmentContext & { p0: { raw: any } }) {
|
||||||
return getSlopeColor(context.p0.raw.slope.segment);
|
const point = context.p0.raw as ElevationProfilePoint;
|
||||||
|
return getSlopeColor(point.slope.segment);
|
||||||
}
|
}
|
||||||
|
|
||||||
surfaceFillCallback(context) {
|
surfaceFillCallback(context: ScriptableLineSegmentContext & { p0: { raw: any } }) {
|
||||||
return getSurfaceColor(context.p0.raw.extensions.surface);
|
const point = context.p0.raw as ElevationProfilePoint;
|
||||||
|
return getSurfaceColor(point.extensions.surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
highwayFillCallback(context) {
|
highwayFillCallback(context: ScriptableLineSegmentContext & { p0: { raw: any } }) {
|
||||||
|
const point = context.p0.raw as ElevationProfilePoint;
|
||||||
return getHighwayColor(
|
return getHighwayColor(
|
||||||
context.p0.raw.extensions.highway,
|
point.extensions.highway,
|
||||||
context.p0.raw.extensions.sac_scale,
|
point.extensions.sac_scale,
|
||||||
context.p0.raw.extensions.mtb_scale
|
point.extensions.mtb_scale
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user