or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

binding-animation.mdcore-diagram.mddata-models.mdgeometry-collections.mdgraphobject-hierarchy.mdindex.mdinteractive-tools.mdlayout-system.mdtheme-management.mdvisual-elements.md
tile.json

data-models.mddocs/

Data Models

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.

Capabilities

Model (Abstract Base)

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;
}

GraphLinksModel extends Model

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' }
];

TreeModel extends Model

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');

Data Binding Integration

Binding Class

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})`)
);

Model Events and Change Tracking

ChangedEvent

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');

Common Patterns

Model Serialization

// 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);
  }
});

Dynamic Model Updates

// 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');
}

Model Validation

// 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)
  );
};