Prodige Software Corporation

Real-Time Data Visualization Using Vector Graphics

Contents

Introduction

This article demonstrates how to add eye-catching vector graphics to a user interface using VG.net, a vector graphics system integrated in Visual Studio. VG.net consists of two parts: a graphical designer and a run-time engine. See the VG.net download page to obtain a Lite or evaluation version.

For this demonstration, we created a simple real-time data simulation: a server application generates data that are sent via TCP/IP to a client application for visualization.

This article focuses on the data visualization portion of that sample.

Running the Sample

To run the sample:

  1. Unzip the files in real_time_vis_demo.zip to a folder.

  2. Run Server.exe - it must be running before the client. The server form is illustrated below.

  3. Run Client.exe, once or many times. The client is illustrated below.

    Resize the client form, and you will see the vector graphics objects automatically scale.

  4. Play with the settings on the server form - try sending pulses to the client.

To run the server and client on separate machines, start Server.exe first. Then run Client.exe from the command line, passing in the name of the server machine as the first argument.

The source code for this sample is in the Samples folder installed by VG.net. To edit VG.net graphics in Visual Studio, you need to download and install a version of VG.net.

Vector Graphics in the .NET Framework

The .NET framework includes drawing classes in the System.Drawing namespace. If you examine the classes in System.Drawing, you find many drawing functions, but no overall system of objects that know how to draw themselves and respond to mouse events.

VG.net provides this missing graphical system. VG.net consists of a graphical designer and a run-time animation engine. Using the graphical designer integrated in Visual Studio, you can create graphical components exactly the same way you create UserControls, using the Toolbox and the Properties window.

Pictures and Elements

A Picture is a .NET class containing vector graphics elements. You create Pictures using the VG.net Picture designer, which opens when you double-click a Picture in the Solution Explorer.

A Picture is the top-level component in the VG.net designer, analogous to a UserControl in the UserControl designer. As with any .NET class, a Picture can have custom properties, methods and events.

Element is the base class for all graphical objects contained in a Picture. Element classes include:

  • Shapes, which draw fills, edges, and text. Shape classes include Rectangle, Ellipse, Polyline, Spline, Pie, Arc, and Path. Polyline and Path can draw polygons.
  • Image
  • Group, created by grouping existing Elements
  • Sub Pictures

Since the Picture class inherits from Element, a Picture can contain other Pictures as child Elements. Child Pictures are called Sub Pictures.

You do not display a Picture directly in a Windows Form. A special Control called a Canvas displays Pictures at run-time.

Let us begin creating the real-time data simulation.

Creating the Solution, Projects and Forms

  1. First create a new Solution in Visual Studio .NET, called "Sockets".

  2. Add to the solution two Windows Forms application projects, called "Client" and "Server".

    The Server generates data in the form of four double values, each representing a single data channel. Data are generated as follows:

    • Channel 1: normally set to 0, can be "pulsed" to 1 for brief periods at the Server
    • Channel 2: set to a constant value selected at the Server, either 0 or 1
    • Channel 3: a sine wave between -1 and 1, with an adjustable wave period
    • Channel 4: a square wave between -1 and 1, with an adjustable wave period

    The Server sends data to the Client using a TCP/IP connection. The Client displays the current value of each data channel. To visualize the data on the Client, we will create an LED and a gauge component.

  3. Create two Windows Forms, one in each project:

    • On the Client, a Form hosting the VG.net Picture containing instances of the LED and gauge components. The Client side Form is called "ClientForm".
    • On the Server, a Form containing the user interface controlling each data channel.the Server side Form is called "ServerForm".

    We will complete these Forms later in the article.

  4. For convenience, add a sub-folder called "UI" to the Client project. This folder will contain the LED component and the gauge component.

Let us now create the first VG.net Picture, the LED.

LED Appearance

First, add a Picture called "LED" to the UI folder in the Client project.

Picture Settings

