({
+ x: [],
+ y: [],
+ z: [],
+ });
+
+ const [showTrackerGraph, setShowTrackerGraph] = useState(false);
+
+ const secondDuration = 60;
+
+ useEffect(() => {
+ if (!showTrackerGraph) {
+ return;
+ }
+
+ const newValue = tracker.info?.isImu
+ ? tracker.linearAcceleration
+ : tracker.position;
+ if (!newValue) {
+ return;
+ }
+
+ const currentTime = new Date().getTime() / 1000;
+ const startTime = currentTime - secondDuration;
+
+ const updateData = (data: AxisData[], newSample: number) => {
+ const remapped = data
+ .filter((value) => value.time >= startTime)
+ .map((value) => ({ ...value, x: value.time - startTime }));
+ remapped.push({
+ time: currentTime,
+ x: secondDuration,
+ y: newSample,
+ });
+ return remapped;
+ };
+
+ const newData = {
+ x: updateData(chartData.x, newValue.x),
+ y: updateData(chartData.y, newValue.y),
+ z: updateData(chartData.z, newValue.z),
+ };
+ setChartData(newData);
+ }, [tracker]);
+
+ useEffect(() => {
+ if (!showTrackerGraph) {
+ setChartData({ x: [], y: [], z: [] });
+ }
+ }, [showTrackerGraph]);
+
+ return (
+ <>
+
+ {showTrackerGraph && (
+
+ `"${font}"`).join(','),
+ size: config?.textSize,
+ },
+ plugins: {
+ title: {
+ display: true,
+ text: l10n.getString(
+ tracker?.info?.isImu
+ ? 'tracker-settings-graph-acceleration-title'
+ : 'tracker-settings-graph-position-title'
+ ),
+ color: 'white',
+ },
+ tooltip: {
+ mode: 'index',
+ intersect: false,
+ animation: false,
+ callbacks: {
+ title: () => '',
+ },
+ },
+ legend: {
+ labels: {
+ color: 'white',
+ },
+ },
+ },
+ scales: {
+ x: {
+ type: 'linear',
+ min: 0,
+ max: secondDuration,
+ ticks: {
+ color: 'white',
+ },
+ },
+ y: {
+ min: -4,
+ max: 4,
+ ticks: {
+ color: 'white',
+ },
+ },
+ },
+ elements: {
+ point: {
+ radius: 0,
+ },
+ },
+ parsing: false,
+ normalized: true,
+ maintainAspectRatio: false,
+ }}
+ data={{
+ labels: ['X', 'Y', 'Z'],
+ datasets: [
+ {
+ label: 'X',
+ data: chartData.x,
+ borderColor: 'rgb(200, 50, 50)',
+ backgroundColor: 'rgb(200, 100, 100)',
+ },
+ {
+ label: 'Y',
+ data: chartData.y,
+ borderColor: 'rgb(50, 200, 50)',
+ backgroundColor: 'rgb(100, 200, 100)',
+ },
+ {
+ label: 'Z',
+ data: chartData.z,
+ borderColor: 'rgb(50, 50, 200)',
+ backgroundColor: 'rgb(100, 100, 200)',
+ },
+ ],
+ }}
+ id="tracker-graph"
+ />
+
+ )}
+ >
+ );
+}
diff --git a/gui/src/components/tracker/TrackerSettings.tsx b/gui/src/components/tracker/TrackerSettings.tsx
index 4f0489cce0..20ab85665f 100644
--- a/gui/src/components/tracker/TrackerSettings.tsx
+++ b/gui/src/components/tracker/TrackerSettings.tsx
@@ -40,6 +40,7 @@ import semver from 'semver';
import { useSetAtom } from 'jotai';
import { ignoredTrackersAtom } from '@/store/app-store';
import { checkForUpdate } from '@/hooks/firmware-update';
+import { TrackerGraph } from './TrackerGraph';
const rotationsLabels: [Quaternion, string][] = [
[rotationToQuatMap.BACK, 'tracker-rotation-back'],
@@ -505,6 +506,7 @@ export function TrackerSettingsPage() {
)}
+ {tracker && }
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 3ffeb45faa..ff2cbf5e56 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -83,6 +83,9 @@ importers:
browser-fs-access:
specifier: ^0.35.0
version: 0.35.0
+ chart.js:
+ specifier: ^4.5.0
+ version: 4.5.0
classnames:
specifier: ^2.5.1
version: 2.5.1
@@ -104,6 +107,9 @@ importers:
react:
specifier: ^18.3.1
version: 18.3.1
+ react-chartjs-2:
+ specifier: ^5.3.0
+ version: 5.3.0(chart.js@4.5.0)(react@18.3.1)
react-dom:
specifier: ^18.3.1
version: 18.3.1(react@18.3.1)
@@ -720,6 +726,9 @@ packages:
'@jridgewell/trace-mapping@0.3.25':
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
+ '@kurkle/color@0.3.4':
+ resolution: {integrity: sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==}
+
'@mediapipe/tasks-vision@0.10.8':
resolution: {integrity: sha512-Rp7ll8BHrKB3wXaRFKhrltwZl1CiXGdibPxuWXvqGnKTnv8fqa/nvftYNuSbf+pbJWKYCXdBtYTITdAUTGGh0Q==}
@@ -1780,6 +1789,10 @@ packages:
character-reference-invalid@2.0.1:
resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==}
+ chart.js@4.5.0:
+ resolution: {integrity: sha512-aYeC/jDgSEx8SHWZvANYMioYMZ2KX02W6f6uVfyteuCGcadDLcYVHdfdygsTQkQ4TKn5lghoojAsPj5pu0SnvQ==}
+ engines: {pnpm: '>=8'}
+
chokidar@3.6.0:
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
engines: {node: '>= 8.10.0'}
@@ -2470,6 +2483,7 @@ packages:
got-fetch@5.1.10:
resolution: {integrity: sha512-Gwj/A2htjvLEcY07PKDItv0WCPEs3dV2vWeZ+9TVBSKSTuWEZ4oXaMD0ZAOsajwx2orahQWN4HI0MfRyWSZsbg==}
engines: {node: '>=14.0.0'}
+ deprecated: please use built-in fetch in nodejs
peerDependencies:
got: ^12.0.0
@@ -3542,6 +3556,12 @@ packages:
resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==}
engines: {node: '>=10'}
+ react-chartjs-2@5.3.0:
+ resolution: {integrity: sha512-UfZZFnDsERI3c3CZGxzvNJd02SHjaSJ8kgW1djn65H1KK8rehwTjyrRKOG3VTMG8wtHZ5rgAO5oTHtHi9GCCmw==}
+ peerDependencies:
+ chart.js: ^4.1.1
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
react-composer@5.0.3:
resolution: {integrity: sha512-1uWd07EME6XZvMfapwZmc7NgCZqDemcvicRi3wMJzXsQLvZ3L7fTHVyPy1bZdnWXM4iPjYuNE+uJ41MLKeTtnA==}
peerDependencies:
@@ -4959,6 +4979,8 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.4.15
+ '@kurkle/color@0.3.4': {}
+
'@mediapipe/tasks-vision@0.10.8': {}
'@mgit-at/typescript-flatbuffers-codegen@0.1.3':
@@ -6072,6 +6094,10 @@ snapshots:
character-reference-invalid@2.0.1: {}
+ chart.js@4.5.0:
+ dependencies:
+ '@kurkle/color': 0.3.4
+
chokidar@3.6.0:
dependencies:
anymatch: 3.1.3
@@ -8159,6 +8185,11 @@ snapshots:
quick-lru@5.1.1: {}
+ react-chartjs-2@5.3.0(chart.js@4.5.0)(react@18.3.1):
+ dependencies:
+ chart.js: 4.5.0
+ react: 18.3.1
+
react-composer@5.0.3(react@18.3.1):
dependencies:
prop-types: 15.8.1