Introduction to GoXam

Northwoods Software

www.nwoods.com

©2008-2015 Northwoods Software Corporation

GoXam provides controls for implementing diagrams in your WPF and Silverlight applications.  GoWPF is the name for the implementation of GoXam for WPF 3.5 or later; GoSilverlight is the name for the implementation of GoXam for Silverlight 4.0 or later.

For web apps, the successor to GoXam for Silverlight is GoJS.  Use GoJS for creating diagrams in HTML and JavaScript, running entirely in the browser.  See more at http://gojs.net.

You can find more documentation for GoXam in the installation kits.  The site www.goxam.com has some online GoXam samples for both WPF and Silverlight.  The sources for these samples are in the kits too.  And you can ask questions and search for answers in the forum www.nwoods.com/forum or by e-mail to GoXam at our domain (nwoods.com).

This document assumes a reasonably good working knowledge of WPF or Silverlight.  For overviews and introductions to these technologies, we suggest you first read several of the very good books about developing WPF or Silverlight applications and the web sites starting at:

·         http://msdn.microsoft.com/en-us/library/aa970268.aspx

·         http://windowsclient.net/wpf/default.aspx

·         http://www.silverlight.net/

·         http://www.wpftutorial.net/

This document also assumes a good working knowledge of .NET programming, including generics and Linq for Objects and Linq for XML.


Table of Contents


Introduction to GoXam.. 1

Summary. 3

Diagram Models and Data Binding. 4

Choosing a Model 4

TreeModel 4

GraphModel 5

GraphLinksModel 5

Model Data. 5

Getting the model data. 6

Discovering Relationships in the Data. 7

Link information in the node data. 7

Link information as separate link data. 8

Group information in the node data. 9

Group information with separate link data. 10

Modifying the Model 12

Data Templates for Nodes. 13

Data Binding. 14

Using NodePanel 17

Resizing. 19

Collapsing and Expanding Trees. 20

In-place Text Editing and Validation. 21

Spots. 23

Data Templates for Links. 24

Data Binding to Link Nodes. 29

Link Routes. 30

Link Labels. 32

Link Connection Points on Nodes. 34

Ports on Nodes. 38

Data Templates for Groups. 39

Collapsing and Expanding SubGraphs. 41

Groups with Ports. 43

Groups as Independent Containers. 44

Layout. 45

TreeLayout. 47

ForceDirectedLayout. 49

LayeredDigraphLayout. 50

CircularLayout. 51

Selection. 52

Selection Adornments. 52

Selection Appearance Changes. 54

Content Alignment and Stretch. 56

Initial Positioning and Scaling. 60

Tools. 61

Events. 62

Mouse Clicks. 63

Other Events. 64

Commands. 65

User Permissions. 65

Link Validation. 67

Diagram Background and Grids. 69

Grids. 70

Custom Grids. 71

Printing. 75

Overview.. 79

Palette. 80

Template Dictionaries. 82

Generating Images. 85

Saving and Loading data using XML. 85

Adding Data Properties. 87

Updating a database. 88

Deploying your application. 90

Appendix. 92

Diagram Templates. 92

Layers. 92

Decorative Elements and Unbound Nodes. 94

Unbound Links. 98

Performance considerations. 99



 

Summary

The Diagram class is a WPF or Silverlight Control that fully supports the standard customization features expected in WPF or Silverlight.  These features include:

·         styling

·         templates

·         data binding

·         use of all WPF/Silverlight elements

·         use of WPF/Silverlight layout

·         animation

·         commands

·         printing

Diagrams consist of nodes that may be connected by links and that may be grouped together into groups.  All of these parts are gathered together in layers and are arranged by layouts.  Most parts are bound to your application data.

Each diagram has a Model which interprets your data to determine node-to-node link relationships and group-member relationships.

Each diagram has a PartManager that is responsible for creating a Node for each data item in the model's NodeSource data collection, and for creating or deleting Links as needed.

Each Node or Link is defined by a DataTemplate that defines its appearance and behavior.

The nodes may be positioned manually (interactively or programmatically) or may be arranged by the diagram's Layout and by each Group’s Layout.

Tools handle mouse events.  Each diagram has a number of tools that perform interactive tasks such as selecting parts or dragging them or drawing a new link between two nodes.  The ToolManager determines which tool should be running, depending on the mouse events and current circumstances.

Each diagram also has a CommandHandler that implements various commands (such as Delete) and that handles keyboard events.

The DiagramPanel provides the ability to scroll the parts of the diagram or to zoom in or out.  The DiagramPanel also contains all of the Layers, which in turn contain all of the Parts (Nodes and Links).

The Overview control allows the user to see the whole model and to control what part of it that the diagram displays.  The Palette control holds nodes that the user may drag-and-drop to a diagram.

You can select one or more parts in the diagram.  The template implementation may change the appearance of the node or link when it is selected.  The diagram may also add Adornments to indicate selection and to support tools such as resizing a node or reconnecting a link.

Diagram Models and Data Binding

One of the principal features of XAML-defined presentation is the use of data binding.  Practically all controls in the typical application will depend on data binding to get the information to be displayed, to be updated when the data changes, and to modify the data based on user input.

A Diagram control, however, must support more complex features than the typical control.  The most complex standard controls inherit from ItemsControl, which will have a CollectionView to filter, sort, and group items into an ordered list.  But unlike the data used by an ItemsControl, a diagram features relationships between data objects in ways more complex than a simple total ordering of items.

There are binary relationships forming a graph of nodes and links. In similar terminology they may be called nodes and arcs, or entities and relationships, or vertices and edges.

There are grouping relationships, where a group contains members.  They may be used for part/sub-part containment or for the nesting of subgraphs.

We make use of a model to discover, maintain, navigate, and modify these relationships based on the data that the diagram is bound to.  Each Diagram has a model, but models can be shared between diagrams.

To be useful, every model needs to provide ways to do each of the following kinds of activities:

·         getting the collection of data

·         discovering the relationships in the data in order to build up the model

·         updating the model when there are changes to the data

·         examining the model and navigating the relationships

·         modifying the collection of data, and changing their relationships

·         notifying users of the model about changes to the model

·         supporting transactions and undo and redo

·         supporting data transfer and persistence

Some models are designed to be easier to use or to be more efficient when they have restrictions on the kinds of relationships they support.  There are different ways of organizing the data.  And you might or might not have any implementation flexibility in the classes used to implement the data, depending on your application requirements and whether you may modify your application’s data schema.

To achieve these goals we provide several model classes in the Northwoods.GoXam.Model namespace.

Choosing a Model

There are currently three primary model classes that implement the basic notion of being a diagram model.

TreeModel

The TreeModel is the simplest model.  It is suitable for applications where the data forms a graph that is a tree structure: each node has at most one parent but may have any number of children, there are no undirected cycles or loops in the graph, and there is at most one link connecting any two nodes.

If your graph is not necessarily tree-structured, or if you want to support grouping as well as links, you will need to use either GraphModel or GraphLinksModel. 

GraphModel

Use GraphModel when each node has a collection of references to the nodes that are connected to that node and are either coming from or going to the node.  GraphModel permits cycles in the graph, including reflexive links where both ends of the link are the same node.  However, there can be at most one link connecting each pair of nodes in a single direction, and there can be no additional information associated with each link.

Grouping in GraphModel supports the membership of any node in at most one other node, and cycles in the grouping relationship are not permitted.  Hence each subgraph is also a node, and node-subgraph membership forms its own tree-like structure.

GraphLinksModel

If you need to support an arbitrary amount of data for each link, or if you need multiple distinct links connecting the same pair of nodes in the same direction, or if you need to connect links to links, you will need to use a separate data structure to represent each link.  The GraphLinksModel takes a second data source that is the collection of link data.

GraphLinksModel also supports additional information at both ends of each link, so that one can implement logically different connection points for each node.

GraphLinksModel supports group-membership (i.e. subgraphs) in exactly the same manner that GraphModel does.

Model Data

The model classes are generic classes.  They are type parameterized by the type of the node data, NodeType, and by the type of the unique key, NodeKey, used as references to nodes.  In the case of GraphLinksModel, there is also a type parameter for the link data type, LinkType, and a type parameter for optional data describing each end of the link, PortKey.  (However, the implementation of diagram Nodes expects that PortKey must be a String.)

The model classes can probably be used with your existing application data classes.  If you do not already have such data classes you can implement them by inheriting from the optional data classes that are in the Northwoods.GoXam.Model namespace, to add application-specific properties.

Generic Models

Suggested data classes

TreeModel <NodeType, NodeKey>

TreeModelNodeData<NodeKey>

GraphModel <NodeType, NodeKey>

GraphModelNodeData<NodeKey>

GraphLinksModel <NodeType, NodeKey, PortKey, LinkType>

GraphLinksModelNodeData<NodeKey> and

GraphLinksModelLinkData<NodeKey, PortKey>

(or UniversalLinkData)

 

 

The typical usage of models and data is:

  // create a typed model

  var model = new GraphLinksModel<MyData, String, String, MyLinkData>();

  // maybe set other model properties too...

 

  // specify the nodes, which includes the subgraph information

  model.NodesSource = new ObservableCollection<MyData>() {

      . . .  // supply the node data

    };

 

  // specify the links between the nodes

  model.LinksSource = new ObservableCollection<MyLinkData>() {

      . . .  // supply the link data

    };

 

  // have the Diagram use the new model

  myDiagram.Model = model;

  // after this point all model changes should be in a transaction

 

The node data and link data classes would be defined like:

  public class MyData : GraphLinksModelNodeData<String> {

    // define node data properties; setters should call RaisePropertyChanged

  }

 

  public class MyLinkData : GraphLinksModelLinkData<String, String> {

    // define link data properties; setters should call RaisePropertyChanged

  }

 

GraphModel and TreeModel do not have a LinksSource property and you would not need to define or use a link data class.

 

Getting the model data

Each model needs access to the collection of data that it is modeling.  This means setting the IDiagramModel.NodesSource property.  The value must be a collection of node data.

For example, consider the following model initialization:

  var model = new GraphModel<String, String>();

  model.NodesSource = new List<String>() { "Alpha", "Beta", "Gamma", "Delta" };

This produces a graph without any links.  The node data are just strings.  Without any customized templates it might appear as:

In future sections we will discuss customizing the appearance and behavior of nodes using DataTemplates.

If you want to be able to add or remove data from the NodesSource collection and have the model (and diagram) automatically updated, you should do the following:

  model.NodesSource =

    new ObservableCollection<String>() { "Alpha", "Beta", "Gamma", "Delta" };

 

ObservableCollection is in the System.Collections.ObjectModel namespace.  It provides notifications when the collection is changed, so Adding a string to that ObservableCollection will cause an extra node to be created in the model and shown in the diagram.

Discovering Relationships in the Data

In order to build up the model’s knowledge of links between nodes, the model must examine each node data for link information, or it needs to be given link data describing the connections between the node data.  Usually that information is stored as property values on the data, so you just need to provide those property names to the model.  For generality, not only are simple property names supported, but also XAML-style property paths, typically property names separated by periods.  Thus most model properties that specify property accessor paths have names that end in “Path”.  An example is NodeKeyPath, which specifies how to get the key value for a node data object.

However, when the information is not accessible via a property path, perhaps because a method call is required or because the information needs to be computed, you can override protected virtual methods on the model to get the needed information.  These discovery-implementation methods have names that start with “Find”.  Because using a property path may use reflection, overriding these methods also produces an implementation that is faster and that is more likely to work in limited-permission environments, such as the typical Silverlight or XBAP application.

Link information in the node data

If you have the link relationship information stored on each node data, you might implement the node data class to have a property holding the name of the node and another property or two holding a collection of names that the node is connected to.  This is how GraphModel expects the information to be organized.

If you don’t want to implement your own node data class, you can use one that we provide, GraphModelNodeData.  This is a generic class, parameterized by the type of the key value.  In the following examples, the keys are strings.  We just need to specify the property names for discovering the “name” of each node and for discovering the collection of connected node names.

  // model is a GraphModel using GraphModelNodeData<String> as the node data

  // and strings as the node key type

  var model = new GraphModel<GraphModelNodeData<String>, String>();

 

  model.NodeKeyPath = "Key";     // use the GraphModelNodeData.Key property

  model.ToNodesPath = "ToKeys";  // the node property to get a list of node keys

 

  model.NodesSource = new ObservableCollection<GraphModelNodeData<String>>() {

      new GraphModelNodeData<String>() {

          Key="Alpha",

          ToKeys=new ObservableCollection<String>() { "Beta", "Gamma" }

      },

      new GraphModelNodeData<String>() {

          Key="Beta",

          ToKeys=new ObservableCollection<String>() { "Beta" }

      },

      new GraphModelNodeData<String>() {

          Key="Gamma",

          ToKeys=new ObservableCollection<String>() { "Delta" }

      },

      new GraphModelNodeData<String>() {

          Key="Delta",

          ToKeys=new ObservableCollection<String>() { "Alpha" }

      }

  };

 

  myDiagram.Model = model;

 

