import * as go from 'gojs';
import { Diagram, ResizingTool, GridLayout, Adornment } from 'gojs';
import { PoolLink, BPMNLinkingTool } from './BPMNClasses';
import { DrawCommandHandler } from './DrawCommandHandler';

export class BPMN { 
    public myDiagram: Diagram;

    private $ = go.GraphObject.make;  // for more concise visual tree definitions

    // constants for design choices
  
    private GradientYellow = this.$(go.Brush, "Linear", { 0: "LightGoldenRodYellow", 1: "#FFFF66" });
    private GradientLightGreen = this.$(go.Brush, "Linear", { 0: "#E0FEE0", 1: "PaleGreen" });
    private GradientLightGray = this.$(go.Brush, "Linear", { 0: "White", 1: "#DADADA" });
  
    private ActivityNodeFill = this.$(go.Brush, "Linear", { 0: "OldLace", 1: "PapayaWhip" });
    private ActivityNodeStroke = "#CDAA7D";
    private ActivityMarkerStrokeWidth = 1.5;
    private ActivityNodeWidth = 120;
    private ActivityNodeHeight = 80;
    private ActivityNodeStrokeWidth = 1;
    private ActivityNodeStrokeWidthIsCall = 4;
  
    SubprocessNodeFill = this.ActivityNodeFill;
    SubprocessNodeStroke = this.ActivityNodeStroke;
  
    EventNodeSize = 42;
    EventNodeInnerSize = this.EventNodeSize - 6;
    EventNodeSymbolSize = this.EventNodeInnerSize - 14;
    EventEndOuterFillColor = "pink";
    EventBackgroundColor = this.GradientLightGreen;
    EventSymbolLightFill = "white";
    EventSymbolDarkFill = "dimgray";
    EventDimensionStrokeColor = "green";
    EventDimensionStrokeEndColor = "red";
    EventNodeStrokeWidthIsEnd = 4;
  
    GatewayNodeSize = 80;
    GatewayNodeSymbolSize = 45;
    GatewayNodeFill = this.GradientYellow;
    GatewayNodeStroke = "darkgoldenrod";
    GatewayNodeSymbolStroke = "darkgoldenrod";
    GatewayNodeSymbolFill = this.GradientYellow;
    GatewayNodeSymbolStrokeWidth = 3;
  
    DataFill = this.GradientLightGray;
    // private MINLENGTH = 400;  // this controls the minimum length of any swimlane
    private MINBREADTH = 20;  // this controls the minimum breadth of any non-collapsed swimlane
    constructor() {
        this.init();
        this.loadDiagramProperties = this.loadDiagramProperties.bind(this);
              var jsondata = JSON.parse('{ "class": "go.GraphLinksModel","linkFromPortIdProperty": "fromPort",          "linkToPortIdProperty": "toPort",          "modelData": {"position":"230 200"},          "nodeDataArray": [        {"category":"event", "item":"start", "key":101, "loc":"450 540", "text":"Start", "eventType":1, "eventDimension":1},        {"category":"event", "item":"End", "key":104, "loc":"1130 540", "text":"End", "eventType":1, "eventDimension":8},        {"category":"activity", "item":"generic task", "key":5, "loc":"1010 540", "text":"Send Invoice", "taskType":0, "boundaryEventArray":[  ]},        {"category":"activity", "item":"generic task", "key":-4, "loc":"860 540", "text":"Fulfill Order", "taskType":0, "boundaryEventArray":[  ]},        {"category":"activity", "item":"generic task", "key":-5, "loc":"710 540", "text":"Credit Check", "taskType":0, "boundaryEventArray":[  ]},        {"category":"activity", "item":"generic task", "key":-6, "loc":"560 540", "text":"Receive Order", "taskType":0, "boundaryEventArray":[  ]}],          "linkDataArray": [        {"from":101, "to":-6, "fromPort":"", "toPort":""},        {"from":-6, "to":-5, "fromPort":"", "toPort":""},        {"from":-5, "to":-4, "fromPort":"", "toPort":""},        {"from":-4, "to":5, "fromPort":"", "toPort":""},        {"from":5, "to":104, "fromPort":"", "toPort":""}        ]}')
      // set these kinds of Diagram properties after initialization, not now
      this.myDiagram.addDiagramListener("InitialLayoutCompleted", this.loadDiagramProperties);  // defined below
      // create the model from the data in the JavaScript object parsed from JSON text
      //this.myDiagram.model = new go.GraphLinksModel(jsondata["nodes"], jsondata["links"]);
      this.myDiagram.model = go.Model.fromJson(jsondata);
      this.loadDiagramProperties();
      this.myDiagram.model.undoManager.isEnabled = true;
      this.myDiagram.isModified = false;
    }
    
// this is called after nodes have been moved or lanes resized, to layout all of the Pool Groups again
private relayoutDiagram() {
    this.myDiagram.layout.invalidateLayout();
    this.myDiagram.findTopLevelGroups().each( (g:any) =>{ if (g.category === "Pool") g.layout.invalidateLayout(); });
    this.myDiagram.layoutDiagram();
  }
   // Make sure the pipes are ordered by their key in the palette inventory
//    private keyCompare(a:any, b:any) {
//     var at = a.data.key;
//     var bt = b.data.key;
//     if (at < bt) return -1;
//     if (at > bt) return 1;
//     return 0;
//   }
  
   // set Default Sequence Flow (backslash From Arrow)
   private setSequenceLinkDefaultFlow(obj:any) {
    this.myDiagram.startTransaction("setSequenceLinkDefaultFlow");
    var model = this.myDiagram.model;
    model.setDataProperty(obj.data, "isDefault", true);
    // Set all other links from the fromNode to be isDefault=null
    obj.fromNode.findLinksOutOf().each( (link:any) =>{
      if (link !== obj && link.data.isDefault) {
        model.setDataProperty(link.data, "isDefault", null);
      }
    });
    this.myDiagram.commitTransaction("setSequenceLinkDefaultFlow");
  }

  // set Conditional Sequence Flow (diamond From Arrow)
  private setSequenceLinkConditionalFlow(obj:any) {
    this.myDiagram.startTransaction("setSequenceLinkConditionalFlow");
    var model = this.myDiagram.model;
    model.setDataProperty(obj.data, "isDefault", false);
    this.myDiagram.commitTransaction("setSequenceLinkConditionalFlow");
  }
  //------------------------------------------  Commands for this application  ----------------------------------------------
  
  // Add a port to the specified side of the selected nodes.   name is beN  (be0, be1)
  // evDim is 5 for Interrupting, 6 for non-Interrupting
//   private addActivityNodeBoundaryEvent(evType:any, evDim:any) {
//       this.myDiagram.startTransaction("addBoundaryEvent");
//       this.myDiagram.selection.each( (node) => {
//         // skip any selected Links
//         if (!(node instanceof go.Node)) return;
//         if (node.data && (node.data.category === "activity" || node.data.category === "subprocess")) {
//           // compute the next available index number for the side
//           var i = 0;
//           var defaultPort = node.findPort("");
//           while (node.findPort("be" + i.toString()) !== defaultPort) i++;           // now this new port name is unique within the whole Node because of the side prefix
//           var name = "be" + i.toString();
//           if (!node.data.boundaryEventArray) { this.myDiagram.model.setDataProperty(node.data, "boundaryEventArray", []); }       // initialize the Array of port data if necessary
//           // create a new port data object
//           var newportdata = {
//             portId: name,
//             eventType: evType,
//             eventDimension: evDim,
//             color: "white",
//             alignmentIndex: i
//             // if you add port data properties here, you should copy them in copyPortData above  ** BUG...  we don't do that.
//           };
//           // and add it to the Array of port data
//           this.myDiagram.model.insertArrayItem(node.data.boundaryEventArray, -1, newportdata);
//         }
//       });
//       this.myDiagram.commitTransaction("addBoundaryEvent");
//     }
    
    // private loadFile() {
    //   let listbox = document.getElementById("mySavedFiles");
    //   // get selected filename
    //   var fileName = undefined;
    //   if(listbox!=null){
    //     for (var i = 0; i < listbox.options.length; i++) {
    //         if (listbox.options[i].selected) fileName = listbox.options[i].text; // selected file
    //     }
    // }
    //   if (fileName !== undefined) {
    //     // changes the text of "currentFile" to be the same as the floor plan now loaded
    //     this.setCurrentFileName(fileName);
    //     // actually load the model from the JSON format string
    //     var savedFile = window.localStorage.getItem(fileName);
    //     if(savedFile)
    //     this.myDiagram.model = go.Model.fromJson(savedFile);
    //     this.loadDiagramProperties();
    //     this.myDiagram.model.undoManager.isEnabled = true;
    //     this.myDiagram.isModified = false;
    //     // eventually loadDiagramProperties will be called to finish
    //     // restoring shared saved model/diagram properties
    //   }
    //   this.closeElement("openDocument");
    // }
    
    // private loadJSON(file:any) {
    //   jQuery.getJSON(file,  (jsondata:any)=> {
    //     // set these kinds of Diagram properties after initialization, not now
    //     this.myDiagram.addDiagramListener("InitialLayoutCompleted", this.loadDiagramProperties);  // defined below
    //     // create the model from the data in the JavaScript object parsed from JSON text
    //     //this.myDiagram.model = new go.GraphLinksModel(jsondata["nodes"], jsondata["links"]);
    //     this.myDiagram.model = go.Model.fromJson(jsondata);
    //     this.loadDiagramProperties();
    //     this.myDiagram.model.undoManager.isEnabled = true;
    //     this.myDiagram.isModified = false;
    //   });
    // }
    
