Coordinate Systems

A Diagram uses two major coordinate systems when drawing Parts: document coordinates and view coordinates. Furthermore each Panel within a Part has its own coordinate system that its elements use.

All coordinate systems in GoJS have Points with increasing values of X going rightwards and increasing values of Y going downwards.

Document and View coordinates

The Part.location and GraphObject.actualBounds and GraphObject.position of Parts are in document coordinates. Thus the Point that may be saved for a Node's location in the model's node data object are normally in document coordinates:


  diagram.model.nodeDataArray = [
    { key: "Alpha", loc: "0 0" },
    { key: "Beta", loc: "100 50" }
  ];

The union of all of the Nodes and Links in a Diagram form the Diagram.documentBounds. This Rect has values that are in document coordinates. Depending on where the Nodes and Links are, the document bounds may cover a very large area. The range of the document bounds might be entirely positive x and y values, or that might be entirely negative, or more likely they may cover both negative and positive values.

But a Part with a Part.location of (0, 0) in document coordinates is not always drawn at the top-left corner of the HTML Div element that the user sees in the page. When the user scrolls the diagram the part will need to be drawn elsewhere on the canvas. And if the user zooms in to make the parts appear larger, the parts will be drawn at different points in the canvas. Yet the Part.location does not change value as the user scrolls or zooms the diagram.

The viewport is the area of the document that is visible in the canvas. That area is available as the Diagram.viewportBounds. Note that the viewport bounds is in document coordinates, not in view coordinates! The top-left corner of the viewport is (0,0) in view coordinates but is at Diagram.position in document coordinates. The bottom-right corner of the viewport is at the canvas's (width,height) in view coordinates. The bottom-right corner of the viewport in document coordinates depends on the Diagram.scale.

Points in the canvas are in view coordinates, which are independent of document coordinates. View coordinates are distances from the top-left corner of the canvas in device-independent pixels. The differences between document coordinates and view coordinates are primarily controlled by two Diagram properties: Diagram.position and Diagram.scale. Scrolling and panning change the Diagram.position. Zooming in or out changes the Diagram.scale. You can also convert between coordinate systems by calling Diagram.transformDocToView and Diagram.transformViewToDoc. However very few properties or method arguments or return values are in view coordinates -- almost everything is in document coordinates or in panel coordinates.

As an example of showing the viewport in the context of the whole document, an Overview does exactly that. Take a look at the overview that is in the Org Chart sample. The overview shows the whole document of the main diagram. The magenta box shows the main diagram's viewport within the whole document. As you scroll or pan the main diagram, the viewport moves. As you zoom out, the viewport gets larger.

To better understand the difference between document and viewport coordinates, look at this diagram:

Coordinate systems example

This example shows three Parts at three different locations in document coordinates. Pass the mouse over each of the parts to see where those locations are in view coordinates. Initially you will see that the only difference between document and view coordinates are a constant offset. That offset is due to the Diagram.padding that puts a little space between the edge of the canvas and the edge of where the diagram's objects are. It is also due to Part.locationSpot having the location be at the center of the "+" Shape, not at the top-left corner of the whole Part.


  // read-only to avoid accidentally moving any Part in document coordinates
  diagram.isReadOnly = true;

  diagram.nodeTemplate =
    $(go.Part,  // no links or grouping, so use the simpler Part class instead of Node
      {
        locationSpot: go.Spot.Center, locationObjectName: "SHAPE",
        layerName: "Background",
        mouseOver: (e, obj) => showPoint(obj.part.location),
        click: (e, obj) => showPoint(obj.part.location)
      },
      new go.Binding("location", "loc", go.Point.parse),
      $(go.Shape, "PlusLine",
        { name: "SHAPE", width: 8, height: 8 }),
      $(go.TextBlock,
        { position: new go.Point(6, 6), font: "8pt sans-serif" },
        new go.Binding("text", "loc"))
    );

  diagram.model.nodeDataArray = [
    { loc: "0 0" },
    { loc: "100 0" },
    { loc: "100 50" }
  ];

  function showPoint(loc) {
    var docloc = diagram.transformDocToView(loc);
    var elt = document.getElementById("Message1");
    elt.textContent = "Selected node location,\ndocument coordinates: " + loc.x.toFixed(2) + " " + loc.y.toFixed(2) +
                      "\nview coordinates: " + docloc.x.toFixed(2) + " " + docloc.y.toFixed(2);
  }
  // make accessible to the HTML buttons:
  myDiagram = diagram;