The result might appear as:

Link information as separate link data

If you have the link data separate from the node data, as is the case for GraphLinksModel, you might do:

  // model is a GraphLinksModel using strings as the node data

  // and UniversalLinkData as the link data

  var model = new GraphLinksModel<String, String, String, UniversalLinkData>();

 

  // the key value for each node data is just the whole data itself, a String

  model.NodeKeyPath = "";

  model.NodeKeyIsNodeData = true;  // NodeType and NodeKey values are the same!

  model.LinkFromPath = "From";  // UniversalLinkData.From è source’s node key

  model.LinkToPath = "To";  // UniversalLinkData.To è destination’s node key

 

  model.NodesSource =

    new ObservableCollection<String>() { "Alpha", "Beta", "Gamma", "Delta" };

 

  model.LinksSource = new ObservableCollection<UniversalLinkData>() {

      new UniversalLinkData("Alpha", "Beta"),

      new UniversalLinkData("Alpha", "Gamma"),

      new UniversalLinkData("Beta", "Beta"),

      new UniversalLinkData("Gamma", "Delta"),

      new UniversalLinkData("Delta", "Alpha")

  };

  myDiagram.Model = model;

 

Note that the node data in this example are just strings.  Because the node value, a string, is also its own key value, there is no property to get the key given a node – hence the NodeKeyPath is the empty string.  Of course in a “real” application you would have your own node data class, either inheriting from GraphLinksModelNodeData or defined from scratch.  This would allow you to add all of the properties you need for each node, bindable from the node data templates.

In this example we are using the UniversalLinkData class that we provide as a convenient pre-defined class that you can use for representing link data.  The From property of UniversalLinkData is supplied as the first argument of the constructor; it refers to the source node.  The To property is supplied as the second argument; it refers to the destination node.

UniversalLinkData inherits from GraphLinksModelLinkData.  As with the node data, the typical “real” application would define its own link data class, either inheriting from GraphLinksModelLinkData or defined from scratch, holding whatever information was needed for each link.  Defining your own data classes is also more type-safe than using the “Universal…” classes that have properties of type Object.

The resulting diagram is exactly the same as for the previous example:

Group information in the node data

Grouping/membership information is accessible in a similar manner, as properties on the node data.  For clarity, we use the subgraph terminology to refer to groups where each node can have at most one container group.  At the current time all GoXam groups are also subgraphs.

 

You need to set two more model properties used for model discovery:

 

  // model is a GraphModel or a GraphLinksModel

  model.NodeIsGroupPath = "IsSubGraph"; // node property is true if it’s a group

  model.GroupNodePath = "SubGraphKey";  // node property gets container’s name

 

Then change the NodesSource data as follows, initializing the two additional properties:

 

  // model is a GraphModel using GraphModelNodeData<String> as the node data,

  // and the node keys are strings

  var model = new GraphModel<GraphModelNodeData<String>, String>();

 

  model.NodeKeyPath = "Key";     // use the GraphModelNodeData.Key property

  model.ToNodesPath = "ToKeys";  // this node property gets a list of node keys

  model.NodeIsGroupPath = "IsSubGraph"; // node property is true if it’s a group

  model.GroupNodePath = "SubGraphKey";  // node property gets container’s name

 

  model.NodesSource= new ObservableCollection<GraphModelNodeData<String>>(){

    new GraphModelNodeData<String>() {

      Key="Alpha",

      ToKeys=new ObservableCollection<String>() { "Beta", "Gamma" }

    },

    new GraphModelNodeData<String>() {

      Key="Beta",

      ToKeys=new ObservableCollection<String>() { "Beta" }

    },

    new GraphModelNodeData<String>() {

      Key="Gamma",

      ToKeys=new ObservableCollection<String>() { "Delta" },

      SubGraphKey="Epsilon"

    },

    new GraphModelNodeData<String>() {

      Key="Delta",

      ToKeys=new ObservableCollection<String>() { "Alpha" },

      SubGraphKey="Epsilon"

    },

    new GraphModelNodeData<String>() {

      Key="Epsilon",

      IsSubGraph=true

    },

  };

  myDiagram.Model = model;

 

This results in a diagram that might look like:

Group information with separate link data

The same result is easily achieved in a GraphLinksModel by using GraphLinksModelNodeData instead of GraphModelNodeData as the node data.  In this example we will subclass GraphLinksModelNodeData in order to add a property for each node.

  // model is a GraphLinksModel using MyData as the node data

  // indexed with strings, and UniversalLinkData as the link data

  var model = new GraphLinksModel<MyData, String, String, UniversalLinkData>();

 

  model.NodeKeyPath = "Key";  // use the GraphLinksModelNodeData.Key property

  model.LinkFromPath = "From";  // UniversalLinkData.From è source’s node key

  model.LinkToPath = "To";  // UniversalLinkData.To è destination’s node key

  model.NodeIsGroupPath = "IsSubGraph"; // node property is true if it’s a group

  model.GroupNodePath = "SubGraphKey";  // node property gets container’s name

 

  // specify the nodes, which includes subgraph information

  // and other properties specific to MyData, such as Color

  model.NodesSource = new ObservableCollection<MyData>() {

    new MyData() { Key="Alpha", Color="Purple" },

    new MyData() { Key="Beta", Color="Orange" },

    new MyData() { Key="Gamma", Color="Red", SubGraphKey="Epsilon" },

    new MyData() { Key="Delta", Color="Green", SubGraphKey="Epsilon" },

    new MyData() { Key="Epsilon", Color="Blue", IsSubGraph=true },

  };

 

  // specify the links between the nodes

  model.LinksSource = new ObservableCollection<UniversalLinkData>() {

    new UniversalLinkData("Alpha", "Beta"),

    new UniversalLinkData("Alpha", "Gamma"),

    new UniversalLinkData("Beta", "Beta"),

    new UniversalLinkData("Gamma", "Delta"),

    new UniversalLinkData("Delta", "Alpha")

  };

  myDiagram.Model = model;

 

// Define custom node data; the node key is of type String

// Add a property named Color that might change

[Serializable]  // serializable only for WPF

public class MyData : GraphLinksModelNodeData<String> {

  public String Color {

    get { return _Color; }

    set {

      if (_Color != value) {

        String old = _Color;

        _Color = value;

        RaisePropertyChanged("Color", old, value);

      }

    }

  }

  private String _Color = "Black";

}

 

This model results in a diagram that looks the same as for the GraphModel above.  (We’ll show how to use the new Color property soon.)

If you did not need to support updating the diagram when the value of Color changes, perhaps because you expect the data to be read-only, you could use a simpler implementation of the property:

// Define custom node data that does not notify the diagram about Color changes

[Serializable]  // serializable only for WPF

public class MyData : GraphLinksModelNodeData<String> {

  public MyData() {

    this.Color = "Black";

  }

  public String Color { get; set; }

}

 

But if you do expect to modify the MyData.Color property and expect the corresponding node to change its appearance, you must use the more verbose definition shown earlier that calls RaisePropertyChanged in the setter.

Modifying the Model

Once you have created a model, told it how to discover relationships between the nodes (set the various …Path properties of the model), initialized the model’s data (created a collection of data objects and set the model’s NodesSource), and assigned the model to your Diagram, you might want to programmatically make changes to the diagram.  You do this by making changes to the model and to the data, not by trying to change the Parts that are in the Diagram.

Here’s the code for creating a node and a link to that node, given a starting node:

// Given a Node, perhaps a selected one, or one that contains a button that

// was clicked, create another Node nearby and connect to it with a new link.

public Node ConnectToNewNode(Node start) {

  MyData fromdata = start.Data as MyData;

  if (fromdata == null) return null;

  // all changes should always occur within a model transaction

  myDiagram.StartTransaction("new connected node");

  // create the new node data

  MyData todata = new MyData();

  // initialize the new node data here...

  todata.Text = "new node";

  todata.Location = new Point(start.Location.X + 250, start.Location.Y);

  // add the new node data to the model's NodesSource collection

  myDiagram.Model.AddNode(todata);

  // add a link to the model connecting the two data objects

  myDiagram.Model.AddLink(fromdata, null, todata, null);

  // finish the transaction

  myDiagram.CommitTransaction("new connected node");

  return myDiagram.PartManager.FindNodeForData(todata, myDiagram.Model);

}

 

Whenever you modify a diagram programmatically, you should wrap the code in a transaction.  StartTransaction and CommitTransaction are methods that you should call either on the Model or on the Diagram.  (The Diagram’s methods just call the same named methods on the DiagramModel.)  Although the primary benefit from using transactions is to group together side-effects for undo/redo, you should use model transactions even if you are not supporting undo/redo.

Note that you do not create a Node directly.  Instead you create a data object corresponding to a node, initialize it, and then add it to the model’s NodesSource collection.  It is most convenient to call the model’s AddNode method, but you could instead insert the data directly into the NodesSource collection, assuming the collection implements INotifyCollectionChanged.

Programmatically creating links uses the same idea: modify the model by adding or modifying data.  For a GraphModel or a TreeModel, you create a link by setting a node data’s property or by adding a reference to a node data’s collection of references.   For a GraphLinksModel you need to create a link data object and insert it into the model’s LinksSource collection.  The AddLink method shown above may work for any kind of model, although for a GraphLinksModel there are some restrictions.

How does one get a reference to a node?  If you can find the node data object, that’s all you need to be able to call PartManager.FindNodeForData, as shown above.  But if the code is being called from an event handler on an element in a Node, you will need to walk up the visual tree until you find the Node.  The easiest way to do that is with:

  Node node = Part.FindAncestor<Node>(sender as UIElement);

  if (node != null) {

    Node newnode = ConnectToNewNode(node);

    if (newdata != null) {

      // Select the new node

      myDiagram.SelectedParts.Clear();

      newnode.IsSelected = true;

    }

  }

Data Templates for Nodes

The appearance of any node is determined not only by the data to which it is bound but also the DataTemplate used to define the elements of its visual tree.

The simplest useful data templates for nodes are probably:

  <DataTemplate>

    <TextBlock Text="{Binding Path=Data}" />

  </DataTemplate>

 

or:

 

  <DataTemplate>

    <TextBlock Text="{Binding Path=Data.Key}" />

  </DataTemplate>

 

The first one just converts the node’s data to a string and displays it; the second one converts the value of the node’s data’s Key property to a string and displays it.  The first one is basically the one used in the screenshots shown before that used strings as the node data; the second one was used for those examples that used GraphModelNodeData or GraphLinksModelNodeData as the node data.

Because templates may be shared, and because it helps to simplify the XAML, you would normally use a node template by defining it as a resource and referring to it as the value of the NodeTemplate property of a Diagram.  For example:

<UserControl.Resources>

  <DataTemplate x:Key="NodeTemplate1">

    <TextBlock Text="{Binding Path=Data.Key}" go:Part.SelectionAdorned="True" />

  </DataTemplate>

  <!-- define other templates here -->

</UserControl.Resources>

. . .

<go:Diagram x:Name="myDiagram" NodeTemplate="{StaticResource NodeTemplate1}" />

 

In this case the node will appear just as with the simpler templates, but when the user clicks on the node, a rectangular selection handle will be appear around the node, visually indicating that it is selected.  In the following screenshot, “Alpha” and “Beta” are selected along with the link between them and the link connecting “Beta” with itself.

The node selection effect was achieved just by setting this attached property:

    go:Part.SelectionAdorned="True"

Because Node inherits from Part, you can refer to precisely the same attached property with:

    go:Node.SelectionAdorned="True"

Setting these kinds of attached properties has to be done on the root visual element of the data template, not on any nested element within that template, nor on the Node itself.

In this section we will present more node data templates.  These designs concentrate on simple nodes.  A later section, “Ports on Nodes”, discusses the ability to have links connect to different elements on a node.  Other sections discuss data templates for groups and for links.

You can also define multiple templates for nodes and dynamically choose which one to use.  This allows you to have many differently appearing nodes in the same diagram.  The technique is discussed in a later section about DataTemplateDictionaries.

