Lay out each of the graph's groups in its own box using API

Jun 29, 2011 at 1:24 AM

Hi all,

anyone has a working example of how to lay out each of the graph's groups in its own box and sort the boxes by group size using NodeXL API?

where is the source code?

Jun 29, 2011 at 6:38 AM

The source code is on the Downloads tab under "Other Downloads," at http://nodexl.codeplex.com/releases.

I don't have sample code for that, but I can outline what you need to do.  You didn't say how you are using the API, so I'll assume here that you are using the NodeXLControl.

1. Populate your graph with vertices and edges.

2. Create an array of Microsoft.NodeXL.Layouts.GroupVertexInformation objects, one for each of the graph's groups, and add the array to the Graph object using NodeXLControl.Graph.SetValue(ReservedMetadataKeys.GroupInformation, array).

3. Set the NodeXLControl.Layout.LayoutStyle property to LayoutStyle.UseGroups.

That's it.  There are a few other options available (see the ReservedMetadataKeys.GroupRectanglePenWidth and IAsyncLayout.ImproveLayoutOfGroups topics in the NodeXLApi.chm help file for details), though you don't need to use them.

-- Tony

 

Jan 5, 2012 at 10:21 PM

I am using version 1.0.1.196 of source code and trying to accomplish similar thing via code. I do not seem to be able to locate  Microsoft.NodeXL.Layouts.GroupVertexInformation (even when changed to smrf.NodeXL.Layouts.GroupVertexInformation).

I have referenced smrf.NodeXL.Layouts in my project. In addition I don't seem to see this class file in the source code directory. Has the name been changed since last Jan or is there something else I am doing wrong?

 

Thanks

Jan 6, 2012 at 6:38 AM

The type name has changed to Smrf.NodeXL.Core.GroupInfo, and the relevant metadata key has changed to ReservedMetadataKeys.GroupInfo.

-- Tony


Feb 2, 2012 at 6:17 PM

How can I use the grouping feature if  don't use the NodeXLControl? This is how I generate graphs rigth now:

        internal static void GenerateGraphImage(IGraph subGraph, string filePath, int targetDPI)
        {
            Int32 GraphWidth = 1024;
            Int32 GraphHeight = 1024;

            Double SCREEN_DPI = 96.0;
            Double TARGET_DPI = targetDPI;


            ILayout oLayout = new FruchtermanReingoldLayout();

            LayoutContext layoutContext = new LayoutContext(
            new System.Drawing.Rectangle(0, 0, GraphWidth, GraphHeight));

            NodeXLVisual nodeXLVisual = new NodeXLVisual();
        
            oLayout.LayOutGraph(subGraph, layoutContext);
            GraphDrawingContext oGraphDrawingContext = new GraphDrawingContext(
                                   new Rect(0, 0, GraphWidth, GraphHeight), oLayout.Margin,
                                   System.Windows.Media.Color.FromRgb(255, 255, 255));


            nodeXLVisual.GraphDrawer.DrawGraph(subGraph, oGraphDrawingContext);
            // RenderTargetBitmap oRenderTargetBitmap = new RenderTargetBitmap(
            //GraphWidth, GraphHeight, 96, 96, PixelFormats.Default);
            RenderTargetBitmap oRenderTargetBitmap = new RenderTargetBitmap(
                (int)Math.Floor(GraphWidth * (TARGET_DPI / SCREEN_DPI))
                , (int)Math.Floor(GraphHeight * (TARGET_DPI / SCREEN_DPI))
                , TARGET_DPI
                , TARGET_DPI
                , PixelFormats.Default);


            oRenderTargetBitmap.Render(nodeXLVisual);
            BmpBitmapEncoder oBmpBitmapEncoder = new BmpBitmapEncoder();
            oBmpBitmapEncoder.Frames.Add(BitmapFrame.Create(oRenderTargetBitmap));
            MemoryStream oMemoryStream = new MemoryStream();
            oBmpBitmapEncoder.Save(oMemoryStream);
            Bitmap oBitmap = new Bitmap(oMemoryStream);

            FileInfo fi = new FileInfo(filePath);
            string fileName = fi.FullName.Replace(fi.Extension, ".png");
            FileStream fs = File.Create(fileName);
            oBitmap.Save(fs, System.Drawing.Imaging.ImageFormat.Png);

            fs.Flush();
            fs.Close();
        }

 

