Skip to content

Web Component API ​

Complete reference for using <graphty-element> declaratively via HTML attributes and properties.

Overview ​

The <graphty-element> Web Component provides a declarative way to add graph visualizations to your page using HTML attributes and element properties.

When to use the Web Component API:

  • Declarative HTML-based configuration
  • Framework integration (React, Vue, Angular, Svelte)
  • Simple graphs configured via attributes
  • Reactive property binding

When to use the JavaScript API instead:

  • Programmatic graph manipulation (add/remove nodes dynamically)
  • Running graph algorithms
  • Camera control and animation
  • Advanced styling with style layers
  • Screenshot and video capture

Properties and Attributes ​

All configuration is done through HTML attributes or their corresponding JavaScript properties:

PropertyAttributeTypeDefaultDescription
nodeDatanode-dataArray<object>[]Array of node objects; assigning replaces the nodes
edgeDataedge-dataArray<object>[]Array of edge objects; assigning replaces the edges
layoutlayoutstring'ngraph'Layout algorithm name
layoutConfiglayout-configobject{}Layout algorithm options
layoutScopelayout-scopescope (JSON)unsetLay out only these nodes and hold the rest still, such as {"set":"set_team"}; unset means the whole graph (see Layouts)
viewModeview-mode'2d' | '3d' | 'vr' | 'ar''3d'Rendering mode
backgroundbackgroundobjectwhitesmokeA colour, or a skybox image
startingCameraDistancestarting-camera-distancenumberunsetHow far the camera starts out; unset frames the graph to fit, set turns automatic framing off
autoFrameauto-framebooleantrueFrame the graph after each load and layout change; auto-frame="false" leaves the camera alone. A view preference, not saved in a project
labelDeclutterlabel-declutterbooleanfalseHide a node label that would overlap another until the reader zooms in; a view preference, not saved in a project
autoRotateauto-rotatenumber0Turn the 3D view on its own, in radians per second; a view preference, not saved in a project
fitInsetsproperty only{ top, right, bottom, left }all 0Room in CSS pixels that framing the graph keeps clear at each edge; a view preference, not saved in a project
pixelRatiopixel-rationumber | "device"1Pixels drawn per CSS pixel; "device" is sharp on a high-resolution screen and costs more per frame. Sizes stay in CSS pixels; a view preference
renderOnDemandrender-on-demandbooleanfalseDraw a frame only when the picture can have changed, so a still graph costs nothing per frame; a view preference, not saved in a project
lightinglighting"studio" | "headlight" | "matte" | "flat""studio"How 3D nodes are lit: key light, fill and a rim in the node's own color (studio); a light beside the camera (headlight); one soft light (matte); or unlit, exactly the style color with an outline (flat). A view preference, not saved in a project
dataSourcedata-sourcestringundefinedData source type
dataSourceConfigdata-source-configobject{}Data source configuration
nodeIdPathnode-id-pathstring'id'Path to node ID in data
edgeSrcIdPathedge-src-id-pathstringunsetPath to source ID in edge data; unset means probe
edgeDstIdPathedge-dst-id-pathstringunsetPath to target ID in edge data; unset means probe
edgeIdPathedge-id-pathstringunsetPath to an edge's own identifier, for data that carries one
repeatedEdgesrepeated-edges'keep' | 'first' | 'last' | 'sum' | 'min' | 'max' | 'error''keep'What a second edge between one pair does
nodeLabelPathnode-label-pathstringunsetPath to what to CALL a node, as distinct from its id
edgeWeightPathedge-weight-pathstring'weight'Path to an edge's weight, which every weighted algorithm reads
positionScaleposition-scalenumber1Multiplier from a record's own coordinates into scene units
directeddirectedboolean | 'auto''auto'Overrules a file header's direction; 'auto' lets the file decide
selectionStyleproperty only{ color?, scale?, opacity? }gold haloWhat a selected node looks like
layoutBehaviorproperty onlyobject{}How the element drives the layout, whether overlapping labels are thinned out, and the two on-demand expansion functions
algorithmsOnLoad(property only)Array<string | object>unsetAlgorithms to run once data loads: names, or { algorithm, params?, style?, seed?, as? } (see Algorithms)
runAlgorithmsOnLoadrun-algorithms-on-loadbooleanfalseWhether algorithmsOnLoad runs; a boolean attribute, on by presence
debugdebugbooleanfalseEnable debug overlay
historyKeyshistory-keysbooleantrueWhether Ctrl+Z, Ctrl+Shift+Z and Ctrl+Y on the focused canvas undo and redo; history-keys="false" turns them off (see Undo and History)

