The Demo shows, how one can use the built-in cChart-HelperClass of the RC6, to "ease the pain" with:
- correct handling of ChartArea-Offsets
- rendering of x- and y-Axis-Ticks and -Texts at the correct (offset) positions
- and last but not least: "reverse Mouse-Interaction" (resolving to the right Data-Values from Mouse-Coords)
The Project needs only a reference to RC6 - and should only contain...
an empty Form with this code:
and a single Drawing-Helper-Class, named cStockDataDays:
These two code-modules will then produce:
![Name: CandleChart.png
Views: 33
Size: 34.3 KB]()
Have fun,
Olaf
- correct handling of ChartArea-Offsets
- rendering of x- and y-Axis-Ticks and -Texts at the correct (offset) positions
- and last but not least: "reverse Mouse-Interaction" (resolving to the right Data-Values from Mouse-Coords)
The Project needs only a reference to RC6 - and should only contain...
an empty Form with this code:
Code:
Option Explicit
Private SDD As cStockDataDays
Private Sub Form_Load()
Caption = "Resize Me": AutoRedraw = True
Set SDD = New cStockDataDays
LoadNewDataInto SDD
End Sub
Sub LoadNewDataInto(SDD As cStockDataDays) 'simulate "recent week-data"... (7 added records)
SDD.ClearData 'reset the SDD-internal Data-Buffers
SDD.AddRecord Date - 6, 115, 125, 110, 135 'Params: xDay, yOpen, yClose, yLow, yHigh
SDD.AddRecord Date - 5, 135, 155, 130, 160
SDD.AddRecord Date - 4, 150, 165, 150, 175
SDD.AddRecord Date - 3, 170, 175, 160, 185
SDD.AddRecord Date - 2, 165, 155, 155, 175 'the last 3 records fullfill a "OpenPrice>ClosePrice"-condition
SDD.AddRecord Date - 1, 150, 145, 140, 155
SDD.AddRecord Date - 0, 135, 125, 120, 150 'the last added "Date - 0"-term resolves to "today"
End Sub
Private Sub Form_Resize()
RefreshChartOn Me 're-render the whole chart in case of a change in Canvas-Size (works with Form or PictureBox)
End Sub
Sub RefreshChartOn(VBCanvas As Object)
SDD.Chart.OffsL = 50 'change the Values of two ChartArea-Offset-Props from their defaults...
SDD.Chart.OffsB = 55 '...OffsT and OffsR are available as well of course
'if we have records, then refresh the underlying Chart-Surface (also updating our Form-Canvas)
If SDD.RecordCount Then SDD.RefreshOn VBCanvas, "Some Chart-Title"
End Sub
'demonstrates reverse-transformation from device-space (xPxl/yPxl MouseCoords), to user-space (time/price Coords)
Private Sub Form_MouseMove(Button As Integer, Shift As Integer, x As Single, y As Single)
If SDD.RecordCount = 0 Then Exit Sub '<- do nothing, when the chart doesn't hold any data yet
Dim xPxlChart, yPxlChart, xDataValue, yDataValue 'ByRef-Var definitions (filled in the next line)
SDD.Chart.MouseCoordsToChartCoords x, y, xPxlChart, yPxlChart, xDataValue, yDataValue
Caption = Format$(xDataValue + 0.5, "yyyy-mm-dd hh:nn") & " " & Format$(yDataValue, "$0.00")
End Sub
Code:
Option Explicit
Public WithEvents Chart As cChart
Private mData(), mRecordCount As Long, mYMin As Double, mYMax As Double
Private Sub Class_Initialize()
Set Chart = New_c.Chart 'initialize the internal Public Chart-Variable to a new helper-instance
ClearData 'init the internal Private-Vars to their default-state
End Sub
Public Sub ClearData() 'implemented as a Public Method, to allow a "reset for new Data" also from the outside
ReDim mData(4, 0): mRecordCount = 0: mYMin = 1E+35: mYMax = -1E+35
End Sub
Public Sub AddRecord(ByVal xDay As Date, ByVal yOpen#, ByVal yClose#, ByVal yLow#, ByVal yHigh#)
ReDim Preserve mData(0 To 4, mRecordCount) 'prolong our 2D-Array by a "new Row"...
mData(0, mRecordCount) = xDay '... and copy the incoming params over
mData(1, mRecordCount) = yOpen
mData(2, mRecordCount) = yClose
mData(3, mRecordCount) = yLow
mData(4, mRecordCount) = yHigh
Dim i As Long
For i = 1 To 4 'determine the total y-Min/Max across all Y-Columns (which start at Index 1)
If mYMin > mData(i, mRecordCount) Then mYMin = mData(i, mRecordCount)
If mYMax < mData(i, mRecordCount) Then mYMax = mData(i, mRecordCount)
Next
mRecordCount = mRecordCount + 1 'increase this for the next round
End Sub
Public Property Get RecordCount() As Long
RecordCount = mRecordCount
End Property
Public Function RefreshOn(VBCanvas As Object, Optional Title As String) As cCairoSurface 'return the used Surface as well
VBCanvas.ScaleMode = vbPixels
Set RefreshOn = Chart.Render(mData, VBCanvas.ScaleWidth, VBCanvas.ScaleHeight, Title) 'the Render-call returns a cCairoSurface
RefreshOn.DrawToDC VBCanvas.hDC 'Blit the generated content of the returned Surface to the VBCanvas-Obj (a Form or PictureBox)
If VBCanvas.AutoRedraw Then VBCanvas.Refresh 'call the Refresh-method on the Canvas, in case AutoRedraw is "On"
End Function
'------------------ Event-interface-implementation of cChart -----------------------------
Private Sub Chart_DrawChartBackGroundAndTitle(CC As RC6.cCairoContext, ByVal Title As String)
CC.Paint 1, Cairo.CreateSolidPatternLng(&H888888)
CC.SelectFont "Arial", 13, vbBlue, True, True
CC.DrawText 0, 5, CC.Surface.Width, Chart.OffsT, Title, True, vbCenter, 0, 1
End Sub
Private Sub Chart_DrawChartAreaRect(CC As cCairoContext, ByVal x As Long, ByVal y As Long, ByVal dx As Long, ByVal dy As Long)
CC.Rectangle x, y, dx, dy: CC.Fill , Cairo.CreateSolidPatternLng(vbBlack)
End Sub
Private Sub Chart_OverrideAxisProps(Axis As cChartAxis, ByVal CurrentMin As Double, ByVal CurrentMax As Double, ByVal CurrentTickIntervals As Long)
If mRecordCount = 0 Then Exit Sub 'nothing to do here
Select Case Axis.Name
Case "X" 're-adjust the Min/Max-DayValues of the X-Axis to "one day more"
Axis.Min = mData(0, 0) - 0.5 '...by shifting our Min-DayValue "half a day to the left"
Axis.Max = mData(0, mRecordCount - 1) + 0.5 '...and the Max-DayValue "half a day to the right"
Axis.TickIntervals = mRecordCount
Case "Y" 're-adjust the Y-Axis to the "total Min/Max" we found across all "Y-Fields"
Axis.Min = mYMin
Axis.Max = mYMax
End Select
End Sub
Private Sub Chart_DrawSingleTickForAxis(Axis As cChartAxis, CC As cCairoContext, ByVal TickValue As Variant, ByVal x As Long, ByVal y As Long, ByVal dx As Long, ByVal dy As Long)
Select Case Axis.Name
Case "X"
If x > Chart.OffsL Then x = x - Axis.TickDistPxl / 2 Else Exit Sub
CC.DrawLine x, y + dy, x, y + dy + 5, True, 1, vbMagenta
Axis.DrawTickText CC, x, y + 10, Format$(TickValue, "mmm-dd")
Axis.DrawTickText CC, x, y + 23, Format$(TickValue, "yyyy")
Case "Y"
CC.DrawLine x - 5, y, x, y, True, 1, vbYellow
Axis.DrawTickText CC, x - 3, y, Format$(TickValue, "0.0")
End Select
' Debug.Print Axis.Name, x, y, TickValue, Axis.TickDist, Axis.TickDistPxl
End Sub
Private Sub Chart_DrawData(CC As cCairoContext, DataArr() As Variant, ByVal dx As Long, ByVal dy As Long)
Dim xPxlSC As Double, yPxlSC As Double, dstPxl As Double, i As Long
xPxlSC = Chart.AxisCol("X").PxlScaleFac 'get the Axis-ScaleFactors into local Vars, before entering the Loop at the end
yPxlSC = Chart.AxisCol("Y").PxlScaleFac
dstPxl = Chart.AxisCol("X").TickDistPxl / 5: If dstPxl > 15 Then dstPxl = 15
For i = 0 To mRecordCount - 1
RenderCandleOn CC, i, xPxlSC, yPxlSC, dstPxl, False 'delegate to a helper-routine
Next
End Sub
Private Sub RenderCandleOn(CC As cCairoContext, ByVal RecIdx&, ByVal xPxlSC#, ByVal yPxlSC#, ByVal dstPxl#, Optional ByVal FillIt As Boolean)
Dim xDay#: xDay = mData(0, RecIdx) * xPxlSC
Dim yOpen#: yOpen = mData(1, RecIdx) * yPxlSC
Dim yClose#: yClose = mData(2, RecIdx) * yPxlSC
Dim yLow#: yLow = mData(3, RecIdx) * yPxlSC
Dim yHigh#: yHigh = mData(4, RecIdx) * yPxlSC
Dim Color&: Color = IIf(yClose < yOpen, vbRed, vbGreen)
If yClose < yOpen And yLow < yHigh Then '<- in this case...
Dim yTmp#: yTmp = yLow: yLow = yHigh: yHigh = yTmp '...swap yLow and yHigh (for correct "Path-rendering-order")
End If
CC.Save 'buffer the prior TransForm-Matrix (the prior Coord-Sys)
CC.TranslateDrawings xDay, 0 'to be able, to leave out the xDay(Offs) in all x-Params of the following block
CC.MoveTo 0, yLow
CC.LineTo 0, yOpen
CC.LineTo -dstPxl, yOpen: CC.LineTo -dstPxl, yClose
CC.LineTo 0, yClose
CC.LineTo 0, yHigh
CC.LineTo 0, yClose
CC.LineTo dstPxl, yClose: CC.LineTo dstPxl, yOpen
CC.LineTo 0, yOpen
CC.Restore 'restore the prior TransForm-Matrix
CC.SetSourceColor Color
CC.SetLineWidth 1
If FillIt Then CC.Fill True
CC.Stroke
End Sub
Have fun,
Olaf