GoDiagram Animation

GoDiagram offers several built-in animations, enabled by default, as well as the ability to create arbitrary animations.

The Diagram.AnimationManager handles animations within a Diagram. The AnimationManager automatically sets up and dispatches default animations, and has properties to customize and disable them. Custom animations are possible by creating instances of Animation or AnimationTrigger.

This introduction page details the different classes used for GoDiagram animation. To see more demonstrations of custom animations, visit the Custom Animations sample.

Default Animations

By default, the AnimationManager creates and runs several animations for its Diagram using a single instance of Animation, the AnimationManager.DefaultAnimation. These animations occur on various commands, by the Diagram.Model setter, and upon layouts. Unlike other Animations, they will be stopped if a new transaction is started during animation.

GoDiagram will begin an animation automatically for these reasons:

Invoked by CommandHandler:

Invoked by Diagram:

Invoked by AnimationTriggers, if any are declared:

The above quoted names are strings passed to AnimationManager.CanStart This method can be overridden to return false if you wish to stop specific automatic animations.

Default Initial Animation

The default initial animation fades the diagram upwards into view. To control the initial animation behavior, there exists AnimationManager.InitialAnimationStyle, which is set to AnimationManager,Default by default, but can be set to AnimationManager,AnimateLocations to animate locations from the origin. You can also set this property to AnimationManager,None and define your own initial animation using the Diagram.InitialAnimationStarting DiagramEvent.

Here is an example with buttons which set AnimationManager.InitialAnimationStyle to the three different values, then reload the Diagram. A fourth button illustrates how one might use the Diagram.InitialAnimationStarting DiagramEvent to make a custom "zoom in" animation.


  diagram.NodeTemplate =
    new Node("Auto")
      .Add(
        new Shape("RoundedRectangle") { StrokeWidth = 0, Fill = "lightblue" },
        new TextBlock { Margin = 8, Font = new Font("Segoe UI", 14, FontWeight.Bold), Stroke = "#333" }
          .Bind("Text", "Key")
      );

  diagram.Model = new MyModel {
    NodeDataSource = new List {
      new NodeData { Key = "Alpha" },
      new NodeData { Key = "Beta" },
      new NodeData { Key = "Gamma" },
      new NodeData { Key = "Delta" }
    }
  };

  // only needed for this demonstration, this flag is used to stop
  // the InitialAnimationStarting listener when other buttons are pressed
  custom = false;

  defaultBtn.Click += (s, e) => {
    custom = false;
    diagram.AnimationManager.InitialAnimationStyle = AnimationStyle.Default;
    diagram.Model = MyModel.FromJson(diagram.Model.ToJson());
  };

  animateLocationsBtn.Click += (s, e) => {
    custom = false;
    diagram.AnimationManager.InitialAnimationStyle = AnimationStyle.AnimateLocations;
    diagram.Model = MyModel.FromJson(diagram.Model.ToJson());
  };

  noneBtn.Click += (s, e) => {
    custom = false;
    diagram.AnimationManager.InitialAnimationStyle = AnimationStyle.None;
    diagram.Model = MyModel.FromJson(diagram.Model.ToJson());
  };

  customBtn.Click += (s, e) => {
    custom = true;
    diagram.AnimationManager.InitialAnimationStyle = AnimationStyle.None;
    // Customer listener zooms-in the Diagram on load:
    diagram.InitialAnimationStarting += (s, e) => {
      var animation = (e.Subject as AnimationManager).DefaultAnimation;
      if (custom == false) {
        // a different button was pressed, restore default values on the default animation:
        animation.Easing = Animation.EaseInOutQuad;
        animation.Duration = -1;
        return;
      }
      animation.Easing = Animation.EaseOutExpo;
      animation.Duration = 1500;
      animation.Add(e.Diagram, "Scale", 0.1, 1);
      animation.Add(e.Diagram, "Opacity", 0, 1);
    };
    diagram.Model = MyModel.FromJson(diagram.Model.ToJson());
  };