You can find the XAML for the default templates and styles as: docs\GenericWPF.xaml or docs\GenericSilverlight.xaml.

Data Binding

Let’s make use of the MyData.Color property.  In this example each node will have a colored cube as the principal shape, with some text below it.  First there needs to be a resource that is a converter from strings (the type of MyData.Color) to Brush:

<go:StringBrushConverter x:Key="theStringBrushConverter" />

 

Then we can bind each text’s Foreground value to the Brush returned by converting the MyData.Color property value.

<DataTemplate x:Key="NodeTemplate2">

  <TextBlock Text="{Binding Path=Data.Key}"

             Foreground="{Binding Path=Data.Color,

                        Converter={StaticResource theStringBrushConverter}}" />

</DataTemplate>

 

We now get the following screenshot:

Here’s a node template consisting of several text blocks surrounded by a border:

  <DataTemplate x:Key="NodeTemplate3">

    <Border BorderThickness="1" BorderBrush="Black"

            Padding="2,0,2,0" CornerRadius="3"

            go:Part.SelectionAdorned="True"

            go:Node.Location="{Binding Path=Data.Location, Mode=TwoWay}">

      <StackPanel>

        <TextBlock Text="{Binding Path=Data.Name}" FontWeight="Bold" />

        <TextBlock Text="{Binding Path=Data.Title}" />

        <TextBlock Text="{Binding Path=Data.ID}" />

        <TextBlock Text="{Binding Path=Data.Boss}" />

      </StackPanel>

    </Border>

  </DataTemplate>

 

This results in nodes that look like these three (with an off-white background for the Diagram):

 

Note how the template is bound to the properties of the node data.  Most of the bindings are one-way, from the data to the elements.  But the binding between the Node.Location attached property and the data’s Location property is two-way: if the value of either property changes, the other one is updated.  This means that not only will modifying the data’s Location property move the node in the diagram, but interactively dragging the node will modify the data.

For a different kind of node, we will use a DrawingImage (WPF only).

<DataTemplate x:Key="NodeTemplateDI">

  <StackPanel go:Part.SelectionAdorned="True"

              go:Node.Location="{Binding Path=Data.Location, Mode=TwoWay}">

    <!-- WPF: This image uses a Drawing object for its source -->

    <Image HorizontalAlignment="Center">

      <Image.Source>

        <DrawingImage PresentationOptions:Freeze="True">

          <DrawingImage.Drawing>

            <GeometryDrawing>

              <GeometryDrawing.Geometry>

                <GeometryGroup>

                  <EllipseGeometry Center="50,50" RadiusX="45" RadiusY="20" />

                  <EllipseGeometry Center="50,50" RadiusX="20" RadiusY="45" />

                </GeometryGroup>

              </GeometryDrawing.Geometry>

              <GeometryDrawing.Brush>

                <LinearGradientBrush>

                  <GradientStop Offset="0.0" Color="Blue" />

                  <GradientStop Offset="1.0" Color="#CCCCFF" />

                </LinearGradientBrush>

              </GeometryDrawing.Brush>

              <GeometryDrawing.Pen>

                <Pen Thickness="10" Brush="Black" />

              </GeometryDrawing.Pen>

            </GeometryDrawing>

          </DrawingImage.Drawing>

        </DrawingImage>

      </Image.Source>

    </Image>

    <TextBlock Text="{Binding Path=Data.Text}" HorizontalAlignment="Center" />

  </StackPanel>

</DataTemplate>

 

The example DrawingImage was taken from the WPF documentation.  This node template just adds a text label centered below the image.  The result is:

Using a DrawingImage in WPF is more resource efficient when the drawing can be shared by multiple nodes.

Silverlight does not permit such sharing, but you can still get the same visual result by using a Path and the same GeometryGroup and LinearGradientBrush.

<DataTemplate x:Key="NodeTemplateEG">

  <StackPanel go:Part.SelectionAdorned="True"

              go:Node.Location="{Binding Path=Data.Location, Mode=TwoWay}">

    <!-- Silverlight: use a Path -->

    <Path Stroke="Black" StrokeThickness="10">

      <Path.Fill>

        <LinearGradientBrush>

          <GradientStop Offset="0.0" Color="Blue" />

          <GradientStop Offset="1.0" Color="#CCCCFF" />

        </LinearGradientBrush>

      </Path.Fill>

      <Path.Data>

        <GeometryGroup>

          <EllipseGeometry Center="50,50" RadiusX="45" RadiusY="20" />

          <EllipseGeometry Center="50,50" RadiusX="20" RadiusY="45" />

        </GeometryGroup>

      </Path.Data>

    </Path>

    <TextBlock Text="{Binding Path=Data.Text}" HorizontalAlignment="Center" />

  </StackPanel>

</DataTemplate>

Using NodePanel

Of course you can make your templates as complex as you need and as pretty as you want.  Because it is common to have each node display some kind of shape along with some text inside it, we have provided the NodePanel class which can hold a NodeShape.   (If you want the text to be outside of the shape, use a StackPanel or Grid to arrange the elements.)

Furthermore, we have implemented geometries for many common shapes.  These are listed by the NodeFigure enumeration.  By setting the go:NodePanel.Figure attached property on the NodeShape, the shape will automatically use a Geometry corresponding to that particular figure.

The NodeFigures sample shows all of the predefined shapes, which are enumerated by the NodeFigure type.

Consider the following two resource definitions:

<!-- define a conversion from String to Color -->

<go:StringColorConverter x:Key="theStringColorConverter" />

 

<DataTemplate x:Key="NodeTemplate4">

  <!-- a NodePanel shows a background shape and

       places the other panel children inside the shape -->

  <go:NodePanel go:Node.SelectionAdorned="True"

                go:Node.ToSpot="LeftSide" go:Node.FromSpot="RightSide" >

    <!-- this shape gets the geometry defined by the NodePanel.Figure attached

         property -->

    <go:NodeShape go:NodePanel.Figure="Database"

                  Stroke="Black" StrokeThickness="1">

      <Shape.Fill>

        <!-- use a fancier brush than a simple solid color -->

        <LinearGradientBrush StartPoint="0.0 0.0" EndPoint="1.0 0.0">

          <LinearGradientBrush.GradientStops>

            <GradientStop Color="{Binding Path=Data.Color,

                    Converter={StaticResource theStringColorConverter}}"

                Offset="0.0" />

            <GradientStop Color="White" Offset="0.5" />

            <GradientStop Color="{Binding Path=Data.Color,

                    Converter={StaticResource theStringColorConverter}}"

                Offset="1.0" />

          </LinearGradientBrush.GradientStops>

        </LinearGradientBrush>

      </Shape.Fill>

    </go:NodeShape>

    <!-- this TextBlock element is arranged inside the NodePanel’s shape -->

    <TextBlock Text="{Binding Path=Data.Key}" TextAlignment="Center"

               HorizontalAlignment="Center" VerticalAlignment="Center" />

  </go:NodePanel>

</DataTemplate>

 

Note how the LinearGradientBrush is constructed, binding two of the gradient stop colors to the MyData.Color property.  [Note: this binding does not work in Silverlight.]  The binding also depends on the presence of a StringColorConverter (not a StringBrushConverter), which was also defined as a resource.  The result might look like:

The above use of NodePanel assumes that the shape, the first child of the panel, has a fixed width and height.  (If the Width or Height are not supplied, they default to 100, as you can see in the “database” shapes above.)  The other children of the NodePanel are arranged inside the first child, observing the HorizontalAlignment and/or VerticalAlignment properties of the child if the width and/or height are smaller than the available area inside the first child.  For example:

<DataTemplate x:Key="NodeTemplate2">

  <go:NodePanel Sizing="Fixed">

    <go:NodeShape go:NodePanel.Figure="Parallelogram1"

          Width="100" Height="50"

          Stroke="Black" StrokeThickness="1" Fill="LightYellow" />

    <TextBlock Text="{Binding Path=Data.Key}" TextAlignment="Left"

               HorizontalAlignment="Right" VerticalAlignment="Bottom" />

  </go:NodePanel>

</DataTemplate>

This might produce:

NodePanel.Sizing defaults to Fixed.  Note the setting of Width and Height of the shape.

But you can also have the first child be sized to fit around the other children.  This is convenient when you want to show a variable amount of text and want the minimal amount of shape surrounding it.  Just set Sizing to Auto:

<DataTemplate x:Key="NodeTemplate3">

  <go:NodePanel Sizing="Auto">

    <go:NodeShape go:NodePanel.Figure="Parallelogram1"

          Stroke="Black" StrokeThickness="1" Fill="LightYellow" />

    <TextBlock Text="{Binding Path=Data.Key}" TextAlignment="Left"

               Margin="10" />

  </go:NodePanel>

</DataTemplate>

This might produce:

Note how we do not set the Width or Height of the shape.  Furthermore we do not set the HorizontalAlignment or VerticalAlignment, because those properties have no effect.  (TextAlignment affects how the text is rendered in its allotted space, not how it is positioned in its panel.  Margin reserves some room around the TextBlock – without it the parallelogram would be tightly around the text.)

Resizing

If you want to let users resize such nodes, you first need to think about which element is the “main” element that will control the size and layout of all of the other elements.  The “main” element may very well not be the outermost or “root” visual element of the template.  So it is not always sufficient to just set go: Part.Resizable="True" on the root element; you also need to indicate which element should be the one to get the resize handles and be resized by the ResizingTool:

<DataTemplate x:Key="NodeTemplate4">

  <go:NodePanel Sizing="Fixed"

                go:Part.Resizable="True" go:Part.ResizeElementName="Shape">

    <go:NodeShape x:Name="Shape" go:NodePanel.Figure="Parallelogram1"

                  Width="100" Height="50"

                  Stroke="Black" StrokeThickness="1" Fill="LightYellow" />

    <TextBlock Text="{Binding Path=Data.Key}" Margin="10"

               HorizontalAlignment="Center" VerticalAlignment="Center" />

  </go:NodePanel>

</DataTemplate>

 

Note how the root element refers to the NodeShape by name, so that user resizing will actually change the width and height of that shape.  If you do not specify the Part.ResizeElementName, the root element will get the resize handles and attempts to resize the NodePanel are not likely to have the effect you want.

Sizing=”Fixed” is appropriate because that causes the NodePanel to fit the other child elements within the shape.  Sizing=”Auto” causes NodePanel to fit the shape around all of the other children, which is not what the user would want if they were trying to resize it.  In this case, if you want the user to interactively resize the node, you will need to have the Part.SelectionElementName refer to a different child of the NodePanel, not the first child.

Collapsing and Expanding Trees

A common technique for simplifying tree-structured graphs is to collapse subtrees.  One way to implement this functionality is to add a Button to each node.

<!-- show either a "+" or a "-" as the Button content -->

<go:BooleanStringConverter x:Key="theButtonConverter"

                           TrueString="-" FalseString="+" />

   

<DataTemplate x:Key="NodeTemplate">

  <StackPanel Orientation="Horizontal" go:Part.SelectionAdorned="True"

              go:Node.IsTreeExpanded="False">

    <!-- go:Node.IsTreeExpanded="False" tells the node to start collapsed -->

    <go:NodePanel Sizing="Auto">

      <go:NodeShape go:NodePanel.Figure="Ellipse"

          Fill="{Binding Path=Data.Color,

                         Converter={StaticResource theBrushConverter}}" />

      <TextBlock Text="{Binding Path=Data.Color}" />

    </go:NodePanel>

    <Button x:Name="myCollapseExpandButton" Click="CollapseExpandButton_Click"

            Content="{Binding Path=Node.IsExpandedTree,

                              Converter={StaticResource theButtonConverter}}"

            Width="20" />

  </StackPanel>

</DataTemplate>

 

Note that the Button.Content is bound to the Node.IsExpandedTree property, via a converter that converts the boolean value to either the string “+” or the string “-“.  Of course you can (and probably should) style the Button the way you want instead of using those two text strings.  But we’ll keep it simple in this document.

 

The Button.Click event handler might be implemented as:

 

private void CollapseExpandButton_Click(object sender, RoutedEventArgs e) {

  // the Button is in the visual tree of a Node

  Button button = (Button)sender;

  Node n = Part.FindAncestor<Node>(button);

  if (n != null) {

    SimpleData parentdata = (SimpleData)n.Data;

    // always make changes within a transaction

    myDiagram.StartTransaction("CollapseExpand");

    // toggle whether this node is expanded or collapsed

    n.IsExpandedTree = !n.IsExpandedTree;

    myDiagram.CommitTransaction("CollapseExpand");

  }

}

 

