Gojs does not accept vue3 props

We have been using gojs for 3 years with vue 2.

We are now conducting a migration to vue 3 and it seems that gojs does not like any vue 3 objects that contain gojs configuration data.

Even after removing the vue 3 reactivity proxy using the toRaw helper gojs still throws an error.

Here is an example of our base diagram component which works with vue 2 but only partially with vue 3:

<template>
  <div ref="diagram" :class="$style.diagram"></div>
</template>

<script>
import * as go from 'gojs'
import { isEmpty, cloneDeep } from 'lodash'
import { toRaw, markRaw } from 'vue'

let Diagram = null

const $ = go.GraphObject.make

export default {
  name: 'Diagram',

  props: {
    model: {
      type: [Object, Array],
      default: () => ({
        nodes: [],
        links: [],
      }),
    },

    options: {
      type: Object,
      default: () => ({}),
    },

    linkTemplate: {
      type: Array,
      default: () => [],
    },

    groupTemplateMaps: {
      type: Object,
      default: () => ({}),
    },

    nodeTemplateMaps: {
      type: Object,
      default: () => ({}),
    },

    highlightBin: {
      type: [String, Number],
      default: '',
    },

  },

  watch: {
    model(newval, oldval) {
      if (Diagram && Diagram.model) {
        Diagram.model.commit(model => this.updateModel(model, newval, oldval))
      }
    },

    highlightBin: {
      handler: 'setSegmentedBin',
      immediate: true,
    },
  },

  async beforeUnmount() {
    await this.$nextTick()
    if (Diagram) {
      Diagram.div = null
      Diagram.clear()
      Diagram = null
    }
  },

  created() {
    // register gojs license
    const { GOJS_LICENSE } = this.$store.state.consts
    if (GOJS_LICENSE) go.licenseKey = GOJS_LICENSE
  },

  async mounted() {
    // Wait for vue to update data and DOM
    await this.$nextTick()

    this.initDiagram()
  },

  methods: {
    getDiagramInstance() {
      return Diagram
    },

    setSegmentedBin(binId) {
      if (Diagram) {
        Diagram.model.setDataProperty(Diagram.model.modelData, 'segmentedBin', binId)
      }
    },

    initDiagram() {
      if (Diagram === null) {
        // Init gojs diagram
        let diagram = $(go.Diagram, this.$el, this.options)
        let vm = this

        let templateMap = new go.Map()
        let groupTemplateMap = new go.Map()

        if (!isEmpty(this.linkTemplate)) diagram.linkTemplate = $(...this.linkTemplate)

        // Add nodeTemplateMaps per keys
        if (!isEmpty(this.nodeTemplateMaps)) {
          for (const [key, value] of Object.entries(toRaw(this.nodeTemplateMaps))) {
            if (key === 'default') templateMap.add('', value)
            else templateMap.add(key, value)
          }
        }

        // Add groupTemplateMaps per keys
        if (!isEmpty(this.groupTemplateMaps)) {
          for (const [key, value] of Object.entries(toRaw(this.groupTemplateMaps))) {
            if (key === 'default') groupTemplateMap.add('', value)
            else groupTemplateMap.add(key, value)
          }
        }

        diagram.nodeTemplateMap = templateMap
        diagram.groupTemplateMap = groupTemplateMap

        let { model } = vm
        let { nodes } = model
        let { links } = model

        nodes = toRaw(nodes)
        links = toRaw(links)

        diagram.model = $(go.GraphLinksModel, {
          linkKeyProperty: 'key',
          linkFromPortIdProperty: 'fromPort',
          linkToPortIdProperty: 'toPort',

          nodeDataArray: nodes,
          linkDataArray: links,
        })

        Diagram = diagram
        this.$emit('diagram-ready', diagram)
      }
    },

    arrayToMap(array) {
      let keyMap = {}
      for (let asset of array) {
        keyMap[asset.key] = asset
      }
      return keyMap
    },

    diffMap(origMap, newMap) {
      let onlyInOrig = []
      let onlyInNew = []
      let changed = []

      for (let key of Object.keys(origMap)) {
        if (!newMap.hasOwnProperty(key)) {
          onlyInOrig = onlyInOrig.concat(key)
        }
      }

      for (let key of Object.keys(newMap)) {
        if (!origMap.hasOwnProperty(key)) {
          onlyInNew = onlyInNew.concat(key)
        } else {
          if (origMap[key].hasOwnProperty('last_modified') && origMap[key].last_modified != newMap[key].last_modified) {
            changed = changed.concat(key)
          }
        }
      }

      return {
        delete: onlyInOrig,
        add: onlyInNew,
        changed,
      }
    },

    getAssetFromKeyArray(array, key) {
      for (let asset of array) {
        if (asset.key == key) {
          return asset
        }
      }
    },

    diffKeyedArray(origArr, newArr) {
      let newMap = this.arrayToMap(newArr)
      let oldMap = this.arrayToMap(origArr)
      let diff = this.diffMap(oldMap, newMap)

      return diff
    },

    updateModel(model, newval, oldval) {
      if (Diagram) {
        let nodesDiff = this.diffKeyedArray(oldval.nodes, newval.nodes)

        for (let key of nodesDiff.delete) {
          model.removeNodeData(model.findNodeDataForKey(key))
        }

        for (let key of nodesDiff.add) {
          model.addNodeData(this.getAssetFromKeyArray(newval.nodes, key))
        }

        for (let key of nodesDiff.changed) {
          model.removeNodeData(model.findNodeDataForKey(key))
          model.addNodeData(this.getAssetFromKeyArray(newval.nodes, key))
        }

        let linksDiff = this.diffKeyedArray(oldval.links, newval.links)

        for (let key of linksDiff.delete) {
          model.removeLinkData(model.findLinkDataForKey(key))
        }

        for (let key of linksDiff.add) {
          model.addLinkData(this.getAssetFromKeyArray(newval.links, key))
        }

      }
    },
  },
}
</script>