Thanx

Jan

Feb 3, 2012 at 6:56 AM

Jan:

It can't be done easily, because the grouping feature you want is available only when laying out the graph asynchronously, which is what the NodeXLControl does.  It is not currently implemented for laying out the graph synchronously, which is what you want to do in your lower-level code.  There is a work item to change that, but I don't know when we'll get to it.

I did come up with an ugly, temporary hack that you might want to try.  I'll post it after this.  I could get it to work only in a Windows Forms application, not a console application.

-- Tony

Feb 3, 2012 at 6:57 AM

using System;
using System.Windows.Forms;
using System.Windows;
using System.Windows.Media.Imaging;
using System.Windows.Media;
using System.IO;
using System.Drawing;
using Smrf.NodeXL.Core;
using Smrf.NodeXL.Layouts;
using Smrf.NodeXL.Visualization.Wpf;

namespace WindowsFormsApplication2
{
public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private void btnCreateGraph_Click(object sender, EventArgs e)
    {
        Graph graph = new Graph(GraphDirectedness.Directed);
        IVertexCollection vertices = graph.Vertices;
        IEdgeCollection edges = graph.Edges;

        IVertex oVertexA = vertices.Add();
        oVertexA.Name = "Vertex A";
        IVertex oVertexB = vertices.Add();
        oVertexB.Name = "Vertex B";
        IVertex oVertexC = vertices.Add();
        oVertexC.Name = "Vertex C";

        edges.Add(oVertexA, oVertexB, true);
        edges.Add(oVertexB, oVertexC, true);
        edges.Add(oVertexC, oVertexA, true);

        GroupInfo group1 = new GroupInfo();
        group1.Vertices.AddLast(oVertexA);

        GroupInfo group2 = new GroupInfo();
        group2.Vertices.AddLast(oVertexB);
        group2.Vertices.AddLast(oVertexC);

        graph.SetValue(ReservedMetadataKeys.GroupInfo, new GroupInfo[] { group1, group2 });

        GenerateGraphImage(graph, "C:\\TempImage.png", 96);
    }

    private void GenerateGraphImage(IGraph subGraph, string filePath, int targetDPI)
    {
        Int32 GraphWidth = 1024;
        Int32 GraphHeight = 1024;

        Double SCREEN_DPI = 96.0;
        Double TARGET_DPI = targetDPI;

        Boolean graphDrawn = false;


        IAsyncLayout oLayout = new FruchtermanReingoldLayout();
        oLayout.LayoutStyle = LayoutStyle.UseGroups;

        LayoutContext layoutContext = new LayoutContext(
        new System.Drawing.Rectangle(0, 0, GraphWidth, GraphHeight));

        NodeXLVisual nodeXLVisual = new NodeXLVisual();

        // Awful hack: Use a delegate to complete the task after the graph has
        // been asynchronously laid out.

        oLayout.LayOutGraphCompleted += delegate
        {
        GraphDrawingContext oGraphDrawingContext = new GraphDrawingContext(
                               new Rect(0, 0, GraphWidth, GraphHeight), oLayout.Margin,
                               System.Windows.Media.Color.FromRgb(255, 255, 255));


        nodeXLVisual.GraphDrawer.DrawGraph(subGraph, oGraphDrawingContext);
        // RenderTargetBitmap oRenderTargetBitmap = new RenderTargetBitmap(
        //GraphWidth, GraphHeight, 96, 96, PixelFormats.Default);
        RenderTargetBitmap oRenderTargetBitmap = new RenderTargetBitmap(
            (int)Math.Floor(GraphWidth * (TARGET_DPI / SCREEN_DPI))
            , (int)Math.Floor(GraphHeight * (TARGET_DPI / SCREEN_DPI))
            , TARGET_DPI
            , TARGET_DPI
            , PixelFormats.Default);


        oRenderTargetBitmap.Render(nodeXLVisual);
        BmpBitmapEncoder oBmpBitmapEncoder = new BmpBitmapEncoder();
        oBmpBitmapEncoder.Frames.Add(BitmapFrame.Create(oRenderTargetBitmap));
        MemoryStream oMemoryStream = new MemoryStream();
        oBmpBitmapEncoder.Save(oMemoryStream);
        Bitmap oBitmap = new Bitmap(oMemoryStream);

        FileInfo fi = new FileInfo(filePath);
        string fileName = fi.FullName.Replace(fi.Extension, ".png");
        FileStream fs = File.Create(fileName);
        oBitmap.Save(fs, System.Drawing.Imaging.ImageFormat.Png);

        fs.Flush();
        fs.Close();

        graphDrawn = true;
        };

        oLayout.LayOutGraphAsync(subGraph, layoutContext);

        while (!graphDrawn)
        {
            Application.DoEvents();
            System.Threading.Thread.Sleep(100);
        }
    }
}
}

