Code Monkey home page Code Monkey logo

gojs-angular's Introduction

gojs-angular

Version 2.0

By Northwoods Software for GoJS 2.1

This project provides Angular components for GoJS Diagrams, Palettes, and Overviews to simplify usage of GoJS within an Angular application. The implementation for these components is inside the projects/gojs-angular folder. See the gojs-angular-basic project for example usage and the Intro page on using GoJS with Angular for more information.

Version 2.0 expects immutability of all @Input properties to Diagram|Palette|Overview components, and removes skipsPaletteUpdate and modelChange properties from PaletteComponent.

Installation

gojs-angular can be installed via NPM. This package has peer dependencies on GoJS and Angular, so make sure those are also installed or included on your page.

NPM

npm install --save gojs-angular

Making Changes

If you want to change how the GoJS / Angular components are implemented, you will need to edit the files in projects/gojs-angular, then, from the main directory, run

npm run package

which will create a new package in the folder, dist/angular-gojs, for you to use. Currently, gojs-angular depends on TypeScript and immer.

Usage

This package provides three components - DiagramComponent, PaletteComponent, and OverviewComponent - corresponding to the related GoJS classes.

Note: As of version 2.0, gojs-angular assumes immutability of the @Input properties given to Diagram/Palette components. The gojs-angular-basic repository provides example usage of these components, as well as preserving state immutability (that project uses immer to maintain immutability, but you can use whatever you like best).

Below is an example of how you might pass properties to each of the components provided by gojs-angular. Here, for immutable data properties that may change, they are stored in an object called state. This is not required, but helps with organization.

<gojs-diagram
  [initDiagram]='initDiagram'
  [divClassName]='myDiagramDiv'
  [nodeDataArray]='state.diagramNodeDataArray'
  [linkDataArray]='state.diagramLinkDataArray'
  [modelData]='state.diagramModelData'
  (modelChange)='diagramModelChange($event)'
  [skipsDiagramUpdate]='state.skipsDiagramUpdate'
></gojs-diagram>

<gojs-palette
  [initPalette]='initPalette'
  [divClassName]='myPaletteDiv'
  [nodeDataArray]='state.paletteNodeData'
></gojs-palette>

<gojs-overview
  [initOverview]='initOverview'
  [divClassName]='myOverviewDiv'
  [observedDiagram]='observedDiagram'
></gojs-overview>

Component Properties

initDiagram/initPalette/initOverview

Specifies a function that is reponsible for initializing and returning a GoJS Diagram, Palette, or Overview. In the case of an Overview, this is an optional property and when not provided, an Overview with default properties and centered content will be created.

function initDiagram() {
  const $ = go.GraphObject.make;

  const diagram = $(go.Diagram,
    {
      'undoManager.isEnabled': true,
      model: $(go.GraphLinksModel, {
        linkKeyProperty: 'key'  // this should always be set when using a GraphLinksModel
      })
    });

  diagram.nodeTemplate =
    $(go.Node, 'Auto',  // the Shape will go around the TextBlock
      $(go.Shape, 'RoundedRectangle', { strokeWidth: 0, fill: 'white' },
        // Shape.fill is bound to Node.data.color
        new go.Binding('fill', 'color')),
      $(go.TextBlock,
        { margin: 8 },  // some room around the text
        // TextBlock.text is bound to Node.data.key
        new go.Binding('text', 'key'))
    );

  return diagram;
}

divClassName

Specifies the CSS classname to add to the rendered div. This should usually specify a width/height.

.myDiagramDiv {
  width: 400px;
  height: 400px;
  border: 1px solid black;
}

nodeDataArray (DiagramComponent and PaletteComponent only)

Specifies the array of nodes for the Diagram's model.

nodeDataArray: [
  { key: 'Alpha', color: 'lightblue' },
  { key: 'Beta', color: 'orange' },
  { key: 'Gamma', color: 'lightgreen' },
  { key: 'Delta', color: 'pink' }
]

