Reloading the Model Resets Some of the Properties

Hello there. So, I’m having this weird issue with the reloading of the model. I’m basically using GoJS with React (gojs-react) in a NextJS app.

Following is the page.tsx file, that contains the node & link data arrays for the initial load,

constructor(props: object) {
    super(props);
    let urlDiagram = "";
    if (typeof window !== "undefined") {
      urlDiagram = window.location.search.split("diagram=")[1];
    }
    this.state = DiagramProvidor(urlDiagram) as AppState; 
    // init maps
    this.mapNodeKeyIdx = new Map<go.Key, number>();
    this.mapLinkKeyIdx = new Map<go.Key, number>();
    this.refreshNodeIndex(this.state.nodeDataArray);
    this.refreshLinkIndex(this.state.linkDataArray);
    // bind handler methods
    this.handleDiagramEvent = this.handleDiagramEvent.bind(this);
    this.handleModelChange = this.handleModelChange.bind(this);
    this.handleInputChange = this.handleInputChange.bind(this);
  }

In the above code snippet, “this.state” is being set through a different file which I call the “DiagramProvider.ts”, based on the URL (that’s a whole different story). For example,

let Default: Diagram = {
  nodeDataArray: [
    {
      text: "NodeText",
      color: "#000000",
      key: 1,
      loc: "315 25",
      scale: 1.1544933012125682,
      fill: "#ffffff",
    },
  ],
  linkDataArray: [],
  modelData: {
    canRelink: true,
  },
  selectedData: null,
  skipsDiagramUpdate: false,
};

Now, I’ve done some binding in the “DiagramWrapper.tsx” file for the TextBlock of a specific node, example (loaded using Model.fromJson)

as you can see, the node with the text “Process” has its textblock aligned to the Top-Left (I’ve bound this one using new go.Binding(“alignment”, “alignTL”)), where as the other node with text “New Node” has its textblock in center (this one is by default set in the TextBlock’s properties), here

  $(
          go.TextBlock,
          {
            textAlign: "center",
            isMultiline: true,
            overflow: go.TextOverflow.Ellipsis,
            margin: 6,
            cursor: "default",
            editable: true,
            isUnderline: false,
            font: "400 1.2rem Arial, sans-serif",
            stroke: "black",
          },
          new go.Binding("alignment", "alignTL").makeTwoWay(), //here
          new go.Binding("alignmentFocus", "alignTL").makeTwoWay(), // here
          new go.Binding("text", "text").makeTwoWay(),
          new go.Binding("stroke", "color"),
          new go.Binding("font", "fontType"),
          new go.Binding("isUnderline", "setUnderline")
        )

And this is the nodeDataArray for the specific shape with the text “Process”,

 {
          key: "2",
          fill: "white",
          text: "Process",
          alignTL: new go.Spot(0, 0), // here you can see what I bound it to
          shape: "Process",
          size: "100 44.791867065429685",
        },

Now, the problem is that, if I save this JSON data of the diagram using Model.toJson & load it using Model.fromJson, it works fine, I get the same result as the image above.

But, if I add the same binding to the DiagramProvider.ts File, that sets the initial node & link Data Arrays in the page.tsx file against “this.state”, for example

let Default: Diagram = {
  nodeDataArray: [
    {
      text: "NodeText",
      color: "#000000",
      key: 1,
      alignTL: new go.Spot(0, 0),  // The Binding added here, gets set to "this.state" in page.tsx when the page reloads
      loc: "315 25",
      scale: 1.1544933012125682,
      fill: "#ffffff",
    },
  ],
  linkDataArray: [],
  modelData: {
    canRelink: true,
  },
  selectedData: null,
  skipsDiagramUpdate: false,
};

I’m getting this result, (without using Model.fromJson, instead setting the Node & Link Data Arrays against this.state in page.tsx)

Both the TextBlocks reset to the center, whereas, loading using Model.fromJson isn’t the same case.

For reference, this is the result of Model.toJson,

{ "class": "GraphLinksModel",
  "linkKeyProperty": "key",
  "linkFromPortIdProperty": "fromPort",
  "linkToPortIdProperty": "toPort",
  "modelData": {"canRelink":true},
  "nodeDataArray": [
{"text":"New Node","fill":"white","stroke":"black","strokeWidth":"2","key":-2,"loc":"-415.4167378719398 -95","color":"#000000"},
{"key":2,"fill":"white","text":"Process","alignTL":{"class":"go.Spot","x":0,"y":0,"offsetX":0,"offsetY":0},"shape":"Process","size":"100 44.791867065429685","loc":"-560 -180","color":"#000000"}
],
  "linkDataArray": []}

Again, loading this same JSON using Model.fromJson gives me accurate result, but using the same values against “this.state” doesn’t.

Also,

I’ve found the same behaviour with one of the samples, Reference to The Sample

Diagram before load, (different links used),

Diagram after reloading the model using the button below on the sample page, (resets all the links to the Normal Routing)
image

Any help would be appreciated! Thank you for reading.