    // Store shared model state in the Model.modelData property
    // (will be loaded by loadDiagramProperties)
    
    
    // // Called by loadFile and loadJSON.
    private loadDiagramProperties() {
      // set Diagram.initialPosition, not Diagram.position, to handle initialization side-effects
      var pos = this.myDiagram.model.modelData.position;
      if (pos) this.myDiagram.initialPosition = go.Point.parse(pos);
    }
    private groupStyle() {  // common settings for both Lane and Pool Groups
        return [
          {
            layerName: "Background",  // all pools and lanes are always behind all nodes and links
            background: "transparent",  // can grab anywhere in bounds
            movable: true, // allows users to re-order by dragging
            copyable: false,  // can't copy lanes or pools
            avoidable: false  // don't impede AvoidsNodes routed Links
          },
          new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify)
        ];
      }
    
      // hide links between lanes when either lane is collapsed
      private updateCrossLaneLinks(group:any) {
        group.findExternalLinksConnected().each( (l:any) => {
          l.visible = (l.fromNode.isVisible() && l.toNode.isVisible());
        });
      }
  // Add a lane to pool (lane parameter is lane above new lane)
  private addLaneEvent(lane:any) {
    this.myDiagram.startTransaction("addLane");
    if (lane != null && lane.data.category === "Lane") {
      // create a new lane data object
      var shape = lane.findObject("SHAPE");
      var size = new go.Size(shape.width, this.MINBREADTH);
      //size.height = MINBREADTH;
      var newlanedata = {
        category: "Lane",
        text: "New Lane",
        color: "white",
        isGroup: true,
        loc: go.Point.stringify(new go.Point(lane.location.x, lane.location.y + 1)), // place below selection
        size: go.Size.stringify(size),
        group: lane.data.group
      };
      // and add it to the model
      this.myDiagram.model.addNodeData(newlanedata);
    }
    this.myDiagram.commitTransaction("addLane");
  }
// This file holds all of the JavaScript code specific to the BPMN.html page.
// conversion functions used by data Bindings

private nodeActivityTaskTypeConverter(s:any) {
    var tasks = ["Empty",
      "BpmnTaskMessage",
      "BpmnTaskUser",
      "BpmnTaskManual",   // Custom hand symbol
      "BpmnTaskScript",
      "BpmnTaskMessage",  // should be black on white
      "BpmnTaskService",  // Custom gear symbol
      "InternalStorage"];
    if (s < tasks.length) return tasks[s];
    return "NotAllowed"; // error
  }

  // location of event on boundary of Activity is based on the index of the event in the boundaryEventArray
  private nodeActivityBESpotConverter(s:any) {
    var x = 10 + (this.EventNodeSize / 2);
    if (s === 0) return new go.Spot(0, 1, x, 0);    // bottom left
    if (s === 1) return new go.Spot(1, 1, -x, 0);   // bottom right
    if (s === 2) return new go.Spot(1, 0, -x, 0);   // top right
    return new go.Spot(1, 0, -x - (s - 2) * this.EventNodeSize, 0);    // top ... right-to-left-ish spread
  }

  private nodeActivityTaskTypeColorConverter(s:any) {
    return (s == 5) ? "dimgray" : "white";
  }

  private nodeEventTypeConverter(s:any) {  // order here from BPMN 2.0 poster
    var tasks = ["NotAllowed",
      "Empty",
      "BpmnTaskMessage",
      "BpmnEventTimer",
      "BpmnEventEscalation",
      "BpmnEventConditional",
      "Arrow",
      "BpmnEventError",
      "ThinX",
      "BpmnActivityCompensation",
      "Triangle",
      "Pentagon",
      "ThickCross",
      "Circle"];
    if (s < tasks.length) return tasks[s];
    return "NotAllowed"; // error
  }

  private nodeEventDimensionStrokeColorConverter(s:any) {
    if (s === 8) return this.EventDimensionStrokeEndColor;
    return this.EventDimensionStrokeColor;
  }

  private nodeEventDimensionSymbolFillConverter(s:any) {
    if (s <= 6) return this.EventSymbolLightFill;
    return this.EventSymbolDarkFill;
  }
  // removing a boundary event doesn't not reposition other BE circles on the node
  // just reassigning alignmentIndex in remaining BE would do that.
  private removeActivityNodeBoundaryEvent(obj:any) {
    this.myDiagram.startTransaction("removeBoundaryEvent");
    var pid = obj.portId;
    var arr = obj.panel.itemArray;
    for (var i = 0; i < arr.length; i++) {
      if (arr[i].portId === pid) {
        this.myDiagram.model.removeArrayItem(arr, i);
        break;
      }
    }
    this.myDiagram.commitTransaction("removeBoundaryEvent");
  }
  private AddUserAction(evType:any, evDim:any) {

    this.myDiagram.startTransaction("addBoundaryEvent");
    this.myDiagram.selection.each( (node:any)=> {
      // skip any selected Links
      if (!(node instanceof go.Node)) return;
      if (node.data && (node.data.category === "activity" || node.data.category === "subprocess")) {
        // compute the next available index number for the side
        var i = 0;
        var defaultPort = node.findPort("");
        while (node.findPort("be" + i.toString()) !== defaultPort) i++;           // now this new port name is unique within the whole Node because of the side prefix
        var name = "be" + i.toString();
        if (!node.data.boundaryEventArray) { this.myDiagram.model.setDataProperty(node.data, "boundaryEventArray", []); }       // initialize the Array of port data if necessary
        // create a new port data object
        var newportdata = {
          portId: name,
          eventType: evType,
          eventDimension: evDim,
          color: "white",
          alignmentIndex: i
          // if you add port data properties here, you should copy them in copyPortData above  ** BUG...  we don't do that.
        };
        // and add it to the Array of port data
        this.myDiagram.model.insertArrayItem(node.data.boundaryEventArray, -1, newportdata);
      }
    });
    this.myDiagram.commitTransaction("addBoundaryEvent");
    alert("Add User action")
  }
  private AddTimerAction() {
    alert("Add Timer Action")
  }
  private StateHandlers() {
    alert("State Handlers")
  }
  private StatePermissions() {
    alert("State Permissions")
  }
  private StateRules() {
    alert("State Rules")
  }
  private DeleteState() {
    alert("Delete State")
  }
  // sub-process,  loop, parallel, sequential, ad doc and compensation markers in horizontal array
  private makeSubButton(sub:any) {
    if (sub)
      return [this.$("SubGraphExpanderButton"),
      { margin: 2, visible: false },
      new go.Binding("visible", "isSubProcess")];
    return [];
  }

  // sub-process,  loop, parallel, sequential, ad doc and compensation markers in horizontal array
  private makeMarkerPanel(sub:any, scale:any) {
    return this.$(go.Panel, "Horizontal",
      { alignment: go.Spot.MiddleBottom, alignmentFocus: go.Spot.MiddleBottom },
      this.$(go.Shape, "BpmnActivityLoop",
        { width: 12 / scale, height: 12 / scale, margin: 2, visible: false, strokeWidth: this.ActivityMarkerStrokeWidth },
        new go.Binding("visible", "isLoop")),
      this.$(go.Shape, "BpmnActivityParallel",
        { width: 12 / scale, height: 12 / scale, margin: 2, visible: false, strokeWidth: this.ActivityMarkerStrokeWidth },
        new go.Binding("visible", "isParallel")),
      this.$(go.Shape, "BpmnActivitySequential",
        { width: 12 / scale, height: 12 / scale, margin: 2, visible: false, strokeWidth: this.ActivityMarkerStrokeWidth },
        new go.Binding("visible", "isSequential")),
      this.$(go.Shape, "BpmnActivityAdHoc",
        { width: 12 / scale, height: 12 / scale, margin: 2, visible: false, strokeWidth: this.ActivityMarkerStrokeWidth },
        new go.Binding("visible", "isAdHoc")),
      this.$(go.Shape, "BpmnActivityCompensation",
        { width: 12 / scale, height: 12 / scale, margin: 2, visible: false, strokeWidth: this.ActivityMarkerStrokeWidth, fill: null },
        new go.Binding("visible", "isCompensation")),
        this.makeSubButton(sub)
    ); // end activity markers horizontal panel
  }
  private nodeGatewaySymbolTypeConverter(s:any) {
    var tasks = ["NotAllowed",
      "ThinCross",      // 1 - Parallel
      "Circle",         // 2 - Inclusive
      "AsteriskLine",   // 3 - Complex
      "ThinX",          // 4 - Exclusive  (exclusive can also be no symbol, just bind to visible=false for no symbol)
      "Pentagon",       // 5 - double cicle event based gateway
      "Pentagon",       // 6 - exclusive event gateway to start a process (single circle)
      "ThickCross"]     // 7 - parallel event gateway to start a process (single circle)
    if (s < tasks.length) return tasks[s];
    return "NotAllowed"; // error
  }

  // tweak the size of some of the gateway icons
  private nodeGatewaySymbolSizeConverter(s:any) {
    var size = new go.Size(this.GatewayNodeSymbolSize, this.GatewayNodeSymbolSize);
    if (s === 4) {
      size.width = size.width / 4 * 3;
      size.height = size.height / 4 * 3;
    }
    else if (s > 4) {
      size.width = size.width / 1.6;
      size.height = size.height / 1.6;
    }
    return size;
  }
  private nodePalGatewaySymbolSizeConverter(s:any) {
    var size = this.nodeGatewaySymbolSizeConverter(s);
    size.width = size.width / 2;
    size.height = size.height / 2;
    return size;
  }
