Click here to Skip to main content
13,629,039 members
Click here to Skip to main content
Add your own
alternative version

Stats

15.6K views
1.1K downloads
19 bookmarked
Posted 1 Jul 2016
Licenced CPOL

Oscillograph, Bargraph and Linegraph in .NET

, 16 Mar 2018
Rate this:
Please Sign up or sign in to vote.
Oscillograph, Bargraph and Linegraph made in Visual Studio with VB.NET

Purpose

Visual illustration of digital performance and statistics.

Introduction

I wrote this library by making use of a Superclass-Childclass hierarchy, by using Graph as the abstract Class, inheriting the .NET UserControl. The base Class Graph is inherited by Oscillograph, Bargraph and Linegraph. It is set up the same way as when we add a new Form to our project, call it Form1 which inherits the Windows.Forms.Form. The abstract Class is Windows.Forms.Form and the inheritor or clone is Form1 in that case. In this way, the child control needs less coding when they already inherit the basic parental attributes.

For more information on how abstract classes work, view the documentation on MSDN:

Namespace XChart

  • Public MustInherit Class Graph: Inherits UserControl
  • Public Class OscilloGraph: Inherits Graph
  • Public Class BarGraph: Inherits Graph
  • Public Class LineGraph: Inherits Graph

Using the Code

The Chart Structure is meant to resemble a chart in the graph. Its main attributte accepts an Integer value between 0 and 100.

Option Explicit On
Option Strict On

Imports System.ComponentModel
Imports System.Drawing
Imports System.Drawing.Drawing2D
Imports System.Runtime.InteropServices
Imports System.Windows.Forms

