/// <summary> /// 雷达分析图控件 /// </summary> [ToolboxItem(true)] [DefaultProperty("ChartLineItems")] [Description("雷达分析图控件")] public partial class RadarChartExt : Control { #region private ChartTypes chartType = ChartTypes.Circle; /// <summary> /// 雷达分析图显示类型 /// </summary> [DefaultValue(ChartTypes.Circle)] [Description("雷达分析图显示类型")] public ChartTypes ChartType { get { return this.chartType; } set { if (this.chartType == value) return; this.chartType = value; this.Invalidate(); } } private ChartAnchors chartAnchor = ChartAnchors.Left; /// <summary> /// 分析图布局 /// </summary> [DefaultValue(ChartAnchors.Left)] [Description("分析图布局")] public ChartAnchors ChartAnchor { get { return this.chartAnchor; } set { if (this.chartAnchor == value) return; this.chartAnchor = value; this.InitializeRectangle(); this.Invalidate(); } } private DescriptionAnchors descriptionAnchor = DescriptionAnchors.Bottom; /// <summary> /// 描述布局 /// </summary> [DefaultValue(DescriptionAnchors.Bottom)] [Description("描述布局")] public DescriptionAnchors DescriptionAnchor { get { return this.descriptionAnchor; } set { if (this.descriptionAnchor == value) return; this.descriptionAnchor = value; this.InitializeRectangle(); this.Invalidate(); } } private Size chartSize = new Size(200, 200); /// <summary> /// 分析图Size /// </summary> [DefaultValue(typeof(Size), "200, 200")] [Description("分析图Size")] public Size ChartSize { get { return this.chartSize; } set { if (this.chartSize == value || value.Width < 2 || value.Height < 2) return; this.chartSize = value; this.InitializeRectangle(); this.Invalidate(); } } private int loopCount = 5; /// <summary> /// 环形数量 /// </summary> [DefaultValue(5)] [Description("环形数量")] public int LoopCount { get { return this.loopCount; } set { if (this.loopCount == value || value < 1) return; this.loopCount = value; this.InitializeLoop(); this.InitializeRectangle(); this.Invalidate(); } } private float loopInterval = 20f; /// <summary> /// 环形间隔值 /// </summary> [DefaultValue(20f)] [Description("环形间隔值")] public float LoopInterval { get { return this.loopInterval; } set { if (this.loopInterval == value || value < 0f) return; this.loopInterval = value; this.InitializeRectangle(); this.Invalidate(); } } private int loopBorderthickness = 1; /// <summary> /// 环形线厚度 /// </summary> [DefaultValue(1)] [Description("环形线厚度")] public int LoopBorderthickness { get { return this.loopBorderthickness; } set { if (this.loopBorderthickness == value || this.loopBorderthickness < 1) return; this.loopBorderthickness = value; this.Invalidate(); } } private Color loopBorderColor = Color.FromArgb(224, 224, 224); /// <summary> /// 环形线颜色 /// </summary> [DefaultValue(typeof(Color), "224, 224, 224")] [Description("环形线颜色")] [Editor(typeof(ColorEditorExt), typeof(System.Drawing.Design.UITypeEditor))] public Color LoopBorderColor { get { return this.loopBorderColor; } set { if (this.loopBorderColor == value) return; this.loopBorderColor = value; this.Invalidate(); } } private Color loopOddBackColor = Color.FromArgb(62, 224, 224, 224); /// <summary> /// 奇数环形背景颜色 /// </summary> [DefaultValue(typeof(Color), "62, 224, 224, 224")] [Description("奇数环形背景颜色")] [Editor(typeof(ColorEditorExt), typeof(System.Drawing.Design.UITypeEditor))] public Color LoopOddBackColor { get { return this.loopOddBackColor; } set { if (this.loopOddBackColor == value) return; this.loopOddBackColor = value; this.Invalidate(); } } private Color loopEvenBackColor = Color.FromArgb(255, 255, 255); /// <summary> /// 偶数环形背景颜色 /// </summary> [DefaultValue(typeof(Color), "255, 255, 255")] [Description("偶数环形背景颜色")] [Editor(typeof(ColorEditorExt), typeof(System.Drawing.Design.UITypeEditor))] public Color LoopEvenBackColor { get { return this.loopEvenBackColor; } set { if (this.loopEvenBackColor == value) return; this.loopEvenBackColor = value; this.Invalidate(); } } private Color loopScaleTextColor = Color.FromArgb(128, 128, 128); /// <summary> /// 环形刻度文本颜色 /// </summary> [DefaultValue(typeof(Color), "128, 128, 128")] [Description("环形刻度文本颜色")] [Editor(typeof(ColorEditorExt), typeof(System.Drawing.Design.UITypeEditor))] public Color LoopScaleTextColor { get { return this.loopScaleTextColor; } set { if (this.loopScaleTextColor == value) return; this.loopScaleTextColor = value; this.Invalidate(); } } private Color loopLineTextColor = Color.FromArgb(0, 192, 192); /// <summary> /// 环形选项文本颜色 /// </summary> [DefaultValue(typeof(Color), "0, 192, 192")] [Description("环形选项文本颜色")] [Editor(typeof(ColorEditorExt), typeof(System.Drawing.Design.UITypeEditor))] public Color LoopLineTextColor { get { return this.loopLineTextColor; } set { if (this.loopLineTextColor == value) return; this.loopLineTextColor = value; this.Invalidate(); } } private bool loopLineMaxValueShow = false; /// <summary> /// 动画中是否显示环形选项最大值高亮背景颜色 /// </summary> [DefaultValue(false)] [Description("动画中是否显示环形选项最大值高亮背景颜色")] public bool LoopLineMaxValueShow { get { return this.loopLineMaxValueShow; } set { if (this.loopLineMaxValueShow == value) return; this.loopLineMaxValueShow = value; this.Invalidate(); } } private Color loopLineMaxValueTextColor = Color.FromArgb(255, 128, 128); /// <summary> /// 环形选项最大值高亮背景颜色 /// </summary> [DefaultValue(typeof(Color), "255, 128, 128")] [Description("环形选项最大值高亮背景颜色")] [Editor(typeof(ColorEditorExt), typeof(System.Drawing.Design.UITypeEditor))] public Color LoopLineMaxValueTextColor { get { return this.loopLineMaxValueTextColor; } set { if (this.loopLineMaxValueTextColor == value) return; this.loopLineMaxValueTextColor = value; this.Invalidate(); } } private bool loopLineMinValueShow = false; /// <summary> /// 动画中是否显示环形选项最小值高亮背景颜色 /// </summary> [DefaultValue(false)] [Description("动画中是否显示环形选项最小值高亮背景颜色")] public bool LoopLineMinValueShow { get { return this.loopLineMinValueShow; } set { if (this.loopLineMinValueShow == value) return; this.loopLineMinValueShow = value; this.Invalidate(); } } private Color loopLineMinValueTextColor = Color.FromArgb(65, 105, 225); /// <summary> /// 环形选项最小值高亮背景颜色 /// </summary> [DefaultValue(typeof(Color), "65, 105, 225")] [Description("环形选项最小值高亮背景颜色")] [Editor(typeof(ColorEditorExt), typeof(System.Drawing.Design.UITypeEditor))] public Color LoopLineMinValueTextColor { get { return this.loopLineMinValueTextColor; } set { if (this.loopLineMinValueTextColor == value) return; this.loopLineMinValueTextColor = value; this.Invalidate(); } } private bool optionAreaShow = true; /// <summary> /// 是否显示选项列表 /// </summary> [DefaultValue(true)] [Description("是否显示选项列表")] public bool OptionAreaShow { get { return this.optionAreaShow; } set { if (this.optionAreaShow == value) return; this.optionAreaShow = value; this.InitializeRectangle(); this.Invalidate(); } } private int optionAreaWidth = 40; /// <summary> /// 选项列表区域宽度 /// </summary> [DefaultValue(40)] [Description("选项列表区域宽度")] public int OptionAreaWidth { get { return this.optionAreaWidth; } set { if (this.optionAreaWidth == value || this.optionAreaWidth < 0) return; this.optionAreaWidth = value; this.InitializeRectangle(); this.Invalidate(); } } private int descriptionAreaHeight = 30; /// <summary> /// 文本区域高度 /// </summary> [DefaultValue(30)] [Description("文本区域高度")] public int DescriptionAreaHeight { get { return this.descriptionAreaHeight; } set { if (this.descriptionAreaHeight == value || this.descriptionAreaHeight < 0) return; this.descriptionAreaHeight = value; this.InitializeRectangle(); this.Invalidate(); } } private Color optionTextColor = Color.FromArgb(128, 128, 128); /// <summary> /// 选项文本颜色 /// </summary> [DefaultValue(typeof(Color), "128, 128, 128")] [Description("选项文本颜色")] [Editor(typeof(ColorEditorExt), typeof(System.Drawing.Design.UITypeEditor))] public Color OptionTextColor { get { return this.optionTextColor; } set { if (this.optionTextColor == value) return; this.optionTextColor = value; this.Invalidate(); } } private Color optionTipTextColor = Color.FromArgb(255, 255, 255); /// <summary> /// 选项提示文本颜色 /// </summary> [DefaultValue(typeof(Color), "255, 255, 255")] [Description("选项提示文本颜色")] [Editor(typeof(ColorEditorExt), typeof(System.Drawing.Design.UITypeEditor))] public Color OptionTipTextColor { get { return this.optionTipTextColor; } set { if (this.optionTipTextColor == value) return; this.optionTipTextColor = value; this.Invalidate(); } } private string title = ""; /// <summary> /// 标题 /// </summary> [DefaultValue("")] [Description("标题")] public string Title { get { return this.title; } set { if (this.title == value) return; this.title = value; this.Invalidate(); } } private Color titleColor = Color.FromArgb(219, 112, 147); /// <summary> /// 标题颜色 /// </summary> [DefaultValue(typeof(Color), "219, 112, 147")] [Description("标题颜色")] [Editor(typeof(ColorEditorExt), typeof(System.Drawing.Design.UITypeEditor))] public Color TitleColor { get { return this.titleColor; } set { if (this.titleColor == value) return; this.titleColor = value; this.Invalidate(); } } private ChartLoopCollection chartLoopCollection; /// <summary> /// 分析图环形配置集合 /// </summary> [Browsable(false)] [Description("分析图环形配置集合")] public ChartLoopCollection ChartLoopItems { get { if (this.chartLoopCollection == null) this.chartLoopCollection = new ChartLoopCollection(this); return this.chartLoopCollection; } } private ChartLineCollection chartLineCollection; /// <summary> /// 分析图角度线配置集合 /// </summary> [Description("分析图角度线配置集合")] [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] public ChartLineCollection ChartLineItems { get { if (this.chartLineCollection == null) this.chartLineCollection = new ChartLineCollection(this); return this.chartLineCollection; } } private ChartItemCollection chartItemOptionCollection; /// <summary> /// 分析图选项数据集合 /// </summary> [Browsable(false)] [Description("分析图选项数据集合")] [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] public ChartItemCollection ChartItemItems { get { if (this.chartItemOptionCollection == null) this.chartItemOptionCollection = new ChartItemCollection(this); return this.chartItemOptionCollection; } } protected override Size DefaultSize { get { return new Size(150, 150); ; } } public override string Text { get { return base.Text; } set { if (base.Text == value) return; base.Text = value; this.Invalidate(); } } protected override Padding DefaultPadding { get { return new Padding(10); } } public new Padding Padding { get { return base.Padding; } set { if (this.Padding == value) return; this.Padding = value; this.InitializeRectangle(); this.Invalidate(); } } /// <summary> /// 是否启用动画变换 /// </summary> private bool isAnimation = false; /// <summary> /// 雷达分析图信息 /// </summary> private ChartRectangle ChartMainRect; /// <summary> /// 分析图选项状态 /// </summary> private ChartItemMoveStatuss ChartItemMoveStatus = ChartItemMoveStatuss.Normal; /// <summary> /// 默认开始角度 /// </summary> private float DefaultAngle = 270f; /// <summary> /// 选中环形对角线 /// </summary> private ItemLine SelectItemLine = null; /// <summary> /// 动画播放定时器 /// </summary> private AnimationTimer animationTimer; #endregion public RadarChartExt() { SetStyle(ControlStyles.UserPaint, true); SetStyle(ControlStyles.AllPaintingInWmPaint, true); SetStyle(ControlStyles.OptimizedDoubleBuffer, true); SetStyle(ControlStyles.ResizeRedraw, true); SetStyle(ControlStyles.SupportsTransparentBackColor, true); InitializeComponent(); this.ChartMainRect = new ChartRectangle(); this.InitializeLoop(); this.InitializeRectangle(); } #region 重写 protected override void OnPaint(System.Windows.Forms.PaintEventArgs e) { base.OnPaint(e); if (this.ChartLineItems.Count < 3) return; Graphics g = e.Graphics; SmoothingMode sm = g.SmoothingMode; g.SmoothingMode = SmoothingMode.AntiAlias; GraphicsPath loop_gp = new GraphicsPath(); #region 环形 Pen loopBorder_pen = new Pen(this.LoopBorderColor, this.LoopBorderthickness); SolidBrush loopoddback_sb = new SolidBrush(this.LoopOddBackColor); SolidBrush loopevenback_sb = new SolidBrush(this.LoopEvenBackColor); SolidBrush looplinetext_sb = new SolidBrush(this.LoopLineTextColor); SolidBrush looplinemaxvaluetext_sb = this.isAnimation && this.LoopLineMaxValueShow ? new SolidBrush(this.LoopLineMaxValueTextColor) : null; SolidBrush looplineminvaluetext_sb = this.isAnimation && this.LoopLineMinValueShow ? new SolidBrush(this.LoopLineMinValueTextColor) : null; for (int i = 0; i < this.ChartLoopItems.Count; i++) { if (this.ChartLoopItems[i].DataPoints.Length < 3) continue; if (this.ChartType == ChartTypes.Rhombus) { loop_gp.Reset(); loop_gp.AddPolygon(this.ChartLoopItems[i].DataPoints); loop_gp.CloseFigure(); g.DrawPath(loopBorder_pen, loop_gp); loop_gp.Reset(); loop_gp.AddPolygon(this.ChartLoopItems[i].DataPoints); if (i > 0) { loop_gp.StartFigure(); loop_gp.AddPolygon(this.ChartLoopItems[i - 1].DataPoints); } g.FillPath((i % 2 == 0) ? loopoddback_sb : loopevenback_sb, loop_gp); } else { loop_gp.Reset(); loop_gp.AddEllipse(new RectangleF(this.ChartLoopItems[i].LoopCenter.X - this.ChartLoopItems[i].LoopRadius, this.ChartLoopItems[i].LoopCenter.Y - this.ChartLoopItems[i].LoopRadius, this.ChartLoopItems[i].LoopRadius * 2, this.ChartLoopItems[i].LoopRadius * 2)); g.DrawPath(loopBorder_pen, loop_gp); loop_gp.Reset(); loop_gp.AddEllipse(new RectangleF(this.ChartLoopItems[i].LoopCenter.X - this.ChartLoopItems[i].LoopRadius, this.ChartLoopItems[i].LoopCenter.Y - this.ChartLoopItems[i].LoopRadius, this.ChartLoopItems[i].LoopRadius * 2, this.ChartLoopItems[i].LoopRadius * 2)); if (i > 0) { loop_gp.StartFigure(); loop_gp.AddEllipse(new RectangleF(this.ChartLoopItems[i - 1].LoopCenter.X - this.ChartLoopItems[i - 1].LoopRadius, this.ChartLoopItems[i - 1].LoopCenter.Y - this.ChartLoopItems[i - 1].LoopRadius, this.ChartLoopItems[i - 1].LoopRadius * 2, this.ChartLoopItems[i - 1].LoopRadius * 2)); } g.FillPath((i % 2 == 0) ? loopoddback_sb : loopevenback_sb, loop_gp); } } #endregion #region 角度线 float maxValue = 0f; float minValue = 0f; List<int> maxIndexList = new List<int>(); List<int> minIndexList = new List<int>(); if (this.LoopLineMaxValueShow && this.ChartItemItems.Count > 0) { for (int i = 0; i < this.ChartItemItems[0].ItemDataTargrt.Length; i++) { if (i == 0) { maxValue = this.ChartItemItems[0].ItemDataTargrt[0]; } if (this.ChartItemItems[0].ItemDataTargrt[i] > maxValue) { maxValue = this.ChartItemItems[0].ItemDataTargrt[i]; maxIndexList.Clear(); maxIndexList.Add(i); } else if (this.ChartItemItems[0].ItemDataTargrt[i] == maxValue) { maxIndexList.Add(i); } } } if (this.LoopLineMinValueShow && this.ChartItemItems.Count > 0) { for (int i = 0; i < this.ChartItemItems[0].ItemDataTargrt.Length; i++) { if (i == 0) { minValue = this.ChartItemItems[0].ItemDataTargrt[0]; } if (this.ChartItemItems[0].ItemDataTargrt[i] < minValue) { minValue = this.ChartItemItems[0].ItemDataTargrt[i]; minIndexList.Clear(); minIndexList.Add(i); } else if (this.ChartItemItems[0].ItemDataTargrt[i] == minValue) { minIndexList.Add(i); } } } for (int i = 0; i < this.ChartLineItems.Count; i++) { string loopline_txt = this.ChartLineItems[i].LineText; SizeF loopline_textsize = g.MeasureString(loopline_txt, this.Font); PointF loopline_txt_point = this.ChartLineItems[i].LineEndPoint; if (this.ChartLineItems[i].LineAngle == 0) { loopline_txt_point.Y = loopline_txt_point.Y - loopline_textsize.Height / 2; } else if (this.ChartLineItems[i].LineAngle < 90) { } else if (this.ChartLineItems[i].LineAngle == 90) { loopline_txt_point.X = loopline_txt_point.X - loopline_textsize.Width / 2; } else if (this.ChartLineItems[i].LineAngle < 180) { loopline_txt_point.X = loopline_txt_point.X - loopline_textsize.Width; } else if (this.ChartLineItems[i].LineAngle == 180) { loopline_txt_point.Y = loopline_txt_point.Y - loopline_textsize.Height / 2; loopline_txt_point.X = loopline_txt_point.X - loopline_textsize.Width; } else if (this.ChartLineItems[i].LineAngle < 270) { loopline_txt_point.Y = loopline_txt_point.Y - loopline_textsize.Height; loopline_txt_point.X = loopline_txt_point.X - loopline_textsize.Width; } else if (this.ChartLineItems[i].LineAngle == 270) { loopline_txt_point.Y = loopline_txt_point.Y - loopline_textsize.Height; loopline_txt_point.X = loopline_txt_point.X - loopline_textsize.Width / 2; } else if (this.ChartLineItems[i].LineAngle < 360) { loopline_txt_point.Y = loopline_txt_point.Y - loopline_textsize.Height; } SolidBrush sb = looplinetext_sb; if (this.isAnimation) { if (maxIndexList.IndexOf(i) != -1) { sb = looplinemaxvaluetext_sb; } if (minIndexList.IndexOf(i) != -1) { sb = looplineminvaluetext_sb; } } g.DrawString(loopline_txt, this.Font, sb, loopline_txt_point); } for (int j = 0; j < this.ChartLineItems.Count; j++) { g.DrawLine(loopBorder_pen, this.ChartLineItems[j].LoopCenter, this.ChartLineItems[j].LineEndPoint); } #endregion loop_gp.Dispose(); loopBorder_pen.Dispose(); loopoddback_sb.Dispose(); loopevenback_sb.Dispose(); looplinetext_sb.Dispose(); if (looplinemaxvaluetext_sb != null) looplinemaxvaluetext_sb.Dispose(); if (looplineminvaluetext_sb != null) looplineminvaluetext_sb.Dispose(); #region 数据 SolidBrush databack_sb = new SolidBrush(Color.White); if (this.ChartItemItems.Count > 0) { GraphicsPath databack_gp = new GraphicsPath(); Pen databorder_pen = new Pen(Color.White, 1); Pen datadot_pen = new Pen(Color.White, 1); for (int i = 0; i < this.ChartItemItems.Count; i++) { if (this.ChartItemItems[i].ItemDataPoints.Length < 2) continue; databack_gp.Reset(); databack_gp.AddPolygon(this.ChartItemItems[i].ItemDataPoints); databack_gp.CloseFigure(); databack_sb.Color = Color.FromArgb(50, this.ChartItemItems[i].ItemBackColor); g.FillPath(databack_sb, databack_gp); databorder_pen.Color = this.ChartItemItems[i].ItemBackColor; g.DrawPath(databorder_pen, databack_gp); datadot_pen.Color = this.ChartItemItems[i].ItemBackColor; for (int j = 0; j < this.ChartItemItems[i].ItemDataPoints.Length; j++) { g.DrawEllipse(datadot_pen, this.ChartItemItems[i].ItemDataRectF[j]); } } databack_gp.Dispose(); databorder_pen.Dispose(); datadot_pen.Dispose(); } #endregion g.SmoothingMode = sm; #region 数据选项 if (this.OptionAreaShow) { float desc_padding = 5; float descitem_height = 0; SolidBrush descitem_sb = new SolidBrush(Color.White); SolidBrush desctext_sb = new SolidBrush(this.OptionTextColor); for (int i = 0; i < this.ChartItemItems.Count; i++) { descitem_sb.Color = this.ChartItemItems[i].ItemBackColor; SizeF desc_size = g.MeasureString(this.ChartItemItems[i].ItemText, this.Font); RectangleF desc_item_rect = new RectangleF(this.ChartMainRect.OptionRect.X + desc_padding, this.ChartMainRect.OptionRect.Y + descitem_height, 20, 10); RectangleF desctxt_rect = new RectangleF(this.ChartMainRect.OptionRect.X + desc_padding + desc_item_rect.Width + 5, this.ChartMainRect.OptionRect.Y + descitem_height, this.ChartMainRect.OptionRect.Width - desc_padding - desc_item_rect.Width - 5, desc_size.Height); g.FillRectangle(descitem_sb, desc_item_rect); g.DrawString(this.ChartItemItems[i].ItemText, this.Font, desctext_sb, desctxt_rect); descitem_height += desc_size.Height + desc_padding; } descitem_sb.Dispose(); desctext_sb.Dispose(); } #endregion #region 刻度 SolidBrush loopscaletext_sb = new SolidBrush(this.LoopScaleTextColor); Pen loopscaletext_pen = new Pen(this.LoopScaleTextColor); for (int i = 0; i < this.ChartLoopItems.Count; i++) { string loopscale_txt = (this.LoopInterval * (i + 1)).ToString(); SizeF loopscale_size = g.MeasureString(loopscale_txt, this.Font); g.DrawLine(loopscaletext_pen, this.ChartLoopItems[i].DataPoints[0].X, this.ChartLoopItems[i].DataPoints[0].Y, this.ChartLoopItems[i].DataPoints[0].X + 3, this.ChartLoopItems[i].DataPoints[0].Y); g.DrawString(loopscale_txt, this.Font, loopscaletext_sb, new RectangleF(this.ChartLoopItems[i].DataPoints[0].X + 5, this.ChartLoopItems[i].DataPoints[0].Y - loopscale_size.Height / 2, loopscale_size.Width, loopscale_size.Height)); } loopscaletext_sb.Dispose(); #endregion #region 数据点提示 if (this.ChartItemMoveStatus == ChartItemMoveStatuss.Enter) { if (this.SelectItemLine != null) { float datadottipback_padding = 2; databack_sb.Color = Color.FromArgb(100, this.ChartItemItems[this.SelectItemLine.ItemIndex].ItemBackColor); Pen tipborder_pen = new Pen(this.ChartItemItems[this.SelectItemLine.ItemIndex].ItemBackColor); SolidBrush datadottiptext_sb = new SolidBrush(this.OptionTipTextColor); StringFormat datadottiptext_sf = new StringFormat() { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center }; string datadottip_txt = this.SelectItemLine.LineIndex >= this.ChartItemItems[this.SelectItemLine.ItemIndex].ItemDataTargrt.Count() ? " " : this.ChartItemItems[this.SelectItemLine.ItemIndex].ItemDataTargrt[this.SelectItemLine.LineIndex].ToString(); SizeF datadottiptxt_size = g.MeasureString(datadottip_txt, this.Font); RectangleF datadottip_rect = new RectangleF(this.ChartItemItems[this.SelectItemLine.ItemIndex].ItemDataRectF[this.SelectItemLine.LineIndex].X - (datadottiptxt_size.Width + datadottipback_padding * 2) / 2, this.ChartItemItems[this.SelectItemLine.ItemIndex].ItemDataRectF[this.SelectItemLine.LineIndex].Y - datadottiptxt_size.Height - datadottipback_padding * 3, datadottiptxt_size.Width + datadottipback_padding * 2, datadottiptxt_size.Height + datadottipback_padding * 2); g.FillRectangle(databack_sb, datadottip_rect); g.DrawRectangle(tipborder_pen, datadottip_rect.X, datadottip_rect.Y, datadottip_rect.Width, datadottip_rect.Height); g.DrawString(datadottip_txt, this.Font, datadottiptext_sb, datadottip_rect, datadottiptext_sf); datadottiptext_sb.Dispose(); tipborder_pen.Dispose(); datadottiptext_sf.Dispose(); } } #endregion databack_sb.Dispose(); #region 标题 SolidBrush title_sb = new SolidBrush(this.TitleColor); StringFormat title_sf = new StringFormat() { Alignment = StringAlignment.Center }; g.DrawString(this.Title, this.Font, title_sb, this.ChartMainRect.DescriptionRect, title_sf); title_sb.Dispose(); title_sf.Dispose(); #endregion } protected override void OnResize(EventArgs e) { this.InitializeRectangle(); base.OnResize(e); } protected override void OnMouseMove(MouseEventArgs e) { base.OnMouseMove(e); if (this.ChartMainRect.ChartRect.Contains(e.Location)) { for (int i = 0; i < this.ChartItemItems.Count; i++) { for (int j = 0; j < this.ChartItemItems[i].ItemDataRectF.Length; j++) { if (this.ChartItemItems[i].ItemDataRectF[j].Contains(e.Location)) { this.SelectItemLine = new ItemLine() { ItemIndex = i, LineIndex = j }; this.ChartItemMoveStatus = ChartItemMoveStatuss.Enter; this.Invalidate(); return; } } } if (this.ChartItemMoveStatus != ChartItemMoveStatuss.Normal) { this.ChartItemMoveStatus = ChartItemMoveStatuss.Normal; this.Invalidate(); } } } protected override void OnMouseLeave(EventArgs e) { base.OnMouseLeave(e); if (this.ChartItemMoveStatus != ChartItemMoveStatuss.Normal) { this.ChartItemMoveStatus = ChartItemMoveStatuss.Normal; this.Invalidate(); } } protected override void Dispose(bool disposing) { if (disposing && (components != null)) { components.Dispose(); if (this.animationTimer != null) this.animationTimer.Dispose(); } base.Dispose(disposing); } #endregion #region 公开 /// <summary> /// 动画更新数据 /// </summary> /// <param name="AnimationData"></param> public void AnimationChange(List<ChartAnimationItem> AnimationData) { if (AnimationData.Count != this.ChartItemItems.Count) return; bool ischange = false; for (int i = 0; i < this.ChartItemItems.Count; i++) { for (int j = 0; j < this.ChartItemItems[i].ItemDataCurrent.Length; j++) { if (this.ChartItemItems[i].ItemDataCurrent[j] != AnimationData[i].ItemData[j]) { ischange = true; break; } } } if (ischange) { if (this.animationTimer == null) { this.CreateAnimation(); this.isAnimation = true; } this.animationTimer.Stop(); for (int i = 0; i < this.ChartItemItems.Count; i++) { this.ChartItemItems[i].ItemDataBefore = this.ChartItemItems[i].ItemDataCurrent; this.ChartItemItems[i].ItemDataTargrt = AnimationData[i].ItemData; } this.animationTimer.Start(true, 0); } } /// <summary> /// 绑定数据后更新 /// </summary> public void DataBind() { for (int i = 0; i < this.ChartItemItems.Count; i++) { this.ChartItemItems[i].ItemDataBefore = this.ChartItemItems[i].ItemDataCurrent; this.ChartItemItems[i].ItemDataTargrt = this.ChartItemItems[i].ItemDataCurrent; this.InitializeRectangle(); this.Invalidate(); } } #endregion #region 私有 /// <summary> /// 初始化环形 /// </summary> private void InitializeLoop() { this.ChartLoopItems.Clear(); for (int i = 0; i < this.LoopCount; i++) { this.ChartLoopItems.Add(new ChartLoop()); } } /// <summary> /// 初始化控件布局 /// </summary> public void InitializeRectangle() { if (this.ChartLineItems.Count < 3) return; float now_radius = 0f; float now_angle = this.DefaultAngle; Rectangle rect = new Rectangle(this.ClientRectangle.X + this.Padding.Left, this.ClientRectangle.Y + this.Padding.Top, this.ClientRectangle.Width - this.Padding.Left - this.Padding.Right, this.ClientRectangle.Height - this.Padding.Top - this.Padding.Bottom); Rectangle option_rect = new Rectangle(); int ptionarea_width = !this.OptionAreaShow ? 0 : this.OptionAreaWidth; #region Rect if (DescriptionAnchor == DescriptionAnchors.Top) { this.ChartMainRect.DescriptionRect = new RectangleF(rect.X, rect.Y, rect.Width, this.DescriptionAreaHeight); if (this.ChartAnchor == ChartAnchors.Left) { this.ChartMainRect.OptionRect = !this.OptionAreaShow ? option_rect : new RectangleF(rect.Right - ptionarea_width, rect.Y + this.DescriptionAreaHeight, ptionarea_width, rect.Height - this.DescriptionAreaHeight); this.ChartMainRect.ChartRect = new RectangleF(rect.X + (rect.Width - ptionarea_width - this.ChartSize.Width) / 2, rect.Y + this.DescriptionAreaHeight + (rect.Height - this.ChartSize.Height) / 2, this.ChartSize.Width, this.ChartSize.Height); } else { this.ChartMainRect.OptionRect = !this.OptionAreaShow ? option_rect : new RectangleF(rect.X, rect.Y + this.DescriptionAreaHeight, ptionarea_width, rect.Height - this.DescriptionAreaHeight); this.ChartMainRect.ChartRect = new RectangleF(rect.X + ptionarea_width + (rect.Width - ptionarea_width - this.ChartSize.Width) / 2, rect.Y + this.DescriptionAreaHeight + (rect.Height - this.ChartSize.Height) / 2, this.ChartSize.Width, this.ChartSize.Height); } } else { this.ChartMainRect.DescriptionRect = new RectangleF(rect.X, rect.Bottom - this.DescriptionAreaHeight, rect.Width, this.DescriptionAreaHeight); if (this.ChartAnchor == ChartAnchors.Left) { this.ChartMainRect.OptionRect = !this.OptionAreaShow ? option_rect : new RectangleF(rect.Right - ptionarea_width, rect.Y, ptionarea_width, rect.Height - this.DescriptionAreaHeight); this.ChartMainRect.ChartRect = new RectangleF(rect.X + (rect.Width - ptionarea_width - this.ChartSize.Width) / 2, rect.Y + (rect.Height - this.ChartSize.Height) / 2, this.ChartSize.Width, this.ChartSize.Height); } else { this.ChartMainRect.OptionRect = !this.OptionAreaShow ? option_rect : new RectangleF(rect.X, rect.Y, ptionarea_width, rect.Height - this.DescriptionAreaHeight); this.ChartMainRect.ChartRect = new RectangleF(rect.X + ptionarea_width + (rect.Width - ptionarea_width - this.ChartSize.Width) / 2, rect.Y + (rect.Height - this.ChartSize.Height) / 2, this.ChartSize.Width, this.ChartSize.Height); } } #endregion PointF chart_center = new PointF(this.ChartMainRect.ChartRect.X + this.ChartMainRect.ChartRect.Width / 2, this.ChartMainRect.ChartRect.Y + this.ChartMainRect.ChartRect.Height / 2); float chart_radius = Math.Min(this.ChartMainRect.ChartRect.Width, this.ChartMainRect.ChartRect.Height) / 2; #region 圆环 for (int i = 0; i < this.ChartLoopItems.Count; i++) { this.ChartLoopItems[i].DataPoints = new PointF[this.ChartLineItems.Count]; now_angle = this.DefaultAngle; now_radius = chart_radius / (this.ChartLoopItems.Count) * (i + 1); this.ChartLoopItems[i].LoopCenter = chart_center; this.ChartLoopItems[i].LoopRadius = now_radius; for (int j = 0; j < this.ChartLineItems.Count; j++) { now_angle = this.DefaultAngle + 360f / this.ChartLineItems.Count * j; if (now_angle > 360) now_angle -= 360; this.ChartLoopItems[i].DataPoints[j] = CalculatePointForAngle(chart_center, now_radius, now_angle); } } #endregion #region 角度线 now_angle = this.DefaultAngle; now_radius = chart_radius / (this.ChartLoopItems.Count) * this.ChartLoopItems.Count; for (int j = 0; j < this.ChartLineItems.Count; j++) { now_angle = this.DefaultAngle + 360f / this.ChartLineItems.Count * j; if (now_angle > 360) now_angle -= 360; this.ChartLineItems[j].LoopCenter = chart_center; this.ChartLineItems[j].LoopRadius = now_radius; this.ChartLineItems[j].LineAngle = now_angle; this.ChartLineItems[j].LineEndPoint = CalculatePointForAngle(chart_center, now_radius, now_angle); } #endregion #region 数据 for (int i = 0; i < this.ChartItemItems.Count; i++) { this.ChartItemItems[i].ItemDataPoints = new PointF[this.ChartLineItems.Count]; this.ChartItemItems[i].ItemDataRectF = new RectangleF[this.ChartLineItems.Count]; float sum = this.ChartLoopItems.Count * this.LoopInterval; int datalen = this.ChartItemItems[i].ItemDataCurrent.Count(); int linelen = this.ChartLineItems.Count; for (int j = 0; j < linelen; j++) { PointF pointf = chart_center; if (j < datalen) { pointf = CalculatePointForAngle(chart_center, this.ChartLineItems[j].LoopRadius * this.ChartItemItems[i].ItemDataCurrent[j] / sum, this.ChartLineItems[j].LineAngle); } this.ChartItemItems[i].ItemDataPoints[j] = pointf; this.ChartItemItems[i].ItemDataRectF[j] = new RectangleF(this.ChartItemItems[i].ItemDataPoints[j].X - 2, this.ChartItemItems[i].ItemDataPoints[j].Y - 2, 4, 4); } } #endregion } /// <summary> /// 计算角度 /// </summary> /// <param name="center">圆心坐标</param> /// <param name="radius">圆半径</param> /// <param name="angle">角度</param> /// <returns></returns> private static PointF CalculatePointForAngle(PointF center, float radius, float angle) { if (radius == 0) return center; float w = 0; float h = 0; if (angle <= 90) { w = radius * (float)Math.Cos(Math.PI / 180 * angle); h = radius * (float)Math.Sin(Math.PI / 180 * angle); } else if (angle <= 180) { w = -radius * (float)Math.Sin(Math.PI / 180 * (angle - 90)); h = radius * (float)Math.Cos(Math.PI / 180 * (angle - 90)); } else if (angle <= 270) { w = -radius * (float)Math.Cos(Math.PI / 180 * (angle - 180)); h = -radius * (float)Math.Sin(Math.PI / 180 * (angle - 180)); } else { w = radius * (float)Math.Sin(Math.PI / 180 * (angle - 270)); h = -radius * (float)Math.Cos(Math.PI / 180 * (angle - 270)); } return new PointF(center.X + w, center.Y + h); } /// <summary> /// 创建动画对象 /// </summary> private void CreateAnimation() { this.animationTimer = new AnimationTimer(this, new AnimationOptions()); this.animationTimer.AT = AnimationType.EaseBoth; this.animationTimer.Interval = 50; this.animationTimer.Options.AllTime = 500; this.animationTimer.Options.ForEverTimer = false; this.animationTimer.AnimationIng += new AnimationTimer.AnimationHandel(this.animationTimer_AnimationIng); } /// <summary> /// 动画进行中 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> protected void animationTimer_AnimationIng(object sender, AnimationEventArgs e) { #region 数据 PointF chart_center = new PointF(this.ChartMainRect.ChartRect.X + this.ChartMainRect.ChartRect.Width / 2, this.ChartMainRect.ChartRect.Y + this.ChartMainRect.ChartRect.Height / 2); for (int i = 0; i < this.ChartItemItems.Count; i++) { this.ChartItemItems[i].ItemDataPoints = new PointF[this.ChartLineItems.Count]; this.ChartItemItems[i].ItemDataRectF = new RectangleF[this.ChartLineItems.Count]; float sum = this.ChartLoopItems.Count * this.LoopInterval; int datalen = this.ChartItemItems[i].ItemDataCurrent.Count(); int linelen = this.ChartLineItems.Count; for (int j = 0; j < linelen; j++) { float value = (float)(this.ChartItemItems[i].ItemDataCurrent[j] + (this.ChartItemItems[i].ItemDataTargrt[j] - this.ChartItemItems[i].ItemDataBefore[j]) * e.progressTime); PointF pointf = chart_center; if (j < datalen) { pointf = CalculatePointForAngle(chart_center, this.ChartLineItems[j].LoopRadius * value / sum, this.ChartLineItems[j].LineAngle); } this.ChartItemItems[i].ItemDataCurrent[j] = value; this.ChartItemItems[i].ItemDataPoints[j] = pointf; this.ChartItemItems[i].ItemDataRectF[j] = new RectangleF(this.ChartItemItems[i].ItemDataPoints[j].X - 2, this.ChartItemItems[i].ItemDataPoints[j].Y - 2, 4, 4); } } #endregion this.Invalidate(); } #endregion /// <summary> /// 分析图环形配置集合 /// </summary> [Description("分析图环形配置集合")] [Editor(typeof(CollectionEditorExt), typeof(UITypeEditor))] public sealed class ChartLoopCollection : IList, ICollection, IEnumerable { private ArrayList groupParamList = new ArrayList(); private RadarChartExt owner; public ChartLoopCollection(RadarChartExt owner) { this.owner = owner; } #region IEnumerable public IEnumerator GetEnumerator() { ChartLoop[] listArray = new ChartLoop[this.groupParamList.Count]; for (int index = 0; index < listArray.Length; ++index) listArray[index] = (ChartLoop)this.groupParamList[index]; return listArray.GetEnumerator(); } #endregion #region ICollection public void CopyTo(Array array, int index) { for (int i = 0; i < this.Count; i++) array.SetValue(this.groupParamList[i], i + index); } public int Count { get { return this.groupParamList.Count; } } public bool IsSynchronized { get { return false; } } public object SyncRoot { get { return (object)this; } } #endregion #region IList public int Add(ChartLoop value) { this.groupParamList.Add(value); this.owner.InitializeRectangle(); this.owner.Invalidate(); return this.Count - 1; } public int Add(object value) { ChartLoop groupParam = (ChartLoop)value; this.groupParamList.Add(groupParam); this.owner.InitializeRectangle(); this.owner.Invalidate(); return this.Count - 1; } public void Clear() { this.groupParamList.Clear(); this.owner.InitializeRectangle(); this.owner.Invalidate(); } public bool Contains(object value) { return this.IndexOf(value) != -1; } public int IndexOf(object value) { return this.groupParamList.IndexOf(value); } public void Insert(int index, object value) { throw new NotImplementedException(); } public bool IsFixedSize { get { return false; } } public bool IsReadOnly { get { return false; } } public void Remove(object value) { if (!(value is ChartLoop)) return; this.groupParamList.Remove((ChartLoop)value); this.owner.InitializeRectangle(); this.owner.Invalidate(); } public void RemoveAt(int index) { this.groupParamList.RemoveAt(index); this.owner.InitializeRectangle(); this.owner.Invalidate(); } public ChartLoop this[int index] { get { if (index >= this.Count) return null; return (ChartLoop)this.groupParamList[index]; } set { this.groupParamList[index] = (ChartLoop)value; this.owner.InitializeRectangle(); this.owner.Invalidate(); } } object IList.this[int index] { get { return (object)this.groupParamList[index]; } set { this.groupParamList[index] = (ChartLoop)value; this.owner.InitializeRectangle(); this.owner.Invalidate(); } } #endregion } /// <summary> /// 分析图角度线配置集合 /// </summary> [Description("分析图角度线配置集合")] [Editor(typeof(CollectionEditorExt), typeof(UITypeEditor))] public sealed class ChartLineCollection : IList, ICollection, IEnumerable { private ArrayList groupParamList = new ArrayList(); private RadarChartExt owner; public ChartLineCollection(RadarChartExt owner) { this.owner = owner; } #region IEnumerable public IEnumerator GetEnumerator() { ChartLine[] listArray = new ChartLine[this.groupParamList.Count]; for (int index = 0; index < listArray.Length; ++index) listArray[index] = (ChartLine)this.groupParamList[index]; return listArray.GetEnumerator(); } #endregion #region ICollection public void CopyTo(Array array, int index) { for (int i = 0; i < this.Count; i++) array.SetValue(this.groupParamList[i], i + index); } public int Count { get { return this.groupParamList.Count; } } public bool IsSynchronized { get { return false; } } public object SyncRoot { get { return (object)this; } } #endregion #region IList public int Add(ChartLine value) { this.groupParamList.Add(value); this.owner.InitializeRectangle(); this.owner.Invalidate(); return this.Count - 1; } public int Add(object value) { ChartLine groupParam = (ChartLine)value; this.groupParamList.Add(groupParam); this.owner.InitializeRectangle(); this.owner.Invalidate(); return this.Count - 1; } public void Clear() { this.groupParamList.Clear(); this.owner.InitializeRectangle(); this.owner.Invalidate(); } public bool Contains(object value) { return this.IndexOf(value) != -1; } public int IndexOf(object value) { return this.groupParamList.IndexOf(value); } public void Insert(int index, object value) { throw new NotImplementedException(); } public bool IsFixedSize { get { return false; } } public bool IsReadOnly { get { return false; } } public void Remove(object value) { if (!(value is ChartLine)) return; this.groupParamList.Remove((ChartLine)value); this.owner.InitializeRectangle(); this.owner.Invalidate(); } public void RemoveAt(int index) { this.groupParamList.RemoveAt(index); this.owner.InitializeRectangle(); this.owner.Invalidate(); } public ChartLine this[int index] { get { if (index >= this.Count) return null; return (ChartLine)this.groupParamList[index]; } set { this.groupParamList[index] = (ChartLine)value; this.owner.InitializeRectangle(); this.owner.Invalidate(); } } object IList.this[int index] { get { return (object)this.groupParamList[index]; } set { this.groupParamList[index] = (ChartLine)value; this.owner.InitializeRectangle(); this.owner.Invalidate(); } } #endregion } /// <summary> /// 分析图选项数据集合 /// </summary> [Description("分析图选项数据集合")] [Editor(typeof(CollectionEditorExt), typeof(UITypeEditor))] public sealed class ChartItemCollection : IList, ICollection, IEnumerable { private ArrayList groupParamList = new ArrayList(); private RadarChartExt owner; public ChartItemCollection(RadarChartExt owner) { this.owner = owner; } #region IEnumerable public IEnumerator GetEnumerator() { ChartItem[] listArray = new ChartItem[this.groupParamList.Count]; for (int index = 0; index < listArray.Length; ++index) listArray[index] = (ChartItem)this.groupParamList[index]; return listArray.GetEnumerator(); } #endregion #region ICollection public void CopyTo(Array array, int index) { for (int i = 0; i < this.Count; i++) array.SetValue(this.groupParamList[i], i + index); } public int Count { get { return this.groupParamList.Count; } } public bool IsSynchronized { get { return false; } } public object SyncRoot { get { return (object)this; } } #endregion #region IList public int Add(ChartItem value) { this.groupParamList.Add(value); return this.Count - 1; } public int Add(object value) { ChartItem groupParam = (ChartItem)value; this.groupParamList.Add(groupParam); return this.Count - 1; } public void Clear() { this.groupParamList.Clear(); } public bool Contains(object value) { return this.IndexOf(value) != -1; } public int IndexOf(object value) { return this.groupParamList.IndexOf(value); } public void Insert(int index, object value) { throw new NotImplementedException(); } public bool IsFixedSize { get { return false; } } public bool IsReadOnly { get { return false; } } public void Remove(object value) { if (!(value is ChartItem)) return; this.groupParamList.Remove((ChartItem)value); } public void RemoveAt(int index) { this.groupParamList.RemoveAt(index); } public ChartItem this[int index] { get { return (ChartItem)this.groupParamList[index]; } set { this.groupParamList[index] = (ChartItem)value; } } object IList.this[int index] { get { return (object)this.groupParamList[index]; } set { this.groupParamList[index] = (ChartItem)value; } } #endregion } }
源码下载:雷达分析图控件.zip