Diagramm zeichnen und drucken


Wie das Leben so spielt, will man gelegentlich ein Diagramm nicht nur auf dem Bildschirm erzeugen, sondern es auch dem Freund/der Freundin zeigen. Deshalb hier ein kommentiertes Beispielprogramm für Darstellung und Druck eines Diagramms. Ich stelle hier eine vorgegebene Funktion dar. Die vorgestellten Routinen sind natürlich auch für andere Zwecke recyclebar.
Zunächst, so sieht es aus:

Die Steuerelemente sind von links nach rechts eingefügt, also zuordenbar.
Zunächst Deklarationen und die Ladeinformationen des Formulars. Die Routine Form_Resize wird gleich anschließend erläutert. Die Startwerte für beide ScrollBars werden beim Anlegen des Formulars eingestellt. Da die Werte mit den Default-Werten nicht übereinstimmen, werden die später erläuterten Change-Ereignisse ausgelöst, die dann auch die Anzeigen in Labels schon beim Start bewirken.

Option Explicit
Dim xvon As Single, xbis As Single, ymax As Single, ymin As Single, Blatt As Object

Private Sub Form_Load()
Form_Resize
HScroll1.Value = 10
HScroll2.Value = 10
End Sub

Jetzt eine für den Nutzer ganz wichtige Sache: Bei Veränderung der Größe des Fensters, in dem die Anwendung läuft, soll die Funktionsdarstellung den jeweiligen Raum im Fenster optimal nutzen. Deshalb werden die Maße der PictureBox, die die eigentliche Diagrammdarstellung enthält, den Fenstermaßen angepaßt. Die Anpassung muß unterbleiben, wenn das Fenster minimiert worden ist und wenn der Platz nur noch für die Steuerelemente aber nicht mehr für ein anständiges Darstellungsfenster reicht. Die Ränder werden in Pixeln festgelegt, damit man von der Bildschirmauflösung unabhängig ist. Der Frame, der die Bedienelemente enthält, besitzt eine feste Größe, die berücksichtigt werden muß.

Private Sub Form_Resize()
Dim fakX As Long, fakY As Long
If Not Form1.WindowState = 1 And Form1.Height > 2 * Frame1.Height Then
Picture1.Cls
fakX = Screen.TwipsPerPixelX
fakY = Screen.TwipsPerPixelY
Picture1.Left = 6 * fakX
Picture1.Width = Form1.Width - 20 * fakX
Picture1.Top = 7 * fakY
Picture1.Height = Form1.Height - Frame1.Height - 37 * fakY
Frame1.Left = 6 * fakX
Frame1.Top = Form1.Height - Frame1.Height - 30 * fakY
End If
End Sub

In den folgenden Unterprogrammen werden die Change-Ereignisse beider ScrollBars ausgewertet. Hier werden die x-Grenzen der Darstellung ausgewählt. Natürlich gibt es dafür auch andere Möglichkeiten. Wichtig ist, daß auf irgendeine Weise Fehleingaben verhindert werden. Hier sind die Min- und Max-Werte beider ScrollBars so eingestellt, daß nichts Schlimmes passieren kann. Der untere geht bis "0" der obere beginnt bei "1", so ist immer ein anzeigbares Intervall vorhanden. Natürlich können die Werte auch abgefragt und evtl. mittels MessageBox und einer Schleife solange erneut erfragt werden, bis sie zulässige Werte erhalten haben.

Private Sub HScroll1_Change()
    Picture1.Cls
    xvon = -HScroll1.Value
    Label1.Caption = "xvon = " & xvon
End Sub



Private Sub HScroll2_Change()
    Picture1.Cls
    xbis = HScroll2.Value
    Label2.Caption = "xbis = " & xbis
End Sub

Es folgen die drei Buttons. Der erste löst die Darstellung auf dem Bildschirm aus. Hier werden zwei erst später definierte Funktionen, nämlich Achsenkreuz() und Male() unter Nennung des Objektes aufgerufen, auf das sie angewandt werden sollen. Im zweiten Unterprogramm geht es um das Drucken. Es wird das Druckerauswahlfenster aufgerufen, danach werden die beiden schon genannten Routinen auf den Drucker angewandt.
Die beiden Unterprogramme werden mit einem Objektparameter, der für Picture1 und Printer stehen, aufgerufen. Inhaltlich sind die Routinen für beide Ausgabemedien (fast) gleich. Der dritte Button beendet in bekannter Weise das ganze Unglück.

Private Sub Command1_Click()
Picture1.Cls
Achsenkreuz Picture1
Male Picture1
End Sub