To make the LED easier to construct, select the Picture called "LED" and set the following properties:

Property Value
BackColor black
GridSpacing 1 (in each dimension)
GridSnap True
GridVisible True

Note

Setting the BackColor property allows to get a better idea of how the LED will appear in the final display. Notice the BackColor property only affects the display within the designer. A Picture object has no background object, unless you provide one.

The Grid property settings above make it easier for you draw precisely when zoomed in on the small LED.

LED Construction

The LED is simple. It consists of two Ellipse objects and a Pie, as illustrated below. The inner Ellipse represents the actual LED. The Pie represents a shiny reflection. The outer Ellipse represents the glow.

You can find the Ellipse and Pie Elements in the Toolbox under the "Drawing" tab.

  1. For the glow, create a first Ellipse of any size and set its properties according to this table:

    Property Value
    Name back
    Location (3, 3)
    Size (24, 24)
    DrawAction Fill

    We will change the fill color when we add Styles. At this point, the Ellipse is very small. Select "To Fit" from the Zoom box on the Drawing toolbar to zoom in to the Ellipse. Then click "ZoomOut" to make it a little smaller.

  2. For the LED itself, add another Ellipse of any size and set its properties according to the following table:

    Property Value
    Name light
    Location (7, 7)
    Size (16, 16)
    DrawAction Fill

    Although it is possible to change the fill of each Ellipse by modifying the Fill property, we will use Styles instead, so that we can change the appearance of the Ellipses at run-time by swapping Style references.

  3. Add six Styles, as illustrated below.

    To do so, select the LED Picture. Open the Styles collection, found under the "Collections" heading in the Properties window. Property settings for the Styles are listed in this table:

    Style Name Fill Property
    OnRed Fill Type: PathGradientFill
    GradientType: TwoColorBell
    StartColor: Bright red: 255, 0, 0
    EndColor: Light red: 255, 149, 149
    FocusPoint: (.5, .66)
    OffRed Fill Type: PathGradientFill
    GradientType: TwoColorBell
    StartColor: Dark red: 128, 0, 0
    EndColor: Darker red: 83, 0, 0
    FocusPoint:(.5, .63)
    BackRed Fill Type: PathGradientFill
    GradientType: TwoColorBell
    StartColor: not set
    StartOpacity: 0
    EndColor: Bright red: 255, 0, 0
    EndOpacity: .753
    FocusScaling:(.65, .65)
    OnGreen Fill Type: PathGradientFill
    GradientType: TwoColorBell
    StartColor: Dull green: 0, 192, 0
    EndColor: Bright green: 0, 255, 0
    FocusPoint:(.5, .66)
    OffGreen Fill Type: PathGradientFill
    GradientType: TwoColorBell
    StartColor: Dark green: 0, 128, 0
    EndColor: Darker green: 0, 85, 0
    FocusPoint:(.5, .63)
    BackGreen Fill Type: PathGradientFill
    GradientType: TwoColorBell
    StartColor: not set
    StartOpacity: 0
    EndColor: Bright green: 0, 255, 0
    EndOpacity: .702
    FocusScaling:(.64, .64)

    Once you create the Styles, you can assign them to the Style property of each Ellipse.

  4. Set the Style of the Ellipse called "back" to BackRed, and the Style of the Ellipse called "light" to OffRed. In the LED we created, we used path gradients to give the LED a glowing 3D effect.

  5. To enhance the 3D effect, add a Pie representing the shine of an overhead light. It will be filled by a path gradient in the shape of a small ellipse toward the top of the LED. First add an Pie the same location and size as the "back" Ellipse, and set the properties of the Pie as follows:

    Property Value
    Name shine
    Location (7, 7)
    Size (16, 16)
    StartAngle 180
    SweepAngle 180
    DrawAction Fill
    Fill Fill Type: PathGradientFill
    GradientType: TwoColorBell
    StartColor: not set
    StartOpacity: 0
    EndColor: White
    EndOpacity: .725
    PathType: EnclosedEllipse
    FocusPoint: (.5, .18)
    Bounds: (.25, 0, .5, .4)
    WrapMode: Clamp

  6. To make the LED easier to position and size, add a Rectangle and set its properties according to the following table:

    Property Value
    Name enclosingRectangle
    Location (0, 0)
    Size (30, 30)
    DrawAction Edge
    Stroke Opacity 0 (transparent)

    The Rectangle defines the bounds of the LED, without being visible.