// Setup all of the Diagrams and what they need.
// This is called after the page is loaded.
private init() {


  // hides open HTML Element

  

  // custom figures for Shapes

  go.Shape.defineFigureGenerator("Empty", (shape, w, h) =>{
    return new go.Geometry();
  });

  var annotationStr = "M 150,0L 0,0L 0,600L 150,600 M 800,0";
  var annotationGeo = go.Geometry.parse(annotationStr);
  annotationGeo.normalize();
  go.Shape.defineFigureGenerator("Annotation", (shape, w, h) =>{
    var geo = annotationGeo.copy();
    // calculate how much to scale the Geometry so that it fits in w x h
    var bounds = geo.bounds;
    var scale = Math.min(w / bounds.width, h / bounds.height);
    geo.scale(scale, scale);
    return geo;
  });

  var gearStr = "F M 391,5L 419,14L 444.5,30.5L 451,120.5L 485.5,126L 522,141L 595,83L 618.5,92L 644,106.5" +
    "L 660.5,132L 670,158L 616,220L 640.5,265.5L 658.122,317.809L 753.122,322.809L 770.122,348.309L 774.622,374.309" +
    "L 769.5,402L 756.622,420.309L 659.122,428.809L 640.5,475L 616.5,519.5L 670,573.5L 663,600L 646,626.5" +
    "L 622,639L 595,645.5L 531.5,597.5L 493.192,613.462L 450,627.5L 444.5,718.5L 421.5,733L 393,740.5L 361.5,733.5" +
    "L 336.5,719L 330,627.5L 277.5,611.5L 227.5,584.167L 156.5,646L 124.5,641L 102,626.5L 82,602.5L 78.5,572.5" +
    "L 148.167,500.833L 133.5,466.833L 122,432.5L 26.5,421L 11,400.5L 5,373.5L 12,347.5L 26.5,324L 123.5,317.5" +
    "L 136.833,274.167L 154,241L 75.5,152.5L 85.5,128.5L 103,105.5L 128.5,88.5001L 154.872,82.4758L 237,155" +
    "L 280.5,132L 330,121L 336,30L 361,15L 391,5 Z M 398.201,232L 510.201,275L 556.201,385L 505.201,491L 399.201,537" +
    "L 284.201,489L 242.201,385L 282.201,273L 398.201,232 Z";
  var gearGeo = go.Geometry.parse(gearStr);
  gearGeo.normalize();

  go.Shape.defineFigureGenerator("BpmnTaskService", (shape, w, h) =>{
    var geo = gearGeo.copy();
    // calculate how much to scale the Geometry so that it fits in w x h
    var bounds = geo.bounds;
    var scale = Math.min(w / bounds.width, h / bounds.height);
    geo.scale(scale, scale);
    // text should go in the hand
    geo.spot1 = new go.Spot(0, 0.6, 10, 0);
    geo.spot2 = new go.Spot(1, 1);
    return geo;
  });

  var handGeo = go.Geometry.parse("F1M18.13,10.06 C18.18,10.07 18.22,10.07 18.26,10.08 18.91," +
    "10.20 21.20,10.12 21.28,12.93 21.36,15.75 21.42,32.40 21.42,32.40 21.42," +
    "32.40 21.12,34.10 23.08,33.06 23.08,33.06 22.89,24.76 23.80,24.17 24.72," +
    "23.59 26.69,23.81 27.19,24.40 27.69,24.98 28.03,24.97 28.03,33.34 28.03," +
    "33.34 29.32,34.54 29.93,33.12 30.47,31.84 29.71,27.11 30.86,26.56 31.80," +
    "26.12 34.53,26.12 34.72,28.29 34.94,30.82 34.22,36.12 35.64,35.79 35.64," +
    "35.79 36.64,36.08 36.72,34.54 36.80,33.00 37.17,30.15 38.42,29.90 39.67," +
    "29.65 41.22,30.20 41.30,32.29 41.39,34.37 42.30,46.69 38.86,55.40 35.75," +
    "63.29 36.42,62.62 33.47,63.12 30.76,63.58 26.69,63.12 26.69,63.12 26.69," +
    "63.12 17.72,64.45 15.64,57.62 13.55,50.79 10.80,40.95 7.30,38.95 3.80," +
    "36.95 4.24,36.37 4.28,35.35 4.32,34.33 7.60,31.25 12.97,35.75 12.97," +
    "35.75 16.10,39.79 16.10,42.00 16.10,42.00 15.69,14.30 15.80,12.79 15.96," +
    "10.75 17.42,10.04 18.13,10.06z ");
  handGeo.rotate(90, 0, 0);
  handGeo.normalize();
  go.Shape.defineFigureGenerator("BpmnTaskManual", (shape, w, h)=> {
    var geo = handGeo.copy();
    // calculate how much to scale the Geometry so that it fits in w x h
    var bounds = geo.bounds;
    var scale = Math.min(w / bounds.width, h / bounds.height);
    geo.scale(scale, scale);
    // guess where text should go (in the hand)
    geo.spot1 = new go.Spot(0, 0.6, 10, 0);
    geo.spot2 = new go.Spot(1, 1);
    return geo;
  });


  // define the appearance of tooltips, shared by various templates
  var tooltiptemplate =
    this.$(go.Adornment, go.Panel.Auto,
      this.$(go.Shape, "RoundedRectangle",
        { fill: "whitesmoke", stroke: "gray" }),
      this.$(go.TextBlock,
        { margin: 3, editable: true },
        new go.Binding("text", "", (data)=> {
          if (data.item !== undefined) return data.item;
          return "(unnamed item)";
        }))
    );

  //------------------------------------------  Activity Node Boundary Events   ----------------------------------------------

  let boundaryEventMenu =  // context menu for each boundaryEvent on Activity node
    this.$(go.Adornment, "Vertical",
      this.$("ContextMenuButton",
        this.$(go.TextBlock, "Remove event"),
        // in the click event handler, the obj.part is the Adornment; its adornedObject is the port
        { click: (e:any, obj:any)=> { this.removeActivityNodeBoundaryEvent(obj.part.adornedObject); } })
    );

  

    let boundaryEventItemTemplate =
    this.$(go.Panel, "Spot",
      {
        contextMenu: boundaryEventMenu,
        alignmentFocus: go.Spot.Center,
        fromLinkable: true, toLinkable: false, cursor: "pointer", fromSpot: go.Spot.Bottom,
        fromMaxLinks: 1, toMaxLinks: 0
      },
      new go.Binding("portId", "portId"),
      new go.Binding("alignment", "alignmentIndex", this.nodeActivityBESpotConverter),
      this.$(go.Shape, "Circle",
        { desiredSize: new go.Size(this.EventNodeSize, this.EventNodeSize) },
        new go.Binding("strokeDashArray", "eventDimension", (s)=> { return (s === 6) ? [4, 2] : null; }),
        new go.Binding("fromSpot", "alignmentIndex",
          (s)=> {
            //  nodeActivityBEFromSpotConverter, 0 & 1 go on bottom, all others on top of activity
            if (s < 2) return go.Spot.Bottom;
            return go.Spot.Top;
          }),
        new go.Binding("fill", "color")),
      this.$(go.Shape, "Circle",
        {
          alignment: go.Spot.Center,
          desiredSize: new go.Size(this.EventNodeInnerSize, this.EventNodeInnerSize), fill: null
        },
        new go.Binding("strokeDashArray", "eventDimension", (s) => { return (s === 6) ? [4, 2] : null; })
      ),
      this.$(go.Shape, "NotAllowed",
        {
          alignment: go.Spot.Center,
          desiredSize: new go.Size(this.EventNodeSymbolSize, this.EventNodeSymbolSize), fill: "white"
        },
        new go.Binding("figure", "eventType", this.nodeEventTypeConverter)
      )
    );

  //------------------------------------------  Activity Node contextMenu   ---------------------------------------------

  let activityNodeMenu =
    this.$(go.Adornment, "Vertical",
      this.$("ContextMenuButton",
        this.$(go.TextBlock, "Add User action", { margin: 3 }),
        { click:  (e:any, obj:any)=> { this.AddUserAction(2, 5) } }),
      this.$("ContextMenuButton",
        this.$(go.TextBlock, "Add Timer Action", { margin: 3 }),
        { click:  (e:any, obj:any)=> { this.AddTimerAction() } }),
      this.$("ContextMenuButton",
        this.$(go.TextBlock, "State Handlers", { margin: 3 }),
        { click:  (e:any, obj:any) =>{ this.StateHandlers() } }),
      this.$("ContextMenuButton",
        this.$(go.TextBlock, "State Permissions", { margin: 3 }),
        { click:  (e:any, obj:any)=> { this.StatePermissions() } }),
      this.$("ContextMenuButton",
        this.$(go.TextBlock, "State Rules", { margin: 3 }),
        { click:  (e:any, obj:any) =>{ this.StateRules() } }),
      this.$("ContextMenuButton",
        this.$(go.TextBlock, "Delete State", { margin: 3 }),
        { click:  (e:any, obj:any)=> { this.DeleteState() } }),
    )
 

  let activityNodeTemplate =
    this.$(go.Node, "Spot",
      {
        locationObjectName: "SHAPE", locationSpot: go.Spot.Center,
        resizable: true, resizeObjectName: "PANEL",
        toolTip: tooltiptemplate,
        selectionAdorned: false,  // use a Binding on the Shape.stroke to show selection
        contextMenu: activityNodeMenu,
        itemTemplate: boundaryEventItemTemplate
      },
      new go.Binding("itemArray", "boundaryEventArray"),
      new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
      // move a selected part into the Foreground layer, so it isn"t obscured by any non-selected parts
      new go.Binding("layerName", "isSelected",  (s)=> { return s ? "Foreground" : ""; }).ofObject(),
      this.$(go.Panel, "Auto",
        {
          name: "PANEL",
          minSize: new go.Size(this.ActivityNodeWidth, this.ActivityNodeHeight),
          desiredSize: new go.Size(this.ActivityNodeWidth, this.ActivityNodeHeight)
        },
        new go.Binding("desiredSize", "size", go.Size.parse).makeTwoWay(go.Size.stringify),
        this.$(go.Panel, "Spot",
          this.$(go.Shape, "RoundedRectangle",  // the outside rounded rectangle
            {
              name: "SHAPE",
              fill: this.ActivityNodeFill, stroke: this.ActivityNodeStroke,
              parameter1: 10, // corner size
              portId: "", fromLinkable: true, toLinkable: true, cursor: "pointer",
              fromSpot: go.Spot.RightSide, toSpot: go.Spot.LeftSide
            },
            new go.Binding("fill", "color"),
            new go.Binding("strokeWidth", "isCall",
               (s)=> { return s ? this.ActivityNodeStrokeWidthIsCall : this.ActivityNodeStrokeWidth; })
          ),
          //        this.$(go.Shape, "RoundedRectangle",  // the inner "Transaction" rounded rectangle
          //          { margin: 3,
          //            stretch: go.GraphObject.Fill,
          //            stroke: ActivityNodeStroke,
          //            parameter1: 8, fill: null, visible: false
          //          },
          //          new go.Binding("visible", "isTransaction")
          //         ),
          // task icon
          this.$(go.Shape, "BpmnTaskScript",    // will be None, Script, Manual, Service, etc via converter
            {
              alignment: new go.Spot(0, 0, 5, 5), alignmentFocus: go.Spot.TopLeft,
              width: 22, height: 22
            },
            new go.Binding("fill", "taskType", this.nodeActivityTaskTypeColorConverter),
            new go.Binding("figure", "taskType", this.nodeActivityTaskTypeConverter)
          ), // end Task Icon
          this.makeMarkerPanel(false, 1) // sub-process,  loop, parallel, sequential, ad doc and compensation markers
        ),  // end main body rectangles spot panel
        this.$(go.TextBlock,  // the center text
          {
            alignment: go.Spot.Center, textAlign: "center", margin: 12,
            editable: true
          },
          new go.Binding("text").makeTwoWay())
      )  // end Auto Panel
    );  // end go.Node, which is a Spot Panel with bound itemArray

  // ------------------------------- template for Activity / Task node in Palette  -------------------------------

  var palscale = 2;
  let activityNodeTemplateForPalette =
    this.$(go.Node, "Vertical",
      {
        locationObjectName: "SHAPE",
        locationSpot: go.Spot.Center,
        selectionAdorned: false
      },
      new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
      this.$(go.Panel, "Spot",
        {
          name: "PANEL",
          desiredSize: new go.Size(this.ActivityNodeWidth / palscale, this.ActivityNodeHeight / palscale)
        },
        this.$(go.Shape, "RoundedRectangle",  // the outside rounded rectangle
          {
            name: "SHAPE",
            fill: this.ActivityNodeFill, stroke: this.ActivityNodeStroke,
            parameter1: 10 / palscale  // corner size (default 10)
          },
          new go.Binding("strokeWidth", "isCall",
             (s) =>{ return s ? this.ActivityNodeStrokeWidthIsCall : this.ActivityNodeStrokeWidth; })),
        this.$(go.Shape, "RoundedRectangle",  // the inner "Transaction" rounded rectangle
          {
            margin: 3,
            stretch: go.GraphObject.Fill,
            stroke: this.ActivityNodeStroke,
            parameter1: 8 / palscale, fill: null, visible: false
          },
          new go.Binding("visible", "isTransaction")),
        // task icon
        this.$(go.Shape, "BpmnTaskScript",    // will be None, Script, Manual, Service, etc via converter
          {
            alignment: new go.Spot(0, 0, 5, 5), alignmentFocus: go.Spot.TopLeft,
            width: 22 / palscale, height: 22 / palscale
          },
          new go.Binding("fill", "taskType", this.nodeActivityTaskTypeColorConverter),
          new go.Binding("figure", "taskType", this.nodeActivityTaskTypeConverter)),
          this.makeMarkerPanel(false, palscale) // sub-process,  loop, parallel, sequential, ad doc and compensation markers
      ), // End Spot panel
      this.$(go.TextBlock,  // the center text
        { alignment: go.Spot.Center, textAlign: "center", margin: 2 },
        new go.Binding("text"))
    );  // End Node

    let subProcessGroupTemplateForPalette =
    this.$(go.Group, "Vertical",
      {
        locationObjectName: "SHAPE",
        locationSpot: go.Spot.Center,
        isSubGraphExpanded: false,
        selectionAdorned: false
      },
      this.$(go.Panel, "Spot",
        {
          name: "PANEL",
          desiredSize: new go.Size(this.ActivityNodeWidth / palscale, this.ActivityNodeHeight / palscale)
        },
        this.$(go.Shape, "RoundedRectangle",  // the outside rounded rectangle
          {
            name: "SHAPE",
            fill: this.ActivityNodeFill, stroke: this.ActivityNodeStroke,
            parameter1: 10 / palscale  // corner size (default 10)
          },
          new go.Binding("strokeWidth", "isCall",  (s) => { return s ? this.ActivityNodeStrokeWidthIsCall : this.ActivityNodeStrokeWidth; })
        ),
        this.$(go.Shape, "RoundedRectangle",  // the inner "Transaction" rounded rectangle
          {
            margin: 3,
            stretch: go.GraphObject.Fill,
            stroke: this.ActivityNodeStroke,
            parameter1: 8 / palscale, fill: null, visible: false
          },
          new go.Binding("visible", "isTransaction")),
          this.makeMarkerPanel(true, palscale) // sub-process,  loop, parallel, sequential, ad doc and compensation markers
      ), // end main body rectangles spot panel
      this.$(go.TextBlock,  // the center text
        { alignment: go.Spot.Center, textAlign: "center", margin: 2 },
        new go.Binding("text"))
    );  // end go.Group

  //------------------------------------------  Event Node Template  ----------------------------------------------

  let eventNodeTemplate =
    this.$(go.Node, "Vertical",
      {
        locationObjectName: "SHAPE",
        locationSpot: go.Spot.Center,
        toolTip: tooltiptemplate
      },
      new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
      // move a selected part into the Foreground layer, so it isn't obscured by any non-selected parts
      new go.Binding("layerName", "isSelected",  (s) => { return s ? "Foreground" : ""; }).ofObject(),
      // can be resided according to the user's desires
      { resizable: false, resizeObjectName: "SHAPE" },
      this.$(go.Panel, "Spot",
        this.$(go.Shape, "Circle",  // Outer circle
          {
            strokeWidth: 1,
            name: "SHAPE",
            desiredSize: new go.Size(this.EventNodeSize, this.EventNodeSize),
            portId: "", fromLinkable: true, toLinkable: true, cursor: "pointer",
            fromSpot: go.Spot.RightSide, toSpot: go.Spot.LeftSide
          },
          // allows the color to be determined by the node data
          new go.Binding("fill", "eventDimension",  (s:any) => { return (s === 8) ? this.EventEndOuterFillColor : this.EventBackgroundColor; }),
          new go.Binding("strokeWidth", "eventDimension",  (s:any) => { return s === 8 ? this.EventNodeStrokeWidthIsEnd : 1; }),
          new go.Binding("stroke", "eventDimension", this.nodeEventDimensionStrokeColorConverter),
          new go.Binding("strokeDashArray", "eventDimension",  (s:any)=>  { return (s === 3 || s === 6) ? [4, 2] : null; }),
          new go.Binding("desiredSize", "size", go.Size.parse).makeTwoWay(go.Size.stringify)
        ),  // end main shape
        this.$(go.Shape, "Circle",  // Inner circle
          { alignment: go.Spot.Center, desiredSize: new go.Size(this.EventNodeInnerSize, this.EventNodeInnerSize), fill: null },
          new go.Binding("stroke", "eventDimension", this.nodeEventDimensionStrokeColorConverter),
          new go.Binding("strokeDashArray", "eventDimension",  (s) => { return (s === 3 || s === 6) ? [4, 2] : null; }), // dashes for non-interrupting
          new go.Binding("visible", "eventDimension",  (s)=> { return s > 3 && s <= 7; }) // inner  only visible for 4 thru 7
        ),
        this.$(go.Shape, "NotAllowed",
          { alignment: go.Spot.Center, desiredSize: new go.Size(this.EventNodeSymbolSize, this.EventNodeSymbolSize), stroke: "black" },
          new go.Binding("figure", "eventType", this.nodeEventTypeConverter),
          new go.Binding("fill", "eventDimension", this.nodeEventDimensionSymbolFillConverter)
        )
      ),  // end Auto Panel
      this.$(go.TextBlock,
        { alignment: go.Spot.Center, textAlign: "center", margin: 5, editable: true },
        new go.Binding("text").makeTwoWay())

    ); // end go.Node Vertical

  //------------------------------------------  Gateway Node Template   ----------------------------------------------

 

  let gatewayNodeTemplate =
    this.$(go.Node, "Vertical",
      {
        locationObjectName: "SHAPE",
        locationSpot: go.Spot.Center,
        toolTip: tooltiptemplate
      },
      new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
      // move a selected part into the Foreground layer, so it isn't obscured by any non-selected parts
      new go.Binding("layerName", "isSelected", (s) => { return s ? "Foreground" : ""; }).ofObject(),
      // can be resided according to the user's desires
      { resizable: false, resizeObjectName: "SHAPE" },
      this.$(go.Panel, "Spot",
        this.$(go.Shape, "Diamond",
          {
            strokeWidth: 1, fill: this.GatewayNodeFill, stroke: this.GatewayNodeStroke,
            name: "SHAPE",
            desiredSize: new go.Size(this.GatewayNodeSize, this.GatewayNodeSize),
            portId: "", fromLinkable: true, toLinkable: true, cursor: "pointer",
            fromSpot: go.Spot.NotLeftSide, toSpot: go.Spot.NotRightSide
          },
          new go.Binding("desiredSize", "size", go.Size.parse).makeTwoWay(go.Size.stringify)),  // end main shape
        this.$(go.Shape, "NotAllowed",
          { alignment: go.Spot.Center, stroke: this.GatewayNodeSymbolStroke, fill: this.GatewayNodeSymbolFill },
          new go.Binding("figure", "gatewayType", this.nodeGatewaySymbolTypeConverter),
          //new go.Binding("visible", "gatewayType", function(s) { return s !== 4; }),   // comment out if you want exclusive gateway to be X instead of blank.
          new go.Binding("strokeWidth", "gatewayType",  (s) => { return (s <= 4) ? this.GatewayNodeSymbolStrokeWidth : 1; }),
          new go.Binding("desiredSize", "gatewayType", this.nodeGatewaySymbolSizeConverter)),
        // the next 2 circles only show up for event gateway
        this.$(go.Shape, "Circle",  // Outer circle
          {
            strokeWidth: 1, stroke: this.GatewayNodeSymbolStroke, fill: null, desiredSize: new go.Size(this.EventNodeSize, this.EventNodeSize)
          },
          new go.Binding("visible", "gatewayType",  (s:any) => { return s >= 5; }) // only visible for > 5
        ),  // end main shape
        this.$(go.Shape, "Circle",  // Inner circle
          {
            alignment: go.Spot.Center, stroke: this.GatewayNodeSymbolStroke,
            desiredSize: new go.Size(this.EventNodeInnerSize, this.EventNodeInnerSize),
            fill: null
          },
          new go.Binding("visible", "gatewayType",  (s) => { return s === 5; }) // inner  only visible for == 5
        )
      ),
      this.$(go.TextBlock,
        { alignment: go.Spot.Center, textAlign: "center", margin: 5, editable: true },
        new go.Binding("text").makeTwoWay())
    ); // end go.Node Vertical

  //--------------------------------------------------------------------------------------------------------------

  let gatewayNodeTemplateForPalette =
    this.$(go.Node, "Vertical",
      {
        toolTip: tooltiptemplate,
        resizable: false,
        locationObjectName: "SHAPE",
        locationSpot: go.Spot.Center,
        resizeObjectName: "SHAPE"
      },
      new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
      this.$(go.Panel, "Spot",
        this.$(go.Shape, "Diamond",
          {
            strokeWidth: 1, fill: this.GatewayNodeFill, stroke: this.GatewayNodeStroke, name: "SHAPE",
            desiredSize: new go.Size(this.GatewayNodeSize / 2, this.GatewayNodeSize / 2)
          }),
        this.$(go.Shape, "NotAllowed",
          { alignment: go.Spot.Center, stroke: this.GatewayNodeSymbolStroke, strokeWidth: this.GatewayNodeSymbolStrokeWidth, fill: this.GatewayNodeSymbolFill },
          new go.Binding("figure", "gatewayType", this.nodeGatewaySymbolTypeConverter),
          //new go.Binding("visible", "gatewayType", function(s) { return s !== 4; }),   // comment out if you want exclusive gateway to be X instead of blank.
          new go.Binding("strokeWidth", "gatewayType",  (s) => { return (s <= 4) ? this.GatewayNodeSymbolStrokeWidth : 1; }),
          new go.Binding("desiredSize", "gatewayType", this.nodePalGatewaySymbolSizeConverter)),
        // the next 2 circles only show up for event gateway
        this.$(go.Shape, "Circle",  // Outer circle
          {
            strokeWidth: 1, stroke: this.GatewayNodeSymbolStroke, fill: null, desiredSize: new go.Size(this.EventNodeSize / 2, this.EventNodeSize / 2)
          },
          //new go.Binding("desiredSize", "gatewayType", new go.Size(EventNodeSize/2, EventNodeSize/2)),
          new go.Binding("visible", "gatewayType",  (s) => { return s >= 5; }) // only visible for > 5
        ),  // end main shape
        this.$(go.Shape, "Circle",  // Inner circle
          {
            alignment: go.Spot.Center, stroke: this.GatewayNodeSymbolStroke,
            desiredSize: new go.Size(this.EventNodeInnerSize / 2, this.EventNodeInnerSize / 2),
            fill: null
          },
          new go.Binding("visible", "gatewayType",  (s) => { return s === 5; }) // inner  only visible for == 5
        )),

      this.$(go.TextBlock,
        { alignment: go.Spot.Center, textAlign: "center", margin: 5, editable: false },
        new go.Binding("text"))
    );

  //--------------------------------------------------------------------------------------------------------------

  let annotationNodeTemplate =
    this.$(go.Node, "Auto",
      { background: this.GradientLightGray, locationSpot: go.Spot.Center },
      new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
      this.$(go.Shape, "Annotation", // A left bracket shape
        { portId: "", fromLinkable: true, cursor: "pointer", fromSpot: go.Spot.Left, strokeWidth: 2, stroke: "gray" }),
      this.$(go.TextBlock,
        { margin: 5, editable: true },
        new go.Binding("text").makeTwoWay())
    );

    let dataObjectNodeTemplate =
    this.$(go.Node, "Vertical",
      { locationObjectName: "SHAPE", locationSpot: go.Spot.Center },
      new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
      this.$(go.Shape, "File",
        {
          name: "SHAPE", portId: "", fromLinkable: true, toLinkable: true, cursor: "pointer",
          fill: this.DataFill, desiredSize: new go.Size(this.EventNodeSize * 0.8, this.EventNodeSize)
        }),
      this.$(go.TextBlock,
        {
          margin: 5,
          editable: true
        },
        new go.Binding("text").makeTwoWay())
    );

    let dataStoreNodeTemplate =
    this.$(go.Node, "Vertical",
      { locationObjectName: "SHAPE", locationSpot: go.Spot.Center },
      new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
      this.$(go.Shape, "Database",
        {
          name: "SHAPE", portId: "", fromLinkable: true, toLinkable: true, cursor: "pointer",
          fill: this.DataFill, desiredSize: new go.Size(this.EventNodeSize, this.EventNodeSize)
        }),
      this.$(go.TextBlock,
        { margin: 5, editable: true },
        new go.Binding("text").makeTwoWay())
    );

  //------------------------------------------  private process Node Template Map   ----------------------------------------------

  let privateProcessNodeTemplate =
    this.$(go.Node, "Auto",
      { layerName: "Background", resizable: true, resizeObjectName: "LANE" },
      new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
      this.$(go.Shape, "Rectangle",
        { fill: null }),
      this.$(go.Panel, "Table",     // table with 2 cells to hold header and lane
        {
          desiredSize: new go.Size(this.ActivityNodeWidth * 6, this.ActivityNodeHeight),
          background: this.DataFill, name: "LANE", minSize: new go.Size(this.ActivityNodeWidth, this.ActivityNodeHeight * 0.667)
        },
        new go.Binding("desiredSize", "size", go.Size.parse).makeTwoWay(go.Size.stringify),
        this.$(go.TextBlock,
          {
            row: 0, column: 0,
            angle: 270, margin: 5,
            editable: true, textAlign: "center"
          },
          new go.Binding("text").makeTwoWay()),
        this.$(go.RowColumnDefinition, { column: 1, separatorStrokeWidth: 1, separatorStroke: "black" }),
        this.$(go.Shape, "Rectangle",
          {
            row: 0, column: 1,
            stroke: null, fill: "transparent",
            portId: "", fromLinkable: true, toLinkable: true,
            fromSpot: go.Spot.TopBottomSides, toSpot: go.Spot.TopBottomSides,
            cursor: "pointer", stretch: go.GraphObject.Fill
          })
      )
    );

    let  privateProcessNodeTemplateForPalette =
    this.$(go.Node, "Vertical",
      { locationSpot: go.Spot.Center },
      this.$(go.Shape, "Process",
        { fill: this.DataFill, desiredSize: new go.Size(this.GatewayNodeSize / 2, this.GatewayNodeSize / 4) }),
      this.$(go.TextBlock,
        { margin: 5, editable: true },
        new go.Binding("text"))
    );

    let  poolTemplateForPalette =
    this.$(go.Group, "Vertical",
      {
        locationSpot: go.Spot.Center,
        computesBoundsIncludingLinks: false,
        isSubGraphExpanded: false
      },
      this.$(go.Shape, "Process",
        { fill: "white", desiredSize: new go.Size(this.GatewayNodeSize / 2, this.GatewayNodeSize / 4) }),
      this.$(go.Shape, "Process",
        { fill: "white", desiredSize: new go.Size(this.GatewayNodeSize / 2, this.GatewayNodeSize / 4) }),
      this.$(go.TextBlock,
        { margin: 5, editable: true },
        new go.Binding("text"))
    );

    let swimLanesGroupTemplateForPalette =
    this.$(go.Group, "Vertical"); // empty in the palette

    let  subProcessGroupTemplate =
    this.$(go.Group, "Spot",
      {
        locationSpot: go.Spot.Center,
        locationObjectName: "PH",
        //locationSpot: go.Spot.Center,
        isSubGraphExpanded: false,
        memberValidation:  (group:any, part:any) => {
          return !(part instanceof go.Group) ||
            (part.category !== "Pool" && part.category !== "Lane");
        },
        mouseDrop:  (e:any, grp:any) => {
          var ok = grp.addMembers(grp.diagram.selection, true);
          if (!ok) grp.diagram.currentTool.doCancel();
        },
        contextMenu: activityNodeMenu,
        itemTemplate: boundaryEventItemTemplate
      },
      new go.Binding("itemArray", "boundaryEventArray"),
      new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
      // move a selected part into the Foreground layer, so it isnt obscured by any non-selected parts
      // new go.Binding("layerName", "isSelected",  (s:any) => { return s ? "Foreground" : ""; }).ofObject(),
      this.$(go.Panel, "Auto",
        this.$(go.Shape, "RoundedRectangle",
          {
            name: "PH", fill: this.SubprocessNodeFill, stroke: this.SubprocessNodeStroke,
            minSize: new go.Size(this.ActivityNodeWidth, this.ActivityNodeHeight),
            portId: "", fromLinkable: true, toLinkable: true, cursor: "pointer",
            fromSpot: go.Spot.RightSide, toSpot: go.Spot.LeftSide
          },
          new go.Binding("strokeWidth", "isCall",  (s) =>{ return s ? this.ActivityNodeStrokeWidthIsCall : this.ActivityNodeStrokeWidth; }
          )
        ),
        this.$(go.Panel, "Vertical", { defaultAlignment: go.Spot.Left },
          this.$(go.TextBlock,  // label
            { margin: 3, editable: true },
            new go.Binding("text", "text").makeTwoWay(),
            new go.Binding("alignment", "isSubGraphExpanded",  (s) => { return s ? go.Spot.TopLeft : go.Spot.Center; })),
          // create a placeholder to represent the area where the contents of the group are
          this.$(go.Placeholder,
            { padding: new go.Margin(5, 5) }),
            this.makeMarkerPanel(true, 1)  // sub-process,  loop, parallel, sequential, ad doc and compensation markers
        )  // end Vertical Panel
      )
    );  // end Group

  //** need this in the subprocess group template above.
  //        this.$(go.Shape, "RoundedRectangle",  // the inner "Transaction" rounded rectangle
  //          { margin: 3,
  //            stretch: go.GraphObject.Fill,
  //            stroke: ActivityNodeStroke,
  //            parameter1: 8, fill: null, visible: false
  //          },
  //          new go.Binding("visible", "isTransaction")
  //         ),


  

  let laneEventMenu =  // context menu for each lane
    this.$(go.Adornment, "Vertical",
      this.$("ContextMenuButton",
        this.$(go.TextBlock, "Add Lane"),
        // in the click event handler, the obj.part is the Adornment; its adornedObject is the port
        { click:  (e:any, obj:any) => { this.addLaneEvent(obj.part.adornedObject); } })
    );
   

 let swimLanesGroupTemplate =
    this.$(go.Group, "Spot", this.groupStyle(),
      {
        name: "Lane",
        contextMenu: laneEventMenu,
        minLocation: new go.Point(NaN, -Infinity),  // only allow vertical movement
        maxLocation: new go.Point(NaN, Infinity),
        selectionObjectName: "SHAPE",  // selecting a lane causes the body of the lane to be highlit, not the label
        resizable: true, resizeObjectName: "SHAPE",  // the custom resizeAdornmentTemplate only permits two kinds of resizing
        layout: this.$(go.LayeredDigraphLayout,  // automatically lay out the lane's subgraph
          {
            isInitial: false,  // don't even do initial layout
            isOngoing: false,  // don't invalidate layout when nodes or links are added or removed
            direction: 0,
            columnSpacing: 10,
            layeringOption: go.LayeredDigraphLayout.LayerLongestPathSource
          }),
        computesBoundsAfterDrag: true,  // needed to prevent recomputing Group.placeholder bounds too soon
        computesBoundsIncludingLinks: false,  // to reduce occurrences of links going briefly outside the lane
        computesBoundsIncludingLocation: true,  // to support empty space at top-left corner of lane
        handlesDragDropForMembers: true,  // don't need to define handlers on member Nodes and Links
        mouseDrop:  (e:any, grp:any) => {  // dropping a copy of some Nodes and Links onto this Group adds them to this Group
          // don't allow drag-and-dropping a mix of regular Nodes and Groups
          if (!e.diagram.selection.any( (n:any) => { return (n instanceof go.Group && n.category !== "subprocess") || n.category === "privateProcess"; })) {
            var ok = grp.addMembers(grp.diagram.selection, true);
            if (ok) {
                this.updateCrossLaneLinks(grp);
                this.relayoutDiagram();
            } else {
              grp.diagram.currentTool.doCancel();
            }
          }
        },
        subGraphExpandedChanged:  (grp:any) =>{
          var shp = grp.resizeObject;
          if (grp.diagram.undoManager.isUndoingRedoing) return;
          if (grp.isSubGraphExpanded) {
            shp.height = grp._savedBreadth;
          } else {
            grp._savedBreadth = shp.height;
            shp.height = NaN;
          }
          this.updateCrossLaneLinks(grp);
        }
      },
      //new go.Binding("isSubGraphExpanded", "expanded").makeTwoWay(),

      this.$(go.Shape, "Rectangle",  // this is the resized object
        { name: "SHAPE", fill: "white", stroke: null },  // need stroke null here or you gray out some of pool border.
        new go.Binding("fill", "color"),
        new go.Binding("desiredSize", "size", go.Size.parse).makeTwoWay(go.Size.stringify)),

      // the lane header consisting of a Shape and a TextBlock
      this.$(go.Panel, "Horizontal",
        {
          name: "HEADER",
          angle: 270,  // maybe rotate the header to read sideways going up
          alignment: go.Spot.LeftCenter, alignmentFocus: go.Spot.LeftCenter
        },
        this.$(go.TextBlock,  // the lane label
          { editable: true, margin: new go.Margin(2, 0, 0, 8) },
          new go.Binding("visible", "isSubGraphExpanded").ofObject(),
          new go.Binding("text", "text").makeTwoWay()),
        this.$("SubGraphExpanderButton", { margin: 4, angle: -270 })  // but this remains always visible!
      ),  // end Horizontal Panel
      this.$(go.Placeholder,
        { padding: 12, alignment: go.Spot.TopLeft, alignmentFocus: go.Spot.TopLeft }),
      this.$(go.Panel, "Horizontal", { alignment: go.Spot.TopLeft, alignmentFocus: go.Spot.TopLeft },
        this.$(go.TextBlock,  // this TextBlock is only seen when the swimlane is collapsed
          {
            name: "LABEL",
            editable: true, visible: false,
            angle: 0, margin: new go.Margin(6, 0, 0, 20)
          },
          new go.Binding("visible", "isSubGraphExpanded",  (e) => { return !e; }).ofObject(),
          new go.Binding("text", "text").makeTwoWay())
      )
    );  // end swimLanesGroupTemplate

  // define a custom resize adornment that has two resize handles if the group is expanded
  //this.myDiagram.groupTemplate.resizeAdornmentTemplate =
  swimLanesGroupTemplate.resizeAdornmentTemplate =
    this.$(go.Adornment, "Spot",
      this.$(go.Placeholder),
      this.$(go.Shape,  // for changing the length of a lane
        {
          alignment: go.Spot.Right,
          desiredSize: new go.Size(7, 50),
          fill: "lightblue", stroke: "dodgerblue",
          cursor: "col-resize"
        },
        new go.Binding("visible", "",  (ad) => {
          if (ad.adornedPart === null) return false;
          return ad.adornedPart.isSubGraphExpanded;
        }).ofObject()),
      this.$(go.Shape,  // for changing the breadth of a lane
        {
          alignment: go.Spot.Bottom,
          desiredSize: new go.Size(50, 7),
          fill: "lightblue", stroke: "dodgerblue",
          cursor: "row-resize"
        },
        new go.Binding("visible", "",  (ad) =>{
          if (ad.adornedPart === null) return false;
          return ad.adornedPart.isSubGraphExpanded;
        }).ofObject())
    );

  let poolGroupTemplate =
    this.$(go.Group, "Auto", this.groupStyle(),
      {
        computesBoundsIncludingLinks: false,
        // use a simple layout that ignores links to stack the "lane" Groups on top of each other
        layout: this.$(PoolLayout, { spacing: new go.Size(0, 0) })  // no space between lanes
      },
      new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
      this.$(go.Shape,
        { fill: "white" },
        new go.Binding("fill", "color")),
      this.$(go.Panel, "Table",
        { defaultColumnSeparatorStroke: "black" },
        this.$(go.Panel, "Horizontal",
          { column: 0, angle: 270 },
          this.$(go.TextBlock,
            { editable: true, margin: new go.Margin(5, 0, 5, 0) },  // margin matches private process (black box pool)
            new go.Binding("text").makeTwoWay())
        ),
        this.$(go.Placeholder,
          { background: "darkgray", column: 1 })
      )
    ); // end poolGroupTemplate

  //------------------------------------------  Template Maps  ----------------------------------------------

  // create the nodeTemplateMap, holding main view node templates:
  let nodeTemplateMap = new go.Map("string", go.Node);
  // for each of the node categories, specify which template to use
  nodeTemplateMap.add("activity", activityNodeTemplate);
  nodeTemplateMap.add("event", eventNodeTemplate);
  nodeTemplateMap.add("gateway", gatewayNodeTemplate);
  nodeTemplateMap.add("annotation", annotationNodeTemplate);
  nodeTemplateMap.add("dataobject", dataObjectNodeTemplate);
  nodeTemplateMap.add("datastore", dataStoreNodeTemplate);
  nodeTemplateMap.add("privateProcess", privateProcessNodeTemplate);
  // for the default category, "", use the same template that Diagrams use by default
  // this just shows the key value as a simple TextBlock

  let groupTemplateMap = new go.Map("string", go.Group);
  groupTemplateMap.add("subprocess", subProcessGroupTemplate);
  groupTemplateMap.add("Lane", swimLanesGroupTemplate);
  groupTemplateMap.add("Pool", poolGroupTemplate);

  // create the nodeTemplateMap, holding special palette "mini" node templates:
  let palNodeTemplateMap = new go.Map("string", go.Node);
  palNodeTemplateMap.add("activity", activityNodeTemplateForPalette);
  palNodeTemplateMap.add("event", eventNodeTemplate);
  palNodeTemplateMap.add("gateway", gatewayNodeTemplateForPalette);
  palNodeTemplateMap.add("annotation", annotationNodeTemplate);
  palNodeTemplateMap.add("dataobject", dataObjectNodeTemplate);
  palNodeTemplateMap.add("datastore", dataStoreNodeTemplate);
  palNodeTemplateMap.add("privateProcess", privateProcessNodeTemplateForPalette);

  let palGroupTemplateMap = new go.Map("string", go.Group);
  palGroupTemplateMap.add("subprocess", subProcessGroupTemplateForPalette);
  palGroupTemplateMap.add("Pool", poolTemplateForPalette);
  palGroupTemplateMap.add("Lane", swimLanesGroupTemplateForPalette);


  //------------------------------------------  Link Templates   ----------------------------------------------

  let sequenceLinkTemplate =
    this.$(go.Link,
      {
        contextMenu:
          this.$(go.Adornment, "Vertical",
            this.$("ContextMenuButton",
              this.$(go.TextBlock, "Default Flow"),

              // in the click event handler, the obj.part is the Adornment; its adornedObject is the port
              { click:  (e:any, obj:any)=> {
                this.setSequenceLinkDefaultFlow(obj.part.adornedObject); }
               }),
            this.$("ContextMenuButton",
              this.$(go.TextBlock, "Conditional Flow"),
              // in the click event handler, the obj.part is the Adornment; its adornedObject is the port
              { click:  (e:any, obj:any) =>{ 
                this.setSequenceLinkConditionalFlow(obj.part.adornedObject); 
              } })
          ),
        routing: go.Link.AvoidsNodes, curve: go.Link.JumpGap, corner: 10,
        //fromSpot: go.Spot.RightSide, toSpot: go.Spot.LeftSide,
        reshapable: true, relinkableFrom: true, relinkableTo: true, toEndSegmentLength: 20
      },
      new go.Binding("points").makeTwoWay(),
      this.$(go.Shape, { stroke: "black", strokeWidth: 1 }),
      this.$(go.Shape, { toArrow: "Triangle", scale: 1.2, fill: "black", stroke: null }),
      this.$(go.Shape, { fromArrow: "", scale: 1.5, stroke: "black", fill: "white" },
        new go.Binding("fromArrow", "isDefault",  (s) => {
          if (s === null) return "";
          return s ? "BackSlash" : "StretchedDiamond";
        }),
        new go.Binding("segmentOffset", "isDefault",  (s) => {
          return s ? new go.Point(5, 0) : new go.Point(0, 0);
        })),
      this.$(go.TextBlock, { // this is a Link label
        name: "Label", editable: true, text: "label", segmentOffset: new go.Point(-10, -10), visible: false
      },
        new go.Binding("text", "text").makeTwoWay(),
        new go.Binding("visible", "visible").makeTwoWay())
    );

 

  let messageFlowLinkTemplate =
    this.$(PoolLink, // defined in BPMNClasses.js
      {
        routing: go.Link.Orthogonal, curve: go.Link.JumpGap, corner: 10,
        fromSpot: go.Spot.TopBottomSides, toSpot: go.Spot.TopBottomSides,
        reshapable: true, relinkableTo: true, toEndSegmentLength: 20
      },
      new go.Binding("points").makeTwoWay(),
      this.$(go.Shape, { stroke: "black", strokeWidth: 1, strokeDashArray: [6, 2] }),
      this.$(go.Shape, { toArrow: "Triangle", scale: 1, fill: "white", stroke: "black" }),
      this.$(go.Shape, { fromArrow: "Circle", scale: 1, visible: true, stroke: "black", fill: "white" }),
      this.$(go.TextBlock, {
        editable: true, text: "label"
      }, // Link label
        new go.Binding("text", "text").makeTwoWay())
    );

  let dataAssociationLinkTemplate =
    this.$(go.Link,
      {
        routing: go.Link.AvoidsNodes, curve: go.Link.JumpGap, corner: 10,
        fromSpot: go.Spot.AllSides, toSpot: go.Spot.AllSides,
        reshapable: true, relinkableFrom: true, relinkableTo: true
      },
      new go.Binding("points").makeTwoWay(),
      this.$(go.Shape, { stroke: "black", strokeWidth: 1, strokeDashArray: [1, 3] }),
      this.$(go.Shape, { toArrow: "OpenTriangle", scale: 1, fill: null, stroke: "blue" })
    );

    let annotationAssociationLinkTemplate =
    this.$(go.Link,
      {
        reshapable: true, relinkableFrom: true, relinkableTo: true,
        toSpot: go.Spot.AllSides,
        toEndSegmentLength: 20, fromEndSegmentLength: 40
      },
      new go.Binding("points").makeTwoWay(),
      this.$(go.Shape, { stroke: "black", strokeWidth: 1, strokeDashArray: [1, 3] }),
      this.$(go.Shape, { toArrow: "OpenTriangle", scale: 1, stroke: "black" })
    );

  let linkTemplateMap = new go.Map("string", go.Link);
  linkTemplateMap.add("msg", messageFlowLinkTemplate);
  linkTemplateMap.add("annotation", annotationAssociationLinkTemplate);
  linkTemplateMap.add("data", dataAssociationLinkTemplate);
  linkTemplateMap.add("", sequenceLinkTemplate);  // default


  //------------------------------------------the main Diagram----------------------------------------------

  this.myDiagram =
    this.$(go.Diagram, "myDiagramDiv",
      {
        nodeTemplateMap: nodeTemplateMap,
        linkTemplateMap: linkTemplateMap,
        groupTemplateMap: groupTemplateMap,

        allowDrop: true,  // accept drops from palette


        mouseDrop:  (e:any) => {
          // when the selection is dropped in the diagram's background,
          // make sure the selected Parts no longer belong to any Group
          var ok = this.myDiagram.commandHandler.addTopLevelParts(this.myDiagram.selection, true);
          if (!ok) this.myDiagram.currentTool.doCancel();
        },
        linkingTool: new BPMNLinkingTool(), // defined in BPMNClasses.js
        "SelectionMoved": this.relayoutDiagram.bind(this),  // defined below
        "SelectionCopied":  this.relayoutDiagram.bind(this)
      });

     let commandHandler =new DrawCommandHandler(); // defined in DrawCommandHandler.js
      // default to having arrow keys move selected nodes
      commandHandler.setArrowKeyBehavior( "move");
      this.myDiagram.commandHandler = commandHandler;
  this.myDiagram.toolManager.mouseDownTools.insertAt(0, new LaneResizingTool(this.myDiagram));

  this.myDiagram.addDiagramListener("LinkDrawn",  (e) => {
    if (e.subject.fromNode.category === "annotation") {
      e.subject.category = "annotation"; // annotation association
    }
    else if (e.subject.fromNode.category === "dataobject" || e.subject.toNode.category === "dataobject") {
      e.subject.category = "data"; // data association
    }
    else if (e.subject.fromNode.category === "datastore" || e.subject.toNode.category === "datastore") {
      e.subject.category = "data"; // data association
    }
  });


  //  uncomment this if you want a subprocess to expand on drop.  We decided we didn't like this behavior
  //  this.myDiagram.addDiagramListener("ExternalObjectsDropped", function(e) {
  //    // e.subject is the collection that was just dropped
  //    e.subject.each(function(part) {
  //        if (part instanceof go.Node && part.data.item === "end") {
  //          part.move(new go.Point(part.location.x  + 350, part.location.y))
  //        }
  //      });
  //    this.myDiagram.commandHandler.expandSubGraph();
  //  });

  // change the title to indicate that the diagram has been modified



  //------------------------------------------  Palette   ----------------------------------------------

 


  //------------------------------------------  Overview   ----------------------------------------------


} // end init


