using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using System.Collections.ObjectModel; using System.ComponentModel; using Microsoft.Expression.Shapes; using NLog.Config; using Misc; namespace FLY.Thick.Blowing.UI.Fix.Client.UiModule { /// <summary> /// UserControl1.xaml 的交互逻辑 /// </summary> public partial class PolarChart : UserControl, IPolarChart, INotifyPropertyChanged { #region 私有成员变量 /// <summary> /// 中心 /// </summary> Point Center; /// <summary> /// 总半径 /// </summary> double Radius; /// <summary> /// 分区环,宽度 /// </summary> double BoltNoRingThickness; /// <summary> /// 最内环,宽度 /// </summary> double InnerRingThickness; /// <summary> /// 图表,宽度 /// </summary> double GraphThickness; double Avg; List<Arc> BoltArc = new List<Arc>(); #endregion #region IPolarChart public static readonly DependencyProperty BoltCntProperty = DependencyProperty.Register(nameof(BoltCnt), typeof(int), typeof(PolarChart), new PropertyMetadata( 88, new PropertyChangedCallback(delegate (DependencyObject d, DependencyPropertyChangedEventArgs e) { (d as PolarChart).NotifyPropertyChanged(nameof(BoltCnt)); }))); public int BoltCnt { get { return (int)GetValue(BoltCntProperty); } set { SetValue(BoltCntProperty, value); } } public static int GetBoltCnt(UIElement element) { return (int)element.GetValue(BoltCntProperty); } public static void SetBoltCnt(UIElement element, int boltCnt) { element.SetValue(BoltCntProperty, boltCnt); } public static readonly DependencyProperty MPIsRightProperty = DependencyProperty.Register(nameof(MPIsRight), typeof(bool), typeof(PolarChart), new PropertyMetadata( true, new PropertyChangedCallback(delegate (DependencyObject d, DependencyPropertyChangedEventArgs e) { (d as PolarChart).NotifyPropertyChanged(nameof(MPIsRight)); }))); public bool MPIsRight { get { return (bool)GetValue(MPIsRightProperty); } set { SetValue(MPIsRightProperty, value); } } public static bool GetMPIsRight(UIElement element) { return (bool)element.GetValue(MPIsRightProperty); } public static void SetMPIsRight(UIElement element, bool isRight) { element.SetValue(MPIsRightProperty, isRight); } public static readonly DependencyProperty OrgBoltNoProperty = DependencyProperty.Register(nameof(OrgBoltNo), typeof(int), typeof(PolarChart), new PropertyMetadata( 1, new PropertyChangedCallback(delegate (DependencyObject d, DependencyPropertyChangedEventArgs e) { (d as PolarChart).NotifyPropertyChanged(nameof(OrgBoltNo)); }))); public int OrgBoltNo { get { return (int)GetValue(OrgBoltNoProperty); } set { SetValue(OrgBoltNoProperty, value); } } public static int GetOrgBoltNo(UIElement element) { return (int)element.GetValue(OrgBoltNoProperty); } public static void SetOrgBoltNo(UIElement element, int value) { element.SetValue(OrgBoltNoProperty, value); } public static readonly DependencyProperty TolerancePercentProperty = DependencyProperty.Register(nameof(TolerancePercent), typeof(double), typeof(PolarChart), new PropertyMetadata( 0.05, new PropertyChangedCallback(delegate (DependencyObject d, DependencyPropertyChangedEventArgs e) { (d as PolarChart).NotifyPropertyChanged(nameof(TolerancePercent)); }))); public double TolerancePercent { get { return (double)GetValue(TolerancePercentProperty); } set { SetValue(TolerancePercentProperty, value); } } public static double GetTolerancePercent(UIElement element) { return (double)element.GetValue(TolerancePercentProperty); } public static void SetTolerancePercent(UIElement element, double value) { element.SetValue(TolerancePercentProperty, value); } public static readonly DependencyProperty YRangePercentProperty = DependencyProperty.Register(nameof(YRangePercent), typeof(double), typeof(PolarChart), new PropertyMetadata( 3.0, new PropertyChangedCallback(delegate (DependencyObject d, DependencyPropertyChangedEventArgs e) { (d as PolarChart).NotifyPropertyChanged(nameof(YRangePercent)); }))); public double YRangePercent { get { return (double)GetValue(YRangePercentProperty); } set { SetValue(YRangePercentProperty, value); } } public static double GetYRangePercent(UIElement element) { return (double)element.GetValue(YRangePercentProperty); } public static void SetYRangePercent(UIElement element, double value) { element.SetValue(YRangePercentProperty, value); } ObservableCollection<double> values = new ObservableCollection<double>(); public ObservableCollection<double> Values { get { return values; } set { if (values != value) { if (values != null) values.CollectionChanged -= _values_CollectionChanged; values = value; values.CollectionChanged += _values_CollectionChanged; NotifyPropertyChanged(nameof(Values)); } } } System.Collections.Specialized.NotifyCollectionChangedEventHandler _values_CollectionChanged; List<Brush> AreaColors; #endregion public PolarChart() { InitializeComponent(); AreaColors = FLY.Thick.Blowing.UI.Fix.Client.Themes.Colors.AreaColors; _values_CollectionChanged = new System.Collections.Specialized.NotifyCollectionChangedEventHandler(values_CollectionChanged); this.PropertyChanged += new PropertyChangedEventHandler(PolarChart_PropertyChanged); Init(); DataBindAll_Init(); } string[] propertynames_draw = new string[] { nameof(BoltCnt), nameof(OrgBoltNo), nameof(MPIsRight), nameof(TolerancePercent), nameof(YRangePercent) }; void PolarChart_PropertyChanged(object sender, PropertyChangedEventArgs e) { if (propertynames_draw.Contains(e.PropertyName)) { Draw(); } } void values_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { //根据修改, 操作mYVal, 与 IsEmpty switch (e.Action) { case System.Collections.Specialized.NotifyCollectionChangedAction.Reset: { DataBindAll(); } break; case System.Collections.Specialized.NotifyCollectionChangedAction.Remove://修改X. 永远不执行 { DataBindAll(); } break; case System.Collections.Specialized.NotifyCollectionChangedAction.Replace://修改Y { DataBindAll(); } break; case System.Collections.Specialized.NotifyCollectionChangedAction.Add://添加 X,Y { DataBindAll(); } break; } } void Init() { Center = new Point(LayoutRoot.Width / 2, LayoutRoot.Width / 2); Radius = LayoutRoot.Width / 2; BoltNoRingThickness = 20; InnerRingThickness = (Radius - BoltNoRingThickness) / 4; GraphThickness = (Radius - InnerRingThickness - BoltNoRingThickness); } void Draw() { Clear(); UpdateCalState(); Draw_Background(); Draw_Manual(); Draw_BoltNo(); Draw_Datas(); Draw_TolerancePercent(); } double TransformDegrees(double degrees) { if (MPIsRight) return -degrees + 90; else return -degrees - 90; } void Draw_BoltNo() { double startAngle; double endAngle; SolidColorBrush brush_line = new SolidColorBrush(Color.FromRgb(208, 208, 208)); Arc arc_org = null; TextBlock t_org = null; for (int i = 0; i < BoltCnt; i++) { startAngle = TransformDegrees((i + 0 - (OrgBoltNo - 1) - 0.5) * 360 / BoltCnt); endAngle = TransformDegrees((i + 1 - (OrgBoltNo - 1) - 0.5) * 360 / BoltCnt); if (startAngle > endAngle) { double tmp = endAngle; endAngle = startAngle; startAngle = tmp; } Arc arc = new Arc() { ArcThickness = BoltNoRingThickness, ArcThicknessUnit = Microsoft.Expression.Media.UnitType.Pixel, StartAngle = startAngle, EndAngle = endAngle, Stretch = System.Windows.Media.Stretch.None, Stroke = System.Windows.Media.Brushes.White, Fill = System.Windows.Media.Brushes.Black, Width = Radius * 2, Height = Radius * 2, Opacity = 0.8 }; Point p = Polar.ToPoint( new Polar() { Degrees = (endAngle + startAngle) / 2, Radius = Radius - BoltNoRingThickness / 2 }, Center); TextBlock t = new TextBlock() { TextWrapping = System.Windows.TextWrapping.NoWrap, TextAlignment = System.Windows.TextAlignment.Center, Width = 20, Height = 15, Text = (i + 1).ToString(), FontFamily = new System.Windows.Media.FontFamily("Trebuchet MS"), FontWeight = System.Windows.FontWeights.Bold, Foreground = System.Windows.Media.Brushes.White }; t.SetValue(Canvas.LeftProperty, p.X - t.Width / 2); t.SetValue(Canvas.TopProperty, p.Y - t.Height / 2); if ((i + 1) == 1) { arc.Fill = System.Windows.Media.Brushes.Red; } if ((i + 1) == (int)OrgBoltNo) { //凸显!!!!!! arc_org = arc; t_org = t; } else { LayoutRoot.Children.Add(arc); LayoutRoot.Children.Add(t); } } if (arc_org != null) { double w = arc_org.EndAngle - arc_org.StartAngle; arc_org.Fill = Brushes.White;// new SolidColorBrush(Color.FromRgb(0x57, 0x9a, 0xab)); if (OrgBoltNo == 1) { arc_org.Fill = Brushes.Red; } arc_org.Stroke = Brushes.Black; arc_org.Width += arc_org.ArcThickness; arc_org.ArcThickness *= 2; arc_org.Height = arc_org.Width; arc_org.Margin = new Thickness(Radius - arc_org.Width / 2); arc_org.StartAngle -= w / 4; arc_org.EndAngle += w / 4; LayoutRoot.Children.Add(arc_org); t_org.Foreground = Brushes.Black; if (OrgBoltNo == 1) { t_org.Foreground = Brushes.White; } LayoutRoot.Children.Add(t_org); } } void Draw_Manual() { double startAngle; double endAngle; //每5个一条线 int XStep = 5; for(int i=0;i<BoltCnt;i+=XStep) { int start_boltindex = i; startAngle = TransformDegrees((start_boltindex + 0 - (OrgBoltNo - 1) - 0.5) * 360 / BoltCnt); Point p1 = Polar.ToPoint( new Polar() { Degrees = startAngle, Radius = InnerRingThickness + GraphThickness }, Center); Point p2 = Polar.ToPoint( new Polar() { Degrees = startAngle, Radius = InnerRingThickness }, Center); Line l = new Line() { X1 = p1.X, Y1 = p1.Y, X2 = p2.X, Y2 = p2.Y, Stroke = new SolidColorBrush(Colors.LightGray) }; LayoutRoot.Children.Add(l); } } void Draw_Background() { SolidColorBrush brush_alarm = new SolidColorBrush(Colors.LightGray); double r = InnerRingThickness; //最里面的圆 Ellipse ellipse_inner = new Ellipse() { Width = r * 2, Height = r * 2, Stroke = Brushes.Black, Margin = new Thickness(Radius - r) }; LayoutRoot.Children.Add(ellipse_inner); //目标线 r = InnerRingThickness + GraphThickness * 0.5; Ellipse ellipse_target = new Ellipse() { Width = r * 2, Height = r * 2, Stroke = AreaColors[0], Margin = new Thickness(Radius - r), StrokeThickness = 1 }; LayoutRoot.Children.Add(ellipse_target); double toleranceThickness = GraphThickness / (2 * YRangePercent); double target_r = r; for (int i = 1; i < YRangePercent; i++) { Brush stroke; if (i == 1) { stroke = AreaColors[1]; } else if (i == 2) { stroke = AreaColors[2]; } else { stroke = brush_alarm; } //-公差线 r = target_r + toleranceThickness * i; Ellipse ellipse_alarm1 = new Ellipse() { Width = r * 2, Height = r * 2, Stroke = stroke, Margin = new Thickness(Radius - r), StrokeThickness = 1 }; //+公差线 r = target_r - toleranceThickness * i; Ellipse ellipse_alarm2 = new Ellipse() { Width = r * 2, Height = r * 2, Stroke = stroke, Margin = new Thickness(Radius - r), StrokeThickness = 1 }; LayoutRoot.Children.Add(ellipse_alarm1); LayoutRoot.Children.Add(ellipse_alarm2); } } void Draw_Datas()//初始化所有 BoltArc { double startAngle; double endAngle; if (BoltArc.Count > 0) { foreach (Arc arc in BoltArc) LayoutRoot.Children.Remove(arc); BoltArc.Clear(); } for (int i = 0; i < BoltCnt; i++) { startAngle = TransformDegrees((i + 0 - (OrgBoltNo - 1) - 0.5) * 360 / BoltCnt); endAngle = TransformDegrees((i + 1 - (OrgBoltNo - 1) - 0.5) * 360 / BoltCnt); if (startAngle > endAngle) { double tmp = endAngle; endAngle = startAngle; startAngle = tmp; } Arc arc = new Arc() { ArcThicknessUnit = Microsoft.Expression.Media.UnitType.Pixel, StartAngle = startAngle, EndAngle = endAngle, Stretch = System.Windows.Media.Stretch.None, Stroke = System.Windows.Media.Brushes.Black, StrokeThickness = 1 }; Update_OneData(i, arc); LayoutRoot.Children.Add(arc); BoltArc.Add(arc); } } void Draw_TolerancePercent() { double startAngle = TransformDegrees(0); double percent = TolerancePercent; if (percent < 0.01) percent = 0.01; double toleranceThickness = GraphThickness / (2 * YRangePercent); double target_r = InnerRingThickness + GraphThickness * 0.5; ////Value 外小 内大 ////负公差 //Draw_Marker(new Polar() { Degrees = startAngle, Radius = target_r + toleranceThickness }, $"{(-100 * percent):F0}%"); ////正公差 //Draw_Marker(new Polar() { Degrees = startAngle, Radius = target_r - toleranceThickness }, $"{(+100 * percent):F0}%"); //Value 外大 内小 //负公差 Draw_Marker(new Polar() { Degrees = startAngle, Radius = target_r - toleranceThickness }, $"{(-100 * percent):F0}%"); //正公差 Draw_Marker(new Polar() { Degrees = startAngle, Radius = target_r + toleranceThickness }, $"{(+100 * percent):F0}%"); } void Draw_Marker(Polar polar, string s) { Point p1 = Polar.ToPoint( polar, Center); SolidColorBrush brush_alarm = new SolidColorBrush(Color.FromRgb(0xff, 0xc1, 0xc1)); SolidColorBrush brush_line = new SolidColorBrush(Color.FromRgb(208, 208, 208)); double r = 15; //底 Ellipse ellipse_background = new Ellipse() { Width = r * 2, Height = r * 2, Fill = System.Windows.Media.Brushes.Red, Opacity = 0.8, Stroke = System.Windows.Media.Brushes.Black, StrokeThickness = 1 }; ellipse_background.SetValue(Canvas.LeftProperty, p1.X - ellipse_background.Width / 2); ellipse_background.SetValue(Canvas.TopProperty, p1.Y - ellipse_background.Width / 2); TextBlock t = new TextBlock() { TextWrapping = System.Windows.TextWrapping.NoWrap, TextAlignment = System.Windows.TextAlignment.Center, Width = 20, Height = 12, Text = s, FontFamily = new System.Windows.Media.FontFamily("Trebuchet MS"), FontWeight = System.Windows.FontWeights.Bold, Foreground = System.Windows.Media.Brushes.White, FontSize = 10 }; t.SetValue(Canvas.LeftProperty, p1.X - t.Width / 2); t.SetValue(Canvas.TopProperty, p1.Y - t.Height / 2); LayoutRoot.Children.Add(ellipse_background); LayoutRoot.Children.Add(t); } void Clear() { LayoutRoot.Children.Clear(); BoltArc.Clear(); } void Update_OneData(int index) { if (index >= BoltArc.Count) return; Update_OneData(index, BoltArc[index]); } void Update_OneData(int index, Arc arc) { double percent = TolerancePercent; if (percent < 0.01) percent = 0.01; double r = 0; if (Values != null && index < Values.Count && !double.IsNaN(Values[index])) { r = ((Values[index] - Avg) / Avg) / (2 * YRangePercent * percent) * GraphThickness; arc.Visibility = Visibility.Visible; } else { arc.Visibility = Visibility.Hidden; } double target_r = InnerRingThickness + GraphThickness * 0.5; //-------------------------------------------------------------- //限制 if (r > GraphThickness * 0.5) { r = GraphThickness * 0.5; } else if (r < -GraphThickness * 0.5) { r = -GraphThickness * 0.5; } //-------------------------------------------------------------- //改变颜色 double toleranceThickness = GraphThickness / (YRangePercent * 2); if (r > toleranceThickness * 2) { arc.Fill = AreaColors[2]; } else if (r < -toleranceThickness * 2) { arc.Fill = AreaColors[2]; } else if (r > toleranceThickness) { arc.Fill = AreaColors[1]; } else if (r < -toleranceThickness) { arc.Fill = AreaColors[1]; } else { arc.Fill = AreaColors[0]; } //扇型的厚度 arc.ArcThickness = Math.Abs(r); //扇型最外圆直径 //Value 外小 内大 //if (r < 0) // arc.Width = (target_r - r) * 2; //else // arc.Width = target_r * 2; //Value 外大 内小 if (r > 0) arc.Width = (target_r + r) * 2; else arc.Width = target_r * 2; arc.Height = arc.Width; arc.Margin = new Thickness(Radius - arc.Width / 2); } /// <summary> /// 与 Multi,EmptyValue,DataFirst,DataLast 相关 /// </summary> void UpdateCalState() { if (Values == null) return; List<double> datlist; if (BoltCnt >= Values.Count()) { datlist = Values.ToList(); } else { datlist = Values.ToList().GetRange(0, BoltCnt); } double avg = datlist.AverageNoNull(); if (!double.IsNaN(avg)) Avg = avg; return; } void Update_Datas() { UpdateCalState(); for (int i = 0; i < BoltCnt; i++) Update_OneData(i); } //public void Draw #region INotifyPropertyChanged 成员 public void NotifyPropertyChanged(string propertyName) { if (PropertyChanged != null) PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName)); } public event PropertyChangedEventHandler PropertyChanged; #endregion private void UserControl_Loaded(object sender, RoutedEventArgs e) { Draw(); } bool batabindall_flag = false; System.Windows.Threading.DispatcherTimer timer = new System.Windows.Threading.DispatcherTimer(); void DataBindAll_Init() { timer = new System.Windows.Threading.DispatcherTimer(); timer.Tick += new EventHandler(delegate (object sender, EventArgs e) { if (batabindall_flag) { batabindall_flag = false; Update_Datas(); } }); timer.Interval = TimeSpan.FromSeconds(0.2); timer.Start(); } void DataBindAll() { batabindall_flag = true; } } public interface IPolarChart { /// <summary> /// 分区数 /// </summary> int BoltCnt { get; set; } /// <summary> /// 测厚仪测量点位置方向:Left, Right (也就是三角形在左还是右) /// </summary> bool MPIsRight { get; set; } /// <summary> /// 复位分区号,也就是三角形对应的 分区号 /// </summary> int OrgBoltNo { get; set; } /// <summary> /// 公差% /// </summary> double TolerancePercent { get; set; } /// <summary> /// Y轴是 TolerancePercent 的倍数 /// </summary> double YRangePercent { get; set; } /// <summary> /// 数据源 /// </summary> ObservableCollection<double> Values { get; set; } } /// <summary> /// Polar 12点方向=0°,3点方向=90°,6点方向=180°,9点方向=270° /// Point 左上角 x=0,y=0; 右上角 x=100, y=0; 左下角 x=0,y=100; /// </summary> class Polar { public double Degrees;//角度制!!! public double Radius; public static Point ToPoint(Polar polar, Point center) { Point p = new Point(); Vector v = new Vector(); v.Y = -polar.Radius * Math.Cos(DegreesToRadians(polar.Degrees)); v.X = polar.Radius * Math.Sin(DegreesToRadians(polar.Degrees)); p = center + v; return p; } public static double DegreesToRadians(double degrees) { double radians = Math.PI / 180 * degrees; return radians; } } }