Changing a Diagram's Layout at runtime

Hi

This is for GoXam Silverlight 2.0.2.5

This post contains two questions:

  1. What is the proper way to change/swap-out a Diagram’s current layout at runtime?

  2. When swapping out the layout of a Diagram that contains groups/sub-graphs, the groups don’t seem to layout correctly.

  3. What is the proper way to change/swap-out a Diagram’s current layout at runtime?


I have a requirement to change an existing Diagram’s layout at runtime so that the user can decide for themselves which layout they wish to use for building or modifying a diagram. For eg. if the current Layout for a Diagram is a DiagramLayout object, I might swap it out with a TreeLayout object. Currently, I’m changing layout using the following code:

Diagram.Layout = _layoutProvider.GetDiagramLayout(layoutType.Value);
Diagram.StartTransaction(“LayoutChanged”);

Diagram.PartManager.RebuildLinkElements();
Diagram.LayoutManager.LayoutDiagram();

Diagram.CommitTransaction(“LayoutChanged”);

where _layoutProvider is an object that constructs a diagram layout based on some user input. My Diagram is using a GraphLinksModel and I find that I need to call Diagram.PartManager.RebuildLinkElements() when switching back and forth between layouts or else the links don’t behave as expected.

My question is, is the code above the proper way to swap out a Diagram’s Layout at runtime?

  1. When swapping out the layout of a Diagram that contains groups/sub-graphs, the groups don’t seem to layout correctly.

Imagine I have a diagram that looks like the following:

The Diagram’s initial layout is a DiagramLayout object.

I then swap out the layout with a TreeLayout object with an Angle of 0 degrees, producing a tree layout of left-to-right:

This all works as expected. However, imagine I have a diagram containing a group as follows:

I then swap out the layout with a TreeLayout as before. However, this produces an unexpected result:

I would expect the layout to look as follows:

Am I doing something wrong?

That DataTemplate for the Group is as follows:

<go:SpotPanel go:Node.LocationElementName="GroupPanel" go:Node.Location="{Binding Data.Location, Mode=TwoWay}" go:Node.LinkableTo="False" go:Node.LinkableFrom="False" go:Node.LinkableSelfNode="False" go:Node.LinkableDuplicates="False" go:Part.SelectionAdorned="true" go:Part.SelectionElementName="GroupBorder" go:Part.LayerName="{Binding Data.LayerID}" go:Part.LayoutId="{Binding Data.LayoutID}" go:Group.IsSubGraphExpanded="{Binding Data.IsSubGraphExpanded}" go:Node.PortId="" go:Part.Rotatable="False" go:Part.Resizable="False"
        >
        
        <Border x:Name="GroupBorder"
            BorderBrush="Black" 
            BorderThickness="1"
            CornerRadius="5"
            Padding="6"                                   
            >
            <go:Group.Layout>
                <go:DiagramLayout Conditions="All" />
            </go:Group.Layout>
            <StackPanel>
                <TextBlock>
                    <Run Text="{Binding Data.Text}" />(<Run Text="{Binding Data.Location}" />)
                </TextBlock>
                <go:GroupPanel x:Name="GroupPanel" go:Node.LinkableFrom="False" go:Node.LinkableTo="False"/>
            </StackPanel>
        </Border>
        
        <Rectangle Fill="Transparent" Width="6" Height="6"
             go:SpotPanel.Spot="MiddleLeft" go:SpotPanel.Alignment="MiddleLeft"
             go:Node.PortId="" go:Node.LinkableFrom="True" go:Node.LinkableTo="True" Cursor="Hand"
              go:Node.LinkableDuplicates="False" />

        <Rectangle Fill="Transparent" Width="6" Height="6"
             go:SpotPanel.Spot="MiddleTop" go:SpotPanel.Alignment="MiddleTop"
             go:Node.PortId="" go:Node.LinkableFrom="True" go:Node.LinkableTo="True" Cursor="Hand"
             go:Node.LinkableDuplicates="False" />

        <Rectangle Fill="Transparent" Width="6" Height="6"
             go:SpotPanel.Spot="MiddleRight" go:SpotPanel.Alignment="MiddleRight"
             go:Node.PortId="" go:Node.LinkableFrom="True" go:Node.LinkableTo="True" Cursor="Hand"
             go:Node.LinkableDuplicates="False" />

        <Rectangle Fill="Transparent" Width="6" Height="6"
             go:SpotPanel.Spot="MiddleBottom" go:SpotPanel.Alignment="MiddleBottom"
             go:Node.PortId="" go:Node.LinkableFrom="True" go:Node.LinkableTo="True" Cursor="Hand"
            go:Node.LinkableDuplicates="False"  />           
        

    </go:SpotPanel>
    
</DataTemplate> 

Thanks
Justin

The Fishbone sample shows one way to dynamically change the layout. There are three buttons, each causing the Diagram to use a different layout.

I suspect that the reason you think you need to call RebuildLinkElements() is because the old layout may have modified Link properties such as .Route.FromSpot and .Route.ToSpot, but the new layout does not do so, causing the old values to persist and thus mess up the appearance of the diagram. So it really depends on the details of what the layouts do.

As it so happens, the Fishbone sample also would have that problem, but it needs to change the LinkTemplate anyway, which effectively calls RebuildLinkElements() automatically. You also shouldn’t need to call LayoutDiagram() at all.

Are you also changing the Group.Layout of the groups in your diagram? Certainly the DiagramLayout that you supply as the Group.Layout in the GroupTemplate XAML won’t get you what you want. Much as with changing the Diagram.Layout, you can change it programmatically. Or you could replace the Diagram.GroupTemplate, at the expense of rebuilding everything.

