通过svg/canvas path绘制饼图(代码为c#,逻辑平台无关)

path参数

  1. M x,y 将起点移动到指定位置
  2. L x,y 从起点绘制直线到目标位置
  3. A 20,20 0 1 0 0,1 起点 起点x,起点y 画椭圆 长轴,短轴 旋转角度 是否是优弧 正角方向绘制 终点x,终点y
  4. z 绘制闭合路径
    TIP:对应的参数均有对应的小写表示,如m,l,a
    使用小写字母的参数坐标对应起点位置的相对坐标,
    大小字母表示绝对坐标

绘图思路

  1. 每一次移动起点到圆心
  2. 绘制对应的扇形的从圆心到圆上的某条边(长度为圆的半径)
  3. 绘制对应的圆弧
  4. 闭合路径

需要解决的问题

  1. 计算对应的圆弧的终点(扇形的半径为圆的半径,起点为直线的终点)
  2. 将终点对应绘图的实际坐标(空间转换)

解决思路

  1. 使用三角函数根据弧度(根据半分比通过2PI计算)、半径(已知)计算对应的x、y长度
  2. 空间转换根据实际绘图空间坐标转换
    (例左上角为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);
                }
            }
        }
    }
}
posted @ 2021-03-10 13:01  Hey,Coder!  阅读(341)  评论(0编辑  收藏  举报