├── src ├── styles.css ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── favicon.ico ├── app │ ├── app.config.ts │ ├── inspector │ │ ├── inspector-row.component.html │ │ ├── inspector.component.html │ │ ├── inspector.component.css │ │ ├── inspector.component.ts │ │ └── inspector-row.component.ts │ ├── app.component.css │ ├── app.component.html │ └── app.component.ts ├── tsconfig.app.json ├── main.ts ├── tsconfig.spec.json ├── index.html ├── browserslist ├── test.ts └── karma.conf.js ├── LICENSE.md ├── tsconfig.app.json ├── .github └── ISSUE_TEMPLATE │ └── issue-with-gojs-angular-basic.md ├── tsconfig.json ├── .gitignore ├── package.json ├── README.md └── angular.json /src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NorthwoodsSoftware/gojs-angular-basic/HEAD/src/favicon.ico -------------------------------------------------------------------------------- /src/app/app.config.ts: -------------------------------------------------------------------------------- 1 | import { ApplicationConfig } from '@angular/core'; 2 | 3 | export const appConfig: ApplicationConfig = { 4 | providers: [], 5 | }; 6 | -------------------------------------------------------------------------------- /src/app/inspector/inspector-row.component.html: -------------------------------------------------------------------------------- 1 | {{id}} 2 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "types": [] 6 | }, 7 | "exclude": [ 8 | "test.ts", 9 | "**/*.spec.ts" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | This project is intended to be used alongside [GoJS](https://gojs.net/latest/index.html), 2 | and is covered by the GoJS software license. 3 | 4 | Copyright 1998-2024 by Northwoods Software Corporation. -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { bootstrapApplication } from '@angular/platform-browser'; 2 | import { appConfig } from './app/app.config'; 3 | import { AppComponent } from './app/app.component'; 4 | 5 | bootstrapApplication(AppComponent, appConfig).catch((err) => console.error(err)); 6 | -------------------------------------------------------------------------------- /src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "test.ts" 12 | ], 13 | "include": [ 14 | "**/*.spec.ts", 15 | "**/*.d.ts" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/app", 6 | "types": [] 7 | }, 8 | "files": ["src/main.ts"], 9 | "include": ["src/**/*.d.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Angular GoJS Component 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/browserslist: -------------------------------------------------------------------------------- 1 | # This file is currently used by autoprefixer to adjust CSS to support the below specified browsers 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | # 5 | # For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed 6 | 7 | > 0.5% 8 | last 2 versions 9 | Firefox ESR 10 | not dead 11 | not IE 9-11 -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/issue-with-gojs-angular-basic.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Issue with GoJS-Angular-Basic 3 | about: For issues related to this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | Please only report potential bugs or suggestions for gojs-angular itself. 11 | 12 | For GoJS help and support generally, please use the GoJS forum: https://forum.nwoods.com/c/gojs 13 | 14 | Or Contact Us: https://www.nwoods.com/contact.html 15 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "module": "es2015", 9 | "moduleResolution": "node", 10 | "emitDecoratorMetadata": true, 11 | "experimentalDecorators": true, 12 | "importHelpers": true, 13 | "target": "es2022", 14 | "typeRoots": ["node_modules/@types"], 15 | "lib": ["es2022", "dom"] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/app/inspector/inspector.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 10 | 11 | 12 |
13 |
14 | 15 |
Select a Node to use Inspector
16 | -------------------------------------------------------------------------------- /src/app/inspector/inspector.component.css: -------------------------------------------------------------------------------- 1 | * { 2 | padding: 5px; 3 | font-family: Arial, Helvetica, sans-serif; 4 | font-weight: 600; 5 | margin: 10px; 6 | width: fit-content; 7 | } 8 | 9 | form { 10 | background: lightgray; 11 | border: 1px solid black; 12 | } 13 | 14 | input { 15 | clear: both; 16 | display: inherit; 17 | float: initial; 18 | font-weight: 300; 19 | border-radius: 10px; 20 | background: white; 21 | } 22 | 23 | input:focus { 24 | border-radius: 10px; 25 | outline: none; 26 | } 27 | -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import { getTestBed } from '@angular/core/testing'; 4 | import { 5 | BrowserDynamicTestingModule, 6 | platformBrowserDynamicTesting 7 | } from '@angular/platform-browser-dynamic/testing'; 8 | 9 | declare const require: any; 10 | 11 | // First, initialize the Angular testing environment. 12 | getTestBed().initTestEnvironment( 13 | BrowserDynamicTestingModule, 14 | platformBrowserDynamicTesting() 15 | ); 16 | // Then we find all the tests. 17 | const context = require.context('./', true, /\.spec\.ts$/); 18 | // And load the modules. 19 | context.keys().map(context); 20 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | }; 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /src/app/inspector/inspector.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Input, Output } from '@angular/core'; 2 | import * as go from 'gojs'; 3 | import { CommonModule } from '@angular/common'; 4 | import { InspectorRowComponent } from './inspector-row.component'; 5 | 6 | @Component({ 7 | selector: 'app-inspector', 8 | imports: [CommonModule, InspectorRowComponent], 9 | templateUrl: './inspector.component.html', 10 | styleUrls: ['./inspector.component.css'], 11 | }) 12 | export class InspectorComponent { 13 | @Input() 14 | public nodeData: go.ObjectData; 15 | 16 | @Output() 17 | public onInspectorChange: EventEmitter = new EventEmitter(); 18 | 19 | constructor() {} 20 | 21 | public onInputChange(propAndValObj: any) { 22 | this.onInspectorChange.emit(propAndValObj); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | .angular 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | # Only exists if Bazel was run 8 | /bazel-out 9 | 10 | # dependencies 11 | /node_modules 12 | 13 | # profiling files 14 | chrome-profiler-events.json 15 | speed-measure-plugin.json 16 | 17 | # IDEs and editors 18 | /.idea 19 | .project 20 | .classpath 21 | .c9/ 22 | *.launch 23 | .settings/ 24 | *.sublime-workspace 25 | 26 | # IDE - VSCode 27 | .vscode/* 28 | !.vscode/settings.json 29 | !.vscode/tasks.json 30 | !.vscode/launch.json 31 | !.vscode/extensions.json 32 | .history/* 33 | 34 | # misc 35 | /.sass-cache 36 | /connect.lock 37 | /coverage 38 | /libpeerconnection.log 39 | npm-debug.log 40 | yarn-error.log 41 | testem.log 42 | /typings 43 | 44 | # System Files 45 | .DS_Store 46 | Thumbs.db 47 | -------------------------------------------------------------------------------- /src/app/inspector/inspector-row.component.ts: -------------------------------------------------------------------------------- 1 | import { Input } from '@angular/core'; 2 | import { Component, EventEmitter, Output } from '@angular/core'; 3 | import { FormsModule } from '@angular/forms'; 4 | 5 | @Component({ 6 | selector: 'app-inspector-row', 7 | templateUrl: './inspector-row.component.html', 8 | imports: [FormsModule], 9 | }) 10 | export class InspectorRowComponent { 11 | @Input() 12 | public id: string; 13 | 14 | @Input() 15 | public value: string; 16 | 17 | @Output() 18 | public onInputChangeEmitter: EventEmitter = new EventEmitter(); 19 | 20 | constructor() {} 21 | 22 | public onInputChange(e: any) { 23 | // when is changed, emit an Object up, with what property changed, and to what new value 24 | this.onInputChangeEmitter.emit({ prop: this.id, newVal: e.target.value }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gojs-angular-basic", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "NG_BUILD_PARALLEL_TS=0 ng serve", 7 | "build": "ng build", 8 | "watch": "ng build --watch --configuration development" 9 | }, 10 | "private": true, 11 | "dependencies": { 12 | "@angular/common": "^20.0.6", 13 | "@angular/compiler": "^20.0.6", 14 | "@angular/core": "^20.0.6", 15 | "@angular/forms": "^20.0.6", 16 | "@angular/platform-browser": "^20.0.6", 17 | "@angular/router": "^20.0.6", 18 | "gojs": "^3.0.18", 19 | "gojs-angular": "^2.1.3", 20 | "rxjs": "~7.8.0", 21 | "tslib": "^2.3.0", 22 | "zone.js": "~0.15.0" 23 | }, 24 | "devDependencies": { 25 | "@angular/build": "^20.0.5", 26 | "@angular/cli": "^20.0.5", 27 | "@angular/compiler-cli": "^20.0.6", 28 | "typescript": "~5.8.3" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/app/app.component.css: -------------------------------------------------------------------------------- 1 | @import url(http://fonts.googleapis.com/css?family=Roboto:400,400italic,500,500italic,700,700italic,900,900italic,300italic,300,100italic,100); 2 | 3 | body { 4 | font-family: 'Roboto', sans-serif; 5 | } 6 | 7 | .myDiagramDiv { 8 | background: whitesmoke; 9 | width: 700px; 10 | height: 300px; 11 | border: 1px solid black; 12 | } 13 | 14 | .myPaletteDiv { 15 | background: lightcyan; 16 | border: 1px solid navy; 17 | width: 125px; 18 | height: 300px; 19 | } 20 | 21 | .myOverviewDiv { 22 | width: 200px; 23 | height: 150px; 24 | background: whitesmoke; 25 | border: 1px solid black; 26 | } 27 | 28 | .left { 29 | float: left; 30 | padding: 5px; 31 | margin: 5px; 32 | } 33 | 34 | .box { 35 | border: solid 1px black; 36 | background: whitesmoke; 37 | box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); 38 | } 39 | 40 | .clear { 41 | clear: both; 42 | } -------------------------------------------------------------------------------- /src/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | // list of files / patterns to load in the browser 7 | files: [ 8 | '.node_modules/gojs/release' 9 | //..rest files 10 | ], 11 | basePath: '', 12 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 13 | plugins: [ 14 | require('karma-jasmine'), 15 | require('karma-chrome-launcher'), 16 | require('karma-jasmine-html-reporter'), 17 | require('karma-coverage-istanbul-reporter'), 18 | require('@angular-devkit/build-angular/plugins/karma') 19 | ], 20 | client: { 21 | clearContext: false // leave Jasmine Spec Runner output visible in browser 22 | }, 23 | coverageIstanbulReporter: { 24 | dir: require('path').join(__dirname, '../coverage/angular-tutorial'), 25 | reports: ['html', 'lcovonly', 'text-summary'], 26 | fixWebpackSourcePaths: true 27 | }, 28 | reporters: ['progress', 'kjhtml'], 29 | port: 9876, 30 | colors: true, 31 | logLevel: config.LOG_INFO, 32 | autoWatch: true, 33 | browsers: ['Chrome'], 34 | singleRun: false, 35 | restartOnFileChange: true 36 | }); 37 | }; 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gojs-angular-basic 2 | 3 | ### By Northwoods Software for [GoJS](https://gojs.net) 4 | 5 | This project provides a basic example of using GoJS in an Angular app. 6 | Check out the [Intro page on using GoJS with Angular](https://gojs.net/latest/intro/angular.html) for more information. 7 | 8 | It makes use of the [gojs-angular](https://github.com/NorthwoodsSoftware/gojs-angular) package to handle setting up Diagram, Palette and Overview components. 9 | 10 | When running the sample, try moving / adding / deleting nodes, editing text in the inspector, relinking nodes via ports, undoing (Ctrl-Z), and re-doing (Ctrl-Y) within the diagram. You'll notice the changes are reflected in app-level data. You'll also notice that changes 11 | made in the inspector are reflected in the diagram. 12 | 13 | ## Installation 14 | 15 | This sample works with GoJS v2.3. 16 | If you want to use GoJS v3.0, be sure to use at least version 3.0.6. 17 | 18 | Start by running npm install to install all necessary dependencies. 19 | 20 | If you do not have Angular already globally installed, you may do so by running: 21 | 22 | ### `npm i -g @angular/cli` 23 | 24 | ## Running the project 25 | 26 | In the project directory, run: 27 | 28 | ### `ng serve` 29 | 30 | Alternatively, if you do not have Angular installed globally, you may run: 31 | 32 | ### `npm run start` 33 | 34 | Runs the app in the development mode.
35 | Open [http://localhost:4200](http://localhost:4200) to view it in the browser. 36 | 37 | The page will reload if you make edits.
38 | 39 | ## Learn More 40 | 41 | To learn Angular, check out [Angular's official site](https://angular.io/). 42 | To learn GoJS, check out [gojs.net](https://gojs.net). 43 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 |

GoJS / Angular - Components Sample

2 | 3 |
4 | Palette 5 | 11 |
12 | 13 |
14 | Diagram 15 | 24 | 25 |
26 | 27 |
28 | Overview 29 | 34 |
35 | 36 |
37 | 40 |
41 | 42 |
43 |

Links in app data

44 |
    45 |
  • 46 | Link Key: {{ ld.key }}, To: {{ ld.to }}, From: {{ ld.from }} 47 |
  • 48 |
49 |
50 | 51 |
52 |

Nodes in app data

53 |
    54 |
  • 55 | Node Key: {{ nd.key }}, Text: {{ nd.text }}, Color: {{ nd.color }}, Loc: {{ nd.loc }} 56 |
  • 57 |
58 |
59 | 60 |
61 |

62 | This gojs-angular sample demonstrates how to use the Diagram, Palette, and Overview 63 | gojs-angular components, made and 64 | maintained by Northwoods Software. 65 |

66 |

67 | Notice how when a Node or Link is removed or added to the Diagram, the list of Nodes and Links 68 | the top-level App Component updates accordingly. 69 |

70 |

71 | For more information on how to use Angular with GoJS, see the 72 | GoJS with Angular intro page. 73 |

74 |
75 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "first-app": { 7 | "projectType": "application", 8 | "schematics": { 9 | "@schematics/angular:component": { 10 | "inlineTemplate": true, 11 | "inlineStyle": true, 12 | "style": "scss", 13 | "skipTests": true 14 | }, 15 | "@schematics/angular:class": { 16 | "skipTests": true 17 | }, 18 | "@schematics/angular:directive": { 19 | "skipTests": true 20 | }, 21 | "@schematics/angular:guard": { 22 | "skipTests": true 23 | }, 24 | "@schematics/angular:interceptor": { 25 | "skipTests": true 26 | }, 27 | "@schematics/angular:pipe": { 28 | "skipTests": true 29 | }, 30 | "@schematics/angular:resolver": { 31 | "skipTests": true 32 | }, 33 | "@schematics/angular:service": { 34 | "skipTests": true 35 | } 36 | }, 37 | "root": "", 38 | "sourceRoot": "src", 39 | "prefix": "app", 40 | "architect": { 41 | "build": { 42 | "builder": "@angular/build:application", 43 | "options": { 44 | "outputPath": "dist/first-app", 45 | "index": "src/index.html", 46 | "browser": "src/main.ts", 47 | "polyfills": ["zone.js"], 48 | "tsConfig": "tsconfig.app.json", 49 | "inlineStyleLanguage": "scss", 50 | "assets": [ 51 | { 52 | "glob": "**/*", 53 | "input": "src/public" 54 | } 55 | ], 56 | "styles": ["src/styles.css"], 57 | "scripts": [] 58 | }, 59 | "configurations": { 60 | "production": { 61 | "budgets": [ 62 | { 63 | "type": "initial", 64 | "maximumWarning": "1.5MB", 65 | "maximumError": "2MB" 66 | }, 67 | { 68 | "type": "anyComponentStyle", 69 | "maximumWarning": "100kB", 70 | "maximumError": "200kB" 71 | } 72 | ], 73 | "outputHashing": "all" 74 | }, 75 | "development": { 76 | "optimization": false, 77 | "extractLicenses": false, 78 | "sourceMap": true 79 | } 80 | }, 81 | "defaultConfiguration": "production" 82 | }, 83 | "serve": { 84 | "builder": "@angular/build:dev-server", 85 | "configurations": { 86 | "production": { 87 | "buildTarget": "first-app:build:production" 88 | }, 89 | "development": { 90 | "buildTarget": "first-app:build:development" 91 | } 92 | }, 93 | "defaultConfiguration": "development" 94 | }, 95 | "extract-i18n": { 96 | "builder": "@angular/build:extract-i18n" 97 | } 98 | } 99 | } 100 | }, 101 | "schematics": { 102 | "@schematics/angular:component": { 103 | "type": "component" 104 | }, 105 | "@schematics/angular:directive": { 106 | "type": "directive" 107 | }, 108 | "@schematics/angular:service": { 109 | "type": "service" 110 | }, 111 | "@schematics/angular:guard": { 112 | "typeSeparator": "." 113 | }, 114 | "@schematics/angular:interceptor": { 115 | "typeSeparator": "." 116 | }, 117 | "@schematics/angular:module": { 118 | "typeSeparator": "." 119 | }, 120 | "@schematics/angular:pipe": { 121 | "typeSeparator": "." 122 | }, 123 | "@schematics/angular:resolver": { 124 | "typeSeparator": "." 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Sample app showcasing gojs-angular components 3 | * For use with gojs-angular version 2.x, assuming immutable data 4 | * This now uses GoJS version 3.0, using some of its new features, 5 | * but your app could use GoJS version 2.3.17, if you don't yet want to upgrade to v3. 6 | */ 7 | 8 | import { ChangeDetectorRef, Component, ViewChild, ViewEncapsulation } from '@angular/core'; 9 | import * as go from 'gojs'; 10 | import { 11 | DataSyncService, 12 | DiagramComponent, 13 | OverviewComponent, 14 | PaletteComponent, 15 | } from 'gojs-angular'; 16 | import { produce } from 'immer'; 17 | import { InspectorComponent } from './inspector/inspector.component'; 18 | import { CommonModule } from '@angular/common'; 19 | 20 | @Component({ 21 | selector: 'app-root', 22 | templateUrl: './app.component.html', 23 | styleUrls: ['./app.component.css'], 24 | imports: [ 25 | DiagramComponent, 26 | PaletteComponent, 27 | InspectorComponent, 28 | OverviewComponent, 29 | CommonModule, 30 | ], 31 | encapsulation: ViewEncapsulation.ShadowDom, 32 | }) 33 | export class AppComponent { 34 | @ViewChild('myDiagram', { static: true }) public myDiagramComponent: DiagramComponent; 35 | @ViewChild('myPalette', { static: true }) public myPaletteComponent: PaletteComponent; 36 | 37 | // Big object that holds app-level state data 38 | // As of gojs-angular 2.0, immutability is expected and required of state for ease of change detection. 39 | // Whenever updating state, immutability must be preserved. It is recommended to use immer for this, a small package that makes working with immutable data easy. 40 | public state = { 41 | // Diagram state props 42 | diagramNodeData: [ 43 | { key: 'Alpha', text: 'Alpha', color: 'lightblue', loc: '0 0' }, 44 | { key: 'Beta', text: 'Beta', color: 'orange', loc: '150 0' }, 45 | { key: 'Gamma', text: 'Gamma', color: 'lightgreen', loc: '0 100' }, 46 | { key: 'Delta', text: 'Delta', color: 'pink', loc: '100 100' }, 47 | ], 48 | diagramLinkData: [ 49 | { key: -1, from: 'Alpha', to: 'Beta', fromPort: 'r', toPort: 'l' }, 50 | { key: -2, from: 'Alpha', to: 'Gamma', fromPort: 'b', toPort: 't' }, 51 | { key: -3, from: 'Beta', to: 'Beta' }, 52 | { key: -4, from: 'Gamma', to: 'Delta', fromPort: 'r', toPort: 'l' }, 53 | { key: -5, from: 'Delta', to: 'Alpha', fromPort: 't', toPort: 'r' }, 54 | ], 55 | diagramModelData: { prop: 'value' }, 56 | skipsDiagramUpdate: false, 57 | selectedNodeData: null, // used by InspectorComponent 58 | 59 | // Palette state props 60 | paletteNodeData: [ 61 | { key: 'Epsilon', text: 'Epsilon', color: 'moccasin' }, 62 | { key: 'Kappa', text: 'Kappa', color: 'lavender' }, 63 | ], 64 | paletteModelData: { prop: 'val' }, 65 | }; 66 | 67 | public diagramDivClassName = 'myDiagramDiv'; 68 | public paletteDivClassName = 'myPaletteDiv'; 69 | 70 | // initialize diagram / templates 71 | public initDiagram(): go.Diagram { 72 | const diagram = new go.Diagram({ 73 | 'commandHandler.archetypeGroupData': { key: 'Group', isGroup: true }, 74 | 'clickCreatingTool.archetypeNodeData': { text: 'new node', color: 'lightblue' }, 75 | 'undoManager.isEnabled': true, 76 | model: new go.GraphLinksModel({ 77 | linkToPortIdProperty: 'toPort', // want to support multiple ports per node 78 | linkFromPortIdProperty: 'fromPort', 79 | linkKeyProperty: 'key', // IMPORTANT! must be defined for merges and data sync when using GraphLinksModel 80 | }), 81 | }); 82 | 83 | // a helper function for defining multiple ports in node templates 84 | function makePort(id: string, spot: go.Spot) { 85 | return new go.Shape('Circle', { 86 | desiredSize: new go.Size(8, 8), 87 | opacity: 0.5, 88 | fill: 'gray', 89 | strokeWidth: 0, 90 | portId: id, 91 | alignment: spot, 92 | fromSpot: spot, 93 | toSpot: spot, 94 | fromLinkable: true, 95 | toLinkable: true, 96 | cursor: 'pointer', 97 | }); 98 | } 99 | 100 | // define the Node template 101 | diagram.nodeTemplate = new go.Node('Spot', { 102 | contextMenu: (go.GraphObject.build('ContextMenu') as go.Adornment).add( 103 | (go.GraphObject.build('ContextMenuButton') as go.Panel).add( 104 | new go.TextBlock('Group', { 105 | click: (e, obj) => e.diagram.commandHandler.groupSelection(), 106 | }) 107 | ) 108 | ), 109 | }) 110 | .bindTwoWay('location', 'loc', go.Point.parse, go.Point.stringifyFixed(1)) 111 | .add( 112 | new go.Panel('Auto').add( 113 | new go.Shape('RoundedRectangle', { strokeWidth: 0.5 }).bind('fill', 'color'), 114 | new go.TextBlock({ margin: 8, editable: true }).bindTwoWay('text') 115 | ), 116 | // Ports 117 | makePort('t', go.Spot.Top), 118 | makePort('l', go.Spot.Left), 119 | makePort('r', go.Spot.Right), 120 | makePort('b', go.Spot.Bottom) 121 | ); 122 | 123 | diagram.linkTemplate = new go.Link({ 124 | curve: go.Curve.Bezier, 125 | fromEndSegmentLength: 30, 126 | toEndSegmentLength: 30, 127 | }).add(new go.Shape({ strokeWidth: 1.5 }), new go.Shape({ toArrow: 'Standard' })); 128 | 129 | return diagram; 130 | } 131 | 132 | // When the diagram model changes, update app data to reflect those changes. Be sure to use immer's "produce" function to preserve immutability 133 | public diagramModelChange = function (changes: go.IncrementalData) { 134 | if (!changes) return; 135 | const appComp = this; 136 | this.state = produce(this.state, (draft) => { 137 | // set skipsDiagramUpdate: true since GoJS already has this update 138 | // this way, we don't log an unneeded transaction in the Diagram's undoManager history 139 | draft.skipsDiagramUpdate = true; 140 | draft.diagramNodeData = DataSyncService.syncNodeData( 141 | changes, 142 | draft.diagramNodeData, 143 | appComp.observedDiagram.model 144 | ); 145 | draft.diagramLinkData = DataSyncService.syncLinkData( 146 | changes, 147 | draft.diagramLinkData, 148 | appComp.observedDiagram.model 149 | ); 150 | draft.diagramModelData = DataSyncService.syncModelData(changes, draft.diagramModelData); 151 | // If one of the modified nodes was the selected node used by the inspector, update the inspector selectedNodeData object 152 | const modifiedNodeData = changes.modifiedNodeData; 153 | if (modifiedNodeData && draft.selectedNodeData) { 154 | for (let i = 0; i < modifiedNodeData.length; i++) { 155 | const mn = modifiedNodeData[i]; 156 | const nodeKeyProperty = appComp.myDiagramComponent.diagram.model 157 | .nodeKeyProperty as string; 158 | if (mn[nodeKeyProperty] === draft.selectedNodeData[nodeKeyProperty]) { 159 | draft.selectedNodeData = mn; 160 | } 161 | } 162 | } 163 | }); 164 | }; 165 | 166 | public initPalette(): go.Palette { 167 | const palette = new go.Palette(); 168 | // define a simpler Node template than the one used by the main Diagram 169 | palette.nodeTemplate = new go.Node('Auto').add( 170 | new go.Shape('RoundedRectangle', { strokeWidth: 0.5 }).bind('fill', 'color'), 171 | new go.TextBlock({ margin: 8 }).bind('text') 172 | ); 173 | return palette; 174 | } 175 | 176 | constructor(private cdr: ChangeDetectorRef) {} 177 | 178 | // Overview Component testing 179 | public oDivClassName = 'myOverviewDiv'; 180 | public initOverview(): go.Overview { 181 | return new go.Overview(); 182 | } 183 | public observedDiagram = null; 184 | 185 | // currently selected node; for inspector 186 | public selectedNodeData: go.ObjectData = null; 187 | 188 | public ngAfterViewInit() { 189 | if (this.observedDiagram) return; 190 | this.observedDiagram = this.myDiagramComponent.diagram; 191 | this.cdr.detectChanges(); // IMPORTANT: without this, Angular will throw ExpressionChangedAfterItHasBeenCheckedError (dev mode only) 192 | 193 | const appComp: AppComponent = this; 194 | // listener for inspector 195 | this.myDiagramComponent.diagram.addDiagramListener('ChangedSelection', function (e) { 196 | if (e.diagram.selection.count === 0) { 197 | appComp.selectedNodeData = null; 198 | } 199 | const node = e.diagram.selection.first(); 200 | appComp.state = produce(appComp.state, (draft) => { 201 | if (node instanceof go.Node) { 202 | var idx = draft.diagramNodeData.findIndex((nd) => nd.key == node.data.key); 203 | var nd = draft.diagramNodeData[idx]; 204 | draft.selectedNodeData = nd; 205 | } else { 206 | draft.selectedNodeData = null; 207 | } 208 | }); 209 | }); 210 | } // end ngAfterViewInit 211 | 212 | /** 213 | * Update a node's data based on some change to an inspector row's input 214 | * @param changedPropAndVal An object with 2 entries: "prop" (the node data prop changed), and "newVal" (the value the user entered in the inspector ) 215 | */ 216 | public handleInspectorChange(changedPropAndVal) { 217 | const path = changedPropAndVal.prop; 218 | const value = changedPropAndVal.newVal; 219 | 220 | this.state = produce(this.state, (draft) => { 221 | var data = draft.selectedNodeData; 222 | data[path] = value; 223 | const key = data.key; 224 | const idx = draft.diagramNodeData.findIndex((nd) => nd.key == key); 225 | if (idx >= 0) { 226 | draft.diagramNodeData[idx] = data; 227 | draft.skipsDiagramUpdate = false; // we need to sync GoJS data with this new app state, so do not skips Diagram update 228 | } 229 | }); 230 | } 231 | } 232 | --------------------------------------------------------------------------------