Skip to content

Commit

Permalink
graph viz layout + styling improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
CiaranMn committed Sep 19, 2024
1 parent f719ac4 commit 6115420
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 318 deletions.
1 change: 0 additions & 1 deletion apps/hash-frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -153,4 +153,3 @@
"node": ">= v20"
}
}

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { SigmaContainer } from "@react-sigma/core";
import { NodeBorderProgram } from "@sigma/node-border";
import { createNodeBorderProgram } from "@sigma/node-border";
import { MultiDirectedGraph } from "graphology";
import { useState } from "react";

Expand Down Expand Up @@ -28,10 +28,28 @@ export const GraphContainer = ({
<SigmaContainer
graph={MultiDirectedGraph}
settings={{
/**
* @see {@link useDefaultSettings} for more settings
*/
/**
* These settings need to be set before the graph is rendered.
*/
defaultNodeType: "bordered",
nodeProgramClasses: {
bordered: NodeBorderProgram,
bordered: createNodeBorderProgram({
borders: [
{
size: { value: 2, mode: "pixels" },
color: { attribute: "borderColor" },
},
{ size: { fill: true }, color: { attribute: "color" } },
],
}),
},
/**
* This setting is dependent on props, and is easiest to set here.
*/
enableEdgeEvents: !!onEdgeClick,
}}
>
<FullScreenButton />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ export const GraphDataLoader = ({

graphState.current.hoveredNeighborIds = getNeighbors(event.node);

sigma.setSetting("renderEdgeLabels", true);

/**
* We haven't touched the graph data, so don't need to re-index.
* An additional optimization would be to supply partialGraph here and only redraw the affected nodes,
Expand All @@ -105,6 +107,7 @@ export const GraphDataLoader = ({
const removeHighlights = () => {
graphState.current.hoveredNodeId = null;
graphState.current.hoveredNeighborIds = null;
sigma.setSetting("renderEdgeLabels", false);
sigma.refresh({ skipIndexation: true });
};

Expand Down Expand Up @@ -178,6 +181,8 @@ export const GraphDataLoader = ({

for (const edge of edges) {
graph.addEdgeWithKey(edge.edgeId, edge.source, edge.target, {
color: "rgba(230, 230, 230, 1)",
label: edge.label,
size: edge.size,
type: "arrow",
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ export const labelRenderedSizeThreshold = {
normal: 16,
};

/**
* See also {@link GraphContainer} for additional settings
*/
export const useDefaultSettings = (state: GraphState) => {
const { palette } = useTheme();
const sigma = useSigma();
Expand All @@ -31,6 +34,13 @@ export const useDefaultSettings = (state: GraphState) => {
*/
sigma.setSetting("labelDensity", 1);

/**
* Edge labels are only shown on hover, controlled in the event handlers.
*/
sigma.setSetting("edgeLabelColor", { color: "rgba(80, 80, 80, 0.6)" });
sigma.setSetting("edgeLabelFont", `"Inter", "Helvetica", "sans-serif"`);
sigma.setSetting("edgeLabelSize", 10);

/**
* Controls what labels will be shown at which zoom levels.
*/
Expand Down Expand Up @@ -104,9 +114,14 @@ export const useDefaultSettings = (state: GraphState) => {
if (state.hoveredNodeId !== node && !state.hoveredNeighborIds.has(node)) {
/**
* Nodes are always drawn over edges by the library, so anything other than hiding non-highlighted nodes
* means that they can obscure the highlighted edges.
* means that they can obscure the highlighted edges, as is the case here.
*
* If they are hidden, it is much more jarring when hovering over nodes because of the rest of the graph
* fully disappears, so having the 'non-highlighted' nodes remain like this is a UX compromise.
*/
nodeData.hidden = true;
nodeData.color = "rgba(170, 170, 170, 0.7)";
nodeData.borderColor = "rgba(170, 170, 170, 0.7)";
nodeData.label = "";
} else {
nodeData.forceLabel = true;
}
Expand Down Expand Up @@ -147,8 +162,12 @@ export const useDefaultSettings = (state: GraphState) => {
}
}

edgeData.hidden = !(sourceIsShown && targetIsShown);
edgeData.zIndex = 2;
if (sourceIsShown && targetIsShown) {
edgeData.zIndex = 2;
edgeData.forceLabel = true;
} else {
edgeData.hidden = true;
}

return edgeData;
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useSigma } from "@react-sigma/core";
import forceAtlas2 from "graphology-layout-forceatlas2";
import FA2Layout from "graphology-layout-forceatlas2/worker";
import { useCallback } from "react";

export const useLayout = () => {
Expand All @@ -13,19 +14,31 @@ export const useLayout = () => {
*/
const settings = forceAtlas2.inferSettings(graph);

forceAtlas2.assign(sigma.getGraph(), {
/**
* How many iterations to run the layout algorithm for.
*/
iterations: 20,
const forceAtlasLayout = new FA2Layout(graph, {
/**
* @see https://graphology.github.io/standard-library/layout-forceatlas2.html
* @see https://observablehq.com/@mef/forceatlas2-layout-settings-visualized
*/
settings: {
...settings,
outboundAttractionDistribution: true,
gravity: 2.5,
},
});

forceAtlasLayout.start();

setInterval(() => {
/**
* The layout process will stop automatically if the algorithm has converged, but this is not guaranteed to happen.
* A few seconds seems sufficient for graphs of ~10k items (nodes and edges).
*/
forceAtlasLayout.stop();
}, 4_000);

return () => {
forceAtlasLayout.kill();
};
}, [sigma]);

return layout;
Expand Down
Loading

0 comments on commit 6115420

Please sign in to comment.