//------------------------------------------  pools / lanes   ----------------------------------------------



// some shared functions



// end LaneResizingTool class



// end PoolLayout class




//===================================================

}


// define a custom ResizingTool to limit how far one can shrink a lane Group
export class LaneResizingTool extends ResizingTool {// swimlanes
    private MINLENGTH = 400;  // this controls the minimum length of any swimlane
    private MINBREADTH = 20;  // this controls the minimum breadth of any non-collapsed swimlane
    private myDiagram:Diagram;
    constructor(mydiagram:Diagram){
        super();
        this.myDiagram = mydiagram;
        go.ResizingTool.call(this);
        
    }

    
public isLengthening() {
    return (this.handle.alignment === go.Spot.Right);
  };
  // compute the minimum size of a Pool Group needed to hold all of the Lane Groups
  private computeMinPoolSize(pool:any) {
    // assert(pool instanceof go.Group && pool.category === "Pool");
    var len = this.MINLENGTH;
    pool.memberParts.each( (lane:any) => {
      // pools ought to only contain lanes, not plain Nodes
      if (!(lane instanceof go.Group)) return;
      var holder = lane.placeholder;
      if (holder !== null) {
        var sz = holder.actualBounds;
        len = Math.max(len, sz.width);
      }
    });
    return new go.Size(len, NaN);
  }
  
