16. Jun 2025
Lesedauer 6 Min.
Stapelgrafik
Chart-Komponenten in Visual Basic
Flexible Stapel-Charts für VB- und WPF-Anwendungen.

Stapel-Charts veranschaulichen die Größenverhältnisse zwischen zusammengehörigen Werten. Ein Beispiel aus dem Finanzbereich sind die sofort oder in wenigen Tagen verfügbaren Gelder, also die Liquidität. Dazu gehören neben dem Kassenbestand die Geldbestände auf dem Girokonto, aber auch Tagesgelder, die, wenn gewünscht, schon am nächsten Bankarbeitstag verfügbar sind. In Bild 1 sehen Sie ein fiktives Beispiel dafür.

Beispiel einer einfachen Stapelgrafik (Bild 1)
Autor
Die hier beschriebenen Stapel-Charts sind wie die Donut-Charts in [1] ausschließlich für den Desktop gedacht und mit der Kombination von Windows Presentation Foundation (WPF) und Visual Basic .NET unter .NET 8 geschrieben. Auch die Hauptziele sind dieselben geblieben: Man soll das Chart-Modul mit möglichst wenigen Eingaben nutzen können. Alle Größenrelationen werden abhängig von der Canvas-Größe automatisch gesetzt, lassen sich bei Bedarf aber auch verändern. Außerdem soll eine ungünstige Reihenfolge der Werte per Mausklick korrigiert werden können, und die Farben soll der Anwender per Mausrad steuern können.Die Beispielanwendung hat zwei Leinwände namens cvLinks und cvRechts, die im XAML-Code definiert werden:
...
<StackPanel Orientation="Horizontal"
Margin="30" Height="550">
<Canvas Name="cvLinks" Margin="10 0 0 0"
Width="550" Height="650"
Background="LavenderBlush" />
<Canvas Name="cvRechts"
Margin="10 0 0 0"
Width="550" Height="650"
Background="LavenderBlush" />
...
StackPanel>
...
Der Methode StapelChart müssen neben der Canvas, auf die gezeichnet werden soll, noch eine Überschrift sowie die Werte-Bezeichner-Paare übergeben werden. Darüber hinaus gibt es zwölf optionale Eingabemöglichkeiten, die für individuelle Anpassungen bereitstehen. Die komplette Signatur der Methode sehen Sie in Listing 1. Der Stapel-Chart in Bild 2 zeigt die Einnahmen eines Vereins. Der Aufruf sieht so aus:
Listing 1: Signatur der Methode StapelChart
Public Sub StapelChart(
cv As Canvas,
HeadLine As String,
wbListe As List(Of wbPaar),
Optional MitSumme As Boolean = True,
Optional Einheit As String = " Euro",
Optional nk2 As Boolean = True,
Optional Farbset As Integer = 0,
Optional Farbverlauf As Integer = 1,
Optional sBreite As Integer = 0,
Optional freeLeft As Integer = 0,
Optional freeTop As Integer = 0,
Optional freeBottom As Integer = 0,
Optional HLFontSize As Integer = 0,
Optional lines As Boolean = True,
Optional CU As String = ""
)
...
End Sub