Feb 3, 2012 at 6:30 PM

Hi Tony,

many thanks for the code. Wondering what the problem is with console apps? It's not too bad because I already have a dependency on WinForms fro some heatmap rendering with GDI+.

Cheers

Jan

Feb 3, 2012 at 6:40 PM

Jan:

In the console app I tried, the asynchronous layout code was running on the wrong thread and throwing an InvalidOperationException when the wrong thread was detected.  It might be fixable, but I didn't spend much time trying to figure it out.  The root cause of the entire problem is that the group feature is implemented at the wrong level.

-- Tony

Feb 3, 2012 at 6:54 PM

I see. Thanks alot. Would be great to have this feature available on the API level without any dependency on a specific UI framework.

Feb 4, 2012 at 7:25 AM

I just copied you code for creating the graph into m consol app and it works fine. But one little thing bothers me. Annotated labels don't stay in the bounderies of their group. Sometimes a label appears in different group when the corresponding vertex is close to the edge of group.

Feb 6, 2012 at 4:24 PM

That's a known bug.  Unfortunately, it's not an easy one to fix, which is why it hasn't been fixed yet, but it's definitely on the bug list.  (The problem is that the code that draws the labels knows nothing about groups or the boxes they are contained within.)

Some possible workarounds for now are 1) decrease the font size; and 2) increase the layout margin, which the layout code honors for each box.  The layout margin can be set with the FruchtermanReingoldLayout.Margin property.

-- Tony

Feb 6, 2012 at 6:13 PM

Ok, I thing margin will do the trick for now.

Many thanks!!!

Jan

Feb 16, 2012 at 12:13 AM

Jan:

Starting with version 1.0.1.202 of NodeXL, which will be released in early March 2012, you can use the group-in-a-box feature while laying out the graph synchronously.  It will no longer be restricted to asynchronous layouts.

-- Tony

Feb 16, 2012 at 12:15 AM

Sounds great. Good work!!!!

Thanx

Jan

Apr 3, 2012 at 11:26 AM
Edited Apr 3, 2012 at 12:00 PM