How the element finds an edge's endpoints ​

Leave edge-src-id-path and edge-dst-id-path unset and the element works out which keys name the endpoints. It tries source and target first, then src and dst, then from and to, and it decides once for a whole batch of records rather than once per record -- so a file cannot spell one edge one way and the next edge another and have both silently accepted.

A batch that answers none of the three fails the load with E_EDGE_ENDPOINTS_UNRESOLVED, naming the columns the records do carry. It never loads a graph with nodes and no edges in silence, which is what 1.x did to every file spelled the way the guides teach.

Setting either attribute settles the question and turns the probe off. A record that then does not answer the column you named is rejected and counted rather than guessed at again.

Read-only Properties ​

PropertyTypeDescription
graphGraphThe underlying Graph instance for advanced operations
stylesStylesAccess to the style manager

Methods ​

The Web Component exposes many methods directly. See Direct Methods below for the complete list.

What a selected node looks like ​

The highlight is a gold halo around the selected node, and it is configuration rather than a style layer -- a selection is what a reader is pointing at, not a property of the data, so a layer drawing it would be reorderable, persistable and lost at a dataset boundary along with every other layer.

javascript
element.selectionStyle = {
    color: "#00BCD4", // any colour the element understands
    scale: 1.8, // how far past the node it stands, as a multiple of the node's size
    opacity: 0.55, // 0 to 1
};

Merged over what is already set, so naming one field leaves the others alone, and it takes effect on a selection that is already on screen. An opacity of 0 draws no halo at all, for a page that marks the selected node its own way, such as with a node.outline style layer.

Expanding a node's neighborhood on demand ​

Double-clicking a node asks you for that node's neighbors and adds what comes back, so a graph too large to load at once can be explored a step at a time. Hand over the two functions through layoutBehavior:

javascript
element.layoutBehavior = {
    fetchEdges: (node) => fetchEdgesFor(node.id), // an iterable of edge records
    fetchNodes: (nodeIds) => fetchNodesFor([...nodeIds]), // an iterable of node records
};

Both are called on the double-click, so switching expansion on after the graph is drawn reaches the nodes already on screen. An edge record may spell its endpoints source/target, src/dst or from/to; the element decides once per batch. An edge the graph already holds is not duplicated.

Pacing the layout ​

layoutBehavior.layout carries the four settings that decide how hard the element drives the layout engine:

javascript
element.layoutBehavior = {
    layout: {
        preSteps: 500, // run this many steps before the first frame is drawn
        stepMultiplier: 2, // steps per rendered frame
        minDelta: 0.001, // stop once a whole frame moves every node less than this
        zoomStepInterval: 5, // re-frame the camera every N steps while the layout runs
    },
    node: { pinOnDrag: true }, // a dropped node stays where the reader put it
};

preSteps is what makes a screenshot of a physics layout the same picture twice. minDelta at its default of 0 leaves the engine to decide when it has finished.

Turning the graph, and leaving room for your own panels ​

autoRotate turns the 3D view on its own, in radians per second (0, the default, never turns). It waits while the reader drags, pinches, holds a camera key or drags a node, and the 2D view never turns. fitInsets keeps room clear, in CSS pixels, at each edge of the canvas when the element frames the graph -- for a page that draws its own panels over part of the canvas. The graph is fitted and centered in what is left, in 2D and 3D, and the 3D view keeps that center while it turns:

javascript
const still = matchMedia("(prefers-reduced-motion: reduce)").matches;
element.autoRotate = still ? 0 : 0.15; // a slow drift
element.fitInsets = { left: 480, top: 64 }; // a panel on the left, a toolbar on top

Both are preferences of the view, like labelDeclutter: setting them records no undo step and is not saved in a project file. They are the same settings as layoutBehavior.camera.autoRotate and layoutBehavior.camera.fitInsets.

The canvas still draws everywhere, so a node turned into an inset is drawn and can be pointed at.

A sharp picture on a high-resolution screen ​

By default the canvas is drawn at one pixel per CSS pixel, which looks soft on a phone, a tablet or a laptop with a high-resolution screen. pixel-ratio="device" draws it at the screen's own devicePixelRatio and follows it as the browser zoom or the screen changes:

html
<graphty-element pixel-ratio="device"></graphty-element>