Fiktiver Verein: StapelChart(cvRechts, "Einnahmen",data2, CU:="Herkunft der Gelder im Jahr 2024") (Bild 2)
Autor
StapelChart(cvRechts, "Einnahmen", data2,
CU:="Herkunft der Gelder im Jahr 2024")
Die Grafik wird folglich auf die rechte Canvas gezeichnet (cvRechts), die Überschrift lautet Einnahmen, in data2 liegen die Werte und Bezeichner, und eine optionale Chartunterschrift (CU:=”...”) wird ebenfalls festgelegt.Weil Wert und Bezeichner quasi unzertrennlich zusammengehören, kennt das Programm die Struktur wbPaar, die wie folgt aussieht:
Structure wbPaar
Dim w As Decimal ' Wert
Dim b As String ' Bezeichner
End Structure
Da in der Regel mehrere Wert-Bezeichner-Pärchen erforderlich sind, werden diese in einer Liste verwaltet, sie heißt wbListe. Die für den obigen Aufruf zusammengestellte Liste data2, deren Ergebnis Sie in Bild 2 sehen, wird mit folgenden Zeilen erzeugt:
Dim data2 As New List(Of wbPaar) From {
New wbPaar With {
.w = 4120.0, .b = "Mitgliedsbeiträge"},
New wbPaar With {
.w = 320.0, .b = "Zuschüsse"},
New wbPaar With {
.w = 640.0, .b = "Spenden"},
New wbPaar With {
.w = 1397.12, .b = "Jubiläumsfest"},
New wbPaar With {
.w = 993.8, .b = "Weihnachtsmarkt"},
New wbPaar With {
.w = 44.5, .b = "Andere"}
}
Das sieht kompliziert aus, ist es aber nur, weil hier die Daten quasi manuell für eine Beispielgrafik erfasst wurden. Nutzt man StapelChart zur Ausgabe von Grafiken zu den von einem Programm ermittelten Daten, gilt es dafür einmalig den Code für die Übergabe der Daten zu schreiben.
Methode StapelChart
Damit die wenigen Eingaben genügen, um eine ansehnliche Grafik zu ergeben, muss die Methode StapelChart einige Aufgaben erledigen:- Prüfen, ob die maximale Anzahl von neun Wert-Bezeichner-Paaren überschritten wurde.
- Die optionalen Werte passend zur Canvas-Größe einstellen.
- Die Eckpunkte für die Grafik (StartPt_oben und StartPt_unten) berechnen.
- Alle Werte in eine Variable von Typ StapelInfos (Listing 2) übernehmen.
Listing 2: Die Struktur StapelInfos
Structure StapelInfos
Dim cv As Canvas
Dim Headline As String
Dim wbListe As List(Of wbPaar)
Dim nk2 As Boolean
Dim MitSumme As Boolean
Dim Einheit As String
Dim Farbset As Integer
Dim Farbverlauf As Integer
Dim sBreite As Integer
Dim freeLeft As Integer
Dim freeTop As Integer
Dim freeBottom As Integer
Dim HLFontSize As Integer
Dim CSpaceH As Integer
Dim StartPt_oben As Point
Dim StartPt_unten As Point
Dim Lines As Boolean
Dim CU As String
End Structure
- Die Methode Stapeln(StapelInfos) aufrufen.
Dim SI As New StapelInfos
With SI
.sBreite = If(sBreite = 0,
CInt(cv.Width * 0.325), sBreite)
.freeLeft = If(freeLeft = 0,
CInt(cv.Width * 0.125), freeLeft)
.freeTop = If(freeTop = 0,
CInt(cv.Height * 0.175), freeTop)
.freeBottom = If(freeBottom = 0,
CInt(cv.Height * 0.2), freeBottom)
.HLFontSize = If(HLFontSize = 0,
CInt(cv.Height * 0.05), HLFontSize)
End With
Die Breite der Balken ist mit 32,5 Prozent der Breite der Canvas spezifiziert, die Größe der Überschrift beträgt 5 Prozent der Höhe der Canvas.Sagen Ihnen die hier gewählten Proportionen nicht zu, können Sie diese einfach ändern und ausprobieren. Nicht alle
automatisch gesetzten Größen sind hier zu finden. Die Größe der Schrift der Bezeichner neben den einzelnen Blöcken wird erst in Stapeln(StapelInfos) mit 60 Prozent der Größe der Überschrift festgelegt, und die Schriftgröße der Chart-Unterschrift wird dort mit der Schriftgröße der Bezeichner gleichgesetzt.Zudem wird die Position der optionalen Linien erst in der Methode Stapeln ermittelt. Den kompletten Code von StapelChart, der mit dem Aufruf von Stapeln(SI) endet, finden Sie in Listing 3.
automatisch gesetzten Größen sind hier zu finden. Die Größe der Schrift der Bezeichner neben den einzelnen Blöcken wird erst in Stapeln(StapelInfos) mit 60 Prozent der Größe der Überschrift festgelegt, und die Schriftgröße der Chart-Unterschrift wird dort mit der Schriftgröße der Bezeichner gleichgesetzt.Zudem wird die Position der optionalen Linien erst in der Methode Stapeln ermittelt. Den kompletten Code von StapelChart, der mit dem Aufruf von Stapeln(SI) endet, finden Sie in Listing 3.
Listing 3: Die Methode StapelChart
Public Sub StapelChart(cv As Canvas,
HeadLine As String,
wbListe As List(Of wbPaar),
Optional ... siehe Listing 1)
' Hier werden die Daten in die Struktur
' StapelInfos gepackt, die optimalen Werte
' passend zur Canvas-Größe eingestellt
' und an Stapeln(StapelInfos) weitergereicht.
If wbListe.Count > 9 Then
MsgBox("Maximal 9 Wertepaare ... !")
Exit Sub
End If
Dim SI As New StapelInfos
With SI
.cv = cv
.wbListe = wbListe
.Einheit = Einheit
.nk2 = nk2
.MitSumme = MitSumme
.Farbset = Farbset
.Headline = HeadLine
.Farbverlauf = Farbverlauf
.Lines = lines
.CU = CU
' Optionale Werte einstellen
.sBreite = If(sBreite = 0,
CInt(cv.Width * 0.325), sBreite)
.freeLeft = If(freeLeft = 0,
CInt(cv.Width * 0.125), freeLeft)
.freeTop = If(freeTop = 0,
CInt(cv.Height * 0.175), freeTop)
.freeBottom = If(freeBottom = 0,
CInt(cv.Height * 0.2), freeBottom)
.HLFontSize = If(HLFontSize = 0,
CInt(cv.Height * 0.05), HLFontSize)
'Startpunkte oben / unten berechnen
.StartPt_oben = New Point(
.freeLeft, .freeTop)
.StartPt_unten = New Point(
.freeLeft, cv.Height - .freeBottom)
End With
Stapeln(SI)
End Sub
Die Methode Stapeln
Die Methode Stapeln übernimmt alle StapelInfos und kann daraus den Stapel-Chart aufbauen. Folgende Teilaufgaben werden hier erledigt:Die Canvas wird leer geräumt, damit nicht mehrere Stapel übereinander gedruckt werden. Falls gewünscht, werden Linien gezeichnet, danach die Daten zur Headline in der Struktur TBDetails(Listing 4) zusammengefasst und deren Init-Methode aufgerufen, damit sie mit der Methode showOnCanvas auf der Leinwand platziert werden kann:Listing 4: Die Strukturen TBDetails und TextWH
Public Structure TBDetails
Dim Text As String
Dim uiObj As TextBlock
Dim twh As TextWH
Dim Start As Point
Dim FontSize As Integer
Dim FontWeight As FontWeight
Dim ForeGround As Brush
Dim MarginBottom As Integer
Sub Init()
FontSize = Math.Max(FontSize, 14)
ForeGround = If(ForeGround, Brushes.Black)
MarginBottom = Math.Max(MarginBottom, 4)
uiObj = getTextBlock(Me)
twh = TextHöheUndBreite(uiObj,
VisualTreeHelper.GetDpi(
Application.Current.MainWindow)
.PixelsPerDip)
End Sub
End Structure
Public Structure TextWH
Dim width As Double
Dim height As Double
End Structure
Sub Stapeln (SI as StapelInfos)
...
Dim HL As New TBDetails
With HL
.Text = SI.Headline
.FontSize = SI.HLFontSize
.FontWeight = FontWeights.SemiBold
.Init()
.Start.X = SI.StartPt_oben.X
.Start.Y = SI.HLFontSize \ 2
End With
showOnCanvas(SI.cv, HL)
...
End Sub
Sub showOnCanvas(c As Canvas, obj As Object)
Canvas.SetLeft(obj.uiObj, obj.start.X)
Canvas.SetTop(obj.uiObj, obj.start.Y)
c.Children.Add(obj.uiObj)
End Sub
Nachdem die Headline gesetzt ist, wird ermittelt, wie viel Platz für die eigentliche Grafik zur Verfügung steht:
SI.CSpaceH = CInt(
SI.cv.Height - SI.freeTop - SI.freeBottom)
Dann wird die Summe der Werte errechnet, um die Pixel pro Werteinheit auszurechnen:
Dim SumWerte As Decimal =
SI.wbListe.Sum(Function(item) item.w)
Dim yPixelProWerteinheit As Double =
SI.CSpaceH / SumWerte
Falls gewünscht, wird die Summe der einzelnen Werte ebenfalls in TBDetails gekleidet und per showOnCanvas angezeigt:
...
If SI.MitSumme Then
Dim SumHL As New TBDetails
With SumHL
.Text = If(SI.nk2, SumWerte.nk2 + SI.Einheit,
SumWerte.ToString + SI.Einheit)
.Text = SumWerte.nk2 + SI.Einheit
.FontSize = CInt(SI.HLFontSize * 0.6)
.FontWeight = FontWeights.SemiBold
.Init()
.Start.X = SI.StartPt_oben.X +
(SI.sBreite - .twh.width) \ 2
.Start.Y = SI.StartPt_oben.Y -
(.FontSize + .FontSize \ 4)
End With
showOnCanvas(SI.cv, SumHL)
End If
...
Dann ist es so weit und die Blöcke werden gezeichnet, siehe Listing 5. Die Rechtecke werden von unten nach oben gestapelt. Für jeden Wert gibt es einen eigenen Block mit Bezeichnung. Die Breite des Blocks bestimmt SI.sBreite, die Höhe wird mit wert * yPixelProWerteinheit errechnet. Als Mindesthöhe – damit man den Block noch gut erkennt – sind 5 Pixel vorgegeben. Gezeichnet werden die Blöcke von der Methode blRechteck, welcher die Farbe mit der Farbnummer f aus dem gewählten Farbset (im Beispielprogramm gibt es drei Farbsets) übergeben wird:
Listing 5: Blöcke zeichnen
Sub Stapeln(SI as StapelInfos)
...
Dim start As Point = SI.StartPt_unten
Dim f As Integer = 0 ' Farbzähler
Dim BU As New TBDetails
For Each item In SI.wbListe
Dim höhe As Double = Math.Max(
item.w * yPixelProWerteinheit, 5)
' neuer Startpunkt: X bleibt gleich,
' Y muss vermindert werden
start.Y = start.Y - höhe
' Das Rechteck zeichnen
blRechteck(SI, start, höhe,
blFarbe(f, SI.Farbset))
With BU ' Die Beschriftung einfügen
' Decimal.nk2 = 2 Nachkommastellen plus
' Umwandeln in einen String
' Für 2-zeilige Texte muss das Rechteck
' mindestens 16 Prozent des Stapels ein-
' einnehmen, sonst bleibt es bei einer Zeile
If item.w > 0.16 * SumWerte Then
.Text = If(SI.nk2, item.b + vbCrLf +
item.w.nk2 + SI.Einheit, item.b +
vbCrLf + item.w.ToString + SI.Einheit)
Else
.Text = If(SI.nk2, item.b + ": " +
item.w.nk2 + SI.Einheit, item.b +
": " + item.w.ToString + SI.Einheit)
End If
.FontSize = CInt(SI.HLFontSize * 0.6)
.FontWeight = FontWeights.SemiBold
.ForeGround = New SolidColorBrush(blFarbe(
f, SI.Farbset))
.Init()
.Start.X = start.X + SI.sBreite + 8 '
.Start.Y = start.Y + höhe / 2 -
.twh.height / 3
End With
showOnCanvas(SI.cv, BU)
f += 1 ' die nächste Farbe ist dran
Next
...
End Sub
blRechteck(SI, start, höhe,
blFarbe(f, SI.Farbset))
Eine Besonderheit bei der Beschriftung (BU) ist, dass für Werte ab 16 Prozent der Gesamtsumme zweizeilige Bezeichner vergeben werden, kleinere Werte sind nur einzeilig. Als FontSize werden 60 Prozent der Größe der Überschrift eingestellt. showOnCanvas übernimmt wieder das Platzieren der BUs auf der Leinwand. Die letzte Aufgabe der Methode Stapeln ist das Setzen der Chart-Unterschrift mit der Methode CU_auf_Canvas_setzen, die auch schon für die vbDonuts [1] für diesen Zweck genutzt wurde. Weil WPF die Zeilenabstände bei zweizeiligen BUs für meinen Geschmack zu groß wählt, wurden diese vermindert mit:
TextBlock.LineHeight = TextBlock.FontSize
TextBlock.LineStackingStrategy =
LineStackingStrategy.BlockLineHeight
Achtung: Fehlt die Zeile .LineStackingStrategy …, wird der Eintrag für LineHeight nicht berücksichtigt.
Blöcke zeichnen
Das Zeichnen der Blöcke erledigt die Methode blRechteck mit wenigen Zeilen Code:
Public Sub blRechteck(SI As StapelInfos,
Start As Point, h As Double, farbe As Color)
Dim myRect As New Rectangle() With {
.Width = SI.sBreite,
.Height = h,
.Stroke = Brushes.White,
.StrokeThickness = 1
}
...
' Setze die Position des Rechtecks
Canvas.SetLeft(myRect, Start.X)
Canvas.SetTop(myRect, Start.Y)
SI.cv.Children.Add(myRect)
End Sub
Anstelle der drei Punkte im Code-Schnipsel oben wird die Füllfarbe des Rechtecks gesetzt, wobei das Beispielprogramm neben einer SolidColorBrush-Variante zwei unterschiedliche Farbverläufe vorsieht:
Select Case SI.Farbverlauf
Case 1
myRect.Fill =
Farbverlauf_V1(farbe)
Case 2
myRect.Fill = Farbverlauf_Metallic(farbe)
Case Else
myRect.Fill = New SolidColorBrush(farbe)
End Select
Außerdem erhält das Rechteck drei Eventhandler, sodass es auf Klicks mit der linken und rechten Maustaste, auf das Drehen des Mausrads sowie auf Doppelklicks mit der linken Maustaste reagieren kann. Die Interaktionen des Anwenders sollen folgende Auswirkungen haben:
- Einfacher Klick mit der linken Maustaste: Sortieren der Werte aufsteigend und beim nächsten Klick absteigend, siehe Bild 3.

