Click here to Skip to main content
13,802,255 members
Click here to Skip to main content
Add your own
alternative version

Stats

23.5K views
498 downloads
33 bookmarked
Posted 5 Jun 2016
Licenced CPOL

.NET Canvas for Network visualization

Rate this:
Please Sign up or sign in to vote.
.NET language Canvas control for Interactive visualizing network data.

 

Gets the binary dll by installing sciBASIC# package via nuget:

PM> Install-Package sciBASIC -Pre

and then add reference to these dll modules:

  • Microsoft.VisualBasic.Data.Csv.dll
  • Microsoft.VisualBasic.MIME.Markup.dll
  • Microsoft.VisualBasic.Imaging.dll
  • Microsoft.VisualBasic.Architecture.Framework_v3.0_22.0.76.201__8da45dcd8060cc9a.dll
  • Microsoft.VisualBasic.Data.visualize.Network.dll
  • Microsoft.VisualBasic.Data.visualize.Network.Canvas.dll

 

Introduction and background

In my recent work on the GCModeller, I want to developing a module for the biological network data visualization. One of the solution is using d3js for this data visualization and the another solution is the Cytoscape software.

The Cytoscape software is doing the best in the network visualization as it provides a lot of style mapping and data imports and exports function. And in my recent work, the cytoscape software was used for visualize the biological network data frequently. But there is problem that the cytoscape software can only generate the statics image of the network, and just can only export a interactive output in web app format. And the cytoscape software is also can not programming with VB.NET. And as I want to build a inner interactive module for the biological network visualization, so that I should try another way for the job. There is another important tools for visualize the network data in the HTML programming: d3js. And the d3js is one of my favorite tools in the data visualization.

Here is my most favorite project from Mr. whichlight's work: a project for visualize the reddit discussion network by using d3js.


Reddit Discussion Network Visualization

And the d3js is my first choice in this work. And here is the force directed graphic engine that I write with d3js hybrid programming with VB.NET (whole source cdoe and example can be download from here):

function d3Network(jsonFile, width, height) {

    var color = d3.scale.category20();
    var force = d3.layout.force()
        .charge(-120)
        .linkDistance(30)
        .size([width, height]);

    var svg = d3.select("body").append("svg")
        .attr("fill", "#DBF3FF")
        .attr("width", width)
        .attr("height", height)
        .attr("class", "chart");

    d3.json(jsonFile, function (error, graph) {
        if (error) throw error;

        force
            .nodes(graph.nodes)
            .links(graph.links)
            .start();

        var link = svg.selectAll(".link")
            .data(graph.links)
            .enter().append("line")
            .attr("class", "link")
            .style("stroke-width", 0.5);

        var node = svg.selectAll(".node")
            .data(graph.nodes)
            .enter().append("circle")
            .attr("class", "node")
            .attr("r", function (d) {
                return Math.sqrt(d.size)+1;
            })
            .style("fill", function (d) {
                return color(d.group);
            })
            .style("opacity", 0.8)
            .call(force.drag);

        node.append("title")
            .attr("class", "tooltip")
            .style("font-size", 16)
            .html(function (d) {
                return "name:\t" + d.name + "\ntype:\t" + d.type + "\nlinks:\t" + d.size;
            });

        force.on("tick", function () {
            link.attr("x1", function (d) { return d.source.x; })
                .attr("y1", function (d) { return d.source.y; })
                .attr("x2", function (d) { return d.target.x; })
                .attr("y2", function (d) { return d.target.y; });

            node.attr("cx", function (d) { return d.x; })
                .attr("cy", function (d) { return d.y; });
        });
    });
}

The VB.NET language was programming hybrids with javascript as the server side on the local client. And by using WinForm web browser control, that we can display the network visualization data. But as the .NET Webbrowser control is based on the IE browser and not support d3js well, so that I have to changes the .NET Webbrowser control to the opensource Firefox browser or Google chrome. And by using Firefox in the project, it actually works, but the problem is that we needs deploy our application with the Chrome kernel(~100MB) and Firefox kernel(~50MB). This is not so convenient and friendly for my customer clients.

And after search on the github, I found a solution for this interactive network visualization work in the .NET language its own way.

This network canvas library is majority based on the work of Mr. Woong Gyu La

Overviews

