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:
| Property | Attribute | Type | Default | Description |
|---|---|---|---|---|
nodeData | node-data | Array<object> | [] | Array of node objects; assigning replaces the nodes |
edgeData | edge-data | Array<object> | [] | Array of edge objects; assigning replaces the edges |
layout | layout | string | 'ngraph' | Layout algorithm name |
layoutConfig | layout-config | object | {} | Layout algorithm options |
layoutScope | layout-scope | scope (JSON) | unset | Lay out only these nodes and hold the rest still, such as {"set":"set_team"}; unset means the whole graph (see Layouts) |
viewMode | view-mode | '2d' | '3d' | 'vr' | 'ar' | '3d' | Rendering mode |
background | background | object | whitesmoke | A colour, or a skybox image |
startingCameraDistance | starting-camera-distance | number | unset | How far the camera starts out; unset frames the graph to fit, set turns automatic framing off |
autoFrame | auto-frame | boolean | true | Frame the graph after each load and layout change; auto-frame="false" leaves the camera alone. A view preference, not saved in a project |
labelDeclutter | label-declutter | boolean | false | Hide a node label that would overlap another until the reader zooms in; a view preference, not saved in a project |
autoRotate | auto-rotate | number | 0 | Turn the 3D view on its own, in radians per second; a view preference, not saved in a project |
fitInsets | property only | { top, right, bottom, left } | all 0 | Room in CSS pixels that framing the graph keeps clear at each edge; a view preference, not saved in a project |
pixelRatio | pixel-ratio | number | "device" | 1 | Pixels 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 |
renderOnDemand | render-on-demand | boolean | false | Draw 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 |
lighting | lighting | "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 |
dataSource | data-source | string | undefined | Data source type |
dataSourceConfig | data-source-config | object | {} | Data source configuration |
nodeIdPath | node-id-path | string | 'id' | Path to node ID in data |
edgeSrcIdPath | edge-src-id-path | string | unset | Path to source ID in edge data; unset means probe |
edgeDstIdPath | edge-dst-id-path | string | unset | Path to target ID in edge data; unset means probe |
edgeIdPath | edge-id-path | string | unset | Path to an edge's own identifier, for data that carries one |
repeatedEdges | repeated-edges | 'keep' | 'first' | 'last' | 'sum' | 'min' | 'max' | 'error' | 'keep' | What a second edge between one pair does |
nodeLabelPath | node-label-path | string | unset | Path to what to CALL a node, as distinct from its id |
edgeWeightPath | edge-weight-path | string | 'weight' | Path to an edge's weight, which every weighted algorithm reads |
positionScale | position-scale | number | 1 | Multiplier from a record's own coordinates into scene units |
directed | directed | boolean | 'auto' | 'auto' | Overrules a file header's direction; 'auto' lets the file decide |
selectionStyle | property only | { color?, scale?, opacity? } | gold halo | What a selected node looks like |
layoutBehavior | property only | object | {} | 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> | unset | Algorithms to run once data loads: names, or { algorithm, params?, style?, seed?, as? } (see Algorithms) |
runAlgorithmsOnLoad | run-algorithms-on-load | boolean | false | Whether algorithmsOnLoad runs; a boolean attribute, on by presence |
debug | debug | boolean | false | Enable debug overlay |
historyKeys | history-keys | boolean | true | Whether 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 ​
| Property | Type | Description |
|---|---|---|
graph | Graph | The underlying Graph instance for advanced operations |
styles | Styles | Access 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.
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:
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:
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:
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 topBoth 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:
<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.
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:
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.
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.
<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 ​
<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 ​
<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:
<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 identifierlabel- Display textcategory/type/group- For style selectorssize- Node size multipliercolor- Node color (hex or named)
Edge Data ​
Each edge object must have source and target properties referencing node IDs:
<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 IDtarget(required) - Target node IDweight- Edge weight for layoutslabel- Display texttype/category- For style selectors
Attribute vs Property ​
Attributes (HTML strings) ​
Set via HTML markup or setAttribute(). Always strings, require JSON serialization for objects:
<graphty-element layout="ngraph" node-data='[{"id": "a"}, {"id": "b"}]'> </graphty-element>element.setAttribute("node-data", JSON.stringify(nodes));
element.setAttribute("layout", "circular");Properties (JavaScript values) ​
Set via JavaScript. Typed values, no JSON serialization needed:
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:
<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 circlehierarchical- Tree-like structure, grown from the nodestartnames (required)grid- Regular grid patternrandom- Random positions
Configure layout options:
<graphty-element layout="ngraph" layout-config='{"springLength": 100, "gravity": -1.2}'> </graphty-element>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:
<graphty-element id="graph" background='{"backgroundType":"color","color":"#101014"}'></graphty-element>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:
<graphty-element view-mode="3d"></graphty-element> <graphty-element view-mode="2d"></graphty-element>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:
graphty-element {
height: 500px;
}Or inline styles:
<graphty-element style="width: 800px; height: 600px;"> </graphty-element>Events ​
Listen to graph events via standard DOM event handling:
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 stabilizeddata-loaded- Data loading completenode-click- Node was clickednode-hover- Mouse entered a nodeselection-changed- Selected node changedgraphty-history-change- A step was recorded, undone or redone; the detail carriescanUndoandcanRedo
See Events for the complete event reference.
Framework Integration ​
React ​
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 ​
<template>
<graphty-element
:node-data="JSON.stringify(nodes)"
:edge-data="JSON.stringify(edges)"
:layout="layout"
style="display: block; width: 100%; height: 500px;"
/>
</template>Angular ​
@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 ​
<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:
<!-- Load from URL -->
<graphty-element data-source="url" data-source-config='{"url": "https://example.com/graph.json"}'> </graphty-element>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 ​
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 ​
// 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 ​
// 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 changingCamera Control ​
// 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 ​
// 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 ​
// 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 ​
// 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 ​
// 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 ​
- Default Graph - Basic configuration
- View Modes - 2D/3D switching
- Layouts - Different layout algorithms