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.

    Peter329 schrieb:

    Aber warum ist es denn wichtig bei der BRUSH ein Using zu verwenden ?
    Nur dann, wenn Du Deinen Brush baust.
    Wenn Du einen Default-Brush verwendest z.B. Brushes.Green, dann musst darfst Du kein Using verwenden.
    Mach Dir ein kleines Testprogramm und probier das mal aus:

    VB.NET-Quellcode

    1. Using br = Brushes.Green
    2. End Using

    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!
    Ich habe mir die Klasse mal etwas getuned.
    1. Mehr Kapsellung, um vorallem globale Variablen zu vermeiden.
    2. Die Benamungen so gestalltet, daß sie möglichst selbsterklärend und damit Kommentare überflüssig sind.
    3. Es werden jetzt zwei Listen an die Klasse übergeben, die eine enthällt die Values, die andere die entsprechenden Colors.
    4. Die Variable rect in pieSizeRectangle umbenannt und so gestalltet, das keine Beulen am Rand entstehen.

    VB.NET-Quellcode

    1. Imports System.Drawing.Drawing2D
    2. Public Class PieChart
    3. Public Sub Paint(ByVal size As Size,
    4. ByVal values As List(Of Double),
    5. ByVal colors As List(Of Color),
    6. ByVal borderColor As Color,
    7. ByVal borderWidth As Integer,
    8. ByVal rotationAngle As Single,
    9. ByVal e As PaintEventArgs)
    10. Dim pieSizeRectangle As New Rectangle(CInt(borderWidth / 2) + 1,
    11. CInt(borderWidth / 2) + 1,
    12. size.Width - borderWidth - 1,
    13. size.Height - borderWidth - 1)
    14. With e.Graphics
    15. .SmoothingMode = SmoothingMode.AntiAlias
    16. .CompositingQuality = CompositingQuality.HighQuality
    17. .PixelOffsetMode = PixelOffsetMode.HighQuality
    18. .InterpolationMode = InterpolationMode.HighQualityBilinear
    19. End With
    20. Using borderPen As New Pen(borderColor, borderWidth)
    21. Using solidBrush As SolidBrush = New SolidBrush(Nothing)
    22. Dim valuesSum As Double = values.Sum
    23. Dim usedValuesSum, startAngle, sweepAngle As Single
    24. For i As Integer = 0 To values.Count - 1
    25. startAngle = rotationAngle +
    26. CSng(360 / valuesSum * values(i)) +
    27. CSng(360 / valuesSum * usedValuesSum)
    28. sweepAngle = -CSng(360 / valuesSum * values(i))
    29. e.Graphics.DrawPie(borderPen,
    30. pieSizeRectangle,
    31. startAngle,
    32. sweepAngle)
    33. solidBrush.Color = colors(i)
    34. e.Graphics.FillPie(solidBrush,
    35. pieSizeRectangle,
    36. startAngle,
    37. sweepAngle)
    38. usedValuesSum = CSng(usedValuesSum + values(i))
    39. Next
    40. End Using
    41. End Using
    42. End Sub
    43. End Class


    VB.NET-Quellcode

    1. Public Class Form1
    2. Private Values As New List(Of Double)
    3. Private Colors As New List(Of Color)
    4. Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    5. Dim driveInfoC As New IO.DriveInfo("C:\")
    6. Values.AddRange({driveInfoC.TotalSize - driveInfoC.AvailableFreeSpace,
    7. driveInfoC.AvailableFreeSpace})
    8. Colors.AddRange({Color.Blue,
    9. Color.Magenta})
    10. End Sub
    11. Private Sub PictureBoxPieChart_Paint(sender As Object, e As PaintEventArgs) Handles PictureBoxPieChart.Paint
    12. Dim pieChart As New PieChart
    13. pieChart.Paint(DirectCast(sender, PictureBox).Size, Values, Colors, Color.Black, 1, 180, e)
    14. End Sub
    15. End Class
    Warum nicht anstatt der Size(Argmument) ein Rectangle nehmen? So lässt sich die Postion dann auch bestimmen, wenn man das Graphisics Object eines Forms nutzt. So ist die Klasse nicht universell einsetzbar. So wie du es gemacht hast, da würde ich dann ein Control machen, da kannst du dir dann auch das erste und letzte Argument sparen, da im Designer Position und Grösse festgelegt wird und das Control ein eigenes Paint-Event hat.

    Ach wo hab ich heute meinem kopf ?(
    Paar Properties, beim setzen dann invalidaieren.
    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

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „NoIde“ ()

    ich habs noch weiter gepimpt, weil eine Klasse mit nur einer Methode, und die mit so viele Parameter - unschick.

    Ich hab jetzt ein richtiges Objekt von gemacht, also ein Dingens mit einem eigenen Zustand - und den malt es halt.
    Und das Malen auch ganz autonom - "Behavior" nennt man diesen Pattern:
    Die Klasse:

    VB.NET-Quellcode

    1. Imports System.Drawing
    2. Imports System.Windows.Forms
    3. Imports System.Drawing.Drawing2D
    4. Public Class PieChart
    5. Public WithEvents Control As Control
    6. Public ReadOnly Values As New List(Of Single)
    7. Public ReadOnly Colors As New List(Of Color)
    8. Public BorderColor As Color = Color.Black, BorderWidth As Integer = 1, RotationAngle As Single = 180.0F
    9. Private _Pen As New Pen(BorderColor, BorderWidth)
    10. Private _Brush As New SolidBrush(Nothing)
    11. Private Sub Control_Paint(sender As Object, e As PaintEventArgs) Handles Control.Paint
    12. Dim size = Control.Size
    13. Dim bw = BorderWidth
    14. Dim rct As New Rectangle(bw \ 2 + 1, bw \ 2 + 1, size.Width - bw - 1, size.Height - bw - 1)
    15. With e.Graphics
    16. .SmoothingMode = SmoothingMode.AntiAlias
    17. .CompositingQuality = CompositingQuality.HighQuality
    18. .PixelOffsetMode = PixelOffsetMode.HighQuality
    19. .InterpolationMode = InterpolationMode.HighQualityBilinear
    20. End With
    21. Dim valuesSum = Values.Sum
    22. Dim usedValuesSum, startAngle, sweepAngle As Single
    23. For i = 0 To Values.Count - 1
    24. startAngle = RotationAngle + 360.0F / valuesSum * Values(i) + 360.0F / valuesSum * usedValuesSum
    25. sweepAngle = -360.0F / valuesSum * Values(i)
    26. _Pen.Width = BorderWidth
    27. e.Graphics.DrawPie(_Pen, rct, startAngle, sweepAngle)
    28. _Brush.Color = Colors(i)
    29. e.Graphics.FillPie(_Brush, rct, startAngle, sweepAngle)
    30. usedValuesSum = usedValuesSum + Values(i)
    31. Next
    32. End Sub
    33. End Class
    Benutzung:

    VB.NET-Quellcode

    1. Public Class frmPieChart_OwnerD
    2. Private _PieChart As PieChart
    3. Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    4. Dim driveInfoC As New IO.DriveInfo("C:\")
    5. _PieChart = New PieChart With {.Control = PictureBoxPieChart}
    6. _PieChart.Values.AddRange({driveInfoC.TotalSize - driveInfoC.AvailableFreeSpace, driveInfoC.AvailableFreeSpace})
    7. _PieChart.Colors.AddRange({Color.Blue, Color.Magenta})
    8. End Sub
    9. End Class
    Beachte auch, dass ich keinerlei Typumwandlung benötige - die Typen passen alle zusammen (naja, fast, aber die Ausnahme musste mitte Lupe suchen)

    NoIde schrieb:

    So lässt sich die Postion dann auch bestimmen, wenn man das Graphisics Object eines Forms nutzt.
    Ich habe keine Ahnung was ich mir dabei gedacht habe.

    NoIde schrieb:

    da würde ich dann ein Control machen
    Das hatte ich eh vor, kann dann ja jeder so machen, wie es beliebt.

    ErfinderDesRades schrieb:

    weil eine Klasse mit nur einer Methode, und die mit so viele Parameter - unschick.
    Sah wirklich etwas einsam aus.
    Parameter sparen kann wirklich nicht schaden.

    ErfinderDesRades schrieb:

    Ich hab jetzt ein richtiges Objekt von gemacht,
    Das ist natürlich OOP, konsequent umgesetzt, da werde ich wohl mal gucken was ich so rumfliegen habe und meinen Kram eventuell weiterentwickeln.

    Schöhne code Evolution !
    Es ist jetzt nicht Perfekt aber ich mag es :P

    C#
    Spoiler anzeigen

    C#-Quellcode

    1. using System;
    2. using System.Collections.Generic;
    3. using System.ComponentModel;
    4. using System.Drawing;
    5. using System.Drawing.Drawing2D;
    6. using System.Linq;
    7. using System.Threading;
    8. using System.Windows.Forms;
    9. namespace PaperWordLib.UI {
    10. public class MultiCircleBar : Control {
    11. #region private
    12. private GraphicsPath _pathOuterPi;
    13. private SolidBrush _brushOuterPi = new SolidBrush(Color.FromArgb(64, 64, 64));
    14. private SolidBrush _brushValuePi = new SolidBrush(Color.DodgerBlue);
    15. //later
    16. private Point _textLocation;
    17. private string _percentText;
    18. private GraphicsPath[] _grapicPathOfPies;
    19. #endregion
    20. #region Properties
    21. #region Settings
    22. private Color _foreColor = Color.White;
    23. private Color _barValueColor = Color.DodgerBlue;
    24. #endregion
    25. private double _minValue = 0;
    26. public double MinValue {
    27. get => _minValue;
    28. set {
    29. if (_minValue != value) {
    30. if (_maxValue < _minValue) {
    31. throw new Exception("MinValue must be smaller then MaxValue");
    32. }
    33. _maxValue = value;
    34. Invalidate();
    35. }
    36. }
    37. }
    38. private double _maxValue = 100;
    39. public double MaxValue {
    40. get => _maxValue;
    41. set {
    42. if (_maxValue != value) {
    43. if (_maxValue < MinValue) {
    44. throw new Exception("MaxValue must be bigger then MinValue");
    45. }
    46. _maxValue = value;
    47. Invalidate();
    48. }
    49. }
    50. }
    51. private bool _autoMaxValue;
    52. public bool AutoMaxValue {
    53. get => _autoMaxValue;
    54. set {
    55. if (_autoMaxValue != value) {
    56. _autoMaxValue = value;
    57. _minValue = 0;
    58. _maxValue = Items.Sum(item => item.Value);
    59. UpdatePiesPathes();
    60. Invalidate();
    61. }
    62. }
    63. }
    64. private List<MultiCircleBarItem> _items;
    65. [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
    66. public List<MultiCircleBarItem> Items {
    67. get => _items;
    68. set {
    69. if (_items != value) {
    70. _items = value;
    71. if (_autoMaxValue) {
    72. _maxValue = _items.Sum(item => item.Value);
    73. }
    74. _grapicPathOfPies = new GraphicsPath[_items.Count];
    75. UpdatePiesPathes();
    76. Invalidate();
    77. }
    78. }
    79. }
    80. private double _openPiValueDeg = 0;
    81. public double OpenPiValueDeg {
    82. get => _openPiValueDeg;
    83. set {
    84. if (_openPiValueDeg != value) {
    85. if (value > 360) {
    86. throw new Exception("Value must be smaler then 360");
    87. } else if (value < 0) {
    88. throw new Exception("Value must be bigger then 0");
    89. }
    90. _openPiValueDeg = value;
    91. Invalidate();
    92. }
    93. }
    94. }
    95. private Direction _OpenPiDirection = Direction.Top;
    96. public Direction OpenPiDirection {
    97. get => _OpenPiDirection;
    98. set {
    99. if (_OpenPiDirection != value) {
    100. _OpenPiDirection = value;
    101. UpdatePiesPathes();
    102. Invalidate();
    103. }
    104. }
    105. }
    106. private Color _barBackColor = Color.FromArgb(36, 36, 36);
    107. public Color BarBackColor {
    108. get => _barBackColor;
    109. set {
    110. if (_barBackColor != value) {
    111. _barBackColor = value;
    112. Interlocked.Exchange(ref _brushOuterPi, new SolidBrush(value));
    113. Invalidate();
    114. }
    115. }
    116. }
    117. #endregion
    118. public MultiCircleBar() {
    119. ControlStyles styles = ControlStyles.OptimizedDoubleBuffer
    120. | ControlStyles.SupportsTransparentBackColor
    121. | ControlStyles.UserPaint
    122. | ControlStyles.ResizeRedraw;
    123. SetStyle(styles, true);
    124. _items = new List<MultiCircleBarItem>();
    125. _autoMaxValue = true;
    126. }
    127. protected override void OnSizeChanged(EventArgs e) {
    128. UpdatePiesPathes();
    129. Invalidate();
    130. }
    131. protected override void OnPaint(PaintEventArgs e) {
    132. Graphics graphics = e.Graphics;
    133. graphics.SmoothingMode = SmoothingMode.AntiAlias;
    134. graphics.CompositingQuality = CompositingQuality.HighQuality;
    135. graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
    136. graphics.InterpolationMode = InterpolationMode.HighQualityBilinear;
    137. if (_pathOuterPi != null) {
    138. graphics.FillPath(_brushOuterPi, _pathOuterPi);
    139. }
    140. if (_items.Count != 0) {
    141. for (int i = 0, n = _grapicPathOfPies.Length; i < n; i += 1) {
    142. using (SolidBrush barBrush = new SolidBrush(_items[i].BarColor)) {
    143. graphics.FillPath(barBrush, _grapicPathOfPies[i]);
    144. }
    145. }
    146. }
    147. }
    148. private void UpdatePiesPathes() {
    149. if (ClientRectangle != Rectangle.Empty) {
    150. Rectangle rect = ClientRectangle;
    151. #region Settings
    152. double startValue = (int)OpenPiDirection - (OpenPiValueDeg / 2);
    153. double movedValue = 0;
    154. double deltaValue = MaxValue - MinValue;
    155. _items.Select(item => deltaValue / item.Value);
    156. double[] pieValues = (from item in
    157. (from element in _items
    158. select element.Value / MaxValue)
    159. orderby item
    160. select item).ToArray();
    161. double real360 = 360 - OpenPiValueDeg;
    162. double endValue = -(real360) + movedValue;
    163. #endregion
    164. if (_grapicPathOfPies == null || _grapicPathOfPies.Length != _items.Count) {
    165. _grapicPathOfPies = new GraphicsPath[_items.Count];
    166. if (_autoMaxValue) {
    167. _maxValue = _items.Sum(item => item.Value);
    168. }
    169. }
    170. for (int i = 0, n = pieValues.Length; i < n; i += 1) {
    171. double pixy = (pieValues[i] * real360);
    172. GraphicsPath path = new GraphicsPath();
    173. path.StartFigure();
    174. path.AddPie(rect, (float)(startValue + movedValue), (float)pixy);
    175. path.CloseFigure();
    176. Interlocked.Exchange(ref _grapicPathOfPies[i], path)?.Dispose();
    177. movedValue += pixy;
    178. }
    179. #region OuterPi
    180. GraphicsPath outerPiPath = new GraphicsPath();
    181. outerPiPath.StartFigure();
    182. outerPiPath.AddPie(rect, (int)startValue, (int)endValue);
    183. outerPiPath.CloseFigure();
    184. Interlocked.Exchange(ref _pathOuterPi, outerPiPath)?.Dispose();
    185. #endregion
    186. #region InnaPi
    187. #endregion
    188. }
    189. }
    190. [Serializable]
    191. public class MultiCircleBarItem {
    192. public event Action<double?> ValueChanged;
    193. public event Action<Color> BarColorChanged;
    194. private double _value;
    195. public double Value {
    196. get => _value;
    197. set {
    198. if (Value != value) {
    199. _value = value;
    200. ValueChanged?.Invoke(_value);
    201. }
    202. }
    203. }
    204. private Color _barColor;
    205. public Color BarColor {
    206. get => _barColor;
    207. set {
    208. if (BarColor == value)
    209. return;
    210. _barColor = value;
    211. BarColorChanged?.Invoke(value);
    212. }
    213. }
    214. public MultiCircleBarItem() {
    215. _value = 0;
    216. _barColor = Color.Firebrick;
    217. }
    218. public MultiCircleBarItem(double value, Color barcolor) {
    219. _value = value;
    220. _barColor = barcolor;
    221. }
    222. }
    223. public enum Direction {
    224. Top = -90,
    225. Right = 0,
    226. Bottom = 90,
    227. Left = 180,
    228. None = -1
    229. }
    230. }
    231. }

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

    In VS2017 auch nicht.
    Zeile#89 geht (bei mir) auch nicht, weil der <>-Operand nicht für den Typ definiert ist
    Z#119 wird angemeckert wegen Rekursion (Property-Setter)
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.