This canvas library is consist with 4 parts majority:

  1. Force Directed layout engine
  2. Canvas control for WinForm
  3. InputDevice for user mouse events
  4. Renderer for the graphics rendering of the network data

Code in details

Force Directed layout engine

The force directed network layout engine which is available at typeMicrosoft.VisualBasic.DataVisualization.Network.Layouts.ForceDirected2D The introduction for this layout provider is available at Mr. Woong Gyu La's article: "EpForceDirectedGraph.cs- A 2D/3D force directed graph algorithm in C#"

Canvas control for WinForm

The canvas control providers the user interface and rendering task thread for the layout engine. Set up the network data just by using the Canvas.Graph property:

Public Property Graph As NetworkGraph
    Get
        If net Is Nothing Then
            Call __invokeSet(New NetworkGraph)
        End If

        Return net
    End Get
    Set(value As NetworkGraph)
        Call __invokeSet(value)
    End Set
End Property

Private Sub __invokeSet(g As NetworkGraph)
    net = g
    fdgPhysics = New ForceDirected2D(net, FdgArgs.Stiffness, FdgArgs.Repulsion, FdgArgs.Damping)
    fdgRenderer = New Renderer(
        Function() paper,
        Function() New Rectangle(New Point, Size),
        fdgPhysics)
    inputs = New InputDevice(Me)
    fdgRenderer.Asynchronous = False
End Sub

And these components is using for the layout engine and rendering of the image:

''' <summary>
''' The network data model for the visualization
''' </summary>
Dim net As NetworkGraph
''' <summary>
''' Layout provider engine
''' </summary>
Protected Friend fdgPhysics As ForceDirected2D
''' <summary>
''' The graphics updates thread.
''' </summary>
Protected Friend timer As New UpdateThread(30, AddressOf __invokePaint)
''' <summary>
''' The graphics rendering provider
''' </summary>
Protected Friend fdgRenderer As Renderer
''' <summary>
''' GDI+ interface for the canvas control.
''' </summary>
Dim paper As Graphics

All of the rendering work is starting from the timer object:

''' <summary>
''' The graphics updates thread.
''' </summary>
Protected Friend timer As New UpdateThread(30, AddressOf __invokePaint)

    Private Sub __invokePaint()
    Call Me.Invoke(Sub() Invalidate())
End Sub

By notifying the Windows message that the control needs to be update in periodic in the timer thread, and then the eventPaint will be trigged, and then we are able to use the graphics gdi+ interface in the rendering engine for the network visualization. As you can see, this event function provides the required gdi+ interface for the rendering engine and invoke updates the network layout and the control graphics image.

Private Sub Canvas_Paint(sender As Object, e As PaintEventArgs) Handles Me.Paint
    paper = e.Graphics
    paper.CompositingQuality = Drawing2D.CompositingQuality.HighQuality
    paper.SmoothingMode = Drawing2D.SmoothingMode.HighQuality

    Call fdgRenderer.Draw(0.05F)
End Sub

Here is the same function that implements in javascript:

force.on("tick", function () {
     link.attr("x1", function (d) { return d.source.x; })
         .attr("y1", function (d) { return d.source.y; })
         .attr("x2", function (d) { return d.target.x; })
         .attr("y2", function (d) { return d.target.y; });

     node.attr("cx", function (d) { return d.x; })
         .attr("cy", function (d) { return d.y; });
    });
InputDevice for user mouse events

The graphics node in the d3js visualization can be drag by the mouse by binding the mouse event to each node object:

var node = svg.selectAll(".node")
      .data(graph.nodes)
      .enter().append("circle")
      .attr("class", "node")
      .attr("r", function (d) {
            return Math.sqrt(d.size)+1;
      })
      .style("fill", function (d) {
            return color(d.group);
      })
      .style("opacity", 0.8)
      .call(force.drag);

And this input device class is implements the same function as the code .call(force.drag) in javascript:

First, when our mouse move on the control, will trigger a mouse move event. And for drag a node on the control, a mouse down event will be triggered. When finish the drag, the mouse up event will be triggered. And both of these events have aMouseEventArgs parameter, which we can using this argument value for the node detectes and node drags.

From the very beginning, we start to drag a node, then we can set the drag event start and knowing which node is clicked by using __getNode(Point) function:

Private Sub Canvas_MouseDown(sender As Object, e As MouseEventArgs) Handles Canvas.MouseDown
    drag = True
    dragNode = __getNode(e.Location)
