标签:基本 als ini 占用 spro dep XML sharp 数值计算
1.前言:
一直在从事CS应用程序开发工作,随着工作需求,要对部分数据进行可视化展示,UI设计稿其中就有玫瑰图、雷达图的展示。
花了一个下午回溯原来丢掉的数学知识点。。特此将实现方法记录下。
2.效果图:

3.数据对象(RadarObj)
每个图都是由一个数据集合对象组成,从而绘制出对应的效果,对象最基本的属性要有某一维度的数值,用于在图像中展示。
public class RadarObj
{
public string RColor { get; set; }
public string Name { get; set; }
public int DataValue { get; set; }
public double DataRaidus { get; set; }
/// <summary>
/// Series stroke
/// </summary>
public Brush Stroke
{
get
{
return new SolidColorBrush((Color)ColorConverter.ConvertFromString(RColor)); ;
}
}
/// <summary>
/// Series Fill
/// </summary>
public Brush Fill
{
get
{
return new SolidColorBrush((Color)ColorConverter.ConvertFromString(RColor));
}
}
}
4.绘制玫瑰图
核心逻辑在于,将玫瑰图先理解为一个饼图,然后根据数值计算出在饼图中占用的角度,以及对应的扇面半径,改动每个扇面的半径就成了玫瑰图
其中需要使用到几何的一些基本概念,工作这么多年,忘记了蛮多的,后面各种恶补。直接上代码吧。
<UserControl x:Class="Painter.NightingaleRose" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:Painter" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> <Grid> <Canvas x:Name="CanvasPanel" HorizontalAlignment="Center" VerticalAlignment="Center" Background="Gray" > </Canvas> </Grid> </UserControl>
public partial class NightingaleRose : UserControl
{
public NightingaleRose()
{
InitializeComponent();
}
#region Property
/// <summary>
/// 数据
/// </summary>
public List<RadarObj> Datas
{
get { return (List<RadarObj>)GetValue(DatasProperty); }
set { SetValue(DatasProperty, value); }
}
/// <summary>
/// 数值的总数
/// </summary>
public int Count
{
get
{
return Datas.Sum(i => i.DataValue);
}
}
public static readonly DependencyProperty DatasProperty = DependencyProperty.Register("Datas", typeof(List<RadarObj>),
typeof(NightingaleRose), new PropertyMetadata(new List<RadarObj>()));
/// <summary>
/// 当前绘制大区域
/// </summary>
private double MaxSize
{
get
{
var par = this.Parent as FrameworkElement;
return par.ActualHeight > par.ActualWidth ? par.ActualWidth : par.ActualHeight;
}
}
/// <summary>
/// 停靠间距
/// </summary>
public int RoseMargin
{
get { return (int)GetValue(RoseMarginProperty); }
set { SetValue(RoseMarginProperty, value); }
}
public static readonly DependencyProperty RoseMarginProperty = DependencyProperty.Register("RoseMargin", typeof(int),
typeof(NightingaleRose), new PropertyMetadata(50));
/// <summary>
/// 空心内环半径
/// </summary>
public int RoseInsideMargin
{
get { return (int)GetValue(RoseInsideMarginProperty); }
set { SetValue(RoseInsideMarginProperty, value); }
}
public static readonly DependencyProperty RoseInsideMarginProperty = DependencyProperty.Register("RoseInsideMargin", typeof(int),
typeof(NightingaleRose), new PropertyMetadata(20));
/// <summary>
/// 显示值标注
/// </summary>
public bool ShowValuesLabel
{
get { return (bool)GetValue(ShowValuesLabelProperty); }
set { SetValue(ShowValuesLabelProperty, value); }
}
public static readonly DependencyProperty ShowValuesLabelProperty = DependencyProperty.Register("ShowValuesLabel", typeof(bool),
typeof(NightingaleRose), new PropertyMetadata(true));
public static readonly DependencyProperty ShowToolTipProperty = DependencyProperty.Register("ShowToolTip", typeof(bool),
typeof(NightingaleRose), new PropertyMetadata(false));
/// <summary>
/// 延伸线长
/// </summary>
public int LabelPathLength
{
get { return (int)GetValue(LabelPathLengthProperty); }
set { SetValue(LabelPathLengthProperty, value); }
}
public static readonly DependencyProperty LabelPathLengthProperty = DependencyProperty.Register("LabelPathLength", typeof(int),
typeof(NightingaleRose), new PropertyMetadata(50));
#endregion Property
#region Method
/// <summary>
/// 初始化数据
/// </summary>
private void initData()
{
CanvasPanel.Children.Clear();
if (this.Datas != null && this.Datas.Count > 0)
{
this.CanvasPanel.Width = this.CanvasPanel.Height = 0;
//求角度比例尺 (每个值占多大的角度 可以算到每一块图所占的角度)
var angelScale = 360.00 / Datas.Sum(i => i.DataValue);
//最大半径
var maxRadius = (MaxSize / 2) - RoseMargin - (ShowValuesLabel ? LabelPathLength : 0);
//半径比例尺 (值和比例尺相乘等于每一块图的半径)
var radiusScale = maxRadius / Datas.Max(o => o.DataValue);
//计算半径宽度值
for (int i = 0; i < Datas.Count; i++)
{
Datas[i].DataRaidus = Datas[i].DataValue * radiusScale;
}
//扇形角度初始化
double angleSectorStart = 0;
double angleSectorEnd = 0;
//循环绘制扇形区域
int scaleTimeSpan = 0;
int pathTimespan = 0;
int textTimeSpan = 0;
for (int index = 0; index < Datas.Count; index++)
{
//计算扇形角度
if (index == 0)
{
angleSectorStart = 0;
angleSectorEnd = Datas[index].DataValue * angelScale;
}
else if (index + 1 == Datas.Count)
{
angleSectorStart += Datas[index - 1].DataValue * angelScale;
angleSectorEnd = 360;
}
else
{
angleSectorStart += Datas[index - 1].DataValue * angelScale;
angleSectorEnd = angleSectorStart + Datas[index].DataValue * angelScale;
}
var currentRadius = RoseInsideMargin + Datas[index].DataRaidus;
//计算扇形点位,用于绘制PATH
Point ptOutSideStart = GetPoint(currentRadius, angleSectorStart * Math.PI / 180);
Point ptOutSideEnd = GetPoint(currentRadius, angleSectorEnd * Math.PI / 180);
Point ptInSideStart = GetPoint(RoseInsideMargin, angleSectorStart * Math.PI / 180);
Point ptInSideEnd = GetPoint(RoseInsideMargin, angleSectorEnd * Math.PI / 180);
if (string.IsNullOrEmpty(Datas[index].RColor) )
Datas[index].RColor = ChartColorPool.ColorStrings[index];
Path pthSector = new Path() { Fill = Datas[index].Fill };
//PATH数据格式 M0,100 L50,100 A50,50 0 0 1 100,50 L100,0 A100,100 0 0 0 0,100 Z
StringBuilder datastrb = new StringBuilder();
#region BuilderPathData
datastrb.Append("M");
datastrb.Append(ptOutSideStart.X.ToString());
datastrb.Append(",");
datastrb.Append(ptOutSideStart.Y.ToString());
datastrb.Append(" L");
datastrb.Append(ptInSideStart.X.ToString());
datastrb.Append(",");
datastrb.Append(ptInSideStart.Y.ToString());
datastrb.Append(" A");
datastrb.Append(RoseInsideMargin.ToString());
datastrb.Append(",");
datastrb.Append(RoseInsideMargin.ToString());
datastrb.Append(" 0 0 1 ");
datastrb.Append(ptInSideEnd.X.ToString());
datastrb.Append(",");
datastrb.Append(ptInSideEnd.Y.ToString());
datastrb.Append(" L");
datastrb.Append(ptOutSideEnd.X.ToString());
datastrb.Append(",");
datastrb.Append(ptOutSideEnd.Y.ToString());
datastrb.Append(" A");
datastrb.Append(currentRadius.ToString());
datastrb.Append(",");
datastrb.Append(currentRadius.ToString());
datastrb.Append(" 0 0 0 ");
datastrb.Append(ptOutSideStart.X.ToString());
datastrb.Append(",");
datastrb.Append(ptOutSideStart.Y.ToString());
datastrb.Append(" Z");
#endregion BuilderPathData
try
{
pthSector.Data = (Geometry)new GeometryConverter().ConvertFromString(datastrb.ToString());
}
catch (Exception exp)
{ }
//设置扇形显示的动画
AnimationUtils.FloatElement(pthSector,1, 200, pathTimespan += 200);
AnimationUtils.ScaleRotateEasingAnimationShow(pthSector,0.1,1,1500, scaleTimeSpan += 200,null );
CanvasPanel.Children.Add(pthSector);
if (ShowValuesLabel)
{
//计算延伸线角度
double lbPathAngle = angleSectorStart + (angleSectorEnd - angleSectorStart) / 2;
//起点
Point ptLbStart = GetPoint(currentRadius, lbPathAngle * Math.PI / 180);
//终点
Point ptLbEnd = GetPoint(maxRadius + LabelPathLength, lbPathAngle * Math.PI / 180);
Path pthLb = new Path() { Stroke = Datas[index].Stroke, StrokeThickness = 1 };
pthLb.Data = (Geometry)new GeometryConverter().ConvertFromString(string.Format("M{0},{1} {2},{3}", ptLbStart.X.ToString(), ptLbStart.Y.ToString(), ptLbEnd.X.ToString(), ptLbEnd.Y.ToString()));
double dur = (textTimeSpan += 200) + 1500;
AnimationUtils.CtrlDoubleAnimation(pthLb, 1000, dur);
CanvasPanel.Children.Add(pthLb);
SetLabel(Datas[index], ptLbEnd, dur);
}
}
this.SizeChanged -= RadarControl_SizeChanged;
this.SizeChanged += RadarControl_SizeChanged;
}
}
public void InitalControl()
{
}
/// <summary>
/// 初始化数据
/// </summary>
/// <param name="dataobj"></param>
public void SetData(object dataobj)
{
this.Datas = (dataobj) as List<RadarObj>;
this.initData();
}
private void RadarControl_SizeChanged(object sender, SizeChangedEventArgs e)
{
initData();
}
#endregion Method
#region Compare
/// <summary>
/// 计算点位
/// </summary>
/// <param name="radius"></param>
/// <param name="angel"></param>
/// <returns></returns>
private Point GetPoint(double radius, double angel)
{
return new Point(radius * Math.Cos(angel), radius * Math.Sin(angel));
}
private void SetLabel(RadarObj obj, Point location,double duration)
{
//计算偏移量
bool x = true;
bool y = true;
if (location.X < 0)
x = false;
if (location.Y < 0)
y = false;
//obj.Name + " " +
TextBlock txb = new TextBlock() { Text = " "+obj.Name+" "+Getbfb(Count.ToString(), obj.DataValue.ToString(), 2)+ " ", Foreground = this.Foreground, FontSize = this.FontSize };
Size s = ControlSizeUtils.GetTextAreaSize(txb.Text, this.FontSize);
CanvasPanel.Children.Add(txb);
AnimationUtils.CtrlDoubleAnimation(txb, 1000, duration);
if (location.X > -5 && location.X < 5)
Canvas.SetLeft(txb, location.X - (s.Width / 2));
else
Canvas.SetLeft(txb, location.X + (x ? 0 : -(s.Width)));
if (location.Y > -5 && location.Y < 5)
Canvas.SetTop(txb, location.Y - (s.Height / 2));
else
Canvas.SetTop(txb, location.Y + (y ? 0 : -(s.Height)));
}
/// <summary>
/// 计算百分比
/// </summary>
/// <param name="zs">总数</param>
/// <param name="tj">当前项的值</param>
/// <param name="num">保留的小数点几位</param>
/// <returns></returns>
public static string Getbfb(string zs, string tj, int num)
{
try
{
if (zs.Equals("0"))
{
return "0";
}
double bfb = (double.Parse(tj) / double.Parse(zs)) * 100;
if (bfb >= 100)
{
bfb = 100;
}
return Math.Round(bfb, num).ToString() + "%";
}
catch (Exception ex)
{
return "0%";
}
}
#endregion Compare
}
调用示例
在主窗体grid里面丢个按钮,按钮点击之后执行下列代码
private void RoseClick(object sender, RoutedEventArgs e)
{
NightingaleRose rdc = new NightingaleRose();
this.GrdMain.Children.Clear();
this.GrdMain.Children.Add(rdc);
rdc.SetData(CrData());
}
private List<RadarObj> CrData()
{
List<RadarObj> list = new List<RadarObj>();
list.Add(new RadarObj() { Name="A", DataValue= rdm.Next(20,100) });
list.Add(new RadarObj() { Name = "B", DataValue = rdm.Next(20, 100) });
list.Add(new RadarObj() { Name = "C", DataValue = rdm.Next(20, 100) });
list.Add(new RadarObj() { Name = "D", DataValue = rdm.Next(20, 100) });
list.Add(new RadarObj() { Name = "E", DataValue = rdm.Next(20, 100) });
list.Add(new RadarObj() { Name = "F", DataValue = rdm.Next(20, 100) });
list.Add(new RadarObj() { Name = "F", DataValue = rdm.Next(20, 100) });
return list;
}
标签:基本 als ini 占用 spro dep XML sharp 数值计算
原文地址:https://www.cnblogs.com/Funk/p/11405434.html