  // compute the minimum size for a particular Lane Group
  private computeLaneSize(lane:any) {
    // assert(lane instanceof go.Group && lane.category !== "Pool");
    var sz = this.computeMinLaneSize(lane);
    if (lane.isSubGraphExpanded) {
      var holder = lane.placeholder;
      if (holder !== null) {
        var hsz = holder.actualBounds;
        sz.height = Math.max(sz.height, hsz.height);
      }
    }
    // minimum breadth needs to be big enough to hold the header
    var hdr = lane.findObject("HEADER");
    if (hdr !== null) sz.height = Math.max(sz.height, hdr.actualBounds.height);
    return sz;
  }
  
  // determine the minimum size of a Lane Group, even if collapsed
  private computeMinLaneSize(lane:any) {
    if (!lane.isSubGraphExpanded) return new go.Size(this.MINLENGTH, 1);
    return new go.Size(this.MINLENGTH, this.MINBREADTH);
  }
  /** @override */
  public computeMinSize() {
    var lane = this.adornedObject.part;
    // assert(lane instanceof go.Group && lane.category !== "Pool");
    var msz = this.computeMinLaneSize(lane);  // get the absolute minimum size
    if (this.isLengthening()) {  // compute the minimum length of all lanes
      var sz = this.computeMinPoolSize(lane.containingGroup);
      msz.width = Math.max(msz.width, sz.width);
    } else {  // find the minimum size of this single lane
      var sz = this.computeLaneSize(lane);
      msz.width = Math.max(msz.width, sz.width);
      msz.height = Math.max(msz.height, sz.height);
    }
    return msz;
  }
  
