ChartDirector 6.0 (ASP/COM/VB Edition)

Realtime Chart Demonstration (Windows)

This sample program demonstrates a realtime chart with configurable chart update rate and with custom realtime information displayed on the chart.

In this sample program, new data values are generated by a random number generator, driven by a timer. The values are "shifted" into data arrays, which are used for creating the chart.

The chart is updated by a separate timer. This allows the chart update rate to be configurable independent of the data rate. Also, the chart can be "frozen" for easy reading, while the data can continue to update on the background.

To demonstrate the code structure for update rate control (even though for the update rate in this demo it is not necessary to have any rate control), instead of directly updating the chart, the chart update timer calls ChartViewer.updateViewPort to fire the ViewPortChanged event, and the chart is updated in the event handler.

In ChartDirector's architecture, a realtime chart can show custom information in realtime. In this demonstration, we show the latest data values as part of the legend box, and display an alert message if the latest data values exceed a configurable threshold value.

Source Code Listing

[Windows Version (in Visual Basic)] vbdemo\frmRealTimeDemo.frm
Option Explicit

Private cd As New ChartDirector.API

' Data to draw the chart. In this demo, the data buffer will be filled by a random
' data generator. In real life, the data is probably stored in a buffer (eg. a
' database table, a text file, or some global memory) and updated by other means.

' We use a data buffer to store the last 240 samples.
Private Const sampleSize = 240
Private dataSeries1(sampleSize - 1)
Private dataSeries2(sampleSize - 1)
Private dataSeries3(sampleSize - 1)
Private timeStamps(sampleSize - 1)

' This is an internal variable used by the real time random number generator so it
' knows what timestamp should be used for the next data point.
Private nextDateTime As Double

' The standard VB date/time functions only has resolution of 1 second. In this realtime
' chart demo, we need millisecond resolution, so we need to use the GetSystemTime API
Private Declare Sub GetSystemTime Lib "kernel32" (lpSystemTime As SYSTEMTIME)
    wYear As Integer
    wMonth As Integer
    wDayOfWeek As Integer
    wDay As Integer
    wHour As Integer
    wMinute As Integer
    wSecond As Integer
    wMilliseconds As Integer
End Type

' A utility to get the current time at millisecond resolution
Private Function getCurrentTime() As Double

    ' We get the year, month, day, hour from the VB date, as it takes into account
    ' of time zone conversions
    Dim currentDate As Date
    currentDate = Now
    ' We get the minute and seconds using GetSystemTime for millisecond resolution
    Dim currentTime As SYSTEMTIME
    GetSystemTime currentTime
    ' Return the date/time in chartTime format, which can have millisecond resolution
    getCurrentTime = cd.chartTime(Year(currentDate), Month(currentDate), _
        Day(currentDate), Hour(currentDate), currentTime.wMinute, _
        currentTime.wSecond) + currentTime.wMilliseconds / 1000#

End Function

' Utility to shift a value into an array
Private Sub shiftData(data, newValue)
    Dim i
    For i = LBound(data) + 1 To UBound(data)
        data(i - 1) = data(i)
    data(UBound(data)) = newValue
End Sub

' Initialize the Form
Private Sub Form_Load()
    SamplePeriod.Text = 1000
    nextDateTime = getCurrentTime()
    Dim i As Long
    For i = 0 To UBound(timeStamps)
        timeStamps(i) = cd.NoValue
        dataSeries1(i) = cd.NoValue
        dataSeries2(i) = cd.NoValue
        dataSeries3(i) = cd.NoValue
End Sub

' User clicks on the Run pushbutton
Private Sub RunPB_Click()
    ' Enable chart update timer
    ChartUpdateTimer.Enabled = True
End Sub

' User clicks on the Freeze pushbutton
Private Sub FreezePB_Click()
    ' Disable chart update timer
    ChartUpdateTimer.Enabled = False
End Sub

' User changes the chart update period
Private Sub SamplePeriod_Click()
    'Modify timer period
    ChartUpdateTimer.Interval = CInt(SamplePeriod.Text)
End Sub

' User presses the alarm threshold spin button
Private Sub AlarmThreshold_Change()
    ' Update the chart immediately after threshold changes
    Call ChartViewer1.UpdateViewPort(True, False)
End Sub

' The data acquisition routine. In this demo, this is invoked every 250ms.
Private Sub DataRateTimer_Timer()

    Dim currentTime As Double
    currentTime = getCurrentTime()

    ' This is our formula for the data generator
    Dim p, dataA, dataB, dataC
    Do While nextDateTime < currentTime
        ' Get a data sample
        p = nextDateTime - Int(nextDateTime / 86400) * 86400
        dataA = Cos(p * 8.5) * 10 + 1 / (Cos(p) * Cos(p) + 0.01) + 20
        dataB = 100 * Sin(p * 4 / 27.7) * Sin(p * 4 / 10.1) + 150
        dataC = 100 * Cos(p * 4 / 6.7) * Cos(p * 4 / 11.9) + 150
        ' Shift the values into the arrays
        shiftData dataSeries1, dataA
        shiftData dataSeries2, dataB
        shiftData dataSeries3, dataC
        shiftData timeStamps, nextDateTime
        nextDateTime = nextDateTime + DataRateTimer.Interval / 1000#

    ' We provide some visual feedback to the numbers generated, so you can see the
    ' data being updated.
    valueA.Caption = Round(dataSeries1(UBound(dataSeries1)), 2)
    valueB.Caption = Round(dataSeries2(UBound(dataSeries2)), 2)
    valueC.Caption = Round(dataSeries3(UBound(dataSeries3)), 2)