Hi Walter

Your comments regarding changing a Diagram’s layout and the reason to call RebuildLinkElements are correct.

Regarding the layout of the Groups:


“Are you also changing the Group.Layout of the groups in your
diagram? Certainly the DiagramLayout that you supply as the Group.Layout
in the GroupTemplate XAML won’t get you what you want. Much as with
changing the Diagram.Layout, you can change
it programmatically. Or you could replace the Diagram.GroupTemplate,
at the expense of rebuilding everything.”



I changed the Layout swap-out code to the following (to update the layout of the Groups as well):

Diagram.StartTransaction(“LayoutChanged”);

LayoutType = layoutType.Value;

var embeddedDiagrams = GetNodes();
foreach(var embeddedDiagram in embeddedDiagrams) {
var part = Diagram.PartManager.FindNodeForData(embeddedDiagram, Diagram.Model);
var existingLayout = Group.GetLayout(part.VisualElement);
Group.SetLayout(part.VisualElement, _layoutProvider.GetDiagramLayout(layoutType.Value));
}

Diagram.Layout = _layoutProvider.GetDiagramLayout(layoutType.Value);
Diagram.PartManager.RebuildLinkElements();

Diagram.CommitTransaction(“LayoutChanged”);

It kinda of works but there are layout problems. If I swap to a TreeLayout with an Angle of 0 degrees, this is what I get:

This first issue is that the link connecting the ‘Cash’ node to the Group is bent into two segments. I would expect it to be a straight line with a single segment. The second issue is the link connecting the ‘Plant’ node and the ‘Prepaid Expenses’ node is plotted outside the bounds of the group. This looks very odd. I would expect it took look similar to the image below:

Any suggestions?

Thanks
Justin

It looks like you have Routing=“AvoidsNodes”. I would change that to be “Orthogonal”.

I don’t know about the relative vertical positioning of the nodes of the top-level layout.

Hi Walter

Thanks, you are correct. Switching to “Orthogonal” solves the issue.

Yes, I was guessing that it was because the Links were being re-built.

What about the relative vertical positioning of the root node relative to its children? What TreeLayout properties have you set?

The only TreeLayout properties that I’ve set are Angle = 0 and Conditions = All.
I’ve also changed the Routing of the links to “Normal”

In regards to the relative vertical positioning of the root node, are you referring to this issue here?

I’m still not sure how to address this issue. Any suggestions?
Even if I remove the containing Border from the Group’s DataTemplate, the problem still persists

How is “Current Assets” implemented? Is it a simple Node or is it a Group?

Do you notice that offset when using a TreeLayout with Angle=90?

Hi Walter

“Current Assets” is a Node. Below is the DataTemplate for it:

<go:SpotPanel
        go:Part.LayerName="{Binding Path=Data.LayerID}"      
        go:Part.LayoutId="{Binding Data.LayoutID}"
        go:Node.Location="{Binding Path=Data.Location, Mode=TwoWay}"
        go:Node.LocationSpot="Center"
        go:Part.SelectionAdorned="true"
        go:Part.SelectionElementName="Container"
        go:Part.Rotatable="False"
        go:Part.Resizable="False"
        go:Node.LinkableFrom="True"
        go:Node.LinkableTo="True"
        go:Node.LinkableSelfNode="False"
        go:Node.LinkableDuplicates="False"
        go:Node.PortId="">
            
          
            <go:NodePanel Sizing="Auto" Cursor="Hand">
                <Border x:Name="Container" 
                    Background="{Binding Data.MeasureStatus, Converter={StaticResource MeasureNodeStatusBackgroundConverter}}"
                    Style="{StaticResource MeasureNodeOuterBorderStyle}" go:NodePanel.Spot1="0 0" go:NodePanel.Spot2="1 1" go:Node.PortId="">
                    <Border                        
                    Style="{StaticResource MeasureNodeInnerBorderStyle}" go:Node.LinkableFrom="false" go:Node.LinkableTo="false">
                        <Grid>
                            <TextBlock Text="{Binding Data.Text}" VerticalAlignment="Center" HorizontalAlignment="Center" TextWrapping="Wrap" />
                            <Button 
                                Content="{Binding Node.IsExpandedTree, Converter={StaticResource NodeExpandButtonConverter}}" 
                                Command="{Binding CollapseExpandNodeTreeCommand, Source={StaticResource NodeCommandManager}}" 
                                CommandParameter="{Binding Node}" 
                                Visibility="{Binding Data.HasOutboundNodes, Converter={StaticResource VisibilityBooleanConverter}}"
                                VerticalAlignment="Bottom" HorizontalAlignment="Right" Height="14" Width="14" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Margin="0,0,2,2" Padding="0" />
                        </Grid>
                    </Border>
                </Border>

            </go:NodePanel>
                          
        </go:SpotPanel>
    </DataTemplate>

It doesn’t matter which TreeLayout Angle I use (0, 90, 180, 270), the problem is still there.

OK, I’ll investigate as soon as I get a chance.

The reason I asked about Angle=90 is that you have a “header” on your Groups that is at the top. As it turns out the alignment of the group is based on the Node.LocationElement, which in the case of Groups is always the GroupPanel, if present. This means that vertical center alignments (Angle=90) would happen to coincide for Groups, but horizontal ones (Angle=0) would not because the “header” makes it unbalanced in that direction.

Although that would explain a little offset (half the height of the group’s “header”), that does not explain the magnitude of the offset that you are showing. That’s why I’m wondering what’s going on in your app.

Thanks Walter

In the mean time I’ll also play around with the Group’s DataTemplate to see if it makes any difference.