gojs用于节点和链接文本的dinamical下拉选项

时间:2018-02-07 23:27:53

标签: javascript python gojs

我想为用户编制自定义选项下拉菜单以编辑节点和链接文本。

enter image description here

到目前为止,我使用此代码在下拉列表中选择用户选择节点和链接文本:

<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>State Chart</title>
<meta name="description" content="A finite state machine chart with editable and interactive features." />
<meta charset="UTF-8">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<link href="http://getbootstrap.com/dist/css/bootstrap.min.css" rel="stylesheet">

<script src="{{url_for('static', filename='go.js')}}"></script>

<!-- custom text editors -->
<script src="{{url_for('static', filename='TextEditorSelectBox.js')}}"></script>
<script src="{{url_for('static', filename='TextEditorRadioButtons.js')}}"></script>
<script src="{{url_for('static', filename='TextEditorSelectBox.js')}}"></script>


<script src="{{url_for('static', filename='DataInspector.js')}}"></script>

<link href="https://gojs.net/latest/extensions/DataInspector.css" rel="stylesheet">
<link href="{{url_for('static', filename='DataInspector.css')}}" rel="stylesheet">


<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet"> 
<style>

body {
  overflow-x: hidden;
}

#wrapper {
  padding-left: 0;
  -webkit-transition: all 0.5s ease;
  -moz-transition: all 0.5s ease;
  -o-transition: all 0.5s ease;
  transition: all 0.5s ease;
}

#wrapper.toggled {
  padding-left: 250px;
}

#sidebar-wrapper {
  z-index: 1000;
  position: fixed;
  left: 250px;
  width: 0;
  height: 100%;
  margin-left: -250px;
  overflow-y: auto;
  background: #000;
  -webkit-transition: all 0.5s ease;
  -moz-transition: all 0.5s ease;
  -o-transition: all 0.5s ease;
  transition: all 0.5s ease;
}

#wrapper.toggled #sidebar-wrapper {
  width: 250px;
}

#page-content-wrapper {
  width: 100%;
  position: absolute;
  padding: 15px;
}

#wrapper.toggled #page-content-wrapper {
  position: absolute;
  margin-right: -250px;
}


/* Sidebar Styles */

.sidebar-nav {
  position: absolute;
  top: 0;
  width: 250px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.sidebar-nav li {
  text-indent: 20px;
  line-height: 40px;
}

.sidebar-nav li a {
  display: block;
  text-decoration: none;
  color: #999999;
}

.sidebar-nav li a:hover {
  text-decoration: none;
  color: #fff;
  background: rgba(255, 255, 255, 0.2);
}

.sidebar-nav li a:active, .sidebar-nav li a:focus {
  text-decoration: none;
}

.sidebar-nav>.sidebar-brand {
  height: 65px;
  font-size: 18px;
  line-height: 60px;
}

.sidebar-nav>.sidebar-brand a {
  color: #999999;
}

.sidebar-nav>.sidebar-brand a:hover {
  color: #fff;
  background: none;
}

@media(min-width:768px) {
  #wrapper {
    padding-left: 0;
  }
  #wrapper.toggled {
    padding-left: 250px;
  }
  #sidebar-wrapper {
    width: 0;
  }
  #wrapper.toggled #sidebar-wrapper {
    width: 250px;
  }
  #page-content-wrapper {
    padding: 20px;
    position: relative;
  }
  #wrapper.toggled #page-content-wrapper {
    position: relative;
    margin-right: 0;
  }
}