A graph might start with a single node:

An expansion (and a control-mouse-wheel zoom-out) might produce:

Further expansions (and zoom outs) might produce:

In-place Text Editing and Validation

When you want to let users modify the text in a node, one possibility is to implement your node’s DataTemplate to have its own TextBox that is normally Collapsed but that you make Visible when you want to edit.  In fact, you can have arbitrarily complex controls in each of your nodes.  However, the disadvantage is that all of those controls will always be created for each node, thereby increasing the overhead.

GoXam supports in-place text editing.  Just set the Part.TextEditable attached property on a TextBlock.

<DataTemplate x:Key="NodeTemplate5">

  <go:NodePanel Sizing="Auto">

    <go:NodeShape go:NodePanel.Figure="Parallelogram1"

          Stroke="Black" StrokeThickness="1" Fill="LightYellow" />

    <TextBlock Text="{Binding Path=Data.Text, Mode=TwoWay}"

               TextWrapping="Wrap" TextAlignment="Left" Margin="5"

               go:Part.TextEditable="True" />

  </go:NodePanel>

</DataTemplate>

 

(Note that since we expect the user to modify the text, we data bind the text to a different property on the data, not the unique Key.)  If the user starts with:

Then if they select the node and then click on the text, the TextEditingTool brings up a TextBox.  The user can edit the text.  Losing focus by clicking elsewhere or by tabbing will accept the changes; typing ESCAPE will cancel the edit and restore the original string.

Here you can see the (blinking) cursor positioned at the end of the second line.

You can implement custom text validation by customizing the TextEditingTool.  This example checks whether the user has typed the letter ‘e’:

  public class CustomTextEditingTool : TextEditingTool {
    protected override bool IsValidText(string oldstring, string newstring) {
      bool valid = !newstring.Contains("e");
      if (!valid) {
        MessageBox.Show("Oops: new string contains 'e'");
      }
      return valid;
    }
  }

and install with either:
  myDiagram.TextEditingTool = new CustomTextEditingTool();
or:
 
    <go:Diagram . . .>
      <go:Diagram.TextEditingTool>
        <local:CustomTextEditingTool />
      </go:Diagram.TextEditingTool>
      </go:Diagram>

From that predicate you can use the AdornedPart.Data property to access the bound data.

Spots

Although previous examples have used standard named values such as Spot.BottomRight and Spot.MiddleLeft, spots are more general than that.  A spot represents a relative point from (0,0) to (1,1) within a rectangle from the top-left corner to the bottom-right corner, plus an absolute offset.

Here’s a demonstration showing nine text objects positioned at the standard nine spots.  This makes use of the SpotPanel panel.  You may find the SpotPanel useful when you want to position smaller elements “inside” another element.

<go:SpotPanel>

  <Rectangle go:SpotPanel.Main="True" Fill="LightCoral"

             Width="200" Height="100" />

  <TextBlock go:SpotPanel.Spot="0.0 0.0" Text="0 0" />

  <TextBlock go:SpotPanel.Spot="0.5 0.0" Text="0.5 0" />

  <TextBlock go:SpotPanel.Spot="1.0 0.0" Text="1 0" />

  <TextBlock go:SpotPanel.Spot="0.0 0.5" Text="0 0.5" />

  <TextBlock go:SpotPanel.Spot="0.5 0.5" Text="0.5 0.5" />

  <TextBlock go:SpotPanel.Spot="1.0 0.5" Text="1 0.5" />

  <TextBlock go:SpotPanel.Spot="0.0 1.0" Text="0 1" />

  <TextBlock go:SpotPanel.Spot="0.5 1.0" Text="0.5 1" />

  <TextBlock go:SpotPanel.Spot="1.0 1.0" Text="1 1" />

</go:SpotPanel>

 

The SpotPanel.Spot attached property specifies where the element should be positioned in a SpotPanel.  The SpotPanel.Alignment attached property specifies what point of the element should be positioned at the SpotPanel.Spot point.  By default the center of each element is aligned at the spot point.

The Main attached property says that the spots are all relative to the bounds of the first child element of the SpotPanel, which in this case is a Rectangle.

Instead of always centering the element at the spot point, you can use any other spot in that element.  The following three child elements are all positioned at the same (0, 0) spot, but with different alignments.

<go:SpotPanel>

  <Rectangle go:SpotPanel.Main="True" Fill="LightCoral"

             Width="200" Height="100" />

  <TextBlock go:SpotPanel.Spot="0 0"

             go:SpotPanel.Alignment="1.0 1.0" Text="1 1" />

  <TextBlock go:SpotPanel.Spot="0 0"

             go:SpotPanel.Alignment="0.5 0.5" Text="0.5 0.5" />

  <TextBlock go:SpotPanel.Spot="0 0"

             go:SpotPanel.Alignment="0.0 0.0" Text="0 0" />

</go:SpotPanel>

 

Finally, Spots can have absolute offsets in addition to the fractional relative position.  These offsets may be negative.  You can specify the X and Y offsets as the third and fourth numbers.  In this example there are three TextBlocks at the bottom-left corner.  All have the default center alignment.  One has an X offset of negative 30 (i.e. further towards the left), one is centered exactly at the bottom-left corner of the rectangle, and one is shifted towards the right by 30.  Similarly there are three TextBlocks at the bottom-right corner, with one shifted up 10, and with one shifted down 10.

<go:SpotPanel>

  <Rectangle go:SpotPanel.Main="True" Fill="LightCoral"

             Width="200" Height="100" />

  <TextBlock go:SpotPanel.Spot="0 1 -30 0" Text="-30 0" />

  <TextBlock go:SpotPanel.Spot="0 1 0 0"   Text="0 0" />

  <TextBlock go:SpotPanel.Spot="0 1 30 0"  Text="30 0" />

  <TextBlock go:SpotPanel.Spot="1 1 0 -10" Text="0 -10" />

  <TextBlock go:SpotPanel.Spot="1 1 0 0"   Text="0 0" />

  <TextBlock go:SpotPanel.Spot="1 1 0 10"  Text="0 10" />

</go:SpotPanel>

Data Templates for Links

The simplest kind of link consists of only a line, perhaps consisting of multiple segments and curves.  You must use the LinkShape element for this:

  <DataTemplate>

    <go:LinkShape Stroke="Black" StrokeThickness="1" />

  </DataTemplate>

 

Like node templates, the typical pattern is to define templates as resources, and refer to them when initializing the Diagram:

<UserControl.Resources>

  <DataTemplate x:Key="LinkTemplate1">

    <go:LinkShape Stroke="Black" StrokeThickness="1" />

  </DataTemplate>

  <!-- define other templates here -->

</UserControl.Resources>

. . .

<go:Diagram x:Name="myDiagram" LinkTemplate="{StaticResource LinkTemplate1}" />

 

But note that such a link template will result in links for which there is no arrowhead nor any other decoration.  Thus such a simple template can only be used where the links are not directional or where the direction is implicit in the diagram, such as in a tree.

It is more common to have at least an arrowhead on each link.  For example, the following template is similar to the default link template – the one used when you do not specify the Diagram.LinkTemplate property.

<DataTemplate x:Key="LinkTemplate2">

  <go:LinkPanel go:Part.SelectionElementName="Path"

                go:Part.SelectionAdorned="True" >

    <go:LinkShape x:Name="Path" go:LinkPanel.IsLinkShape="True"

                  Stroke="Black" StrokeThickness="1" />

    <Polygon Fill="Black" Points="8 4  0 8  2 4  0 0"  <!-- the arrowhead -->

             go:LinkPanel.Alignment="MiddleRight" go:LinkPanel.Index="-1"

             go:LinkPanel.Orientation="Along" />

  </go:LinkPanel>

</DataTemplate>

 

Here is a visual representation of the points of the polygon:

This results in links that appear like those with arrowheads shown before:

Note the use of the LinkPanel class.  A LinkPanel is a Panel that should have a LinkShape in it named “Path”.  The path’s Geometry is computed by the link’s Route – i.e. it is given a set of points so that the link shape appears to connect the link’s two nodes.

Once the link’s route is determined, the LinkPanel can arrange all of the other child elements of the panel to be somewhere along the path of the link. In this template, there is a Polygon that is acting as an arrowhead.  There are three attached properties that control how a LinkPanel child such as this Polygon is positioned and rotated relative to the link shape.  All three are used in this example.

·         The LinkPanel.Alignment attached property is a Spot that indicates what point within the polygon should be positioned along the link path.  (More about spots later.)  In the above case the MiddleRight spot happens to be the point 8,4.

·         The LinkPanel.Index attached property specifies at which segment the child element should be placed; zero means at the end near the “from” node, -1 means at the end near the “to” node.

·         The LinkPanel.Orientation attached property controls whether and how the child element is rotated; “Along” means at the same angle as that link segment.

As a practical matter most link templates consist of a LinkPanel holding a LinkShape and some varying number of decorations positioned along the link path.

Setting the Part.SelectionElementName attached property indicates which element should get a selection handle when the part becomes selected.  In this case the link shape will get the selection handle, which is what you would normally want.  If you did not set the SelectionElementName, the user would see a big Rectangle surrounding the whole link, which is probably not what you want.

You can have as many arrowheads as you like.  For example, here’s a double-headed link:

<DataTemplate x:Key="LinkTemplate9">

  <go:LinkPanel go:Part.SelectionElementName="Path">

    <go:LinkShape x:Name="Path" go:LinkPanel.IsLinkShape="True"

                  Stroke="Black" StrokeThickness="1" />

    <!-- the “to” arrowhead -->

    <Polygon Fill="Black" Points="8 4  0 8  2 4  0 0"

             go:LinkPanel.Alignment="1 0.5" go:LinkPanel.Index="-1"

             go:LinkPanel.Orientation="Along" />

    <!-- the “from” arrowhead -->

    <Polyline Stroke="Black" StrokeThickness="1"

              Points="7 0  0 3.5  7 7"

              go:LinkPanel.Alignment="0 0.5" go:LinkPanel.Index="0"

              go:LinkPanel.Orientation="Along" />

  </go:LinkPanel>

</DataTemplate>

 

This might look like:

With this mechanism you can implement any arrowhead that you like.  The arrowhead element need not be a Polygon but can be as complicated as you want.  However, this general mechanism isn’t so convenient to use.

Therefore we have predefined a number of common arrowheads.  You have to provide a Path element as an immediate child of the LinkPanel, and naturally you can specify its Fill and Stroke properties.  Then you can just set the attached property LinkPanel.ToArrow.  For example, the following XAML is the same as the above “to” arrowhead element:

    <Path Fill="Black" go:LinkPanel.ToArrow="Standard" />

You can also change the size of the arrowhead by setting the LinkPanel.ToArrowScale attached property.  And you can also set FromArrow and FromArrowScale.

All of the arrowheads are shown by the Arrowheads sample.  Note that this screenshot may be out-of-date; look at the Arrowhead enumerated type for the complete list.

Of course link templates can be complicated too.  If you are using a GraphLinksModel, you can bind to the link data.  Let’s add a text element with a white background:

<DataTemplate x:Key="LinkTemplate3">

  <go:LinkPanel go:Part.SelectionElementName="Path"

                go:Part.SelectionAdorned="True">

    <go:LinkShape x:Name="Path" go:LinkPanel.IsLinkShape="True"

                  Stroke="Black" StrokeThickness="1" />

    <!-- the arrowhead -->

    <Polygon Fill="Black" Points="8 4  0 8  2 4  0 0"

             go:LinkPanel.Alignment="1 0.5" go:LinkPanel.Index="-1"

             go:LinkPanel.Orientation="Along" />

    <!-- when using a GraphLinksModel, bind to MyLinkData.Cost as a label -->

    <StackPanel Background="White">

      <TextBlock Text="{Binding Path=Data.Cost}" Foreground="Blue" />

    </StackPanel>

  </go:LinkPanel>

</DataTemplate>

 

This makes use of a MyLinkData type that you might define with a Cost property:

[Serializable]  // serializable only for WPF

public class MyLinkData : GraphLinksModelLinkData<String, String> {

  public double Cost {

    get { return _Cost; }

    set {

      if (_Cost != value) {

        double old = _Cost;

        _Cost = value;

        RaisePropertyChanged("Cost", old, value);

      }

    }

  }

  private double _Cost;

}

 

If you expect the link data not to change and need to update the diagram, you could have a simpler implementation of the link data class:

[Serializable]  // serializable only for WPF

public class MyLinkData : GraphLinksModelLinkData<String, String> {

