From 800b0977a5bc3099f8b64292f8e8a6048111365a Mon Sep 17 00:00:00 2001 From: Benoit Simard Date: Mon, 9 Feb 2026 13:58:34 +0100 Subject: [PATCH] [layout] fixed node for FA2 --- .../core/layouts/collection/forceAtlas2.ts | 7 +++++ packages/gephi-lite/src/core/layouts/index.ts | 27 ++++++++++++++++--- packages/gephi-lite/src/locales/dev.json | 3 +++ packages/gephi-lite/src/locales/fr.json | 3 +++ .../controllers/EventsController.tsx | 13 ++++++--- packages/sdk/src/graph/types.ts | 1 + 6 files changed, 47 insertions(+), 7 deletions(-) diff --git a/packages/gephi-lite/src/core/layouts/collection/forceAtlas2.ts b/packages/gephi-lite/src/core/layouts/collection/forceAtlas2.ts index 657b08b5..826d14ed 100644 --- a/packages/gephi-lite/src/core/layouts/collection/forceAtlas2.ts +++ b/packages/gephi-lite/src/core/layouts/collection/forceAtlas2.ts @@ -76,5 +76,12 @@ export const ForceAtlas2Layout = { }, { id: "slowDown", type: "number", defaultValue: FA2_DEFAULT_SETTINGS.slowDown, min: 1, step:"any"}, { id: "strongGravityMode", type: "boolean", defaultValue: FA2_DEFAULT_SETTINGS.strongGravityMode }, + { + id: "getNodeFixedAttribut", + type: "attribute", + itemType: "nodes", + restriction: ["boolean"], + required: false, + }, ], } as WorkerLayout; diff --git a/packages/gephi-lite/src/core/layouts/index.ts b/packages/gephi-lite/src/core/layouts/index.ts index 765343fd..efc21880 100644 --- a/packages/gephi-lite/src/core/layouts/index.ts +++ b/packages/gephi-lite/src/core/layouts/index.ts @@ -59,10 +59,11 @@ export const stopLayout = asyncAction(async (isForRestart = false) => { if (!isForRestart) layoutStateAtom.set((prev) => ({ ...prev, type: "idle" })); }); -export const startLayout = asyncAction(async (id: string, params: unknown, isForRestart = false) => { +export const startLayout = asyncAction(async (id: string, params: Record, isForRestart = false) => { // Stop the previous algo (the "if needed" is done in the function itself) await stopLayout(isForRestart); + const dataset = graphDatasetAtom.get(); const { setNodePositions } = graphDatasetActions; // search the layout @@ -74,7 +75,6 @@ export const startLayout = asyncAction(async (id: string, params: unknown, isFor layoutStateAtom.set((prev) => ({ ...prev, type: "running", layoutId: id, supervisor: undefined })); // generate positions - const dataset = graphDatasetAtom.get(); const fullGraph = dataGraphToFullGraph(dataset); const positions = layout.run(fullGraph, { settings: params }); @@ -91,7 +91,28 @@ export const startLayout = asyncAction(async (id: string, params: unknown, isFor // Async layout if (layout.type === "worker") { - const worker = new layout.supervisor(sigmaGraphAtom.get(), { settings: params }); + const graph = sigmaGraphAtom.get(); + + // Fixed node management + // --------------------- + // If layout parameter has a `getNodeFixedAttribut`, then we have to set the 'fixed' attribut in sigma's graph + // On a layout restart, if parameter has been removed, we need to set to false + // We also use the 'fixed' attribut for drag'n'drop + graph.updateEachNodeAttributes((id, attrs) => { + let fixed = attrs.dragging === true; + if ("getNodeFixedAttribut" in params && params.getNodeFixedAttribut) { + const fixedAttribut = `${params.getNodeFixedAttribut}`; + if (dataset.nodeData[id][fixedAttribut] === true) { + fixed = true; + } + } + return { + ...attrs, + fixed, + }; + }); + + const worker = new layout.supervisor(graph, { settings: params }); worker.start(); layoutStateAtom.set((prev) => ({ ...prev, type: "running", layoutId: id, supervisor: worker })); } diff --git a/packages/gephi-lite/src/locales/dev.json b/packages/gephi-lite/src/locales/dev.json index 47f216cd..e97b042a 100644 --- a/packages/gephi-lite/src/locales/dev.json +++ b/packages/gephi-lite/src/locales/dev.json @@ -539,6 +539,9 @@ "description": "Influence of the edge’s weights on the layout", "title": "Edge weight influence" }, + "getNodeFixedAttribut": { + "title": "Attribute indicating whether the node’s position is fixed" + }, "gravity": { "description": "Strength of the layout’s gravity", "title": "Gravity" diff --git a/packages/gephi-lite/src/locales/fr.json b/packages/gephi-lite/src/locales/fr.json index a8be512e..7bceb965 100644 --- a/packages/gephi-lite/src/locales/fr.json +++ b/packages/gephi-lite/src/locales/fr.json @@ -613,6 +613,9 @@ "description": "Influence des poids des liens sur la spatialisation", "title": "Influence du poids des liens" }, + "getNodeFixedAttribut": { + "title": "Attribut indiquant si la position du nœud est fixée" + }, "gravity": { "description": "Intensité de la gravité de la spatialisation", "title": "Gravité" diff --git a/packages/gephi-lite/src/views/graphPage/controllers/EventsController.tsx b/packages/gephi-lite/src/views/graphPage/controllers/EventsController.tsx index 48e5fea0..da46a9a2 100644 --- a/packages/gephi-lite/src/views/graphPage/controllers/EventsController.tsx +++ b/packages/gephi-lite/src/views/graphPage/controllers/EventsController.tsx @@ -96,8 +96,8 @@ export const EventsController: FC = () => { const initialNodesPosition: LayoutMapping = {}; nodes.forEach((node) => { - // Fixing the node position, it's needed while a layout is running - graph.setNodeAttribute(node, "fixed", true); + // Set dragging prop on node, which fix the node + graph.setNodeAttribute(node, "dragging", true); const { x, y } = graph.getNodeAttributes(node); initialNodesPosition[node] = { x, y }; }); @@ -131,7 +131,7 @@ export const EventsController: FC = () => { for (const node in dragState.initialNodesPosition) { const initialPosition = dragState.initialNodesPosition[node]; - graph.setNodeAttribute(node, "fixed", true); + graph.setNodeAttribute(node, "dragging", true); graph.setNodeAttribute(node, "x", initialPosition.x + delta.x); graph.setNodeAttribute(node, "y", initialPosition.y + delta.y); } @@ -162,7 +162,12 @@ export const EventsController: FC = () => { resetHoveredNode(); resetHoveredEdge(); } - graph.forEachNode((node) => graph.setNodeAttribute(node, "fixed", false)); + // Remove the dragging state on each node + graph.forEachNode((node) => graph.setNodeAttribute(node, "dragging", false)); + // Emit the last dragged event when the dragging prop is removed + // so the algo can restart with the good data + globalEmitter.emit(EVENTS.nodesDragged); + // Update drag status dragStateRef.current = { type: "idle" }; } }; diff --git a/packages/sdk/src/graph/types.ts b/packages/sdk/src/graph/types.ts index 8d8b9f2a..556b337c 100644 --- a/packages/sdk/src/graph/types.ts +++ b/packages/sdk/src/graph/types.ts @@ -40,6 +40,7 @@ export type NodeRenderingData = Attributes & rawSize?: number; image?: string | null; fixed?: boolean; + dragging?:boolean; }; export interface GraphMetadata {