  /** @override */
  public canStart():boolean {
    if (!go.ResizingTool.prototype.canStart.call(this)) return false;
  
    // if this is a resize handle for a "Lane", we can start.
    var diagram = this.diagram;
    if (diagram === null) return false;
    var handl = this.findToolHandleAt(diagram.firstInput.documentPoint, this.name);
    if (handl !== null && handl.part !== null){
        var adornedObject =  handl.part as Adornment;
        if(adornedObject!==null && adornedObject.part!==null){
            return (adornedObject.part.category === "Lane");
        }
    }
   
    return false;    
  }
  
  /** @override */
  public resize (newr:any) {
    var lane = this.adornedObject.part;
    if (this.isLengthening()) {  // changing the length of all of the lanes
      lane.containingGroup.memberParts.each( (lane:any) => {
        if (!(lane instanceof go.Group)) return;
        var shape = lane.resizeObject;
        if (shape !== null) {  // set its desiredSize length, but leave each breadth alone
          shape.width = newr.width;
        }
      });
    } else {  // changing the breadth of a single lane
      go.ResizingTool.prototype.resize.call(this, newr);
    }
    this.relayoutDiagram();  // now that the lane has changed size, layout the pool again
  };
  // this is called after nodes have been moved or lanes resized, to layout all of the Pool Groups again
private relayoutDiagram() {
    this.myDiagram.layout.invalidateLayout();
    this.myDiagram.findTopLevelGroups().each( (g:any) =>{ if (g.category === "Pool") g.layout.invalidateLayout(); });
    this.myDiagram.layoutDiagram();
  }
}


