Chart-Komponenten in Visual Basic 14.04.2025, 00:00 Uhr

Donut im Eigenbau

Einfach nutzen oder individuell ausbauen.
(Quelle: EMGenie)
Eine meiner Aufgaben bestand kürzlich darin, ein in Excel umgesetztes Finanzprogramm auf .NET zu portieren und dabei zu modernisieren. Umgesetzt wurde die Aufgabe in Visual Basic .NET und mit der Windows Presentation Foundation (WPF), weil das .NET-Programm künftig ausschließlich auf Windows-Desktop-Rechnern laufen soll. Der Umbau gestaltete sich nicht besonders schwierig und war in kurzer Zeit erledigt. Mit einer Ausnahme: Die Excel-Anwendung überzeugte vor allem mit ihrem Dashboard, welches die aktuellen Daten übersichtlich in Form von Charts zusammengefasst hat. Um das Dashboard nachzubauen, waren unabdingbar Chart-Komponenten für .NET erforderlich.
Die in vielen kostenpflichtigen Komponentensammlungen enthaltenen Charts kamen wegen der Kosten nicht infrage. Eine kurze Recherche förderte die .NET-Chartbibliothek Live­Charts2 [1] zutage, die unter der kostenlosen MIT-Lizenz läuft und optional ein kostenpflichtiges Paket zur Verbesserung der Leistung und Erweiterung der Funktionen umfasst. So wunderbar die animierten Beispiele auf der Webseite der Bibliothek auch waren, ein Blick in die Dokumentation zeigte, dass diese Lösung wohl etwas zu umfangreich für das Vorhaben ist. So reifte der Entschluss, die Zeit nicht in die Einarbeitung zu investieren, sondern mit VB.NET und WPF etwas Eigenes aufzubauen. Die Ergebnisse – vorerst nur die Donut- und Kreisdiagramme – werden hier vorgestellt.
Ziel der Arbeit war ein besonders einfach zu nutzendes System, das zugleich flexible Anpassungen erlaubt, aber längst nicht alle Möglichkeiten eines vollständigen Profi-Chartsystems umfasst. Wichtiger war, dass es übersichtlich bleibt und leicht weiterzuentwickeln ist. Mit dem Stempel „Yagni“ (You ain’t gonna need it) versehen wurden neben den Animationen zum Beispiel die Möglichkeit, einzelne Kreissegmente aus dem Chart herauszuziehen, oder die automatische Vermeidung von Kollisionen der Segmentbeschriftungen. Letzteres wurde einfach damit abgefangen, dass Anwender den Kreis bei Bedarf per Mausklick drehen und eventuell ineinanderlaufende Texte damit entflechten können.
Um einen blDonut – so der Name des Chart-Elements – zu erzeugen, genügt der folgende minimale Aufruf:

blDonut(canvas, "Minimal", {224, 876})
Dabei ist canvas der Name der im XAML-Code angelegten Leinwand, auf die der Donut gezeichnet werden soll, Minimal ist die Überschrift des Charts, und das Array {224, 876} ­liefert die beiden Werte, für die jeweils ein Kreissegment dargestellt werden soll. Alle übrigen für den Aufbau des Donuts erforderlichen Werte setzt blDonut anhand seiner Vorgaben selbst ein. In Bild 1 sehen Sie das Ergebnis.
Minimaler Donut-Chart (Bild 1)
Quelle: Autor
Die Canvas, auf welcher der Donut gezeichnet wird, gibt der WPF-XAML-Code vor, beispielsweise so:

<Window x:Class="MainWindow"
  ...
  Title="blDonut" Width="1600" Height="1000">
  <Grid>
    <StackPanel x:Name="stpChart" 
      VerticalAlignment="top">
      <Canvas x:Name="canvas" Margin="20" 
        Width="1040" Height="980"
        Background="LavenderBlush"
      />
      ...
    </StackPanel>
    ...