</style>
<script id="code">

    var nodeChoices = ['choice 1', 'choice 2', 'choice 3', 'choice 4', 'choice 5'];
    const originalNodeChoices = ['choice 1', 'choice 2', 'choice 3', 'choice 4', 'choice 5'];
    var linkChoices = ['link choice 1', 'link choice 2', 'link choice 3', 'link choice 4', 'link choice 5'];
    const originalLinkChoices = ['link choice 1', 'link choice 2', 'link choice 3', 'link choice 4', 'link choice 5'];

    function init() {

        var $ = go.GraphObject.make;
        myDiagram =
        $(go.Diagram, "myDiagramDiv",  // must name or refer to the DIV HTML element
        {
            // start everything in the middle of the viewport
            initialContentAlignment: go.Spot.Center,
            // have mouse wheel events zoom in and out instead of scroll up and down
            "toolManager.mouseWheelBehavior": go.ToolManager.WheelZoom,
            // support double-click in background creating a new node
            "clickCreatingTool.archetypeNodeData": { text: "new node" },
            // enable undo & redo
            "textEditingTool.defaultTextEditor": window.TextEditorSelectBox,
            "undoManager.isEnabled": true,
            "layout": new go.ForceDirectedLayout(),
            "ModelChanged": function(e) {
                //console.log("Diagram model changed!");

              if (e.change === go.ChangedEvent.Remove && e.modelChange === "linkDataArray") {
                console.log("eee its linkDataArray");
                console.log(e);
                var linkdata = e.oldValue;
                console.log("linkdata");
                console.log(linkdata);
                var oldstr = linkdata.text;
                console.log("oldstr");
                console.log(oldstr);

                if (!oldstr) return;
                var customModelData = e.model.modelData.choices;
                var choices = e.model.modelData.choices.linkChoices;
                console.log("choices");
                console.log(choices);
                var idx = choices.indexOf(oldstr);
                if (idx < 0) {
                  console.log("adding choice: " + oldstr);
                  var newchoices = Array.prototype.slice.call(choices);
                  newchoices.push(oldstr);
                  e.model.set(e.model.modelData, "choices", newchoices);
                }
              }else if(e.change === go.ChangedEvent.Remove && e.modelChange === "nodeDataArray"){
                    console.log("eee its nodeDataArray");
              }
              /*
              else{
                console.log("elseeeeeee111!");
                console.log("e.change");
                console.log(e.change);
                console.log("e.modelChange");
                console.log(e.modelChange);
              }*/
            }
        });

        //myDiagram.model.set(myDiagram.model.modelData, "choices", JSON.parse('{{ link_choices | tojson | safe}}'));
        //myDiagram.model.set(myDiagram.model.modelData, "choices", ["one", "two", "three"]);
        console.log("myDiagram.model.modelData");
        console.log(myDiagram.model.modelData);
        console.log("myDiagram.model.modelData.choices");
        console.log(myDiagram.model.modelData.choices);


        // when the document is modified, add a "*" to the title and enable the "Save" button
        myDiagram.addDiagramListener("Modified", function(e) {
            var button = document.getElementById("SaveButton");
            if (button) button.disabled = !myDiagram.isModified;
            var idx = document.title.indexOf("*");
            if (myDiagram.isModified) {
                if (idx < 0) document.title += "*";
            } 
            else {
                if (idx >= 0) document.title = document.title.substr(0, idx);
            }
        });

        myDiagram.addDiagramListener("textEdited", function(e) {
            console.log("Text is edited");
            console.log(e);

            //CHECK IF LINK,
            //IF YES REMOVE THAT OPTION FROM LIST

        });

        myDiagram.addDiagramListener("SelectionDeleting", function(e) {
            console.log("inside SelectionDeleting");
            console.log(e);

            //CHECK IF LINK,
            //IF YES PUT THAT OPTION BACK IN OPTION LIST

        });

            // define the Node template
        myDiagram.nodeTemplate =
          $(go.Node, "Auto",
            new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
            // define the node's outer shape, which will surround the TextBlock
            $(go.Shape, "RoundedRectangle",
              {
                parameter1: 20,  // the corner has a large radius
                fill: $(go.Brush, "Linear", { 0: "rgb(254, 201, 0)", 1: "rgb(254, 162, 0)" }),
                stroke: null,
                portId: "",  // this Shape is the Node's port, not the whole Node
                fromLinkable: true, fromLinkableDuplicates: true,
                toLinkable: true, toLinkableDuplicates: true,
                cursor: "pointer"
              }),
            $(go.TextBlock,
              {
                font: "bold 11pt helvetica, bold arial, sans-serif",
                editable: true,  // editing the text automatically updates the model data
                //textEditor: window.TextEditorRadioButtons, // defined in textEditorRadioButtons.js
                // this specific TextBlock has its own choices:

                textEditor: window.TextEditorRadioButtons,
                textEdited: function(tb, oldstr, newstr) {
                    console.log("textEdited nodeeeeeeeeeeeeeeeeeeeeeeeee!!!");
                  var choices = tb.diagram.model.modelData.choices.nodeChoices;
                  console.log("choices");
                  console.log(choices);
                  console.log("newstr");
                  console.log(newstr);
                  console.log("oldstr");
                  console.log(oldstr);
                  var idx = choices.indexOf(newstr);
                  if (idx >= 0 && oldstr !== newstr ) {
                    console.log(choices);
                    console.log("choices");
                    console.log(choices);
                    console.log("removing choice " + idx + ": " + newstr);
                    var newchoices = Array.prototype.slice.call(choices);

                    newchoices.splice(idx, 1);
                    console.log("after remove");
                    console.log(newchoices);
                    var currentChoicesFromModel = tb.diagram.model.modelData.choices;
                    console.log("currentChoicesFromModel");
                    console.log(currentChoicesFromModel);
                    currentChoicesFromModel.nodeChoices = newchoices;
                    console.log("currentChoicesFromModel after update: ");
                    console.log(currentChoicesFromModel);
                    tb.diagram.model.set(tb.diagram.model.modelData, "choices", currentChoicesFromModel);
                    //tb.editable = false;  // don't allow choice again
                  }else{
                        console.log("elseeeeee");
                  }
                },
                //choices: JSON.parse('{{ choices | tojson | safe}}')
                choices: nodeChoices
              },
            new go.Binding("text").makeTwoWay())
        );

        myDiagram.nodeTemplate.selectionAdornmentTemplate =
        $(go.Adornment, "Spot",
            $(go.Panel, "Auto",
            $(go.Shape, { stroke: "dodgerblue", strokeWidth: 2, fill: null }),
            $(go.Placeholder)
        ),
        $(go.Panel, "Horizontal",
            { alignment: go.Spot.Top, alignmentFocus: go.Spot.Bottom },
            $("Button",
                { click: editText },  // defined below, to support editing the text of the node
                $(go.TextBlock, "t",
                { font: "bold 10pt sans-serif", desiredSize: new go.Size(15, 15), textAlign: "center" })
            ),
            $("Button",
            { // drawLink is defined below, to support interactively drawing new links
                click: drawLink,  // click on Button and then click on target node
                actionMove: drawLink  // drag from Button to the target node
            },
            $(go.Shape,
                { geometryString: "M0 0 L8 0 8 12 14 12 M12 10 L14 12 12 14" })
            ),
            $("Button",
            {
                actionMove: dragNewNode,  // defined below, to support dragging from the button
                _dragData: { text: "?????", color: "lightgray" },  // node data to copy
                click: clickNewNode  // defined below, to support a click on the button
            },
            $(go.Shape,
                { geometryString: "M0 0 L3 0 3 10 6 10 x F1 M6 6 L14 6 14 14 6 14z", fill: "gray" })
          )
        )
      );

    //myDiagram.model.set(myDiagram.model.modelData, "choices", JSON.parse('{{ link_choices | tojson | safe}}'));
    //myDiagram.model.set(myDiagram.model.modelData, "choices", ["one", "two", "three"]);
    console.log("myDiagram.model.modelData");
    console.log(myDiagram.model.modelData);
    console.log(myDiagram.model.modelData.choices);

    function editText(e, button) {
        //console.log(e);

      var node = button.part.adornedPart;
      console.log("node");
      //console.log(node);
      e.diagram.commandHandler.editTextBlock(node.findObject("TEXTBLOCK"));
      //$("#nodeText").val(node.findObject("TEXTBLOCK"));
    }

    function drawLink(e, button) {
      var node = button.part.adornedPart;
      var tool = e.diagram.toolManager.linkingTool;
      tool.startObject = node.port;
      e.diagram.currentTool = tool;
      tool.doActivate();
    }

   // used by both clickNewNode and dragNewNode to create a node and a link
    // from a given node to the new node
    function createNodeAndLink(data, fromnode) {
        var diagram = fromnode.diagram;
        var model = diagram.model;
        var nodedata = model.copyNodeData(data);
        model.addNodeData(nodedata);
        var newnode = diagram.findNodeForData(nodedata);
        var linkdata = model.copyLinkData({});
        model.setFromKeyForLinkData(linkdata, model.getKeyForNodeData(fromnode.data));
        model.setToKeyForLinkData(linkdata, model.getKeyForNodeData(newnode.data));
        model.addLinkData(linkdata);
        diagram.select(newnode);
        return newnode;
    }

    // the Button.click event handler, called when the user clicks the "N" button
    function clickNewNode(e, button) {
        var data = button._dragData;
        if (!data) return;
        e.diagram.startTransaction("Create Node and Link");
        var fromnode = button.part.adornedPart;
        var newnode = createNodeAndLink(button._dragData, fromnode);
        newnode.location = new go.Point(fromnode.location.x + 200, fromnode.location.y);
        e.diagram.commitTransaction("Create Node and Link");
    }

    // the Button.actionMove event handler, called when the user drags within the "N" button
    function dragNewNode(e, button) {
        var tool = e.diagram.toolManager.draggingTool;
        if (tool.isBeyondDragSize()) {
            var data = button._dragData;
            if (!data) return;
            e.diagram.startTransaction("button drag");  // see doDeactivate, below
            var newnode = createNodeAndLink(data, button.part.adornedPart);
            newnode.location = e.diagram.lastInput.documentPoint;
            // don't commitTransaction here, but in tool.doDeactivate, after drag operation finished
            // set tool.currentPart to a selected movable Part and then activate the DraggingTool
            tool.currentPart = newnode;
            e.diagram.currentTool = tool;
            tool.doActivate();
        }
    }

    // using dragNewNode also requires modifying the standard DraggingTool so that it
    // only calls commitTransaction when dragNewNode started a "button drag" transaction;
    // do this by overriding DraggingTool.doDeactivate:
    var tool = myDiagram.toolManager.draggingTool;
    tool.doDeactivate = function() {
        // commit "button drag" transaction, if it is ongoing; see dragNewNode, above
        if (tool.diagram.undoManager.nestedTransactionNames.elt(0) === "button drag") {
            tool.diagram.commitTransaction();
        }
        go.DraggingTool.prototype.doDeactivate.call(tool);  // call the base method
    };

    // replace the default Link template in the linkTemplateMap
    myDiagram.linkTemplate =
        $(go.Link,  // the whole link panel
        {
            curve: go.Link.Bezier, 
            adjusting: go.Link.Stretch,
            reshapable: true, 
            relinkableFrom: true, 
            relinkableTo: true,
            toShortLength: 3
        },
        new go.Binding("points").makeTwoWay(),
        new go.Binding("curviness"),
        $(go.Shape,  // the link shape
            { strokeWidth: 1.5 }),
        $(go.Shape,  // the arrowhead
            { toArrow: "standard", stroke: null }),
        $(go.Panel, "Auto",
            $(go.Shape,  // the label background, which becomes transparent around the edges
            {
                fill: $(go.Brush, "Radial", { 0: "rgb(240, 240, 240)", 0.3: "rgb(240, 240, 240)", 1: "rgba(240, 240, 240, 0)" }),
                stroke: null
            }),
            $(go.TextBlock,
              {
                background: "white",
                editable: true,
                textEditor: window.TextEditorSelectBox, // defined in extensions/textEditorSelectBox.js
                textEdited: function(tb, oldstr, newstr) {
                  var choices = tb.diagram.model.modelData.choices.linkChoices;
                  console.log("choices");
                  console.log(choices);
                  console.log("newstr");
                  console.log(newstr);
                  console.log("oldstr");
                  console.log(oldstr);
                  var idx = choices.indexOf(newstr);
                  if (idx >= 0 && oldstr !== newstr ) {
                    console.log(choices);
                    console.log("choices");
                    console.log(choices);
                    console.log("removing choice " + idx + ": " + newstr);
                    var newchoices = Array.prototype.slice.call(choices);
                    newchoices.splice(idx, 1);
                    tb.diagram.model.set(tb.diagram.model.modelData.choices.linkChoices, "choices", newchoices);
                    //tb.editable = false;  // don't allow choice again
                  }else{
                        console.log("elseeeeee");
                  }
                }
              },
            // editing the text automatically updates the model data
            //new go.Binding("text"),
            new go.Binding("text").makeTwoWay(),
            new go.Binding("choices").ofModel())
        )
    );


    var inspector = new Inspector('myInspectorDiv', myDiagram,
    {
        // uncomment this line to only inspect the named properties below instead of all properties on each object:
        // includesOwnProperties: false,
        properties: {
            "text": { },
            // an example of specifying the type
            "password": { show: Inspector.showIfPresent, type: 'password' },
            // key would be automatically added for nodes, but we want to declare it read-only also:
            "key": { readOnly: true, show: Inspector.showIfPresent },
            // color would be automatically added for nodes, but we want to declare it a color also:
            "color": { show: Inspector.showIfPresent, type: 'color' },
            // Comments and LinkComments are not in any node or link data (yet), so we add them here:
            "Comments": { show: Inspector.showIfNode  },
            "flag": { show: Inspector.showIfNode, type: 'checkbox' },
            "LinkComments": { show: Inspector.showIfLink },
            "isGroup": { readOnly: true, show: Inspector.showIfPresent }
        }
    });

    // read in the JSON data from flask
    loadGraphData();

    }

    function loadGraphData() {
        var graphDataString = JSON.parse('{{ diagramData | tojson | safe}}');
        //console.log("graphDataString");
        //console.log(graphDataString);

        var allChoices = JSON.parse('{{ allChoices | tojson | safe}}');
        console.log("allChoices");
        console.log(allChoices);

        myDiagram.model = go.Model.fromJson(graphDataString);
        //myDiagram.model.set(myDiagram.model.modelData, "choices", ["one", "two", "three"]);

        var customModelData = {
            "linkChoices": linkChoices, "nodeChoices": nodeChoices
        };

        myDiagram.model.set(myDiagram.model.modelData, "choices", allChoices);
        //myDiagram.model.set(myDiagram.model.modelData, "choices", JSON.parse('{{ link_choices | tojson | safe}}'));
        //myDiagram.model.set(myDiagram.model.modelData, "choices", customModelData);
        console.log("whole model modelData");
        console.log(myDiagram.model.modelData);
    }

    function saveGraphData(form, event) {
        console.log("inside saveGraphData");
        event.preventDefault();

        document.getElementById("mySavedModel").value = myDiagram.model.toJson();
        form.submit();
    }

    function zoomToFit(){
        console.log("inside zoomToFit");
        myDiagram.zoomToRect(myDiagram.documentBounds);
    }

    function zoomIn(){
        console.log("inside zoomIn");
        myDiagram.commandHandler.increaseZoom();
    }
    function zoomOut(){
        console.log("inside zoomOut");
        myDiagram.commandHandler.decreaseZoom();
    }