Then try scrolling or zooming in and looking at the locations of those parts in view coordinates. Zooming in increases the Diagram.scale by a small factor. That changes the locations in view coordinates, even though the locations in document coordinates did not change.

To "move" a node one must change its GraphObject.position or Part.location in document coordinates. To "scroll" a diagram one must change the Diagram.position. Either way will cause a node to appear at a different point in the viewport.

Document bounds

All of the Parts of a diagram have positions and sizes (i.e. their GraphObject.actualBounds) in document coordinates. The union of all of those parts' actualBounds constitutes the Diagram.documentBounds. If all of the parts are close together, the document bounds might be small. If some or all of the parts are far apart from each other, the document bounds might be large, even if there are only two parts or if there is just one really large part. The Diagram.documentBounds value is independent of the Diagram.viewportBounds. The former only depends on the bounds of the parts; the latter only depends on the size of the canvas and the diagram's position and scale.

Diagram.computeBounds, which is responsible for the bounds computation, also adds the Diagram.padding Margin so that no Parts appear directly up against the edge of the diagram when scrolled to that side. You may want to keep some parts, particularly background decorations, from being included in the document bounds computation. Just set Part.isInDocumentBounds to false for such parts.

The diagram does not compute a new value for Diagram.documentBounds immediately upon any change to any part or the addition or removal of a part. Thus the Diagram.documentBounds property value may not be up-to-date until after a transaction completes.

The relative sizes of the Diagram.documentBounds and Diagram.viewportBounds control whether or not scrollbars are needed. You can set Diagram.hasHorizontalScrollbar and/or Diagram.hasVerticalScrollbar to false to make sure no scrollbar appears even when needed. Or set Diagram.scrollMode to ScrollMode.Infinite.

If you do not want the Diagram.documentBounds to always reflect the sizes and locations of all of the nodes and links, you can set the Diagram.fixedBounds property. However if there are any nodes that are located beyond the fixedBounds, the user may be unable to scroll the diagram to see them.

If you want to be notified whenever the document bounds changes, you can register a "DocumentBoundsChanged" DiagramEvent listener.

Viewport bounds

The Diagram.viewportBounds always has x and y values that are given by the Diagram.position. It always has width and height values that are computed from the canvas size and the Diagram.scale.

Users can scroll the document contents using keyboard commands, scrollbars or panning. Programmatically, you can scroll using several means:

Furthermore, scrolling may happen automatically as nodes or links are added to or removed from or change visibility in the diagram. Also, zooming will typically result in scrolling as well.

When scrolling, the Diagram.position normally will be limited to the range specified by the Diagram.documentBounds. The short or "line" scrolling distance is controlled by Diagram.scrollHorizontalLineChange and Diagram.scrollVerticalLineChange. The long or "page" scrolling distance is controlled by the size of the viewport. If you want to control the precise values that the Diagram.position may have, you can specify a Diagram.positionComputation function. See the example below.

User can zoom in or out using keyboard commands, mouse wheel, or pinching. Programmatically, you can zoom using several means:

When zooming in or out, the Diagram.scale normally will be limited to the range given by Diagram.minScale and Diagram.maxScale. If you want to control the precise values that the Diagram.scale may have, you can specify a Diagram.scaleComputation function. See the example below.

If you want to be notified whenever the viewport bounds changes, you can register a "ViewportBoundsChanged" DiagramEvent listener.