</Window>
Damit ist dem Programm die Leinwand für die Grafik bekannt. Den minimalen Aufruf von blDonut von oben kann man jetzt einfach ins Ereignis MainWindow_Loaded(...) setzen, und die Grafik wird beim Programmstart gezeichnet. Die im StackPanel stpChart angelegte Canvas hat im Beispiel die Maße 1040 x 980 Pixel. Diese Werte für Breite und Höhe nutzt blDonut, um passende Maße für den Chart zu ermitteln. Außer­dem sind Standardfarben hinterlegt, sodass man auf Anhieb eine ansprechende Grafik erhält. Der Hintergrund LavenderBlush wird im XAML-Code gesetzt und kann folglich selbst gewählt werden, auch wenn man weiterhin die Standardfarben nutzen möchte.
Die übergebenen Werte werden jeweils neben der Mitte des zugehörigen Kreissegments in der Segmentfarbe als Beschriftung angezeigt, und die Summe der Werte steht, wie in Bild 1 zu sehen ist, im „Auge“ des Donuts, sofern dort genügend Platz für den Wert ist.

Flexibel ausgebaut

Das minimale API ist allerdings nur für den schnellen Donut zwischendurch gedacht. Die Methode blDonut bietet darüber hinaus eine Menge Möglichkeiten, den Chart an spezielle Anforderungen anzupassen. Welche Optionen zur Verfügung stehen, zeigt Listing 1. Alle darin als optional angegebenen ­Parameter kann man beim Aufruf der Methode übergeben und damit das Standardverhalten ändern.
Listing 1: Signatur der Methode blDonut()
Sub blDonut(canvas As Canvas,
  headline As String,
  werte() As Decimal,
  Optional Farben() As String = Nothing,
  Optional vorWert() As String = Nothing,
  Optional nachWert() As String = Nothing,
  Optional textMitte As String = "",
  Optional FontSize As Integer = 0,
  Optional stAngle As Integer = 0,
  Optional TopMargin As Integer = 0,
  Optional outerRadius As Integer = 0,
  Optional innerRadius As Integer = -1,
  Optional CU As String = "",
  Optional DebugMode As Boolean = False
)
Const MaxSegmente = 9
...
Der folgende Beispielaufruf hat vier Segmente, die Überschrift lautet Private Ausgaben, in der Mitte steht anstelle der Summe der Werte das Datum, bis zu dem das Geld ausgegeben wurde. Vor den Werten steht, wofür das Geld verwendet wurde, und dahinter das Euro-Zeichen. Zudem sorgt der Aufruf für von der Standardpalette abweichende Farben und wählt einen größeren Innenradius, wodurch der Donut dünner gezeichnet wird. Abschließend wird bestimmt, dass der Chart um 36 Grad im Uhrzeigersinn gedreht wird.

blDonut(canvas,
  headline:="Private Ausgaben",
  Farben:={"DarkTurquoise", "Sienna", "Purple", 
    "DarkOliveGreen"},
  vorWert:={"Irish Pub: ", "Supermarkt: ", 
    "Bäcker: ", "Tanken: "},
  werte:={22.0, 33.12, 12.8, 72.02}, 
  nachWert:={" €"},
  textMitte:="31.01.2025",
  CU:="Übersicht der Ausgaben am 31. Januar 2025",
  innerRadius:=200,
  stAngle:=36)