LED Behavior

This LED is limited to two colors: red or green. To enable the user to select a LED color, we add an enum called LEDColor. Inside the LED class, we add a field called "color" of type LEDColor, and a property modifying that field:

public LEDColor Color
{ 
    get { return color; }
    set
    { 
        if (color == value)
            return;
        color = value;
        UpdateStyles();
    } 
}

The UpdateStyles method handles the actual updating of the Style properties according to the color field value. UpdateStyles makes use of a Boolean field added to LED called on. This field specifies if the LED is on or off. The LED state is set using the On property:

private void UpdateStyles()
{
    if (color == LEDColor.Red)
    {
        back.Style = "BackRed";
        if (on)
            light.Style = "OnRed";
        else
            light.Style = "OffRed";
    }
    if (color == LEDColor.Green)
    {
        back.Style = "BackGreen";
        if (on)
            light.Style = "OnGreen";
        else
            light.Style = "OffGreen";
    }
    back.Visible = on;
}

public bool On
{
    get { return on; }
    set
    {
        if (on == value)
            return;
        on = value;
        UpdateStyles();
    }
}

HorizontalGauge Appearance

First add a Picture called "HorizontalGauge" to the UI folder in the Client project.

Picture Settings

To simplify the gauge construction, select the Picture "HorizontalGauge" and set the following properties:

Property Value
BackColor black
GridSpacing 2 (in each dimension)
GridSnap True
GridVisible True

Gauge Construction

The gauge, illustrated below, consists of a border rectangle and an inner Group of objects, containing three overlapping rectangles exactly the same size. The width of the inner Group will be changed when a data value changes.

  1. First, create the border. Add a rounded Rectangle of any size and set its properties according to this table:

    Property Value
    Name border
    CornerRadius 7
    Location (0, 0)
    Size (104, 24)
    Stroke Color = dark blue,
    Width= 2
    DrawAction Edge

    The following picture illustrates the Stroke settings within the Stroke dialog.

    Because the "border" Rectangle is small, select "To Fit" from the Drawing toolbar to zoom in closely.

  2. Next, create the background of the value bar. Add another rounded Rectangle and set its properties according to this table:

    Property Value
    Name back
    CornerRadius 5
    Location (4, 4)
    Size (96, 16)
    Fill Solid, with Color = bright blue
    DrawAction Fill

    The "back" Rectangle should resemble the picture below.

  3. Over this, we add another Rectangle of the same dimensions, with the same corner radius. Select the "back" Rectangle and perform Copy followed by a Paste operation. You now have a new Rectangle identical to the old. Make the following changes to the properties:

    Property Value
    Name shine
    Fill LinearGradientFill:
    GradientType: TwoColorBell
    StartColor: (not set)
    StartOpacity: 0
    EndColor: white
    EndOpacity: .412
    Angle: 90

    The "shine" Rectangle over the "back" Rectangle should resemble the picture below.

  4. Now add another Rectangle of the same dimensions, with the same corner radius. Select the "shine" Rectangle and perform Copy followed by a Paste. Make the following changes to the properties of the new Rectangle:

    Property Value
    Name ends
    Fill LinearGradientFill:
    GradientType: TwoColorBell
    StartColor: very dark blue
    StartOpacity: .502
    EndColor: (not set)
    EndOpacity: 0
    Angle: 0

    The "ends" Rectangle over the other Rectangles should resemble the picture below. Together, these objects create a glowing effect.

  5. Now select all three of the interior Rectangles, "back", "shine", and "ends", and click the Group command on the Drawing toolbar. The easiest way to select all three Rectangles is to lasso them by dragging the mouse. Once you have selected the Rectangles, create a Group by clicking on the Group icon in the Drawing toolbar. Name the Group "valueBar". The Group enables you to change the width of all three Rectangles in one operation at run-time.