Optional - linkDataArray (DiagramComponent and PaletteComponent only)

Specifies the array of links for the Diagram's model, only needed when using a GraphLinksModel, not for Models or TreeModels. If are using this property, make sure to set the GraphLinksModel's linkKeyProperty in its corresponding initDiagram or initPalette function.

linkDataArray: [
  { key: -1, from: 'Alpha', to: 'Beta' },
  { key: -2, from: 'Alpha', to: 'Gamma' },
  { key: -3, from: 'Beta', to: 'Beta' },
  { key: -4, from: 'Gamma', to: 'Delta' },
  { key: -5, from: 'Delta', to: 'Alpha' }
]

Optional - modelData (DiagramComponent and PaletteComponent only)

Specifies a shared modelData object for the Diagram's model.

skipsDiagramUpdate (DiagramComponent only)

Specifies whether the Diagram component should skip updating, often set to true when updating state from a GoJS model change.

Because GoJS Palettes are read-only by default, this property is not present in PaletteComponent.

modelChange (DiagramComponent)

Specifies a function to be called when a GoJS transaction has completed. This function will typically be responsible for updating app-level state. Remember, these state properties are assumed to be immutable. This example modelChange, is taken from the gojs-angular-basic project, which uses immer's produce function to maintain immutability.

It is important that state updates made in this function include setting skipsDiagramUpdate to true, since the changes are known by GoJS.

Because GoJS Palettes are read-only by default, this property is not present on PaletteComponent. Although there won't be user-driven changes to a Palette's model due to the read-only nature of Palettes, changes to the nodeDataArray, linkDataArray, or shared modelData props described above allow for a Palette's model to be changed, if necessary.

// When the diagram model changes, update app data to reflect those changes. Be sure to preserve immutability
  public diagramModelChange = function(changes: go.IncrementalData) {
    const appComp = this;
    this.state = produce(this.state, draft => {
      // set skipsDiagramUpdate: true since GoJS already has this update
      draft.skipsDiagramUpdate = true;
      draft.diagramNodeData = DataSyncService.syncNodeData(changes, draft.diagramNodeData, appComp.observedDiagram.model);
      draft.diagramLinkData = DataSyncService.syncLinkData(changes, draft.diagramLinkData, appComp.observedDiagram.model);
      draft.diagramModelData = DataSyncService.syncModelData(changes, draft.diagramModelData);
    });
  };

Notice the use of the three static functions of the DataSyncService (syncNodeData, syncLinkData, and syncModelData), which is included with this package to make syncing your app-level data with Diagram / Palette data simple. Be aware: If you have set your Diagram's model.nodeKeyProperty or model.linkKeyProperty to anything other than 'key', you will need to pass your Diagram's model as a third parameter to DataSyncService.syncNodeData and DataSyncService.syncLinkData.

observedDiagram (OverviewComponent only)

Specifies the Diagram which the Overview will observe.

Migrating to Version 2.0

This page assumes use of gojs-angular version 2.0, which requires immutable state, unlike version 1.0. It is recommended to use the 2.0 version. If you have a gojs-angular project using version 1.x and want to upgrade, reference this section for tips on migrating to version 2.

Should I upgrade?

In general, yes.

If you have very simple node and link data, using the latest 1.x version might be okay. But new features and quality of life changes will be published on the 2.x branch moving forward.

Version 2.0 handles complex nested data much better than the previous version, due to its focus on immutable data. Additionally, it is a bit smaller in file size.

One may wish to hold off on upgrading if they have lots of operations mutating their @Input properties, and they do not want to take the time to rewrite those operations immutably. However, the guide below details one way one could do this. Our gojs-angular-basic sample also has demonstrations of immutably updating @Input properties to make such a rewrite easier.

Upgrade gojs-angular Version

Update your package.json to require gojs-angular version 2.0 or greater, then run npm install.

It is also recommended to upgrade to the lastest version of gojs.

Immutability