End Sub

' Chart update timer handler
Private Sub ChartUpdateTimer_Timer()
     Call ChartViewer1.UpdateViewPort(True, False)
End Sub

' View port changed event
Private Sub ChartViewer1_ViewPortChanged(needUpdateChart As Boolean, _
    needUpdateImageMap As Boolean)
    Call drawChart(ChartViewer1)
End Sub

' Draw the chart and display it in the given viewer
Private Sub drawChart(viewer As ChartViewer)

    ' Create an XYChart object 600 x 270 pixels in size, with light grey (f4f4f4)
    ' background, black (000000) border, 1 pixel raised effect, and with a rounded frame.
    Dim c As XYChart
    Set c = cd.XYChart(600, 270, &HF4F4F4, &H0, 0)
    Call c.setRoundedFrame
    ' Set the plotarea at (55, 62) and of size 520 x 175 pixels. Use white (ffffff)
    ' background. Enable both horizontal and vertical grids by setting their colors to
    ' grey (cccccc). Set clipping mode to clip the data lines to the plot area.
    Call c.setPlotArea(55, 62, 520, 175, &HFFFFFF, -1, -1, &HCCCCCC, &HCCCCCC)
    Call c.setClipping
    ' Add a title to the chart using 15 pts Times New Roman Bold Italic font, with a
    ' light grey (dddddd) background, black (000000) border, and a glass like raised
    ' effect.
    Call c.addTitle("Realtime Chart Demonstration", "timesbi.ttf", 15 _
        ).setBackground(&HDDDDDD, &H0, cd.glassEffect())
    ' Add a legend box at the top of the plot area with 9pts Arial Bold font. We set the
    ' legend box to the same width as the plot area and use grid layout (as opposed to
    ' flow or top/down layout). This distributes the 3 legend icons evenly on top of the
    ' plot area.
    Dim b As legendBox
    Set b = c.addLegend2(55, 33, 3, "arialbd.ttf", 9)
    Call b.setBackground(cd.Transparent, cd.Transparent)
    Call b.setWidth(520)
    ' Configure the y-axis with a 10pts Arial Bold axis title
    Call c.yAxis().setTitle("Price (USD)", "arialbd.ttf", 10)
    ' Set the x-axis to auto-scale with at least 75 pixels between major tick and 15
    ' pixels between minor ticks. This shows more minor grid lines on the chart.
    Call c.xAxis().setTickDensity(75, 15)
    ' Set the axes width to 2 pixels
    Call c.xAxis().setWidth(2)
    Call c.yAxis().setWidth(2)
    ' Now we add the data to the chart.
    Dim lastTime As Double
    lastTime = timeStamps(UBound(timeStamps))
    If lastTime <> cd.NoValue Then
        ' Set up the x-axis to show the time range in the data buffer
        Call c.xAxis().setDateScale(lastTime - DataRateTimer.Interval * _
            (UBound(timeStamps) + 1) / 1000#, lastTime)
        ' Set the x-axis label format
        Call c.xAxis().setLabelFormat("{value|hh:nn:ss}")
        ' Create a line layer to plot the lines
        Dim layer As lineLayer
        Set layer = c.addLineLayer2()
        ' The x-coordinates are the timeStamps.
        Call layer.setXData(timeStamps)
        ' The 3 data series are used to draw 3 lines. Here we put the latest data values
        ' as part of the data set name, so you can see them updated in the legend box.
        Call layer.addDataSet(dataSeries1, &HFF0000, c.formatValue(dataSeries1( _
            UBound(dataSeries1)), "Software: <*bgColor=FFCCCC*> {value|2} "))
        Call layer.addDataSet(dataSeries2, &HCC00, c.formatValue(dataSeries2( _
            UBound(dataSeries2)), "Hardware: <*bgColor=CCFFCC*> {value|2} "))
        Call layer.addDataSet(dataSeries3, &HFF, c.formatValue(dataSeries3( _
            UBound(dataSeries3)), "Services: <*bgColor=CCCCFF*> {value|2} "))
        ' To show the capabilities of ChartDirector, we are add a movable threshold
        ' line to the chart and dynamically print a warning message on the chart if
        ' a data value exceeds the threshold
        Dim thresholdValue As Double
        thresholdValue = CDbl(AlarmThreshold.Text)

        ' Add a red mark line to the chart, with the mark label shown at the left of
        ' the mark line.
        Dim m As Mark
        Set m = c.yAxis().addMark(thresholdValue, &HFF0000, "Alarm = " & thresholdValue)
        Call m.setAlignment(cd.Left)
        Call m.setBackground(&HFFCCCC)

        If dataSeries3(UBound(dataSeries3)) > thresholdValue Or _
            dataSeries2(UBound(dataSeries2)) > thresholdValue Then
            ' Add an alarm message as a custom text box on top-right corner of the
            ' plot area if the latest data value exceeds threshold.
            Call c.addText(575, 62, "Alarm - Latest Value Exceeded Threshold", _
                "arialbi.ttf", 10, &HFFFFFF, cd.TopRight).setBackground(&HDD0000)
        End If

        ' Fill the region above the threshold as semi-transparent red (80ff8888)
        Call c.addInterLineLayer(layer.getLine(1), m.getLine(), &H80FF8888, cd.Transparent)
        Call c.addInterLineLayer(layer.getLine(2), m.getLine(), &H80FF8888, cd.Transparent)
    End If
    Set viewer.Picture = c.makePicture()
End Sub