HorizontalGauge Behavior

A reusable graphical component is more flexible if it has properties that change appearance. We will add properties to set the "border" Rectangle color, the "back" Rectangle color, and one of the colors used in the "ends" Rectangle. We do not expose the "shine" colors, since they are an intrinsic part of the glowing appearance, and will work well with any other colors.

The BorderColor property, and associated methods are as follow:

public Color BorderColor
{
    get { return BorderFill.Color; }
    set { BorderFill.Color = value; }
}

private bool ShouldSerializeBorderColor()
{
    return BorderColor != defaultBorderColor;
}
        
private void ResetBorderColor()
{
    BorderColor = defaultBorderColor;
}

private SolidFill BorderFill
{
    get { return (SolidFill)Styles["Border"].Stroke.Fill; }
}

The BorderColor property makes use of a private property, BorderFill. The BorderFill property is merely a convenience, enclosing three operations: getting the Stroke object from the "border" Rectangle, getting the Fill object from the Stroke, and casting the Fill to SolidFill. The BorderColor property simply gets or sets the Color property of BorderFill.

The ShouldSerializeBorderColor and ResetBorderColor methods provide a better designer experience. You can read more about ShouldSerialize and Reset methods in the topic titled "ShouldSerialize and Reset Methods" in the .NET Framework Developer's Guide.

The ShouldSerializeBorderColor method prevents the BorderColor property from being serialized unless it is set to a non-default value. The ResetBorderColor method allows the user to reset the BorderColor property to a default value by right-clicking in the Properties window. Both of these methods make use of the static field defaultBorderColor, which is initiliazed to a default value in the HorizontalGauge constructor.

The BackColor and EndColor properties in the HorizontalGauge allow the user to change the color of the "back" Rectangle, and the non-transparent color of the "ends" Rectangle. These properties are implemented in the same manner as the BorderColor property.

In addition to appearance properties, the HorizontalGauge has properties for displaying a data value:

  • Value: the current data value, stored in the field called "current"
  • Minimum: the minimum value of the Value property, stored in the "minimum" field
  • Maximum: the maximum value of the Value property, stored in the "maximum" field

The implementation of these properties is as follows:

public float Minimum
{
    get { return minimum; }
    set
    {
        if (minimum == value)
            return;
        minimum = value;
        UpdateWidth();
    }
}
        
public float Maximum
{
    get { return maximum; }
    set
    {
        if (maximum == value)
            return;
        maximum = value;
        UpdateWidth();
    }
}
        
public float Value
{
    get { return current; }
    set
    {
        if (current == value)
            return;
        current = value;
        UpdateWidth();
    }
}

private void UpdateWidth()
{
    // update the width of the valueBar to reflect the current value
    float range = maximum - minimum;
    if (range == 0)
        return;
    float value = current;
    if (value > maximum)
        value = maximum;
    if (value < minimum)
        value = minimum;
    float width = ((value - minimum) / range) * maxBarWidth;
    if (width < 1)
        width = 1;
    valueBar.Width = width;
}

The bulk of the work is done in the method UpdateWidth, which scales the Width property of the "valueBar" group in proportion to the "current" field.

Building the Data Display Picture

