path参数
- M x,y 将起点移动到指定位置
- L x,y 从起点绘制直线到目标位置
- A 20,20 0 1 0 0,1 起点 起点x,起点y 画椭圆 长轴,短轴 旋转角度 是否是优弧 正角方向绘制 终点x,终点y
- z 绘制闭合路径
TIP:对应的参数均有对应的小写表示,如m,l,a
使用小写字母的参数坐标对应起点位置的相对坐标,
大小字母表示绝对坐标
绘图思路
- 每一次移动起点到圆心
- 绘制对应的扇形的从圆心到圆上的某条边(长度为圆的半径)
- 绘制对应的圆弧
- 闭合路径
需要解决的问题
- 计算对应的圆弧的终点(扇形的半径为圆的半径,起点为直线的终点)
- 将终点对应绘图的实际坐标(空间转换)
解决思路
- 使用三角函数根据弧度(根据半分比通过2PI计算)、半径(已知)计算对应的x、y长度
- 空间转换根据实际绘图空间坐标转换
(例左上角为0,0,水平向右和水平向下对应x,y轴正方向,此时转换需要将x加上一半的宽度(将x移动到圆心),将y轴翻转再加上一半高度)
绘图逻辑代码
namespace Aixueshi.ExamManagement.UserControls.CommonControl
{
public class PieChartViewModel
{
/// <summary>
/// 绘制颜色
/// </summary>
//public Brush Brushes { get; set; }
/// <summary>
/// 百分比,范围[0-1]
/// </summary>
public double Percent { get; set; }
/// <summary>
/// 颜色
/// </summary>
public string Color { get; set; }
}
/// <summary>
/// 饼图
/// 设置上下文为List<PieChartViewModel>
/// 可通过设置所有percent之和小于1来绘制纯扇形
/// !!! 注意:需同时设置PieChart组件的容器Width、Height,否则会导致对应的宽度计算为NULL !!!
/// </summary>
public partial class PieChart : UserControl
{
double ChartWidth { get { return this.Width; } }
double ChartHeight { get { return this.Height; } }
double MidWidth
{
get
{
return ChartWidth / 2;
}
}
double MidHeight
{
get
{
return ChartHeight / 2;
}
}
//记录上一次绘图结束位置x
double lastX;
//记录上一次绘图结束位置y
double lastY;
double lastPercent = 0;
//List<PieChartViewModel> model { get; set; }
public PieChart()
{
InitializeComponent();
//DataContext = model;
}
private void Clear()
{
canvas.Children.Clear();
lastX = ChartWidth;
lastY = MidHeight;
lastPercent = 0;
}
private StringBuilder MoveMid(StringBuilder str)
{
return str.Append($"M {MidWidth} {MidHeight} ");
}
private StringBuilder AddLine(StringBuilder str)
{
return str.Append($"L {lastX} {lastY} ");
}
private void AddArc(StringBuilder str, double percent)
{
double x = MidWidth, y = MidHeight;
if (lastPercent >= 0 && lastPercent < 1)
{
//绘制整圆
if (lastPercent == 0 && lastPercent + percent >= 1)
{
str.Append($"A {MidWidth},{MidHeight} 0 0 0 {0}, {y} z ");
MoveMid(str);
str.Append($"L {0} {y} ");
str.Append($"A {MidWidth},{MidHeight} 0 0 0 {ChartWidth}, {y} z ");
return;
}
lastPercent += percent;
if (lastPercent > 1)
{
lastPercent = 1;
}
var arc = lastPercent * (2 * Math.PI);
var reletiveX = Math.Round(Math.Cos(arc) * MidWidth);
var reletiveY = -Math.Round(Math.Sin(arc) * MidWidth);
x += reletiveX;
y += reletiveY;
str.Append($"A {MidWidth},{MidHeight} 0 0 0 {x}, {y} z ");
//更新坐标
lastX = x;
lastY = y;
}
else
{
if (lastPercent < 0)
{
}
else
{
}
}
}
public void Add(double percent, string color)
{
var data = new StringBuilder();
MoveMid(data);
AddLine(data);
AddArc(data, percent);
var path = new Path();
path.Fill = new SolidColorBrush((Color)ColorConverter.ConvertFromString(color));
path.Data = Geometry.Parse(data.ToString());
canvas.Children.Add(path);
}
int index = 0;
SolidColorBrush randomBrush()
{
if (index > 3)
{
index = 0;
}
index++;
switch (index)
{
case 1:
return new SolidColorBrush((Color)ColorConverter.ConvertFromString("#34E1B6"));
case 2:
return new SolidColorBrush((Color)ColorConverter.ConvertFromString("#FB7468"));
case 3:
return Brushes.Gray;
default:
break;
}
return Brushes.Black;
}
private void pieChart_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
Clear();
if (this.DataContext is List<PieChartViewModel> data)
{
foreach (var item in data)
{
Add(item.Percent, item.Color);
}
}
}
}
}