Nodes
You can customize your nodes to have exactly the appearance and behavior that you want. On this page we demonstrate some of the choices you can make when designing your nodes.
Surrounding content
It is common to surround interesting information with a border or other background.
Simple Nodes
Many of the simplest nodes just consist of a Panel of type Panel.Auto with a Shape surrounding a TextBlock. The Shape surrounding the content need not be rectangular. This example demonstrates a number of shapes.
The surrounding/background object need not be a Shape. You could use a Picture or even a more complex object such as a Panel.
Complex contents
Just like the surrounding object, the content of a Panel of type Panel.Auto need not be limited to a single TextBlock -- you can have arbitrarily complex panels of objects. In this example the content is a Panel of type Panel.Table with three rows of TextBlocks.
This example uses Gradient Brushes for the background, Shape.strokeDashArray for the border, and Table Panels for the layout.
Fixed-size Nodes
The above examples use Auto Panels, which take the main element (the first child) and size it to surround the other elements (the content). That results in different sizes depending on the size of the content. For manual control over which element is the main element, explicitly mark children with GraphObject.isPanelMain.
If you want a GraphObject to be a fixed size, you can set GraphObject.desiredSize on it. Because many classes extend GraphObject, this applies to Panels, Parts, and applicably here, Nodes. Setting GraphObject.width and GraphObject.height is equivalent to setting desiredSize. Setting a fixed size may result in the clipping of content that is too large, or it may result in extra space if the content is smaller than the available area provided by the Panel.
When manually setting sizing, prefer Spot Panels over Auto Panels. Manual sizing (setting desiredSize) works against the automatic main element sizing of Auto Panels. Using an Auto Panel for this purpose works, but can lead to unintuitive behavior.
Note how the "Alpha..." and "Beta..." TextBlocks are sized with the width constraint of their parent Panel; in this case, a Node. That results in the text being wrapped before (maybe) being clipped.
You can change the "Spot" to "Auto" to see an example of undesired Panel.Auto behavior.
When creating fixed size Nodes, you probably want to set the size of the parent, and not the siblings. In this example, the TextBlock extends outside of the Shape.
In this case, the TextBlock does not "know" about the size constraint (because size constraints are not passed between siblings), and so does not respect it.
Stacked content
Many simple nodes consist of a few objects positioned above each other or next to each other.
Icons
Perhaps the most commonly seen kind of node can be implemented using a Panel of type Panel.Vertical. You can add as many GraphObjects to a Vertical Panel as you'd like.
Small icons
Another commonly seen kind of node can be implemented using a Panel of type Panel.Horizontal. You can add as many GraphObjects to a Horizontal Panel as you'd like.
Nested Panels
Panels can be nested. For example, here is an elaborate node defined as a "Vertical" Panel consisting of an "Auto" Panel surrounding a "Vertical" Panel that itself includes "Horizontal" Panels, more nested "Vertical" Panels, and another nested "Auto" Panel.
Decorated content
Sometimes you want to have a simple node that may display additional visuals to indicate what state it is in.
One way to implement this is to use a Panel of type Panel.Spot, where the main element is itself a Panel containing the elements that you always want to display, and there are additional objects located at spots around the main element. The basic outline would be:
So the basic body of the node is in any kind of Panel, which is surrounded by the Shape using an "Auto" Panel, which gets decorations using the "Spot" Panel that is also the Node.
"Spot" Panels can also be used for placing Link ports relative to the body of a node.
Note the use of Part.locationObjectName and GraphObject.name. By using the main content as the location and not the whole Node, the decoration is not considered when positioning the Node. If one were to rename the Panel to something other than "BODY", Rack 2 would sit slightly lower than its neighbors. More detail is in the "Position and Location" section.
As another example of a node decoration, this implements a "ribbon" at the top right corner of the node. The ribbon is implemented by a Panel that contains both a Shape and a TextBlock, and the panel is positioned by its GraphObject.alignment and GraphObject.alignmentFocus in the Spot Panel that also is the Node. The appearance of the ribbon is achieved by using a custom Geometry and binding GraphObject.opacity.
Position and Location
GraphObject.position controls where the top-left corner of a GraphObject's GraphObject.actualBounds is placed. Part.location controls where the Part.locationSpot is placed.
By default, setting position is identical to setting location. In this example, the node on the left is positioned via "position" and the node on the right is positioned via "location".
Other than the x-offset, both nodes are positioned the same relative to the grid lines. This is because by default, Part.location and GraphObject.position control the same point (the top-left of the element's bounds).
You can set the Part.locationSpot of a Part to any Spot to change where Part.location refers to. In this example, location now controls the center of the element, not the top-left.
The first node is only setting position, which ignores the locationSpot and just uses the top-left corner. The second node is only setting location, which means the locationSpot (the center) will be moved to the given Point instead of the top-left corner being moved.
To summarize, using locationSpot alongside location allows you to control not only what coordinates a node is placed at, but exactly what part of the node is placed at those coordinates.
Commonly, one may wish to position an element while ignoring all decorations, labels, or extraneous elements that exist on top of the original element. Because positioning happens at the Node level, it may seem difficult to try to set the locationSpot of a Node on a particular piece of content. To achieve this, you can place a Part's (and therefore Node's) Part.locationSpot on a GraphObject inside of it by setting Part.locationObjectName to that object's GraphObject.name, like in this example.
The left node only has "position" set, so is positioned off of the gridlines due to the label. The right node only has "location" set, so is positioned according to both locationObjectName and locationSpot, which results in the center of the circle being placed at (100, 0).
If the position or location of a Node is not Point.isReal, it will not be seen, because GoJS will not know where to draw the node.
In fact, the default value for a node's position or location is NaN, NaN and it is the responsibility
of either the Diagram.layout or data bindings to assign real point values for each node.