Repository navigation
Graph Model
This page describes the programming model behind a draw.io diagram: the cells, their geometry, transactions, events, undo, the view, selection, labels and pages. It is for plugin authors and forkers who read or change diagrams in code. For the on-disk XML see File format; for style strings see Styles.
All examples run inside a plugin (see Plugins):
Draw.loadPlugin(function(ui)
{
var graph = ui.editor.graph;
var model = graph.getModel();
// ...
});| Object | Class | Source |
|---|---|---|
ui |
App (extends EditorUi) |
diagramly/App.js, grapheditor/EditorUi.js, diagramly/EditorUi.js
|
ui.editor |
Editor |
grapheditor/Editor.js, diagramly/Editor.js
|
ui.editor.graph |
Graph (extends mxGraph) |
grapheditor/Graph.js, mxgraph/src/view/mxGraph.js
|
graph.model / graph.getModel()
|
mxGraphModel |
mxgraph/src/model/ |
graph.view / graph.getView()
|
mxGraphView |
mxgraph/src/view/mxGraphView.js |
graph.getStylesheet() |
mxStylesheet |
see Styles |
graph.getSelectionModel() |
mxGraphSelectionModel |
mxgraph/src/view/ |
ui.editor.undoManager |
mxUndoManager |
created in Editor.prototype.createUndoManager
|
ui.pages, ui.currentPage
|
DiagramPage |
diagramly/Pages.js |
One Graph instance shows one page at a time. The model holds the cells of the current page only; the other pages are kept in ui.pages (see Pages).
Every page has the same three-level structure:
root cell (id "0") model.getRoot()
└── layer (id "1", the default) model.getChildAt(root, 0), graph.getDefaultParent()
├── vertex
│ └── child vertex (a group, container or relative child)
└── edge
└── edge label (a vertex with a relative geometry)
- The root has no geometry. Its children are the layers;
model.isLayer(cell)is true when the parent is the root. -
graph.getDefaultParent()returns the layer that new cells go into (the current layer in the Layers window). Add a layer withgraph.addCell(new mxCell('Name'), model.root). - A hidden layer has
visible = false; a locked layer haslocked=1in its style. - Vertices can contain vertices and edges (groups, containers, tables, swimlanes). Edges can contain vertices (edge labels).
- The model moves an edge into the nearest common ancestor of its terminals (
mxGraphModel.prototype.maintainEdgeParent, default true).
mxCell is a plain data object. Read its fields directly, but change them only through the model (model.setValue, model.setStyle, ...). The setters on the cell itself (cell.setStyle, cell.setAttribute, ...) bypass the transaction, so there is no undo, no event and no repaint.
| Field | Accessor | Meaning |
|---|---|---|
id |
cell.getId() |
String, unique within the page. Created by the model if missing. |
value |
model.getValue(cell) |
A string (the label) or an XML element (a user object, see Values and labels). |
style |
model.getStyle(cell) |
The style string, see Styles. |
vertex, edge
|
model.isVertex(cell), model.isEdge(cell)
|
At most one is true. Root and layers have neither. |
parent |
model.getParent(cell) |
Containing cell. |
children |
model.getChildCount(cell), model.getChildAt(cell, i), model.getChildren(cell)
|
Child cells in z-order (first is at the back). |
source, target
|
model.getTerminal(edge, isSource) |
Connected vertices (or edges) of an edge, or null. |
edges |
model.getEdgeCount(cell), model.getEdgeAt(cell, i), model.getEdges(cell)
|
Edges connected to a cell. |
geometry |
model.getGeometry(cell) |
mxGeometry, see below. |
connectable |
model.isConnectable(cell) |
Default true. Graph.isCellConnectable also checks the connectable style. |
visible |
model.isVisible(cell) |
Default true. |
collapsed |
model.isCollapsed(cell) |
Default false. A collapsed container swaps its geometry with geometry.alternateBounds. |
Other useful lookups: model.getCell(id), model.getDescendants(cell), model.filterDescendants(filter, parent), model.getTopmostCells(cells), model.isAncestor(parent, child), model.getEdgesBetween(a, b, directed).
mxGeometry extends mxRectangle. Coordinates are in model units (pixels at 100% zoom) and relative to the origin of the parent cell (a layer's origin is 0,0).
| Field | Vertices | Edges |
|---|---|---|
x, y, width, height
|
Bounds in the parent. |
x, y place the edge's own label (see relative); width/height are ignored. |
relative |
If true, x and y are fractions (0..1) of the parent's size and offset is added in pixels. Used for ports and decorations attached to a parent. |
Always true for edges created by graph.createEdge. x is the position along the edge from -1 (source) to 1 (target), 0 is the centre; y is the orthogonal distance in pixels. |
offset |
For relative geometries the pixel offset of the origin, otherwise the label offset. | Pixel offset of the label. |
points |
Not used. | Array of mxPoint waypoints in parent coordinates. Interpreted by the edge style. |
sourcePoint, targetPoint
|
Not used. | End points used when the edge has no source or target terminal. |
alternateBounds |
Bounds to swap in when the cell is collapsed or expanded. | Not used. |
Edge labels added in the editor are child vertices of the edge with a relative geometry (x along the edge, y orthogonal, plus offset) and the edgeLabel style.
Geometries are shared with the undo history. Never modify a geometry in place; clone it, change the clone and set it:
var geo = model.getGeometry(cell).clone();
geo.x += 20;
geo.points = [new mxPoint(100, 40)]; // edges only
model.setGeometry(cell, geo);All changes go through the model and are grouped into transactions. A transaction is one undo step and one change event:
model.beginUpdate();
try
{
// any number of changes
}
finally
{
model.endUpdate();
}Transactions nest: only the outermost endUpdate fires the change and undo events. Always use try/finally, otherwise an exception leaves the model in an open transaction and later changes are never shown or saved.
Model-level changes (each creates a change object, see below): model.add(parent, child, index), model.remove(cell), model.setValue, model.setStyle, model.setGeometry, model.setTerminal(edge, terminal, isSource), model.setTerminals(edge, source, target), model.setVisible, model.setCollapsed, model.setRoot and model.execute(change). A single call outside beginUpdate/endUpdate is its own transaction.
The mxGraph methods (insertVertex, addCells, moveCells, resizeCells, removeCells, setCellStyles, groupCells, labelChanged, ...) open their own transaction and also apply the graph's rules (constraints, layouts, container resizing). Prefer them to raw model calls when there is one.
Each atomic change is an object with an execute() method; executing it again undoes it. After execute(), the fields describe the current state and previous holds the state before:
| Change | Fields |
|---|---|
mxChildChange |
child, parent (null when the cell was removed), previous (null when it was added), index
|
mxTerminalChange |
cell, terminal, previous, source (boolean) |
mxValueChange |
cell, value, previous
|
mxStyleChange |
cell, style, previous
|
mxGeometryChange |
cell, geometry, previous
|
mxCollapseChange, mxVisibleChange
|
cell, collapsed / visible, previous
|
mxRootChange |
root, previous
|
Page changes in Pages.js (SelectPage, ChangePage, RenamePage, MovePage, ...) use the same mechanism.
When the outermost endUpdate runs, the model fires, in order:
| Event | Property | Use |
|---|---|---|
mxEvent.CHANGE |
edit (an mxUndoableEdit; edit.changes is the array of changes) |
React to any change. Also fires on undo and redo. |
mxEvent.NOTIFY |
edit |
Same as CHANGE; intended for syncing with a remote model. |
mxEvent.UNDO |
edit |
Fired once per new transaction (not on undo/redo). The undo manager listens to this. |
Other model events: mxEvent.BEGIN_UPDATE, mxEvent.END_UPDATE, mxEvent.EXECUTE / mxEvent.EXECUTED (per change, while the transaction is open). The graph fires higher-level events such as mxEvent.CELLS_ADDED, mxEvent.MOVE_CELLS, mxEvent.LABEL_CHANGED and mxEvent.ROOT (the root, i.e. the page, was replaced).
Editor.prototype.createUndoManager creates ui.editor.undoManager and adds every mxEvent.UNDO edit of the model and the view to it. To undo or redo in the app, call ui.undo() / ui.redo() (or run the undo / redo actions) rather than ui.editor.undoManager.undo(): the EditorUi versions also handle in-place text editing and repair the page after the step.
The model is pure data. mxGraphView keeps an mxCellState for each visible cell of the current page, with the resolved style, the absolute position and the DOM shapes.
var state = graph.view.getState(cell); // null if the cell is not rendered
if (state != null)
{
state.style; // resolved style object (see Styles)
state.x; state.y; // screen position: scale and translate applied
state.width; state.height;
state.getCellBounds(); // absolute bounds in model units
state.absolutePoints; // edges: screen points of the route (cellPoints in model units)
state.shape; state.text; // mxShape instances that paint the cell and its label
}graph.view.scale is the zoom factor and graph.view.translate the pan offset in model units; a screen coordinate is scale * (modelCoordinate + translate). Read or write the model, not the state: states are rebuilt when the model changes, and cells in collapsed or hidden containers and on other pages have none. graph.getCellGeometry(cell) returns the geometry; graph.getBoundingBox(cells) and graph.getGraphBounds() return screen bounds.
| Call | Purpose |
|---|---|
graph.getSelectionCells() / graph.getSelectionCell()
|
Current selection (array / first cell). |
graph.setSelectionCells(cells) / graph.setSelectionCell(cell)
|
Replace the selection. |
graph.addSelectionCells(cells), graph.removeSelectionCells(cells), graph.clearSelection()
|
Modify it. |
graph.getSelectionCount(), graph.isSelectionEmpty(), graph.isCellSelected(cell)
|
Query it. |
graph.selectAll(parent, descendants) |
Select the children of a parent. |
Listen to graph.getSelectionModel() for mxEvent.CHANGE. Its added and removed properties are swapped for historical reasons: added holds the cells that were deselected and removed the cells that were selected. Usually it is simpler to call graph.getSelectionCells() in the listener.
From mxGraph.js:
graph.insertVertex(parent, id, value, x, y, width, height, style, relative) // returns the vertex
graph.insertEdge(parent, id, value, source, target, style) // returns the edge
graph.addCell(cell, parent, index, source, target) // returns the cell
graph.addCells(cells, parent, index, source, target, absolute) // returns the cells-
parentnull meansgraph.getDefaultParent().idnull lets the model create one. In draw.io, generated IDs are a random prefix plus a counter (model.prefix = Editor.guid() + '-', reset when a file is opened and when a page is selected), so never derive meaning from them; pass your ownidif you need a stable one, and check it is free withmodel.getCell(id). -
valueis a label string or an XML element (see below).styleis a style string; with no style the stylesheet defaults apply, which differ from the defaults the sidebar uses (see Styles). -
graph.createVertex(...)andgraph.createEdge(...)take the same arguments and return a cell that is not yet in the model.
To remove cells use graph.removeCells(cells, includeEdges) or, as the Delete key does, graph.deleteCells(cells, includeEdges).
A cell value is either a string or an XML element, called a user object. User objects carry the label plus any number of custom attributes (Edit Data in the editor, Ctrl+M or Cmd+M). In the file they wrap the mxCell (see File format).
| Call | Purpose |
|---|---|
graph.convertValueToString(cell) |
The label text: the value, or the label attribute of a user object (or the attribute named by placeholder). |
graph.getLabel(cell) |
The label as rendered, with %placeholders% replaced. |
graph.labelChanged(cell, text) |
Set the label (undoable). Updates the label attribute of a user object. |
graph.getAttributeForCell(cell, name, defaultValue) |
Read an attribute of a user object. |
graph.setAttributeForCell(cell, name, value) |
Set (or, with null, remove) an attribute. Undoable; clones the value and converts a string value into a UserObject element first. |
cell.getAttribute(name, defaultValue), cell.hasAttribute(name)
|
Read directly from the user object. |
Attributes with a meaning in draw.io:
| Attribute | Meaning |
|---|---|
label |
The label. |
tooltip |
Tooltip text (graph.getTooltipForCell). Without it, the tooltip lists the other attributes. |
note |
Markdown note (Edit Note; graph.getNoteForCell, graph.setNoteForCell). |
link, linkTarget
|
Cell link (graph.getLinkForCell, graph.setLinkForCell). data:page/id,<pageId> links to a page. |
placeholders |
1 replaces %name% in the label and tooltip with the value of attribute name on the cell or its nearest ancestor (up to the root cell), then the global variables. |
placeholder |
The label is the value of the named attribute on the cell or its nearest ancestor. |
tags |
Space-separated tags (graph.getTagsForCell, graph.addTagsForCells). |
Attributes on the user object of the root cell (Edit Data with nothing selected) are therefore page-wide variables. See Placeholders and Predefined placeholders.
Labels are plain text unless the cell style has html=1; then the label is HTML. Labels are stored raw and sanitised only when rendered (cellRenderer.getLabelValue in Graph.js calls Graph.sanitizeHtml). If your code puts a label into the DOM itself, sanitise it with Graph.sanitizeHtml or write it as text with mxUtils.write; see Security.
| Call | Returns / does |
|---|---|
new mxCodec(mxUtils.createXmlDocument()).encode(model) |
<mxGraphModel> element of a model, cells only. |
new mxCodec(doc).decode(node, model) |
Decodes an <mxGraphModel> element into a model. |
ui.editor.getGraphXml() |
<mxGraphModel> element of the current page, including the page settings (grid, page size, background, ...). |
graph.encodeCells(cells) |
<mxGraphModel> element with clones of the given cells. |
ui.getXmlFileData() |
<mxfile> element with all pages. |
ui.getFileData(true) |
The whole file as an XML string, as it would be saved. |
ui.importXml(xml, dx, dy) |
Inserts copies of the cells (or extra pages) of an <mxGraphModel> or <mxfile> string into the diagram; undoable; returns the new cells. |
ui.replaceDiagramData(xml) |
Replaces the current page with an <mxGraphModel> string; undoable. |
mxUtils.parseXml(text), mxUtils.getXml(node), mxUtils.getPrettyXml(node)
|
Parse and serialise. |
The codec registry (mxgraph/src/io/) maps class names to codecs. mxCellCodec writes a user object as the outer element with the mxCell inside it, and mxModelCodec writes the <root> (and <defs> with style compression).
A file has one or more pages. ui.pages is an array of DiagramPage objects; ui.currentPage is the one in the graph.
DiagramPage member |
Meaning |
|---|---|
getId(), getName(), setName()
|
Page ID and name (attributes of the <diagram> node). |
node |
The <diagram> XML node; holds the page content while the page is not current. |
root |
The decoded root cell. Null until the page has been shown or decoded; call ui.updatePageRoot(page) to decode it. |
viewState |
Grid, page format, background, scale and other page settings. |
Switching pages saves the current root and view state into the page, then gives the graph the new page's root (graph.model.rootChanged). The graph fires mxEvent.ROOT and ui.editor fires pageSelected. Cell objects and IDs of other pages are therefore not in graph.model; use ui.updatePageRoot(page).root to read them, and select a page before changing it.
| Call | Purpose |
|---|---|
ui.selectPage(page), ui.selectNextPage(forward)
|
Show a page. |
ui.insertPage(page, index) |
Insert (and show) a page; with no arguments creates an empty page at the end. Returns the page. |
ui.createPage(name, id) |
New DiagramPage that is not yet in the file; ui.createPageId() returns a free ID. |
ui.removePage(page), ui.duplicatePage(page, name), ui.movePage(oldIndex, newIndex)
|
Edit the page list. |
ui.getPageById(id), ui.getPageIndex(page), ui.getSelectedPageIndex()
|
Look up pages. |
graph.model.execute(new RenamePage(ui, page, name)) |
Rename without the dialog (ui.renamePage(page) opens it). |
Inserting, removing, duplicating, moving and renaming pages are executed as changes on graph.model, so they are undoable and mark the file as modified. Selecting a page is also recorded in the undo history but does not modify the file.
var parent = graph.getDefaultParent();
var v1, v2, e1;
model.beginUpdate();
try
{
v1 = graph.insertVertex(parent, null, 'Start', 40, 40, 120, 60,
'rounded=1;whiteSpace=wrap;html=1;');
v2 = graph.insertVertex(parent, null, 'End', 240, 40, 120, 60,
'ellipse;whiteSpace=wrap;html=1;');
e1 = graph.insertEdge(parent, null, 'next', v1, v2,
'edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;');
}
finally
{
model.endUpdate();
}
graph.setSelectionCells([v1, v2, e1]);var cells = graph.getSelectionCells();
model.beginUpdate();
try
{
graph.setCellStyles('fillColor', '#dae8fc', cells);
graph.setCellStyles('strokeColor', '#6c8ebf', cells);
graph.setCellStyles('dashed', null, cells); // null removes the key
}
finally
{
model.endUpdate();
}// Current page: vertices and edges in z-order
var cells = model.filterDescendants(function(cell)
{
return model.isVertex(cell) || model.isEdge(cell);
});
// All pages (read only)
for (var i = 0; i < ui.pages.length; i++)
{
var root = ui.updatePageRoot(ui.pages[i]).root;
var stack = [root];
while (stack.length > 0)
{
var cell = stack.pop();
// ... cell.vertex, cell.edge, cell.value, cell.style
for (var j = 0; j < cell.getChildCount(); j++)
{
stack.push(cell.getChildAt(j));
}
}
}function findByAttribute(name, value)
{
return model.filterDescendants(function(cell)
{
return mxUtils.isNode(cell.value) && cell.getAttribute(name) == value;
});
}
graph.setSelectionCells(findByAttribute('owner', 'ops'));var doc = mxUtils.createXmlDocument();
var obj = doc.createElement('object');
obj.setAttribute('label', 'web-01');
obj.setAttribute('owner', 'ops');
var v = graph.insertVertex(graph.getDefaultParent(), null, obj,
40, 160, 120, 60, 'whiteSpace=wrap;html=1;');
graph.setAttributeForCell(v, 'ip', '10.0.0.1'); // undoable
graph.getAttributeForCell(v, 'ip'); // '10.0.0.1'model.addListener(mxEvent.CHANGE, function(sender, evt)
{
var changes = evt.getProperty('edit').changes;
for (var i = 0; i < changes.length; i++)
{
var change = changes[i];
if (change instanceof mxChildChange && change.previous == null)
{
console.log('added', change.child.id);
}
else if (change instanceof mxChildChange && change.parent == null)
{
console.log('removed', change.child.id);
}
else if (change instanceof mxValueChange || change instanceof mxStyleChange)
{
console.log('changed', change.cell.id);
}
}
});
graph.getSelectionModel().addListener(mxEvent.CHANGE, function()
{
console.log('selected', graph.getSelectionCount());
});// null creates the group cell; edges between the cells move into the group
var group = graph.groupCells(null, 0, graph.getSelectionCells());
graph.setSelectionCell(group);
// Later: moves the children to the group's parent and removes the empty group
graph.setSelectionCells(graph.ungroupCells([group]));// Current page as an <mxGraphModel> string
var xml = mxUtils.getXml(ui.editor.getGraphXml());
// Whole file (all pages) as it would be saved
var file = ui.getFileData(true);
// Replace the current page (undoable)
ui.replaceDiagramData(xml);
// Insert cells from XML, offset by 20px (undoable)
var inserted = ui.importXml('<mxGraphModel><root><mxCell id="0"/>' +
'<mxCell id="1" parent="0"/><mxCell id="a" value="Imported" ' +
'style="whiteSpace=wrap;html=1;" vertex="1" parent="1">' +
'<mxGeometry x="0" y="0" width="120" height="60" as="geometry"/>' +
'</mxCell></root></mxGraphModel>', 20, 20);importXml inserts copies with new IDs (links between the imported cells are updated). replaceDiagramData decodes new cell objects, so look up cells again with model.getCell(id) afterwards.
var page = ui.insertPage(ui.createPage('Details', ui.createPageId()));
// The new page is now current: graph.model holds its (empty) root
graph.insertVertex(graph.getDefaultParent(), null, 'On page 2', 40, 40, 120, 60);
ui.selectPage(ui.pages[0]);graph.moveCells(graph.getSelectionCells(), 20, 0); // dx, dy
var cell = graph.getSelectionCell();
var geo = graph.getCellGeometry(cell).clone();
geo.width = 200;
graph.resizeCells([cell], [geo]); // fires RESIZE_CELLS, which layouts react to- File format: how the model is stored.
- Styles: style strings, the stylesheet and style keys.
- Plugins and Extending the editor: where this code runs.
-
Architecture and Code map: how
mxGraph,grapheditoranddiagramlyfit together. - Shapes and stencils: custom shapes.
- Security: handling labels and links safely.
Describes the dev branch of jgraph/drawio as of release 32.3.0 (October 2026). Internal JavaScript APIs change between releases; check the code of the version you use. · Questions: Discussions · Bugs: Issues · Vulnerabilities: report privately · User docs: drawio.com/docs
Get started
Concepts
Guides
- Self-hosting
- Configuration
- Storage backends
- OneDrive app registration
- Embedding
- Diagrams in GitHub
- Plugins
- Extending the editor
- Shapes and stencils
- Import and export
- Desktop app
- Internationalization
Reference
Project
Elsewhere