Controlling element sizes when zooming


I have an application where I want to be able to control the size of some of the elements displayed on the Diagram.
Here's what I'd like to achieve...
I have a diagram with a background and a number of elements drawn on it.
When the user zooms into/out of the diagram, I want the elements to be positioned correctly (and the background to zoom in/out). However, I want the elements to stay the same size on-screen.
Currently, when I zoom in/out the elements get larger/smaller in line with the scale.
Is it possible to to achieve what I'm looking for?

You’ll want to either allow the DiagramPanel.Scale to change normally for a zoom and compensate in the size of your nodes, or you’ll want to not have the DiagramPanel.Scale change at all but explicitly have your nodes move closer/farther apart and change the scale of your background.

Are you using Silverlight or WPF?


I'm using WPF.
I'm currently implementing the same sort of thing using GoDiagram (detecting the scale being changed and then iterating through all of the nodes on the diagram and 'reverse scaling' them so they appear the correct size).
However, I was wondering if there was some way using the WPF version that this could be done in a more elegant/automatic way.

Well, that way should work. Furthermore I believe you can literally “reverse scale” the contents of the Nodes by applying a ScaleTransform to the root element of the node (not to the Node itself), instead of setting the Width and Height of the node.

However, I haven’t tried this to see if it works, particularly if you allow nodes to be resized or rotated interactively.

I’m working on a sample that demonstrates one way of doing what you want. It replaces the standard zooming mechanism of modifying the DiagramPanel.Scale with a separate mechanism that effectively changes the Node.Location of all of the nodes.

Maybe later today…

OK, here is a new sample in the next couple of replies. The two files are Zoom.xaml and Zoom.xaml.cs.

The basic idea is that the “nominal” location for each node is in the node data, as the Location property. Normally there is a data binding to get the location information from the data to the Node:

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

However, in this sample, we have added a Binding data Converter:

 go:Node.Location="{Binding Path=Data.Location, Mode=TwoWay, Converter={StaticResource myDiagramSpacedLocationConverter}}"

This SpacedLocationConverter has a Space dependency property that scales up the Node.Location value. Each Diagram has to have its own SpacedLocationConverter. That also means that you won’t be able to share the same node template between different diagrams.

There is also a custom CommandHandler that reimplements the control-mouse-wheel and zoom keyboard commands to change the SpacedLocationConverter.Space property instead of changing the DiagramPanel.Scale property.

The XAML is for WPF, but naturally there aren’t many differences for Silverlight.
The CS code is precisely the same for both WPF and for Silverlight.

This was developed using version 1.1. I don’t know if this will work with 1.0, but off hand I don’t know of any reasons why it wouldn’t.

<local:SpacedLocationConverter x:Key=“myDiagramSpacedLocationConverter” />

<DataTemplate x:Key="StateTemplate">
  <go:NodePanel Sizing="Auto"
                go:Node.Location="{Binding Path=Data.Location, Mode=TwoWay,
                     Converter={StaticResource myDiagramSpacedLocationConverter}}"
    <go:NodeShape go:NodePanel.Figure="RoundedRectangle"
                  Fill="LightBlue" Stroke="Black" StrokeThickness="1"
                  go:Node.LinkableFrom="True" go:Node.LinkableTo="True" />
    <TextBlock Text="{Binding Path=Data.Key}" Margin="10"
               HorizontalAlignment="Center" VerticalAlignment="Center" />


/* Copyright © Northwoods Software Corporation, 2008-2010. All Rights Reserved. */

using System;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using Northwoods.GoXam;
using Northwoods.GoXam.Model;

namespace Zoom {
public partial class Zoom : UserControl {
public Zoom() {

  // create the diagram's data model
  var model = new GraphLinksModel<State, String, String, Transition>();
  model.NodesSource = new ObservableCollection<State>() {
    new State() { Key="S1" },
    new State() { Key="S2" },
    new State() { Key="S3" },
  model.LinksSource = new ObservableCollection<Transition>() {
    new Transition() { From="S1", To="S2" },
    new Transition() { From="S1", To="S3" },
  model.Modifiable = true;  // let the user modify the graph
  model.HasUndoManager = true;  // support undo/redo
  myDiagram.Model = model;

  // initialize the converter to know about the Diagram
  var conv = Diagram.FindResource<SpacedLocationConverter>(this, "myDiagramSpacedLocationConverter");
  if (conv != null) conv.Diagram = myDiagram;


// This redefines mouse wheel and keyboard zoom commands to change
// the SpacedLocationConverter.Space, rather than the DiagramPanel.Scale.
public class SpaceZoomCommandHandler : CommandHandler {
// This needs to be initialized!
public SpacedLocationConverter Conv { get; set; }

public override void DecreaseZoom(object param) {
  this.Conv.Space = Math.Max(0.1, this.Conv.Space / 1.05);
public override void IncreaseZoom(object param) {
  this.Conv.Space = Math.Min(10.0, this.Conv.Space * 1.05);
public override void Zoom(object param) {
  this.Conv.Space = (param is double ? (double)param : 1.0);


// This is used in the Node.Location binding in the NodeTemplate.
// There has to be one of these for each Diagram –
// thus the NodeTemplate cannot be shared by different Diagrams.
[ValueConversion(typeof(Point), typeof(Point))]
public class SpacedLocationConverter : DependencyObject, IValueConverter {
// This needs to be initialized!
public Diagram Diagram { get; set; }

static SpacedLocationConverter() {
  SpaceProperty = DependencyProperty.Register("Space", typeof(double), typeof(SpacedLocationConverter),
    new PropertyMetadata(1.0, OnSpaceChanged));

private static readonly DependencyProperty SpaceProperty;
public double Space {
  get { return (double)GetValue(SpaceProperty); }
  set { SetValue(SpaceProperty, value); }
private static void OnSpaceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
  var conv = (SpacedLocationConverter)d;
  if ((double)e.NewValue <= 0.01) {  // don't allow negative or very small value
    conv.Space = 1;
  } else if (conv.Diagram != null) {
    foreach (Node n in conv.Diagram.Nodes) {
      // re-evaluate the binding to get the updated Location for the Node
      FrameworkElement root = n.VisualElement;
      if (root != null) {
        BindingExpression expr = root.GetBindingExpression(Node.LocationProperty);
        if (expr != null) root.SetBinding(Node.LocationProperty, expr.ParentBinding);

public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
  Point p = (Point)value;
  return new Point(p.X*this.Space, -p.Y*this.Space);

public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
  Point p = (Point)value;
  return new Point(p.X/this.Space, -p.Y/this.Space);


// the data for each node; the predefined data class is enough for this sample
public class State : GraphLinksModelNodeData {
public State() {
this.Key = “State”;

// the data for each link
public class Transition : GraphLinksModelLinkData<String, String> { }


Thanks for the sample code. It looks like it will help me achieve the effect I'm looking for.
However, when I build my project using the above code, the overrides of 'IncreaseZoom', 'DecreaseZoom' and 'Zoom' in the custom CommandHandler never seem to get called (although I can still zoom in/out of the diagram using the mouse wheel and Cntrl).
If I temporarily add some other overrides to the class (e.g. SelectAll), these do get called.
I've tried doing this in a very simple project (creating a custom CommandHandler and overriding the IncreaseZoom method) and that doesn't work either.
I wondered if you have any ideas why the zoom related overrides aren't being called?

Are you using GoXam 1.0? The change in 1.1 is that the control-mouse-wheel events go through the CommandHandler commands for zooming.

Sorry, I forgot about that. If you want this code to work as-is in 1.0, you’ll need to define a replacement ToolManager that overrides StandardMouseWheel to do what you want.