The DataDisplay Picture will have some LED components, some HorizontalGauge components, and some transparent rectangles containing text labels.

  1. First compile your project, to make the LED and HorizontalGauge components available.

  2. Add a Picture called "DataDisplay" to the Client project.

  3. To simplify construction, select the Picture and set the following properties:

    Property Value
    BackColor black
    GridSnap True
    GridVisible True

  4. Now create two LED objects called "led1" and "led2". Make the first LED green by changing the LEDColor property.

  5. Create two HorizontalGauge objects called "gauge3" and "gauge4". Change the three custom color properties on "gauge4", so it can be distinguished from "gauge3". These properties are BorderColor, BackColor, and EdgeColor. Pick any colors you like.

  6. Finally, add some labels. The labels are actually Rectangles, with DrawAction set to Fill. To make the interior transparent, and the text white, create a Style called "Label". In the "Label" style, modify the Fill property to be completely transparent (Opacity 0), and modify the TextAppearance property use white as the text color. Then set the Style property of each label rectangle to Label.

  7. The DataDisplay Picture needs to route incoming data to the LED and HorizontalGauge components. Add four properties to DataDisplay, one for each data channel, as shown below:

    public double Value1
    {
        set { led1.On = value > 0; }
    }
    
    public double Value2
    {
        set { led2.On = value > 0; }
    }
    
    public double Value3
    {
        set { gauge3.Value = (float)value; }
    }
    
    public double Value4
    {
        set { gauge4.Value = (float)value; }
    }

The final DataDisplay Picture should resemble the picture below.

Building the Client Form

  1. First, compile the Client project to make the DataDisplay Picture available.

  2. Modify the Form named "ClientForm" in the Client project. In the sample code, ClientForm is tall and narrow, but you may use any size that approximates your DataDisplay Picture aspect ratio.

  3. While the Form is open in the Forms designer, open the Drawing tab of the Toolbox. In there you will find the Canvas class. Drop a Canvas onto the Form, and set its properties as follows:

    Property Value
    Name canvas
    Dock Fill
    BackGround Black
    AutoSizePicture True
    AutoSizePadding 10,10

  4. The DataDisplay Picture should be visible as an item in the Drawing section of the Toolbox. Drag this item onto your Form, and a DataDisplay object will appear in the Component Tray below the Form. Rename this object to "dataDisplay".

  5. Now select "canvas" and change the Picture property to "dataDisplay". Your Picture will appear in the Canvas, as below. The AutoSizePicture property causes the Picture to scale and center itself within the Canvas. AutoSizePadding determines the x and y padding used between the bounds of the Picture and the edges of the Canvas.

Building the Server Form

In the interest of space, we will not describe the construction of the Server Form, illustrated at the beginning of the article. The Server Form contains a simple collection of standard Windows Forms Controls. Please examine the sample code for details.

Data Communication with TCP/IP

In the Server application, data is generated by the SignalGenerator class. This class uses a Timer to trigger data generation at 30 millisecond intervals. All data is computed in the function UpdateData. Sine and square wave values are based on elapsed time from the beginning of the simulation.

SignalGenerator listens for client connection requests with a TcpListener object. When a connection is pending, SignalGenerator accepts the request and passes the corresponding Socket object to a new ClientWriter object. Each ClientWriter object created sends data to a single client.

In the Client application, the ClientReader class is responsible for connecting to a server and receiving data. It uses the TcpClient class from the .NET Framework. When data is received, the ClientReader raises an event and invokes the DataReceived method in the ClientForm. DataReceived is as follows:

private void DataReceived(double value1, double value2,
    double value3, double value4)
{
    dataDisplay.Value1 = value1;
    dataDisplay.Value2 = value2;
    dataDisplay.Value3 = value3;
    dataDisplay.Value4 = value4;
}

The DataReceived method modifies properties in the DataDisplay class, and VG.net updates the screen on the next display pass.

Conclusion

This article focused on real-time data visualization using VG.net. This vector graphics system has many other uses. Because each graphical object supports mouse events, just like a Control object, you can create user interface components such as buttons and sliders using VG.net. The VG.net installation folder contains samples of user interface objects. You can find more tips about VG.net at www.vgdotnet.com and the VG.net development blog.