  public double Cost { get; set; }  // if setter does not need to notify

}

 

The result might look like:

Data Binding to Link Nodes

The example above performed data binding of a TextBlock’s Text property to a property on the Link’s Data (an instance of MyLinkData).  However, it is also possible to data bind link properties to properties on either of the Link’s connected Nodes.  This will work even if the model does not support separate link data.

For instance, if you want each link to be colored according to some property of the “To” node, you can bind the Stroke to {Binding Path=Link.ToData.SomeProperty, Converter={StaticResource someConverter}}.

For example, we can customize the link colors of the DoubleTree sample by changing the link’s template to depend on the Info.LayoutId property, where Info is a node data class defined in that sample, and where the LayoutId property indicates which direction the tree is growing at that node.

<local:LinkBrushConverter x:Key="theLinkBrushConverter" />

<DataTemplate x:Key="LinkTemplate">

  <go:LinkPanel>

    <go:LinkShape StrokeThickness="1"

          Stroke="{Binding Path=Link.ToData.LayoutId,

                           Converter={StaticResource theLinkBrushConverter}}" />

    <Polygon Fill="{Binding Path=Link.ToData.LayoutId,

                            Converter={StaticResource theLinkBrushConverter}}"

             Points="8 4  0 8  2 4  0 0" go:LinkPanel.Index="-1"

             go:LinkPanel.Alignment="1 0.5" go:LinkPanel.Orientation="Along" />

  </go:LinkPanel>

</DataTemplate>

 

Note that in this example both the Path.Stroke and the Polygon.Fill are bound to the same data property using the same converter.

The LinkBrushConverter needs to convert the string value of Info.LayoutId to the desired Brush.  This is an example of defining your own custom data converter:

  public class LinkBrushConverter : Northwoods.GoXam.Converter {

    public override object Convert(object value, Type targetType,

               object parameter, System.Globalization.CultureInfo culture) {

      if (value is String) {

        switch ((String)value) {

          case "Right": return Black;

          case "Left": return Red;

          case "Up": return Green;

          case "Down": return Blue;

          default: return Black;

        }

      }

      return Black;

    }

 

    private static Brush Black = new SolidColorBrush(Colors.Black);

    private static Brush Red = new SolidColorBrush(Colors.Red);

    private static Brush Green = new SolidColorBrush(Colors.Green);

    private static Brush Blue = new SolidColorBrush(Colors.Blue);

  }

 

For efficiency this example converter only returns one of four predefined solid brushes that are shared.  However, it is common to return a new SolidColorBrush when the color is more variable.  In any case, this is what the results might look like:

Link Routes

So far all of the example links have been fairly simple.  If you want to customize the path that each link takes, you need to set properties on the link’s Route.  Each Link has a Route that it creates by default, but you can replace it with one that you have initialized.

<DataTemplate x:Key="LinkTemplate4">

  <go:LinkPanel go:Part.SelectionElementName="Path"

                go:Part.SelectionAdorned="True">

    <go:Link.Route>

      <go:Route Routing="Orthogonal" />

    </go:Link.Route>

    <go:LinkShape x:Name="Path" go:LinkPanel.IsLinkShape="True"

                  Stroke="Black" StrokeThickness="1" />

    <Polygon Fill="Black" Points="8 4  0 8  2 4  0 0"

             go:LinkPanel.Alignment="1 0.5" go:LinkPanel.Index="-1"

             go:LinkPanel.Orientation="Along" />

  </go:LinkPanel>

</DataTemplate>

 

The Route.Routing property controls what general route the link will take.  The default value is LinkRouting.Normal, which produces the direct paths you have seen so far.  But if you use LinkRouting.Orthogonal, which tries to make each segment of the link either horizontal or vertical, it might look like:

Another routing option assumes orthogonal segments for the link, but also tries to avoid crossing over other nodes.

    <go:Link.Route>

      <go:Route Routing="AvoidsNodes" />

    </go:Link.Route>

 

After adding two nodes to be in the way:

 

The Route.Curve property specifies what kind of path to draw given the points calculated for the route.  The default value is LinkCurve.None, which produces the straight line segments you have seen in the examples so far.  The LinkCurve.Bezier value produces naturally curved paths.

    <go:Link.Route>

      <go:Route Curve="Bezier" />

    </go:Link.Route>

 

You can control the amount of curvature by setting the Route.Curviness property.  With varying numbers of links between the same pair of nodes it will automatically compute values for Curviness unless you assign it explicitly.

Combining orthogonal Routing and Corner:

    <go:Link.Route>

      <go:Route Routing="Orthogonal" Corner="10" />

    </go:Link.Route>

 

produces:

 

 

Or use Curve.JumpOver with LinkRouting.Orthogonal or AvoidsNodes:

    <go:Link.Route>

      <go:Route Routing="Orthogonal" Curve="JumpOver" Corner="10" />

    </go:Link.Route>

 

Link Labels

It is common to add annotations or decorations to links, particularly text.  You can easily add any elements you want to a LinkPanel.  For example, let us add three text labels to a link, one in the middle, one on the left side of the link and one on the right side of the link:

<DataTemplate x:Key="LinkTemplate5">

  <go:LinkPanel>

    <go:LinkShape Stroke="Black" StrokeThickness="1" />

    <Polygon Fill="Black" Points="8 4  0 8  2 4  0 0" go:LinkPanel.Index="-1"

             go:LinkPanel.Alignment="1 0.5" go:LinkPanel.Orientation="Along" />

    <TextBlock Text="Left"

               go:LinkPanel.Offset="0 -10" go:LinkPanel.Orientation="Upright" />

    <TextBlock Text="Middle"

               go:LinkPanel.Offset="0 0" go:LinkPanel.Orientation="Upright" />

    <TextBlock Text="Right"

               go:LinkPanel.Offset="0 10" go:LinkPanel.Orientation="Upright" />

  </go:LinkPanel>

</DataTemplate>

 

The LinkPanel.Offset attached property controls where to position the element relative to a point on a segment of the link.  A positive value for the Y offset moves the label element towards the right side of the link, as seen going in the direction of the link.  Naturally a negative value for the Y offset moves it towards the left side.

The segment is specified by the LinkPanel.Index attached property, which defaults to the middle of the whole link.  The offset is rotated according to the angle formed by that link segment.  Here are the results, with the nodes at different relative positions to demonstrate how the labels follow the (only) segment of the link.


The LinkPanel.Orientation attached property controls the angle of the label relative to the angle of the link segment.  The value of Along, as you have seen above with arrowheads, results in a label angle that is the same as the segment’s angle.  The value of Upright is useful for elements containing text because the text will not be upside down, although like Along it will always be angled to follow the link.  To continue the counter-clockwise rotation of the Beta node around the Alpha node:

When you specify the LinkPanel.Index, you can position labels at places other than the middle of the link.  The index of zero is at the very beginning of the link; a value of one is at the next point in the route.  Negative values are permitted – they count down from the “to” end of the link, with index -1 at the very last point of the link.

  <TextBlock Text="From" go:LinkPanel.Index="0"

             go:LinkPanel.Offset="NaN NaN" go:LinkPanel.Orientation="Upright" />

  <TextBlock Text="To" go:LinkPanel.Index="-1"

             go:LinkPanel.Offset="NaN NaN" go:LinkPanel.Orientation="Upright" />

The uses of NaN in the Offset mean half the width and half the height of the label element, which is convenient when the size of the label element may vary.

Links need not be straight with a single segment.  Here are examples of Orthogonal routing and of Bezier curves, with the middle label having two lines of text:

Labels need not be TextBlocks.  The default LinkPanel.Orientation is None, meaning that the label element is not rotated at all.  For example:

  <!-- LinkPanel labels in Silverlight -->

  <go:NodePanel go:LinkPanel.Index="0" go:LinkPanel.Offset="5 5" >

    <go:NodeShape go:NodePanel.Figure="EightPointedStar" Fill="Red"

                  Width="10" Height="10" />

  </go:NodePanel>

  <Button Content="?" Click="Button_Click" />

 

  <!-- LinkPanel labels in WPF -->

  <go:NodeShape go:LinkPanel.Index="0" go:LinkPanel.Offset="5 5"

                go:NodePanel.Figure="EightPointedStar" Fill="Red"

                Width="10" Height="10" />

  <Button Content="?" Click="Button_Click" />

 

Link Connection Points on Nodes

In the examples above you have seen how each link will end at the edge of the node.  To illustrate this further, notice in the following screenshot where the arrowheads appear to terminate around the “Alpha” node, around the rectangular bounds of the text:

If the node is not shaped like a rectangle, the link will connect at the edge.

<DataTemplate x:Key="NodeTemplate1">

  <go:NodePanel go:Node.SelectionAdorned="True">

    <go:NodeShape go:NodePanel.Figure="OrGate" Width="70" Height="70"

          Stroke="Black" StrokeThickness="1"

          Fill="{Binding Path=Data.Color,

                         Converter={StaticResource theStringBrushConverter}}" />

    <TextBlock Text="{Binding Path=Data.Key}" TextAlignment="Center"

               HorizontalAlignment="Center" VerticalAlignment="Center" />

  </go:NodePanel>

</DataTemplate>

 

But what if you want to limit the points at which links may connect to a node?  You can do so by setting the Node.FromSpot and Node.ToSpot attached properties on the root visual element of the node.  The default value is Spot.None, which means to calculate a point along the edge of the element.  But you can specify spot values that describe particular positions on the element.  For example:

<DataTemplate x:Key="NodeTemplate2">

  <TextBlock Text="{Binding Path=Data.Key}" go:Node.SelectionAdorned="True"

             go:Node.ToSpot="MiddleLeft" go:Node.FromSpot="MiddleRight" />

</DataTemplate>

 

This specifies that links coming into this node connect at the middle of the left side, and that links going out of this node connect at the middle of the right side.  Such a convention is appropriate for diagrams that have a general sense of direction to them, such as the following one which goes from left to right:

You can also specify that the links go into a node not at a single spot but spread out along one side.  Change the previous example to use:

    go:Node.ToSpot="LeftSide" go:Node.FromSpot="RightSide"

And you will get:

Of course specifying a side works well only for nodes that are basically rectangular and probably larger than in this case.  So let’s add a border around the text to make each node bigger:

<DataTemplate x:Key="NodeTemplate3">

  <Border BorderBrush="Black" BorderThickness="1" Padding="3"

          go:Node.SelectionAdorned="True"

          go:Node.ToSpot="LeftSide" go:Node.FromSpot="RightSide" >

    <TextBlock Text="{Binding Path=Data.Key}" />

  </Border>

</DataTemplate>

 

Note how the attached node properties have been moved to the new root element of the data template.  This node template with the same data results in:

Of course you can use different kinds of Routes for the link template.   Consider:

    <go:Link.Route>

      <go:Route Curve="Bezier" />

    </go:Link.Route>

 

Or:

    <go:Link.Route>

      <go:Route Routing="Orthogonal" Corner="10" />

    </go:Link.Route>

 

Ports on Nodes

Although you have some control over where links will connect at a node (at a particular spot, along one or more sides, or at the intersection with the edge), there are times when you want to have different logical and graphical places at which links should connect.  The elements to which a link may connect are called ports.  There may be any number of ports in a node.  By default there is just one port, the root visual element, which results in the effect of having the whole node act as the port, as you have seen above.  Support for multiple ports is only possible in a GraphLinksModel because only when you have separate data for each link can you attach information describing which port the link should connect to.

To declare that a particular element is a port, set the Node.PortId attached property on it.  Unlike most of the Part and Node attached properties, which may only be applied to the root visual element of the node, the port-related Node attached properties may apply to any element in the visual tree of the node.  These attached properties have names that start with “Port”, “From”, “To”, or “Linkable”.