Die optionalen Parameter wurden hier mit ihrem Namen angegeben (Beispiel: Farben:= {DarkTurquoise, ...) , sodass die Reihenfolge nicht beachtet werden muss. Den durch diesen Aufruf erzeugten Chart sehen Sie in Bild 2. Die Abkürzung CU steht übrigens für Chart-Unterschrift. Beim Abschätzen, um wie viel man den Startwinkel (stAngle) verändern sollte (Standard = 0), hilft der Debug-Modus, den Sie weiter unten kennenlernen.
Donut mit individuellen Farben und selbst gewähltem Innendurchmesser (Bild 2)
Quelle: Autor
Absolute Werte, wie hier für den Innendurchmesser, sollte man mit etwas Fingerspitzengefühl wählen. Bestimmt man aus Versehen zum Beispiel einen inneren Radius, der größer ist als der hier automatisch berechnete äußere Radius, führt das zwar zu keinem Absturz, aber teilweise zu seltsamen Ergebnissen, bei denen die Beschriftungen innerhalb des Donuts liegen.

Sonderfall Kreisdiagramm

Um anstelle eines Donuts einen Kreis zeichnen zu lassen, genügt es, den Innenradius auf 0 zu setzen. blDonut überprüft, ob der in die Mitte zu schreibende Text dort auch Platz hat. Ist das nicht der Fall, wird er automatisch weggelassen. Hier der Aufruf für das einfache Kreisdiagramm, das Sie in Bild 3 sehen:
Mit einem Innendurchmesser von 0 wird der Donut zum Kreisdiagramm (Bild 3)
Quelle: Autor

blDonut(canvas, 
  "7 Segmente Kreisdiagramm", 
  {16696, 3917, 7435, 997, 1855, 20464, 1214}, 
  innerRadius:=0)
Die Nutzung und der Zweck der in Bild 2 und Bild 3 benutzten optionalen Parameter dürfte klar sein. Die weiteren optio­nalen Parameter sind FontSize für die Schriftgröße der Segmentwerte, TopMargin für einen größeren oder kleineren Abstand vom oberen Rand der Canvas und outerRadius für die Größe des Donut-Kreises insgesamt.
Bild 4 zeigt ein Beispiel für einen schlanken Donut mit dem Maximalausbau von neun Segmenten und einer zweizeiligen Überschrift, welche einfach durch einen in den String eingefügten Zeilenumbruch erzeugt wird. Der Code für den Aufruf sieht so aus:
Dünner Ring mit den als Maximum festgelegten 9 Werten (Bild 4)
Quelle: Autor

Dim Headline As String = "9 Segmente," & 
  vbCrLf & "großer Innenradius"
Dim Werte() As Decimal = {2400, 9030, 220, 4620, 
  1340, 1880, 16000, 1450, 999}
Dim vorWert() As String = {"1 -->: ", "2 -->: ", 
  "3: --> ", "4: --> ", "5: --> ", "6: --> ", 
  "7: --> ", "8: --> ", "9: --> "}
blDonut(canvas, Headline, Werte, 
  vorWert:=vorWert, FontSize:=20, 
  outerRadius:=220, innerRadius:=190, 
  stAngle:=45, CU:=" ")
Das Zentrieren der beiden Zeilen der Überschrift auf der Canvas übernimmt blDonut ohne weiteres Zutun. Außerdem erlaubt blDonut es noch, den Parameter DebugMode auf True zu setzen:

blDonut(... DebugMode:=True)
Dadurch werden zum eigentlichen Chart noch Begrenzungslinien für die Canvas und Linien durch den Mittelpunkt des Kreises hinzugefügt. Die Segmentbeschriftungen erhalten ­einen rechteckigen Rahmen, und es gibt eine Linie zum Kreispunkt in der Mitte des aktuellen Segments zum oberen linken Startpunkt des Segmenttextes. Die Chart-Unterschrift meldet außerdem alle Werte zum Chart. In Bild 5 sehen Sie ein Beispiel, dessen Aufrufdaten in der folgenden Zeile stehen:
Debug-Modus mit Größenangaben in der Chart-Unterschrift (Bild 5)
Quelle: Autor

blDonut(canvas, "Prozentwerte, Debug-Modus", 
  {70, 17, 9, 4}, vorWert:={"FestVerz. ", 
  "Aktien ", "Liquide ", "Bitcoin "},
  nachWert:={"%"}, textMitte:="Plan 2026", 
  DebugMode:=True, outerRadius:=240, 
  innerRadius:=180)

Blick in den Maschinenraum

Beim Einsatz von blDonut kommt man nur mit der gerade vorgestellten, gleichnamigen Methode in Berührung. Diese Methode ist in ein Modul namens ChartModul ausgelagert. Im Modul wird zunächst die Struktur DonutSkelett angelegt, die neben den schon bekannten Daten auch die Anzahl der Segmente als Integer-Wert aufnimmt, siehe Listing 2. Diese Struktur wird instanziert mit Dim don As DonutSkelett und im weiteren Verlauf an alle Methoden durchgereicht, die Zugriff auf diese Daten benötigen.
Listing 2: ChartModul mit Struktur DonutSkelett
Module ChartModul
  Structure DonutSkelett
    Dim canvas As Canvas
    Dim AnzSegmente As Integer
    Dim TopMargin As Integer
    Dim Headline As String
    Dim outerRadius As Integer
    Dim innerRadius As Integer
    Dim Center As Windows.Point
    Dim startAngle As Integer
    Dim werte() As Double
    Dim Farben() As String
    Dim vorWert() As String
    Dim nachWert() As String
    Dim textMitte As String
    Dim fontSize As Integer
    Dim CU As String  ' Chart-Unterschrift
    Dim DebugMode As Boolean
    Sub Init()
      Center.X = canvas.Width \ 2
      Center.Y = TopMargin + outerRadius
    End Sub
  End Structure
  ...
Die Methode blDonut(), deren Signatur sie schon in Listing 1 gesehen haben, ermittelt die Anzahl der Segmente aus der Anzahl der übergebenen Werte und überträgt die Eingabe­parameter nach einer Fehlerprüfung in eine Variable vom Typ DonutSkelett. Für fehlende Daten werden Standardwerte eingesetzt, beispielsweise das Array mit den Standardfarben.

If Farben Is Nothing Then Farben = {
  "DarkBlue", "MediumSeaGreen", 
  "DarkOrange", "CornflowerBlue", "Goldenrod",
  "DarkOliveGreen", "DarkRed", "DarkCyan",
  "Orchid", "Black"}
Zu den Werten, die im Aufruf von blDonut nicht angegeben werden müssen, gehört auch die Größe des Charts. Diese wird anhand der folgenden Relationen automatisch gesetzt:

Dim don As DonutSkelett
...
With don 
  ...
  .outerRadius = .canvas.Width \ 4,
  .innerRadius = .outerRadius \ 2,
  .TopMargin = .canvas.Height \ 10,
  .fontSize = Math.Min(20, .outerRadius \ 10)
  ...
End With
Zum Abschluss ruft blDonut noch die Init-Methode des DonutSkeletts auf, welche den Mittelpunkt des Kreises ermittelt, dessen y-Koordinate abhängig von den angegebenen Größendaten ist.

Structure DonutSkelett
  ...
  Sub Init()
    Center.X = canvas.Width \ 2
    Center.Y = TopMargin + outerRadius
  End Sub
End Structure
Dann übergibt blDonut das DonutSkelett an die Methode MakeDonut, die das Zeichnen des Charts übernimmt.

MakeDonut(don)

Die Kernmethoden

Die Methode MakeDonut hat dank der Struktur DonutSkelett eine sehr einfache Signatur:

Sub MakeDonut(d As DonutSkelett)
Alle noch anfallenden Aufgaben werden von dieser Methode erledigt:
  • In einer For-Schleife werden die Kreissegmente berechnet, gezeichnet, mit Eventhandlern für Mausklicks versehen, denen ebenfalls das DonutSkelett übergeben wird, und auch das Einsetzen der Segmenttexte wird hier erledigt, im Debug-Modus inklusive Rahmen und Linie von Kreis zum Textblock.
  • Der Text für das „Auge“ des Donuts wird eingesetzt, sofern dort genug Platz dafür vorhanden ist.
  • Im Debug-Modus werden die Daten für die spezielle Chart-Unterschrift zusammengestellt.
  • Die Chart-Unterschrift wird platziert.
Die For-Schleife läuft von 0 bis zur Anzahl der Segmente, die sich aus der Anzahl der darzustellenden Werte ergibt und schon im DonutSkelett gespeichert ist. Das erste Kreissegment startet beim im Skelett hinterlegten Startwinkel (stAn­gle, Standard = 0) und läuft bis zum Endwinkel ea. Dieser wird ermittelt über den Anteil, den der aktuelle Wert an der Gesamtsumme aller Werte (total) hat. Die Berechnung innerhalb der Schleife sieht somit so aus:

' Endwinkel ea des Segments berechnen
ea = d.werte(i) / total * 360 + stAngle
Das genügt, um mit der Funktion CreateDonutSegment ein Segment vom Datentyp Path zu berechnen:

segment(i).Data = 
  CreateDonutSegment(d, stAngle, ea)
Die Funktion CreateDonutSegment(Listing 3) ermittelt vier ­spezifische Punkte: Zwei befinden sich am äußeren Rand des Donuts, nämlich startOuter und endOuter, und zwei weitere am inneren Rand, nämlich startInner und endInner.
Listing 3: CreateDonutSegment
Private Function CreateDonutSegment(
  d As DonutSkelett, 
  startAngle As Double, 
  endAngle As Double) As Geometry
 
  Dim startOuter As System.Windows.Point =
    GetPoint(d.Center, d.outerRadius, 
    startAngle)
  Dim endOuter As System.Windows.Point = 
    GetPoint(d.Center, d.outerRadius, endAngle)
  Dim startInner As System.Windows.Point = 
    GetPoint(d.Center, d.innerRadius, 
    startAngle)
  Dim endInner As System.Windows.Point = 
    GetPoint(d.Center, d.innerRadius, endAngle)
  Dim pathFigure As New PathFigure() With {
    .StartPoint = startOuter
  }
  pathFigure.Segments.Add(
    New ArcSegment(endOuter, 
    New System.Windows.Size(
      d.outerRadius, d.outerRadius), 0,
    endAngle - startAngle > 180,
    SweepDirection.Clockwise, True))
  pathFigure.Segments.Add(
    New LineSegment(endInner, True))
  pathFigure.Segments.Add(
    New ArcSegment(startInner, 
    New System.Windows.Size(
      d.innerRadius, d.innerRadius), 0,
    endAngle - startAngle > 180, 
    SweepDirection.Counterclockwise, True))
  pathFigure.Segments.Add(
    New LineSegment(startOuter, True))
  Dim pathGeometry As New PathGeometry()
  pathGeometry.Figures.Add(pathFigure)
  Return pathGeometry
End Function
Die Berechnung durch die Funktion Point, deren Code Sie inListing 4 finden, erfolgt anhand des Mittelpunkts des Donuts (d.Center) sowie des äußeren und inneren Radius (d.outer­Radius und d.innerRadius). Außerdem fließen noch die Start- und Endwinkel (startAngle und endAngle) in das Funktionsergebnis ein.
Listing 4: Die Methode GetPoint
Private Function GetPoint(
  Center As Windows.Point, 
  radius As Double, 
  angle As Double) As System.Windows.Point
  Dim radian As Double = angle * Math.PI / 180
  Return New System.Windows.Point(
    Center.X + radius * Math.Cos(radian), 
    Center.Y + radius * Math.Sin(radian))
End Function
Anschließend erstellt die Funktion eine PathFigure, die ­ihren Ursprung bei startOuter hat. Die nächsten Schritte beinhalten das Hinzufügen der Segmente:
  • Ein Bogen (ArcSegment) verbindet den startOuter- mit dem endOuter-Punkt.
  • Eine Linie (LineSegment) führt vom endOuter-Punkt zum endInner-Punkt.
  • Ein weiterer Bogen (ArcSegment) verläuft vom endInner-Punkt zum startInner-Punkt.
  • Eine abschließende Linie (LineSegment) führt vom startInner-Punkt zurück zum startOuter-Punkt, um das Segment zu schließen.
Zum Schluss wird eine PathGeometry angelegt, die PathFigure hinzugefügt und das fertige Objekt an die aufrufende Methode übermittelt.
Zurück in MakeDonut wird das Segment mit Farbe gefüllt, eine äußere Linie (Stroke) bestimmt, das Segment platziert und zur Leinwand hinzugefügt:

Dim bc As New BrushConverter
...
segment(i).Data = 
  CreateDonutSegment(d, stAngle, ea)
segment(i).Fill = 
  CType(bc.ConvertFromString(
  d.Farben(i)), SolidColorBrush)
segment(i).Stroke = Brushes.White
segment(i).StrokeThickness = 1.5
d.canvas.SetLeft(segment(i), 0)
d.canvas.SetTop(segment(i), d.TopMargin)
d.canvas.Children.Add(segment(i))
...
Der Eventhandler für einen Klick mit der linken Maustaste soll den Startwinkel um sechs Grad weiterdrehen und dann MakeDonut erneut aufrufen. Das klappt, indem man dem Eventhandler das Donut-Skelett durchreicht:

AddHandler segment(i).MouseLeftButtonDown, 
  Sub(sender As Object, e As MouseButtonEventArgs)
    Segment_MouseLeftButtonDown(sender, e, d)
  End Sub
...
Private Sub Segment_MouseLeftButtonDown(
  sender As Object,
  e As MouseButtonEventArgs, d As DonutSkelett)
  d.startAngle = (d.startAngle + 6) Mod 360
  MakeDonut(d)
End Sub
Der Segmenttext wird als WPF-TextBlock auf der Canvas platziert. Anzugeben ist dafür die linke obere Ecke des Blocks. Dabei helfen die Funktionen TextHöheUndBreite sowie StartPunktText. Wie Höhe und Breite eines Blocks ermittelt werden, sehen Sie in Listing 5. Der Aufruf dieser Funktion berücksichtigt die Auflösung des Monitors und sieht folgendermaßen aus:
Listing 5: Höhe und Breite eines TextBlocks ermitteln
Private Function TextHöheUndBreite(
  t As TextBlock, 
  pixelsPerDip As Double) As TextWH
  ' TextWH ist eine Struktur mit den Double-
  ' Werten width und height
  '
  ' Public Structure TextWH
  '   Dim width As Double
  '   Dim height As Double
  ' End Structure
  Dim twh As New TextWH
  Dim formattedText As New FormattedText(
    t.Text,
    System.Globalization.CultureInfo.CurrentCulture,
    System.Windows.FlowDirection.LeftToRight, 
    New Typeface(t.FontFamily, t.FontStyle, 
    t.FontWeight, t.FontStretch),
    t.FontSize, Brushes.Black, pixelsPerDip)
  twh.width = formattedText.Width
  twh.height = formattedText.Height
  Return twh
End Function

twh = TextHöheUndBreite(t(i), 
  VisualTreeHelper.GetDpi(
  Application.Current.MainWindow).PixelsPerDip)
Bedeutend aufwendiger ist es, den Startpunkt für den Segmenttext zu finden. Er soll neben dem Kreis und in der Mitte des zugehörigen Segments stehen. Da der Text immer von links nach rechts läuft, ist es dafür erforderlich zu berücksichtigen, ob der Text links oder rechts vom Kreis sowie ob er oben oder unten stehen soll. Listing 6 zeigt einen Auszug für den Fall, dass der Text rechts unten (Winkel 0 bis 90 Grad) stehen soll. tAng ist dabei der Winkel – beginnt das Segment bei 0 Grad und ist der Endwinkel bei 60 Grad, wäre der ­gesuchte Winkel tAng 30 Grad. Kpt ist der Bezugspunkt für den Segmenttext auf dem Kreis (für den Winkel tAng), der Abstand sorgt dafür, dass der Text nicht direkt am Kreis beginnt oder endet, t0 ist das Ergebnis der Funktion. Im DebugModuswird eine Linie vom Kreispunkt zum Startpunkt des Textes gezogen (Bild 6).
Listing 6: Startpunkt für den Segmenttext ermitteln
Private Function StartPunktText(
  tAng As Integer, 
  d As DonutSkelett, 
  twh As TextWH, 
  FontSize As Integer) As Windows.Point
  Dim Kpt As System.Windows.Point =  
    GetPoint(d.Center, d.outerRadius, tAng)
  Dim t0 As System.Windows.Point
  Dim Abstand As Integer = FontSize / 2
  ...
  If tAng >= 0 And tAng <= 90 Then
    dx = Abstand
    dy = Abstand - twh.height / 2
    t0.X = Kpt.X + dx
    t0.Y = d.TopMargin + Kpt.Y + dy
    If d.DebugMode Then
      Linie(d.canvas, 
        New Windows.Point(t0.X, t0.Y), 
        New Windows.Point(Kpt.X, Kpt.Y + 
          d.TopMargin))
    End If
  ElseIf ...
    ...
  End If
  ...
  Return t0
End Function 
Im Debug-Modus wird eine Linie vom Kreispunkt „Kpt“ zur linken oberen Ecke des eingerahmten Beschriftungstextes gezogen (Bild 6)
Quelle: Autor
Gesetzt wird der Segmenttext in der Methode MakeDonut über die folgenden Zeilen – t(i) repräsentiert hier den zum Segment i gehörenden TextBlock:

d.canvas.SetLeft(t(i), t0.X)
d.canvas.SetTop(t(i), t0.Y)
d.canvas.Children.Add(t(i))
Jetzt fehlt noch der Text für das „Auge“ des Donuts, wofür die Länge des TextBlocks wie oben über die Funktion TextHöheUndBreite gemessen und mit dem Innendurchmesser (d.innerRadius * 2 - 4) verglichen wird. Zwei Pixel rechts und links vom Text soll der Mindestabstand betragen.
Während man den einzeiligen Text in der Mitte des Donuts noch leicht selbst zentrieren kann, wäre das im Fall einer mehrzeiligen Überschrift oder für die Chart-Unterschrift schwierig. Da hilft aber folgende TextBlock-Eigenschaft:

TextBlock.TextAlignment = TextAlignment.Center
Sie zentriert jede einzelne Zeile des Textblocks ohne weiteres Zutun. Das Einsetzen der Chart-Unterschrift erledigt die Funktion CU_auf_Canvas_setzen, der das DonutSkelett übergeben wird und deren Code in Listing 7 zu finden ist.
Listing 7: Chart-Unterschrift auf Canvas setzen
Sub CU_auf_Canvas_setzen(d As DonutSkelett)
  ' Erstelle den Textblock
  Dim tb As New TextBlock()
  With tb
    .Text = d.CU
    .Foreground = Brushes.Black
    .FontSize = d.fontSize
    .FontWeight = FontWeights.Normal
    .HorizontalAlignment = 
      System.Windows.HorizontalAlignment.Center
    .VerticalAlignment =
      System.Windows.VerticalAlignment.Center
    .TextAlignment = TextAlignment.Center
  End With
  Dim startPunkt As New Windows.Point
  Dim twh As TextWH
  twh =
    TextHöheUndBreite(tb, 
    VisualTreeHelper.GetDpi(
    Application.Current.MainWindow)
    .PixelsPerDip)
  startPunkt.X = d.Center.X - twh.width \ 2
  startPunkt.Y = d.Center.Y + d.outerRadius + 
    d.TopMargin + 30
  d.canvas.SetLeft(tb, startPunkt.X)
  d.canvas.SetTop(tb, startPunkt.Y)
  d.canvas.Children.Add(tb)
End Sub

Fazit

Das Chart-Modul blDonut ist sehr einfach zu nutzen und dabei flexibel, weil sich die Geometrie des Charts am auf der Canvas vorhandenen Platz orientiert, gegebenenfalls aber auch strikt vorgegeben werden kann und zudem noch etliche optionale Feineinstellungen vorgesehen sind.
Dokumente
Artikel als PDF herunterladen
Fußnoten


Das könnte Sie auch interessieren