Pie Chart mit Blazor und SVG

Unglaubliche drei Jahre ist es her, das ich hier einen Blog Artikel über eine Kuchendiagramm mit Blazor geschrieben habe. Damals noch der Meinung, das nur eine C# Klasse auf Basis ComponentBase die höheren Blazor Weihen darstellt. Blödsinn.

Deswegen für euch eine neue, schönere Chart Component mit nur einer Razor Blazor Datei. 42 Codezeilen reichen! Dabei beherrscht meine Version 2 mit Diagramm Hoover und Segment Click Events wesentlich mehr Features.

 ChartBlazor

Zunächst wird die index.razor genutzt um die Chart Component mit Daten eines int, Double Dictionarys zu füttern und auf die Event zu reagieren.

   1:  <div class="row">
   2:      <div class="col-3">
   3:          <ChartPPEDV Daten="Daten" 
OnClick="Click" OnHoover="Hoover"></ChartPPEDV></div>
   4:      <div class="col-2">
   5:          <div class="card shadow">
   6:              @Ausgabe
   7:          </div>
   8:      </div>
   9:      <div class="col-auto">
  10:   </div>

Im Code Abschnitt der Datei sparsamer minimal C# Code

   1:    public Dictionary<int, Double> Daten { get; set; }
   2:      string Ausgabe;
   3:      void Click(int id)
   4:      {
   5:          Ausgabe = $"Clicked: {id}";
   6:      }
   7:      void Hoover(int id)
   8:      {
   9:          Ausgabe = $"Hoovered: {id}";
  10:      }
  11:      protected override void OnInitialized()
  12:      {
  13:          Daten = new Dictionary<int, Double>();
  14:          Daten.Add(1, 40);
  15:          Daten.Add(2, 100);
  16:          Daten.Add(3, 50);
  17:          Daten.Add(4, 30);
  18:          Daten.Add(5, 90);
  19:      }

Dann wird die Blazor Datei ChartPPEDV.Razor dem Visual Studio Projekt hinzugefügt.

Wir nehmen SVG um den Kuchen zu backen. Da es zwar Circle gibt, aber kein Arcsegment muss man sich diese selber aus Path schnitzen.  Dazu werden pro Element die beiden Events Click und MouseOver verdrahtet.

   1:  @using System.Globalization
   2:  <svg width="200px" height="auto"
   3:       viewBox="0 0 200 200">
   4:      <g style="cursor: pointer;">
   5:          @foreach (var item in Daten)
   6:          {
   7:              <path d="@ArcSegment(item.Key,item.Value)" fill="none" 
   8:                stroke="#446688" stroke-width="20"
   9:                @onclick="()=>OnClick.InvokeAsync(item.Key)"
@onmouseover="()=>OnHoover.InvokeAsync(item.Key)">
  10:              </path>
  11:          }
  12:      </g>
  13:  </svg>

Jetzt wird es ein wenig spannend bzw kompliziert. Der Bogen hat X Y Radius der im Kreisfall ident sein muss. Dazu einen Anfang und Endpunkt im Kartesischen Koordinatensystem. Zur Berechnung der Koordinaten nimmt man die Kreisfunktionen aus dem guten alten Mathe Unterricht – Sinus und Cosinus.
Die Daten werden in Winkelanteile umgerechnet, wobei die Winkelsummer der bisherigen Segmente den Startpunkt des nächsten Kreissegmentes definiert.

   1:  [Parameter]
   2:  public Dictionary<int, Double> Daten { get; set; }
   3:  [Parameter]
   4:  public EventCallback<int> OnClick { get; set; }
   5:  [Parameter]
   6:  public EventCallback<int> OnHoover { get; set; }
   7:  Double GradWert;
   8:  Double LastGrad = 0;
   9:  string ArcSegment(int segement, double Wert)
  10:  {
  11:     var winkel = Wert * (GradWert);
  12:     var ret = ArcSegment2(100, 100, 64, LastGrad,LastGrad+winkel-1 );
  13:     LastGrad += winkel;
  14:     return ret;
  15:  }
  16:  string ArcSegment2(double x, double y, double radius, double startWinkel, double endWinkel)
  17:  {
  18:    var startx = x + (radius * Math.Cos((endWinkel - 90) * Math.PI / 180.0));
  19:    var starty = y + (radius * Math.Sin((endWinkel - 90) * Math.PI / 180.0));
  20:    var endx = x + (radius * Math.Cos((startWinkel - 90) * Math.PI / 180.0));
  21:    var endy = y + (radius * Math.Sin((startWinkel - 90) * Math.PI / 180.0));
  22:    string d = $@"M {startx.ToString("0.00", CultureInfo.InvariantCulture)} 
{starty.ToString("
0.00", CultureInfo.InvariantCulture)}
A {radius.ToString("
0.00", CultureInfo.InvariantCulture)}
{radius.ToString("
0.00", CultureInfo.InvariantCulture)} 0
{(endWinkel - startWinkel > 180 ? 1 : 0)} 0
{endx.ToString("
0.00", CultureInfo.InvariantCulture)}
{endy.ToString("
0.00", CultureInfo.InvariantCulture)}";
  23:          // M 124.20249303011667 40.75272722624986 A 64 64 0 0 0 100 36
  24:    return d;
  25:  }
  26:  protected override void OnInitialized()
  27:   {
  28:    GradWert = 360/Daten.Sum(x => x.Value) ;
  29:  }

Damit man ganz sicher einen Punkt als Komma Seperator bekommt, habe ich hier die Culturinfo verwendet.

Kommentare sind geschlossen