Platforms to show: All Mac Windows Linux Cross-Platform

/ChartDirector/RealTime ViewPort
Function:
Required plugins for this example: MBS ChartDirector Plugin
You find this example project in your Plugins Download as a Xojo project file within the examples folder: /ChartDirector/RealTime ViewPort
This example is the version from Thu, 31th Mar 2021.
Project "RealTime ViewPort.xojo_binary_project"
Class App Inherits Application
Const kEditClear = "&Delete"
Const kFileQuit = "&Quit"
Const kFileQuitShortcut = ""
EventHandler Sub Open() // CDBaseChartMBS.set License Code ... CDBaseChartMBS.ScaleFactor = 2.0 // HiDPI End EventHandler
End Class
Class RealTimeWindow Inherits Window
Const initialFullRange = 180
Const initialVisibleRange = 30
Const sampleSize = 10000
Const zoomInLimit = 5
Control savePB Inherits BevelButton
ControlInstance savePB Inherits BevelButton
EventHandler Sub Action() onSave End EventHandler
End Control
Control zoomOutPB Inherits BevelButton
ControlInstance zoomOutPB Inherits BevelButton
EventHandler Sub Action() If Me.Value Then onMouseUsageChanged ChartViewer.MouseUsageZoomOut pointerPB.Value = False zoomInPB.Value = False End If End EventHandler
End Control
Control zoomInPB Inherits BevelButton
ControlInstance zoomInPB Inherits BevelButton
EventHandler Sub Action() If Me.Value Then onMouseUsageChanged ChartViewer.MouseUsageZoomIn pointerPB.Value = False zoomOutPB.Value = False End If End EventHandler
End Control
Control pointerPB Inherits BevelButton
ControlInstance pointerPB Inherits BevelButton
EventHandler Sub Action() If Me.Value Then onMouseUsageChanged ChartViewer.MouseUsageDefault zoomInPB.Value = False zoomOutPB.Value = False End If End EventHandler
End Control
Control ChartViewer Inherits ChartViewerCanvas
ControlInstance ChartViewer Inherits ChartViewerCanvas
EventHandler Sub mouseMovePlotArea(MouseEvent as MouseEvent) self.onMouseMovePlotArea MouseEvent End EventHandler
EventHandler Sub viewPortChanged() Self.onViewPortChanged ViewPortControl.onViewPortChanged End EventHandler
End Control
Control ChartUpdateTimer Inherits Timer
ControlInstance ChartUpdateTimer Inherits Timer
EventHandler Sub Action() onChartUpdateTimer End EventHandler
End Control
Control LeftLine Inherits Line
ControlInstance LeftLine Inherits Line
End Control
Control TopLine Inherits Line
ControlInstance TopLine Inherits Line
End Control
Control BottomLine Inherits Line
ControlInstance BottomLine Inherits Line
End Control
Control RightLine Inherits Line
ControlInstance RightLine Inherits Line
End Control
Control SecondCanvas Inherits ViewPortControlCanvas
ControlInstance SecondCanvas Inherits ViewPortControlCanvas
End Control
Control HelpLabel Inherits Label
ControlInstance HelpLabel Inherits Label
End Control
EventHandler Sub Close() dataSource.stopThread If ViewPortControl <> Nil Then ViewPortControl.Close ViewPortControl = Nil End If End EventHandler
EventHandler Sub Open() Redim Self.timeStamps(sampleSize-1) Redim self.dataSeriesA(sampleSize-1) Redim self.dataSeriesB(sampleSize-1) Setup End EventHandler
Sub InitRandomWalk() dataSource = New RandomWalk AddHandler dataSource.GotNewValues, WeakAddressOf onData dataSource.run End Sub
Sub Setup() // connect line controls ChartViewer.LeftLine = LeftLine ChartViewer.RightLine = RightLine ChartViewer.TopLine = TopLine ChartViewer.BottomLine = BottomLine // Pointer push button pointerPB.Value = True // Zoom In push button zoomInPB.Value = False // Zoom Out push button zoomOutPB.Value = False // Save push button // Chart Viewer 'ChartViewer.setGeometry(0, 0, 640, 350) // Viewport Control ViewPortControl = New ViewPortControl ViewPortControl.Output = SecondCanvas // <- for output in Xojo ViewPortControl.setViewer(ChartViewer) SecondCanvas.ViewPortControl = ViewPortControl ChartViewer.HelpLabel = HelpLabel // // Initialize member variables // Self.currentIndex = 0 // Initially, auto-move the track line to make it follow the data series self.trackLineEndPos = 0 Self.trackLineIsAtEnd = True // Initially set the mouse to drag to scroll mode. pointerPB.Value = True // Enable mouse wheel zooming by setting the zoom ratio to 1.1 per wheel event ChartViewer.setMouseWheelZoomRatio(1.1) // Configure the initial viewport ChartViewer.setViewPortWidth(initialVisibleRange / initialFullRange) // Start the random data generator InitRandomWalk // Set up the chart update timer ChartUpdateTimer.mode = timer.ModeMultiple // The chart update rate is set to 100ms ChartUpdateTimer.Period = 100 End Sub
Sub drawChart(viewer as ChartViewerCanvas) // Draw chart // Get the start date and end date that are visible on the chart. Dim viewPortStartDate As Double = viewer.getValueAtViewPort("x", viewer.getViewPortLeft) Dim viewPortEndDate As Double = viewer.getValueAtViewPort("x", viewer.getViewPortLeft + viewer.getViewPortWidth) // Extract the part of the data arrays that are visible. Dim viewPortTimeStamps() As Double Dim viewPortDataSeriesA() As Double Dim viewPortDataSeriesB() As Double If (self.currentIndex > 0) Then // Get the array indexes that corresponds to the visible start and end dates Dim startIndex As Integer = Floor(CDXYChartMBS.bSearch(DoubleArray(self.timeStamps, self.currentIndex), viewPortStartDate)) Dim endIndex As Integer = Ceil(CDXYChartMBS.bSearch(DoubleArray(self.timeStamps, self.currentIndex), viewPortEndDate)) Dim noOfPoints As Integer = endIndex - startIndex + 1 // Extract the visible data If (self.timeStamps(endIndex) >= viewPortStartDate) Then viewPortTimeStamps = DoubleArray(self.timeStamps , noOfPoints, startIndex) viewPortDataSeriesA = DoubleArray(self.dataSeriesA, noOfPoints, startIndex) viewPortDataSeriesB = DoubleArray(self.dataSeriesB, noOfPoints, startIndex) End If // Keep track of the latest available data at chart plotting time self.trackLineEndPos = self.timeStamps(self.currentIndex - 1) End If // // At this stage, we have extracted the visible data. We can use those data to plot the chart. // //================================================================================ // Configure overall chart appearance. //================================================================================ // Create an XYChart object of size 640 x 350 pixels Dim c As New CDXYChartMBS(640, 350) // Set the plotarea at (20, 30) with width 41 pixels less than chart width, and height 50 pixels // less than chart height. Use a vertical gradient from light blue (f0f6ff) to sky blue (a0c0ff) // as background. Set border to transparent and grid lines to white (ffffff). Call c.setPlotArea(20, 30, c.getWidth - 41, c.getHeight - 50, c.linearGradientColor(0, 30, 0, c.getHeight - 50, &hf0f6ff, &ha0c0ff), -1, c.kTransparent, &hffffff, &hffffff) // As the data can lie outside the plotarea in a zoomed chart, we need enable clipping. c.setClipping // Add a title to the chart using 18pt Arial font Call c.addTitle(" Multithreading Real-Time Chart", "arial.ttf", 18) // Add a legend box at (55, 25) using horizontal layout. Use 10pt Arial Bold as font. Set the // background and border color to transparent and use line style legend key. Dim b As CDLegendBoxMBS = c.addLegend(55, 25, False, "arialbd.ttf", 10) b.setBackground(c.kTransparent) b.setLineStyleKey // Set the x and y axis stems to transparent and the label font to 10pt Arial c.xAxis.setColors(c.kTransparent) c.yAxis.setColors(c.kTransparent) Call c.xAxis.setLabelStyle("arial.ttf", 10) Call c.yAxis.setLabelStyle("arial.ttf", 10, &h336699) // Set the y-axis tick length to 0 to disable the tick and put the labels closer to the axis. c.yAxis.setTickLength(0) // Add axis title using 10pt Arial Bold Italic font Call c.yAxis.setTitle("Ionic Temperature (C)", "arialbd.ttf", 10) // Configure the y-axis label to be inside the plot area and above the horizontal grid lines c.yAxis.setLabelGap(-1) c.yAxis.setLabelAlignment(1) c.yAxis.setMargin(20) // Configure the x-axis labels to be to the left of the vertical grid lines c.xAxis.setLabelAlignment(1) //================================================================================ // Add data to chart //================================================================================ // // In this example, we represent the data by lines. You may modify the code below to use other // representations (areas, scatter plot, etc). // // Add a line layer for the lines, using a line width of 2 pixels Dim layer As CDLineLayerMBS = c.addLineLayer layer.setLineWidth(2) layer.setFastLineMode // Now we add the 2 data series to the line layer with red (ff0000) and green (00cc00) colors layer.setXData(viewPortTimeStamps) Call layer.addDataSet(viewPortDataSeriesA, &hff0000, "Alpha") Call layer.addDataSet(viewPortDataSeriesB, &h00cc00, "Beta") //================================================================================ // Configure axis scale and labelling //================================================================================ // Set the x-axis as a date/time axis with the scale according to the view port x range. If (self.currentIndex > 0) Then c.xAxis.setDateScale(viewPortStartDate, viewPortEndDate, 5.0) End If // For the automatic axis labels, set the minimum spacing to 75/30 pixels for the x/y axis. c.xAxis.setTickDensity(75) c.yAxis.setTickDensity(30) // We use "hh:nn:ss" as the axis label format. c.xAxis.setLabelFormat("{value|hh:nn:ss}") // We make sure the tick increment must be at least 1 second. c.xAxis.setMinTickInc(1) // Set the auto-scale margin to 0.05, and the zero affinity to 0.6 c.yAxis.setAutoScale(0.05, 0.05, 0.6) //================================================================================ // Output the chart //================================================================================ // We need to update the track line too. If the mouse is moving on the chart (eg. if // the user drags the mouse on the chart to scroll it), the track line will be updated // in the MouseMovePlotArea event. Otherwise, we need to update the track line here. If (Not ChartViewer.isInMouseMoveEvent) Then Dim x As Integer If self.trackLineIsAtEnd Then x = c.getWidth Else x = ChartViewer.getPlotAreaMouseX End If Call trackLineLabel(c, x) End If // Set the chart image to the QChartViewer viewer.setChart(c) End Sub
Sub drawFullChart(vpc as ViewPortControl) // Draw the full thumbnail chart and display it in the given QViewPortControl // Create an XYChart object of size 640 x 60 pixels Dim c As New CDXYChartMBS(640, 60) // Set the plotarea with the same horizontal position as that in the main chart for alignment. Call c.setPlotArea(20, 0, c.getWidth - 41, c.getHeight - 1, &hc0d8ff, -1, &hcccccc, c.kTransparent, &hffffff) // Set the x axis stem to transparent and the label font to 10pt Arial c.xAxis.setColors(c.kTransparent) Call c.xAxis.setLabelStyle("Arial", 10) // Put the x-axis labels inside the plot area by setting a negative label gap. Use // setLabelAlignment to put the label at the right side of the tick. c.xAxis.setLabelGap(-1) c.xAxis.setLabelAlignment(1) // Set the y axis stem and labels to transparent (that is, hide the labels) c.yAxis.setColors(c.kTransparent, c.kTransparent) // Add a line layer for the lines with fast line mode enabled Dim layer As CDLineLayerMBS = c.addLineLayer layer.setFastLineMode // Now we add the 3 data series to a line layer, using the color red (&hff3333), green // (&h008800) and blue (&h3333cc) layer.setXData( DoubleArray(self.timeStamps, self.currentIndex) ) Call layer.addDataSet(DoubleArray(self.dataSeriesA, self.currentIndex), &hff3333) Call layer.addDataSet(DoubleArray(Self.dataSeriesB, Self.currentIndex), &h008800) // The x axis scales should reflect the full range of the view port Dim lowerLimit As Double = vpc.getViewer.getValueAtViewPort("x", 0) Dim higherLimit As Double = vpc.getViewer.getValueAtViewPort("x", 1) c.xAxis.setDateScale(lowerLimit, higherLimit, 10) c.xAxis.setLabelFormat("{value|nn:ss}") // For the automatic x-axis labels, set the minimum spacing to 75 pixels. c.xAxis.setTickDensity(75) // For the auto-scaled y-axis, as we hide the labels, we can disable axis rounding. This can // make the axis scale fit the data tighter. c.yAxis.setRounding(False, False) // Output the chart vpc.SetChart c End Sub
Sub onChartUpdateTimer() // Get data from the queue, update the viewport and update the chart display if necessary. Dim viewer As ChartViewerCanvas = Self.ChartViewer // Enables auto scroll if the viewport is showing the latest data before the update Dim autoScroll As Boolean = False If (Self.currentIndex > 0) Then Dim value As Double = viewer.getValueAtViewPort("x", viewer.getViewPortLeft + viewer.getViewPortWidth) Dim LastTimestamp As Double = Self.timeStamps(Self.currentIndex - 1) autoScroll = (0.001 + value >= LastTimestamp) End If // // Get new data from the queue and append them to the data arrays // Dim NewQueue() As DataPacket // take old queue Dim Packets() As DataPacket = Queue // and put new array there instead Queue = NewQueue // now did we get data? Dim count As Integer = Packets.Ubound + 1 If count <= 0 Then Return End If // if data arrays have insufficient space, we need to remove some old data. If (Self.currentIndex + count >= sampleSize) Then // For safety, we check if the queue contains too much data than the entire data arrays. If // this is the case, we only use the latest data to completely fill the data arrays. While (count > sampleSize) packets.Remove(0) count = count -1 Wend // Remove oldest data to leave space for new data. To avoid frequent removal, we ensure at // least 5% empty space available after removal. Dim originalIndex As Integer = Self.currentIndex Self.currentIndex = sampleSize * 95 / 100 - 1 If (Self.currentIndex > sampleSize - count) Then Self.currentIndex = sampleSize - count End If For i As Integer = 0 To Self.currentIndex - 1 Dim srcIndex As Integer = i + originalIndex - Self.currentIndex Self.timeStamps(i) = Self.timeStamps(srcIndex) Self.dataSeriesA(i) = Self.dataSeriesA(srcIndex) Self.dataSeriesB(i) = Self.dataSeriesB(srcIndex) Next End If // Append the data from the queue to the data arrays For n As Integer = 0 To count-1 self.timeStamps(self.currentIndex) = packets(n).elapsedTime self.dataSeriesA(self.currentIndex) = packets(n).series0 self.dataSeriesB(self.currentIndex) = packets(n).series1 self.currentIndex = self.currentIndex + 1 Next // // As we added more data, we may need to update the full range of the viewport. // Dim startDate As Double = self.timeStamps(0) Dim endDate As Double = self.timeStamps(self.currentIndex - 1) // Use the initialFullRange (which is 60 seconds in this demo) if this is sufficient. Dim duration As Double = endDate - startDate If (duration < initialFullRange) Then endDate = startDate + initialFullRange End If // Update the new full data range to include the latest data Const KeepVisibleRange = 1 Dim axisScaleHasChanged As Boolean = viewer.updateFullRangeH("x", startDate, endDate, KeepVisibleRange) If (autoScroll) Then // Scroll the viewport if necessary to display the latest data Dim viewPortEndPos As Double = viewer.getViewPortAtValue("x", Self.timeStamps(Self.currentIndex - 1)) Dim ViewPortLeft As Double = viewer.getViewPortLeft Dim ViewPortWidth As Double = viewer.getViewPortWidth If (viewPortEndPos > ViewPortLeft + ViewPortWidth) Then viewer.setViewPortLeft(viewPortEndPos - ViewPortWidth) axisScaleHasChanged = true End If end if // Set the zoom in limit as a ratio to the full range viewer.setZoomInWidthLimit(zoomInLimit / (viewer.getValueAtViewPort("x", 1) - viewer.getValueAtViewPort("x", 0))) // Trigger the viewPortChanged event. Updates the chart if the axis scale has changed // (scrolling or zooming) or if new data are added to the existing axis scale. viewer.updateViewPort(axisScaleHasChanged Or (duration < initialFullRange), False) End Sub
Sub onData(r As RandomWalk, currentTime As Double, value1 As Double, value2 As Double) // // Handles realtime data from RandomWalk. The RandomWalk will call this method from its own thread. // This is connect to an event // Dim p As New DataPacket p.elapsedTime = currentTime p.series0 = value1 p.series1 = value2 Queue.Append p #Pragma unused r End Sub
Sub onMouseMovePlotArea(MouseEvent as MouseEvent) // Draw track cursor when mouse is moving over plotarea Dim BaseChart As CDBaseChartMBS = ChartViewer.currentChart Dim Chart As CDXYChartMBS = CDXYChartMBS(BaseChart) Dim trackLinePos As Double = trackLineLabel(Chart, ChartViewer.getPlotAreaMouseX) self.trackLineIsAtEnd = (self.currentIndex <= 0) or (trackLinePos = self.trackLineEndPos) ChartViewer.updateDisplay #Pragma unused MouseEvent End Sub
Sub onMouseUsageChanged(mouseUsage as integer) // Pointer/zoom in/zoom out button clicked ChartViewer.setMouseUsage(mouseUsage) End Sub
Sub onSave() Dim p As Picture = ChartViewer.GetPicture If p <> Nil Then Dim f As FolderItem = GetSaveFolderItem(FileTypes1.Png, "chart.png") If f <> Nil Then p.Save(f, Picture.SaveAsPNG) End If End If End Sub
Sub onViewPortChanged() // View port changed event // Update the chart if necessary If (ChartViewer.needUpdateChart) Then drawChart(ChartViewer) End If // Update the full chart drawFullChart(ViewPortControl) End Sub
Function trackLineLabel(c as CDXYChartMBS, mouseX as Integer) As double // Draw the track line with data point labels // Clear the current dynamic layer and get the DrawArea object to draw on it. Dim d As CDDrawAreaMBS = c.initDynamicLayer // The plot area object Dim plotArea As CDPlotAreaMBS = c.getPlotArea // Get the data x-value that is nearest to the mouse, and find its pixel coordinate. dim xValue as double = c.getNearestXValue(mouseX) Dim xCoor As Integer = c.getXCoor(xValue) If (xCoor < plotArea.getLeftX) then Return xValue end if // Draw a vertical track line at the x-position d.vline(plotArea.getTopY, plotArea.getBottomY, xCoor, &h888888) // Draw a label on the x-axis to show the track line position. Dim xlabel As String xlabel = "<*font,bgColor=000000*> " xlabel = xlabel + c.xAxis.getFormattedLabel(xValue + 0.00499, "hh:nn:ss.ff") xlabel = xlabel + " <*/font*>" Dim t As CDTTFTextMBS = d.Text(xlabel, "arialbd.ttf", 10) // Restrict the x-pixel position of the label to make sure it stays inside the chart image. Dim xLabelPos As Integer = Max(0, Min(xCoor - t.getWidth / 2, c.getWidth - t.getWidth)) t.draw(xLabelPos, plotArea.getBottomY + 2, &hffffff) t.destroy // Iterate through all layers to draw the data labels For i As Integer = 0 To c.getLayerCount-1 Dim layer As CDLayerMBS = c.getLayerByZ(i) // The data array index of the x-value Dim xIndex As Integer = layer.getXIndexOf(xValue) // Iterate through all the data sets in the layer For j As Integer = 0 To layer.getDataSetCount-1 Dim dataSet As CDDataSetMBS = layer.getDataSetByZ(j) Dim dataSetName As String = dataSet.getDataName // Get the color, name and position of the data label Dim ColorValue As Integer = dataSet.getDataColor Dim yCoor As Integer = c.getYCoor(dataSet.getPosition(xIndex), dataSet.getUseYAxis) // Draw a track dot with a label next to it for visible data points in the plot area If ((yCoor >= plotArea.getTopY) And (yCoor <= plotArea.getBottomY) And (ColorValue <> c.kTransparent) And dataSetName.Len > 0) Then d.circle(xCoor, yCoor, 4, 4, ColorValue, ColorValue) Dim label as string label = "<*font,bgColor=" label = label + Right("000000"+Hex(ColorValue), 6) label = label + "*> " label = label + c.formatValue(dataSet.getValue(xIndex), "{value|P4}") label = label + " <*font*>" t = d.Text(label, "arialbd.ttf", 10) // Draw the label on the right side of the dot if the mouse is on the left side the // chart, and vice versa. This ensures the label will not go outside the chart image. If (xCoor <= (plotArea.getLeftX + plotArea.getRightX) / 2) Then t.draw(xCoor + 6, yCoor, &hffffff, c.kLeft) Else t.draw(xCoor - 6, yCoor, &hffffff, c.kRight) End If t.destroy end if next Next Return xValue End Function
Property Queue() As DataPacket
Property ViewPortControl As ViewPortControl
Property currentIndex As Integer
Property dataSeriesA() As double
Property dataSeriesB() As double
Property dataSource As RandomWalk
Property fullChart As CDXYChartMBS
Property timeStamps() As Double
Property trackLineEndPos As double
If the track cursor Is at the End Of the data series, we will automatic move the track line when new data arrives.
Property trackLineIsAtEnd As Boolean
If the track cursor Is at the End Of the data series, we will automatic move the track line when new data arrives.
End Class
MenuBar MainMenuBar
MenuItem FileMenu = "&File"
MenuItem FileQuit = "#App.kFileQuit"
MenuItem EditMenu = "&Edit"
MenuItem EditUndo = "&Undo"
MenuItem EditSeparator1 = "-"
MenuItem EditCut = "Cu&t"
MenuItem EditCopy = "&Copy"
MenuItem EditPaste = "&Paste"
MenuItem EditClear = "#App.kEditClear"
MenuItem EditSeparator2 = "-"
MenuItem EditSelectAll = "Select &All"
End MenuBar
Class RandomWalk Inherits Thread
Event GotNewValues(currentTime as double, value1 as double, value2 as Double) End Event
EventHandler Sub Run() Dim currentTime As Int64 = 0 Dim nextTime As Int64 = 0 // Random walk variables Dim series0 As Double = 32 Dim series1 As Double = 63 Dim upperLimit As Double = 94 Dim scaleFactor As Double = Sqrt(interval * 0.3) Dim r As New Random // Variables to keep track of the timing Dim startTime As Int64 = Microseconds While Not stoppedThread // Compute the next data value currentTime = Microseconds - startTime series0 = Abs(series0 + (r.Number - 0.5) * scaleFactor) If (series0 > upperLimit) Then series0 = upperLimit * 2 - series0 End If series1 = Abs(series1 + (r.Number - 0.5) * scaleFactor) If (series1 > upperLimit) Then series1 = upperLimit * 2 - series1 end if // Call the handler Dim milliseconds As Double = currentTime / 1000.0 Dim seconds As Double = milliseconds / 1000.0 RaiseEvent GotNewValues(seconds, series0, series1) 'System.DebugLog Str(milliseconds, "0.0")+" "+Str(series0)+" "+Str(series1) // Sleep until next walk nextTime = nextTime + interval If (nextTime <= currentTime) Then nextTime = currentTime + interval End If Dim delta As Int64 = (nextTime - currentTime) Me.Sleep delta Wend End EventHandler
Sub stopThread() stoppedThread = True app.YieldToNextThread // wait for ending While Me.State = Me.Running app.YieldToNextThread Wend stoppedThread = False End Sub
Property interval As Integer = 100
// The period of the data series in milliseconds. This random series implementation just use the // windows timer for timing. In many computers, the default windows timer resolution is 1/64 sec, // or 15.6ms. This means the interval may not be exactly accurate.
Property stoppedThread As Boolean
End Class
Class DataPacket
Property elapsedTime As double
Property series0 As double
Property series1 As double
End Class
Class ChartViewerCanvas Inherits ViewPortManagerCanvas
Const CLOCKS_PER_SEC = 1000
Const MouseUsageDefault = 0
Const MouseUsageScroll = 1
Const MouseUsageZoomIn = 3
Const MouseUsageZoomOut = 4
Const NEED_DELAY = 1
Const NEED_UPDATE = 2
Const NO_DELAY = 0
Const UNDEFINED_COOR = -1073741823
Event Open() End Event
Event clicked(MouseEvent as MouseEvent) End Event
Event mouseLeaveChart(MouseEvent as MouseEvent) End Event
Event mouseLeavePlotArea(MouseEvent as MouseEvent) End Event
Event mouseMove(MouseEvent as MouseEvent) End Event
Event mouseMoveChart(MouseEvent as MouseEvent) End Event
Event mouseMovePlotArea(MouseEvent as MouseEvent) End Event
Event mouseWheel(MouseEvent as MouseEvent) End Event
Event viewPortChanged() End Event
EventHandler Function MouseDown(X As Integer, Y As Integer) As Boolean Dim m As New MouseEvent(x,y) m.SetButton mousePressEvent(m) Return True End EventHandler
EventHandler Sub MouseDrag(X As Integer, Y As Integer) Dim m As New MouseEvent(x,y) m.SetButton mouseMoveEvent m End EventHandler
EventHandler Sub MouseEnter() 'Dim m As New MouseEvent(Self.Mousex, Self.MouseY) End EventHandler
EventHandler Sub MouseExit() Dim m As New MouseEvent(Self.Mousex, Self.MouseY) leaveEvent m End EventHandler
EventHandler Sub MouseMove(X As Integer, Y As Integer) Dim m As New MouseEvent(x,y) mouseMoveEvent m End EventHandler
EventHandler Sub MouseUp(X As Integer, Y As Integer) Dim m As New MouseEvent(x,y) m.SetButton mouseReleaseEvent(m) End EventHandler
EventHandler Function MouseWheel(X As Integer, Y As Integer, deltaX as Integer, deltaY as Integer) As Boolean Dim m As New MouseEvent(x,y) m.delta = deltay wheelEvent(m) #Pragma Unused deltaX End EventHandler
EventHandler Sub Open() ViewPortManager = New CDViewPortManagerMBS // current chart and hot spot tester Self.currentChart = Nil hotSpotTester = nil // initialize chart configuration selectBoxLineColor = &c000000 selectBoxLineWidth = 2 mouseUsage = ChartViewerCanvas.MouseUsageDefault zoomDirection = CDBaseChartMBS.kDirectionHorizontal zoomInRatio = 2 zoomOutRatio = 0.5 mouseWheelZoomRatio = 1 scrollDirection = CDBaseChartMBS.kDirectionHorizontal minDragAmount = 5 updateInterval = 20 // current state of the mouse isOnPlotArea = false isPlotAreaMouseDown = false isDragScrolling = false currentHotSpot = -1 isClickable = false isMouseTracking = false isInMouseMove = false // chart update rate support needUpdateChart = False needUpdateImageMap = False holdTimerActive = False isInViewPortChanged = false delayUpdateChart = NO_DELAY delayedChart = nil lastMouseMove = 0 delayedMouseEvent = nil delayImageMapUpdate = false // track cursor support autoHideMsg = "" currentMouseX = UNDEFINED_COOR currentMouseY = UNDEFINED_COOR isInMouseMovePlotArea = false // selection rectangle 'LeftLine = 0 'RightLine = 0 'TopLine = 0 'BottomLine = 0 // xojo does it always 'setMouseTracking(True) RaiseEvent Open End EventHandler
EventHandler Sub Paint(g As Graphics, areas() As REALbasic.Rect) If ChartPicture <> Nil Then g.DrawPicture ChartPicture, 0, 0, g.Width, g.height, 0, 0, ChartPicture.Width, ChartPicture.Height End If #Pragma Unused areas End EventHandler
Sub Constructor() // Calling the overridden superclass constructor. Super.Constructor End Sub
Sub SetChart(c as CDBaseChartMBS) // Set the chart to the control Self.currentChart = c Dim map As String ' = c.getHTMLImageMap("test") Self.setImageMap(map) If c <> Nil Then commitPendingSyncAxis(c) If delayUpdateChart <> NO_DELAY Then // render chart now Call c.makeChart End If End If Self.updateDisplay End Sub
Sub applyAutoHide(msg as string) // Attempt to hide the dynamic layer using the specified message If (autoHideMsg = msg) then If (Nil <> currentChart) Then currentChart.removeDynamicLayer End If autoHideMsg = "" updateDisplay End If End Sub
Function clock() As double // in milliseconds Return Microseconds / 1000.0 End Function
Sub commitMouseMove(MouseEvent as MouseEvent) // Remember the mouse coordinates for later use Self.currentMouseX = MouseEvent.x Self.currentMouseY = MouseEvent.y // The chart can be updated more than once during mouse move. For example, it can update due to // drag to scroll, and also due to drawing track cursor. So we delay updating the display until // all all events has occured. Self.delayUpdateChart = NEED_DELAY Self.isInMouseMove = True // Check if mouse is dragging on the plot area Self.isOnPlotArea = Self.isPlotAreaMouseDown Or inPlotArea(MouseEvent.x, MouseEvent.y) If (Self.isPlotAreaMouseDown) Then onPlotAreaMouseDrag(MouseEvent) End If // Emit mouseMoveChart RaiseEvent mouseMoveChart(MouseEvent) If (inExtendedPlotArea(MouseEvent.x, MouseEvent.y)) Then // Mouse is in extended plot area, emit mouseMovePlotArea Self.isInMouseMovePlotArea = True RaiseEvent mouseMovePlotArea(MouseEvent) Elseif (Self.isInMouseMovePlotArea) Then // Mouse was in extended plot area, but is not in it now, so emit mouseLeavePlotArea Self.isInMouseMovePlotArea = False RaiseEvent mouseLeavePlotArea(MouseEvent) applyAutoHide("mouseleaveplotarea") end if // // Show hot spot tool tips if necessary // (Due to QT bug, tooltip cannot be put in delayedMouseEvent, otherwise sometimes tooltip // position will be incorrect.) // If (Self.delayImageMapUpdate) Then Self.delayImageMapUpdate = False If (Not Self.isPlotAreaMouseDown) Then updateViewPort(False, True) End If End If If (MouseEvent.button <> MouseEvent.NoButton) Then // Hide tool tips if mouse button is pressed. Self.HelpTag = "" If HelpLabel <> Nil Then HelpLabel.Text = "" End If Else // Use the ChartDirector ImageMapHandler to determine if the mouse is over a hot spot dim hotSpotNo as integer = 0 If (Nil <> Self.hotSpotTester) Then hotSpotNo = Self.hotSpotTester.getHotSpot(MouseEvent.x, MouseEvent.y) End If // If the mouse is in the same hot spot since the last mouse move event, there is no need // to update the tool tip. If (hotSpotNo <> Self.currentHotSpot) Then // Hot spot has changed - update tool tip text Self.currentHotSpot = hotSpotNo If (hotSpotNo = 0) Then // Mouse is not actually on hanlder hot spot - use default tool tip text and reset // the clickable flag. Self.HelpTag = Self.defaultToolTip Self.isClickable = False If HelpLabel <> Nil Then HelpLabel.text = self.defaultToolTip End If Else // Mouse is on a hot spot. In this implementation, we consider the hot spot as // clickable if its href ("path") parameter is not empty. Dim path As String = Self.hotSpotTester.getValue("path") Self.isClickable = ("" <> path) Dim s As String = Self.hotSpotTester.getValue("title") Self.HelpTag = s If HelpLabel <> Nil Then HelpLabel.Text = s End If End If End If End If // Cancel the delayed mouse event if any If (Self.delayedMouseEvent <> Nil) Then killTimer(delayedMouseEventTimerId) Self.delayedMouseEvent = nil End If // Can update chart now commitUpdateChart Self.isInMouseMove = False Self.lastMouseMove = clock End Sub
Sub commitUpdateChart() // Commit chart to display If (Self.delayUpdateChart = NEED_DELAY) Then // No actual update occur Self.delayUpdateChart = NO_DELAY Return end if // Get the image and metrics for the chart Dim c As CDBaseChartMBS If delayUpdateChart = NEED_UPDATE Then c = Self.delayedChart Else c = Self.currentChart end if Dim ChartMetrics As String dim image as Picture If c <> Nil Then // Output chart as Device Indpendent Bitmap with file headers image = c.makeChartPicture // Get chart metrics ChartMetrics = c.getChartMetrics end if // Set the QPixmap for display Self.ChartPicture = image Self.Invalidate // Set the chart metrics and clear the image map setChartMetrics(ChartMetrics) // Any delayed chart has been committed self.delayUpdateChart = NO_DELAY Self.delayedChart = Nil End Sub
Sub drawRect(x as integer, y as integer, width as integer, height as integer) // Create the edges of the rectangle if not already created // width < 0 is interpreted as the rectangle extends to the left or x. // height <0 is interpreted as the rectangle extends to above y. If (width < 0) Then width = -width x = x - width End If If (height < 0) Then height = -height y = y - height End If // Put the edges along the sides of the rectangle TopLine.X1 = Me.Left + x TopLine.Y1 = Me.top + y TopLine.X2 = TopLine.X1 + width TopLine.Y2 = TopLine.Y1 TopLine.LineColor = selectBoxLineColor TopLine.BorderWidth = selectBoxLineWidth LeftLine.X1 = Me.Left + x LeftLine.Y1 = Me.top + y LeftLine.X2 = LeftLine.X1 LeftLine.Y2 = LeftLine.Y1 + height LeftLine.LineColor = selectBoxLineColor LeftLine.BorderWidth = selectBoxLineWidth BottomLine.X1 = Me.Left + x BottomLine.Y1 = Me.top + y + height - selectBoxLineWidth + 1 BottomLine.X2 = BottomLine.X1 + width BottomLine.Y2 = BottomLine.Y1 BottomLine.LineColor = selectBoxLineColor BottomLine.BorderWidth = selectBoxLineWidth RightLine.X1 = Me.Left + x + width - selectBoxLineWidth + 1 RightLine.Y1 = Me.top + y RightLine.X2 = RightLine.X1 RightLine.Y2 = RightLine.Y1 + height RightLine.LineColor = selectBoxLineColor RightLine.BorderWidth = selectBoxLineWidth End Sub
Function getChart() As CDBaseChartMBS // Get back the same BaseChart pointer provided by the previous setChart call. Return self.currentChart End Function
Function getChartMouseX() As integer // Get the current mouse x coordinate when used in a mouse move event handler Dim ret As Integer = currentMouseX If (ret = UNDEFINED_COOR) then ret = self.getPlotAreaLeft + self.getPlotAreaWidth End If Return ret End Function
Function getChartMouseY() As integer // Get the current mouse y coordinate when used in a mouse move event handler Dim ret As Integer = currentMouseY If ret = UNDEFINED_COOR then ret = Self.getPlotAreaTop + Self.getPlotAreaHeight End If Return ret End Function
Function getImageMapHandler() As CDImageMapHandlerMBS // Get the image map handler for the chart Return hotSpotTester End Function
Function getMinimumDrag() As Integer // Get the minimum mouse drag before the dragging is considered as real. Return minDragAmount End Function
Function getMouseUsage() As Integer Return mouseUsage End Function
Function getMouseWheelZoomRatio() As double // Get the mouse wheel zoom ratio Return mouseWheelZoomRatio End Function
Function getPicture() As Picture If ChartPicture <> Nil Then Return ChartPicture Else // create if needed Return currentChart.makeChartPicture End If End Function
Function getPlotAreaMouseX() As integer // Get the current mouse x coordinate bounded to the plot area when used in a mouse event handler Dim ret As Integer = getChartMouseX If (ret < getPlotAreaLeft) then ret = getPlotAreaLeft End If If (ret > getPlotAreaLeft + getPlotAreaWidth) Then ret = getPlotAreaLeft + getPlotAreaWidth End If Return ret End Function
Function getPlotAreaMouseY() As integer // Get the current mouse y coordinate bounded to the plot area when used in a mouse event handler Dim ret As Integer = getChartMouseY If (ret < getPlotAreaTop) Then ret = getPlotAreaTop End If If (ret > getPlotAreaTop + getPlotAreaHeight) Then ret = getPlotAreaTop + getPlotAreaHeight End If Return ret End Function
Function getScrollDirection() As Integer // Get the scroll direction Return scrollDirection End Function
Function getSelectionBorderColor() As color // Get the border color of the selection box. Return selectBoxLineColor End Function
Function getSelectionBorderWidth() As Integer Return selectBoxLineWidth End Function
Function getUpdateInterval() As Integer // Get the minimum interval between ViewPortChanged events. Return Self.updateInterval End Function
Function getZoomDirection() As Integer // Get the zoom direction Return zoomDirection End Function
Function getZoomInRatio() As double // Get the zoom-in ratio for mouse click zoom-in // Return zoomInRatio End Function
Function getZoomOutRatio() As Double // Get the zoom-out ratio Return zoomOutRatio End Function
Sub initRect() // Create the edges for the selection rectangle 'LeftLine = New QLabel(this); 'LeftLine.setAutoFillBackground(True); 'RightLine = New QLabel(this); 'RightLine.setAutoFillBackground(True); 'TopLine = New QLabel(this); 'TopLine.setAutoFillBackground(True); 'BottomLine = New QLabel(this); 'BottomLine.setAutoFillBackground(True); setSelectionBorderColor(getSelectionBorderColor) End Sub
Function isDrag(direction as integer, MouseEvent as MouseEvent) As Boolean // Determines if the mouse is dragging. // We only consider the mouse is dragging it is has dragged more than self.minDragAmount. This is // to avoid small mouse vibrations triggering a mouse drag. Dim spanX As Integer = Abs(MouseEvent.X - self.plotAreaMouseDownXPos) Dim spanY As Integer = Abs(MouseEvent.Y - Self.plotAreaMouseDownYPos) Return _ ((direction <> CDBaseChartMBS.kDirectionVertical ) And (spanX >= minDragAmount)) Or _ ((direction <> CDBaseChartMBS.kDirectionHorizontal) And (spanY >= minDragAmount)) End Function
Function isInMouseMoveEvent() As Boolean // Check if is currently processing a mouse move event Return self.isInMouseMove End Function
Function isInViewPortChangedEvent() As Boolean // Check if is currently processing a view port changed event Return isInViewPortChanged End Function
Function isMouseDragging() As Boolean // Check if mouse is dragging to scroll or to select the zoom rectangle Return isPlotAreaMouseDown End Function
Function isMouseOnPlotArea() As Boolean // Check if mouse is on the extended plot area If (isMouseTracking) Then Return inExtendedPlotArea(getChartMouseX, getChartMouseY) Else Return False End If End Function
Sub killTimer(byref t as timer) If t <> Nil Then t.Mode = timer.ModeOff t = Nil End If End Sub
Sub leaveEvent(MouseEvent as MouseEvent) // Process delayed mouse move, if any onDelayedMouseMove // Mouse tracking is no longer active Self.isMouseTracking = false If (self.isInMouseMovePlotArea) then // Mouse was in extended plot area, but is not in it now, so emit mouseLeavePlotArea self.isInMouseMovePlotArea = false RaiseEvent mouseLeavePlotArea(MouseEvent) applyAutoHide("mouseleaveplotarea") End If // emit mouseLeaveChart RaiseEvent mouseLeaveChart(MouseEvent) applyAutoHide("mouseleavechart") End Sub
Sub mouseMoveEvent(MouseEvent as MouseEvent) // Enable mouse tracking to detect mouse leave events Self.isMouseTracking = True RaiseEvent mouseMove(MouseEvent) // On Windows, mouse events can by-pass the event queue. If there are too many mouse events, // the event queue may not get processed, preventing other controls from updating. If two mouse // events are less than 10ms apart, there is a risk of too many mouse events. So we repost the // mouse event as a timer event that is queued up normally, allowing the queue to get processed. Dim timeBetweenMouseMove As Integer = ((clock) - Self.lastMouseMove) * 1000 / CLOCKS_PER_SEC If ((Self.delayedMouseEvent <> Nil And (timeBetweenMouseMove < 250)) Or (timeBetweenMouseMove < 10)) Then If (Nil = Self.delayedMouseEvent) then Self.delayedMouseEventTimerId = startTimer(1) Else Self.delayedMouseEvent = Nil End If Self.delayedMouseEvent = MouseEvent Else commitMouseMove(MouseEvent) End If onSetCursor End Sub
Sub mousePressEvent(MouseEvent as MouseEvent) // Mouse button down event. onDelayedMouseMove If ((MouseEvent.button = MouseEvent.LeftButton) And inPlotArea(MouseEvent.x, MouseEvent.y) And (mouseUsage <> MouseUsageDefault)) Then // Mouse usage is for drag to zoom/scroll. Capture the mouse to prepare for dragging and // save the mouse down position to draw the selection rectangle. Self.isPlotAreaMouseDown = True Self.plotAreaMouseDownXPos = MouseEvent.x Self.plotAreaMouseDownYPos = MouseEvent.y startDrag End If End Sub
Sub mouseReleaseEvent(MouseEvent as MouseEvent) // Mouse button up event. onDelayedMouseMove If ((MouseEvent.button = MouseEvent.LeftButton) and self.isPlotAreaMouseDown) then // Release the mouse capture. self.isPlotAreaMouseDown = false setRectVisible(False) Dim hasUpdate As Boolean = False Select Case Self.mouseUsage Case MouseUsageZoomIn If (canZoomIn(Self.zoomDirection)) Then If (isDrag(Self.zoomDirection, MouseEvent)) Then // Zoom to the drag selection rectangle. hasUpdate = zoomTo(Self.zoomDirection, Self.plotAreaMouseDownXPos, Self.plotAreaMouseDownYPos, MouseEvent.x, MouseEvent.y) Else // User just click on a point. Zoom-in around the mouse cursor position. hasUpdate = zoomAt(Self.zoomDirection, MouseEvent.x, MouseEvent.y, Self.zoomInRatio) end if End If Case MouseUsageZoomOut // Zoom out around the mouse cursor position. If (canZoomOut(Self.zoomDirection)) Then hasUpdate = zoomAt(Self.zoomDirection, MouseEvent.x, MouseEvent.y, Self.zoomOutRatio) End If Else If (Self.isDragScrolling) Then // Drag to scroll. We can update the image map now as scrolling has finished. updateViewPort(False, True) Else // Is not zooming or scrolling, so is just a normal click event. RaiseEvent clicked(MouseEvent) end if End Select self.isDragScrolling = false If (hasUpdate) then // View port has changed - update it. updateViewPort(True, True) End If Else RaiseEvent clicked(MouseEvent) End If onSetCursor End Sub
Sub onDelayedMouseMove() // Delayed MouseMove event handler If (delayedMouseEvent <> nil) Then commitMouseMove(Self.delayedMouseEvent) End If End Sub
Function onMouseWheelZoom(x as integer, y as integer, zDelta as integer) As Boolean // Handles mouse wheel zooming // Zoom ratio = 1 means no zooming If (Self.mouseWheelZoomRatio = 1) Then Return False End If // X and Y zoom ratios Dim rx As Double = 1 Dim ry As Double = 1 If (getZoomDirection <> CDBaseChartMBS.kDirectionVertical) Then If zDelta = 0 Then elseIf (zDelta > 0) Then rx = Self.mouseWheelZoomRatio Else rx = 1.0 / Self.mouseWheelZoomRatio end if End If If (getZoomDirection <> CDBaseChartMBS.kDirectionHorizontal) Then If zDelta = 0 Then elseIf (zDelta > 0) Then ry = Self.mouseWheelZoomRatio Else ry = 1.0 / Self.mouseWheelZoomRatio End If End If // Do the zooming If (zoomAround(x, y, rx, ry)) Then updateViewPort(True, True) End If Return True End Function
Sub onPlotAreaMouseDrag(MouseEvent as MouseEvent) Select Case mouseUsage Case MouseUsageZoomIn // // Mouse is used for zoom in. Draw the selection rectangle if necessary. // Dim isDragZoom As Boolean = canZoomIn(zoomDirection) And isDrag(zoomDirection, MouseEvent) If (isDragZoom) Then Dim spanX As Integer = plotAreaMouseDownXPos - MouseEvent.x Dim spanY As Integer = plotAreaMouseDownYPos - MouseEvent.y Select Case zoomDirection Case CDBaseChartMBS.kDirectionHorizontal drawRect(MouseEvent.x, getPlotAreaTop, spanX, getPlotAreaHeight) Case CDBaseChartMBS.kDirectionVertical drawRect(getPlotAreaLeft, MouseEvent.y, getPlotAreaWidth, spanY) Else drawRect(MouseEvent.x, MouseEvent.y, spanX, spanY) End Select End If setRectVisible(isDragZoom) Case MouseUsageScroll // // Mouse is used for drag scrolling. Scroll and update the view port. // If (isDragScrolling Or isDrag(scrollDirection, MouseEvent)) Then isDragScrolling = True If (dragTo(scrollDirection, MouseEvent.x - plotAreaMouseDownXPos, MouseEvent.y - plotAreaMouseDownYPos)) then updateViewPort(True, False) End If end if End Select End Sub
Sub onSetCursor() If isDragScrolling Then Select Case scrollDirection Case CDBaseChartMBS.kDirectionHorizontal Me.MouseCursor = System.Cursors.ArrowEastWest Case CDBaseChartMBS.kDirectionVertical Me.MouseCursor = System.Cursors.ArrowNorthSouth Else Me.MouseCursor = System.Cursors.StandardPointer End Select Return End If If isOnPlotArea then select case mouseUsage Case MouseUsageZoomIn If (canZoomIn(zoomDirection)) Then Me.MouseCursor = System.Cursors.MagnifyLarger Else Me.MouseCursor = System.Cursors.FingerPointer End If Return Case MouseUsageZoomOut If (canZoomOut(zoomDirection)) Then Me.MouseCursor = System.Cursors.MagnifySmaller Else Me.MouseCursor = System.Cursors.FingerPointer End If Return end Select End If If isClickable Then Me.MouseCursor = System.Cursors.FingerPointer Else Me.MouseCursor = Nil End If End Sub
Sub removeDynamicLayer(msg as string) // Set the message used to remove the dynamic layer autoHideMsg = msg.Lowercase If autoHideMsg = "now" then applyAutoHide(msg) End If End Sub
Sub setDefaultToolTip(d as string) // Set the default tool tip to use Self.defaultToolTip = d End Sub
Sub setImageMap(imageMap as string) // Set image map used by the chart // //delete the existing ImageMapHandler Self.currentHotSpot = -1 Self.isClickable = False //create a new ImageMapHandler to represent the image map If imageMap = "" Then Self.hotSpotTester = Nil Else Self.hotSpotTester = New CDImageMapHandlerMBS(imageMap) End If End Sub
Sub setMinimumDrag(offset as integer) // Set the minimum mouse drag before the dragging is considered as real. This is to avoid small // mouse vibrations triggering a mouse drag. Self.minDragAmount = offset End Sub
Sub setMouseUsage(m as integer) // Set the mouse usage mode Self.mouseUsage = m End Sub
Sub setMouseWheelZoomRatio(ratio as Double) // Set the mouse wheel zoom ratio Self.mouseWheelZoomRatio = ratio End Sub
Sub setRectVisible(b as Boolean) // Show/hide selection rectangle // Create the edges of the rectangle if not already created // Show/hide the edges If TopLine <> Nil Then TopLine.Visible = b LeftLine.Visible = b BottomLine.Visible = b RightLine.Visible = b End If End Sub
Sub setScrollDirection(value as integer) // Set the scroll direction Self.scrollDirection = value End Sub
Sub setSelectionBorderColor(c as color) Self.selectBoxLineColor = c If TopLine <> Nil Then TopLine.LineColor = c End If If LeftLine <> Nil Then LeftLine.LineColor = c End If If RightLine <> Nil Then RightLine.LineColor = c End If If BottomLine <> Nil Then BottomLine.LineColor = c End If End Sub
Sub setSelectionBorderWidth(width as integer) // Set the border width of the selection box Self.selectBoxLineWidth = width End Sub
Sub setUpdateInterval(interval as integer) // Set the minimum interval between ViewPortChanged events. This is to avoid the chart being // updated too frequently. (Default is 20ms between chart updates.) Multiple update events // arrived during the interval will be merged into one chart update and executed at the end // of the interval. Self.updateInterval = interval End Sub
Sub setZoomDirection(value as integer) // Set the zoom direction Self.zoomDirection = value End Sub
Sub setZoomInRatio(value as double) // Set the zoom-in ratio for mouse click zoom-in Self.zoomInRatio = value End Sub
Sub setZoomOutRatio(value as Double) // Set the zoom-out ratio Self.zoomOutRatio = value End Sub
Function startTimer(Interval as integer) As timer Dim t As New timer AddHandler t.Action, WeakAddressOf timerEvent t.Period = Interval t.Mode = timer.ModeMultiple Return t End Function
Sub timerEvent(timerId as timer) // Chart hold timer. If (delayedMouseEvent <> nil and (timerId = delayedMouseEventTimerId)) Then // Is a delayed mouse move event onDelayedMouseMove Elseif (holdTimerActive And (timerId = holdTimerId)) then holdTimerId.mode = timer.ModeOff holdTimerActive = False // If has pending chart update request, handles them now. If (self.needUpdateChart Or self.needUpdateImageMap) Then updateViewPort(self.needUpdateChart, self.needUpdateImageMap) End If End If End Sub
Sub updateDisplay() // Update the display If delayUpdateChart = NO_DELAY Then commitUpdateChart Else delayUpdateChart = NEED_UPDATE delayedChart = currentChart End If End Sub
Sub updateViewPort(NeedUpdateChartParam as Boolean, needUpdateImageMapParam as Boolean) // Trigger the ViewPortChanged event // Already updating, no need to update again If (Self.isInViewPortChanged) Then Return end if // Merge the current update requests with any pending requests. Self.needUpdateChart = Self.needUpdateChart Or needUpdateChartParam self.needUpdateImageMap = needUpdateImageMapParam // Hold timer has not expired, so do not update chart immediately. Keep the requests pending. If (Self.holdTimerActive) Then Return end if // The chart can be updated more than once during mouse move. For example, it can update due to // drag to scroll, and also due to drawing track cursor. So we delay updating the display until // all all updates has occured. Dim hasDelayUpdate As Boolean = (Self.delayUpdateChart <> NO_DELAY) If (Not hasDelayUpdate) Then Self.delayUpdateChart = NEED_DELAY End If // Can trigger the ViewPortChanged event. validateViewPort Self.isInViewPortChanged = True RaiseEvent viewPortChanged Self.isInViewPortChanged = False // Can update chart now If (Not hasDelayUpdate) Then commitUpdateChart End If // Clear any pending updates. Self.needUpdateChart = False self.needUpdateImageMap = False // Set hold timer to prevent multiple chart updates within a short period. If (Self.updateInterval > 0) Then Self.holdTimerActive = True Self.holdTimerId = startTimer(self.updateInterval) End If End Sub
Sub wheelEvent(MouseEvent as MouseEvent) Dim hasReceivers As Boolean = True // receivers(SIGNAL(mouseWheel(QWheelEvent *))) > 0; If (hasReceivers) Then // Process delayed mouse move, if any onDelayedMouseMove // emit mouseWheel event RaiseEvent mouseWheel(MouseEvent) End If // Process the mouse wheel only if the mouse is over the plot area Dim hasMouseWheelZoom As Boolean = isMouseOnPlotArea And onMouseWheelZoom(getPlotAreaMouseX, getPlotAreaMouseY, MouseEvent.delta) If (not (hasReceivers or hasMouseWheelZoom)) then // ignore End If End Sub
Property BottomLine As Line
Property ChartPicture As Picture
Property HelpLabel As label
Property LeftLine As Line
Property RightLine As Line
Property TopLine As Line
Property autoHideMsg As string
Property currentChart As CDBaseChartMBS
// Current BaseChart object
Property currentHotSpot As Integer
// The hot spot under the mouse cursor.
Property currentMouseX As Integer
// Get the mouse x-pixel coordinate
Property currentMouseY As Integer
// Get the mouse y-pixel coordinate
Property defaultToolTip As string
// Default tool tip text
Property delayImageMapUpdate As Boolean
Property delayUpdateChart As Integer
Property delayedChart As CDBaseChartMBS
Property delayedMouseEvent As MouseEvent
Property delayedMouseEventTimerId As Timer
Property holdTimerActive As Boolean
// Delay chart update to limit update frequency
Property holdTimerId As timer
Property hotSpotTester As CDImageMapHandlerMBS
// ImageMapHander representing the image map
Property isClickable As Boolean
// Mouse is over a clickable hot spot.
Property isDragScrolling As Boolean
// Is current dragging scrolling the chart.
Property isInMouseMove As Boolean
// Is in mouse moeve event handler
Property isInMouseMovePlotArea As Boolean
// flag to indicate if is in a mouse move plot area event.
Property isInViewPortChanged As Boolean
Property isMouseTracking As Boolean
// Is tracking mouse leave event
Property isOnPlotArea As Boolean
// Mouse is over the plot area.
Property isPlotAreaMouseDown As Boolean
// Mouse left button is down in the plot area.
Property lastMouseMove As Integer
Property minDragAmount As Integer
// Minimum drag amount
Property mouseUsage As Integer
// Mouse usage mode
Property mouseWheelZoomRatio As double
// Mouse wheel zoom ratio
Property needUpdateChart As Boolean
// Has pending chart update request
Property needUpdateImageMap As Boolean
// Has pending image map udpate request
Property plotAreaMouseDownXPos As Integer
Property plotAreaMouseDownYPos As Integer
Property scrollDirection As Integer
// Scroll direction
Property selectBoxLineColor As color
// Selectiom box border color
Property selectBoxLineWidth As Integer
// Selectiom box border width
Property updateInterval As Integer
// Minimum interval between chart updates
Property zoomDirection As Integer
// Zoom direction
Property zoomInRatio As double
// Click zoom in ratio
Property zoomOutRatio As double
// Click zoom out ratio
End Class
Module UtilModule
Function DoubleArray(values() as double, Count as Integer, Offset as Integer = 0) As Double() Dim u As Integer = values.Ubound Dim c As Integer = u + 1 If Offset = 0 And c = count Then // full array Return values End If Dim r() As Double u = Offset + count - 1 For i As Integer = Offset To u r.Append values(i) Next Return r End Function
End Module
Class ViewPortControl Inherits CDViewPortControlBaseMBS
Sub Close() If Self.output <> Nil Then Self.output.ViewPortControl = Nil Self.Output = Nil End If Self.viewer = Nil End Sub
Sub Constructor() // Calling the overridden superclass constructor. Super.Constructor Self.Viewer = Nil Self.mouseDownX = 0 Self.mouseDownY = 0 End Sub
Function getChart() As CDBaseChartMBS // Get back the same BaseChart pointer provided by the previous setChart call. Return Self.Chart End Function
Function getViewer() As ChartViewerCanvas Return viewer End Function
Function isDrag(MouseEvent as MouseEvent) As Boolean // Determines if the mouse is dragging. If viewer = Nil Then Return False End If Dim minimumDrag As Integer = viewer.getMinimumDrag Dim moveX As Integer = Abs(Self.mouseDownX - MouseEvent.x) Dim moveY As Integer = Abs(Self.mouseDownY - MouseEvent.y) Return (moveX >= minimumDrag) Or (moveY >= minimumDrag) End Function
Sub mouseMoveEvent(MouseEvent as MouseEvent) // MouseMove event handler // // Get the QChartViewer zoom/scroll state to determine which type of mouse action is allowed syncState // Handle the mouse move event handleMouseMove(toImageX(MouseEvent.x), toImageY(MouseEvent.y), isDrag(MouseEvent)) // Update the chart viewer if the viewport has changed updateChartViewerIfNecessary // Update the mouse cursor updateCursor(Self.Cursor) // Update the display If (needUpdateDisplay) Then paintDisplay End If End Sub
Sub mousePressEvent(MouseEvent as MouseEvent) // Mouse button down event. If (MouseEvent.button <> MouseEvent.LeftButton) then Return End If // Remember current mouse position Self.mouseDownX = MouseEvent.x self.mouseDownY = MouseEvent.y // Get the QChartViewer zoom/scroll state to determine which type of mouse action is allowed syncState // Handle the mouse down event handleMouseDown(toImageX(MouseEvent.x), toImageY(MouseEvent.y)) // Update the chart viewer if the viewport has changed updateChartViewerIfNecessary End Sub
Sub mouseReleaseEvent(MouseEvent as MouseEvent) // Mouse button up event. If (MouseEvent.button <> MouseEvent.LeftButton) Then Return End If // Get the QChartViewer zoom/scroll state to determine which type of mouse action is allowed syncState // Handle the mouse down event handleMouseUp(toImageX(MouseEvent.x), toImageY(MouseEvent.y)) // Update the chart viewer if the viewport has changed updateChartViewerIfNecessary End Sub
Sub onViewPortChanged() // Handle the ViewPortChanged event from the associated ChartViewer Self.updateDisplay End Sub
Sub paintDisplay() // Paint the display Dim c As CDBaseChartMBS = Self.Chart If c <> Nil Then Output.ChartPicture = c.makeChartPicture Else System.DebugLog "No chart in "+CurrentMethodName Output.ChartPicture = Nil End If Output.invalidate End Sub
Sub setChart(c as CDXYChartMBS) // Set the chart to be displayed in the control if c <> nil then Self.Chart = c Self.updateDisplay Else Break End If End Sub
Sub setViewer(v as ChartViewerCanvas) // Set the CChartViewer to be associated with this control Self.viewer = v self.ViewPortManager = v.ViewPortManager updateDisplay End Sub
Sub syncState() // Synchronize the CViewPortControl with CChartViewer If viewer <> Nil Then self.setZoomScrollDirection viewer.zoomDirection, viewer.scrollDirection End If End Sub
Function toImageX(x as integer) As double // In this version, no conversion is done. It is assumed the control does not stretch or shrink // the image and does not provide any additional margin to offset the image. Return x End Function
Function toImageY(y as integer) As double // In this version, no conversion is done. It is assumed the control does not stretch or shrink // the image and does not provide any additional margin to offset the image. Return y End Function
Sub updateChartViewerIfNecessary() // Update ChartViewer if viewport has changed If viewer = Nil Then Break Return End If If (Self.needUpdateChart Or Self.needUpdateImageMap) Then viewer.updateViewPort(self.needUpdateChart, self.needUpdateImageMap) End If End Sub
Sub updateCursor(position as integer) // Update the mouse cursor Select Case position Case CDBaseChartMBS.kLeft, CDBaseChartMBS.kRight output.MouseCursor = System.Cursors.ArrowEastWest Case CDBaseChartMBS.kTop, CDBaseChartMBS.kBottom output.MouseCursor = System.Cursors.ArrowNorthSouth Case CDBaseChartMBS.kTopLeft, CDBaseChartMBS.kBottomRight output.MouseCursor = System.Cursors.ArrowNorthwestSoutheast Case CDBaseChartMBS.kTopRight, CDBaseChartMBS.kBottomLeft output.MouseCursor = System.Cursors.ArrowNortheastSouthwest else output.MouseCursor = Nil End Select End Sub
Sub updateDisplay() // Update the display paintViewPort paintDisplay End Sub
Sub wheelEvent(MouseEvent as MouseEvent) // MouseWheel handler // Process the mouse wheel only if the mouse is over the plot area If ((Nil = Viewer) Or (Not isOnPlotArea(MouseEvent.x, MouseEvent.y))) Then // ignore Else // Ask the CChartViewer to zoom around the center of the chart Dim x As Integer = Viewer.getPlotAreaLeft + Viewer.getPlotAreaWidth / 2 Dim y As Integer = Viewer.getPlotAreaTop + Viewer.getPlotAreaHeight / 2 If (Not viewer.onMouseWheelZoom(x, y, MouseEvent.delta)) then // ignore End If End If End Sub
Property Output As ViewPortControlCanvas
Property Viewer As ChartViewerCanvas
Property mouseDownX As Integer
Property mouseDownY As Integer
End Class
FileTypes1
Filetype image/png
End FileTypes1
Class ViewPortManagerCanvas Inherits Canvas
Sub Constructor() self.ViewPortManager = New CDViewPortManagerMBS // Calling the overridden superclass constructor. Super.Constructor End Sub
Function canZoomIn(zoomDirection as integer) As boolean return ViewPortManager.canZoomIn(zoomDirection) End Function
Function canZoomOut(zoomDirection as integer) As boolean return ViewPortManager.canZoomOut(zoomDirection) End Function
Sub clearAllRanges() ViewPortManager.clearAllRanges End Sub
Sub commitPendingSyncAxis(baseChart as CDBaseChartMBS) ViewPortManager.commitPendingSyncAxis(baseChart) End Sub
Function dragTo(scrollDirection as Integer, x as Integer, y as Integer) As boolean Return ViewPortManager.dragTo(scrollDirection, x, y) End Function
Function getPlotAreaHeight() As integer Return ViewPortManager.getPlotAreaHeight End Function
Function getPlotAreaLeft() As integer Return ViewPortManager.getPlotAreaLeft End Function
Function getPlotAreaTop() As integer Return ViewPortManager.getPlotAreaTop End Function
Function getPlotAreaWidth() As integer Return ViewPortManager.getPlotAreaWidth End Function
Function getValueAtViewPort(id as string, ratio as double, isLogScale as boolean = false) As double Return ViewPortManager.getValueAtViewPort(id, ratio, isLogScale) End Function
Function getViewPortAtValue(id as string, ratio as double, isLogScale as boolean = false) As double Return ViewPortManager.getViewPortAtValue(id, ratio, isLogScale) End Function
Function getViewPortHeight() As Double Return ViewPortManager.getViewPortHeight End Function
Function getViewPortLeft() As Double return ViewPortManager.getViewPortLeft End Function
Function getViewPortTop() As Double return ViewPortManager.getViewPortTop End Function
Function getViewPortWidth() As Double return ViewPortManager.getViewPortWidth End Function
Function inExtendedPlotArea(x as integer, y as integer) As boolean Return ViewPortManager.inExtendedPlotArea(x,y) End Function
Function inPlotArea(x as Integer, y as Integer) As boolean Return ViewPortManager.inPlotArea(x,y) End Function
Sub setChartMetrics(metrics as string) ViewPortManager.setChartMetrics metrics End Sub
Sub setFullRange(ID as string, minValue as Double, maxValue as Double) ViewPortManager.setFullRange(ID, minValue, maxValue) End Sub
Sub setPlotAreaMouseMargin(leftMargin as Integer, rightMargin as Integer, topMargin as Integer, bottomMargin as Integer) ViewPortManager.setPlotAreaMouseMargin(leftMargin, rightMargin, topMargin, bottomMargin) End Sub
Sub setViewPortHeight(value as double) 'System.DebugLog CurrentMethodName + " " + Str(value) ViewPortManager.setViewPortHeight(value) End Sub
Sub setViewPortLeft(value as double) System.DebugLog CurrentMethodName + " " + Str(value) ViewPortManager.setViewPortLeft(value) End Sub
Sub setViewPortTop(value as double) 'System.DebugLog CurrentMethodName + " " + Str(value) ViewPortManager.setViewPortTop(value) End Sub
Sub setViewPortWidth(value as double) 'System.DebugLog CurrentMethodName + " " + Str(value) ViewPortManager.setViewPortWidth(value) End Sub
Sub setZoomInHeightLimit(value as double) ViewPortManager.setZoomInHeightLimit(value) End Sub
Sub setZoomInWidthLimit(value as double) ViewPortManager.setZoomInWidthLimit(value) End Sub
Sub setZoomOutHeightLimit(value as double) System.DebugLog CurrentMethodName + " " + Str(value) ViewPortManager.setZoomOutHeightLimit(value) End Sub
Sub setZoomOutWidthLimit(value as double) System.DebugLog CurrentMethodName + " " + Str(value) ViewPortManager.setZoomOutWidthLimit(value) End Sub
Sub startDrag() ViewPortManager.startDrag End Sub
Function updateFullRangeH(id as string, minValue as double, maxValue as double, updateType as Integer) As boolean 'System.DebugLog CurrentMethodName+" minValue: "+Str(minValue)+" maxValue: "+Str(maxValue) Return ViewPortManager.updateFullRangeH(id, minValue, maxValue, updateType) End Function
Function updateFullRangeV(id as string, minValue as double, maxValue as double, updateType as Integer) As boolean Return ViewPortManager.updateFullRangeV(id, minValue, maxValue, updateType) End Function
Sub validateViewPort() ViewPortManager.validateViewPort End Sub
Function zoomAround(x as Integer, y as Integer, xZoomRatio as Double, yZoomRatio as Double) As boolean Return ViewPortManager.zoomAround(x, y, xZoomRatio, yZoomRatio) End Function
Function zoomAt(zoomDirection as Integer, x as Integer, y as Integer, zoomRatio as Double) As boolean Return ViewPortManager.zoomAt(zoomDirection, x, y, zoomRatio) End Function
Function zoomTo(zoomDirection as Integer, x1 as Integer, y1 as Integer, x2 as Integer, y2 as Integer) As boolean return ViewPortManager.zoomTo(zoomDirection, x1, y1, x2, y2) End Function
Note "About this class"
In C++ you can have a class inherit from multiple classes In Xojo you can't. So we have this intermediate class to forward all calls to Once this code is fully debugged and running, someone may remove this intermediate class and change all calls to the methods with ViewPortManager. prefix.
Property ViewPortManager As CDViewPortManagerMBS
End Class
Class MouseEvent
Const LeftButton = 1
Const NoButton = 0
Const RightButton = 2
Sub Constructor(MouseX as Integer, MouseY as integer) Self.x = mouseX Self.y = MouseY End Sub
Sub SetButton() If IsContextualClick Then button = RightButton Else button = LeftButton End If End Sub
Property button As Integer
Property delta As Integer
Property x As Integer
Property y As Integer
End Class
Class ViewPortControlCanvas Inherits Canvas
EventHandler Function MouseDown(X As Integer, Y As Integer) As Boolean 'System.DebugLog CurrentMethodName+" "+Str(x)+"/"+Str(y) Dim m As New MouseEvent(x,y) m.SetButton ViewPortControl.mousePressEvent(m) Return True End EventHandler
EventHandler Sub MouseDrag(X As Integer, Y As Integer) 'System.DebugLog CurrentMethodName+" "+Str(x)+"/"+Str(y) Dim m As New MouseEvent(x,y) ViewPortControl.mouseMoveEvent m End EventHandler
EventHandler Sub MouseMove(X As Integer, Y As Integer) 'System.DebugLog CurrentMethodName+" "+Str(x)+"/"+Str(y) Dim m As New MouseEvent(x,y) ViewPortControl.mouseMoveEvent m End EventHandler
EventHandler Sub MouseUp(X As Integer, Y As Integer) 'System.DebugLog CurrentMethodName+" "+Str(x)+"/"+Str(y) Dim m As New MouseEvent(x,y) m.SetButton ViewPortControl.mouseReleaseEvent(m) End EventHandler
EventHandler Function MouseWheel(X As Integer, Y As Integer, deltaX as Integer, deltaY as Integer) As Boolean Dim m As New MouseEvent(x,y) m.delta = deltay ViewPortControl.wheelEvent(m) #Pragma Unused deltaX End EventHandler
EventHandler Sub Paint(g As Graphics, areas() As REALbasic.Rect) 'System.DebugLog CurrentMethodName If ChartPicture <> Nil Then g.DrawPicture ChartPicture, 0, 0, g.Width, g.height, 0, 0, ChartPicture.Width, ChartPicture.Height Else System.DebugLog "No chart picture in "+CurrentMethodName End If #Pragma Unused areas End EventHandler
Property ChartPicture As Picture
Property ViewPortControl As ViewPortControl
End Class
End Project

Feedback, Comments & Corrections

The items on this page are in the following plugins: MBS ChartDirector Plugin.

The biggest plugin in space...




Links
MBS Xojo PDF Plugins

Start Chat