/src/ProtonVPN.App/Views/Controls/PieChart.xaml.cs

https://github.com/ProtonVPN/win-app · C# · 194 lines · 141 code · 35 blank · 18 comment · 12 complexity · 4d8fb42da313d14f74f55d6b2e1ba484 MD5 · raw file

  1. /*
  2. * Copyright (c) 2020 Proton Technologies AG
  3. *
  4. * This file is part of ProtonVPN.
  5. *
  6. * ProtonVPN is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * ProtonVPN is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with ProtonVPN. If not, see <https://www.gnu.org/licenses/>.
  18. */
  19. using System;
  20. using System.Windows;
  21. using System.Windows.Controls;
  22. using System.Windows.Media;
  23. namespace ProtonVPN.Views.Controls
  24. {
  25. public partial class PieChart
  26. {
  27. private Image _pieChartImage;
  28. public static readonly DependencyProperty PercentageProperty =
  29. DependencyProperty.Register("Percentage", typeof(double), typeof(PieChart), new FrameworkPropertyMetadata(0.5, OnPiePropertyChanged, CoercePercentageCallback));
  30. public static readonly DependencyProperty SizeProperty =
  31. DependencyProperty.Register("Size", typeof(double), typeof(PieChart), new PropertyMetadata(100.0, OnPiePropertyChanged));
  32. public static readonly DependencyProperty InnerPieSliceFillProperty =
  33. DependencyProperty.Register("InnerPieSliceFill", typeof(Brush), typeof(PieChart), new PropertyMetadata(CreateBrush("#939496"), OnPiePropertyChanged));
  34. public static readonly DependencyProperty OuterPieSliceFillProperty =
  35. DependencyProperty.Register("OuterPieSliceFill", typeof(Brush), typeof(PieChart), new PropertyMetadata(CreateBrush("#D0D1D3"), OnPiePropertyChanged));
  36. public double Percentage
  37. {
  38. get => (double)GetValue(PercentageProperty);
  39. set => SetValue(PercentageProperty, value);
  40. }
  41. public double Size
  42. {
  43. get => (double)GetValue(SizeProperty);
  44. set => SetValue(SizeProperty, value);
  45. }
  46. public Brush InnerPieSliceFill
  47. {
  48. get => (Brush)GetValue(InnerPieSliceFillProperty);
  49. set => SetValue(InnerPieSliceFillProperty, value);
  50. }
  51. public Brush OuterPieSliceFill
  52. {
  53. get => (Brush)GetValue(OuterPieSliceFillProperty);
  54. set => SetValue(OuterPieSliceFillProperty, value);
  55. }
  56. public PieChart()
  57. {
  58. InitializeComponent();
  59. }
  60. public override void OnApplyTemplate()
  61. {
  62. _pieChartImage = (Image)Template.FindName("PART_PieChart", this);
  63. CreatePieChart();
  64. }
  65. private static object CoercePercentageCallback(DependencyObject dep, object baseValue)
  66. {
  67. var value = (double)baseValue;
  68. if (value < 0.0)
  69. {
  70. value = 0.0;
  71. }
  72. else if (value > 1.0)
  73. {
  74. value = 1.0;
  75. }
  76. return value;
  77. }
  78. private static void OnPiePropertyChanged(DependencyObject dep, DependencyPropertyChangedEventArgs ev)
  79. {
  80. var chart = (PieChart)dep;
  81. if (chart.IsInitialized)
  82. {
  83. chart.CreatePieChart();
  84. }
  85. }
  86. private void CreatePieChart()
  87. {
  88. if (_pieChartImage != null)
  89. {
  90. if (!double.IsNaN(Size) && !double.IsNaN(Percentage))
  91. {
  92. _pieChartImage.Width = _pieChartImage.Height = Width = Height = Size;
  93. var di = new DrawingImage();
  94. _pieChartImage.Source = di;
  95. var dg = new DrawingGroup();
  96. di.Drawing = dg;
  97. dg.Children.Add(CreateEllipseGeometry(BorderBrush, Size / 2 + BorderThickness.Left));
  98. if (Percentage > 0.0 && Percentage < 1.0)
  99. {
  100. var angle = 360 * Percentage;
  101. var radians = Math.PI / 180 * angle;
  102. var endPointX = Math.Sin(radians) * Height / 2 + Height / 2;
  103. var endPointY = Width / 2 - Math.Cos(radians) * Width / 2;
  104. var endPoint = new Point(endPointX, endPointY);
  105. dg.Children.Add(CreatePathGeometry(InnerPieSliceFill, new Point(Width / 2, 0), endPoint, Percentage > 0.5));
  106. dg.Children.Add(CreatePathGeometry(OuterPieSliceFill, endPoint, new Point(Width / 2, 0), Percentage <= 0.5));
  107. }
  108. else
  109. {
  110. dg.Children.Add(CreateEllipseGeometry(Math.Abs(Percentage - 0.0) < 0.0001 ? OuterPieSliceFill : InnerPieSliceFill, Size / 2));
  111. }
  112. }
  113. else
  114. {
  115. _pieChartImage.Source = null;
  116. }
  117. }
  118. }
  119. private GeometryDrawing CreatePathGeometry(Brush brush, Point startPoint, Point arcPoint, bool isLargeArc)
  120. {
  121. var midPoint = new Point(Width / 2, Height / 2);
  122. var drawing = new GeometryDrawing { Brush = brush };
  123. var pathGeometry = new PathGeometry();
  124. var pathFigure = new PathFigure { StartPoint = midPoint };
  125. var ls1 = new LineSegment(startPoint, false);
  126. var arc = new ArcSegment
  127. {
  128. SweepDirection = SweepDirection.Clockwise,
  129. Size = new Size(Width / 2, Height / 2),
  130. Point = arcPoint,
  131. IsLargeArc = isLargeArc
  132. };
  133. var ls2 = new LineSegment(midPoint, false);
  134. drawing.Geometry = pathGeometry;
  135. pathGeometry.Figures.Add(pathFigure);
  136. pathFigure.Segments.Add(ls1);
  137. pathFigure.Segments.Add(arc);
  138. pathFigure.Segments.Add(ls2);
  139. return drawing;
  140. }
  141. private GeometryDrawing CreateEllipseGeometry(Brush brush, double size)
  142. {
  143. var midPoint = new Point(Width / 2, Height / 2);
  144. var drawing = new GeometryDrawing { Brush = brush };
  145. var ellipse = new EllipseGeometry(midPoint, size, size);
  146. drawing.Geometry = ellipse;
  147. return drawing;
  148. }
  149. private static SolidColorBrush CreateBrush(string brush)
  150. {
  151. var color = ColorConverter.ConvertFromString(brush);
  152. if (color != null)
  153. {
  154. return new SolidColorBrush((Color)color);
  155. }
  156. return null;
  157. }
  158. }
  159. }