Limitations of Default Animations

The AnimationManager can be turned off by setting AnimationManager.IsEnabled to false. Specific default animations can be turned off or modified by overriding AnimationManager.CanStart and potentially returning false.

The default animation will be stopped if a new transaction begins during the animation. The same is not true of other Animations, which are not stopped by new transactions, and can continue indefinitely.

Animatable Properties

By default, AnimationTriggers and Animations can animate these properties of GraphObjects:

Additionally Animations (but not AnimationTriggers) can animate these properties of Diagram:

It is possible to animate other properties if they are defined by the programmer -- see the section "Custom Animation Effects" below.

The AnimationTrigger Class

An AnimationTrigger is used to declare GraphObject properties to animate when their value has changed. When a trigger is defined, changes to the target property will animate from the old value to the new value. In templates, triggers are defined in a similar fashion to Bindings:


// In this shape definition, two triggers are defined on a Shape.
// These will cause all changes to Shape.Stroke and Shape.Fill to animate
// from their old values to their new values.
new Shape("Rectangle")
  { StrokeWidth = 12, Stroke = "black", Fill = "white" }
  .Trigger("Stroke")
  .Trigger("Fill")

Here is an example, with a button that sets the Shape's Stroke and Fill to new random values:


  diagram.NodeTemplate =
    new Node()
      .Add(
        new Shape("Rectangle") { StrokeWidth = 12, Stroke = "black", Fill = "white" }
          .Trigger("Stroke")
          .Trigger("Fill")
      );

  diagram.Model = new MyModel {
    NodeDataSource = new List { new NodeData { Key = "Alpha" } }
  };

  colorsBtn.Click += (s, e) => {
    diagram.Commit((d) => {
      var node = d.Nodes.FirstOrDefault();
      var shape = node.Elt(0) as Shape;
      shape.Stroke = Brush.RandomColor();
      shape.Fill = Brush.RandomColor();
    });
  };

AnimationTriggers can invoke an animation immediately, starting a new animation with each property of each GraphObject that has been modified, or they can (much more efficiently) be bundled together into the default animation (AnimationManager.DefaultAnimation) and begin at the end of the next transaction. These behaviors can be set with AnimationTrigger.StartCondition by the values StartCondition.Immediate and StartCondition.Bundled, respectively. The default value, StartCondition.Default, attempts to infer which is best. It will start immediately if there is no ongoing transaction or if Diagram.SkipsUndoManager is true.

AnimationTriggers are only definable in templates, on GraphObjects, and cannot be used on RowColumnDefinitions or Diagrams.

The Animation Class

General animation of GraphObject and Diagram properties is possible by creating one or more instances of the Animation class.


  var animation = new Animation();
  // Animate the node's angle from its current value to a random value between 0 and 180 degrees
  animation.Add(node, "Angle", node.Angle, rand.Next(180));
  animation.Duration = 1000; // Animate over 1 second, instead of the default 600 milliseconds
  animation.Start(); // starts the animation immediately

Animation.Add is used to specify which objects should animate, which properties, and their starting and ending values:


  animation.Add(GraphObjectOrDiagram, "EffectName", StartingValue, EndingValue);

Here's the above animation in an example, where each node is animated by a button. Note carefully that each node is added to the same animation. The same effect would be had with one animation per node, but it is always more efficient to group the properties you are animating into a single animation, if possible (for instance, it is possible if they are all going to start at the same time and have the same duration).


  diagram.NodeTemplate =
  new Node("Spot") { LocationSpot = Spot.Center }
    .Bind("Angle")
    .Add(
      new Shape("Diamond") { StrokeWidth = 0, Width = 75, Height = 75 }
        .Bind("Fill", "Color"),
      new TextBlock { Margin = 8, Font = new Font("Segoe UI", 12, FontWeight.Bold) }
        .Bind("Text", "Key")
    );