For sure I'm doing something wrong but I'm not be able to get out groups-in-a-box using v. 1.0.1.203 . This is code I'm using (and what's about using new GroupInfo(un.Nome, true, null) with a NodeXLVisual in ASPNET PAge ? ) :

 

            Graph oGraph = new Graph(GraphDirectedness.Directed);
            IVertexCollection oVertices = oGraph.Vertices;
            IEdgeCollection oEdges = oGraph.Edges;

            GroupInfo[] gi = new GroupInfo[unitaorg.Count];
            Hashtable indexGroups = new Hashtable();
            int i = 0;
            foreach (UnitaOrgNodeXL un in unitaorg.Values)
            {
                gi[i] = new GroupInfo(un.Nome, false, null);
                gi[i].Label = un.Nome;
                gi[i].Rectangle = new Rectangle(100 * (i + 1), 100 * (i + 1), 50, 50);
                indexGroups.Add(un.Nome, i);
                i++;
            }
            // Add vertices.
            IVertex oVertex;
            Hashtable htUt = new Hashtable();
            foreach (UtenteNodeXL utXL in utenti.Values)
            {
                oVertex = oVertices.Add();
                oVertex.Name = utXL.LoginName;
                oVertex.SetValue(ReservedMetadataKeys.PerVertexLabel, utXL.Nome);
                if (utXL.UnitaAppartenenza != null)
                {
                    gi[(int)indexGroups[utXL.UnitaAppartenenza.Nome]].Vertices.AddLast(oVertex);
                }
                htUt.Add(utXL.LoginName, oVertex);
            }
            oGraph.SetValue(ReservedMetadataKeys.GroupInfo, gi);
            // Connect the vertices with directed edges.
            IEdge oEdge = null;
            foreach (LeggeArcNodeXL lettura in letture.Values)
            {
                oEdge = oEdges.Add((IVertex)htUt[lettura.Lettore.LoginName], (IVertex)htUt[lettura.Autore.LoginName], true);
                oEdge.SetValue(ReservedMetadataKeys.PerEdgeWidth, (Single)lettura.totaleLetture);
            }
            foreach (IVertex vrt in htUt.Values)
            {
                vrt.SetValue(ReservedMetadataKeys.PerVertexRadius, (Single)(vrt.IncomingEdges.Count + 1) * 10);
            }
            // Synchronously lay out the graph using one of the NodeXL-supplied
            // layout objects.
            ILayout oLayout = new FruchtermanReingoldLayout();
            LayoutContext oLayoutContext = new LayoutContext(new Rectangle(0, 0, GraphWidth, GraphHeight));
            oLayout.LayOutGraph(oGraph, oLayoutContext);
            // Create an object that can render a NodeXL graph as a Visual.
            NodeXLVisual oNodeXLVisual = new NodeXLVisual();
            oLayout.LayoutStyle = LayoutStyle.UseGroups;

            // Use the NodeXLVisual object's GraphDrawer to draw the graph onto the Visual.
            GraphDrawingContext oGraphDrawingContext = new GraphDrawingContext(
                new Rect(0, 0, GraphWidth, GraphHeight), oLayout.Margin,
                System.Windows.Media.Color.FromRgb(255, 255, 255));
            oNodeXLVisual.GraphDrawer.VertexDrawer.Shape = VertexShape.Sphere;
            oNodeXLVisual.GraphDrawer.VertexDrawer.Color = System.Windows.Media.Color.FromRgb(255, 0, 0);
            oNodeXLVisual.GraphDrawer.VertexDrawer.Effect = VertexEffect.DropShadow;
            oNodeXLVisual.GraphDrawer.EdgeDrawer.CurveStyle = EdgeCurveStyle.Bezier;
            oNodeXLVisual.GraphDrawer.DrawGraph(oGraph, oGraphDrawingContext);

 

thanks,

Alberto

Apr 3, 2012 at 7:46 PM

Alberto:

You are setting oLayout.LayoutStyle to LayoutStyle.UseGroups after you have laid out the graph.  The LayoutStyle property must be set before you lay out the graph.

I do not understand your parenthetical question, "and what's about using new GroupInfo(un.Nome, true, null) with a NodeXLVisual in ASPNET PAge"?  What are you asking?

-- Tony

Apr 4, 2012 at 10:11 AM

Thank you very much Tony,

I get group boxes now. Sorry for my cryptic question, I mean, what's about the Collapsed View of groups ( constructor: GroupInfo(string name, bool isCollapsed, string collapsedAttributes) ) support status in a NodeXLVisual context ? I'm interested to develop an ASPNET application so I understand that I cannot use a NodeXLControl pattern in this context. I'm wrong ?

Alberto

Apr 5, 2012 at 12:03 AM
Edited Apr 5, 2012 at 1:38 AM

Alberto:

You are correct.  The collapse/expand functionality is available only when using the NodeXLControl.  It's meant for user interaction, and I didn't realize that anyone would ever want to use it for static images.

-- Tony

Apr 11, 2012 at 9:49 AM

Tony,

I'd like to realize an ASPNET page able to visualize both collapsed or expanded groups, reloading the page with a somehow user interaction.  However, my first result is already impressive so, thank you

Alberto