<style lang="scss" module>
.diagram {
  width: 100%;
  height: 100%;

  canvas {
    outline: none;
  }
}
</style>

When passing in these nodes:

[{"key":"GROUP|types","isGroup":true,"title":"Types","__gohashid":583},{"key":"GROUP|services","isGroup":true,"title":"Services","__gohashid":584},{"key":"C_ARM|Philips","category":"block_node_template","icon":"/s3/statics/customer-dashboard-ui/assets/images/asset-icons/C_ARM.svg","device_type":"C_ARM","type_display_name":"C-Arm","vendor":"Philips","count":4,"isSegmented":false,"segmentedId":null,"group":"GROUP|types","__gohashid":585},{"key":"Cloud Services/Clinical Analytics","category":"service_template","label":"Cloud Services Clinical Analytics","serviceName":"Cloud Services/Clinical Analytics","group":"GROUP|services","__gohashid":586}]

And this link data:

[{"from":"C_ARM|Philips","to":"Cloud Services/Clinical Analytics","key":"C_ARM|Philips|Cloud Services/Clinical Analytics","direction":"outbound","__gohashid":588}]

I get the following error in the console:

Uncaught Error: EnumValue.Default is not a valid geometryStretch.
    at B (go.js?0187:formatted:137)
    at Kf.t.Hm (go.js?0187:formatted:29019)
    at Kf.Y.measure (go.js?0187:formatted:20905)
    at Qm.measure (go.js?0187:formatted:25574)
    at S.t.Hm (go.js?0187:formatted:26538)
    at S.Y.measure (go.js?0187:formatted:20905)
    at R.t.Vw (go.js?0187:formatted:15105)
    at th (go.js?0187:formatted:15087)
    at Hf (go.js?0187:formatted:15030)
    at R.t.hd (go.js?0187:formatted:14995)

Is there a way to fix this?

We’re not familiar with Vue3. But I know some people are using GoJS with Vue3: https://github.com/NorthwoodsSoftware/GoJS/issues/114
Maybe that will help you?

Apparently the above can be resolved by stripping the vue 3 proxy wrapper using a vue helper called toRaw.

Example:

import * as go from 'gojs'
import { toRaw } from 'vue'

const $ = go.GraphObject.make

export default {

props: {
  model: {
    type: [Object],
    default: () => ({
        nodes: [],
        links: [],
      }),
  }

  methods: {
    initDiagram () {
        let { model } = this
        let { nodes } = model
        let { links } = model

        let diagram = $(go.Diagram, this.$el, { /* options */ })

        nodes = toRaw(nodes)     // <--- toRaw() removes vue 3 reactivity
        links = toRaw(links)     // <--- toRaw() removes vue 3 reactivity

        diagram.model = $(go.GraphLinksModel, {
          linkKeyProperty: 'key',
          linkFromPortIdProperty: 'fromPort',
          linkToPortIdProperty: 'toPort',

          nodeDataArray: nodes,
          linkDataArray: links,
        })
    }
  }

}

@walter I would suggest adding this to your docs.

Thank you!