The biggest change with 2.0 is you must enforce immutability of @Input properties to your Diagram and Palette components.

So, for instance, whenever an entry of diagramNodeData is updated, removed, or changed, you will need to generate a whole new Array for DiagramComponent.diagramNodeData. This can be done in many different ways with many different packages. A popular choice is immer, which exposes a produce function that allows one to immutability manipulate their data on a draft variable. We will use that function here for demonstration purposes.

The Version 1.0 Way

In gojs-angular version 1, if you wanted to add some node data to your diagramNodeData @Input property, you could do so by simply adding to the diagramNodeData Array, mutating it. Such as:

// When the diagram model changes, update app data to reflect those changes
public addNode = function(nodeData: go.ObjectData) {
  this.skipsDiagramUpdate = false; // sync changes with GoJS model
  this.diagramNodeData.push(nodeData);
}
The Version 2.0 Way

In gojs-angular version 2, that same addNode function must be changed so the diagramNodeData property is updated immutably (that is, replaced with an entirely new Array). Here is an example of doing that with immer's produce function.

// When the diagram model changes, update app data to reflect those changes
public addNode = function(nodeData: go.ObjectData) {
  this.state = produce(this.state, draft => {
    var nodedata = { id: "Zeta", text: "Zorro", color: "red" };
    draft.skipsDiagramUpdate = false;
    draft.diagramNodeData.push(nodedata);
  });
}

Notice we are also using a massive state object to hold gojs-angular component properties. This makes these kinds of immutable operations (especially if you are using immer, or a package like it) straightforward (see how we were able to update both skipsDiagramUpdate and diagramNodeData on the same draft variable).

To see more samples of enforcing immutability with gojs-angular, see gojs-angular-basic, particularly the modelChange property of the Diagram Component.

Additional Considerations

Additionally, as of 2.0, PaletteComponent no longer supports skipsPaletteUpdate or modelChange properties. As GoJS Palettes are read-only by default, their models should not be changing based on user input. Instead, if you need to update their node/link/model data, update their @Input properties (immutably, of course).

License

This project is intended to be used alongside GoJS, and is covered by the GoJS software license.

Copyright 1998-2021 by Northwoods Software Corporation.

gojs-angular's People

Contributors

cdelgadob avatar dependabot[bot] avatar mattlewis92 avatar rjohnson465 avatar simonsarris avatar walternorthwoods avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

gojs-angular's Issues

GoJS with Angular 14.x works locally but not on Stackblitz

I am trying to have a discussion with support and it would be very easy to share code on Stackblitz, but once I upgrade after Angular 12.5 things stop working. Here is an example I got up to Angular 12.5 then past that it won't work, just to be clear the only thing I did between 12.5 working example is upgrade dependencies:

Works (v12.5)

https://stackblitz.com/edit/angular-gojs-xa4mc1?file=README.md

Does NOT work (v14+)

https://stackblitz.com/edit/angular-gojs-48pipq?file=README.md

Error in src/app/library/library.component.ts (1:29)
Cannot find module '@angular/compiler/src/render3/r3_ast' or its corresponding type declarations.

Here is another example that works LOCALLY on my machine but doesn't work in Stackblitz:

https://stackblitz.com/edit/github-xjnzp2?file=src/app/_components/drag-drop/drag-drop.component.ts

Error in src/app/app.module.ts (19:5)
'GojsAngularModule' does not appear to be an NgModule class.

(modelChange) not being triggered when the link object is custom and `data` prop modified

Hi,

I'm not exactly positive if this is a bug or not but it's a scenario I've encountered in one of my usages of the library.

The (modelChange) Angular output on the <gojs-diagram> component, does not trigger whenever i specifically try modifying the data property of a specific link. I am including a code sample below for further clarification.

Note: My gut feeling tells me I'm somehow doing this wrong but on other node properties, using the setProperties(...) works perfectly fine, while on link properties the setProperties(...) is only working for 'original' (non-custom) link properties.

