Tortendiagramm (pie chart) malen

  • VB.NET
  • .NET (FX) 4.5–4.8

Es gibt 30 Antworten in diesem Thema. Der letzte Beitrag () ist von Facebamm.

    Tortendiagramm (pie chart) malen

    Hi,

    ihr kennt sicher die Anzeige von "Disk Space" in Windows. Da wird das Verhältnis von belegtem zu unbelegtem Space mit einer "pie chart" dargestellt. (s. Anhang).

    Das würde ich auch gern machen. Dazu habe ich mir die Grafik Klassen in .NET angeschaut. Eine ausgefüllte Ellipse in eine Picture Box zu zeichen ist kein Problem:

    VB.NET-Quellcode

    1. Private Sub PictureBox1_Paint(ByVal sender As System.Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles PictureBox1.Paint
    2. Dim g As Graphics = e.Graphics
    3. Dim myPen As Pen = Pens.Black
    4. Dim myRec As New Rectangle(10, 10, 200, 100)
    5. Dim myBrush As System.Drawing.SolidBrush = New System.Drawing.SolidBrush(System.Drawing.Color.Red)
    6. g.DrawEllipse(myPen, myRec)
    7. g.FillEllipse(myBrush, myRec)
    8. End Sub


    Aber wie kann ich einen SEKTOR einer Ellipse malen, um den dann (mit anderer Farbe) auf die BasisEllipse zu überlagern ?

    Inzwischen habe ich Zweifel, ob das überhaupt mit diesen Klassen geht.

    Es wäre nett, wenn ihr mir sagt, ob und wie das möglich sein könnte ... oder wie man das (mit vernünftigem Aufwand) anderweitig löst.

    LG
    Peter
    Bilder
    • s 2018-09-26 11-50-340.jpg

      13,58 kB, 335×231, 202 mal angesehen

    Peter329 schrieb:

    VB.NET-Quellcode

    1. Dim myBrush As System.Drawing.SolidBrush = New System.Drawing.SolidBrush(System.Drawing.Color.Red)
    Da machst Du erst mal ein Using draus.
    Dann gibt es noch die Methoden
    Graphics.DrawPie() docs.microsoft.com/de-de/dotne…tem_Single_System_Single_
    und
    Graphics.FillPie() msdn.microsoft.com/en-us/library/aa327625(v=vs.71).aspx
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „Facebamm“ ()

    Auffe schnelle:

    VB.NET-Quellcode

    1. Public Class Form1
    2. Dim x As New MyPie
    3. Private Sub Form1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles Me.Paint
    4. x.Paint({1.1, 11.1, 22.1}, e.Graphics)
    5. End Sub
    6. End Class
    7. Public Class MyPie
    8. Public Sub Paint(ByVal values() As Double, ByVal g As Graphics)
    9. Dim sum As Double = values.ToList().Sum
    10. Dim usedFromSum As Single = 0
    11. Dim rotationAngle As Single = 0
    12. For i = values.Length - 1 To 0 Step -1
    13. g.DrawPie(Pens.Black, New Rectangle(20, 20, 100, 100), rotationAngle + CSng(360 / sum * values(i)) + CSng(360 / sum * usedFromSum), -CSng(360 / sum * values(i)))
    14. usedFromSum = CSng(usedFromSum + values(i))
    15. Next
    16. End Sub
    17. End Class
    Cloud Computer? Nein Danke! Das ist nur ein weiterer Schritt zur totalen Überwachung.
    „Wer die Freiheit aufgibt, um Sicherheit zu gewinnen, wird am Ende beides verlieren.“
    Benjamin Franklin
    Also, ihr seid schlicht und ergreifend KLASSE ! Ganz herzlichen Dank erst mal für eure tollen Ratschläge.

    Ich habe das versucht umzusetzen. Hier ist meine Klasse zum Zeichnen der Pie Chart:

    VB.NET-Quellcode

    1. Public Class myPie
    2. Public Sub Paint(ByVal values() As Double, ByVal g As Graphics)
    3. 'Define grafic objects
    4. Dim myPen As Pen = Pens.Black 'Define outline
    5. Dim myRect As New Rectangle(10, 10, 200, 100) 'Define rectangle
    6. Using myBrush As SolidBrush = New SolidBrush(Color.Red) 'Define brush (dummy color red)
    7. Dim colors() As Color = {Color.Magenta, Color.Blue} 'List of pie chart colors
    8. 'Paint pie chart
    9. Dim sum As Double = values.ToList().Sum 'Initialize work fields
    10. Dim usedFromSum As Single = 0
    11. Dim rotationAngle As Single = 180 'Set start position
    12. For i = values.Length - 1 To 0 Step -1 'Process all values
    13. 'Draw silhouette
    14. g.DrawPie(myPen,
    15. myRect,
    16. rotationAngle + CSng(360 / sum * values(i)) + CSng(360 / sum * usedFromSum),
    17. -CSng(360 / sum * values(i)))
    18. 'Fill pie
    19. Dim myColor As Color = colors(i Mod 2) 'Determine color
    20. myBrush.Color = myColor
    21. g.FillPie(myBrush, myRect,
    22. rotationAngle + CSng(360 / sum * values(i)) + CSng(360 / sum * usedFromSum),
    23. -CSng(360 / sum * values(i)))
    24. 'Accumulate values
    25. usedFromSum = CSng(usedFromSum + values(i))
    26. Next
    27. End Using
    28. End Sub


    Und so wird das aufgerufen:

    VB.NET-Quellcode

    1. Dim x As New myPie
    2. Private Sub Form1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles Me.Paint
    3. x.Paint({68.9, 145}, e.Graphics)
    4. End Sub


    Das Ergebnis kann sich durchaus sehen lassen. (s. Anhang)

    Es gibt aber noch zwei kleine Wermutstropfen:

    1. Die "Borderline" ist ein bissl verpixelt ... das ist zwar nicht weltbewegend ... aber schön ist halt anders. :)

    2. Die Werte 68.5 und 145 habe ich zum Testen fest eingetragen. Aber natürlich müssen die variabel sein, denn die werden ja beim Aufruf erst errechnet. Wie kriege ich das denn hin ?

    LG
    Peter

    P.S.: Den Hinweis von @Facebamm verstehe ich nicht. Wieso sollte denn die Length = 10000000 sein ? Ich habe doch nur zwei Werte die ich eintrage ! Und selbst bei "größeren" Pie Charts wird man doch i.a. nur eine handvoll Werte haben. Hab ich da irgend etwas falsch verstanden ?

    nochmals LG
    d.O.
    Bilder
    • s 2018-09-26 14-32-065.jpg

      7,05 kB, 246×165, 1.750 mal angesehen
    wenn du die unschönen ecken weg haben willst, dann kannst du

    VB.NET-Quellcode

    1. g.SmoothingMode = SmoothingMode.AntiAlias
    2. g.CompositingQuality = CompositingQuality.HighQuality
    3. g.PixelOffsetMode = PixelOffsetMode.HighQuality
    4. g.InterpolationMode = InterpolationMode.HighQualityBilinear


    setzen :D


    .. ne ich hab falsch gelesen xD
    Also ich bin hellauf begeistert ! Die Grafik sieht jetzt besser aus als die von Microsoft ! hehehe ... Herzlichen Dank ! :)

    Jetzt bleibt nur noch das letzte Problem zu lösen:

    Die Größe der Tortenscheiben werden ERRECHNET ... d.h. ich muss erst diese Werte ermitteln und DANACH kann ich die Pie Chart malen.

    Zur Zeit wird die Pie Chart über das Event Me.Paint gezeichnet. Das ist zu früh ! Ich würde das gern etwa in das Event Button1.Click verlagern. So habe ich das versucht:

    VB.NET-Quellcode

    1. Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    2. Dim g As Graphics
    3. x.Paint({68.9, 148}, g) 'Passing pie sizes as parameters !
    4. End Sub


    Aber natürlich klappt das nicht, weil g noch nicht instanziert ist. Der Parameter "new" ist aber nicht zulässig ...

    Wie mache ich das denn also ?

    LG
    Peter
    Mach dir eine Instanze von einem Bitmap mit der größe des PI
    jetzt gehts du ihn und glaust dir Graphic vom Bitmap

    also so

    VB.NET-Quellcode

    1. Dim img as new Bitmap(300, 300);
    2. ...
    3. Dim g as Graphics = Graphics.FromImage(img );
    Alles abfangen, sodass es zu keinem Fehler beim ersten zeichnen im Paint Event kommt,
    und anschließend kannst du einfach via myPie1.Invalidate() das Paint Event Manuell neu aufrufen.
    So kannst du später das ganze neu zeichnen. (Auch wichtig solltest du Werte ändern wollen)
    Mfg: Gather
    Private Nachrichten bezüglich VB-Fragen werden Ignoriert!


    Peter329 schrieb:

    VB.NET-Quellcode

    1. Dim g As Graphics
    What :?:
    Entweder e.Graphics aus dem Paint-Event oder Graphics.FromImage(img) für eine Bitmap.
    OK. @Facebamm war schneller.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Ok, jetzt hab ich's geschnallt ! :)

    Ich verwende ein Flag um die Paint Routine während des Load zu deaktivieren ... dann berechne ich in aller Ruhe meine Werte ... und dann lösche ich mein Flag und löse das Paint Event mit .Invalidate() aus. Und schon schnackelt die Sache. Auch ohne die Grafik Methode zu "klauen". :)

    Dank eurer Hilfe ist das jetzt eine sehr hübsche und überschaubare Routine geworden. Ich bin euch wirklich sehr dankbar, denn ohne euch hätte ich das so nicht hinbekommen.

    Have a nice day ! Problem gelöst !
    LG
    Peter
    Aber gern doch ...

    Diese Klassenvariable verwende ich:

    VB.NET-Quellcode

    1. Dim x As New DrawPieChart
    2. Dim blnPieDisabled As Boolean = True
    3. Dim intUsed As Double
    4. Dim intUnused As Double


    Das ist der Code am Ende der LoadForm Prozedur also nach dem Berechnen der Space Daten zum Starten der Paint Methode:

    VB.NET-Quellcode

    1. intUsed = TotalUsedSpace 'Accept total used space and total space
    2. intUnused = TotalSpace - TotalUsedSpace 'Compute unused space
    3. blnPieDisabled = False 'Enable drawing of pie chart
    4. picPieChart.Invalidate() 'Invoke paint method for pie chart


    Und das ist die PaintMethode der PictureBox:

    VB.NET-Quellcode

    1. Private Sub picPieChart_Paint(sender As Object, e As PaintEventArgs) Handles picPieChart.Paint
    2. If blnPieDisabled Then Exit Sub 'Disable routine for manual invocation
    3. Dim mySize As Size = picPieChart.Size 'Get size of PictureBox hosting pie chart
    4. Dim myRect As New Rectangle(0, 0, mySize.Width, mySize.Height) 'Define rectangle to fit PictureBox
    5. x.Paint(myRect, {intUnused, intUsed}, e.Graphics) 'Invoke paint method to draw pie chart
    6. End Sub


    Und das ist die Klasse zum Zeichnen der PieChart:

    VB.NET-Quellcode

    1. Imports System.Drawing.Drawing2D
    2. Public Class DrawPieChart
    3. Public Sub Paint(ByVal rect As Rectangle, ByVal values() As Double, ByVal g As Graphics) 'Thanks to @Facebamm
    4. 'Enhance quality of display
    5. g.SmoothingMode = SmoothingMode.AntiAlias
    6. g.CompositingQuality = CompositingQuality.HighQuality
    7. g.PixelOffsetMode = PixelOffsetMode.HighQuality
    8. g.InterpolationMode = InterpolationMode.HighQualityBilinear
    9. 'Define grafic objects
    10. Dim myPen As Pen = Pens.Black 'Define outline
    11. Using myBrush As SolidBrush = New SolidBrush(Color.Red) 'Define brush (dummy color red)
    12. Dim colors() As Color = {Color.Magenta, Color.Blue} 'List of pie chart colors
    13. 'Paint pie chart
    14. Dim sum As Double = values.ToList().Sum 'Initialize work fields
    15. Dim usedFromSum As Single = 0
    16. Dim rotationAngle As Single = 180 'Set start position
    17. For i = values.Length - 1 To 0 Step -1 'Process all values
    18. Dim StartAngle As Single = rotationAngle + CSng(360 / sum * values(i)) + CSng(360 / sum * usedFromSum)
    19. Dim SweepAngle As Single = -CSng(360 / sum * values(i))
    20. 'Draw silhouet
    21. g.DrawPie(myPen,
    22. rect,
    23. StartAngle,
    24. SweepAngle)
    25. 'Fill pie
    26. Dim myColor As Color = colors(i Mod 2) 'Determine color
    27. myBrush.Color = myColor
    28. g.FillPie(myBrush,
    29. rect,
    30. StartAngle,
    31. SweepAngle)
    32. 'Accumulate values
    33. usedFromSum = CSng(usedFromSum + values(i))
    34. Next
    35. End Using
    36. End Sub
    37. End Class


    Ein Sample Display hab ich im Anhang beigefügt.

    Ich hoffe, dass alles im Sinne der reinen Lehre stimmig ist. Vielleicht kann ja der eine oder andere was mit dem Coding anfangen ...

    LG
    Peter
    Bilder
    • s 2018-09-26 20-15-512.jpg

      14,31 kB, 449×231, 129 mal angesehen
    Also ein Pen implementiert IDisposeAble, den kannst du auch mit einer Using Anweisung nutzen. Hier passt die Anzahl der Farben mit den übergebenen Werten überein, was wenn mehr Tortenstückchen gezeigt werden sollen? Nimm nun mal 3 Werte(values()) übergeben, dann schaut das nicht mehr korrekt aus.
    Cloud Computer? Nein Danke! Das ist nur ein weiterer Schritt zur totalen Überwachung.
    „Wer die Freiheit aufgibt, um Sicherheit zu gewinnen, wird am Ende beides verlieren.“
    Benjamin Franklin
    Ich versuche deine Anmerkungen zu verstehen. Ich bin nicht ganz sicher, ob mir das gelungen ist:

    NoIde schrieb:

    Also ein Pen implementiert IDisposeAble, den kannst du auch mit einer Using Anweisung nutzen.


    Also wenn ich aus der Dim eine Using Anweisung mache, dann ist das zwar korrekte Syntax

    VB.NET-Quellcode

    1. Using myPen As Pen = Pens.Black


    Aber die Pie Chart wird nicht mehr angezeigt ! Die PictureBox enthält dann nur noch ein rotes X ... warum das so ist, weiß ich nicht.

    Was mich zu der Frage bewegt, welchen Vorteil die Using Anweisung bringt. Denn wenn die Form geschlossen wird, werden die Ressourcen doch ohnehin abgebaut.

    NoIde schrieb:

    Hier passt die Anzahl der Farben mit den übergebenen Werten überein, was wenn mehr Tortenstückchen gezeigt werden sollen? Nimm nun mal 3 Werte(values()) übergeben, dann schaut das nicht mehr korrekt aus.


    Na, ja wenn mehr Tortenstücke angezeigt werden sollen, dann muss man halt eine längere Liste von Farben bilden und die Sache mit mod 2 durch folgendes Coding ersetzen:

    VB.NET-Quellcode

    1. Dim myColor As Color = colors(i Mod colors.Length)


    Aber das ist doch eigentlich trivial. Oder habe ich deinen Einwand falsch verstanden ?

    LG
    Peter

    Peter329 schrieb:


    Using myPen As Pen = Pens.Black
    Aber die Pie Chart wird nicht mehr angezeigt ! Die PictureBox enthält dann nur noch ein rotes X ... warum das so ist, weiß ich nicht.


    So ist es richtig;

    VB.NET-Quellcode

    1. Using p As New Pen(Color.Black, 1)
    2. End Using


    Du hast ja jedesmal eine neue Instanz vom Pen, würdest du als Argument beim DrawArc den Statischen Pen nehmen(also Pens.Black) dann kommst du hier auch ohne using aus. Der Fehler Tritt auf weil durch die using-Anweisung der statische Pen am Ende des usings Disposed wird, ich habe mal ein wenig probiert, heraus kam, sobald man den statischen Pen disposen will, fliegt einem eine System.ArgumentException um die Ohren.

    Spoiler anzeigen

    VB.NET-Quellcode

    1. Private Sub Form1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles Me.Paint
    2. 'e.Graphics.DrawLine(Pens.Black, 0, 0, Width, Height)
    3. Pens.Black.Dispose()
    4. ' If Pens.Black Is Nothing Then
    5. 'Debug.WriteLine(Now)
    6. ' End If
    7. End Sub


    Peter329 schrieb:

    Was mich zu der Frage bewegt, welchen Vorteil die Using Anweisung bringt. Denn wenn die Form geschlossen wird, werden die Ressourcen doch ohnehin abgebaut.


    Könnte passieren das der Speicherbedarf(RAM-Last) anwächst.

    Peter329 schrieb:

    Aber das ist doch eigentlich trivial. Oder habe ich deinen Einwand falsch verstanden ?


    Möglicher weise vllt. ja. Da du die Farben hardcoded hast, wären bei 3 Tortenstückchen und 2 Farben(fixed), 2 der 3 Tortenstückchen von der selben Farbe. Kann man nun überlegen, ob man noch ein weiteres Argument für die Funktion nutzt(Color()).

    Ich lege immer viel Wert darauf das Kassen wieder verwendbar sind. Aber so das keine weitere Änderung nötig ist, alles sofort verwendet werden kann.
    Cloud Computer? Nein Danke! Das ist nur ein weiterer Schritt zur totalen Überwachung.
    „Wer die Freiheit aufgibt, um Sicherheit zu gewinnen, wird am Ende beides verlieren.“
    Benjamin Franklin

    NoIde schrieb:

    So ist es richtig;

    VB.NET-Quellcode

    1. Using p As New Pen(Color.Black, 1)
    2. End Using
    Nö.
    Peter329 verwendet Pens.Black und feddich.
    @Peter329 Vielleicht spardt Du Dir einfach die Variable und setzt Pens.Black direkt ein.
    Oder hast Du vor, den Rand wählbar zu färben?
    Aber auch dann kannst Du einen Default-Pen verwenden.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Das ist richtig .... die Klassen sollten möglichst ohne Veränderung wiederverwendbar sein. Ich hab jetzt einfach prophylaktisch 10 Farben definiert ... die halt nur dann verwendet werden, wenn es auch so viele Werte gibt.

    Jetzt hab ich nur noch ein Frage an RFG: beim PEN kann ich mit Dim arbeiten. Das hab ich verstanden. Aber warum ist es denn wichtig bei der BRUSH ein Using zu verwenden ? Ist das nicht das Gleiche in "grün" ?

    LG
    Peter

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Peter329“ ()