<DataTemplate x:Key="NodeTemplate4">

  <Border BorderBrush="Black" BorderThickness="1"

          go:Node.SelectionAdorned="True">

    <Grid Background="LightGray">

      <Grid.ColumnDefinitions>

        <ColumnDefinition Width="Auto" />

        <ColumnDefinition Width="*" />

        <ColumnDefinition Width="Auto" />

      </Grid.ColumnDefinitions>

      <Grid.RowDefinitions>

        <RowDefinition Height="Auto" />

        <RowDefinition Height="*" />

        <RowDefinition Height="*" />

      </Grid.RowDefinitions>

      <TextBlock Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="3"

                 Text="{Binding Path=Data.Key}" TextAlignment="Center"

                 FontWeight="Bold" TextWrapping="Wrap" Margin="4,4,4,2" />

      <StackPanel Grid.Column="0" Grid.Row="1" Orientation="Horizontal">

        <!-- this Rectangle is a port, identified with the string “A”;

             links only come into it at the middle of the left side -->

        <Rectangle Width="6" Height="6" Fill="Black"

                   go:Node.PortId="A" go:Node.ToSpot="MiddleLeft" />

        <TextBlock Text="A" />

      </StackPanel>

      <StackPanel Grid.Column="0" Grid.Row="2" Orientation="Horizontal">

        <!-- this Rectangle is another input port, named “B” -->

        <Rectangle Width="6" Height="6" Fill="Black"

                   go:Node.PortId="B" go:Node.ToSpot="MiddleLeft" />

        <TextBlock Text="B" />

      </StackPanel>

      <StackPanel Grid.Column="2" Grid.Row="1" Grid.RowSpan="2"

                  Orientation="Horizontal" VerticalAlignment="Center">

        <TextBlock Text="Out" />

        <!-- this Rectangle is another port, identified with the string “Out”;

             links only go out of it at the middle of the right side -->

        <Rectangle Width="6" Height="6" Fill="Black"

                   go:Node.PortId="Out" go:Node.FromSpot="MiddleRight" />

      </StackPanel>

    </Grid>

  </Border>

</DataTemplate>

 

Each port has a Node.PortId that corresponds to the optional port parameter information at both ends of each link.  To avoid visual confusion in this example there is also a TextBlock next to each port, showing the same string.

This node template, combined with a GraphLinksModel and data such as:

  var model = new GraphLinksModel<MyData, String, String, MyLinkData>();

 

  model.NodesSource = new ObservableCollection<MyData>() {

    new MyData() { Key="Add1" },

    new MyData() { Key="Add2"},

    new MyData() { Key="Subtract" },

  };

 

  model.LinksSource = new ObservableCollection<MyLinkData>() {

    new MyLinkData() { From="Add1", FromPort="Out", To="Subtract", ToPort="A" },

    new MyLinkData() { From="Add2", FromPort="Out", To="Subtract", ToPort="B" },

  };

 

  myDiagram.Model = model;

 

can produce a diagram like:

Data Templates for Groups

To define the appearance of group nodes, you can set the Diagram.GroupTemplate property.  The default template produces the following simple representation of a group, in this case “Epsilon”.

To customize the appearance of a group, you could define a template such as:

<DataTemplate x:Key="GroupTemplate1">

  <StackPanel go:Node.LocationElementName="myGroupPanel">

    <!-- This is the “header” for the group -->

    <TextBlock x:Name="Label" Text="{Binding Path=Data.Key}"

               FontSize="18" FontWeight="Bold" Foreground="Green"

               HorizontalAlignment="Center"/>

    <Border x:Name="myBorder" CornerRadius="5"

            BorderBrush="Green" BorderThickness="2">

      <!-- The GroupPanel is the placeholder for member parts -->

      <go:GroupPanel x:Name="myGroupPanel" Padding="5" />

    </Border>

    <!-- This is some extra information for the group -->

    <TextBlock Text="BottomRight" HorizontalAlignment="Right" />

  </StackPanel>

</DataTemplate>

 

Notice that there is a GroupPanel element inside the Border.  You use a GroupPanel as the placeholder for all of the nodes and links that are members of the group.  The member Nodes and Links are not visual children of the panel or of the group node – they are independent parts in the diagram.

If you use a GroupPanel, and if it is not the root visual element of the data template, it must be named as the Node.LocationElementName for the group.  Just give the GroupPanel a Name and refer to it via the attached property Node.LocationElementName on the root element.  This means that the Node’s location will always be the same as the GroupPanel’s location, even as elements outside of the GroupPanel change size or move around with respect to the panel.

<DataTemplate x:Key="GroupTemplate2">

  <Border x:Name="myBorder" CornerRadius="5"

          BorderBrush="Green" BorderThickness="2"

          go:Node.LocationElementName="myGroupPanel">

    <StackPanel>

      <TextBlock x:Name="Label" Text="{Binding Path=Data.Key}"

                 FontSize="16" FontWeight="Bold" Foreground="Green"

                 HorizontalAlignment="Center" />

      <go:GroupPanel x:Name="myGroupPanel" Padding="5" />

      <TextBlock Text="BottomRight" FontSize="7"

                 HorizontalAlignment="Right" />

    </StackPanel>

  </Border>

</DataTemplate>

 

The second screenshot shows the result of dragging the “Gamma” node downward a bit.

A GroupPanel always encloses its member Nodes, even while the nodes are being dragged.  If you don’t want this behavior during dragging, for example in order to permit a Node to be dragged outside of its Group, you can set GroupPanel.SurroundsMembersAfterDrop to true.  This changes the behavior of the GroupPanel so that it does not resize during a drag until the drop is completed.

Collapsing and Expanding SubGraphs

It is common to simplify graphs by collapsing subgraphs into a single node.  One way to implement collapsible subgraphs is with a button.

<!-- show either a "+" or a "-" as the Button content -->

<go:BooleanStringConverter x:Key="theButtonConverter"

                           TrueString="-" FalseString="+" />

 

<DataTemplate x:Key="GroupTemplate">

  <Border CornerRadius="5" BorderThickness="2" Background="Transparent"

          BorderBrush="{Binding Path=Data.Color,

                                Converter={StaticResource theBrushConverter}}"

          go:Part.SelectionAdorned="True"

          go:Node.LocationElementName="myGroupPanel"

          go:Group.IsSubGraphExpanded="False">

    <!-- go:Group.IsSubGraphExpanded="False" causes it to start collapsed -->

    <StackPanel>

      <StackPanel Orientation="Horizontal" HorizontalAlignment="Left">

        <Button x:Name="myCollapseExpandButton"

            Click="CollapseExpandButton_Click"

            Content="{Binding Path=Group.IsExpandedSubGraph,

                              Converter={StaticResource theButtonConverter}}"

            Width="20" Margin="0 0 5 0"/>

        <TextBlock Text="{Binding Path=Data.Key}" FontWeight="Bold" />

      </StackPanel>

      <go:GroupPanel x:Name="myGroupPanel" Padding="5" />

    </StackPanel>

    <!-- each Group can have its own Layout -->

    <go:Group.Layout>

      <!-- this Layout is performed whenever any nested Group changes size -->

      <go:LayeredDigraphLayout Direction="90"

                               Conditions="Standard GroupSizeChanged" />

    </go:Group.Layout>

  </Border>

</DataTemplate>

 

Note that the Button.Content is bound to the Group.IsExpandedSubGraph property, via a converter that converts the boolean value to either the string “+” or the string “-“.

Collapsed it might appear as:

The Button.Click event handler might be defined as:

private void CollapseExpandButton_Click(object sender, RoutedEventArgs e) {

  // the Button is in the visual tree of a Node

  Button button = (Button)sender;

  Group sg = Part.FindAncestor<Group>(button);

  if (sg != null) {

    SimpleData subgraphdata = (SimpleData)sg.Data;

    // always make changes within a transaction

    myDiagram.StartTransaction("CollapseExpand");

    // toggle whether this node is expanded or collapsed

    sg.IsExpandedSubGraph = !sg.IsExpandedSubGraph;

    myDiagram.CommitTransaction("CollapseExpand");

  }

}

 

Expanded it might look like:

A more “standard” implementation for a Group might use an Expander:

<DataTemplate x:Key="GroupTemplate6">

  <Expander Header="{Binding Path=Data.Name}"

            IsExpanded="{Binding Path=Group.IsExpandedSubGraph, Mode=TwoWay}"

            go:Node.LocationElementName="myGroupPanel">

    <Border BorderBrush="Green" BorderThickness="2"

            Background="Transparent" CornerRadius="5">

      <go:GroupPanel x:Name="myGroupPanel" Padding="6" />

    </Border>

  </Expander>

</DataTemplate>

 

Note how the Expander.IsExpanded property is data-bound to Group.IsExpandedSubGraph.

 

Groups with Ports

The previous examples did not treat groups as nodes in their own right.  As with regular Nodes, a link to a Group will by default treat the whole node as the only “port”.  For example, connecting “Alpha” to “Epsilon” (instead of to “Gamma”) and “Epsilon” (instead of “Delta”) to “Beta” might result in the following screenshot.  The “Alpha” and “Beta” nodes have been moved to make clearer the connections to the group.

The following example gives group nodes three ports on the left and two on the right, spaced equally within the thick border.  The input ports on the left are named “zero”, “one”, and “two”; the output ports on the right are named “OutA” and “OutB”.  This example has no text labels to visually name each port.

<DataTemplate x:Key="GroupTemplate3">

  <Border x:Name="myBorder" CornerRadius="5"

          BorderBrush="LightGreen" BorderThickness="10"

          go:Node.LocationElementName="myGroupPanel">

    <go:GroupPanel x:Name="myGroupPanel" Padding="10 5 10 5" Margin="0 20 0 0" >

      <TextBlock x:Name="Label" go:Node.PortId=""

                 Text="{Binding Path=Data.Key}" FontSize="14" Foreground="Navy"

                 go:SpotPanel.Spot="1 0 0 -2" go:SpotPanel.Alignment="1 1" />

      <Rectangle go:SpotPanel.Spot="0 0.25" go:SpotPanel.Alignment="1 0.5"

                 Fill="Blue" Width="10" Height="10" go:Node.PortId="zero" />

      <Rectangle go:SpotPanel.Spot="0 0.50" go:SpotPanel.Alignment="1 0.5"

                 Fill="Blue" Width="10" Height="10" go:Node.PortId="one" />

      <Rectangle go:SpotPanel.Spot="0 0.75" go:SpotPanel.Alignment="1 0.5"

                 Fill="Blue" Width="10" Height="10" go:Node.PortId="two" />

      <Rectangle go:SpotPanel.Spot="1 0.33" go:SpotPanel.Alignment="0 0.5"

                 Fill="Orange" Width="10" Height="10" go:Node.PortId="OutA" />

      <Rectangle go:SpotPanel.Spot="1 0.67" go:SpotPanel.Alignment="0 0.5"

                 Fill="Orange" Width="10" Height="10" go:Node.PortId="OutB" />

    </go:GroupPanel>

  </Border>

</DataTemplate>

 

This diagram was created with the same node data as before but with the following link data:

  model.LinksSource = new ObservableCollection<MyLinkData>() {

    new MyLinkData() { From="Alpha", To="Epsilon", ToPort="two" },

    new MyLinkData() { From="Gamma", To="Delta" },

    new MyLinkData() { From="Epsilon", To="Beta", FromPort="OutA" },

  };

Groups as Independent Containers

The above examples all intend to have each group exactly surround its collection of member nodes plus some padding.  However, there are other scenarios where you want to treat each group as a fixed size box where the user might add or remove items (i.e. nodes) via drag-and-drop.

<DataTemplate x:Key="GroupTemplateFixedSize">

  <StackPanel go:Node.LocationElementName="main"

              go:Part.SelectionElementName="main"

              go:Part.SelectionAdorned="True"

              go:Part.DropOntoBehavior="AddsToGroup">

    <TextBlock Text="{Binding Path=Data.Key}" FontWeight="Bold"

               HorizontalAlignment="Left" />

    <Rectangle x:Name="main" Fill="White" StrokeThickness="3"

               Stroke="{Binding Path=Part.IsDropOntoAccepted,

                                Converter={StaticResource theStrokeChooser}}"

               Width="100" Height="100" />

  </StackPanel>

</DataTemplate>

Note the addition of go:Part.DropOntoBehavior="AddsToGroup".  You can enable “drop onto” behavior by adding this attached property on groups and by also setting DraggingTool.DropOntoEnabled to true:

<go:Diagram Grid.Row="0" . . . >

  <go:Diagram.DraggingTool>

    <go:DraggingTool DropOntoEnabled="True" />

  </go:Diagram.DraggingTool>

</go:Diagram>

This will allow users to drag nodes into and out of this rectangular box.  When the drop occurs, the nodes become members of the group.  That means that copying the group will also copy the members, and that deleting the group will also delete the members.  Dragging a node out of such a group also removes it from that group – copying or deleting the group will have no effect on the dragged node.

To help provide feedback to the user, note the binding of the Rectangle.Stroke on the Part.IsDropOntoAccepted property.  The DraggingTool will temporarily set that Part property during the dragging process if the dragged nodes might be added to that Group.  You can override the DraggingTool.IsValidMember predicate to return false if you do not want a particular node to become a member of a particular group.  For example, in the Planogram sample, IsValidMember is defined to return false when the dragged node is a Rack or a Shelf, to prevent nesting of Racks or Shelves.

