export function SetKnowledgeGraph({
graphRef,
graphComponentRef,
data,
containerId,
zoomValues
}: IProps) {
const graph = graphRef.current;
const graphComponent = graphComponentRef.current;
if (!graph || !graphComponent) {
return;
}
// Clear the graph
graph.clear();
const { width, height } = CalculateLayoutOptions(containerId, zoomValues);
// Set default node and edge styles
graph.nodeDefaults.style = new HtmlEditableNodeStyle(PerspectiveItem);
graph.nodeDefaults.size = [width, height];
graph.edgeDefaults.style = new PolylineEdgeStyle({
stroke: '1px #3F3F46',
targetArrow: new Arrow({
fill: '#3F3F46',
stroke: '#3F3F46',
type: ArrowType.TRIANGLE
})
});
// Extract topics, documents, and edges
const topics = data.items.filter(i => hasItemLabel(i, 'Topic'));
const documents = data.documents.map(doc => ({
...doc,
graph,
relations: data.relations.filter(d => d.documentId === doc.id)
}));
let edges = data.relations
.map(item => ({
from: item.documentId,
to: item.itemId,
label: ''
}))
.filter(item => item.from !== item.to);
const filtered = filteredItem.get();
const itemId = showItemModal.get()?.item?.id ?? filtered;
if (filtered || itemId) {
// Remove edges that are not related to the selected item
edges = edges.filter(item => item.from === itemId || item.to === itemId);
}
const graphBuilder = new GraphBuilder(graph);
graphBuilder.createNodesSource(topics, 'id');
graphBuilder.createNodesSource(documents, 'id');
graphBuilder.createNodesSource(data.subPerspectives, 'id');
graphBuilder.createEdgesSource(edges, 'from', 'to');
graphBuilder.buildGraph();
// Set up the layout and layering
graphComponent.inputMode = new GraphViewerInputMode({});
const layout = new HierarchicLayout();
const layoutData = new HierarchicLayoutData();
layout.orthogonalRouting = true;
layout.automaticEdgeGrouping = true;
layout.backLoopRouting = false;
const layering = layoutData.layerConstraints;
const graphNodes = graphComponent.graph.nodes.toArray();
const documentLayer = 0;
const parentTopicLayer = 1;
const activeTopicLayer = 2;
let topicLayer = 3;
let index = 0;
if (itemId || filtered) {
graphNodes.forEach(node => {
const nodeTag = node.tag;
if (hasItemLabel(nodeTag, 'Document')) {
nodeTag.layer = documentLayer;
} else if (hasItemLabel(nodeTag, 'Topic')) {
if (nodeTag.id === itemId) {
nodeTag.layer = activeTopicLayer;
} else {
const incomingEdge = edges.find(e => e.from === nodeTag.id);
const outgoingEdge = edges.find(e => e.to === nodeTag.id);
if (incomingEdge) {
nodeTag.layer = parentTopicLayer;
} else if (outgoingEdge) {
nodeTag.layer = topicLayer;
index++;
if (index % 5 === 0) {
topicLayer++;
}
}
}
} else {
nodeTag.layer = 0;
}
});
graphNodes.forEach(node => {
layering.nodeComparables.mapper.set(node, node.tag.layer);
});
}
// Set up bus descriptors
const busDescriptor = new HierarchicLayoutBusDescriptor();
const busIncomingEdges = graphComponent.graph.edges.filter(e => e.tag.to === itemId).toArray();
if (busIncomingEdges.length > 0) {
const docDescriptor = new HierarchicLayoutBusDescriptor();
const topicDescriptor = new HierarchicLayoutBusDescriptor();
const docEdges = busIncomingEdges.filter(e =>
graphComponent.graph.nodes.find(n => n.tag.id === e.tag.from && hasItemLabel(n.tag, 'Document'))
);
const topicEdges = busIncomingEdges.filter(e =>
graphComponent.graph.nodes.find(n => n.tag.id === e.tag.from && hasItemLabel(n.tag, 'Topic'))
);
if (docEdges.length > 0) {
layoutData.buses.add(docDescriptor).items = docEdges;
}
if (topicEdges.length > 0) {
layoutData.buses.add(topicDescriptor).items = topicEdges;
}
}
const busEdges = graphComponent.graph.edges.filter(e => e.tag.from === itemId).toArray();
if (busEdges.length > 0) {
layoutData.buses.add(busDescriptor).items = busEdges;
}
// Apply layout to the graph
graphComponent.graph.applyLayout(layout, layoutData);
// Fit the graph bounds
const limiter = graphComponent.viewportLimiter;
limiter.honorBothDimensions = true;
limiter.bounds = null;
graphComponent.fitGraphBounds();
}