Rubrik: Controls | VB-Versionen: VB2008, VB2010 | 28.02.13 |
MSChart-WinForms Animation Dieser Tipp zeigt einen ersten Ansatz für die Animation von Charts in WindowsForms. | ||
Autor: Dietrich Herrmann | Bewertung: | Views: 8.532 |
ohne Homepage | System: WinXP, Win7, Win8, Win10, Win11 | kein Beispielprojekt |
Am Markt gibt es einige Pakete für die Datenvisualisierung mittels Diagrammen, die auch gewisse Animationen gestatten. Uns ist das Paket Microsoft Charts für WindowsForms und Web kostenlos gegeben. Und für WindowsForms-Lösungen habe ich die im folgenden beschriebenen Funktionen zur Animation von Datapoints mit "Bordmitteln" entwickelt.
Es ist als ein erster Ansatz zu sehen, der mit etwas Fantasie eingesetzt und möglicherweise noch weiter entwickelt werden kann.
Die Lösung geht prinzipiell davon aus, dass ein bis mehrere Datenserien gezeigt werden sollen, die allerdings alle von nur einer XValues-Menge ausgehen. Dann werden die YValues individuell in Schrittweiten eingeteilt, die auf
dem Maximalwert der Serie beruhen.
Die gezeigte Prozedur benötigt folgende Prämissen:
XValues und YValues müssen jeweils in einem Array zur Verfügung gestellt werden. Stellt man die Werte mittels Datenbank eventuell über Dataview bereit, zeige ich im Beispiel, wie man die Werte einfach in Arrays kopiert.
Für die YValues benötigt man zwei Arrays, eines mit den Endwerten und eines mit der gleichen Anzahl Null-Werten. Die Animation ist dann so gelöst, dass im Null-Werte-Array für jeden XPoint die Schrittweite addiert wird, bis der zugehörige Endwert erreicht wird. Aus dem XValue-Array und dem berechneten YValue-Array wird die neue Serie mitteles DataBindXY gebundenund die Chart upgedatet. Nach dem Update wird eine Minimalpause eingelegt. Dieser Prozess wird so lange ausgeführt, bis alle DataPoints die YValue-Endwerte erreicht haben.
Die Prozedur stellt verschiedene Möglichkeiten der Animation zur Verfügung, was aus den Parameterangaben ersichtlich ist.
Hier erst mal die Prozedur:
''' <summary> ''' Animation von Charts ''' </summary> ''' <param name="theChart">die Chart</param> ''' <param name="theSerie">die zu animierende Serie</param> ''' <param name="anzXValues">die Anzahl der XValues</param> ''' <param name="theSteps">die Schrittweite der YValues bei der Animation</param> ''' <param name="xvField">das Feld mit den XValues</param> ''' <param name="yvField">das Feld mit den YValues</param> ''' <param name="pSec">die Zeitspanne für die Pause während der Animation</param> ''' <param name="animArt">die Animationsart (0..einzelnes oder 1..gleichzeitiges Wachsen</param> ''' <param name="direction">die Richtung der Animation (0..von links, 1..von rechts</param> ''' <param name="clearing">sollen die Serienpunkte gelöscht werden, ja|nein</param> Public Sub AnimateCharts(theChart As Chart, theSerie As Object, anzXValues As Short, theSteps As Object, _ xvField As Object, yvField As Object, _ pSec As Single, Optional animArt As Short = 0, _ Optional direction As Short = 0, _ Optional clearing As Boolean = False) Dim w As Boolean Dim anfang, ende, schritt As Short anfang = 0 : schritt = 1 : ende = anzXValues - 1 If direction = 1 Then ' Animation von rechts anfang = anzXValues - 1 : ende = 0 : schritt = -1 End If ' ein Feld für alle Datapoint-Values = 0 Dim iniField(anzXValues - 1) As Object iniField.Initialize() ' ein Feld für die XValues, ein Feld für die YValues Dim xField(anzXValues - 1), yField(anzXValues - 1) As Object ' die Felder mit Daten aus Dataview füllen For j As Short = 0 To anzXValues - 1 xField(j) = xvField(j) yField(j) = yvField(j) Next With theChart If clearing Then ' Series.Points löschen .Series(theSerie).Points.Clear() ' Series.Points mit den aktuellen Xvalues und den YValues=0 verbinden .Series(theSerie).Points.DataBindXY(xField, iniField) ' Chart updaten .Update() End If ' Animation gemäß gewünschter Art Select Case animArt Case 0 ' die Animation ausführen für alle XValues ' Art: einen Datapoint nach dem anderen gemäß der XValues-Reihenfolge animieren ' (einzelnes Wachsen) For i As Short = anfang To ende Step schritt Do iniField(i) += theSteps ' Wert aus dem Null-YValues-Feld um Schrittweite erhöhen If theSteps < 0 Then w = (iniField(i) + theSteps <= yField(i)) Else w = (iniField(i) + theSteps >= yField(i)) End If ' Abfrage, ob aktueller YValue den endgültigen YValue erreicht hat If w Then iniField(i) = yField(i) ' den Null-YValue auf den endgültigen Wert setzen ' Series.Points nun mit Null-YValue-Feld verbinden .Series(theSerie).Points.DataBindXY(xField, iniField) ' Chart updaten .Update() ' Pause einlegen bis zur nächsten Wertberechnung Delay(pSec) If w Then Exit Do ' Schleife verlassen Loop Next Case 1 ' die Animation ausführen für alle XValues ' Art: alle Datapoints gleichzeitig animieren, bei 0 beginnend bis zum jeweiligen Endpunkt ' (gleichzeitiges Wachsen) Dim dmax(yField.Length - 1) As Short ' Feld für Registrieren, ob Endpunkte erreicht dmax.Initialize() ' Schleife für die Animation Do For i As Short = anfang To ende Step schritt If dmax(i) = 0 Then iniField(i) += theSteps If theSteps < 0 Then w = (iniField(i) <= yField(i)) Else w = (iniField(i) >= yField(i)) End If If w Then dmax(i) += 1 Else dmax(i) = 0 End If Next .Series(theSerie).Points.DataBindXY(xField, iniField) .Update() Delay(pSec) ' sind alle Endpunkte erreicht? Dim x As Boolean = Array.TrueForAll(dmax, AddressOf NoZeroValues) If x Then Exit Do ' alle Endpunkte erreicht Loop End Select End With End Sub
Und gleich noch die benötigten Hilfsfunktionen:
1. Delay für das Erzeugen einer "Pseudo-Pause"
' eine Pause erzeugen Public Function Delay(ByVal seconds As Single) As Single Dim StartTime As DateTime = DateTime.Now Do ' Nichts tun. Loop While DateTime.Now.Subtract(StartTime).TotalSeconds < seconds Return seconds End Function
2. NoZeroValues als Predicate(Of T) für die Array-Funktion Array.TrueForAll
Private Function NoZeroValues(v As Short) As Boolean Return (v > 0) End Function
3. MaxMinOfArray zum Ermitten des Maximums|Minimums der Werte eines Array
''' <summary> ''' Ermitteln von Maximum/Minimum der Werte eines Feldes ''' </summary> ''' <param name="theArray">das zu durchsuchende Feld</param> ''' <param name="flag">True für Maximum, False für Minimum</param> ''' <returns>den Wert (max oder min)</returns> Public Function MaxMinOfArray(ByVal theArray As Array, _ Optional ByVal flag As Boolean = True) As Object Dim max = theArray(0) For i As Integer = 0 To theArray.Length - 1 If flag Then If theArray(i) > max Then max = theArray(i) ' Maximum End If Else If theArray(i) < max Then max = theArray(i) ' Minimum End If End If Next i Return max End Function
Anwendung der Prozedur:
Die Chart sollte schon "fertig definiert" sein, entweder im Designer oder programmatisch. Also Erscheinungsbild, Achsen, Serien usw.
Dann ruft man die Animationsprozedur folgendermaßen auf: man definiert zunächst die Zeit für die Animationspause, im Beispiel delSec. Durch Ausprobieren kann man den Wert austesten, aber ich verwende jetzt 0.02, was noch
eine einigermaßen "flüssige" Animation erlaubt.
Dim delSec As Single = 0.02
Mein Beispiel hier geht von zwei Serien aus, die dargestellt werden sollen und zeige gleichzeitig, wie man die benötigten Felder aus DataViews erstellt.
Dim xdv1(dv1.Count - 1), ydv1(dv1.Count - 1), ydv2(dv2.Count - 1) As Object For j As Short = 0 To dv1.Count - 1 xdv1(j) = dv1.Item(j)("Jahr") ydv1(j) = dv1.Item(j)("Betrag") ydv2(j) = dv2.Item(j)("Stunden") Next
Dabei sind
- dv1: die XValues aus dem Dataview dv1 (bspw. enthält dv1 die Zeilen mit den Inhalten Jahr und Betrag)
- dv2: die YValues aus dem Dataview dv2 (bspw. enthält dv2 die Zeilen mit den Inhalten Jahr und Stunden)
und es werden die Felder erstellt
- xdv1 enthält die XValues für die Serien
- ydv1... enthält die YValues für die Serie "Umsatz"
- ydv2... enthält die YValues für die Serie "Stunden"
Für jede Serie ermittle ich eine passende Schrittweite.
In meinem Falle:
Dim stp1 As Single = MaxMinOfArray(ydv1) * delSec Dim stp2 As Single = MaxMinOfArray(ydv2) * delSec
Und definiere zwei Felder für die anfänglichen Null-YValues je Serie:
Dim in1(dv1.Count - 1) As Single Dim in2(dv2.Count - 1) As Short
Jetzt erfolgt der Aufruf der Prozedur:
AnimateCharts(Chart1, "Umsatz", dv1.Count, MaxMinOfArray(ydv1) * delSec, _ xdv1, ydv1, delSec, , CShort(Rnd())) AnimateCharts(Chart1, "Stunden", dv2.Count, CShort(MaxMinOfArray(ydv2) * delSec), _ xdv1, ydv2, delSec, , CShort(Rnd()))
CShort(Rnd()) setze ich hier ein für eine zufällige Animationsart ein...
Wie löst man die Animation aus?
Ein richtiges Chart-Ereignis habe ich dafür nicht gefunden. Man muss eventuell den Trick anwenden:
Einen Button definieren, diesen außer Sicht lokalisieren, den Animationsstart ins Click-Ereignis dieses Buttons legen und dann mit Performclick auslösen.
Ich habe beispielsweise Charts auf Seiten eines TabControls und löse die Animation bei Wechsel der Seite (TabControl.SelectedIndexChanged) aus.
Nun wünsche ich Spaß beim Experimentieren und Ausprobieren!