</script>
</head>
<body onload="init()">



    <div id="wrapper">

        <!-- Sidebar -->
        <div id="sidebar-wrapper">
            <ul class="sidebar-nav">
                <li class="sidebar-brand"> <a href="#"> CUSTOM CHOICES MODE </a>  </li>


                <li> <a href="{{ url_for('main') }}">Free editing mode</a> </li>
                <li> <a href="{{ url_for('graphMode1') }}">Fill mode</a> </li>
                <li> <a href="{{ url_for('graphMode2') }}">Custom choices mode</a> </li>
                <li> <a href="{{ url_for('graphMode3') }}">Locked choices mode</a> </li>

            </ul>
        </div>
        <!-- /#sidebar-wrapper -->

        <!-- Page Content -->
        <div id="page-content-wrapper">
            <div class="container-fluid">


                <div id=formWrapper style="padding: 30px;">

                    <a href="#menu-toggle" class="btn btn-secondary" id="menu-toggle" style="margin-bottom: 15px;">Toggle Menu</a>

                    <form method="POST" action="http://localhost:5000/updateResultFile" name="updateResultFileForm" 
                    id="updateResultFileForm" 
                    onsubmit="saveGraphData(this, event);">

                        <div id="graphWrapper" style="margin-bottom: 15px;">
                            <div id="myDiagramDiv" style="border: solid 1px black; width: 100%; height: 800px;margin-bottom: 15px;"></div>
                            <div style="display: none;"><input id="mySavedModel" name="mySavedModel"></div>

                            <button class="btn btn-default" type="submit"> Save <i class="fa fa-save"> </i> </button>

                        </div>

                    </form>

                    <div id="myInspectorDiv">
                    </div>

                    <div>
                        <button class="btn btn-default" onclick="zoomToFit()"> Zoom to fit  <i class="fa fa-search"> </i> </button>
                        <button class="btn btn-default" onclick="zoomIn()"> Zoom in  <i class="fa fa-search-plus"> </i> </button>
                        <button class="btn btn-default" onclick="zoomOut()"> Zoom out  <i class="fa fa-search-minus"> </i> </button>
                    </div>


                </div>

            </div>
        </div>

    </div>

    <script src="{{url_for('static', filename='jquery.min.js')}}"></script>
    <script src="{{url_for('static', filename='bootstrap.bundle.min.js')}}"></script>

    <!-- Menu Toggle Script -->
    <script>
        var menuOpen = false;
        $("#menu-toggle").click(function(e) {
            e.preventDefault();
            $("#wrapper").toggleClass("toggled");

            if(menuOpen){
                menuOpen = false;
                $("#myDiagramDiv").css({border: "solid 1px black"});
            }else{
                menuOpen = true;
                $("#myDiagramDiv").css({border: ""});
            }
        });

    </script>