<EditorBrowsable(EditorBrowsableState.Advanced), ComVisible(False)>
Public MustInherit Class Graph : Inherits UserControl
    Public Event PrePaint(sender As Object, ByRef e As PaintEventArgs)
    Public Event PostPaint(sender As Object, ByRef e As PaintEventArgs)

    Structure Chart
        ''' <summary>
        '''Value range 0-100.
        ''' </summary> 
        Sub New(ByVal value As Integer, ByVal id As String, _
                  ByVal description As String, ByVal brush As Brush)
            Me.Value = value
            Me.Id = id
            Me.Description = description
            Me.Brush = brush
        End Sub
        Public Value As Integer
        Public Id As String
        Public Description As String
        Public Brush As Brush
    End Structure

#Region "Fields"
    Public Charts As New List(Of Chart)
    Private _VisibleItems As Integer
    Private _Flow As Flows = Flows.Right
    Private _Thickness As Integer = 2
    Private _Space As Integer = 1
#End Region

#Region "Enumeration"
    Enum Flows
        Top = 2
        Right = 1
        Bottom = 3
        Left = 0
    End Enum
#End Region

The Space property is literally the distance between each chart. The overridable Flow property is overridden on the cloned Class, when for example, no Flow.Top and Flow.Bottom attributes are applicable.

The VisibleItems property returns the count of charts that could fit in the DisplayRectangle. If we declare the Control with a width of 130, and the element attributes with ElementWidth 10 and Space 5, the VisibleItems property will return 9 {130 / (10+5) = 8 (+ 1addition = 9)}. Items that fit not in the DisplayRectangle are not graphically processed.

The Count Property returns the number of Charts present in the list.

#Region "Properties"
    ''' <summary>
    '''Returns the total amount of visible items.
    ''' </summary> 
    ReadOnly Property VisibleItems As Integer
        Get
            Return Me._VisibleItems
        End Get
    End Property
    ''' <summary>
    '''Gets or Sets the width of each chart.
    ''' </summary> 
    Property Thickness As Integer
        Get
            Return Me._Thickness
        End Get
        Set(ByVal value As Integer)
            Me._Thickness = value
            Me.CalculateVisibleItems()
            Me.Invalidate()
        End Set
    End Property
    ''' <summary>
    '''Gets or Sets the distance between each chart.
    ''' </summary> 
    Property Space As Integer
        Get
            Return Me._Space
        End Get
        Set(ByVal value As Integer)
            Me._Space = value
            Me.CalculateVisibleItems()
            Me.Invalidate()
        End Set
    End Property
    Overridable Property Flow As Flows
        Get
            Return Me._Flow
        End Get
        Set(ByVal value As Flows)
            Me._Flow = value
            Me.CalculateVisibleItems()
            Me.Invalidate()
        End Set
    End Property
    ''' <summary>
    '''Returns the total amount of charts in the List.
    ''' </summary> 
    ReadOnly Property Count As Integer
        Get
            Return Me.Charts.Count
        End Get
    End Property
#End Region
#Region "Methods"
    ''' <summary>
    '''Returns the description text of an item.
    ''' </summary> 
    Public Function ChartDescription(ByVal index As Integer) As String
        Return Me.Charts(index).Description
    End Function

    ''' <summary>
    '''Returns the first item that matches the specified Id text.
    ''' </summary> 
    Public Function IndexOf(ByVal id As String) As Integer
        Dim result As Integer = -1
        If (Me.Count > 0) Then
            For i = 0 To (Me.Count - 1)
                If (Me.Charts(i).Id.ToLower() = id.ToLower()) Then
                    result = i
                    Exit For
                End If
            Next
        End If
        Return result
    End Function

    Sub New()
        Me.Size = New Size(110, 40)
        Me.MinimumSize = New Size(10, 10)
        Me.BackColor = Color.FromKnownColor(KnownColor.Control)
        Me.DoubleBuffered = True
        Me.SetStyle(ControlStyles.AllPaintingInWmPaint Or ControlStyles.OptimizedDoubleBuffer _
                    Or ControlStyles.UserPaint, True)
        Me.UpdateStyles()

        Dim thr As New Threading.Thread(Sub() FPS_TIMER_Tick())
        thr.Start()
    End Sub

The Install procedure receives a List(Of Chart) by reference. You declare such a list on your project then call this method with the list as reference. Once installed, every time you insert/add an item to that list, the Graph subclasses will already have the reference.

Sub Install(ByRef input As List(Of Chart))
    Me.Charts = input
    Me.CalculateVisibleItems()
    Me.Invalidate()
End Su
Public Sub RefreshGraph()
    Me.Invalidate()
End Sub

The CalculateVisibleItems procedure is called when the values of Thickness, Space and Flow are changed. It updates the VisibleItems property.

Private Sub CalculateVisibleItems()
    Dim result As Integer

    If (Thickness = 0) Then
        result = 0
    Else
        Select Case _Flow
            Case Flows.Right, Flows.Left
                If (Me.ClientSize.Width >= Thickness) Then
                    result = (Me.ClientSize.Width \ (Thickness + _Space)) + 1
                End If

            Case Flows.Top, Flows.Bottom
                If (Me.ClientSize.Height >= Thickness) Then
                    result = (Me.ClientSize.Height \ (Thickness + _Space)) + 1
                End If
        End Select
    End If
    Me._VisibleItems = result
End Sub
Private Sub Me_SizeChanged(ByVal sender As Object, ByVal e As EventArgs) Handles Me.SizeChanged
    Me.CalculateVisibleItems()
    Me.Invalidate()
End Sub

Methods that provide reports to graphical frame rates.

Public Class FpsEventArgs : Inherits EventArgs
    Sub New()

    End Sub
    Sub SetFPS(ByVal fps As Integer)
        Me.FPS = [fps]
    End Sub
    Sub New(ByVal fps As Integer)
        _FPS = fps
    End Sub
    Public Property FPS As Integer
        Get
            Return _FPS
        End Get
        Set(ByVal value As Integer)
            _FPS = value
        End Set
    End Property
    Private _FPS As Integer
End Class
Public Event FramesPerSecondChanged(ByVal sender As Object, ByVal e As FpsEventArgs)
Dim _FpsEventArgs As New FpsEventArgs()
Dim frames As Integer = 0
Private Sub FPS_TIMER_Tick()
    Do
        Threading.Thread.Sleep(999)
        _FpsEventArgs.SetFPS(frames)
        DelegateFPS()
    Loop
End Sub
Private Delegate Sub Delegate_FPS()
Private Sub DelegateFPS()
    If Me.InvokeRequired Then
        Invoke(New Delegate_FPS(AddressOf DelegateFPS))
    Else
        RaiseEvent FramesPerSecondChanged(Me, _FpsEventArgs)
        frames = 0
    End If
End Sub

Final

The overridable AnimationPaint procedure, is overriden on the derived Classes, and there we write the code for the animation.

Protected Overridable Sub AnimationPaint(ByRef e As PaintEventArgs, ByVal charts() As Chart)
    '###################
    '###################
    '###################
End Sub
Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
    Using e
        e.Graphics.CompositingQuality = CompositingQuality.HighQuality
        e.Graphics.SmoothingMode = SmoothingMode.AntiAlias
        e.Graphics.PixelOffsetMode = PixelOffsetMode.HighQuality
        e.Graphics.InterpolationMode = InterpolationMode.High

        '###################
        RaiseEvent PrePaint(Me, e)
        AnimationPaint(e, Charts.ToArray())
        RaiseEvent PostPaint(Me, e)
        '###################
    End Using
    frames += 1

    MyBase.OnPaint(e)
End Sub

That was all about the abstract class Graph, below I explain how BarGraph works and how to use it.

<Browsable(True), EditorBrowsable(EditorBrowsableState.Always), ComVisible(True), _
ToolboxBitmap(GetType(BarGraph), "bargraph.png"), Description("Bargraph")>
Public Class BarGraph : Inherits Graph

We override the Flow property, as we will not use some of its features. The constructor MyBase refers to the base (abstract) class Graph. The attributes Top and Bottom are not needed for the BarGraph as it makes no sense. We replace any of the two with the Left attribute, thus we will use only the Left and Right, Flow features.

Public Overrides Property Flow As Flows
     Get
         Return MyBase.Flow
     End Get
     Set(value As Flows)
         If (value = Flows.Top) Or (value = Flows.Bottom) Then
             value = Flows.Left
         End If
         MyBase.Flow = value
     End Set
 End Property

Here, we override the AnimationPaint method located on the base Graph. Do not dispose of the e variable referencing the PaintEventArgs as the base class will take care of it.

Protected Overrides Sub AnimationPaint(ByRef e As PaintEventArgs, ByVal charts() As Chart)
    'Note, the first item in the array is the newest added, the last one the oldest
    'the charts range is 1-based, while the index is 0-based
    If (Not Count = 0) Then
        For i = 1 To Count
            If (i > VisibleItems) Then Exit For

            Dim _chart As Chart = charts(i - 1)
            Dim rect As Rectangle = Me.ChartRect(i, _chart.Value)
            'draw the chart into the control,
            e.Graphics.FillRectangle(_chart.Brush, rect)
            'dispose the variables  that are not needed anymore
            _chart = Nothing
            rect = Nothing
        Next
        charts = Nothing
    End If
End Sub

Private Function ChartRect(ByVal index As Integer, ByVal value As Integer) As Rectangle
    Dim x, y, chart_width, chart_height As Integer

    Select Case Flow
        Case Flows.Left
            chart_width = Thickness
            x = Space + CInt((index - 1) * (Space + chart_width))
            chart_height = CInt((Me.ClientSize.Height / 100) * (value))
            y = CInt((Me.ClientSize.Height - chart_height))

        Case Flows.Right
            chart_width = Thickness
            x = CInt(Me.ClientSize.Width - ((index) * (Space + chart_width)))
            chart_height = CInt((Me.ClientSize.Height / 100) * (value))
            y = CInt((Me.ClientSize.Height - chart_height))

    End Select
    Return New Rectangle(x, y, chart_width, chart_height)
End Function

Public Overloads Function ChartArea(ByVal index As Integer) As Rectangle
    Return ChartRect(index + 1, Me.Charts(index).Value)
End Function

Public Overloads Function ChartArea(ByVal id As String) As Rectangle
    Dim idx As Integer = IndexOf(id)
    If (idx = -1) Then
        Return New Rectangle(0, 0, 0, 0)
    Else
        Return ChartArea(idx)
    End If
End Function

Public Function ChartID(ByVal posX As Integer) As String
    Dim result As String = "IndexOutOfRange"
    If (posX < 0) OrElse (posX > Me.Width) Then Return result
    For i As Integer = 0 To (Me.Count - 1)
        Dim points As New List(Of Integer)
        For pt = ChartArea(i).X To ((ChartArea(i).X + ChartArea(i).Width) - 1)
            points.Add(pt)
        Next
        If points.Contains(posX) Then
            Return Me.Charts(i).Id
        End If
    Next
    Return result
End Function

How Can the Graphs be Used in Our Application?

 Public Class Form1 
    Friend WithEvents BarGraph1 As New BarGraph() 
    Dim ITEMS As New List(Of Graph.Chart) 
    Private Sub Me_Load() Handles Me.Load 
           BarGraph1.Size = New Size(420,140)
           BarGraph1.Location = New Point(50,50) 
           Me.Controls.Add( BarGraph1 )
           BarGraph1.Install( ITEMS )                      
      
           'value, id, description, brush
           ITEMS.Insert(0, New Graph.Chart(5, "2018.3.13 (timestamp)", _
                        "five potatoes fried", Brushes.Yellow))
           ITEMS.Insert(0, New Graph.Chart(20, "", "twenty peas", Brushes.LIMEGREEN)) 
           ITEMS.Insert(0, New Graph.Chart(30, CStr(30), "", Brushes.Red))
           BarGraph1.RefreshGraph()
           BarGraph1.Show()
    End Sub
    'Optional
    Private Sub BarGraph1_PrePaint(ByVal sender As Object, ByRef e As PaintEventArgs) _
             Handles BarGraph1.PrePaint
        'Draw Lines
        For i = 1 To 4
            e.Graphics.DrawLine(Pens.Blue, New Point(0, i * BarGraph1.Height \ 5), _
                       New Point(BarGraph1.Width, i * BarGraph1.Height \ 5))
        Next
    End Sub
    Private Sub BarGraph1_PostPaint(ByVal sender As Object, ByRef e As PaintEventArgs) _
              Handles BarGraph1.PostPaint    
        'Draw a shadow or the id and description text when you hover at items
        'retrieve the mouse position from BarGraph1 MouseMove and get the 
        'actual chart using ChartID and ChartArea functions...
    End Sub

End Class 

History

  • 6th July, 2016 - The list of items is now maintained externally. Download Graph_VB.zip
    Added functionality to represent each item with unique brush
  • 31st July, 2016 - Added PrePaint() Event and updated the library
  • 16th March.2016 - Updated the library and added PostPaint() Event

License

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

Share

About the Author

kmn1235
Albania Albania
Hobby Programmer

You may also be interested in...

Comments and Discussions

 
GeneralMy vote of 1 Pin
Alexey KK8-Aug-16 21:13
professionalAlexey KK8-Aug-16 21:13 
GeneralRe: My vote of 1 Pin
Gegniani8-Aug-16 23:39
memberGegniani8-Aug-16 23:39 
GeneralRe: My vote of 1 Pin
Alexey KK8-Aug-16 23:46
professionalAlexey KK8-Aug-16 23:46 
GeneralRe: My vote of 1 Pin
Gegniani9-Aug-16 0:04
memberGegniani9-Aug-16 0:04 
GeneralRe: My vote of 1 Pin
Alexey KK9-Aug-16 0:13
professionalAlexey KK9-Aug-16 0:13 
GeneralMy vote of 4 Pin
Muhammad Shahid Farooq30-Jul-16 18:58
professionalMuhammad Shahid Farooq30-Jul-16 18:58 
GeneralRe: My vote of 4 Pin
Gegniani31-Jul-16 0:45
memberGegniani31-Jul-16 0:45 
Questionthanks for reply Pin
avisal11-Jul-16 8:19
memberavisal11-Jul-16 8:19 
QuestionDrawing Pin
avisal7-Jul-16 6:39
memberavisal7-Jul-16 6:39 
AnswerRe: Drawing Pin
Gegnjani11-Jul-16 8:02
memberGegnjani11-Jul-16 8:02 
GeneralMy vote of 5 Pin
raiserle4-Jul-16 7:35
memberraiserle4-Jul-16 7:35 
GeneralRe: My vote of 5 Pin
Gegnjani4-Jul-16 13:07
memberGegnjani4-Jul-16 13:07 
QuestionHard but helpful ! Pin
XavierRossignol3-Jul-16 4:22
memberXavierRossignol3-Jul-16 4:22 
AnswerRe: Hard but helpful ! Pin
Gegnjani4-Jul-16 13:08
memberGegnjani4-Jul-16 13:08 
AnswerRe: Hard but helpful ! Pin
Alexey KK8-Aug-16 21:26
professionalAlexey KK8-Aug-16 21:26 

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
Web01-2016 | 2.8.180712.1 | Last Updated 16 Mar 2018
Article Copyright 2016 by kmn1235
Everything else Copyright © CodeProject, 1999-2018
Layout: fixed | fluid