Example: (please note example has been simplified on-purpose
HTML:

    <gojs-diagram
      #myDiagram
      (modelChange)='diagramModelChange($event)'
      [initDiagram]='initDiagram'
      [linkDataArray]='state.diagramLinkData'
      [nodeDataArray]='state.diagramNodeData'
      [skipsDiagramUpdate]='state.skipsDiagramUpdate'
      divClassName='myDiagramDiv'
    >
    </gojs-diagram>

TS:

...

state = {
  diagramNodeData: [
    {
      id: 1,
      text: 'Alpha',
    },
    {
      id: 2,
      text: 'Beta',
    },
  ],
  diagramLinkData: [
    {
      key: 1,
      from: 1,
      to: 2,
      text: 'transition',
      customLabel: 'myCustomLabel' // <--- This is my issue (custom property)
    },
  ],
}

...

Then in the initDiagram() function where I declare my link template I am binding the customLabel text as follows:

...
      $$(
        go.TextBlock, new go.Binding('text', 'customLabel'),
      ),
...

My main issue/question here is: how do I update the customLabel text dynamically/programmatically?

I've tried using the below approach but even though the text update happens successfully, whenever I use the below code and update the data property below, from that point onwards the (modelChange) output stops triggering for any links that get deleted, whilst for nodes updates it still works ok.

      // once the below code executes, I stop receiving updates on the `modelChange` for any links (e.g. link deletion, etc...)
      this.myDiagramComponent.diagram
        .findLinkForKey(1) // using ID 1 for example
        .setProperties({
          data: {
            customLabel: 'newCustomLabelValue'
          }
        });

I'm concerned that I'm doing the above, last part wrong here, but I couldn't find any other way of how to update 'custom labels' dynamically on links and couldn't find any examples in the docs.

Thanks!

Model data has to be passed at multiple places for proper rendering of Family Tree

I'm using gojs-angular in my Angular 8 application.

I followed the instructions from the Family Tree example: https://gojs.net/latest/samples/familyTree.html

But to render the family tree using gojs-angular, I have to pass the family data at three different places.

  1. nodeDataArray input of the component
  2. modelData input of the component
  3. Also assign to go diagram model property.

Recreate Issue

I created an example stackblitz: https://stackblitz.com/edit/angular-gojs-family-tree

Remove any of the above property and the graph rendering will fail.

Environment

Angular: 8
gojs-angular: 1.0.0
gojs: 2.1.5

Compatibility Issue with Angular 16 Ivy Compilation

I am currently migrating to Angular 16 and encountered a build error related to the gojs-angular library, which seems not to be compatible with Ivy. The error message is as follows:

  node_modules/gojs-angular/lib/gojs-angular.module.d.ts:1:22
    1 export declare class GojsAngularModule {
                           ~~~~~~~~~~~~~~~~~
    This likely means that the library (gojs-angular) which declares GojsAngularModule is not compatible with Angular Ivy. Check if a newer version of the library is available, and update if so. Also consider checking with the library's authors to see if the library is expected to be compatible with Ivy.

I would appreciate it if you could provide any updates regarding Ivy compatibility or if there's a workaround to resolve this issue. Thank you!

SyncNodeData and SyncLinkData methods of DataSyncService don't delete elements correctly

In the SyncNodeData method of the DataSyncService the following code is not correct:

// account for removed node data
if (changes.removedNodeKeys) {
    const removals = changes.removedNodeKeys.map(key => keyIdxMap.get(key)).sort();
    for (let i = removals.length - 1; i >= 0; i--) {
        draft.splice(removals[i], 1);
    }
}

Specifically the sort() function of javascript is sorting the array of indices as strings. This is the default behavior if no sort function is provided. If you try deleting multiple nodes with indices of different orders of magnitude (for example [10, 11, 7]) a semi-random node will be deleted instead. The correct version of the code would look like this:

// account for removed node data
if (changes.removedNodeKeys) {
    const removals = (changes.removedNodeKeys.map((key) => keyIdxMap.get(key)) as number[]).sort(
        (a, b) => a - b
    );
    for (let i = removals.length - 1; i >= 0; i--) {
        draft.splice(removals[i], 1);
    }
}

The SyncLinkData method behaves analogously.

contextMenu does not work

  • I've been trying a lot to implement contextMenu in my application, but it doesn't work in any way (via Adornment or HTMLInfo).
  • I've found contextMenu in the gojs-angular-basic demo, but it does not work either.

So I assume it's an issue of the gojs-angular package.

DiagramComponent - Stuck in an infinite recursive loop

There is a case that makes the method mergeChanges(component, kvchanges, str) from the gojs-angular library get stuck in an infinite loop. More specifically, is a non-ending recursion call in the compareObjs(obj1, obj2) method.

I think it's caused by the circular structure discussed here.

My guess is that in the line if (!compareObjs(obj1[p], obj2[p])) it never ends, because of that circular structure mentioned before.

Here is the code, just imagine that the obj1 has a reference to itself in any property: the recursion would never end.

function compareObjs(obj1, obj2) {
            // Loop through properties in object 1
            for (const p in obj1) {
                // Check property exists on both objects
                if (obj1.hasOwnProperty(p) !== obj2.hasOwnProperty(p))
                    return false;
                switch (typeof (obj1[p])) {
                    // Deep compare objects
                    case 'object':
                        if (!compareObjs(obj1[p], obj2[p]))
                            return false;
                        break;
                    // Compare values
                    default:
                        if (obj1[p] !== obj2[p])
                            return false;
                }
            }
            // Check object 2 for any extra properties
            for (const p in obj2) {
                if (typeof (obj1[p]) === 'undefined')
                    return false;
            }
            return true;
        }

Support for component method as callback to diagram events

Adding angular's component member function as callback to context menu button click is giving error

TypeError: `functionName` is not a function

The stackblitz example: https://stackblitz.com/edit/angular-gojs-family-tree

I have added two menu for explanation where first menu is working fine but the second one where callback method is used is not working.

I also tried to bind the callback to this context using

click: this.buttonCallback.bind(this)

But error is still there.

Same issue is being faced where external methods are being used to return some value to the diagram properties.

Ex.,

$(go.TextBlock, this.getStyle(), new go.Binding('text', 'name'));

private getStyle() {
   return {
        font: '700 12px Droid Serif, sans-serif',
        textAlign: 'center',
        margin: 10, maxSize: new go.Size(80, NaN)
   };
}

Gojs angular synclinkdata issue

After I delete a link and the synclinkdata function runs, I receive an error:

image

This seems to happen when there is a change that contains both a removal and a change in link data.

Cannot add property __gohashid, object is not extensible

The error still appears. I have established an easy way of reproducing the error.

First cause the modelchange to fire with any change you want (move a step or copy paste a few graph items).
Then Change the node and link array passed into gojs angular using immer produce ( draft.diagramLinkData.push(any new data)/ draft.diagramNodeData.push(any new data).
Error will appear in console.

SyncLinkData or SyncNodeData is the cause for the error, i am not certain as to why

Cannot add property __gohashid, object is not extensible

Once again I am faced with this error. After a lot of tracing I found that it occurs when you bind an itemarray to a property on the nodedata. When a change is triggered on the node data and the updatetargetbindings gets called, it tries to add gohashid to the itemarray object and the objects inside the array for some reason? It is not two way binding.

Since gojs angular uses immutable data, it then breaks.

createPortArea(direction: string, alignment: go.Spot, portCollection: string): go.GraphObject {
        const portTemplate = new PortTemplate(this.canvasService);
        return goMake(go.Panel, direction, {
            name: portCollection,
            alignment,
            itemTemplate: portTemplate.getPortTemplate(alignment)
        } as go.Panel,
        new go.Binding('itemArray', '', (node: NodeData, target: go.GraphObject): PortData[] => node[portCollection])

        );
    }

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.