End Sub

The __getNode(Point) function loops on the nodes in the network, and detected the mouse location is located in which nodes region by using System.Drawing.Rectangle.Contains(System.Drawing.Point) As Boolean method, due to the reason of the node location is the location in the data mode, can not be used on the user client directly, so that a functionCanvas.fdgRenderer.GraphToScreen for the projection between the data model and user interface is used in this function:

Private Function __getNode(p As Point) As Node
    For Each node As Node In Canvas.Graph.nodes
        Dim r As Single = node.Data.radius
        Dim npt As Point =
            Canvas.fdgRenderer.GraphToScreen(
            Canvas.fdgPhysics.GetPoint(node).position)
        Dim pt As New Point(npt.X - r / 2, npt.Y - r / 2)
        Dim rect As New Rectangle(pt, New Size(r, r))

        If rect.Contains(p) Then
            Return node
        End If
    Next

    Return Nothing
End Function

Then by start the mouse move we are able to move the location of the draged node:

Private Sub Canvas_MouseMove(sender As Object, e As MouseEventArgs) Handles Canvas.MouseMove
    If Not drag Then
        Return
    End If

    If dragNode IsNot Nothing Then
        Dim vec As FDGVector2 =
                Canvas.fdgRenderer.ScreenToGraph(
                New Point(e.Location.X, e.Location.Y))

        dragNode.Pinned = True
        Canvas.fdgPhysics.GetPoint(dragNode).position = vec
    Else
        dragNode = __getNode(e.Location)
    End If
End Sub

If is in the drag mode, then we are set the location of the draged node to the mouse current location, then are we implements the drag events. When we finish the drag event, a mouse up event will be trigged, we release the drag node at here:

Private Sub Canvas_MouseUp(sender As Object, e As MouseEventArgs) Handles Canvas.MouseUp
    drag = False
    If dragNode IsNot Nothing Then
        dragNode.Pinned = False
        dragNode = Nothing
    End If
End Sub
Renderer for the graphics rendering of the network data

As we mentioned above, the world between the inner data model and the user interface is different, so that we needs two function for the projection between the inner data model world and the outside user world:

''' <summary>
''' Projects the data model to our screen for display.
''' </summary>
''' <param name="iPos"></param>
''' <returns></returns>
Public Function GraphToScreen(iPos As FDGVector2) As Point
    Dim rect = __regionProvider()
    Dim x = CInt(Math.Truncate(iPos.x + (CSng(rect.Right - rect.Left) / 2.0F)))
    Dim y = CInt(Math.Truncate(iPos.y + (CSng(rect.Bottom - rect.Top) / 2.0F)))
    Return New Point(x, y)
End Function
''' <summary>
''' Projects the client graphics data to the data model.
''' </summary>
''' <param name="iScreenPos"></param>
''' <returns></returns>
Public Function ScreenToGraph(iScreenPos As Point) As FDGVector2
    Dim retVec As New FDGVector2()
    Dim rect = __regionProvider()
    retVec.x = CSng(iScreenPos.X) - (CSng(rect.Right - rect.Left) / 2.0F)
    retVec.y = CSng(iScreenPos.Y) - (CSng(rect.Bottom - rect.Top) / 2.0F)
    Return retVec
End Function

And these two project function required of a graphics region on the client controls, which the data source is provides by a lambda expression in the canvas control:

Imports System.Collections.Generic
Imports System.Linq
Imports System.Text
Imports Microsoft.VisualBasic.DataVisualization.Network.Graph
Imports Microsoft.VisualBasic.DataVisualization.Network.Layouts
Imports Microsoft.VisualBasic.DataVisualization.Network.Layouts.Interfaces

Public Class Renderer
    Inherits AbstractRenderer

    ''' <summary>
    ''' Gets the graphics source
    ''' </summary>
    Dim __graphicsProvider As Func(Of Graphics)
    ''' <summary>
    ''' gets the graphics region for the projections: <see cref="GraphToScreen"/> and <see cref="ScreenToGraph"/>
    ''' </summary>
    Dim __regionProvider As Func(Of Rectangle)

The lambda expression in the constructor provides the required client data for the data visualization renderer:

