diff --git a/packages/graph-explorer/src/core/StateProvider/graphStyles.ts b/packages/graph-explorer/src/core/StateProvider/graphStyles.ts index 8026c7a37..5e6fc1238 100644 --- a/packages/graph-explorer/src/core/StateProvider/graphStyles.ts +++ b/packages/graph-explorer/src/core/StateProvider/graphStyles.ts @@ -58,12 +58,23 @@ export const ARROW_STYLES = [ ] as const; export type ArrowStyle = (typeof ARROW_STYLES)[number]; +/** The visual appearance of a label badge (shared by edge and vertex labels). */ +export type LabelVisualStyle = { + labelColor: string; + labelBackgroundOpacity: number; + labelBorderColor: string; + labelBorderStyle: LineStyle; + labelBorderWidth: number; +}; + /** * The visual appearance of a vertex — the fields that make sense for both a * per-type style and a type-less global default. Every field is required: this - * is the resolved baseline shape a rendered vertex always has. + * is the resolved baseline shape a rendered vertex always has. Includes + * {@link LabelVisualStyle} so a vertex type can style its own label badge, the + * same way an edge type already can (see {@link EdgeVisualStyle}). */ -export type VertexVisualStyle = { +export type VertexVisualStyle = LabelVisualStyle & { /** Color overwrite for vertex */ color: string; /** Icon overwrite for vertex */ @@ -91,15 +102,6 @@ export type VertexTypeStyle = { longDisplayNameAttribute: string; }; -/** The visual appearance of a label badge (shared by edge and vertex labels). */ -export type LabelVisualStyle = { - labelColor: string; - labelBackgroundOpacity: number; - labelBorderColor: string; - labelBorderStyle: LineStyle; - labelBorderWidth: number; -}; - /** The visual appearance of an edge, shared by per-type styles and defaults. */ export type EdgeVisualStyle = LabelVisualStyle & { lineColor: string; @@ -135,7 +137,13 @@ export type EdgeStyle = Simplify< Readonly >; -/** The default values to use when no user provided value is given. */ +/** + * The default values to use when no user provided value is given. Label-badge + * fields mirror the node label config in + * `components/Graph/styles/defaultNodeStyle.ts` (`text.background` → + * `labelColor`, `text.opacity` → `labelBackgroundOpacity`) — keep in sync if + * that canvas style changes. + */ export const appDefaultVertexStyle = { displayNameAttribute: RESERVED_ID_PROPERTY, longDisplayNameAttribute: RESERVED_TYPES_PROPERTY, @@ -147,23 +155,20 @@ export const appDefaultVertexStyle = { borderWidth: 0, borderColor: "#128EE5", borderStyle: "solid", -} as const satisfies Omit; - -/** - * The default appearance of a node's label badge. Nodes have no per-type label - * styling (see {@link VertexVisualStyle}), so this is the single source for how - * a node label looks. Values mirror the node label config in - * `components/Graph/styles/defaultNodeStyle.ts` (`text.background` → - * `labelColor`, `text.opacity` → `labelBackgroundOpacity`) — keep in sync if - * that canvas style changes. - */ -export const appDefaultNodeLabelStyle = { labelColor: "#1d2531", labelBackgroundOpacity: 0.7, labelBorderColor: "#1d2531", labelBorderStyle: "solid", labelBorderWidth: 0, -} as const satisfies LabelVisualStyle; +} as const satisfies Omit; + +/** + * The default appearance of a node's label badge, for preview/legend UI that + * needs just the label fields (e.g. `VertexPreview.tsx`) — derived from + * {@link appDefaultVertexStyle}, which is now the single source of truth for + * a vertex type's label styling (see {@link VertexVisualStyle}). + */ +export const appDefaultNodeLabelStyle: LabelVisualStyle = appDefaultVertexStyle; /** The default values to use when no user provided value is given. */ export const appDefaultEdgeStyle = { diff --git a/packages/graph-explorer/src/core/styling/stylingParser.ts b/packages/graph-explorer/src/core/styling/stylingParser.ts index b38e2348d..244088fdf 100644 --- a/packages/graph-explorer/src/core/styling/stylingParser.ts +++ b/packages/graph-explorer/src/core/styling/stylingParser.ts @@ -138,6 +138,11 @@ const vertexEntrySchema = z borderWidth: z.number().optional(), borderColor: z.string().optional(), borderStyle: z.enum(LINE_STYLES).optional(), + labelColor: z.string().optional(), + labelBackgroundOpacity: z.number().optional(), + labelBorderColor: z.string().optional(), + labelBorderStyle: z.enum(LINE_STYLES).optional(), + labelBorderWidth: z.number().optional(), }) .transform( ({ icon, ...rest }): Omit => diff --git a/packages/graph-explorer/src/modules/GraphViewer/useGraphStyles.test.tsx b/packages/graph-explorer/src/modules/GraphViewer/useGraphStyles.test.tsx index 33da0ec8a..871f9adc5 100644 --- a/packages/graph-explorer/src/modules/GraphViewer/useGraphStyles.test.tsx +++ b/packages/graph-explorer/src/modules/GraphViewer/useGraphStyles.test.tsx @@ -66,6 +66,47 @@ describe("useGraphStyles", () => { shape: "ellipse", width: 24, height: 24, + color: "#FFFFFF", + "text-background-color": "#1d2531", + "text-background-opacity": 0.7, + "text-border-color": "#1d2531", + "text-border-style": "solid", + "text-border-width": 0, + }); + }); + }); + + it("should apply a vertex type's own label-badge style, not just the app default", async () => { + const vertexConfig = { + ...createRandomVertexTypeConfig(), + ...RASTER_ICON, + type: createVertexType("Person"), + color: "#128EE5", + backgroundOpacity: 0.8, + borderColor: "#000000", + borderWidth: 2, + borderStyle: "solid" as const, + shape: "ellipse" as const, + labelColor: "#f8fafc", + labelBackgroundOpacity: 0.94, + labelBorderColor: "#cbd5e1", + labelBorderWidth: 1, + labelBorderStyle: "solid" as const, + }; + dbState.activeSchema.vertices = [vertexConfig]; + dbState.addVertexStyle(vertexConfig.type, vertexConfig); + + const { result } = renderHookWithState(() => useGraphStyles(), dbState); + + await waitFor(() => { + const vertexStyle = getStyles(result)[`node[type="Person"]`] as any; + expect(vertexStyle).toMatchObject({ + color: "#000000", // Black text for a light background + "text-background-color": "#f8fafc", + "text-background-opacity": 0.94, + "text-border-color": "#cbd5e1", + "text-border-width": 1, + "text-border-style": "solid", }); }); }); diff --git a/packages/graph-explorer/src/modules/GraphViewer/useGraphStyles.ts b/packages/graph-explorer/src/modules/GraphViewer/useGraphStyles.ts index 869ded042..f6b1316b9 100644 --- a/packages/graph-explorer/src/modules/GraphViewer/useGraphStyles.ts +++ b/packages/graph-explorer/src/modules/GraphViewer/useGraphStyles.ts @@ -58,6 +58,12 @@ function createGraphStyles( shape: vtConfig.shape, width: 24, height: 24, + color: new Color(vtConfig.labelColor).isDark() ? "#FFFFFF" : "#000000", + "text-background-opacity": vtConfig.labelBackgroundOpacity, + "text-background-color": vtConfig.labelColor, + "text-border-width": vtConfig.labelBorderWidth, + "text-border-color": vtConfig.labelBorderColor, + "text-border-style": vtConfig.labelBorderStyle, }; }