The Planogram sample also demonstrates how these groups can be resizable by the user.  Because the template is not using a GroupPanel, there are no inherent limits on where the group appears to be relative to its member nodes.

However, there may be times when you want to use a GroupPanel most of the time, but you still want to support drag-and-drop re-parenting of nodes between groups.  The problem with the use of a GroupPanel is that as the user tries to drag a member node out of a group, the group automatically expands to include its member node.  In this particular case you can use a GroupPanel when you also set its SurroundsMembersAfterDrop property to true.  Basically the auto-sizing behavior of a GroupPanel is temporarily disabled during a move conducted by the DraggingTool.

<DataTemplate x:Key="GroupTemplateAddableRemovable">

  <StackPanel go:Node.LocationElementName="main"

              go:Part.SelectionElementName="main"

              go:Part.SelectionAdorned="True"

              go:Part.DropOntoBehavior="AddsToGroup">

    <TextBlock Text="{Binding Path=Data.Key}" FontWeight="Bold"

               HorizontalAlignment="Left" />

    <Border Background="White" BorderThickness="3" CornerRadius="5"

            BorderBrush="{Binding Path=Part.IsDropOntoAccepted,

                                  Converter={StaticResource theStrokeChooser}}">

      <go:GroupPanel x:Name="main" SurroundsMembersAfterDrop="True"

                     MinWidth="100" MinHeight="100" />

    </Border>

  </StackPanel>

</DataTemplate>

 

Layout

The positioning of FrameworkElements in Nodes is achieved with the standard WPF/Silverlight layout system, primarily the use of various kinds of Panels.

In GoXam diagrams, you can position a node by setting or data-binding in XAML the Node.Location attached property on its root visual element, or by setting programmatically the Node.Location property.  And users can reposition a node by dragging it.

However, there are also some automated means of positioning the nodes.  These are implemented by several DiagramLayout classes, primarily: GridLayout, CircularLayout, TreeLayout, ForceDirectedLayout, and LayeredDigraphLayout.  Any layout can work with any kind of model.

A layout can be associated with a whole diagram by setting the Diagram.Layout property.

<go:Diagram . . .>

  <go:Diagram.Layout>

    <go:TreeLayout . . . />

  </go:Diagram.Layout>

</go:Diagram>

 

A layout can also be associated with a Group by setting the Group.Layout attached property.  If a Group has a layout, that layout will only position the members (nodes and links) of the group, and the Diagram’s layout will not operate on those members but will treat the group as a single node.

Because there may be many layouts present in a diagram, the Diagram.LayoutManager is responsible for managing them, including deciding when they need to run again.  By default there are a number of events that may cause a re-layout.  These cases are specified by the LayoutChange enumeration, such as LayoutChange .NodeAdded or LayoutChange .LinkRemoved.

Each DiagramLayout has a Conditions property that governs which LayoutChanges will cause a re-layout.  The default behavior is to perform another layout when any node, link, or group membership is added or removed, or when a Layout is replaced or when a template is replaced.  If you don’t want a layout to happen when users delete nodes or links, you could say:

    <go:TreeLayout Conditions="NodeAdded LinkAdded" . . . />

 

Then only when the user adds a node or draws a new link (or reconnects an existing one) will a layout automatically occur.

The most commonly set properties on LayoutManager involve animation.  By default the LayoutManager.Animated property is true, so that each layout will cause top-level nodes to move smoothly from their original location to their new one.  (Nodes that are members of groups will move instantly.)  The default animation time is 500 milliseconds.

<go:Diagram . . .>

  <go:Diagram.LayoutManager>

    <go:LayoutManager AnimationTime="1000" />

  </go:Diagram.LayoutManager>

  <go:Diagram.Layout>

    <go:TreeLayout . . . />

  </go:Diagram.Layout>

</go:Diagram>

 

Normally all of the nodes and links in the diagram are laid out by the Diagram.Layout.  You can cause a node or link not to participate in a layout by setting its Part.LayoutId property to “None” on the root element of the node or link template:

    go:Part.LayoutId="None"

Nodes that are not laid out will not be positioned; links that are not laid out will not be routed specially and will not be considered when arranging the connected nodes.

TreeLayout

The simplest layout involves tree structures.  It is very fast and can handle many nodes.

<go:Diagram . . .>

  <go:Diagram.Layout>

    <go:TreeLayout />

  </go:Diagram.Layout>

</go:Diagram>

 

With a model containing node data forming a tree structure, the result might look like:

There are is a lot of customization possible for trees.  Angle controls the general growth direction – it must be 0 (towards the right), 90 (downward), 180 (leftward) or 270 (upward).  Alignment controls how the parent node is positioned relative to its children.

  <go:TreeLayout Angle="90" Alignment="CenterSubtrees" />

 

You can control how closely the layers and the nodes are placed.  For example, you can really pack them close together with:

  <go:TreeLayout LayerSpacing="20" NodeSpacing="0" />

You can have the children of each node be sorted.  By default the TreeLayout.Comparer compares the Node.Text property.  So if the Diagram.NodeTemplate includes:

         go:Part.Text="{Binding Path=Data.Key}"

 

on the root element, and if you specify the TreeLayout.Sorting property:

  <go:TreeLayout Angle="90" Alignment="Start" Sorting="Ascending" />

The set of children for each node is alphabetized.  (In this case that means alphabetical ordering of the English names of the letters of the Greek alphabet.)

If your graph structure is mostly tree-like, but you have a few “extra” links that should be ignored for the purpose of deciding the tree structure, you can set the Part.LayoutId attached property on those links to be “None”.

You can experiment with the TreeLayout properties in the TLayout sample of the demo.

 

ForceDirectedLayout

The ForceDirectedLayout uses forces similar to physical forces to push and pull nodes.  Links are treated as if they were springs of a particular length and stiffness.  Each node has an electrical charge that repels other nodes.

An example of a ForceDirectedLayout:

<go:ForceDirectedLayout DefaultSpringLength="10" DefaultElectricalCharge="50" />

For small nodes that do not have too much connectivity you can use smaller values than the defaults of 50 for the spring length and 150 for the electrical charge.

Unlike the other layouts, ForceDirectedLayout produces incremental results, so running it for longer (i.e. values of ForceDirectedLayout.MaxIterations > 100) may improve the results.

There are a number of properties that control the behavior of the layout.  The ones most commonly set include Conditions and the Default… properties.

You can experiment with the ForceDirectedLayout properties in the FDLayout sample of the demo.

LayeredDigraphLayout

When the nodes of a graph can be naturally organized into layers but the structure is not tree-like, you can use LayeredDigraphLayout.

This layout can handle multiple links coming into a node as well as links that create cycles.  However, it is slower than TreeLayout, and it does not have tree-specific customization features.

As with the other layouts, there are a number of properties that control its behavior.  The ones most commonly set include Direction, LayerSpacing, ColumnSpacing, and Conditions.

You can experiment with the LayeredDigraphLayout properties in the LDLayout sample of the demo.

CircularLayout

The CircularLayout positions all of its nodes in a circular or elliptical pattern.

There are a number of properties that control the behavior of the layout.  These include how the nodes are ordered, how they are spaced, the X radius of the ellipse, the aspect ratio of the ellipse, and the start and sweep angles of the ellipse that are occupied.

You can experiment with the CircularLayout properties in the CLayout sample of the demo.

Selection

Users can typically select and deselect parts by clicking on them or by clicking in the background.  You can programmatically select or deselect a Part by setting its Part.IsSelected property.

The Diagram keeps a collection of selected parts, Diagram.SelectedParts.  It also has a reference to the primary selected part: Diagram.SelectedPart.  In order to show detail information about the primary selection it is natural to bind to Diagram.SelectedPart.  If you only want to bind to the primary selection when it is a Node (and not a Group), bind to Diagram.SelectedNode.  Similarly, you can bind to Diagram.SelectedGroup or Diagram.SelectedLink.

You can limit how many parts are selected by setting Diagram.MaximumSelectionCount.

You can show that a part is selected using either or both of two general techniques: adding Adornments or changing the appearance of some of the elements of the visual tree.

Selection Adornments

It is common to display that a part is selected by having it show a selection Adornment when the part is selected.  That is accomplished by setting the Part.SelectionAdorned attached property to true:

<DataTemplate x:Key="NodeTemplate1">

  <Grid go:Node.SelectionAdorned="True" . . .>

    . . .

  </Grid >

</DataTemplate>

 

 This is the default selection adornment template, which defines what is shown when the part becomes selected:

 

<DataTemplate>  <!-- Silverlight -->

  <Path go:NodePanel.Figure="None" Stroke="DodgerBlue" StrokeThickness="3"

        go:Part.Selectable="False" />

</DataTemplate>

 

<DataTemplate>  <!-- WPF -->

  <go:SelectionHandle Stroke="{x:Static SystemColors.HighlightBrush}"

    StrokeThickness="3" go:Part.Selectable="False" SnapsToDevicePixels="True" />

</DataTemplate>

 

These Adornment shapes automatically take the shape of the FrameworkElement that is the selected part’s SelectionElement.

 

But you can customize the elements that are shown when a part is selected by specifying its SelectionAdornmentTemplate.  For example, you can arrange four triangles to be positioned outside of the adorned element by using a Grid with a SpotPanel in the middle cell:

 

<DataTemplate x:Key="OuterSelectionAdornmentTemplate">

  <Grid go:Node.LocationElementName="Main">

    <Grid.ColumnDefinitions>

      <ColumnDefinition Width="Auto" />

      <ColumnDefinition Width="Auto" />

      <ColumnDefinition Width="Auto" />

    </Grid.ColumnDefinitions>

    <Grid.RowDefinitions>

      <RowDefinition Height="Auto" />

      <RowDefinition Height="Auto" />

      <RowDefinition Height="Auto" />

    </Grid.RowDefinitions>

    <!-- when in an Adornment, automatically sized to the AdornedElement -->

    <go:SpotPanel Grid.Row="1" Grid.Column="1" x:Name="Main"

                  HorizontalAlignment="Center" VerticalAlignment="Center" />

    <!-- demonstrate triangles around the AdornedElement -->

    <!-- in WPF can use plain go:NodeShape;

         in Silverlight must surround it with a go:NodePanel -->

    <go:NodeShape Grid.Row="0" Grid.Column="1" Margin="10"

        HorizontalAlignment="Center" VerticalAlignment="Center"

        Fill="LightGreen" go:NodePanel.Figure="TriangleUp"

        Width="20" Height="20" />

    <go:NodeShape Grid.Row="1" Grid.Column="0" Margin="10"

        HorizontalAlignment="Center" VerticalAlignment="Center"

        Fill="LightGreen" go:NodePanel.Figure="TriangleLeft"

        Width="20" Height="20" />

    <go:NodeShape Grid.Row="1" Grid.Column="2" Margin="10"

        HorizontalAlignment="Center" VerticalAlignment="Center"

        Fill="LightGreen" go:NodePanel.Figure="TriangleRight"

        Width="20" Height="20" />

    <go:NodeShape Grid.Row="2" Grid.Column="1" Margin="10"

        HorizontalAlignment="Center" VerticalAlignment="Center"

        Fill="LightGreen" go:NodePanel.Figure="TriangleDown"

        Width="20" Height="20" />

  </Grid>

</DataTemplate>

 

Here’s what you might see with a node, both unselected and selected using this adornment template:

 

If you want to display some elements within the bounds, more or less, of the adorned element, you can use a SpotPanel in your adornment template:

 

<DataTemplate x:Key="InnerSelectionAdornmentTemplate">

  <!-- automatically sized to the AdornedElement -->

  <go:SpotPanel Grid.Row="1" Grid.Column="1"

                HorizontalAlignment="Center" VerticalAlignment="Center">

    <!-- demonstrate triangles just inside the AdornedElement -->

    <!-- in WPF can use plain go:NodeShape;

         in Silverlight must surround it with a go:NodePanel -->

    <go:NodeShape go:SpotPanel.Spot="MiddleTop"

                  go:SpotPanel.Alignment="MiddleTop"

                  Fill="LightGreen" go:NodePanel.Figure="TriangleUp"

                  Width="10" Height="10" />

    <go:NodeShape go:SpotPanel.Spot="MiddleLeft"

                  go:SpotPanel.Alignment="MiddleLeft"

                  Fill="LightGreen" go:NodePanel.Figure="TriangleLeft"

                  Width="10" Height="10" />

    <go:NodeShape go:SpotPanel.Spot="MiddleRight"

                  go:SpotPanel.Alignment="MiddleRight"

                  Fill="LightGreen" go:NodePanel.Figure="TriangleRight"

                  Width="10" Height="10" />

    <go:NodeShape go:SpotPanel.Spot="MiddleBottom"

                  go:SpotPanel.Alignment="MiddleBottom"

                  Fill="LightGreen" go:NodePanel.Figure="TriangleDown"

                  Width="10" Height="10" />

  </go:SpotPanel>

