From a9c2508d3e295ad8ba157df4e1b34eb43a337743 Mon Sep 17 00:00:00 2001 From: imsarang Date: Sun, 10 May 2026 22:28:45 +0530 Subject: [PATCH] issue-1730 --- .../src/modules/GraphViewer/GraphViewer.tsx | 36 ++++++++-- .../filterLegendVertexTypeConfigs.test.ts | 67 +++++++++++++++++++ .../filterLegendVertexTypeConfigs.ts | 22 ++++++ 3 files changed, 120 insertions(+), 5 deletions(-) create mode 100644 packages/graph-explorer/src/modules/GraphViewer/filterLegendVertexTypeConfigs.test.ts create mode 100644 packages/graph-explorer/src/modules/GraphViewer/filterLegendVertexTypeConfigs.ts diff --git a/packages/graph-explorer/src/modules/GraphViewer/GraphViewer.tsx b/packages/graph-explorer/src/modules/GraphViewer/GraphViewer.tsx index f5978abf7..8ed7f1a3b 100644 --- a/packages/graph-explorer/src/modules/GraphViewer/GraphViewer.tsx +++ b/packages/graph-explorer/src/modules/GraphViewer/GraphViewer.tsx @@ -4,6 +4,7 @@ import { Activity, type ComponentPropsWithRef, type MouseEvent, + useMemo, useState, } from "react"; @@ -38,9 +39,12 @@ import { createRenderedVertexId, getEdgeIdFromRenderedEdgeId, getVertexIdFromRenderedVertexId, + type DisplayVertex, + type DisplayVertexTypeConfig, type RenderedEdgeId, type RenderedVertex, type RenderedVertexId, + useDisplayVerticesInCanvas, useDisplayVertexTypeConfigs, useRenderedEdges, useRenderedVertices, @@ -52,6 +56,7 @@ import { useDefaultNeighborExpansionLimit } from "@/hooks/useExpandNode"; import { cn, isVisible } from "@/utils"; import { ExportGraphButton } from "./ExportGraphButton"; +import { filterVertexTypeConfigsForCanvasVertices } from "./filterLegendVertexTypeConfigs"; import { GraphViewerEmptyState } from "./GraphViewerEmptyState"; import { ImportGraphButton } from "./ImportGraphButton"; import ContextMenu from "./internalComponents/ContextMenu"; @@ -150,6 +155,22 @@ function GraphViewerContent({ const nodes = useRenderedVertices(); const edges = useRenderedEdges(); + const allVertexTypeConfigs = useDisplayVertexTypeConfigs().values().toArray(); + const displayVerticesInCanvas = useDisplayVerticesInCanvas(); + + const legendVertexTypeConfigs = useMemo(() => { + const visibleVertices: DisplayVertex[] = []; + for (const node of nodes) { + const vertex = displayVerticesInCanvas.get(node.data.vertexId); + if (vertex != null) { + visibleVertices.push(vertex); + } + } + return filterVertexTypeConfigsForCanvasVertices( + allVertexTypeConfigs, + visibleVertices, + ); + }, [allVertexTypeConfigs, displayVerticesInCanvas, nodes]); const isEmpty = !nodes.length && !edges.length; @@ -224,7 +245,10 @@ function GraphViewerContent({
- setLegendOpen(false)} /> + setLegendOpen(false)} + />
@@ -235,11 +259,13 @@ function GraphViewerContent({ function Legend({ onClose, + vertexTypeConfigs, className, ...props -}: { onClose: () => void } & ComponentPropsWithRef) { - const vtConfigs = useDisplayVertexTypeConfigs().values().toArray(); - +}: { + onClose: () => void; + vertexTypeConfigs: DisplayVertexTypeConfig[]; +} & ComponentPropsWithRef) { return ( @@ -250,7 +276,7 @@ function Legend({
    - {vtConfigs.map(vtConfig => ( + {vertexTypeConfigs.map(vtConfig => (
  • { + it("keeps only configs whose type appears on a canvas vertex", () => { + const allConfigs: DisplayVertexTypeConfig[] = [ + configForType("airport"), + configForType("country"), + ]; + const canvasVertices = [ + { types: [createVertexType("airport")] }, + ] as DisplayVertex[]; + + const result = filterVertexTypeConfigsForCanvasVertices( + allConfigs, + canvasVertices, + ); + + expect(result).toHaveLength(1); + expect(result[0]?.type).toBe(allConfigs[0]?.type); + }); + + it("includes types from every label on a multi-label vertex", () => { + const allConfigs: DisplayVertexTypeConfig[] = [ + configForType("person"), + configForType("worker"), + configForType("company"), + ]; + const canvasVertices = [ + { + types: [createVertexType("person"), createVertexType("worker")], + }, + ] as DisplayVertex[]; + + const result = filterVertexTypeConfigsForCanvasVertices( + allConfigs, + canvasVertices, + ); + + expect(result.map(c => c.type)).toEqual([ + allConfigs[0]?.type, + allConfigs[1]?.type, + ]); + }); + + it("returns an empty list when the canvas has no vertices", () => { + const allConfigs: DisplayVertexTypeConfig[] = [configForType("airport")]; + + const result = filterVertexTypeConfigsForCanvasVertices(allConfigs, []); + + expect(result).toEqual([]); + }); +}); diff --git a/packages/graph-explorer/src/modules/GraphViewer/filterLegendVertexTypeConfigs.ts b/packages/graph-explorer/src/modules/GraphViewer/filterLegendVertexTypeConfigs.ts new file mode 100644 index 000000000..f62bf6c02 --- /dev/null +++ b/packages/graph-explorer/src/modules/GraphViewer/filterLegendVertexTypeConfigs.ts @@ -0,0 +1,22 @@ +import type { + DisplayVertex, + DisplayVertexTypeConfig, + VertexType, +} from "@/core"; + +/** + * Keeps legend rows only for vertex types that appear on at least one canvas vertex + * (including every label on multi-label vertices). + */ +export function filterVertexTypeConfigsForCanvasVertices( + allConfigs: DisplayVertexTypeConfig[], + canvasVertices: DisplayVertex[], +): DisplayVertexTypeConfig[] { + const visibleTypes = new Set(); + for (const vertex of canvasVertices) { + for (const type of vertex.types) { + visibleTypes.add(type); + } + } + return allConfigs.filter(config => visibleTypes.has(config.type)); +}