<body onload="init()">
  <div style="width:100%; white-space:nowrap;">
    <span style="display: inline-block; vertical-align: top">
      <div class="palettezone">
        <div class="draggable" draggable="true">Water</div>
        <div class="draggable" draggable="true">Coffee</div>
        <div class="draggable" draggable="true">Tea</div>
      </div>
    </span>
    <span style="display: inline-block; vertical-align: top">
      <div class="dropzone" id="myDiagramDiv" style="border: solid 1px black; width:400px; height:400px;"></div>
    </span>
  </div>
  <p><a href="https://gojs.net"/>gojs.net<a/></p>
</body>
.draggable {
    font: bold 16px sans-serif;
    width: 140px;
    height: 20px;
    text-align: center;
    background: white;
    cursor: move;
    margin-top: 20px;
  }

  .palettezone {
    width: 160px;
    height: 400px;
    background: lightblue;
    padding: 10px;
    padding-top: 1px;
    float: left;
  }
function init() {

    // *********************************************************
    // First, set up the infrastructure to do HTML drag-and-drop
    // *********************************************************

    var dragged = null; // A reference to the element currently being dragged

    // highlight stationary nodes/links during an external drag-and-drop into a Diagram
    function highlight(part) {  // may be null
      var oldskips = myDiagram.skipsUndoManager;
      myDiagram.skipsUndoManager = true;
      myDiagram.startTransaction("highlight");
      if (part !== null) {
        myDiagram.highlight(part);
      } else {
        myDiagram.clearHighlighteds();
      }
      myDiagram.commitTransaction("highlight");
      myDiagram.skipsUndoManager = oldskips;
    }

    // This event should only fire on the drag targets.
    // Instead of finding every drag target,
    // we can add the event to the document and disregard
    // all elements that are not of class "draggable"
    document.addEventListener("dragstart", function(event) {
      if (event.target.className !== "draggable") return;
      // Some data must be set to allow drag
      event.dataTransfer.setData("text", event.target.textContent);

      // store a reference to the dragged element
      dragged = event.target;
      // Objects during drag will have a red border
      event.target.style.border = "2px solid red";
    }, false);

    // This event resets styles after a drag has completed (successfully or not)
    document.addEventListener("dragend", function(event) {
      // reset the border of the dragged element
      dragged.style.border = "";
      highlight(null);
    }, false);

    // Next, events intended for the drop target - the Diagram div

    var div = document.getElementById("myDiagramDiv");
    div.addEventListener("dragenter", function(event) {
      // Here you could also set effects on the Diagram,
      // such as changing the background color to indicate an acceptable drop zone

      // Requirement in some browsers, such as Internet Explorer
      event.preventDefault();
    }, false);

    div.addEventListener("dragover", function(event) {
      // We call preventDefault to allow a drop
      // But on divs that already contain an element,
      // we want to disallow dropping

      if (this === myDiagram.div) {
        var can = event.target;
        var pixelratio = window.PIXELRATIO;

        // if the target is not the canvas, we may have trouble, so just quit:
        if (!(can instanceof HTMLCanvasElement)) return;

        var bbox = can.getBoundingClientRect();
        var bbw = bbox.width;
        if (bbw === 0) bbw = 0.001;
        var bbh = bbox.height;
        if (bbh === 0) bbh = 0.001;
        var mx = event.clientX - bbox.left * ((can.width / pixelratio) / bbw);
        var my = event.clientY - bbox.top * ((can.height / pixelratio) / bbh);
        var point = myDiagram.transformViewToDoc(new go.Point(mx, my));
        var curpart = myDiagram.findPartAt(point, false);
        if (curpart instanceof go.Node || curpart instanceof go.Link) {
          highlight(curpart);
        } else {
          highlight(null);
        }
      }

      if (event.target.className === "dropzone") {
        // Disallow a drop by returning before a call to preventDefault:
        return;
      }

      // Allow a drop on everything else
      event.preventDefault();
    }, false);

    div.addEventListener("dragleave", function(event) {
      // reset background of potential drop target
      if (event.target.className == "dropzone") {
        event.target.style.background = "";
      }
      highlight(null);
    }, false);

    // handle the user option for removing dragged items from the Palette
    var remove = document.getElementById('remove');

    div.addEventListener("drop", function(event) {
      // prevent default action
      // (open as link for some elements in some browsers)
      event.preventDefault();

      // Dragging onto a Diagram
      if (this === myDiagram.div) {
        var can = event.target;
        var pixelratio = window.PIXELRATIO;

        // if the target is not the canvas, we may have trouble, so just quit:
        if (!(can instanceof HTMLCanvasElement)) return;

        myDiagram.startTransaction('new node');
        var overPart = myDiagram.highlighteds.first();
        var group = undefined;
        if (overPart && overPart.containingGroup !== null) {
          group = overPart.containingGroup.data.key;
        }
        var newData = {
          key: event.dataTransfer.getData('text'),
          color: "lightyellow",
          group: group
        }
        myDiagram.model.addNodeData(newData);
        
        if (overPart instanceof go.Node) {
          // could do something for drop on node here
        } else if (overPart instanceof go.Link) {
          if (overPart.containingGroup !== null) {
            console.log(overPart.containingGroup.data.key);
            newData.group = overPart.containingGroup.data.key;
          }
          dropOntoLink(newData, overPart);
        }
        myDiagram.commitTransaction('new node');
        myDiagram.animationManager.stopAnimation();
      }

      // If we were using drag data, we could get it here, ie:
      // var data = event.dataTransfer.getData('text');
    }, false);

    function dropOntoLink(newData, link) {
      var diagram = link.diagram;
      // add links between old from/to and new node
      var oldFrom = link.fromNode.data.key;
      var oldTo = link.toNode.data.key;
      var newKey = diagram.model.getKeyForNodeData(newData);
      diagram.model.addLinkData({
        from: oldFrom,
        to: newKey
      });
      diagram.model.addLinkData({
        from: newKey,
        to: oldTo
      });
      // remove this link
      diagram.model.removeLinkData(link.data);
    }

    // *********************************************************
    // Second, set up a GoJS Diagram
    // *********************************************************

    var $ = go.GraphObject.make;  // for conciseness in defining templates

    myDiagram = $(go.Diagram, "myDiagramDiv",  // create a Diagram for the DIV HTML element
                  {
                    "undoManager.isEnabled": true,
                    layout: $(go.TreeLayout, { angle: 90 })
                  });
    window.PIXELRATIO = myDiagram.computePixelRatio(); // constant needed to determine mouse coordinates on the canvas
  
    myDiagram.groupTemplate =
      $(go.Group, "Auto",
        { layout: $(go.TreeLayout) },
        $(go.Shape,
          { fill: "whitesmoke" }
        ),
        $(go.Panel, "Vertical",
          $(go.Panel, "Horizontal",
            $("SubGraphExpanderButton", { margin: 3 }),
            $(go.TextBlock, "Group",
              { margin: 3, font: "bold 16px sans-serif" } 
            )
          ),
          $(go.Placeholder, { padding: 5 })
        )
      );

    // define a simple Node template
    myDiagram.nodeTemplate =
      $(go.Node, "Auto",
        // { locationSpot: go.Spot.Center },
        // new go.Binding("location"),
        $(go.Shape, "Rectangle",
          { fill: 'white' },
          // Shape.fill is bound to Node.data.color
          new go.Binding("fill", "color"),
          // this binding changes the Shape.fill when Node.isHighlighted changes value
          new go.Binding("fill", "isHighlighted", function(h, shape) {
            if (h) return "red";
            var c = shape.part.data.color;
            return c ? c : "white";
          }).ofObject()),  // binding source is Node.isHighlighted
        $(go.TextBlock,
          { margin: 3, font: "bold 16px sans-serif", width: 140, textAlign: 'center' },
          // TextBlock.text is bound to Node.data.key
          new go.Binding("text", "key"))
      );

    myDiagram.linkTemplate =
      $(go.Link,
        { toShortLength: 4, layoutConditions: go.Part.LayoutAdded | go.Part.LayoutRemoved, selectable: false },
        $(go.Shape,
          { stroke: "black", strokeWidth: 3 },
          new go.Binding("stroke", "isHighlighted", linkHighlightConverter).ofObject()
        ),
        $(go.Shape,
          { toArrow: "standard", stroke: null, fill: "black" },
          new go.Binding("fill", "isHighlighted", linkHighlightConverter).ofObject()
        )
      );

    function linkHighlightConverter(h, shape) {
      if (h) return "red";
      return "black";
    }

    // create the model data that will be represented by Nodes and Links
    myDiagram.model = new go.GraphLinksModel(
    [
      { key: "Alpha", color: "lightblue" },
      { key: "Beta", color: "orange" },
      { key: "Gamma", color: "lightgreen" },
      { key: "Delta", color: "pink" },
      { key: "g1", isGroup: true },
      { key: "Omega", group: "g1" },
      { key: "Epsilon", group: "g1" }
    ],
    [
      { from: "Alpha", to: "g1" },
      { from: "g1", to: "Beta" },
      { from: "Beta", to: "Gamma" },
      { from: "Beta", to: "Delta" },
      { from: "Omega", to: "Epsilon" }
    ]);
  }
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://gojs.net/latest/release/go.js