Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | 1x 1x 1x 1x 1x 1x 1x 6x 1x 10x 10x 2x 1x 4x 5x 3x 3x 2x 2x 3x 3x 2x 2x 3x | /**
* Forces de la simulation 3D — transposition des réglages de `GraphView` (2D, `d3-force`).
*
* 2D (`GraphView.jsx`) 3D (ici)
* ───────────────────────────────────── ─────────────────────────────────────────────
* forceLink.distance(70 + libellés / 2) link.distance(linkDistance)
* forceLink.strength(0.3) link.strength(0.3)
* forceManyBody(-320, distanceMax 700) charge.strength(-320).distanceMax(900)
* forceCenter(cx, cy) center (fourni par three-forcegraph)
* forceCollide(nodeExtent) collide(forceCollide(nodeExtent))
* velocityDecay(0.4) / alphaDecay(0.022) props `d3VelocityDecay` / `d3AlphaDecay`
*
* Module séparé pour deux raisons : `d3-force-3d` est ESM (mockable sous jsdom) et la
* configuration se teste sur un faux objet `fg`, sans three.js.
*/
import { forceCollide } from 'd3-force-3d';
/** Distance de base entre deux nœuds liés (identique à la 2D). */
export const LINK_BASE_DISTANCE = 70;
/** Largeur moyenne d'un caractère de libellé (identique à la 2D). */
const CHAR_WIDTH = 2.4;
/** Nombre de caractères pris en compte pour l'emprise d'un libellé. */
export const LABEL_MAX_CHARS = 26;
export const CHARGE_STRENGTH = -320;
export const CHARGE_DISTANCE_MAX = 900;
export const LINK_STRENGTH = 0.3;
const COLLIDE_STRENGTH = 0.9;
/** Rayon visuel du nœud (pastille). */
export const nodeRadius = (node) => (node?.type === 'literal' ? 6 : 9);
/** Largeur approximative du libellé d'un nœud. */
export const labelWidth = (node) => {
const label = node && typeof node === 'object' ? node.label ?? node.id : '';
return Math.min(String(label ?? '').length, LABEL_MAX_CHARS) * CHAR_WIDTH;
};
/** Encombrement d'un nœud : pastille + libellé + marge. */
export const nodeExtent = (node) => nodeRadius(node) + labelWidth(node) / 2 + 6;
/**
* Distance cible d'un lien, adaptée à la largeur des libellés des deux extrémités.
* `link.source`/`link.target` sont des nœuds après initialisation, des ids avant :
* `labelWidth` tolère les deux.
*/
export const linkDistance = (link) =>
LINK_BASE_DISTANCE + labelWidth(link?.source) / 2 + labelWidth(link?.target) / 2;
/**
* Applique les forces à une instance `3d-force-graph` (exposée par `react-force-graph-3d`).
* Tolérant : une force absente ou une API partielle ne doit pas casser la vue.
*
* @param {{ d3Force?: Function }} fg instance du graphe
* @param {{ distance?: Function, extent?: Function }} [options] accesseurs surchargeables
*/
export function applyForces(fg, { distance = linkDistance, extent = nodeExtent } = {}) {
if (!fg || typeof fg.d3Force !== 'function') return;
const charge = fg.d3Force('charge');
if (charge && typeof charge.strength === 'function') {
charge.strength(CHARGE_STRENGTH);
Eif (typeof charge.distanceMax === 'function') charge.distanceMax(CHARGE_DISTANCE_MAX);
}
const link = fg.d3Force('link');
if (link && typeof link.distance === 'function') {
link.distance(distance);
Eif (typeof link.strength === 'function') link.strength(LINK_STRENGTH);
}
// Collision sur l'emprise complète (pastille + libellé) pour éviter les chevauchements.
fg.d3Force('collide', forceCollide(extent).strength(COLLIDE_STRENGTH));
}
|