DIR : /home/kozerus/public_html/ko/pipes.html

/home/kozerus/public_html/ko

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, viewport-fit=cover"/>
<meta name="description" content="An editor for snapping pipes together and moving and rotating them as a single assembly."/> 
<link rel="stylesheet" href="../assets/css/style.css"/> 
<!-- Copyright 1998-2023 by Northwoods Software Corporation. -->
<title>Pipes</title>
</head>

<body>
  <!-- This top nav is not part of the sample code -->
  <nav id="navTop" class="w-full z-30 top-0 text-white bg-nwoods-primary">
    <div class="w-full container max-w-screen-lg mx-auto flex flex-wrap sm:flex-nowrap items-center justify-between mt-0 py-2">
      <div class="md:pl-4">
        <a class="text-white hover:text-white no-underline hover:no-underline
        font-bold text-2xl lg:text-4xl rounded-lg hover:bg-nwoods-secondary " href="../">
          <h1 class="my-0 p-1 ">GoJS</h1>
        </a>
      </div>
      <button id="topnavButton" class="rounded-lg sm:hidden focus:outline-none focus:ring" aria-label="Navigation">
        <svg fill="currentColor" viewBox="0 0 20 20" class="w-6 h-6">
          <path id="topnavOpen" fill-rule="evenodd" d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM9 15a1 1 0 011-1h6a1 1 0 110 2h-6a1 1 0 01-1-1z" clip-rule="evenodd"></path>
          <path id="topnavClosed" class="hidden" fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path>
        </svg>
      </button>
      <div id="topnavList" class="hidden sm:block items-center w-auto mt-0 text-white p-0 z-20">
        <ul class="list-reset list-none font-semibold flex justify-end flex-wrap sm:flex-nowrap items-center px-0 pb-0">
          <li class="p-1 sm:p-0"><a class="topnav-link" href="../learn/">Learn</a></li>
          <li class="p-1 sm:p-0"><a class="topnav-link" href="../samples/">Samples</a></li>
          <li class="p-1 sm:p-0"><a class="topnav-link" href="../intro/">Intro</a></li>
          <li class="p-1 sm:p-0"><a class="topnav-link" href="../api/">API</a></li>
          <li class="p-1 sm:p-0"><a class="topnav-link" href="https://www.nwoods.com/products/register.html">Register</a></li>
          <li class="p-1 sm:p-0"><a class="topnav-link" href="../download.html">Download</a></li>
          <li class="p-1 sm:p-0"><a class="topnav-link" href="https://forum.nwoods.com/c/gojs/11">Forum</a></li>
          <li class="p-1 sm:p-0"><a class="topnav-link" href="https://www.nwoods.com/contact.html"
           target="_blank" rel="noopener" onclick="getOutboundLink('https://www.nwoods.com/contact.html', 'contact');">Contact</a></li>
          <li class="p-1 sm:p-0"><a class="topnav-link" href="https://www.nwoods.com/sales/index.html"
           target="_blank" rel="noopener" onclick="getOutboundLink('https://www.nwoods.com/sales/index.html', 'buy');">Buy</a></li>
        </ul>
      </div>
    </div>
    <hr class="border-b border-gray-600 opacity-50 my-0 py-0" />
  </nav>
  <div class="md:flex flex-col md:flex-row md:min-h-screen w-full max-w-screen-xl mx-auto">
    <div id="navSide" class="flex flex-col w-full md:w-48 text-gray-700 bg-white flex-shrink-0"></div>
    <!-- * * * * * * * * * * * * * -->
    <!-- Start of GoJS sample code -->
    
    <script src="/js/gojs/release/go.js"></script>
    <div id="allSampleContent" class="p-4 w-full">
    <script id="code">
    function init() {

      // Since 2.2 you can also author concise templates with method chaining instead of GraphObject.make
      // For details, see https://gojs.net/latest/intro/buildingObjects.html
      const $ = go.GraphObject.make;  // for more concise visual tree definitions

      myDiagram =
        $(go.Diagram, "myDiagramDiv",
          {
            initialScale: 1.5,
            "commandHandler.defaultScale": 1.5,
            allowLink: false,  // no user-drawn links
            // use a custom DraggingTool instead of the standard one, defined below
            draggingTool: new SnappingTool(),
            contextMenu:
              $("ContextMenu",
                makeButton("Paste",
                  (e, obj) => e.diagram.commandHandler.pasteSelection(e.diagram.toolManager.contextMenuTool.mouseDownPoint),
                  o => o.diagram.commandHandler.canPasteSelection(o.diagram.toolManager.contextMenuTool.mouseDownPoint)),
                makeButton("Undo",
                  (e, obj) => e.diagram.commandHandler.undo(),
                  o => o.diagram.commandHandler.canUndo()),
                makeButton("Redo",
                  (e, obj) => e.diagram.commandHandler.redo(),
                  o => o.diagram.commandHandler.canRedo())
              ),
            "undoManager.isEnabled": true
          });

      // To simplify this code we define a function for creating a context menu button:
      function makeButton(text, action, visiblePredicate) {
        return $("ContextMenuButton",
          $(go.TextBlock, text),
          { click: action },
          // don't bother with binding GraphObject.visible if there's no predicate
          visiblePredicate ? new go.Binding("visible", "", (o, e) => o.diagram ? visiblePredicate(o, e) : false).ofObject() : {});
      }

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

      myDiagram.nodeTemplateMap.add("Comment",
        $(go.Node,
          new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
          $(go.TextBlock,
            { stroke: "brown", font: "9pt sans-serif" },
            new go.Binding("text"))
        ));

      // Define the generic "pipe" Node.
      // The Shape gets it Geometry from a geometry path string in the bound data.
      // This node also gets all of its ports from an array of port data in the bound data.
      myDiagram.nodeTemplate =
        $(go.Node, "Spot",
          {
            locationObjectName: "SHAPE",
            locationSpot: go.Spot.Center,
            selectionAdorned: false,  // use a Binding on the Shape.stroke to show selection
            itemTemplate:
              // each port is an "X" shape whose alignment spot and port ID are given by the item data
              $(go.Panel,
                new go.Binding("portId", "id"),
                new go.Binding("alignment", "spot", go.Spot.parse),
                $(go.Shape, "XLine",
                  { width: 6, height: 6, background: "transparent", fill: null, stroke: "gray" },
                  new go.Binding("figure", "id", portFigure),  // portFigure converter is defined below
                  new go.Binding("angle", "angle"))
              ),
            // hide a port when it is connected
            linkConnected: (node, link, port) => {
              if (link.category === "") port.visible = false;
            },
            linkDisconnected: (node, link, port) => {
              if (link.category === "") port.visible = true;
            }
          },
          // this creates the variable number of ports for this Spot Panel, based on the data
          new go.Binding("itemArray", "ports"),
          // remember the location of this Node
          new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
          // remember the angle of this Node
          new go.Binding("angle", "angle").makeTwoWay(),
          // move a selected part into the Foreground layer, so it isn't obscured by any non-selected parts
          new go.Binding("layerName", "isSelected", s => s ? "Foreground" : "").ofObject(),
          $(go.Shape,
            {
              name: "SHAPE",
              // the following are default values;
              // actual values may come from the node data object via data binding
              geometryString: "F1 M0 0 L20 0 20 20 0 20 z",
              fill: "rgba(128, 128, 128, 0.5)"
            },
            // this determines the actual shape of the Shape
            new go.Binding("geometryString", "geo"),
            // selection causes the stroke to be blue instead of black
            new go.Binding("stroke", "isSelected", s => s ? "dodgerblue" : "black").ofObject())
        );

      // Show different kinds of port fittings by using different shapes in this Binding converter
      function portFigure(pid) {
        if (pid === null || pid === "") return "XLine";
        if (pid[0] === 'F') return "CircleLine";
        if (pid[0] === 'M') return "PlusLine";
        return "XLine";  // including when the first character is 'U'
      }

      myDiagram.nodeTemplate.contextMenu =
        $("ContextMenu",
          makeButton("Rotate +45",
            (e, obj) => rotate(obj.part.adornedPart, 45)),
          makeButton("Rotate -45",
            (e, obj) => rotate(obj.part.adornedPart, -45)),
          makeButton("Rotate 180",
            (e, obj) => rotate(obj.part.adornedPart, 180)),
          makeButton("Detach",
            (e, obj) => detachSelection()),
          makeButton("Cut",
            (e, obj) => e.diagram.commandHandler.cutSelection(),
            o => o.diagram.commandHandler.canCutSelection()),
          makeButton("Copy",
            (e, obj) => e.diagram.commandHandler.copySelection(),
            o => o.diagram.commandHandler.canCopySelection()),
          makeButton("Paste",
            (e, obj) => e.diagram.commandHandler.pasteSelection(e.diagram.toolManager.contextMenuTool.mouseDownPoint),
            o => o.diagram.commandHandler.canPasteSelection(o.diagram.toolManager.contextMenuTool.mouseDownPoint)),
          makeButton("Delete",
            (e, obj) => e.diagram.commandHandler.deleteSelection(),
            o => o.diagram.commandHandler.canDeleteSelection()),
          makeButton("Undo",
            (e, obj) => e.diagram.commandHandler.undo(),
            o => o.diagram.commandHandler.canUndo()),
          makeButton("Redo",
            (e, obj) => e.diagram.commandHandler.redo(),
            o => o.diagram.commandHandler.canRedo())
        );

      // Change the angle of the parts connected with the given node
      function rotate(node, angle) {
        const tool = myDiagram.toolManager.draggingTool;  // should be a SnappingTool
        myDiagram.startTransaction("rotate " + angle.toString());
        const sel = new go.Set(/*go.Node*/);
        sel.add(node);
        const coll = tool.computeEffectiveCollection(sel).toKeySet();
        const bounds = myDiagram.computePartsBounds(coll);
        const center = bounds.center;
        coll.each(n => {
          n.angle += angle;
          n.location = n.location.copy().subtract(center).rotate(angle).add(center);
        });
        myDiagram.commitTransaction("rotate " + angle.toString());
      }

      function detachSelection() {
        myDiagram.startTransaction("detach");
        const coll = new go.Set(/*go.Link*/);
        myDiagram.selection.each(node => {
          if (!(node instanceof go.Node)) return;
          node.linksConnected.each(link => {
            if (link.category !== "") return;  // ignore comments
            // ignore links to other selected nodes
            if (link.getOtherNode(node).isSelected) return;
            // disconnect this link
            coll.add(link);
          });
        });
        myDiagram.removeParts(coll, false);
        myDiagram.commitTransaction("detach");
      }

      // no visual representation of any link data
      myDiagram.linkTemplate = $(go.Link, { visible: false });

      // support optional links from comment nodes to pipe nodes
      myDiagram.linkTemplateMap.add("Comment",
        $(go.Link,
          { curve: go.Link.Bezier },
          $(go.Shape, { stroke: "brown", strokeWidth: 2 }),
          $(go.Shape, { toArrow: "OpenTriangle", stroke: "brown" })
        ));

      // this model needs to know about particular ports
      myDiagram.model =
        new go.GraphLinksModel(
          {
            copiesArrays: true,
            copiesArrayObjects: true,
            linkFromPortIdProperty: "fid",
            linkToPortIdProperty: "tid"
          });

      // Make sure the pipes are ordered by their key in the palette inventory
      function keyCompare(a, b) {
        const at = a.data.key;
        const bt = b.data.key;
        if (at < bt) return -1;
        if (at > bt) return 1;
        return 0;
      }

      myPalette =
        $(go.Palette, "myPaletteDiv",
          {
            initialScale: 1.2,
            contentAlignment: go.Spot.Center,
            nodeTemplate: myDiagram.nodeTemplate,  // shared with the main Diagram
            "contextMenuTool.isEnabled": false,
            layout: $(go.GridLayout,
              {
                cellSize: new go.Size(1, 1), spacing: new go.Size(5, 5),
                wrappingColumn: 12, comparer: keyCompare
              }),
            // initialize the Palette with a few "pipe" nodes
            model: new go.GraphLinksModel(
              {
                copiesArrays: true,
                copiesArrayObjects: true,
                linkFromPortIdProperty: "fid",
                linkToPortIdProperty: "tid",
                nodeDataArray: [
                  // Several different kinds of pipe objects, some already rotated for convenience.
                  // Each "glue point" is implemented by a port.
                  // The port's identifier's first letter must be the type of connector or "fitting".
                  // The port's identifier's second letter must be indicate the direction in which a
                  // connection may be made: 0-7, indicating multiples of 45-degree angles starting at zero.
                  // If you want more than one port of a particular type in the same direction,
                  // you will need to add a suffix to the port identifier.
                  // The Spot determines the approximate location of the port on the shape.
                  // The exact position is offset in order to account for the thickness of the stroke.
                  // Each should be shifted towards the center of the shape by the fraction of its
                  // distance from the center times the stroke thickness.
                  // The following offsets assume the strokeWidth == 1.
                  {
                    key: 1,
                    geo: "F1 M0 0 L20 0 20 20 0 20z",
                    ports: [
                      { id: "U6", spot: "0.5 0 0 0.5" },
                      { id: "U2", spot: "0.5 1 0 -0.5" }
                    ]
                  },
                  {
                    key: 3, angle: 90,
                    geo: "F1 M0 0 L20 0 20 20 0 20z",
                    ports: [
                      { id: "U6", spot: "0.5 0 0 0.5" },
                      { id: "U2", spot: "0.5 1 0 -0.5" }
                    ]
                  },
                  {
                    key: 5,
                    geo: "F1 M0 0 L20 0 20 60 0 60z",
                    ports: [
                      { id: "U6", spot: "0.5 0 0 0.5" },
                      { id: "U2", spot: "0.5 1 0 -0.5" }
                    ]
                  },
                  {
                    key: 7, angle: 90,
                    geo: "F1 M0 0 L20 0 20 60 0 60z",
                    ports: [
                      { id: "U6", spot: "0.5 0 0 0.5" },
                      { id: "U2", spot: "0.5 1 0 -0.5" }
                    ]
                  },
                  {
                    key: 11,
                    geo: "F1 M0 40 L0 30 Q0 0 30 0 L40 0 40 20 30 20 Q20 20 20 30 L20 40z",
                    ports: [
                      { id: "U0", spot: "1 0.25 -0.5 0.25" },
                      { id: "U2", spot: "0.25 1 0.25 -0.5" }
                    ]
                  },
                  {
                    key: 12, angle: 90,
                    geo: "F1 M0 40 L0 30 Q0 0 30 0 L40 0 40 20 30 20 Q20 20 20 30 L20 40z",
                    ports: [
                      { id: "U0", spot: "1 0.25 -0.5 0.25" },
                      { id: "U2", spot: "0.25 1 0.25 -0.5" }
                    ]
                  },
                  {
                    key: 13, angle: 180,
                    geo: "F1 M0 40 L0 30 Q0 0 30 0 L40 0 40 20 30 20 Q20 20 20 30 L20 40z",
                    ports: [
                      { id: "U0", spot: "1 0.25 -0.5 0.25" },
                      { id: "U2", spot: "0.25 1 0.25 -0.5" }
                    ]
                  },
                  {
                    key: 14, angle: 270,
                    geo: "F1 M0 40 L0 30 Q0 0 30 0 L40 0 40 20 30 20 Q20 20 20 30 L20 40z",
                    ports: [
                      { id: "U0", spot: "1 0.25 -0.5 0.25" },
                      { id: "U2", spot: "0.25 1 0.25 -0.5" }
                    ]
                  },
                  {
                    key: 21,
                    geo: "F1 M0 0 L60 0 60 20 50 20 Q40 20 40 30 L40 40 20 40 20 30 Q20 20 10 20 L0 20z",
                    ports: [
                      { id: "U0", spot: "1 0.25 -0.5 0.25" },
                      { id: "U4", spot: "0 0.25 0.5 0.25" },
                      { id: "U2", spot: "0.5 1 0 -0.5" }
                    ]
                  },
                  {
                    key: 22, angle: 90,
                    geo: "F1 M0 0 L60 0 60 20 50 20 Q40 20 40 30 L40 40 20 40 20 30 Q20 20 10 20 L0 20z",
                    ports: [
                      { id: "U0", spot: "1 0.25 -0.5 0.25" },
                      { id: "U4", spot: "0 0.25 0.5 0.25" },
                      { id: "U2", spot: "0.5 1 0 -0.5" }
                    ]
                  },
                  {
                    key: 23, angle: 180,
                    geo: "F1 M0 0 L60 0 60 20 50 20 Q40 20 40 30 L40 40 20 40 20 30 Q20 20 10 20 L0 20z",
                    ports: [
                      { id: "U0", spot: "1 0.25 -0.5 0.25" },
                      { id: "U4", spot: "0 0.25 0.5 0.25" },
                      { id: "U2", spot: "0.5 1 0 -0.5" }
                    ]
                  },
                  {
                    key: 24, angle: 270,
                    geo: "F1 M0 0 L60 0 60 20 50 20 Q40 20 40 30 L40 40 20 40 20 30 Q20 20 10 20 L0 20z",
                    ports: [
                      { id: "U0", spot: "1 0.25 -0.5 0.25" },
                      { id: "U4", spot: "0 0.25 0.5 0.25" },
                      { id: "U2", spot: "0.5 1 0 -0.5" }
                    ]
                  },
                  {
                    key: 31,
                    geo: "F1 M0 0 L20 0 20 10 Q20 14.142 22.929 17.071 L30 24.142 15.858 38.284 8.787 31.213 Q0 22.426 0 10z",
                    ports: [
                      { id: "U6", spot: "0 0 10.5 0.5" },
                      { id: "U1", spot: "1 1 -7.571 -7.571", angle: 45 }
                    ]
                  },
                  {
                    key: 32, angle: 90,
                    geo: "F1 M0 0 L20 0 20 10 Q20 14.142 22.929 17.071 L30 24.142 15.858 38.284 8.787 31.213 Q0 22.426 0 10z",
                    ports: [
                      { id: "U6", spot: "0 0 10.5 0.5" },
                      { id: "U1", spot: "1 1 -7.571 -7.571", angle: 45 }
                    ]
                  },
                  {
                    key: 33, angle: 180,
                    geo: "F1 M0 0 L20 0 20 10 Q20 14.142 22.929 17.071 L30 24.142 15.858 38.284 8.787 31.213 Q0 22.426 0 10z",
                    ports: [
                      { id: "U6", spot: "0 0 10.5 0.5" },
                      { id: "U1", spot: "1 1 -7.571 -7.571", angle: 45 }
                    ]
                  },
                  {
                    key: 34, angle: 270,
                    geo: "F1 M0 0 L20 0 20 10 Q20 14.142 22.929 17.071 L30 24.142 15.858 38.284 8.787 31.213 Q0 22.426 0 10z",
                    ports: [
                      { id: "U6", spot: "0 0 10.5 0.5" },
                      { id: "U1", spot: "1 1 -7.571 -7.571", angle: 45 }
                    ]
                  },
                  {
                    key: 41,
                    geo: "F1 M14.142 0 L28.284 14.142 14.142 28.284 0 14.142z",
                    ports: [
                      { id: "U1", spot: "1 1 -7.321 -7.321" },
                      { id: "U3", spot: "0 1 7.321 -7.321" },
                      { id: "U5", spot: "0 0 7.321 7.321" },
                      { id: "U7", spot: "1 0 -7.321 7.321" }
                    ]
                  },

                  // Example M-F connector pipes
                  /*
                  {
                    key: 107, //angle: 90,
                    geo: "F1 M0 0 L5 0, 5 10, 15 10, 15 0, 20 0, 20 40, 0 40z",
                    ports: [
                      { id: "F6", spot: "0.5 0 0 10.5" },
                      { id: "U2", spot: "0.5 1 0 -0.5" }
                    ]
                  },
                  {
                    key: 108, //angle: 90,
                    geo: "F1 M0 0, 20 0, 20 30, 15 30, 15 40, 5 40, 5 30, 0 30z",
                    ports: [
                      { id: "U6", spot: "0.5 0 0 10.5" },
                      { id: "M2", spot: "0.5 1 0 -0.5" }
                    ]
                  }
                  */

                ]  // end nodeDataArray
              })  // end model
          });  // end Palette

      load();
    } // end init


  // Define a custom DraggingTool
  class SnappingTool extends go.DraggingTool {
    // This predicate checks to see if the ports can snap together.
    // The first letter of the port id should be "U", "F", or "M" to indicate which kinds of port may connect.
    // The second letter of the port id should be a digit to indicate which direction it may connect.
    // The ports also need to not already have any link connections and need to face opposite directions.
    compatiblePorts(p1, p2) {
      // already connected?
      const part1 = p1.part;
      const id1 = p1.portId;
      if (id1 === null || id1 === "") return false;
      if (part1.findLinksConnected(id1).filter(l => l.category === "").count > 0) return false;
      const part2 = p2.part;
      const id2 = p2.portId;
      if (id2 === null || id2 === "") return false;
      if (part2.findLinksConnected(id2).filter(l => l.category === "").count > 0) return false;
      // compatible fittings?
      if ((id1[0] === 'U' && id2[0] === 'U') ||
        (id1[0] === 'F' && id2[0] === 'M') ||
        (id1[0] === 'M' && id2[0] === 'F')) {
        // find their effective sides, after rotation
        const a1 = this.effectiveAngle(id1, part1.angle);
        const a2 = this.effectiveAngle(id2, part2.angle);
        // are they in opposite directions?
        if (a1 - a2 === 180 || a1 - a2 === -180) return true;
      }
      return false;
    }

    // At what angle can a port connect, adjusting for the node's rotation
    effectiveAngle(id, angle) {
      const dir = id[1];
      let a = 0;
      if (dir === '1') a = 45;
      else if (dir === '2') a = 90;
      else if (dir === '3') a = 135;
      else if (dir === '4') a = 180;
      else if (dir === '5') a = 225;
      else if (dir === '6') a = 270;
      else if (dir === '7') a = 315;
      a += angle;
      if (a < 0) a += 360;
      else if (a >= 360) a -= 360;
      return a;
    }

    // Override this method to find the offset such that a moving port can
    // be snapped to be coincident with a compatible stationary port,
    // then move all of the parts by that offset.
    moveParts(parts, offset, check) {
      // when moving an actually copied collection of Parts, use the offset that was calculated during the drag
      if (this._snapOffset && this.isActive && this.diagram.lastInput.up && parts === this.copiedParts) {
        super.moveParts(parts, this._snapOffset, check);
        this._snapOffset = undefined;
        return;
      }

      let commonOffset = offset;

      // find out if any snapping is desired for any Node being dragged
      const sit = parts.iterator;
      while (sit.next()) {
        const node = sit.key;
        if (!(node instanceof go.Node)) continue;
        const info = sit.value;
        const newloc = info.point.copy().add(offset);

        // now calculate snap point for this Node
        const snapoffset = newloc.copy().subtract(node.location);
        let nearbyports = null;
        let closestDistance = 20 * 20;  // don't bother taking sqrt
        let closestPort = null;
        let closestPortPt = null;
        let nodePort = null;
        const mit = node.ports;
        while (mit.next()) {
          const port = mit.value;
          if (node.findLinksConnected(port.portId).filter(l => l.category === "").count > 0) continue;
          const portPt = port.getDocumentPoint(go.Spot.Center);
          portPt.add(snapoffset);  // where it would be without snapping

          if (nearbyports === null) {
            // this collects the Nodes that intersect with the NODE's bounds,
            // excluding nodes that are being dragged (i.e. in the PARTS collection)
            const nearbyparts = this.diagram.findObjectsIn(node.actualBounds,
              x => x.part,
              p => !parts.has(p),
              true);

            // gather a collection of GraphObjects that are stationary "ports" for this NODE
            nearbyports = new go.Set(/*go.GraphObject*/);
            nearbyparts.each(n => {
              if (n instanceof go.Node) {
                nearbyports.addAll(n.ports);
              }
            });
          }

          const pit = nearbyports.iterator;
          while (pit.next()) {
            const p = pit.value;
            if (!this.compatiblePorts(port, p)) continue;
            const ppt = p.getDocumentPoint(go.Spot.Center);
            const d = ppt.distanceSquaredPoint(portPt);
            if (d < closestDistance) {
              closestDistance = d;
              closestPort = p;
              closestPortPt = ppt;
              nodePort = port;
            }
          }
        }

        // found something to snap to!
        if (closestPort !== null) {
          // move the node so that the compatible ports coincide
          const noderelpt = nodePort.getDocumentPoint(go.Spot.Center).subtract(node.location);
          const snappt = closestPortPt.copy().subtract(noderelpt);
          // save the offset, to ensure everything moves together
          commonOffset = snappt.subtract(newloc).add(offset);
          // ignore any node.dragComputation function
          // ignore any node.minLocation and node.maxLocation
          break;
        }
      }

      // now do the standard movement with the single (perhaps snapped) offset
      this._snapOffset = commonOffset.copy();  // remember for mouse-up when copying
      super.moveParts(parts, commonOffset, check);
    }

    // Establish links between snapped ports,
    // and remove obsolete links because their ports are no longer coincident.
    doDropOnto(pt, obj) {
      super.doDropOnto(pt, obj);
      // Need to iterate over all of the dropped nodes to see which ports happen to be snapped to stationary ports
      const coll = this.copiedParts || this.draggedParts;
      const it = coll.iterator;
      while (it.next()) {
        const node = it.key;
        if (!(node instanceof go.Node)) continue;
        // connect all snapped ports of this NODE (yes, there might be more than one) with links
        const pit = node.ports;
        while (pit.next()) {
          const port = pit.value;
          // maybe add a link -- see if the port is at another port that is compatible
          const portPt = port.getDocumentPoint(go.Spot.Center);
          if (!portPt.isReal()) continue;
          const nearbyports =
            this.diagram.findObjectsAt(portPt,
              x => {  // some GraphObject at portPt
                let o = x;
                // walk up the chain of panels
                while (o !== null && o.portId === null) o = o.panel;
                return o;
              },
              p => {  // a "port" Panel
                // the parent Node must not be in the dragged collection, and
                // this port P must be compatible with the NODE's PORT
                if (coll.has(p.part)) return false;
                const ppt = p.getDocumentPoint(go.Spot.Center);
                if (portPt.distanceSquaredPoint(ppt) >= 0.25) return false;
                return this.compatiblePorts(port, p);
              });
          // did we find a compatible port?
          const np = nearbyports.first();
          if (np !== null) {
            // connect the NODE's PORT with the other port found at the same point
            this.diagram.toolManager.linkingTool.insertLink(node, port, np.part, np);
          }
        }
      }
    }

    // Just move selected nodes when SHIFT moving, causing nodes to be unsnapped.
    // When SHIFTing, must disconnect all links that connect with nodes not being dragged.
    // Without SHIFT, move all nodes that are snapped to selected nodes, even indirectly.
    computeEffectiveCollection(parts) {
      if (this.diagram.lastInput.shift) {
        const links = new go.Set(/*go.Link*/);
        const coll = super.computeEffectiveCollection(parts);
        coll.iteratorKeys.each(node => {
          // disconnect all links of this node that connect with stationary node
          if (!(node instanceof go.Node)) return;
          node.findLinksConnected().each(link => {
            if (link.category !== "") return;
            // see if this link connects with a node that is being dragged
            const othernode = link.getOtherNode(node);
            if (othernode !== null && !coll.has(othernode)) {
              links.add(link);  // remember for later deletion
            }
          });
        });
        // outside of nested loops we can actually delete the links
        links.each(l => l.diagram.remove(l));
        return coll;
      } else {
        const map = new go.Map(/*go.Part, Object*/);
        if (parts === null) return map;
        parts.iterator.each(n => this.gatherConnecteds(map, n));
        return map;
      }
    }

    // Find other attached nodes.
    gatherConnecteds(map, node) {
      if (!(node instanceof go.Node)) return;
      if (map.has(node)) return;
      // record the original Node location, for relative positioning and for cancellation
      map.add(node, new go.DraggingInfo(node.location));
      // now recursively collect all connected Nodes and the Links to them
      node.findLinksConnected().each(link => {
        if (link.category !== "") return;  // ignore comment links
        map.add(link, new go.DraggingInfo());
        this.gatherConnecteds(map, link.getOtherNode(node));
      });
    }
  }
  // end SnappingTool class

    function save() {
      document.getElementById("mySavedModel").value = myDiagram.model.toJson();
      myDiagram.isModified = false;
    }
    function load() {
      myDiagram.model = go.Model.fromJson(document.getElementById("mySavedModel").value);
    }

    window.addEventListener('DOMContentLoaded', init);
  </script>

