GoJS uses a model-driven architecture where diagram content is defined by data models that are automatically synchronized with the visual diagram through data binding. Models provide change tracking, undo/redo support, and JSON serialization.
Base class for all diagram data models, providing core data management and change tracking functionality.
abstract class Model {
/**
* Creates a new Model.
* @param init - Optional initialization properties
*/
constructor(init?: Partial<Model>);
// Data Arrays
nodeDataArray: ObjectData[];
// Configuration
dataFormat: string;
nodeKeyProperty: string;
nodeCategoryProperty: string;
copyNodeDataFunction: ((data: ObjectData, model: Model) => ObjectData) | null;
// Validation
nodeFilter: ((data: ObjectData) => boolean) | null;
// Change Events
addChangedListener(listener: (e: ChangedEvent) => void): void;
removeChangedListener(listener: (e: ChangedEvent) => void): void;
// Node Data Management
addNodeData(obj: ObjectData): void;
removeNodeData(obj: ObjectData): void;
containsNodeData(obj: ObjectData): boolean;
findNodeDataForKey(key: any): ObjectData | null;
getKeyForNodeData(data: ObjectData): any;
getCategoryForNodeData(data: ObjectData): string;
// Property Management
setDataProperty(obj: ObjectData, propname: string, val: any): void;
getDataProperty(obj: ObjectData, propname: string): any;
// Serialization
toJson(): string;
toIncrementalJson(e: ChangedEvent): string;
fromJson(str: string): void;
fromIncrementalJson(str: string): void;
// Transactions
startTransaction(tname?: string): boolean;
commitTransaction(tname?: string): boolean;
rollbackTransaction(): boolean;
// State
isReadOnly: boolean;
skipsUndoManager: boolean;
undoManager: UndoManager | null;
}Model for diagrams with nodes and separate link objects, supporting complex many-to-many relationships.
class GraphLinksModel extends Model {
/**
* Creates a new GraphLinksModel.
* @param nodeDataArray - Array of node data objects
* @param linkDataArray - Array of link data objects
* @param init - Optional initialization properties
*/
constructor(
nodeDataArray?: ObjectData[],
linkDataArray?: ObjectData[],
init?: Partial<GraphLinksModel>
);
// Link Data Management
linkDataArray: ObjectData[];
linkKeyProperty: string;
linkFromKeyProperty: string;
linkToKeyProperty: string;
linkFromPortIdProperty: string;
linkToPortIdProperty: string;
linkCategoryProperty: string;
linkLabelKeysProperty: string;
// Link Operations
addLinkData(obj: ObjectData): void;
removeLinkData(obj: ObjectData): void;
containsLinkData(obj: ObjectData): boolean;
findLinkDataForKey(key: any): ObjectData | null;
getKeyForLinkData(data: ObjectData): any;
getFromKeyForLinkData(data: ObjectData): any;
getToKeyForLinkData(data: ObjectData): any;
getFromPortIdForLinkData(data: ObjectData): string;
getToPortIdForLinkData(data: ObjectData): string;
getCategoryForLinkData(data: ObjectData): string;
// Link Queries
findLinksForNode(nodedata: ObjectData): ObjectData[];
findLinksInto(nodedata: ObjectData): ObjectData[];
findLinksOutOf(nodedata: ObjectData): ObjectData[];
// Validation
linkFilter: ((data: ObjectData) => boolean) | null;
copyLinkDataFunction: ((data: ObjectData, model: Model) => ObjectData) | null;
// Group Management (for nodes that act as groups)
nodeGroupKeyProperty: string;
getGroupKeyForNodeData(data: ObjectData): any;
setGroupKeyForNodeData(data: ObjectData, key: any): void;
// Member Queries
findMemberNodeData(groupdata: ObjectData): ObjectData[];
// Validation Functions
isLinkValid(fromdata: ObjectData, todata: ObjectData, linkdata: ObjectData): boolean;
}Usage Examples:
// Basic GraphLinksModel setup
const model = new go.GraphLinksModel([
{ key: 1, text: 'Node 1', color: 'lightblue' },
{ key: 2, text: 'Node 2', color: 'lightgreen' },
{ key: 3, text: 'Node 3', color: 'pink' }
], [
{ from: 1, to: 2, text: 'Link 1-2' },
{ from: 2, to: 3, text: 'Link 2-3' },
{ from: 1, to: 3, text: 'Link 1-3' }
]);
// Custom property configuration
const customModel = new go.GraphLinksModel({
nodeKeyProperty: 'id',
nodeCategoryProperty: 'type',
linkFromKeyProperty: 'source',
linkToKeyProperty: 'target',
linkKeyProperty: 'linkId'
});
customModel.nodeDataArray = [
{ id: 'start', type: 'start', text: 'Start' },
{ id: 'process', type: 'process', text: 'Process Data' },
{ id: 'end', type: 'end', text: 'End' }
];
customModel.linkDataArray = [
{ linkId: 'l1', source: 'start', target: 'process' },
{ linkId: 'l2', source: 'process', target: 'end' }
];
// Model with groups
const groupModel = new go.GraphLinksModel({
nodeGroupKeyProperty: 'group'
});
groupModel.nodeDataArray = [
{ key: 'group1', text: 'Group 1', isGroup: true },
{ key: 'a', text: 'Node A', group: 'group1' },
{ key: 'b', text: 'Node B', group: 'group1' },
{ key: 'c', text: 'Node C' }
];
groupModel.linkDataArray = [
{ from: 'a', to: 'b' },
{ from: 'b', to: 'c' }
];Specialized model for hierarchical tree structures with parent-child relationships.
class TreeModel extends Model {
/**
* Creates a new TreeModel.
* @param nodeDataArray - Array of node data objects
* @param init - Optional initialization properties
*/
constructor(nodeDataArray?: ObjectData[], init?: Partial<TreeModel>);
// Tree Structure Properties
nodeParentKeyProperty: string;
nodeChildrenProperty: string;
// Tree Operations
getParentKeyForNodeData(data: ObjectData): any;
setParentKeyForNodeData(data: ObjectData, key: any): void;
getChildrenForNodeData(data: ObjectData): ObjectData[];
// Tree Queries
findTreeRoots(): ObjectData[];
findTreeParentNode(nodedata: ObjectData): ObjectData | null;
findTreeChildrenNodes(nodedata: ObjectData): ObjectData[];
findTreeLevel(nodedata: ObjectData): number;
// Tree Modifications
addNodeData(obj: ObjectData): void;
removeNodeData(obj: ObjectData): void;
}Usage Examples:
// Basic tree model
const treeModel = new go.TreeModel([
{ key: 1, text: 'Root' },
{ key: 2, text: 'Child 1', parent: 1 },
{ key: 3, text: 'Child 2', parent: 1 },
{ key: 4, text: 'Grandchild 1', parent: 2 },
{ key: 5, text: 'Grandchild 2', parent: 2 }
]);
// Tree model with custom properties
const orgChart = new go.TreeModel({
nodeParentKeyProperty: 'boss',
nodeKeyProperty: 'id'
});
orgChart.nodeDataArray = [
{ id: 'ceo', name: 'CEO', title: 'Chief Executive Officer' },
{ id: 'vp1', name: 'VP Sales', title: 'Vice President Sales', boss: 'ceo' },
{ id: 'vp2', name: 'VP Dev', title: 'Vice President Development', boss: 'ceo' },
{ id: 'mgr1', name: 'Sales Mgr', title: 'Sales Manager', boss: 'vp1' },
{ id: 'dev1', name: 'Developer 1', title: 'Senior Developer', boss: 'vp2' },
{ id: 'dev2', name: 'Developer 2', title: 'Junior Developer', boss: 'vp2' }
];
// Adding nodes to tree dynamically
orgChart.startTransaction('add employee');
orgChart.addNodeData({
id: 'intern1',
name: 'Intern',
title: 'Development Intern',
boss: 'dev1'
});
orgChart.commitTransaction('add employee');Connects model data properties to GraphObject visual properties with automatic updates.
class Binding {
/**
* Creates a new Binding.
* @param targetProperty - GraphObject property to bind to
* @param sourceProperty - Model data property to bind from (defaults to targetProperty)
*/
constructor(targetProperty: string, sourceProperty?: string);
name: string; // Source property name
property: string; // Target property name
converter?: (val: any, obj: GraphObject) => any; // Value conversion function
mode: BindingMode; // OneWay or TwoWay
// Fluent API
transform(converter: (val: any, obj: GraphObject) => any): Binding;
makeTwoWay(): Binding;
}
enum BindingMode {
OneWay = 'OneWay',
TwoWay = 'TwoWay'
}Usage Examples:
// Basic data binding
const nodeTemplate = new go.Node('Auto')
.add(
new go.Shape('RoundedRectangle')
.bind('fill', 'color'), // bind fill to data.color
new go.TextBlock()
.bind('text', 'name') // bind text to data.name
.bind('font', 'isImportant', (val) => // conditional formatting
val ? 'bold 14pt sans-serif' : '12pt sans-serif'
)
);
// Two-way binding for editable text
const editableTemplate = new go.Node('Auto')
.add(
new go.Shape('Rectangle', { fill: 'white', stroke: 'black' }),
new go.TextBlock({
editable: true,
margin: 4
})
.bindTwoWay('text', 'name') // changes update the model
);
// Complex binding with conversion
const statusTemplate = new go.Node('Auto')
.add(
new go.Shape('RoundedRectangle')
.bind('fill', 'status', (status) => {
switch (status) {
case 'active': return 'lightgreen';
case 'inactive': return 'lightgray';
case 'error': return 'lightcoral';
default: return 'white';
}
}),
new go.TextBlock()
.bind('text', '', (data) => `${data.name} (${data.status})`)
);Represents changes to model data for undo/redo and data binding updates.
class ChangedEvent {
model: Model;
change: ChangeType;
modelChange: string;
isTransactionFinished: boolean;
propertyName: string;
object: any;
oldValue: any;
newValue: any;
oldParam: any;
newParam: any;
}
enum ChangeType {
Transaction = 'Transaction',
Property = 'Property',
Insert = 'Insert',
Remove = 'Remove'
}Usage Examples:
// Listen to model changes
model.addChangedListener((e) => {
if (e.change === go.ChangeType.Property) {
console.log(`Property ${e.propertyName} changed from ${e.oldValue} to ${e.newValue}`);
}
if (e.change === go.ChangeType.Insert && e.modelChange === 'nodeDataArray') {
console.log('Node added:', e.newValue);
}
if (e.isTransactionFinished) {
console.log('Transaction completed');
saveModelToServer(model.toJson());
}
});
// Programmatically modify model data
model.startTransaction('update status');
model.setDataProperty(nodeData, 'status', 'completed');
model.setDataProperty(nodeData, 'completedDate', new Date());
model.commitTransaction('update status');// Save model state
const jsonData = model.toJson();
localStorage.setItem('diagram-data', jsonData);
// Load model state
const savedData = localStorage.getItem('diagram-data');
if (savedData) {
model.fromJson(savedData);
}
// Incremental updates for real-time collaboration
model.addChangedListener((e) => {
if (e.isTransactionFinished) {
const incrementalJson = model.toIncrementalJson(e);
sendToServer(incrementalJson);
}
});// Add nodes and links dynamically
function addEmployee(bossKey: any, employeeData: ObjectData) {
model.startTransaction('add employee');
// Add the new employee node
employeeData.boss = bossKey;
model.addNodeData(employeeData);
model.commitTransaction('add employee');
}
// Remove node and all connected links (GraphLinksModel only)
function removeNodeAndLinks(nodeData: ObjectData) {
model.startTransaction('remove node');
// Remove all links connected to this node
const links = model.findLinksForNode(nodeData);
links.forEach(linkData => model.removeLinkData(linkData));
// Remove the node
model.removeNodeData(nodeData);
model.commitTransaction('remove node');
}// Custom validation functions
const validatedModel = new go.GraphLinksModel({
nodeFilter: (data) => {
// Only allow nodes with required properties
return data.hasOwnProperty('id') && data.hasOwnProperty('text');
},
linkFilter: (data) => {
// Only allow links with valid from/to keys
return data.hasOwnProperty('from') && data.hasOwnProperty('to');
}
});
// Override isLinkValid for custom link validation
validatedModel.isLinkValid = (fromdata, todata, linkdata) => {
// Prevent self-links
if (fromdata === todata) return false;
// Prevent duplicate links
const existingLinks = validatedModel.findLinksOutOf(fromdata);
return !existingLinks.some(link =>
validatedModel.getToKeyForLinkData(link) === validatedModel.getKeyForNodeData(todata)
);
};