</DataTemplate>

 

Here’s what you might see with a node, both unselected and selected using this adornment template:

Selection Appearance Changes

However one can also modify the appearance of a selected part.  Basically you can bind properties of your node to values that depend on the Part.IsSelected property.  For example, you could define a converter that returned a red brush if the input value is true or that returned a normal brush if the value is false.

  <go:BooleanBrushConverter x:Key="theSelectedBrushConverter"

                            TrueColor="Red">

    <go:BooleanBrushConverter.FalseBrush>

      <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">

        <GradientStop Color="White" Offset="0.0" />

        <GradientStop Color="LightBlue" Offset="1.0" />

      </LinearGradientBrush>

    </go:BooleanBrushConverter.FalseBrush>

  </go:BooleanBrushConverter>

 

In this case, the normal brush is a linear gradient.  Now we can bind the Background of a panel to the brush returned by this converter based on the value of the part’s IsSelected property:

<DataTemplate x:Key="NodeTemplate2">

  <!-- note that the binding path is Path=Node.xxx not Path=Data.xxx -->

  <Grid Background="{Binding Path=Node.IsSelected,

                        Converter={StaticResource theSelectedBrushConverter}}">

    . . .

  </Grid>

</DataTemplate>

 

Note how the binding goes to the Part.IsSelected property, not to Data.IsSelected, because there is no IsSelected property on the data class.

As a concrete example:

<DataTemplate x:Key="NodeTemplate3">

  <Border BorderBrush="Gray" BorderThickness="2" CornerRadius="5"

          Background="{Binding Path=Part.IsSelected,

                          Converter={StaticResource theSelectedBrushConverter}}"

          go:Node.Location="{Binding Path=Data.Location, Mode=TwoWay}">

    <Border.Effect>

      <DropShadowEffect />

    </Border.Effect>

    <StackPanel Orientation="Vertical">

      <go:NodePanel HorizontalAlignment="Center">

        <Path go:NodePanel.Figure="Arrow" Width="25" Height="25"

              Fill="{Binding Path=Data.Color,

                        Converter={StaticResource theStringBrushConverter}}" />

      </go:NodePanel>

      <TextBlock x:Name="Text" Text="{Binding Path=Data.Key}"

                 HorizontalAlignment="Center" />

    </StackPanel>

  </Border>

</DataTemplate>

 

So when you select “Delta” and “Beta”, they appear as follows:

If you want to execute your own code when the selection changes, you can handle the Diagram.SelectionChanged event.

Content Alignment and Stretch

The DiagramPanel is the panel that holds all of the Layers that together hold all of the Nodes and Links.  The DiagramPanel is what supports scrolling around and zooming into the diagram.  You can scroll programmatically by setting DiagramPanel.Position and you can zoom in or out programmatically by setting DiagramPanel.Scale.  The user can scroll using the scrollbars or the PanningTool, and the user can zoom in or out using Control-Mouse-Wheel.

The DiagramPanel.DiagramBounds property indicates the total extent of all of the nodes and links.  This value is automatically updated as nodes are added or removed.  If you do not want the DiagramBounds to always reflect the sizes and locations of all of the nodes and links, you can set the FixedBounds property.  However, if there are any nodes that are located beyond the FixedBounds, it is possible that one cannot scroll the diagram to see them.

The DiagramPanel has four properties that you will find useful in controlling what is seen and where.

The HorizontalContentAlignment and VerticalContentAlignment properties determine how the diagram is aligned in the viewport shown by the DiagramPanel, when the DiagramBounds at the current Scale can fit in the viewport.  If you want to keep everything centered in the diagram, set both of these properties to “Center”.  With the standard ControlTemplate you can set these properties on the Diagram:

  <go:Diagram x:Name="myDiagram"

      HorizontalContentAlignment="Center" VerticalContentAlignment="Center" />

 

Caution: the default values for these two Control alignment properties differ between WPF and Silverlight.

Here’s some random content that fits in the DiagramPanel at the current scale:

Resize the Diagram to be much smaller, and it automatically keeps the center of the diagram centered in the DiagramPanel and shows the scrollbars.

Or, leave the Diagram size the same, but zoom in with either Control-plus (WPF) or Keypad-plus (Silverlight), and it also keeps the center point and shows the scrollbars.

The user can also zoom in at a particular mouse point by using Control-wheel.

If you don’t want the diagram contents to be aligned continuously, use values of HorizontalAlignment.Stretch and/or VerticalAlignment.Stretch.  In this context the meaning of those enumeration values is somewhat different than normal, because the diagram never “stretches” the content.  It is common for Diagrams to use values of …Alignment.Stretch where the user is manually constructing the graph by drag-and-drop.

If you want the scale to change automatically as the Diagram is resized, use the DiagramPanel.Stretch property.  (This is not an alignment property, but a property to control the scale of the diagram contents.)

  <go:Diagram x:Name="myDiagram" Stretch="Uniform"

      HorizontalContentAlignment="Center" VerticalContentAlignment="Center" />

 

This will automatically rescale the diagram so that the whole diagram’s bounds fits.  You can also use the value of StretchPolicy.UniformToFill, which rescales the diagram so that the narrower or the shorter distance fills up the whole area, using a scrollbar to scroll the other dimension (taller or wider).  The default value is StretchPolicy.Unstretched, which does not change the DiagramPanel.Scale.

When there is extra space left over, the contents are centered, according to the two …Alignment properties.

Finally, the DiagramPanel.Padding property adds a little space to the DiagramPanel.DiagramBounds, to avoid having the edge of the DiagramPanel come too close to the contents.  Because the default value of Control.Padding is a Thickness of zero on all four sides, we recommend a larger value so that the edges of Nodes will not appear to bump against the edges of the Diagram.

As the default ControlTemplate above shows, the four DiagramPanel properties (HorizontalContentAlignment, VerticalContentAlignment, Stretch, and Padding) normally get their values from the Diagram, via TemplateBindings.

If you want to set some properties on a DiagramPanel or call its methods, be sure to do so only after the Diagram.Template has been applied (i.e. expanded and copied).  Until the ControlTemplate has been applied, the value of Diagram.Panel will be null.

For example if you want to establish an event handler on a Diagram’s Panel, you can do so in a Diagram.TemplateApplied event handler:

  // wait until the Diagram's Panel exists before establishing its event handler

  myDiagram.TemplateApplied += (s, e) => {

    myDiagram.Panel.ViewportBoundsChanged += Panel_ViewportBoundsChanged;

  };

 

Initial Positioning and Scaling

The aforementioned DiagramPanel properties control the scale (Stretch) and position (HorizontalContentAlignment and VerticalContentAlignment) all the time.  However, it is common to want to set the scale and/or position of the diagram after the first layout has positioned all of the nodes, but not thereafter.  Towards that end you can set the Diagram.InitialScale and/or Diagram.InitialPosition properties.

  <go:Diagram InitialPosition="0 0" . . . >

 

But there are additional Diagram properties that are convenient for setting the initial scale and/or position of the DiagramPanel.  You can set the Diagram.InitialStretch property to perform a one-time rescaling.  For example:

  <go:Diagram x:Name="myDiagram" InitialStretch="Uniform"

      HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" />

 

This will perform an initial layout of the contents of the diagram, compute the new DiagramPanel.DiagramBounds, and rescale it and position it so that everything fits.  Afterwards, the user is free to zoom in or out and to scroll around, as needed.

Two related Diagram properties help position the diagram in the panel based on the diagram’s bounds: InitialDiagramBoundsSpot and InitialPanelSpot.  The former property specifies which spot of the diagram contents should be positioned, and the latter property specifies where in the DiagramPanel it should be positioned.  For example:

  <go:Diagram x:Name="myDiagram"

      InitialDiagramBoundsSpot="MiddleTop" InitialPanelSpot="MiddleTop"

      HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" />

 

This will position the middle-top point of the laid-out diagram at the middle-top point of the panel.  You will need to be careful not to choose combinations of values that result in nothing being visible.

DiagramPanel implements the IScrollInfo interface, so you can use those methods and properties to scroll programmatically.  The DiagramPanel.MakeVisible method is useful to scroll the view if the given Part is not somewhere in the viewport.  The DiagramPanel.CenterPart method is useful to try to center a given Part in the viewport, although the panel might not be able to scroll that far, especially if the content alignment properties are not “Stretch”.

Tools

For flexibility and simplicity, all mouse input is redirected by the Diagram to go the diagram’s CurrentTool.  By default the CurrentTool is an instance of ToolManager, which is responsible for finding another tool that is ready to run and then making it the new CurrentTool.  This causes the new tool to process mouse events and keyboard events until the tool decides it is finished, at which time the diagram’s current tool reverts to the default ToolManager tool.

There are a number of predefined tools that each Diagram has – they are accessible as diagram properties and can be replaced by setting those properties.  The name of the tool class is the same as the name of the diagram property.

Some tools want to run when a mouse-down occurs.  These tools include:

·         RelinkingTool, for reconnecting an existing Link

·         LinkReshapingTool, for changing the route of a Link

·         ResizingTool, for resizing a Node or an element within a Node

·         RotatingTool, for rotating a Node or an element within a Node

Some tools want to run when a mouse-move occurs, after a mouse-down.  These tools include:

·         LinkingTool, for drawing a new Link

·         DraggingTool, for moving or copying selected Parts

·         DragSelectingTool, for rubber-band selection of some Parts within a rectangular area

·         PanningTool, for panning/scrolling the diagram

Some tools only want to run upon a mouse-up event, after a mouse-down.  These tools include:

·         TextEditingTool, for in-place editing of TextBlocks in selected Parts

·         ClickCreatingTool, for inserting a new Node where the user clicked

·         ClickSelectingTool, for selecting or de-selecting a Part

Finally, there are some tools, such as DragZoomingTool, that are not normally invoked by the mouse, but can be started explicitly by setting Diagram.CurrentTool.

To change the behavior of a tool, you can set its properties in XAML and replace the corresponding Diagram property.  For example, to cause control-drag copies to copy the whole effective selection instead of only the selected parts:

  <go:Diagram . . . >

    <go:Diagram.DraggingTool>

      <gotool:DraggingTool CopiesEffectiveCollection="True" />

    </go:Diagram.DraggingTool>

  </go:Diagram>

 

To remove a tool, set it to null.  For example, to remove the background rubber-band selection tool:

  <go:Diagram DragSelectingTool="{x:Null}" . . . />

 

Removing this tool also allows the PanningTool to be able to run, because by default the DragSelectingTool takes precedence.

As another example, it turns out that the ClickCreatingTool is normally never eligible to run because it does not have a value for ClickCreatingTool.PrototypeData.  You might find it suitable to enable it by setting that property:

 

  <go:Diagram . . . >

    <go:Diagram.ClickCreatingTool>

      <go:ClickCreatingTool>

        <go:ClickCreatingTool.PrototypeData>

          <local:MyData Key="Lambda" Color="Fuchsia" />

        </go:ClickCreatingTool.PrototypeData>

      </go:ClickCreatingTool>

    </go:Diagram.ClickCreatingTool>

  </go:Diagram>

 

Caution: do not define a tool in XAML as the value of a Style Setter, because only one instance of each tool is ever created, and would thus be shared by all diagrams affected by that style.  A DiagramTool must not be shared by different Diagrams.

Events

All of the predefined tools that modify the model do so within a model transaction, and they also raise an event.

Tool

Event

ClickCreatingTool

NodeCreatedEvent

DraggingTool

SelectionMovedEvent or

SelectionCopiedEvent or

ExternalObjectsDroppedEvent

LinkingTool

LinkDrawnEvent

RelinkingTool

LinkRelinkedEvent

LinkReshapingTool

LinkReshapedEvent

ResizingTool

NodeResizedEvent

RotatingTool

NodeRotatedEvent

TextEditingTool

TextEditedEvent

The other predefined tools do not have model-changing side-effects.

There are a number of events raised by commands, implemented by the CommandHandler.

Command

Event

Delete

Cut

SelectionDeletingEvent and

SelectionDeletedEvent

Paste