Skip to content

Commit

Permalink
add new node to dragged cluster position
Browse files Browse the repository at this point in the history
  • Loading branch information
SerhiiTsybulskyi committed Nov 6, 2024
1 parent eab95c0 commit eca7172
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 1 deletion.
9 changes: 9 additions & 0 deletions src/layout/forceDirected.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { LayoutFactoryProps, LayoutStrategy } from './types';
import { buildNodeEdges } from './layoutUtils';
import { forceInABox } from './forceInABox';
import { FORCE_LAYOUTS } from './layoutProvider';
import { ClusterGroup } from '../utils/cluster';

export interface ForceDirectedLayoutInputs extends LayoutFactoryProps {
/**
Expand Down Expand Up @@ -45,6 +46,11 @@ export interface ForceDirectedLayoutInputs extends LayoutFactoryProps {
*/
clusterStrength?: number;

/**
* The clusters dragged position to reuse for the layout.
*/
clusters: Map<string, ClusterGroup>;

/**
* The type of clustering.
*/
Expand Down Expand Up @@ -102,6 +108,7 @@ export function forceDirected({
forceCharge = -700,
getNodePosition,
drags,
clusters,
clusterAttribute,
forceLayout
}: ForceDirectedLayoutInputs): LayoutStrategy {
Expand Down Expand Up @@ -156,6 +163,8 @@ export function forceDirected({
}

groupingForce = forceInABox()
// The clusters dragged position to reuse for the layout
.setClusters(clusters)
// Strength to foci
.strength(clusterStrength)
// Either treemap or force
Expand Down
17 changes: 17 additions & 0 deletions src/layout/forceInABox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
forceCollide
} from 'd3-force-3d';
import { treemap, hierarchy } from 'd3-hierarchy';
import { ClusterGroup } from '../utils/cluster';

/**
* Used for calculating clusterings of nodes.
Expand All @@ -28,6 +29,7 @@ export function forceInABox() {
let id = index;
let nodes = [];
let links = []; // needed for the force version
let clusters: Map<string, ClusterGroup>;
let tree;
let size = [100, 100];
let forceNodeSize = constant(1); // The expected node size used for computing the cluster node
Expand Down Expand Up @@ -277,6 +279,15 @@ export function forceInABox() {
checkLinksAsObjects();

net = getGroupsGraph();

// Use dragged clusters position if available
if (clusters.size > 0) {
net.nodes.forEach(n => {
n.fx = clusters.get(n.id)?.position?.x;
n.fy = clusters.get(n.id)?.position?.y;
});
}

templateForce = forceSimulation(net.nodes)
.force('x', forceX(size[0] / 2).strength(0.1))
.force('y', forceY(size[1] / 2).strength(0.1))
Expand Down Expand Up @@ -463,5 +474,11 @@ export function forceInABox() {

force.getFocis = getFocisFromTemplate;

force.setClusters = function (value: any) {
clusters = value;

return force;
};

return force;
}
52 changes: 51 additions & 1 deletion src/useGraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
} from './layout';
import { LabelVisibilityType, calcLabelVisibility } from './utils/visibility';
import { tick } from './layout/layoutUtils';
import { GraphEdge, GraphNode } from './types';
import { GraphEdge, GraphNode, InternalGraphNode } from './types';
import { buildGraph, transformGraph } from './utils/graph';
import { DragReferences, useStore } from './store';
import { getVisibleEntities } from './collapse';
Expand Down Expand Up @@ -50,6 +50,8 @@ export const useGraph = ({
layoutOverrides
}: GraphInputs) => {
const graph = useStore(state => state.graph);
const clusters = useStore(state => state.clusters);
const storedNodes = useStore(state => state.nodes);
const setClusters = useStore(state => state.setClusters);
const stateCollapsedNodeIds = useStore(state => state.collapsedNodeIds);
const setEdges = useStore(state => state.setEdges);
Expand All @@ -64,6 +66,31 @@ export const useGraph = ({
const layout = useRef<LayoutStrategy | null>(null);
const camera = useThree(state => state.camera) as PerspectiveCamera;
const dragRef = useRef<DragReferences>(drags);
const clustersRef = useRef<any>([]);

// When a new node is added, remove the dragged position of the cluster nodes to put new node in the right place
useEffect(() => {
if (!clusterAttribute) {
return;
}

const existedNodesIds = storedNodes.map(n => n.id);
const newNode = nodes.find(n => !existedNodesIds.includes(n.id));
if (newNode) {
const clusterName = newNode.data[clusterAttribute];
const cluster = clusters.get(clusterName);
const drags = { ...dragRef.current };

cluster?.nodes?.forEach(node => {
drags[node.id] = undefined;
});

dragRef.current = drags;
setDrags({
...drags
});
}
}, [storedNodes, nodes, clusterAttribute, clusters, setDrags]);

// Calculate the visible entities
const { visibleEdges, visibleNodes } = useMemo(
Expand All @@ -76,6 +103,20 @@ export const useGraph = ({
[stateCollapsedNodeIds, nodes, edges]
);

const updateDrags = useCallback(
(nodes: InternalGraphNode[]) => {
const drags = { ...dragRef.current };
nodes.forEach(node => {
drags[node.id] = node;
});
dragRef.current = drags;
setDrags({
...drags
});
},
[setDrags]
);

const updateLayout = useCallback(
async (curLayout?: any) => {
// Cache the layout provider
Expand All @@ -86,6 +127,7 @@ export const useGraph = ({
type: layoutType,
graph,
drags: dragRef.current,
clusters: clustersRef?.current,
clusterAttribute
});

Expand Down Expand Up @@ -115,6 +157,10 @@ export const useGraph = ({
setEdges(result.edges);
setNodes(result.nodes);
setClusters(clusters);
if (clusterAttribute) {
// Set drag positions for nodes to prevent them from being moved by the layout update
updateDrags(result.nodes);
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[
Expand All @@ -138,6 +184,10 @@ export const useGraph = ({
dragRef.current = drags;
}, [drags, clusterAttribute, updateLayout]);

useEffect(() => {
clustersRef.current = clusters;
}, [clusters]);

useEffect(() => {
// When the camera position/zoom changes, update the label visibility
const nodes = stateNodes.map(node => ({
Expand Down

0 comments on commit eca7172

Please sign in to comment.