<div id="sample">
  <div id="myPaletteDiv" style="border: solid 1px black; width: 100%; height: 160px"></div>
  <div id="myDiagramDiv" style="border: solid 1px black; width: 100%; height: 500px; margin-top: 3px"></div>
  <p>
    Nodes in this sample use <a>Shape.geometryString</a> to determine their shape.
    You can see more custom geometry examples and read about geometryString
    on the <a href="../intro/geometry.html">Geometry Path Strings Introduction page.</a>
  </p>
  <p>
    As a part's unconnected port (shown by an X) comes close to a stationary port
    with which it is compatible, the dragged selection snaps so that those ports coincide.
    A custom <a>DraggingTool</a>, called <b>SnappingTool</b>, is used to check compatibility.
  </p>
  <p>
    Dragging automatically drags all connected parts.
    Hold down the Shift key before dragging in order to detach a part from the parts it is connected with.
    These functionalities are also controlled by the custom SnappingTool.
  </p>
  <p>
    Use the <a>GraphObject.contextMenu</a> to rotate, detach, or delete a node.
    If it is connected with other parts, the whole collection rotates.
  </p>
  <div>
    <div>
      <button id="SaveButton" onclick="save()">Save</button>
      <button onclick="load()">Load</button>
      Diagram Model saved in JSON format:
    </div>
    <textarea id="mySavedModel" style="width:100%;height:300px">
{ "class": "go.GraphLinksModel",
  "copiesArrays": true,
  "copiesArrayObjects": true,
  "linkFromPortIdProperty": "fid",
  "linkToPortIdProperty": "tid",
  "nodeDataArray": [
{"key":0, "category":"Comment", "text":"Use Shift to disconnect a shape", "loc":"0 -13"},
{"key":1, "category":"Comment", "text":"The Context Menu has more commands", "loc":"0 20"},
{"key":2, "category":"Comment", "text":"Gray Xs are unconnected ports", "loc":"0 -47"},
{"key":3, "category":"Comment", "text":"Dragged shapes snap to unconnected ports", "loc":"0 -80"},
{"key":11, "geo":"F1 M0 40 L0 30 Q0 0 30 0 L40 0 40 20 30 20 Q20 20 20 30 L20 40z", "ports":[ {"id":"U0", "spot":"1 0.25 -0.5 0.25"},{"id":"U2", "spot":"0.25 1 0.25 -0.5"} ], "loc":"-187.33333333333331 -69.33333333333331", "angle":0},
{"key":12, "angle":90, "geo":"F1 M0 40 L0 30 Q0 0 30 0 L40 0 40 20 30 20 Q20 20 20 30 L20 40z", "ports":[ {"id":"U0", "spot":"1 0.25 -0.5 0.25"},{"id":"U2", "spot":"0.25 1 0.25 -0.5"} ], "loc":"-147.33333333333331 -69.33333333333331"},
{"key":21, "geo":"F1 M0 0 L60 0 60 20 50 20 Q40 20 40 30 L40 40 20 40 20 30 Q20 20 10 20 L0 20z", "ports":[ {"id":"U0", "spot":"1 0.25 -0.5 0.25"},{"id":"U4", "spot":"0 0.25 0.5 0.25"},{"id":"U2", "spot":"0.5 1 0 -0.5"} ], "loc":"-137.33333333333331 -9.333333333333314", "angle":0},
{"key":5, "geo":"F1 M0 0 L20 0 20 60 0 60z", "ports":[ {"id":"U6", "spot":"0.5 0 0 0.5"},{"id":"U2", "spot":"0.5 1 0 -0.5"} ], "loc":"-197.33333333333331 -19.333333333333314", "angle":0},
{"key":13, "angle":180, "geo":"F1 M0 40 L0 30 Q0 0 30 0 L40 0 40 20 30 20 Q20 20 20 30 L20 40z", "ports":[ {"id":"U0", "spot":"1 0.25 -0.5 0.25"},{"id":"U2", "spot":"0.25 1 0.25 -0.5"} ], "loc":"-147.33333333333331 30.666666666666685"},
{"key":14, "angle":270, "geo":"F1 M0 40 L0 30 Q0 0 30 0 L40 0 40 20 30 20 Q20 20 20 30 L20 40z", "ports":[ {"id":"U0", "spot":"1 0.25 -0.5 0.25"},{"id":"U2", "spot":"0.25 1 0.25 -0.5"} ], "loc":"-187.33333333333331 30.666666666666685"},
{"key":-7, "geo":"F1 M0 40 L0 30 Q0 0 30 0 L40 0 40 20 30 20 Q20 20 20 30 L20 40z", "ports":[ {"id":"U0", "spot":"1 0.25 -0.5 0.25"},{"id":"U2", "spot":"0.25 1 0.25 -0.5"} ], "loc":"-76.66666666666663 -8.666666666666657", "angle":0},
{"key":-8, "angle":90, "geo":"F1 M0 40 L0 30 Q0 0 30 0 L40 0 40 20 30 20 Q20 20 20 30 L20 40z", "ports":[ {"id":"U0", "spot":"1 0.25 -0.5 0.25"},{"id":"U2", "spot":"0.25 1 0.25 -0.5"} ], "loc":"-36.66666666666663 -8.666666666666657"},
{"key":-9, "angle":180, "geo":"F1 M0 40 L0 30 Q0 0 30 0 L40 0 40 20 30 20 Q20 20 20 30 L20 40z", "ports":[ {"id":"U0", "spot":"1 0.25 -0.5 0.25"},{"id":"U2", "spot":"0.25 1 0.25 -0.5"} ], "loc":"-36.66666666666663 31.333333333333343"},
{"key":-10, "angle":270, "geo":"F1 M0 40 L0 30 Q0 0 30 0 L40 0 40 20 30 20 Q20 20 20 30 L20 40z", "ports":[ {"id":"U0", "spot":"1 0.25 -0.5 0.25"},{"id":"U2", "spot":"0.25 1 0.25 -0.5"} ], "loc":"-76.66666666666663 31.333333333333343"}
],
  "linkDataArray": [
{"from":12, "to":11, "fid":"U2", "tid":"U0"},
{"from":5, "to":11, "fid":"U6", "tid":"U2"},
{"from":13, "to":21, "fid":"U2", "tid":"U2"},
{"from":14, "to":5, "fid":"U0", "tid":"U2"},
{"from":13, "to":14, "fid":"U0", "tid":"U2"},
{"from":-8, "to":-7, "fid":"U2", "tid":"U0"},
{"from":-9, "to":-8, "fid":"U2", "tid":"U0"},
{"from":-10, "to":-7, "fid":"U0", "tid":"U2"},
{"from":-10, "to":-9, "fid":"U2", "tid":"U0"}
]}
    </textarea>
  </div>
</div>
    </div>
    <!-- * * * * * * * * * * * * * -->
    <!--  End of GoJS sample code  -->
  </div>
</body>
<!--  This script is part of the gojs.net website, and is not needed to run the sample -->
<script src="../assets/js/goSamples.js"></script>
</html>
//


koh5_pano



Your browser does not support the HTML5 canvas element.


Drag mouse to navigate.

Navigation





17.Aug.2010, Martin Wengenmayer