Each frame then costs about the square of the ratio more to draw, which is why it is a choice. Every size the element measures on screen stays in CSS pixels whatever the ratio: fitInsets, screen-sized labels, line widths and nodeScreenPosition.

A 3D graph is framed by default so the box around it fits, seen from the front. A turning graph never shows its box's empty corners, so it is drawn smaller than it needs to be. Set layoutBehavior.camera.fitBounds to "turning" to fit the nodes themselves at every angle the view turns to about the vertical axis: the graph is drawn as large as it can be and still stays in the frame however far it turns. 2D always fits the box.

javascript
element.layoutBehavior = { camera: { fitBounds: "turning" } };

Groups of nodes ​

A file can nest nodes inside another node: a Graphviz cluster, a yEd group, a Cytoscape.js compound node. When the file stores no position for such a group, the element does not lay it out as a node of its own. It is placed on the box its members make, with a little padding, every frame the layout moves them; it is drawn that size, behind its members; and it neither pushes nor pulls any other node. Framing the camera includes the box. A group the file does place keeps the position and size the file gives it.

To lay groups out like every other node instead:

javascript
element.layoutBehavior = { layout: { groupsFollowMembers: false } };

Overlapping labels ​

labelDeclutter hides a node label whose words would be drawn over another label's. It is off by default, so every label a style asks for is drawn; see Labels that would overlap. It is a preference of the view: switching it records no undo step and is not saved in a project file.

javascript
element.labelDeclutter = true;

Assigning layoutBehavior is merged over what is already set, section by section, so this line leaves layout, node and the two fetch functions as they were.

Drawing only when something changes ​

By default the element draws the graph on every animation frame, whether or not anything moved. renderOnDemand stops drawing once the layout has settled, the styles are painted and the camera is at rest, and draws again as soon as the data, a style, the layout, the camera, the selection or the reader's pointer, wheel or keys change the picture. A still graph then costs the page nothing per frame, which matters most where the GPU is emulated in software (virtual machines, CI runners, headless browsers): there each frame costs the page tens of milliseconds or more, and the page's own buttons and fields answer that much later.

html
<graphty-element render-on-demand></graphty-element>

It can also be set as layoutBehavior.rendering.onDemand. If your own code changes the Babylon.js scene directly, through element.graph.scene: adding or removing a mesh, material, texture, light or camera is drawn, and so is a new background color, but changing a property of something already in the scene (a material's color, a mesh's position) is not drawn until something else changes the picture. Leave the option off in that case. Like labelDeclutter, it is a preference of the view, not saved in a project file.

Basic Usage ​

Minimal Example ​

html
<graphty-element
    node-data='[{"id": "a"}, {"id": "b"}, {"id": "c"}]'
    edge-data='[{"source": "a", "target": "b"}, {"source": "b", "target": "c"}]'
>
</graphty-element>

With Layout and Styling ​

html
<graphty-element
    layout="circular"
    background='{"backgroundType":"color","color":"#101014"}'
    node-data='[{"id": "a"}, {"id": "b"}, {"id": "c"}]'
    edge-data='[{"source": "a", "target": "b"}, {"source": "b", "target": "c"}]'
>
</graphty-element>

Data Format ​

Node Data ​

Each node object must have an id property. Additional properties are available for styling:

html
<graphty-element
    node-data='[
  {"id": "node1", "label": "First Node", "category": "type-a"},
  {"id": "node2", "label": "Second Node", "category": "type-b"},
  {"id": "node3", "label": "Third Node", "size": 2.0}
]'
></graphty-element>

Common node properties:

  • id (required) - Unique identifier
  • label - Display text
  • category / type / group - For style selectors
  • size - Node size multiplier
  • color - Node color (hex or named)

Edge Data ​

Each edge object must have source and target properties referencing node IDs:

html
<graphty-element
    edge-data='[
  {"source": "node1", "target": "node2"},
  {"source": "node2", "target": "node3", "weight": 1.5},
  {"source": "node3", "target": "node1", "label": "connects"}
]'
></graphty-element>

Common edge properties:

  • source (required) - Source node ID
  • target (required) - Target node ID
  • weight - Edge weight for layouts
  • label - Display text
  • type / category - For style selectors

Attribute vs Property ​

Attributes (HTML strings) ​

Set via HTML markup or setAttribute(). Always strings, require JSON serialization for objects:

html
<graphty-element layout="ngraph" node-data='[{"id": "a"}, {"id": "b"}]'> </graphty-element>
javascript
element.setAttribute("node-data", JSON.stringify(nodes));
element.setAttribute("layout", "circular");

