diff --git a/.changeset/cuddly-sails-attend.md b/.changeset/cuddly-sails-attend.md
new file mode 100644
index 000000000..013a2eb37
--- /dev/null
+++ b/.changeset/cuddly-sails-attend.md
@@ -0,0 +1,5 @@
+---
+"@viamrobotics/motion-tools": patch
+---
+
+Feat: force directed graph labels
diff --git a/eslint.config.js b/eslint.config.js
index dd2bd68b0..b5ffbec77 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -123,6 +123,7 @@ export default defineConfig(
'unicorn/prefer-add-event-listener': 'off',
'unicorn/prefer-blob-reading-methods': 'off',
'unicorn/prefer-code-point': 'off',
+ 'unicorn/prefer-modern-math-apis': 'off',
'unicorn/prefer-string-replace-all': 'error',
'unicorn/prefer-switch': 'off',
'unicorn/prefer-top-level-await': 'off',
diff --git a/package.json b/package.json
index c3bcbd53a..2c7f92ed2 100644
--- a/package.json
+++ b/package.json
@@ -67,6 +67,7 @@
"@threlte/rapier": "3.4.1",
"@threlte/xr": "1.6.0",
"@types/bun": "1.2.21",
+ "@types/d3-force": "^3.0.10",
"@types/earcut": "^3.0.0",
"@types/lodash-es": "4.17.12",
"@types/node": "^25.6.0",
@@ -209,6 +210,7 @@
"dependencies": {
"@bufbuild/protobuf": "1.10.1",
"@neodrag/svelte": "^2.3.3",
+ "d3-force": "^3.0.0",
"filtrex": "^3.1.0",
"koota": "0.6.5",
"lodash-es": "4.18.1",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index a947c765f..ee000199e 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -21,6 +21,9 @@ importers:
'@zag-js/dialog':
specifier: '>=1.31'
version: 1.32.0
+ d3-force:
+ specifier: ^3.0.0
+ version: 3.0.0
filtrex:
specifier: ^3.1.0
version: 3.1.0
@@ -121,6 +124,9 @@ importers:
'@types/bun':
specifier: 1.2.21
version: 1.2.21(@types/react@19.2.2)
+ '@types/d3-force':
+ specifier: ^3.0.10
+ version: 3.0.10
'@types/earcut':
specifier: ^3.0.0
version: 3.0.0
@@ -1787,6 +1793,9 @@ packages:
'@types/cookie@0.6.0':
resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==}
+ '@types/d3-force@3.0.10':
+ resolution: {integrity: sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==}
+
'@types/deep-eql@4.0.2':
resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
@@ -2458,6 +2467,22 @@ packages:
csstype@3.2.3:
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
+ d3-dispatch@3.0.1:
+ resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==}
+ engines: {node: '>=12'}
+
+ d3-force@3.0.0:
+ resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==}
+ engines: {node: '>=12'}
+
+ d3-quadtree@3.0.1:
+ resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==}
+ engines: {node: '>=12'}
+
+ d3-timer@3.0.1:
+ resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==}
+ engines: {node: '>=12'}
+
data-urls@5.0.0:
resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==}
engines: {node: '>=18'}
@@ -5858,6 +5883,8 @@ snapshots:
'@types/cookie@0.6.0': {}
+ '@types/d3-force@3.0.10': {}
+
'@types/deep-eql@4.0.2': {}
'@types/earcut@3.0.0': {}
@@ -6750,6 +6777,18 @@ snapshots:
csstype@3.2.3: {}
+ d3-dispatch@3.0.1: {}
+
+ d3-force@3.0.0:
+ dependencies:
+ d3-dispatch: 3.0.1
+ d3-quadtree: 3.0.1
+ d3-timer: 3.0.1
+
+ d3-quadtree@3.0.1: {}
+
+ d3-timer@3.0.1: {}
+
data-urls@5.0.0:
dependencies:
whatwg-mimetype: 4.0.0
diff --git a/src/lib/components/Entities/Entities.svelte b/src/lib/components/Entities/Entities.svelte
index 55108e44e..95ae41608 100644
--- a/src/lib/components/Entities/Entities.svelte
+++ b/src/lib/components/Entities/Entities.svelte
@@ -2,12 +2,13 @@
import { Not, Or } from 'koota'
import { traits, useQuery } from '$lib/ecs'
+ import { useSettings } from '$lib/hooks/useSettings.svelte'
import Arrows from './Arrows/ArrowGroups.svelte'
import Frame from './Frame.svelte'
import Geometry from './Geometry.svelte'
import GLTF from './GLTF.svelte'
- import Label from './Label.svelte'
+ import Labels from './Labels.svelte'
import Line from './Line.svelte'
import Points from './Points.svelte'
import Pose from './Pose.svelte'
@@ -60,56 +61,48 @@
const points = useQuery(traits.Points)
const lines = useQuery(traits.LinePositions)
const gltfs = useQuery(traits.GLTF)
+
+ const settings = useSettings()
+
+ const enableLabels = $derived(settings.current.enableLabels)
{#each machineFramesEntities.current as entity (entity)}