// define a custom grid layout that makes sure the length of each lane is the same
// and that each lane is broad enough to hold its subgraph
export class PoolLayout extends GridLayout{
    private MINLENGTH = 400;  // this controls the minimum length of any swimlane
    private MINBREADTH = 20;  // this controls the minimum breadth of any non-collapsed swimlane
    constructor(){
        super();
        go.GridLayout.call(this);
        this.cellSize = new go.Size(1, 1);
        this.wrappingColumn = 1;
        this.wrappingWidth = Infinity;
        this.isRealtime = false;  // don't continuously layout while dragging
        this.alignment = go.GridLayout.Position;
        // This sorts based on the location of each Group.
        // This is useful when Groups can be moved up and down in order to change their order.
        this.comparer =  (a, b) => {
          var ay = a.location.y;
          var by = b.location.y;
          if (isNaN(ay) || isNaN(by)) return 0;
          if (ay < by) return -1;
          if (ay > by) return 1;
          return 0;
        };
    }
      // determine the minimum size of a Lane Group, even if collapsed
  private computeMinLaneSize(lane:any) {
    if (!lane.isSubGraphExpanded) return new go.Size(this.MINLENGTH, 1);
    return new go.Size(this.MINLENGTH, this.MINBREADTH);
  }
    // compute the minimum size of a Pool Group needed to hold all of the Lane Groups
    private computeMinPoolSize(pool:any) {
      // assert(pool instanceof go.Group && pool.category === "Pool");
      var len = this.MINLENGTH;
      pool.memberParts.each( (lane:any) => {
        // pools ought to only contain lanes, not plain Nodes
        if (!(lane instanceof go.Group)) return;
        var holder = lane.placeholder;
        if (holder !== null) {
          var sz = holder.actualBounds;
          len = Math.max(len, sz.width);
        }
      });
      return new go.Size(len, NaN);
    }
      // compute the minimum size for a particular Lane Group
  private computeLaneSize(lane:any) {
    // assert(lane instanceof go.Group && lane.category !== "Pool");
    var sz = this.computeMinLaneSize(lane);
    if (lane.isSubGraphExpanded) {
      var holder = lane.placeholder;
      if (holder !== null) {
        var hsz = holder.actualBounds;
        sz.height = Math.max(sz.height, hsz.height);
      }
    }
    // minimum breadth needs to be big enough to hold the header
    var hdr = lane.findObject("HEADER");
    if (hdr !== null) sz.height = Math.max(sz.height, hdr.actualBounds.height);
    return sz;
  }
     /** @override */
    public doLayout(coll:any) {
        var diagram = this.diagram;
        if (diagram === null) return;
        diagram.startTransaction("PoolLayout");
        var pool = this.group;
        if (pool !== null && pool.category === "Pool") {
          // make sure all of the Group Shapes are big enough
          var minsize = this.computeMinPoolSize(pool);
          pool.memberParts.each( (lane) => {
            if (!(lane instanceof go.Group)) return;
            if (lane.category !== "Pool") {
              var shape = lane.resizeObject;
              if (shape !== null) {  // change the desiredSize to be big enough in both directions
                var sz = this.computeLaneSize(lane);
                shape.width = (isNaN(shape.width) ? minsize.width : Math.max(shape.width, minsize.width));
                shape.height = (!isNaN(shape.height)) ? Math.max(shape.height, sz.height) : sz.height;
                var cell = lane.resizeCellSize;
                if (!isNaN(shape.width) && !isNaN(cell.width) && cell.width > 0) shape.width = Math.ceil(shape.width / cell.width) * cell.width;
                if (!isNaN(shape.height) && !isNaN(cell.height) && cell.height > 0) shape.height = Math.ceil(shape.height / cell.height) * cell.height;
              }
            }
          });
        }
        // now do all of the usual stuff, according to whatever properties have been set on this GridLayout
        go.GridLayout.prototype.doLayout.call(this, coll);
        diagram.commitTransaction("PoolLayout");
      };
    }