Links die ursprünglichen Werte, rechts die per Mausklick sortierten Werte. Ein weiterer Linksklick sortiert die Blöcke in umgekehrter Reihenfolge (Bild 3)
Autor
- Doppelklick mit der linken Maustaste: Zusammenfassen aller Werte, die einzeln kleiner als 5 Prozent der Gesamtsumme sind, zum neuen Wert Übrige, siehe Bild 4 und Listing 6.

Ein großer, viele Mini-Werte: Ein Doppelklick links fasst die kleinen Werte zu „Übrige“ zusammen (Bild 4)
Autor
Listing 6: Zusammenfassen kleiner Werte
Sub übrigeZusammenfassen(ByRef wb As List(
Of wbPaar), Optional Prozent As Integer= 5)
Dim sum As Decimal =
wb.Sum(Function(item) item.w)
' Anhand des übergebenen Parameters Prozent
' den Schwellenwert min berechnen
Dim min As Decimal = sum * Prozent / 100
' MiniWerte herausfiltern
Dim MiniWerte As Integer = wb.Where(
Function(item) item.w < min).Count()
If MiniWerte < 2 Then Exit Sub
Dim übrig As Decimal
' Summiere Werte kleiner als min und
' lösche die entsprechenden Elemente
übrig = wb.Where(
Function(item) item.w < min).Sum(
Function(item) item.w)
wb.RemoveAll(
Function(item) item.w < min)
wb.Add(New wbPaar With {
.w = übrig, .b = "Übrige"})
End Sub
- Einfacher Rechtsklick: Zufälliges Neuanordnen der Reihenfolge der Blöcke, damit zu eng beieinanderstehende Beschriftungen sich voneinander entfernen (Bild 5).