Properties (JavaScript values) ​

Set via JavaScript. Typed values, no JSON serialization needed:

javascript
const element = document.querySelector("graphty-element");

element.nodeData = [{ id: "a" }, { id: "b" }];
element.edgeData = [{ source: "a", target: "b" }];
element.layout = "circular";
element.viewMode = "2d";

Properties are preferred when working with JavaScript as they:

  • Avoid JSON serialization overhead
  • Preserve object types
  • Enable reactive updates

Layout Algorithms ​

Set the layout algorithm via the layout attribute:

html
<graphty-element layout="ngraph"></graphty-element>
<graphty-element layout="circular"></graphty-element>
<graphty-element layout="hierarchical" layout-config='{"start": "a"}'></graphty-element>
<graphty-element layout="grid"></graphty-element>
<graphty-element layout="random"></graphty-element>

Available layouts:

  • ngraph - Force-directed (default, 2D/3D)
  • d3 - D3's force simulation (2D/3D)
  • circular - Nodes arranged in a circle
  • hierarchical - Tree-like structure, grown from the node start names (required)
  • grid - Regular grid pattern
  • random - Random positions

Configure layout options:

html
<graphty-element layout="ngraph" layout-config='{"springLength": 100, "gravity": -1.2}'> </graphty-element>
javascript
element.layoutConfig = {
    springLength: 100,
    springCoefficient: 0.0008,
    gravity: -1.2,
};

Styling ​

The background is a property of the element. What the nodes and edges look like is a stack of style layers, which is reached through the session:

html
<graphty-element id="graph" background='{"backgroundType":"color","color":"#101014"}'></graphty-element>
javascript
await document.querySelector("#graph").session.styles.add({
    name: "Nodes",
    target: "node",
    selector: { match: "everything" },
    set: { "node.color": "#E5E7EB" },
});

See the styling guide for selectors, channels and the rest of the vocabulary.

View Modes ​

Switch between rendering modes:

html
<graphty-element view-mode="3d"></graphty-element> <graphty-element view-mode="2d"></graphty-element>
javascript
element.viewMode = "3d";
element.viewMode = "2d";

For VR/AR modes, see the VR/AR Guide.

CSS Styling ​

The element is a block that fills its container's width. Its height is 2:1 -- half its width -- unless you give it a height or place it in a parent that has one, in which case it fills that parent. Size it with ordinary CSS:

css
graphty-element {
    height: 500px;
}

Or inline styles:

html
<graphty-element style="width: 800px; height: 600px;"> </graphty-element>

Events ​

Listen to graph events via standard DOM event handling:

javascript
const element = document.querySelector("graphty-element");

element.addEventListener("graph-settled", (e) => {
    console.log("Layout complete");
});

element.addEventListener("node-click", (e) => {
    console.log("Clicked node:", e.detail.node.id);
});

element.addEventListener("data-loaded", (e) => {
    console.log(`Loaded ${e.detail.nodeCount} nodes`);
});

Common events:

  • graph-settled - Layout has stabilized
  • data-loaded - Data loading complete
  • node-click - Node was clicked
  • node-hover - Mouse entered a node
  • selection-changed - Selected node changed
  • graphty-history-change - A step was recorded, undone or redone; the detail carries canUndo and canRedo

See Events for the complete event reference.

Framework Integration ​

React ​

tsx
function GraphComponent({ nodes, edges, layout = "ngraph" }) {
    return (
        <graphty-element
            node-data={JSON.stringify(nodes)}
            edge-data={JSON.stringify(edges)}
            layout={layout}
            style={{ height: "500px" }}
        />
    );
}

If the element module is loaded lazily, make sure it is defined before React renders the tag; otherwise React writes object props as "[object Object]" attributes. See Load the element before React renders it.

Vue ​

vue
<template>
    <graphty-element
        :node-data="JSON.stringify(nodes)"
        :edge-data="JSON.stringify(edges)"
        :layout="layout"
        style="display: block; width: 100%; height: 500px;"
    />
</template>

Angular ​

typescript
@Component({
    template: `
        <graphty-element [attr.node-data]="nodesJson" [attr.edge-data]="edgesJson" [attr.layout]="layout">
        </graphty-element>
    `,
})
export class GraphComponent {
    nodes = [{ id: "a" }, { id: "b" }];
    edges = [{ source: "a", target: "b" }];
    layout = "ngraph";

    get nodesJson() {
        return JSON.stringify(this.nodes);
    }
    get edgesJson() {
        return JSON.stringify(this.edges);
    }
}