</body>
</html>

基本上我已经为节点和链接做了自定义选择。例如,对于节点I,我有以下选择:

  • 节点选择1
  • 节点选择2
  • 节点选择3

对于链接,我有以下选择:

  • link choice 1
  • link choice 2
  • link choice 3

我想要完成的是当我选择&#34;节点选择1&#34;从一个节点上的下拉列表中,该选项&#34;节点选择1&#34;无法再为另一个节点选择。但是如果用户删除节点,或者在节点文本上设置空选择,那么节点选择1&#34;必须再次可用。

链接也是如此。

我认为我的代码适用于链接,但我在检测节点文本更改和更新dataModel内容时遇到问题。

1 个答案:

答案 0 :(得分:1)

我之前似乎错过了这个。这里有一些代码可以通过实现它来回答您的问题:

function init() {
  var $ = go.GraphObject.make;

  myDiagram =
    $(go.Diagram, "myDiagramDiv",
      {
        initialContentAlignment: go.Spot.Center,
        "undoManager.isEnabled": true,
        "ModelChanged": function(e) {     // just for demonstration purposes,
          if (e.isTransactionFinished) {  // show the model data in the page's TextArea
            document.getElementById("mySavedModel").textContent = e.model.toJson();
          }
          if (e.change === go.ChangedEvent.Remove) {
            if (e.modelChange === "linkDataArray") {
              var linkdata = e.oldValue;
              var oldstr = linkdata.text;
              if (!oldstr) return;
              var linkchoices = e.model.modelData.linkchoices;
              var idx = linkchoices.indexOf(oldstr);
              if (idx < 0) {
                console.log("deletion adding link choice: " + oldstr);
                var newlinkchoices = Array.prototype.slice.call(linkchoices);
                newlinkchoices.push(oldstr);
                e.model.set(e.model.modelData, "linkchoices", newlinkchoices);
              }
            } else if (e.modelChange === "nodeDataArray") {
              var nodedata = e.oldValue;
              var oldstr = nodedata.text;
              if (!oldstr) return;
              var nodechoices = e.model.modelData.nodechoices;
              var idx = nodechoices.indexOf(oldstr);
              if (idx < 0) {
                console.log("deletion adding node choice: " + oldstr);
                var newnodechoices = Array.prototype.slice.call(nodechoices);
                newnodechoices.push(oldstr);
                e.model.set(e.model.modelData, "nodechoices", newnodechoices);
              }
            }
          }
        }
      });

  myDiagram.nodeTemplate =
    $(go.Node, "Auto",
      $(go.Shape,
        { fill: "white", portId: "", fromLinkable: true, toLinkable: true, cursor: "pointer" },
        new go.Binding("fill", "color")),
      $(go.TextBlock,
        {
          margin: 8,
          editable: true,
          textEditor: window.TextEditorSelectBox, // defined in extensions/textEditorSelectBox.js
          textEdited: function(tb, oldstr, newstr) {
            if (oldstr !== newstr) {
              console.log("changing from " + oldstr + " to " + newstr);
              var model = tb.diagram.model;
              var nodechoices = model.modelData.nodechoices;
              if (!nodechoices) {
                nodechoices = [];
                model.set(model.modelData, "nodechoices", nodechoices);
              }
              var idx = nodechoices.indexOf(newstr);
              if (idx >= 0) {
                console.log("removing node choice " + idx + ": " + newstr);
                model.removeArrayItem(nodechoices, idx);
              }
              if (oldstr) {
                console.log("  adding node choice " + oldstr);
                model.addArrayItem(nodechoices, oldstr);
              }
            }
          }
        },
        new go.Binding("text").makeTwoWay(),
        new go.Binding("choices", "nodechoices").ofModel())
    );

  myDiagram.linkTemplate =
    $(go.Link,
      $(go.Shape),
      $(go.Shape, { toArrow: "OpenTriangle" }),
      $(go.TextBlock,
        {
          background: "white",
          editable: true,
          textEditor: window.TextEditorSelectBox, // defined in extensions/textEditorSelectBox.js
          textEdited: function(tb, oldstr, newstr) {
            if (oldstr !== newstr) {
              console.log("changing from " + oldstr + " to " + newstr);
              var model = tb.diagram.model;
              var linkchoices = model.modelData.linkchoices;
              if (!linkchoices) {
                linkchoices = [];
                model.set(model.modelData, "linkchoices", linkchoices);
              }
              var idx = linkchoices.indexOf(newstr);
              if (idx >= 0) {
                console.log("removing link choice " + idx + ": " + newstr);
                model.removeArrayItem(linkchoices, idx);
              }
              if (oldstr) {
                console.log("  adding link choice " + oldstr);
                model.addArrayItem(linkchoices, oldstr);
              }
            }
          }
        },
        new go.Binding("text").makeTwoWay(),
        new go.Binding("choices", "linkchoices").ofModel())
    );

  myDiagram.model = new go.GraphLinksModel(
  [
    { key: 1, text: "Alpha", color: "lightblue" },
    { key: 2, text: "Beta", color: "orange" },
    { key: 3, text: "Gamma", color: "lightgreen" },
    { key: 4, text: "Delta", color: "pink" }
  ],
  [
    { from: 1, to: 2, text: "one" },
    { from: 1, to: 3, text: "two" },
    { from: 3, to: 4, text: "three" }
  ]);
  myDiagram.model.set(myDiagram.model.modelData, "nodechoices", ["Epsilon"]);
  myDiagram.model.set(myDiagram.model.modelData, "linkchoices", ["four"]);
}