// window.setTimeout(
//     private () {
//       init();
//       var jsondata = JSON.parse('{ "class": "go.GraphLinksModel","linkFromPortIdProperty": "fromPort",          "linkToPortIdProperty": "toPort",          "modelData": {"position":"230 200"},          "nodeDataArray": [        {"category":"event", "item":"start", "key":101, "loc":"450 540", "text":"Start", "eventType":1, "eventDimension":1},        {"category":"event", "item":"End", "key":104, "loc":"1130 540", "text":"End", "eventType":1, "eventDimension":8},        {"category":"activity", "item":"generic task", "key":5, "loc":"1010 540", "text":"Send Invoice", "taskType":0, "boundaryEventArray":[  ]},        {"category":"activity", "item":"generic task", "key":-4, "loc":"860 540", "text":"Fulfill Order", "taskType":0, "boundaryEventArray":[  ]},        {"category":"activity", "item":"generic task", "key":-5, "loc":"710 540", "text":"Credit Check", "taskType":0, "boundaryEventArray":[  ]},        {"category":"activity", "item":"generic task", "key":-6, "loc":"560 540", "text":"Receive Order", "taskType":0, "boundaryEventArray":[  ]}],          "linkDataArray": [        {"from":101, "to":-6, "fromPort":"", "toPort":""},        {"from":-6, "to":-5, "fromPort":"", "toPort":""},        {"from":-5, "to":-4, "fromPort":"", "toPort":""},        {"from":-4, "to":5, "fromPort":"", "toPort":""},        {"from":5, "to":104, "fromPort":"", "toPort":""}        ]}')
//       // set these kinds of Diagram properties after initialization, not now
//       this.myDiagram.addDiagramListener("InitialLayoutCompleted", loadDiagramProperties);  // defined below
//       // create the model from the data in the JavaScript object parsed from JSON text
//       //this.myDiagram.model = new go.GraphLinksModel(jsondata["nodes"], jsondata["links"]);
//       this.myDiagram.model = go.Model.fromJson(jsondata);
//       loadDiagramProperties();
//       this.myDiagram.model.undoManager.isEnabled = true;
//       this.myDiagram.isModified = false;
//     }, 2000);