Svelte ​

svelte
<script>
  export let nodes = [];
  export let edges = [];
  export let layout = 'ngraph';
</script>

<graphty-element
  node-data={JSON.stringify(nodes)}
  edge-data={JSON.stringify(edges)}
  {layout}
  style="display: block; width: 100%; height: 500px;"
/>

Data Sources ​

Load data from external sources:

html
<!-- Load from URL -->
<graphty-element data-source="url" data-source-config='{"url": "https://example.com/graph.json"}'> </graphty-element>
javascript
element.dataSource = "url";
element.dataSourceConfig = { url: "https://example.com/graph.json" };

Assigning the pair again, with or without clearData() first, loads the new source. The first load adds to the graph; a later one replaces it once the new source has parsed, so a bad file leaves the current graph on screen and reports data-loading-error. Every event about a load carries its loadId. To await a load instead, call loadFromUrl, loadFromFile or addDataFromSource, which resolve to { loadId } and take a replace option.

See Data Sources for available data source types.

Direct Methods on the Web Component ​

Many methods from the Graph class are available directly on the <graphty-element> for convenience. This eliminates the need to access .graph for common operations.

Data Methods ​

javascript
const element = document.querySelector("graphty-element");

// Bulk data loading
element.setData({
    nodes: [{ id: "a" }, { id: "b" }],
    edges: [{ source: "a", target: "b" }],
});

// Add nodes/edges
await element.addNode({ id: "c", label: "Node C" });
await element.addEdge({ source: "b", target: "c" });

// Get data
const node = element.getNode("a");
const allNodes = element.getNodes();
const nodeCount = element.getNodeCount();

Undo and Redo ​

javascript
// Every change a project saves is one step
await element.session.undo();
await element.session.redo();

// Several changes as one step: call them through tx
await element.batchOperations(async (tx) => {
    await tx.data.addNodes([{ id: "d" }]);
    await tx.data.addEdges([{ source: "c", target: "d" }]);
});

See Undo and History.

Selection and Layout ​

javascript
// Selection
element.selectNode("node1");
element.deselectNode();
const selected = element.getSelectedNode();

// Layout control
element.setLayout("circular");
await element.waitForSettled(); // the queued operations are done
await element.waitForStableFrame(); // the picture has stopped changing

Camera Control ​

javascript
// Fit view
element.zoomToFit();

// Camera state
const state = element.getCameraState();
element.setCameraPosition({ x: 0, y: 0, z: 100 });
element.setCameraTarget({ x: 0, y: 0, z: 0 });

// Camera mode: each view mode has one camera ("orbit" in 3D, "2d" in 2D), and a camera
// from the other mode is refused. Change view mode with viewMode / setViewMode instead.
await element.setCameraMode("orbit");
const controller = element.getCameraController();

Manager Access ​

javascript
// Access internal managers
const dataManager = element.getDataManager();
const layoutManager = element.getLayoutManager();
const selectionManager = element.getSelectionManager();
const eventManager = element.getEventManager();
const statsManager = element.getStatsManager();

AI Control ​

javascript
// Enable AI commands
await element.enableAiControl({
    provider: "anthropic",
    apiKey: "your-api-key",
});

// Execute commands
const result = await element.aiCommand("Find the central node");

// Status tracking
element.onAiStatusChange((status) => {
    console.log("AI:", status.stage);
});

// Voice input
element.startVoiceInput();
if (element.isVoiceActive()) {
    console.log("Listening...");
}
element.stopVoiceInput();

// Cleanup
element.disableAiControl();

Screenshot and Capture ​

javascript
// Take screenshot
const result = await element.captureScreenshot({
    width: 1920,
    height: 1080,
});

// Check capabilities before asking for a large one
const capability = await element.canCaptureScreenshot({ multiplier: 4 });
if (!capability.supported) {
    console.warn(capability.reason);
}

Algorithms ​

javascript
// Run algorithm
await element.runAlgorithm("graphty", "degree");

// Apply styling from results
element.applySuggestedStyles("graphty:degree");

Need More Control? ​

While many methods are now available directly on the Web Component, some advanced operations still require accessing the underlying Graph instance:

  • Custom layout engine implementations
  • Direct Babylon.js scene access (element.graph.getScene())
  • Mesh cache access (element.graph.getMeshCache())
  • Advanced XR session management

See the JavaScript API Guide for the complete Graph class reference.

Interactive Examples ​