Einfaches Plotten von Funktionen

    • VB.NET

    Es gibt 3 Antworten in diesem Thema. Der letzte Beitrag () ist von FreakJNS.

      Einfaches Plotten von Funktionen

      Hi
      da des Öfteren mal eine Frage zum Plotten von Funktionen gestellt wird, habe ich mal einen einfachen Algorithmus dafür geschrieben. Im Prinzip wird immer der Mittelwert zwischen zwei Werten genommen und in die Funktion eingesetzt. Anschließend wird jeweils überprüft, ob der Abstand zum 1. bzw. 2. Wert größer dem übergebenen delta ist und, falls zutreffend, die Prozedur erneut aufgerufen. Dadurch ist eine erheblich größere Genauigkeit gegeben, als durch einen immer gleichbleibenden Abstand zwischen den zu zeichnenden Punkten und DrawLine.
      Als Anmerkung vorweg: Der Algorithmus ist bei Weitem nicht perfekt. Bei vertikalen Asymptoten und häufig auch bei unstetigen Funktionen hängt er sich bspw. auf, bei kleinen Abständen (< delta) bricht er das Zeichnen bereits vorher ab. Dies liegt vor allem daran, dass durch die Verwendung des Delegatens quasi keine Analyse auf die Asymptoten, sowie somit das Verhalten an diesen möglich ist. Kleine Abstände werden hingegen einfach übersprungen, da eine Analyse auf lokale Extremwerte ebenfalls nicht möglich ist. Insgesamt ist der Quellcode auch eigentlich nicht auf diesen einfachen Fall zugeschnitten, daher bitte ich über etwaige Unsauberkeiten und Unschönheiten hinwegzusehen. Das Verfahren selbst ist extrem stapellastig, daher kann es zu Stapelüberläufen kommen.

      Spoiler anzeigen

      VB.NET-Quellcode

      1. <System.Runtime.InteropServices.DllImport("gdi32.dll")> _
      2. Private Shared Function SetPixel(hdc As IntPtr, x As Integer, y As Integer, argb As Integer) As Integer
      3. End Function
      4. <System.Runtime.InteropServices.DllImport("gdi32.dll")> _
      5. Private Shared Function GetPixel(hdc As IntPtr, x As Integer, y As Integer) As Integer
      6. End Function
      7. ''' <summary>
      8. ''' Kapselt eine Funktion mit einem Parameter vom Typ
      9. ''' <see cref="System.Double"/>
      10. ''' und einem Rückgabewert vom Typ
      11. ''' <see cref="System.Double"/>.
      12. ''' </summary>
      13. ''' <param name="value">Der Wert, der ausgewertet wird.</param>
      14. ''' <returns>Der von der gekapselten Funktion zurückgegebene Wert.</returns>
      15. Public Delegate Function Func(value As Double) As Double
      16. ''' <summary>
      17. ''' Stellt ein von zwei Werten begrenztes Intervall dar.
      18. ''' </summary>
      19. Public Structure Range
      20. Private _first As Double, _last As Double
      21. ''' <summary>
      22. ''' Gibt den ersten Wert zurück.
      23. ''' </summary>
      24. Public ReadOnly Property First() As Double
      25. Get
      26. Return _first
      27. End Get
      28. End Property
      29. ''' <summary>
      30. ''' Gibt den zweiten Endwert zurück.
      31. ''' </summary>
      32. Public ReadOnly Property Last() As Double
      33. Get
      34. Return _last
      35. End Get
      36. End Property
      37. ''' <param name="first">Der erste Wert des Intervalls.</param>
      38. ''' <param name="last">Der zweite Wert des Intervalls.</param>
      39. Public Sub New(first As Double, last As Double)
      40. _first = first
      41. _last = last
      42. End Sub
      43. End Structure
      44. ''' <summary>
      45. ''' Zeichnet die angegebene Funktion auf das Ziel.
      46. ''' </summary>
      47. ''' <param name="surface">Die Oberfläche, auf die gezeichnet werden soll.</param>
      48. ''' <param name="color">Die Darstellungsfarbe der dargestellten Funktion.</param>
      49. ''' <param name="xRange">Die zu zeichnende Definitionsmenge.</param>
      50. ''' <param name="yRange">Die zu zeichnende Wertemenge.</param>
      51. ''' <param name="bounds">Der Bereich, in dem die Funktion dargestellt werden soll.</param>
      52. ''' <param name="origin">Der Koordinatenursprung.</param>
      53. ''' <param name="evaluatedFunction">Die zu zeichnende Funktion.</param>
      54. ''' <remarks>
      55. ''' Funktionen mit asymptotischem Verhalten innerhalb eines zu zeichnenden Intervalls müssen
      56. ''' vor dem Zeichnen aufgespalten werden und nacheinander gezeichnet werden.
      57. ''' </remarks>
      58. Public Shared Sub Plot(surface As Graphics, color As Color, xRange As Range, yRange As Range, bounds As RectangleF, origin As PointF, _
      59. evaluatedFunction As Func)
      60. Plot(surface, color, xRange, yRange, bounds, origin, _
      61. 1.0, evaluatedFunction)
      62. End Sub
      63. ''' <summary>
      64. ''' Zeichnet die angegebene Funktion auf das Ziel.
      65. ''' </summary>
      66. ''' <param name="surface">Die Oberfläche, auf die gezeichnet werden soll.</param>
      67. ''' <param name="color">Die Darstellungsfarbe der dargestellten Funktion.</param>
      68. ''' <param name="xRange">Die zu zeichnende Definitionsmenge.</param>
      69. ''' <param name="yRange">Die zu zeichnende Wertemenge.</param>
      70. ''' <param name="bounds">Der Bereich, in dem die Funktion dargestellt werden soll.</param>
      71. ''' <param name="origin">Der Koordinatenursprung.</param>
      72. ''' <param name="delta">Der minimale Abstand zwischen zwei Werten, für die eine weitere Zeichenoperation durchgeführt werden soll.</param>
      73. ''' <param name="evaluatedFunction">Die zu zeichnende Funktion.</param>
      74. ''' <remarks>
      75. ''' Funktionen mit asymptotischem Verhalten innerhalb eines zu zeichnenden Intervalls müssen
      76. ''' vor dem Zeichnen aufgespalten werden und nacheinander gezeichnet werden.
      77. ''' </remarks>
      78. Public Shared Sub Plot(surface As Graphics, color As Color, xRange As Range, yRange As Range, bounds As RectangleF, origin As PointF, _
      79. delta As Double, evaluatedFunction As Func)
      80. Dim prev As Region = surface.Clip
      81. Dim cur As New Region(bounds)
      82. Dim lv As Double, rv As Double
      83. cur.Intersect(prev)
      84. 'Zu fuellenden Bereich beschraenken
      85. surface.Clip = cur
      86. 'linker y-Wert
      87. lv = evaluatedFunction(xRange.First)
      88. 'rechter y-Wert
      89. rv = evaluatedFunction(xRange.Last)
      90. 'Verschiebung in x- und y-Richtung berechnen
      91. Dim xoffs As Single = bounds.Left + origin.X - bounds.Width * CSng(xRange.First / (xRange.Last - xRange.First))
      92. Dim yoffs As Single = bounds.Bottom + origin.Y + bounds.Height * CSng(yRange.First / (yRange.Last - yRange.First))
      93. 'Streckfaktor in x- und y-Richtung berechnen
      94. Dim xfct As Double = bounds.Width / (xRange.Last - xRange.First)
      95. Dim yfct As Double = -bounds.Height / (yRange.Last - yRange.First)
      96. 'Positionen von linkem und rechten Wert berechnen
      97. Dim lpos As New PointF(CSng(xRange.First * xfct) + xoffs, CSng(lv * yfct) + yoffs)
      98. Dim rpos As New PointF(CSng(xRange.Last * xfct) + xoffs, CSng(rv * yfct) + yoffs)
      99. 'Intervall zwischen den beiden Werten berechnen
      100. PlotInterval(surface.GetHdc(), color.ToArgb(), xRange.First, xRange.Last, lv, rv, _
      101. lpos, rpos, xfct, xoffs, yfct, yoffs, _
      102. delta, evaluatedFunction)
      103. surface.ReleaseHdc()
      104. 'Linken und rechten Punkt zeichnen
      105. surface.FillRectangle(Brushes.Black, lpos.X, lpos.Y, 1F, 1F)
      106. surface.FillRectangle(Brushes.Black, rpos.X, rpos.Y, 1F, 1F)
      107. 'urspruenglichen zu fuellenden Bereich wieder uebernehmen
      108. surface.Clip = prev
      109. cur.Dispose()
      110. End Sub
      111. Private Shared Sub PlotInterval(hdc As IntPtr, color As Integer, l As Double, r As Double, lv As Double, rv As Double, _
      112. lpos As PointF, rpos As PointF, xfct As Double, xoffs As Single, yfct As Double, yoffs As Single, _
      113. delta As Double, func As Func)
      114. 'Mittle vom linken x-Wert (l) und rechten x-Wert (r) ermitteln
      115. Dim m As Double = (l + r) / 2.0
      116. 'y-Wert des x-Wertes berechnen
      117. Dim mv As Double = func(m)
      118. 'Abstand zwischen den beiden Punkten ermitteln
      119. Dim dx As Double = (m - l) * xfct, dy As Double = (mv - lv) * yfct
      120. 'zu zeichnende Position des mittleren Werts ermitteln
      121. Dim pos As New PointF(CSng(m * xfct) + xoffs, CSng(mv * yfct) + yoffs)
      122. 'Falls der Abstand groesser, als der maximale Abstand ist, wird der Wert zwischen den beiden Werten ebenfalls gezeichnet, sonst ausgelassen.
      123. If dx * dx + dy * dy >= delta Then
      124. PlotInterval(hdc, color, l, m, lv, mv, _
      125. lpos, pos, xfct, xoffs, yfct, yoffs, _
      126. delta, func)
      127. End If
      128. 'selbiges fuer den rechten Teil
      129. dx = (r - m) * xfct
      130. dy = (rv - mv) * yfct
      131. If dx * dx + dy * dy >= delta Then
      132. PlotInterval(hdc, color, r, m, rv, mv, _
      133. pos, rpos, xfct, xoffs, yfct, yoffs, _
      134. delta, func)
      135. End If
      136. 'Pixel darstellen
      137. DrawPixel(hdc, color, pos.X, pos.Y)
      138. End Sub
      139. Private Shared Sub DrawPixel(hdc As IntPtr, color As Integer, x As Single, y As Single)
      140. Dim xl As Integer = CInt(Math.Truncate(Math.Floor(x)))
      141. Dim yl As Integer = CInt(Math.Truncate(Math.Floor(y)))
      142. Dim fctx As Single = x - xl
      143. Dim fcty As Single = y - yl
      144. 'Einfache "billige" Antialisierung anwenden
      145. SetPixel(hdc, xl, yl, BlendPixelBgr(GetPixel(hdc, xl, yl), color, (1F - fctx) * (1F - fcty)))
      146. SetPixel(hdc, xl + 1, yl, BlendPixelBgr(GetPixel(hdc, xl + 1, yl), color, fctx * (1F - fcty)))
      147. SetPixel(hdc, xl, yl + 1, BlendPixelBgr(GetPixel(hdc, xl, yl + 1), color, (1F - fctx) * fcty))
      148. SetPixel(hdc, xl + 1, yl + 1, BlendPixelBgr(GetPixel(hdc, xl + 1, yl + 1), color, fctx * fcty))
      149. End Sub
      150. Private Shared Function BlendPixelBgr(backbgr As Integer, argb As Integer, alphafactor As Single) As Integer
      151. Dim a As Integer = CInt(Math.Truncate(((argb >> 24) And &Hff) * alphafactor))
      152. If a = 0 Then
      153. 'fore-color ist nicht sichtbar
      154. Return backbgr
      155. ElseIf a = 255 Then
      156. 'back-color ist unsichtbar ==> r und b vertauschen
      157. Return ((argb And &Hff) << 16) Or (argb And &Hff00) Or ((argb And &Hff0000) >> 16)
      158. Else
      159. 'sonst einfach Alpha-Blending anwenden
      160. Return ((a * (argb And &Hff) + (255 - a) * ((backbgr >> 16) And &Hff)) And &Hff00) << 8 Or ((a * ((argb >> 8) And &Hff) + (255 - a) * ((backbgr >> 8) And &Hff)) And &Hff00) Or ((a * ((argb >> 16) And &Hff) + (255 - a) * (backbgr And &Hff)) And &Hff00) >> 8
      161. End If
      162. End Function


      Beispiel für Aufruf:

      VB.NET-Quellcode

      1. Dim g As Graphics = 'Ziel
      2. Plot(g, _
      3. New Range(3, 100), _
      4. New Range(-1, 1), _
      5. ClientRectangle, _
      6. New PointF(0, 0), _
      7. 1.0, _
      8. Function(v As Double) Math.Sin(v * v / 10))


      Viel Spaß.

      Gruß
      ~blaze~

      Dieser Beitrag wurde bereits 4 mal editiert, zuletzt von „~blaze~“ ()

      @bla
      unter VB6 gab es mal eine SetPixel-Mehtode um direkt Punkte auf den Bildschirm zu zeichnen. Bei .NET geht das nur über diesen umweg oder indem man mit GDI+ ein Rectangle ausfüllt, dass genau 1x1Pixel groß ist. SetPixel kenn ich in .NET nur noch als Bitmap-Methode.