Backing up a bit,

new go.Binding("alignment", "alignTL").makeTwoWay(), //here
new go.Binding("alignmentFocus", "alignTL").makeTwoWay(), // here

Why are these two-way bindings? What happens if you remove the .makeTwoWay()?

I’ve found the same behaviour with one of the samples, Reference to The Sample

(resets all the links to the Normal Routing)

In that sample, it is happening because (for instance) the animation of the new link sets the link.curviness to some value, but that value is never saved in the model. This is somewhat expected behavior (not a great look for the sample, but otherwise OK)

These two Bindings are being used for custom alignment for s specific shape, in my case the Process Shape (in the above post). It’s working fine if loaded using Model.fromJson.

And, removing “makeTwoWay()” doesn’t make a difference, the model still loads with the TextBlock centred in all the shapes.

I think it has something to do with the modelData property, look modelData Property

The 4th bullet mentions objects or arrays as properties, it’s pretty close to what I have here (fromJson works, reloading the model doesn’t as Most object classes cannot be serialized into JSON without special knowledge and processing at both ends)

Example,

let Default: Diagram = {
  nodeDataArray: [
    {
      text: "Start by Adding Shapes",
      color: "#000000",
      key: 1,
      alignTL: { class: "go.Spot", x: 0, y: 0, offsetX: 0, offsetY: 0 },  // look, I guess it's not being serialized to supported JSON, (Which is automatically done using Model.fromJson, but in my case I'm not using fromJson) 
      loc: "315 25",
      scale: 1.1544933012125682,
      fill: "#ffffff",
    },
  ],
  linkDataArray: [],
  modelData: {    // I think this has to something with it, I'm not sure
    canRelink: true,  
  },
  selectedData: null,
  skipsDiagramUpdate: false,
};

Just for the reference, this is how Model.fromJson processes the
“alignTL: { class: “go.Spot”, x: 0, y: 0, offsetX: 0, offsetY: 0 }” property,

alignTL: Spot {S: 0, P: 0, Vs: 0, Bs: 0, f: false},


You mentioned that in the Custom Animation Sample, the value of link.curviness is never saved in the model. Is there a way to do that? That might be the solution to my problem as well.

Can you give me enough code to reproduce an example of the this.state setting? Reading over what you’ve written before I don’t think I see where the new model is getting set.

Here you go, this is basically the same thing but done in a simpler way,

Code Sandbox - GoJS Implementation

In this example, you can see a node as soon as you land on the canvas, that node’s data is declared inside the App.tsx (Line 34-51). Notice that the nodeDataArray has the following property,

alignTL: { class: “go.Spot”, x: 0, y: 0, offsetX: 0, offsetY: 0 },

But, it doesn’t apply to the node when the canvas is loaded initially or if I reload the page (same thing). - This is what the problem exactly is


Now, try moving the nodes from the palette. Their names define how their TextBlocks will be aligned when they’re dragged onto the canvas. (this binding is done inside the GoJSWrapper.tsx, check line 120-126 for bindings and line 152-160 for pre-defined properties of the nodes in palette)

You should have something like this right now,
image


Now, you should have a button on the right side of the canvas, that says "Reload using Model.fromJson. It’s just a button that takes the current reference of the diagram, converts it to JSON using model.toJson & reloads it using Model.fromJson.

If you click on it, you’ll get this result,
image

Notice how the blue node’s TextBlock is now aligned according to the property assigned to its nodeDataArray in App.tsx (the alignTL property).


Now the question is, why reloading the page shows the TextBlock of Blue Node Centred but if we reload the model using the button (that uses fromJson method), the TextBlock goes where I want it to be according to the property applied in App.tsx.

Is this an issue with the serialization of JSON, that is done automatically by the Model.fromJson method? If so, how can I achieve the same on the initial load or when reloading the page?

Thank you for reading.

I see. Sorry it took a while for me to understand you. It’s a matter of our JSON parsing, where we attempt to infer from

{ class: "go.Spot" ... },

That we are looking at a GoJS class. In normal model data loading, we do not do that kind of JSON (expecting) parsing. Instead we are expecting, if anything, an actual class instance. So if you replaced it with:

//alignTL: { class: "go.Spot", x: 0, y: 0, offsetX: 0, offsetY: 0 },
alignTL: new go.Spot(0, 0, 0, 0),

It would work as you expect.

So you have a few options. The easiest is probably to save/load these Spots using Spot.Parse and Spot.Stringify:

new go.Binding("alignment", "alignTL", go.Spot.parse).makeTwoWay(go.Spot.stringify), //here
new go.Binding("alignmentFocus", "alignTL", go.Spot.parse).makeTwoWay(go.Spot.stringify), // here

That would remove the need for any other special parsing you might have to do. But if this is unacceptable, and you really want to save it as a class and then load it via data like that, let me know.

Thank you so much for your help, Simon!

It worked perfectly! If I ever need to save it as a class and load it via data, I’ll definitely reach out again. But for now, this solution is exactly what I needed.

Appreciated the help!