diff --git a/src/WPFDevelopers.Samples.Shared/ExampleViews/ChartPieExample.xaml b/src/WPFDevelopers.Samples.Shared/ExampleViews/ChartPieExample.xaml
new file mode 100644
index 00000000..0fc4bf16
--- /dev/null
+++ b/src/WPFDevelopers.Samples.Shared/ExampleViews/ChartPieExample.xaml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/WPFDevelopers.Samples.Shared/ExampleViews/ChartPieExample.xaml.cs b/src/WPFDevelopers.Samples.Shared/ExampleViews/ChartPieExample.xaml.cs
new file mode 100644
index 00000000..4a02bb14
--- /dev/null
+++ b/src/WPFDevelopers.Samples.Shared/ExampleViews/ChartPieExample.xaml.cs
@@ -0,0 +1,68 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.NetworkInformation;
+using System.Windows;
+using System.Windows.Controls;
+using static System.Windows.Forms.VisualStyles.VisualStyleElement.Rebar;
+
+namespace WPFDevelopers.Samples.ExampleViews
+{
+ ///
+ /// ChartPieExample.xaml 的交互逻辑
+ ///
+ public partial class ChartPieExample : UserControl
+ {
+ public IEnumerable> Datas
+ {
+ get { return (IEnumerable>)GetValue(DatasProperty); }
+ set { SetValue(DatasProperty, value); }
+ }
+
+ public static readonly DependencyProperty DatasProperty =
+ DependencyProperty.Register("Datas", typeof(IEnumerable>), typeof(ChartPieExample), new PropertyMetadata(null));
+
+ private Dictionary>> keyValues = new Dictionary>>();
+ private int _index = 0;
+ public ChartPieExample()
+ {
+ InitializeComponent();
+ var models1 = new[]
+ {
+ new KeyValuePair("Mon", 120),
+ new KeyValuePair("Tue", 530),
+ new KeyValuePair("Wed", 1060),
+ new KeyValuePair("Thu", 140),
+ new KeyValuePair("Fri", 8000.123456) ,
+ new KeyValuePair("Sat", 200) ,
+ new KeyValuePair("Sun", 300) ,
+ };
+ var models2 = new[]
+ {
+ new KeyValuePair("Bing", 120),
+ new KeyValuePair("Google", 170),
+ new KeyValuePair("Baidu", 30),
+ new KeyValuePair("Github", 200),
+ new KeyValuePair("Stack Overflow", 100) ,
+ new KeyValuePair("Runoob", 180) ,
+ new KeyValuePair("Open AI", 90) ,
+ new KeyValuePair("Open AI2", 93) ,
+ new KeyValuePair("Open AI3", 94) ,
+ new KeyValuePair("Open AI4", 95) ,
+ };
+ keyValues.Add("1", models1);
+ keyValues.Add("2", models2);
+ Datas = models1;
+ }
+
+ private void Button_Click(object sender, RoutedEventArgs e)
+ {
+ _index++;
+ if (_index >= keyValues.Count)
+ {
+ _index = 0;
+ }
+ Datas = keyValues.ToList()[_index].Value;
+ }
+ }
+}
diff --git a/src/WPFDevelopers.Samples.Shared/Helpers/MenuEnum.cs b/src/WPFDevelopers.Samples.Shared/Helpers/MenuEnum.cs
index e9b21987..dd0a98bf 100644
--- a/src/WPFDevelopers.Samples.Shared/Helpers/MenuEnum.cs
+++ b/src/WPFDevelopers.Samples.Shared/Helpers/MenuEnum.cs
@@ -82,6 +82,7 @@ public enum MenuEnum
WaterfallPanel,
ChartLine,
Drap,
+ ChartPie,
VirtualizingWrapPanel,
AcrylicBlur,
TaskbarInfo
diff --git a/src/WPFDevelopers.Samples.Shared/ViewModels/MainVM.cs b/src/WPFDevelopers.Samples.Shared/ViewModels/MainVM.cs
index d873cb5b..90cec06f 100644
--- a/src/WPFDevelopers.Samples.Shared/ViewModels/MainVM.cs
+++ b/src/WPFDevelopers.Samples.Shared/ViewModels/MainVM.cs
@@ -349,6 +349,9 @@ void MenuItemSelection(string _menuName)
case MenuEnum.Drap:
ControlPanel = new DrapViewExample();
break;
+ case MenuEnum.ChartPie:
+ ControlPanel = new ChartPieExample();
+ break;
case MenuEnum.VirtualizingWrapPanel:
ControlPanel = new VirtualizingWrapPanel();
new VirtualizingWrapPanelExample().MaskShowDialog();
diff --git a/src/WPFDevelopers.Samples.Shared/WPFDevelopers.Samples.Shared.projitems b/src/WPFDevelopers.Samples.Shared/WPFDevelopers.Samples.Shared.projitems
index 798e7ea1..bdf4b191 100644
--- a/src/WPFDevelopers.Samples.Shared/WPFDevelopers.Samples.Shared.projitems
+++ b/src/WPFDevelopers.Samples.Shared/WPFDevelopers.Samples.Shared.projitems
@@ -107,6 +107,9 @@
ChartLineExample.xaml
+
+ ChartPieExample.xaml
+
Code
ChatEmojiExample.xaml
@@ -584,6 +587,10 @@
MSBuild:Compile
Designer
+
+ MSBuild:Compile
+ Designer
+
MSBuild:Compile
Designer
diff --git a/src/WPFDevelopers.SamplesCode/WPFDevelopers.SamplesCode.csproj b/src/WPFDevelopers.SamplesCode/WPFDevelopers.SamplesCode.csproj
index 94a94a89..2b61d3f5 100644
--- a/src/WPFDevelopers.SamplesCode/WPFDevelopers.SamplesCode.csproj
+++ b/src/WPFDevelopers.SamplesCode/WPFDevelopers.SamplesCode.csproj
@@ -144,6 +144,7 @@
+
@@ -488,5 +489,8 @@
ExampleViews\ChartLineExample.xaml
+
+ ExampleViews\ChartPieExample.xaml
+
diff --git a/src/WPFDevelopers.Shared/Controls/Charts/ChartPie.cs b/src/WPFDevelopers.Shared/Controls/Charts/ChartPie.cs
new file mode 100644
index 00000000..43a0877c
--- /dev/null
+++ b/src/WPFDevelopers.Shared/Controls/Charts/ChartPie.cs
@@ -0,0 +1,250 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Controls.Primitives;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Effects;
+using System.Windows.Shapes;
+
+namespace WPFDevelopers.Controls
+{
+ public class ChartPie : Control
+ {
+ private double centerX, centerY, radius;
+ private Popup _popup;
+ private Border _border;
+ private StackPanel _stackPanel;
+ private TextBlock _textBlock;
+ private Ellipse _ellipse;
+ private bool isPopupOpen = false;
+ private KeyValuePair _lastItem;
+ private Dictionary pathGeometries = new Dictionary();
+
+ private Color[] vibrantColors;
+
+ public static readonly DependencyProperty DatasProperty =
+ DependencyProperty.Register("Datas", typeof(IEnumerable>),
+ typeof(ChartPie), new UIPropertyMetadata(DatasChanged));
+ public IEnumerable> Datas
+ {
+ get => (IEnumerable>)GetValue(DatasProperty);
+ set => SetValue(DatasProperty, value);
+ }
+
+ private static void DatasChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ var ctrl = d as ChartPie;
+ if (e.NewValue != null)
+ ctrl.InvalidateVisual();
+ }
+
+ public ChartPie()
+ {
+ vibrantColors = new Color[]
+ {
+ Color.FromArgb(255, 84, 112, 198),
+ Color.FromArgb(255, 145, 204, 117),
+ Color.FromArgb(255, 250, 200, 88),
+ Color.FromArgb(255, 238, 102, 102),
+ Color.FromArgb(255, 115, 192, 222),
+ Color.FromArgb(255, 59, 162, 114),
+ Color.FromArgb(255, 252, 132, 82),
+ Color.FromArgb(255, 154, 96, 180),
+ Color.FromArgb(255, 234, 124, 204),
+ };
+ }
+ protected override void OnMouseMove(MouseEventArgs e)
+ {
+ base.OnMouseMove(e);
+ if (Datas == null || Datas.Count() == 0 || isPopupOpen) return;
+ if (_popup == null)
+ {
+ _popup = new Popup
+ {
+ AllowsTransparency = true,
+ Placement = PlacementMode.MousePoint,
+ PlacementTarget = this,
+ StaysOpen = false,
+ };
+ _popup.MouseMove += (y, j) =>
+ {
+ var point = j.GetPosition(this);
+ if (isPopupOpen && _lastItem.Value != null)
+ {
+ if (!IsMouseOverGeometry(_lastItem.Key))
+ {
+ _popup.IsOpen = false;
+ isPopupOpen = false;
+ _lastItem = new KeyValuePair();
+ }
+ }
+ };
+ _popup.Closed += delegate
+ {
+ isPopupOpen = false;
+ };
+
+ _textBlock = new TextBlock()
+ {
+ HorizontalAlignment = HorizontalAlignment.Center,
+ VerticalAlignment = VerticalAlignment.Center,
+ Foreground = (Brush)Application.Current.TryFindResource("WD.WindowForegroundColorBrush"),
+ Padding = new Thickness(4, 0, 2, 0)
+ };
+ _ellipse = new Ellipse()
+ {
+ Width = 10,
+ Height = 10,
+ Stroke = Brushes.White,
+ };
+ _stackPanel = new StackPanel() { Orientation = Orientation.Horizontal };
+ _stackPanel.Children.Add(_ellipse);
+ _stackPanel.Children.Add(_textBlock);
+
+ _border = new Border
+ {
+ Child = _stackPanel,
+ Background = (Brush)Application.Current.TryFindResource("WD.ChartFillSolidColorBrush"),
+ Effect = Application.Current.TryFindResource("WD.PopupShadowDepth") as DropShadowEffect,
+ Margin = new Thickness(10),
+ CornerRadius = new CornerRadius(3),
+ Padding = new Thickness(6)
+ };
+ _popup.Child = _border;
+ }
+ int index = 0;
+ foreach (var pathGeometry in pathGeometries)
+ {
+ if (IsMouseOverGeometry(pathGeometry.Key))
+ {
+ isPopupOpen = true;
+ _ellipse.Fill = new SolidColorBrush()
+ {
+ Color = vibrantColors[index >= vibrantColors.Length ? index % vibrantColors.Length : index]
+ };
+ _textBlock.Text = pathGeometry.Value;
+ //var bounds = pathGeometry.Key.Bounds;
+ //var center = new Point(bounds.Left + bounds.Width / 2, bounds.Top + bounds.Height / 2);
+ //_popup.HorizontalOffset = center.X - (_border.ActualWidth / 2);
+ //_popup.VerticalOffset = center.Y - (_border.ActualHeight / 2);
+ _popup.IsOpen = true;
+ _lastItem = pathGeometry;
+ break;
+ }
+ index++;
+ }
+ }
+ private bool IsMouseOverGeometry(PathGeometry pathGeometry)
+ {
+ var mousePosition = Mouse.GetPosition(this);
+ return pathGeometry.FillContains(mousePosition);
+ }
+
+ protected override void OnRender(DrawingContext drawingContext)
+ {
+ base.OnRender(drawingContext);
+ if (Datas == null || Datas.Count() == 0)
+ return;
+ SnapsToDevicePixels = true;
+ UseLayoutRounding = true;
+ pathGeometries.Clear();
+ var drawingPen = CreatePen(2);
+ var boldDrawingPen = CreatePen(4);
+ var pieWidth = ActualWidth > ActualHeight ? ActualHeight : ActualWidth;
+ var pieHeight = ActualWidth > ActualHeight ? ActualHeight : ActualWidth;
+ centerX = pieWidth / 2;
+ centerY = pieHeight / 2;
+ radius = ActualWidth > ActualHeight ? ActualHeight / 2 : ActualWidth / 2;
+ var angle = 0d;
+ var prevAngle = 0d;
+ var sum = Datas.Select(ser => ser.Value).Sum();
+ var index = 0;
+ var isFirst = false;
+ foreach (var item in Datas)
+ {
+
+ var arcStartX = radius * Math.Cos(angle * Math.PI / 180) + centerX;
+ var arcStartY = radius * Math.Sin(angle * Math.PI / 180) + centerY;
+ angle = item.Value / sum * 360 + prevAngle;
+ var arcEndX = 0d;
+ var arcEndY = 0d;
+ if (Datas.Count() == 1 && angle == 360)
+ {
+ isFirst = true;
+ arcEndX = centerX + Math.Cos(359.99999 * Math.PI / 180) * radius;
+ arcEndY = (radius * Math.Sin(359.99999 * Math.PI / 180)) + centerY;
+ }
+ else
+ {
+ arcEndX = centerX + Math.Cos(angle * Math.PI / 180) * radius;
+ arcEndY = (radius * Math.Sin(angle * Math.PI / 180)) + centerY;
+ }
+ var startPoint = new Point(arcStartX, arcStartY);
+ var line1Segment = new LineSegment(startPoint, false);
+ bool isLargeArc = item.Value / sum > 0.5;
+ var arcSegment = new ArcSegment();
+ var size = new Size(radius, radius);
+ var endPoint = new Point(arcEndX, arcEndY);
+ arcSegment.Size = size;
+ arcSegment.Point = endPoint;
+ arcSegment.SweepDirection = SweepDirection.Clockwise;
+ arcSegment.IsLargeArc = isLargeArc;
+ var center = new Point(centerX, centerY);
+ var line2Segment = new LineSegment(center, false);
+
+ var pathGeometry = new PathGeometry(new PathFigure[]
+ {
+ new PathFigure(new Point(centerX, centerY), new List()
+ {
+ line1Segment,
+ arcSegment,
+ line2Segment,
+ }, true)
+ });
+ pathGeometries.Add(pathGeometry, $"{item.Key} : {item.Value.ToString("#,##0.#########################")}");
+ var backgroupBrush = new SolidColorBrush()
+ {
+ Color = vibrantColors[index >= vibrantColors.Length ? index % vibrantColors.Length : index]//RandomColorGenerator.GenerateRandomColor(),
+ };
+ backgroupBrush.Freeze();
+
+ drawingContext.DrawGeometry(backgroupBrush, null, pathGeometry);
+ index++;
+ if (!isFirst)
+ {
+ if (index == 1)
+ drawingContext.DrawLine(boldDrawingPen, center, startPoint);
+ else
+ drawingContext.DrawLine(drawingPen, center, startPoint);
+ }
+ prevAngle = angle;
+ }
+
+ }
+ private Pen CreatePen(double thickness)
+ {
+ var pen = new Pen
+ {
+ Thickness = thickness,
+ Brush = Brushes.White,
+ };
+ pen.Freeze();
+ return pen;
+ }
+ }
+ public class RandomColorGenerator
+ {
+ private static Random random = new Random();
+ public static Color GenerateRandomColor()
+ {
+ byte[] rgb = new byte[3];
+ random.NextBytes(rgb);
+ var randomColor = Color.FromArgb(255, rgb[0], rgb[1], rgb[2]);
+ return randomColor;
+ }
+ }
+}
diff --git a/src/WPFDevelopers.Shared/WPFDevelopers.Shared.projitems b/src/WPFDevelopers.Shared/WPFDevelopers.Shared.projitems
index 53988172..9769bd4f 100644
--- a/src/WPFDevelopers.Shared/WPFDevelopers.Shared.projitems
+++ b/src/WPFDevelopers.Shared/WPFDevelopers.Shared.projitems
@@ -13,6 +13,7 @@
+