diagram.Model = new MyModel {
  NodeDataSource = new List {
    new NodeData { Key = "Alpha", Color = "lightblue" },
    new NodeData { Key = "Beta", Color = "orange" },
    new NodeData { Key = "Gamma", Color = "lightgreen" },
    new NodeData { Key = "Delta", Color = "pink", Angle = 45 }
  },
  LinkDataSource = new List {
    new LinkData { From = "Alpha", To = "Beta" },
    new LinkData { From = "Gamma", To = "Delta" }
  }
};

anglesBtn.Click += (s, e) => {
  var rand = new Random();
  var animation = new Animation();
  foreach (var node in diagram.Nodes) {
    // Animate the node's angle from its current value to a random value between 0 and 150 degrees
    animation.Add(node, "Angle", node.Angle, rand.Next(150));
  }
  animation.Duration = 1000; // Animate over 1 second, instead of the default 600 milliseconds
  animation.Start(); // starts the animation immediately
};

Animating the Diagram is possible by passing it as the object to be animated:


  animation.Add(myDiagram, "Position", myDiagram.Position, myDiagram.Position.Offset(200, 15));
  ...
  animation.Add(myDiagram, "Scale", myDiagram.Scale, 0.2);

Animations can also be reversed, as is common with animations that are intended to be cosmetic in nature, by setting Animation.Reversible to true. This doubles the effective duration of the Animation.

Below are several example Animations, all with Animation.Reversible set to true. The first animates Nodes, the other three animate Diagram position and scale.


  diagram.NodeTemplate =
    new Node("Spot") { LocationSpot = Spot.Center }
      .Bind("Angle")
      .Add(
        new Shape("Diamond") { StrokeWidth = 0, Width = 75, Height = 75 }
          .Bind("Fill", "Color"),
        new TextBlock { Margin = 8, Font = new Font("Segoe UI", 12, FontWeight.Bold) }
          .Bind("Text", "Key")
      );

  diagram.Model = new MyModel {
    NodeDataSource = new List {
      new NodeData { Key = "Alpha", Color = "lightblue" },
      new NodeData { Key = "Beta", Color = "orange" },
      new NodeData { Key = "Gamma", Color = "lightgreen" },
      new NodeData { Key = "Delta", Color = "pink", Angle = 45 }
    },
    LinkDataSource = new List {
      new LinkData { From = "Alpha", To = "Beta" },
      new LinkData { From = "Gamma", To = "Delta" }
    }
  };

  EventHandler animate(Action f) {
    return new EventHandler((s, e) => {
      // Stop any currently running animations
      diagram.AnimationManager.StopAnimation(true);

      var animation = new Animation();
      animation.Reversible = true; // reverse the animation at the end, doubling its total time
      f(animation);  // initialize the Animation
      animation.Start(); // start the animation immediately
    });
  };

  anglesReverseBtn.Click += animate((animation) => {
    var rand = new Random();
    foreach (var node in diagram.Nodes) {
      // Animate the node's angle from its current value to a random value between 0 and 90 degrees
      animation.Add(node, "Angle", node.Angle, rand.Next(90));
    }
  });

  diagramPosBtn.Click += animate((animation) => {
    // shift the diagram contents towards the right and then back
    animation.Add(diagram, "Position", diagram.Position, diagram.Position.Offset(50, 15));
    animation.Duration = 700;
  });

  zoomOutBtn.Click += animate((animation) => {
    animation.Add(diagram, "Scale", diagram.Scale, 0.2);
  });

  zoomInBtn.Click += animate((animation) => {
    animation.Add(diagram, "Scale", diagram.Scale, 4);
  });

Without the call to AnimationManager.StopAnimation to protect against rapid button clicks, you would notice that if you clicked Zoom Out, and then during the animation clicked the same button again, the Diagram's scale would not return to its initial value of 1.0. This is because the Animation animates from the current Diagram scale value, to its final value, and back again, but the current value is also what's being changed due to the ongoing animation.

Animation Examples

To see more examples of custom animations, visit the Custom Animations sample. It demonstrates a number of Node creation/deletion animations, linking animations, and more.