Open danielpgp1012 opened 3 years ago
The following is a sample implementation of box selection for 3d-force-graph
. The code snippet emulates the 2d implementation from this example but adds the necessary components to work in 3d -- which is mostly converting between 2d/3d coordinate spaces.
Thanks much to @Alexithemia for the prior example.
import { SelectionBox } from 'three/addons/interactive/SelectionBox.js';
let graph, // your 'ForceGraph3D' instance
selectionBox, // three.js's 'SelectionBox' (see links below)
selectedNodes, // your array of selected nodes
cameraPos; // camera position
// forceGraph element is the element provided to the Force Graph Library
document.getElementById('forceGraph').addEventListener('pointerdown', (e) => {
if (e.shiftKey) {
e.preventDefault();
boxSelect = document.createElement('div');
boxSelect.id = 'boxSelect';
boxSelect.style.left = e.offsetX.toString() + 'px';
boxSelect.style.top = e.offsetY.toString() + 'px';
boxSelectStart = {
x: e.offsetX,
y: e.offsetY
};
// app element is the element just above the forceGraph element.
document.getElementById('app').appendChild(boxSelect);
// utility to convert between 2d select box coordinates and 3d graph coordinates
selectionBox = new SelectionBox(graph.camera(), graph.scene());
// window <-> graph-coords translation
selectionBox.startPoint.set(
(evt.clientX / window.innerWidth) * 2 - 1,
- (evt.clientY / window.innerHeight) * 2 + 1,
0.5,
);
// save camera position
cameraPos = graph.cameraPosition();
}
});
document.getElementById('forceGraph').addEventListener('pointermove', (e) => {
if (e.shiftKey && boxSelect) {
e.preventDefault();
if (e.offsetX < boxSelectStart.x) {
boxSelect.style.left = e.offsetX.toString() + 'px';
boxSelect.style.width = (boxSelectStart.x - e.offsetX).toString() + 'px';
} else {
boxSelect.style.left = boxSelectStart.x.toString() + 'px';
boxSelect.style.width = (e.offsetX - boxSelectStart.x).toString() + 'px';
}
if (e.offsetY < boxSelectStart.y) {
boxSelect.style.top = e.offsetY.toString() + 'px';
boxSelect.style.height = (boxSelectStart.y - e.offsetY).toString() + 'px';
} else {
boxSelect.style.top = boxSelectStart.y.toString() + 'px';
boxSelect.style.height = (e.offsetY - boxSelectStart.y).toString() + 'px';
}
// window <-> graph-coords translation
selectionBox.endPoint.set(
(evt.clientX / window.innerWidth) * 2 - 1,
- (evt.clientY / window.innerHeight) * 2 + 1,
0.5,
);
graph.cameraPosition(cameraPos);
} else if (boxSelect) {
boxSelect.remove();
}
});
document.getElementById('forceGraph').addEventListener('pointerup', (e) => {
if (e.shiftKey && boxSelect) {
e.preventDefault();
boxSelect.remove();
// window <-> graph-coords translation
selectionBox.endPoint.set(
(evt.clientX / window.innerWidth) * 2 - 1,
- (evt.clientY / window.innerHeight) * 2 + 1,
0.5,
);
// set selected nodes
// (these accessors may be specific to how I put nodes together...)
selectedNodes = new Set(selectionBox.select()
.filter((item) => item.__graphObjType === 'node')
.map((item) => item.__data));
// reset camera position var
cameraPos = undefined;
} else if (boxSelect) {
boxSelect.remove();
}
});
// CSS for box to show up on top -
#boxSelect {
position: absolute;
z-index: 300000;
border-style: dotted;
border-color: #3e74cc;
background-color: rgba(255, 255, 255, 0.5);
pointer-events: none;
}
These code snippets were adapted from the following three.js examples:
SelectionHelper
(adapted here) -- draws the actual selection box.
SelectionBox
(imported here) -- converts between window coordinates and graph coordinates.
Edit: Fix camera rather than disable controls.
As a user I find it difficult to select multiple nodes individually. I would like it to be possible to drag the mouse and select volumes with nodes or edges