Ungünstige Reihenfolge (links): Ein einfacher Rechtsklick ändert die Abfolge der Daten (Bild 5)
Autor
- Mausrad nach oben drehen: Farbverlauf wechseln; nach unten drehen: Farbset wechseln (Bild 6).

Die Farbsets und -verläufe werden per Mausrad geändert (Bild 6)
Autor
Da Rectangle-Objekte eigentlich kein Doppelklick-Ereignis kennen, wird dieser Handler hier vorgestellt. Nach der Zuordnung des Farbverlaufs wird der Handler hinzugefügt, wobei ihm auch die StapelInfos (SI) übergeben werden:
AddHandler myRect.MouseLeftButtonDown,
Sub(sender As Object, e As MouseButtonEventArgs)
myRect_MouseLeftButtonDown(sender, e, SI)
End Sub
Der Eventhandler sieht wie folgt aus:
Private Sub myRect_MouseLeftButtonDown(
sender As Object, e As MouseButtonEventArgs,
SI As StapelInfos)
If e.ClickCount = 2 Then
übrigeZusammenfassen(SI.wbListe)
Else
If SI.wbListe.Item(1).w <
SI.wbListe.Item(0).w Then
SI.wbListe =
SI.wbListe.OrderBy(
Function(item) item.w)
.ToList()
Else
SI.wbListe = SI.wbListe
.OrderByDescending(
Function(item) item.w)
.ToList()
End If
End If
Stapeln(SI)
End Sub
Steht der Klickzähler auf 2, wird die mit den StapelInfos übergebene wbListe auf- beziehungsweise absteigend sortiert, die StapelInfos werden angepasst und mit Stapeln(SI) das Neuzeichnen des Charts veranlasst. Die beiden übrigen Eventhandler sind ähnlich, wichtigster Unterschied: Anstelle der MouseButtonEventArgs müssen dem Mausrad-Handler die MouseWheelEventArgs übergeben werden.
Fussnoten
- Bernhard Lauer, Donut im Eigenbau, dotnetpro 4-5/2025, Seite 54 ff., http://www.dotnetpro.de/A2504-05VBDonuts