Private Sub Command2_Click()
On Error GoTo hier
CommonDialog1.Flags = &H40
CommonDialog1.CancelError = True
CommonDialog1.Action = 5
MousePointer = 11 'drucken dauert
Printer.ScaleTop = 0
Printer.ScaleLeft = 0
Achsenkreuz Printer
Male Printer
Printer.EndDoc
raus:
MousePointer = 0
Exit Sub
hier:
If Not CommonDialog1.CancelError Then MsgBox "Der Drucker will oder kann nicht!"
End Sub
Private Sub Command3_Click()
End
End Sub

Nun folgt die Funktion, die dargestellt werden soll. Natürlich ist das Programm, solange es nur für eine einzige Funktion gilt, relativ witzlos. Aber die Erweiterung überlasse ich gern meinen Nachnutzern. Aufgerufen wird die Funktion mit dem x-Wert als Parameter und sie meldet den berechneten y-Wert zurück. Die frei wählbare Funktionsbezeichnung hat zur Folge, daß ein Ausdruck wie y = f(x) eine gültige Befehlszeile im Programm darstellt.

Function f(ByVal x As Single) As Single
f = (Sin(x) - Cos(x ^ 2)) * Sqr(Abs(x))
End Function

Es folgen die aufwendigeren, aber auch wiederverwendbaren Teile des Programms. Die Routine Achsenkreuz() wird aufgerufen mit dem Objekt, auf dem das Achsenkreuz errichtet werden soll, einmal also Picture1, einmal Printer. Theoretisch soll das reibungslos funktionieren. Leider haben aber die Erfinder von Visual Basic keinen Drucker besessen, deshalb gehört die Druckereinbindung bis heute zu den problematischen Teilen von Visual Basic.
Zu den einzelnen Abschnitten:

Sub Achsenkreuz(wohin As Object)
Dim i%, x As Single, dx As Single, y As Single
Dim xrand As Single, yrand As Single
'Spannweite der y-Werte feststellen
ymax = -1000000#
ymin = 1000000#
dx = (xbis - xvon) / 1000
For i = 0 To 1000
x = xvon + i * dx
y = f(x)
If y < ymin Then ymin = y
If y > ymax Then ymax = y
Next
'Darstellungsfeld skalieren und mit gleichmäßigem Rand versehen
Select Case wohin.hDC
Case Picture1.hDC
xrand = 20 * (xbis - xvon) / (wohin.Width / Screen.TwipsPerPixelX - 40)
yrand = 20 * (ymax - ymin) / (wohin.Height / Screen.TwipsPerPixelY - 40)
Case Printer.hDC
xrand = 150 * (xbis - xvon) / (wohin.Width / wohin.TwipsPerPixelX - 300)
yrand = 150 * (ymax - ymin) / (wohin.Height / wohin.TwipsPerPixelY - 300)
End Select
wohin.Scale (xvon - xrand, -yrand)-(xbis + xrand, ymax - ymin + yrand)
wohin.Line (xvon, 0)-(xbis, ymax - ymin), , B
'Achsen malen, beschriften
If Sgn(xvon) <> Sgn(xbis) Then
wohin.Line (0, 0)-(0, -(ymin - ymax))
If ymax - ymin > 1 Then
For i = Fix(ymin) To Fix(ymax)
If i <> 0 Then
wohin.Line (0, -(i - ymax))-(xrand / 5, -(i - ymax))
wohin.Print i
End If
Next
End If
End If
If Sgn(ymin) <> Sgn(ymax) Then
wohin.Line (xvon, ymax)-(xbis, ymax)
If xbis - xvon > 1 Then
For i = Fix(xvon) To Fix(xbis) - 1
If i <> 0 Then
wohin.Line (i, ymax)-(i, yrand / 5 + ymax)
wohin.Print i
End If
Next
End If
End If
'Angabe der Darstellungsgrenzen
wohin.CurrentX = xvon
wohin.CurrentY = ymax - ymin + yrand / 10
wohin.Print xvon & " <= X <= " & xbis & ";   " & Format$(ymin, "#.##") & " <= Y <= " & Format$(ymax, "#.##");
wohin.Print " F(X) = (Sin(X) - Cos(X^2)) * Sqr(Abs(X))"
End Sub

Gleich sind wir fertig. Nur noch schnell die Kurve gemalt. Bemerkenswert hier nur die Klimmzüge, die man machen muß, um die falsch orientierte y-Richtung zu kompensieren. Vgl. dazu den obigen Abschnitt zur Skalierung.

Sub Male(wohin As Object)
Dim i%, x As Single, xalt As Single, yalt As Single, dx As Single, y As Single
dx = (xbis - xvon) / 1000
xalt = xvon
yalt = f(xvon)
For i = 1 To 1000
x = xvon + i * dx
y = f(x)
wohin.Line (xalt, -(yalt - ymax))-(x, -(y - ymax))
xalt = x
yalt = y
Next
End Sub

Viel Erfolg!


© R. Hirte * 2001