From 686a0eb697509baf08e383b95cf9468ebecb8a5a Mon Sep 17 00:00:00 2001 From: Mario Juarros Date: Tue, 18 Aug 2026 16:04:53 -0600 Subject: [PATCH] feat(samples): add sample styles file and documentation for air_routes sample (#1936) --- .../src/core/styling/sampleStyles.test.ts | 51 ++++++ samples/air_routes/README.md | 14 ++ samples/air_routes/STYLING-REFERENCE.md | 168 ++++++++++++++++++ samples/air_routes/styles.json | 52 ++++++ 4 files changed, 285 insertions(+) create mode 100644 packages/graph-explorer/src/core/styling/sampleStyles.test.ts create mode 100644 samples/air_routes/STYLING-REFERENCE.md create mode 100644 samples/air_routes/styles.json diff --git a/packages/graph-explorer/src/core/styling/sampleStyles.test.ts b/packages/graph-explorer/src/core/styling/sampleStyles.test.ts new file mode 100644 index 000000000..22840b959 --- /dev/null +++ b/packages/graph-explorer/src/core/styling/sampleStyles.test.ts @@ -0,0 +1,51 @@ +import { readFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { describe, expect, test } from "vitest"; + +import { createEdgeType, createVertexType } from "@/core/entities"; +import { parseStylingFile } from "@/core/styling"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +function asFile(contents: string, name: string): File { + return new File([contents], name, { type: "application/json" }); +} + +describe("air_routes sample styles file", () => { + test("parses and differentiates air routes vertex and edge types", async () => { + const sampleStylesPath = join( + __dirname, + "../../../../../samples/air_routes/styles.json", + ); + const sampleStyles = readFileSync(sampleStylesPath, "utf-8"); + + const parsed = await parseStylingFile(asFile(sampleStyles, "styles.json")); + + const airport = parsed.vertexStyles.get(createVertexType("airport")); + const country = parsed.vertexStyles.get(createVertexType("country")); + const continent = parsed.vertexStyles.get(createVertexType("continent")); + const route = parsed.edgeStyles.get(createEdgeType("route")); + const contains = parsed.edgeStyles.get(createEdgeType("contains")); + + expect(airport).toBeDefined(); + expect(country).toBeDefined(); + expect(continent).toBeDefined(); + expect(route).toBeDefined(); + expect(contains).toBeDefined(); + + expect(airport!.color).not.toBe(country!.color); + expect(airport!.color).not.toBe(continent!.color); + expect(country!.color).not.toBe(continent!.color); + + expect( + new Set([airport!.shape, country!.shape, continent!.shape]).size, + ).toBe(3); + + expect(airport!.iconUrl).toMatch(/^lucide:/); + + expect(route!.lineColor).not.toBe(contains!.lineColor); + expect(route!.lineStyle).not.toBe(contains!.lineStyle); + }); +}); diff --git a/samples/air_routes/README.md b/samples/air_routes/README.md index 65d3ec1a0..9e148ff32 100644 --- a/samples/air_routes/README.md +++ b/samples/air_routes/README.md @@ -37,4 +37,18 @@ This sample uses Gremlin Server 3.8 as the database pre-loaded with the [air rou ``` 4. Open the browser and navigate to: [http://localhost:8080/explorer](http://localhost:8080/explorer) +## Loading the sample styles + +1. Launch the sample and open [http://localhost:8080/explorer](http://localhost:8080/explorer). +2. Open **Settings** from the left sidebar. +3. Go to the **Styles** section. +4. Click **Load styles**. +5. Select the `samples/air_routes/styles.json` file from this repository on your machine. If you copied only `docker-compose.yaml`, download `styles.json` from the repository first: https://github.com/aws/graph-explorer/blob/main/samples/air_routes/styles.json +6. In the selective import modal, keep all five styles selected (or pick only the ones you want), then click **Load selected**. +7. Return to the **Graph** or **Schema** view. Airports, countries, and continents now render with distinct shapes, colors, and icons; routes and contains edges also have distinct line styles. + +> **Note:** The browser file picker reads from your host filesystem, not the Docker container. No volume mount is needed — just ensure `styles.json` is available on your machine before loading it. + +To customize the styles or create your own, see [STYLING-REFERENCE.md](./STYLING-REFERENCE.md) for the complete property reference and available values. + Once it is running, the [Getting Started tutorial](../../docs/getting-started/README.md) walks you through exploring the air routes data step by step. diff --git a/samples/air_routes/STYLING-REFERENCE.md b/samples/air_routes/STYLING-REFERENCE.md new file mode 100644 index 000000000..b182d9d48 --- /dev/null +++ b/samples/air_routes/STYLING-REFERENCE.md @@ -0,0 +1,168 @@ +# Styling Reference + +This document describes the styling file format used by Graph Explorer. The sample `styles.json` in this directory demonstrates a subset of these properties. + +## File format + +Styling files use the File Envelope format with `kind: "styling-export"` and `version: 1`. The payload is a JSON object with two top-level keys: `vertices` and `edges`. + +## Vertex style properties + +Each vertex type can have the following optional properties: + +- `color` (string): Hex color code, e.g., `"#128EE5"` +- `shape` (string): Node shape (see Shape values below) +- `icon` (string): Icon reference (see Icon formats below) +- `iconImageType` (string): MIME type for the icon, e.g., `"image/svg+xml"` +- `displayLabel` (string): Override label text +- `displayNameAttribute` (string): Attribute to use as the label +- `longDisplayNameAttribute` (string): Attribute to use as the description +- `backgroundOpacity` (number): Opacity from 0 to 1 +- `borderWidth` (number): Border width in pixels +- `borderColor` (string): Hex color code for the border +- `borderStyle` (string): Border line style (see Line style values below) + +**Example:** + +```json +{ + "airport": { + "color": "#E63946", + "shape": "round-rectangle", + "icon": "lucide:plane", + "iconImageType": "image/svg+xml", + "displayNameAttribute": "code", + "longDisplayNameAttribute": "desc" + } +} +``` + +## Edge style properties + +Each edge type can have the following optional properties: + +- `lineColor` (string): Hex color code, e.g., `"#264653"` +- `lineThickness` (number): Line width in pixels +- `lineStyle` (string): Line style (see Line style values below) +- `sourceArrowStyle` (string): Arrow style at the source (see Arrow style values below) +- `targetArrowStyle` (string): Arrow style at the target (see Arrow style values below) +- `displayLabel` (string): Override label text +- `displayNameAttribute` (string): Attribute to use as the label +- `labelColor` (string): Hex color code for the label text +- `labelBackgroundOpacity` (number): Label background opacity from 0 to 1 +- `labelBorderColor` (string): Hex color code for the label border +- `labelBorderStyle` (string): Label border line style (see Line style values below) +- `labelBorderWidth` (number): Label border width in pixels + +**Example:** + +```json +{ + "route": { + "lineColor": "#264653", + "lineThickness": 2, + "lineStyle": "solid", + "targetArrowStyle": "triangle", + "displayNameAttribute": "dist" + } +} +``` + +## Icon formats + +Icons can be specified in two formats: + +### Lucide icons (recommended) + +Use the `lucide:` prefix followed by the icon name: + +```json +{ + "icon": "lucide:plane", + "iconImageType": "image/svg+xml" +} +``` + +Lucide icons are bundled with Graph Explorer and always render. See [Lucide icons](https://lucide.dev/icons/) for the full list. + +### Base64 data URIs + +Embed an image as a base64-encoded data URI: + +```json +{ + "icon": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...", + "iconImageType": "image/png" +} +``` + +The subtype can be any image type (e.g., `image/png`, `image/jpeg`, `image/svg+xml`). + +**Security note:** Remote URLs (e.g., `https://example.com/icon.png`) are rejected for security reasons. Only Lucide references and base64 data URIs are allowed. + +## Shape values + +The following shapes are available: + +- `rectangle` +- `roundrectangle` +- `ellipse` +- `triangle` +- `pentagon` +- `hexagon` +- `heptagon` +- `octagon` +- `star` +- `barrel` +- `diamond` +- `vee` +- `rhomboid` +- `tag` +- `round-rectangle` +- `round-triangle` +- `round-diamond` +- `round-pentagon` +- `round-hexagon` +- `round-heptagon` +- `round-octagon` +- `round-tag` +- `cut-rectangle` +- `concave-hexagon` + +## Line style values + +The following line styles are available: + +- `solid` +- `dashed` +- `dotted` + +## Arrow style values + +The following arrow styles are available: + +- `triangle` +- `triangle-tee` +- `circle-triangle` +- `triangle-cross` +- `triangle-backcurve` +- `tee` +- `vee` +- `square` +- `circle` +- `diamond` +- `none` + +## All properties are optional + +You only need to specify the properties you want to override. Unspecified properties use the app defaults. For example, this is a valid minimal entry: + +```json +{ + "airport": { + "color": "#E63946" + } +} +``` + +This sets only the color; all other properties (shape, icon, labels, etc.) use the defaults. diff --git a/samples/air_routes/styles.json b/samples/air_routes/styles.json new file mode 100644 index 000000000..e9d3fe213 --- /dev/null +++ b/samples/air_routes/styles.json @@ -0,0 +1,52 @@ +{ + "meta": { + "kind": "styling-export", + "version": 1, + "timestamp": "2026-08-17T00:00:00.000Z", + "source": "Graph Explorer", + "sourceVersion": "3.2.2" + }, + "data": { + "vertices": { + "airport": { + "displayNameAttribute": "code", + "longDisplayNameAttribute": "desc", + "icon": "lucide:plane", + "iconImageType": "image/svg+xml", + "color": "#e66412", + "shape": "round-rectangle" + }, + "country": { + "displayNameAttribute": "code", + "longDisplayNameAttribute": "desc", + "icon": "lucide:flag", + "iconImageType": "image/svg+xml", + "color": "#e612b8", + "shape": "hexagon" + }, + "continent": { + "displayNameAttribute": "code", + "longDisplayNameAttribute": "desc", + "icon": "lucide:globe", + "iconImageType": "image/svg+xml", + "color": "#128EE5", + "shape": "diamond" + } + }, + "edges": { + "route": { + "displayNameAttribute": "dist", + "lineColor": "#0c4a6e", + "lineThickness": 2, + "lineStyle": "solid", + "targetArrowStyle": "triangle" + }, + "contains": { + "lineColor": "#b3b3b3", + "lineThickness": 1, + "lineStyle": "dotted", + "targetArrowStyle": "triangle" + } + } + } +}