Private Sub __invokeSet(g As NetworkGraph)
    net = g
    fdgPhysics = New ForceDirected2D(net, FdgArgs.Stiffness, FdgArgs.Repulsion, FdgArgs.Damping)
    fdgRenderer = New Renderer(
        Function() paper,
        Function() New Rectangle(New Point, Size),
        fdgPhysics)
    inputs = New InputDevice(Me)
    fdgRenderer.Asynchronous = False
End Sub

Then by using the GraphToScreen function, we are knowing where the node should be draw and using ScreenToGraph we can update the draged node its location into the data model.

For draw a edge, we just using the Graphics.DrawLine(pen As Pen, x1 As Integer, y1 As Integer, x2 As Integer, y2 As Integer) function, and by using the function Graphics.FillPie(brush As Brush, rect As Rectangle, startAngle As Single, sweepAngle As Single) that we can draw a node on the canvas.

How to use?

Just create a empty form, and then put the canvas control in your form like this:

Imports System.Windows.Forms
Imports Microsoft.VisualBasic.DataVisualization.Network.Canvas
Imports Microsoft.VisualBasic.DataVisualization.Network.FileStream

Public Class Form1

    Dim canvas As New Canvas With {
        .Dock = DockStyle.Fill
    }

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Call Me.Controls.Add(canvas)

        canvas.Graph = CytoscapeExportAsGraph(
            App.HOME & "\Resources\xcb-main-Edges.csv",
            App.HOME & "\Resources\xcb-main-Nodes.csv")
    End Sub
End Class

Performance issue

Both VisualBasic and C# have the performance issue of the gdi+ graphics on large image rendering, as all of the gdi+ graphics work is on the CPU. This tools works well on the small network, but it get stuck when trying to rendering a large scale network data, and the graphics display is not so smoothly.

Planning changes the graphics engine from gdi+ to Microsoft Win2D or OpenGL in the future work.

Running the Test

The testing project source code can be download from the github and here is the example release program. You can tweaks the force directed graph physics parameter by modify the ini file in the program directory: ForceDirectedArgs.ini

[ForceDirectedArgs]
Stiffness=80
Repulsion=4000
Damping=0.83

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Mr. xieguigang 谢桂纲
Student 中国南方微生物资源利用中心(SMRUCC)
China China
He is good and loves VisualBasic!



github: https://github.com/xieguigang

You may also be interested in...

Comments and Discussions

 
GeneralMy vote of 5 Pin
Franc Morales5-Nov-16 23:47
memberFranc Morales5-Nov-16 23:47 
PraiseInteresting Pin
Vander Wunderbar5-Nov-16 11:57
memberVander Wunderbar5-Nov-16 11:57 
Questionpizap Pin
PK Akira5-Nov-16 0:59
memberPK Akira5-Nov-16 0:59 
QuestionGreat job Pin
Terence Wallace6-Jun-16 16:53
memberTerence Wallace6-Jun-16 16:53 
AnswerRe: Great job Pin
Mr. xieguigang 谢桂纲7-Jun-16 10:07
professionalMr. xieguigang 谢桂纲7-Jun-16 10:07 
GeneralRe: Great job Pin
Louis van Alphen7-Jun-16 11:04
memberLouis van Alphen7-Jun-16 11:04 
Generalgood info Pin
Southmountain6-Jun-16 16:47
memberSouthmountain6-Jun-16 16:47 
GeneralRe: good info Pin
Mr. xieguigang 谢桂纲7-Jun-16 9:42
professionalMr. xieguigang 谢桂纲7-Jun-16 9:42 
QuestionAbout your source code location Pin
Pete O'Hanlon6-Jun-16 11:39
protectorPete O'Hanlon6-Jun-16 11:39 
AnswerRe: About your source code location Pin
Mr. xieguigang 谢桂纲6-Jun-16 13:15
professionalMr. xieguigang 谢桂纲6-Jun-16 13:15 
QuestionWow... Pin
Louis van Alphen6-Jun-16 11:02
memberLouis van Alphen6-Jun-16 11:02 
AnswerRe: Wow... Pin
Mr. xieguigang 谢桂纲6-Jun-16 13:19
professionalMr. xieguigang 谢桂纲6-Jun-16 13:19 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web04 | 2.8.181215.1 | Last Updated 5 Nov 2016
Article Copyright 2016 by Mr. xieguigang 谢桂纲
Everything else Copyright © CodeProject, 1999-2018
Layout: fixed | fluid