If you want a Part to always be displayed within the viewport, no matter how the user scrolls or zooms, you can add them to a Layer that has Layer.isViewportAligned set to true. Such Parts are positioned by the GraphObject.alignment and GraphObject.alignmentFocus properties rather than by the GraphObject.position or Part.location properties. Read more at Static Parts.

Scroll margin

Diagram.scrollMargin allows the user to scroll into empty space at the edges of the viewport, when the document bounds (including its Diagram.padding margin) is greater than the viewport bounds. This can be useful when users need extra space at the edges of a Diagram, for instance to have an area to create new nodes with the ClickCreatingTool.

Diagram.padding is added as if part of the document bounds, whereas Diagram.scrollMargin makes sure you can scroll to empty space beyond the document bounds. Because of this, Diagram.scrollMargin does not create additional scrollable empty space if none is needed to scroll the margin distance beyond, such as when the document bounds are very small in the viewport.

Below is a Diagram with Diagram.scrollMargin set to 100. As you drag to the boundary, you will find the additional space created by the margin.

Scrolling modes

Diagram.scrollMode allows the user to either scroll to document bound borders with ScrollMode.Document (the default), or scroll endlessly with ScrollMode.Infinite.

Diagram.positionComputation and Diagram.scaleComputation allow you to determine what positions and scales are acceptable to be scrolled to. For instance, you could allow only integer position values, or only allow scaling to the values of 0.5, 1, or 2.

The Scroll Modes sample displays all the code for the example below, which lets you toggle these three properties.


  myDiagram.scrollMode = checked ? go.ScrollMode.Infinite : go.ScrollMode.Document;


  function positionfunc(diagram, pos) {
    var size = diagram.grid.gridCellSize;
    return new go.Point(
      Math.round(pos.x / size.width) * size.width,
      Math.round(pos.y / size.height) * size.height);
  }


  function scalefunc(diagram, scale) {
    var oldscale = diagram.scale;
    if (scale > oldscale) {
      return oldscale + 0.25;
    } else if (scale < oldscale) {
      return oldscale - 0.25;
    }
    return oldscale;
  }

Panel coordinates

A GraphObject that is not a Part but is an element of a Panel has measurements that are in panel coordinates, not in document coordinates. That means that GraphObject.position, GraphObject.actualBounds, GraphObject.maxSize, GraphObject.minSize, GraphObject.measuredBounds, GraphObject.margin, and RowColumnDefinition properties apply to all elements of a panel using the same coordinate system.

Some GraphObject properties use units that have values before they are transformed for use by the containing Panel's coordinate system. In particular, GraphObject.desiredSize (which means GraphObject.width and GraphObject.height), GraphObject.naturalBounds, Shape.geometry, and Shape.strokeWidth are in "local" coordinates, before the object is scaled and rotated by the value of GraphObject.scale and GraphObject.angle.

GraphObject.actualBounds will tell you the position and size of an element within its panel. If you want to get the document position of some object that is within a Node, call GraphObject.getDocumentPoint.

For examples of the sizes of elements in a panel, see Sizing GraphObjects.

Nested Panel coordinates

The transformations of each element in a Panel are compounded by that panel's transformations.

The TextBlock that is "Bottom" has the default GraphObject.angle of zero, so that the text is drawn upright. But that TextBlock is an element in the green "Spot" Panel whose GraphObject.angle to 30, so it and its text should appear somewhat tilted. However the blue "Vertical" Panel itself has an GraphObject.angle of 165. Because each Panel has its own coordinate system and because transformations on nested elements are compounded, the effective angle for the green Panel is 195 degrees, the sum of those individual angles (30 + 165), which is nearly upside down.

The GraphObject.scale property also affects how an object is sized in its container Panel. The brown "Position" Panel has a scale of 0.8 relative to its container. But because the "Vertical" Panel has a scale of 1.5, its effective scale is 1.2 overall, the product of those individual scales (0.8 x 1.5).