Unity_雷达图(属性图)+ UI动画

GitHub项目链接点这里(求一个star⭐)

效果

雷达图

介绍

实现雷达图的大体思路:因为要根据不同数量的数据绘制图图形,所以和之前数据图表不同的是,需要自己对UGUI进行重绘。在这之前我没有了解过UGUI底层,所以参考了其他博主的博客(主要是这篇@MrASL(CSDN),这个功能是我在2019年8月份写的,现在是2020年4月份,参考的其他博客没有印象了,抱歉😬)。
绘制UGUI图形的主要思路,和Image组件一样,继承自MaskableGraphic类,当调用SetAllDirty()函数时,使Canvas重新绘制图形,内部调用了MaskableGraphic.OnPopulateMesh(VertexHelper vh)函数,VertexHelper是UI绘制的一个帮助类,绘制之前需要先调用vh.Clear()函数将原有的UI网格清空,然后调用AddUIVertexQuad(UIVertex[] verts)函数、AddVert(Vector3 position, Color32 color, Vector2 uv0)等函数添加绘制图形的顶点、颜色等数据,VertexHelper类里面的函数可以到Unity的官方文档查看使用,这里不做具体介绍了。
继续说雷达图的思路:首先根据一组数据的数量将一张平面圆形的360°平分(我在代码中注释为:坐标轴),得到坐标轴顶点(零点的另一端)的位置数据的描述文本,其次,要在坐标轴上显示数据从0-100(最小-最大)之间的等比刻度,雷达图是根据数据在相应轴的比例计算uv坐标,绘制三角形填充内部,绘线连接形成外框。最后是使用DOTween做的缩放动画。

解释

关键字

1. 对象池
2. DoTween 动画
3. Unity_UI重绘

分析及代码

  • 绘图数据

和之前一样,定义绘图数据的结构GraphData,包含数据的描述信息_desc,数据的值_value。在需要绘图时传递一个GraphData[]数组给此模块就可以实现图表的绘制。

[System.Serializable]
public struct GraphData
{
    public string _desc;
    [Range(0, 100)]
    public float _value;

    public float Rate
    {
        get
        {
            return _value / 100;
        }
    }

    public GraphData(string desc, float value)
    {
        _desc = desc;
        _value = value;
    }
}
  • 1.定义参数

下图是Unity的布局:RadarGraphPanel挂载UIRadarGraphManager组件用来绘制底图,Radar挂载UIRadarImage用来绘制数据图,DescContent是空对象用来管理所有描述文本,Desc是描述文本预制体。
根据↑上面的介绍:需要的参数有刻度数(假设0-100,刻度=5,每个刻度为0、25、50、75、100)…其他的参数都有注释,自己看吧。😁

结构

    public GraphData[] _datas;
    public int _rulingCount = 4;//刻度数
    public float _lineWidth = 1f;//背景线宽度
    public float _radarLineWidth = 1f;//雷达边框宽度
    public Color _lineColor = Color.gray;//背景线颜色
    public Color _radarLineColor = Color.blue;//雷达边框颜色
    public UIRadarImage _radarImage;//雷达图
    public Text _descPrefab;//描述Prefab
    public Transform _descContent;//描述Content
    public float _tweenTime = 1f;//动画事件

    private Vector2[] _vertexs;//顶点
    private float _radius;//半径
    private float _perRadian;//弧度
    private float _descSpace;//描述间隔
    private const string DESCPOOL = "RDescPool";
  • 2.绘制底图、生成文本描述

根据数量平分360°绘制每个数据的坐标轴,根据刻度数绘制刻度并连线,获得顶点位置生成文本描述。
绘制底图用到的是UI画线,一条线相当于一个很长的矩形,对于UI网格来说,相当于绘制两个三角形,共用四个顶点。

    /// <summary>
    /// 画坐标轴
    /// </summary>
    /// <param name="vh"></param>
    private void DrawAxis(VertexHelper vh)
    {
        GetVertexs();
        for (int i = 0; i < _vertexs.Length; i++)
        {
            vh.AddUIVertexQuad(GetQuad(Vector2.zero, _vertexs[i], _lineColor, _lineWidth));
        }
    }

    /// <summary>
    /// 画刻度
    /// </summary>
    private void DrawRuling(VertexHelper vh)
    {
        float perRadius = _radius / (_rulingCount - 1);//原点不需要画
        for (int i = 1; i < _rulingCount; i++)
        {
            for (int j = 0; j < _datas.Length; j++)
            {
                float startRadian = _perRadian * j + 90 * Mathf.Deg2Rad;
                float endRadian = _perRadian * (j + 1) + 90 * Mathf.Deg2Rad;
                Vector2 startPos = new Vector2(Mathf.Cos(startRadian), Mathf.Sin(startRadian)) * perRadius * i;
                Vector2 endPos = new Vector2(Mathf.Cos(endRadian), Mathf.Sin(endRadian)) * perRadius * i;
                UIVertex[] newVertexs = GetQuad(startPos, endPos, _lineColor, _lineWidth);
                vh.AddUIVertexQuad(newVertexs);
            }
        }
    }

    /// <summary>
    /// 描述
    /// </summary>
    private void DrawDesc()
    {
        GetVertexs();
        for (int i = 0; i < _vertexs.Length; i++)
        {
            Text desc = ObjectPool.Instance.GetObject(DESCPOOL, _descContent).GetComponent<Text>();
            desc.text = _datas[i]._desc;
            Vector2 pos = _vertexs[i];
            if (Mathf.Abs(pos.x) >= 0.1f)
                pos.x += _descSpace * (pos.x > 0 ? 1 : -1);
            if (Mathf.Abs(pos.y) >= 0.1f)
                pos.y += _descSpace * (pos.y > 0 ? 1 : -1);
            desc.rectTransform.localPosition = pos;
            desc.gameObject.SetActive(true);
        }
    }

    /// <summary>
    /// 获取顶点
    /// </summary>
    /// <returns></returns>
    private void GetVertexs()
    {
        _perRadian = Mathf.PI * 2 / _datas.Length;
        _vertexs = new Vector2[_datas.Length];
        for (int i = 0; i < _datas.Length; i++)
        {
            float radian = _perRadian * i + 90 * Mathf.Deg2Rad;
            Vector2 endPos = new Vector2(Mathf.Cos(radian), Mathf.Sin(radian)) * _radius;
            _vertexs[i] = endPos;
        }
    }

    /// <summary>
    /// 获取一条线的四个顶点
    /// </summary>
    /// <param name="startPos"></param>
    /// <param name="endPos"></param>
    /// <returns></returns>
    private UIVertex[] GetQuad(Vector2 startPos, Vector2 endPos, Color color, float width)
    {
        float dis = Vector2.Distance(startPos, endPos);
        float x = width / 2 * (endPos.y - startPos.y) / dis;//sin
        float y = width / 2 * (endPos.x - startPos.x) / dis;//cos
        if (y <= 0) y = -y;
        else x = -x;
        UIVertex[] vertex = new UIVertex[4];
        vertex[0].position = new Vector3(startPos.x + x, startPos.y + y);
        vertex[1].position = new Vector3(endPos.x + x, endPos.y + y);
        vertex[2].position = new Vector3(endPos.x - x, endPos.y - y);
        vertex[3].position = new Vector3(startPos.x - x, startPos.y - y);
        for (int i = 0; i < vertex.Length; i++)
            vertex[i].color = color;
        return vertex;
    }
  • 3.绘制雷达图

绘制雷达图是上面绘制底图的升级版:绘制三角面用于填充雷达图内部,外部仍然使用绘线连接图形。
以最简单的三边形雷达图为例,用到的是三个数据,在三条坐标轴上绘制三个点,依次两两组合与原点绘制三角形,总共三个。
数据在坐标轴上的位置,与绘制底图时相同,根据数据的index得到平面360°被平分的弧度坐标轴(三边形雷达图角度:0°,120°,240°),根据刻度比例计算得出。

    /// <summary>
    /// 画雷达图
    /// </summary>
    /// <param name="vh"></param>
    private void DrawRadar(VertexHelper vh)
    {
        int edgeCount = _datas.Length;//边数量
        //画雷达三角面
        for (int i = 0; i < edgeCount; i++)
        {
            DrawTriangle(vh, GetVertex(i), i);
        }
    }

    /// <summary>
    /// 画雷达图边框
    /// </summary>
    /// <param name="vh"></param>
    private void DrawLine(VertexHelper vh)
    {
        int edgeCount = _datas.Length;//边数量
        //画雷达三角面
        for (int i = 0; i < edgeCount; i++)
        {
            DrawLine(vh, GetVertex(i));
        }
    }

    /// <summary>
    /// 画三角面
    /// </summary>
    /// <param name="vh"></param>
    /// <param name="index"></param>
    /// <param name="deltaAngle"></param>
    private void DrawTriangle(VertexHelper vh, Vector3[] poses, int index)
    {
        Color color = _lineColor;
        color.a = 0.5f;

        vh.AddVert(Vector3.zero, color, Vector2.zero);//中心点
        vh.AddVert(poses[0], color, Vector2.zero);
        vh.AddVert(poses[1], color, Vector2.zero);//UI的法线可以随便设置
        vh.AddTriangle(index * 3, index * 3 + 1, index * 3 + 2);//将三角面加入UI绘制缓冲区。参数是三角面的三个顶点索引//所以绘制n边形需要绘制3n的顶点
    }

    /// <summary>
    /// 画线
    /// </summary>
    /// <param name="vh"></param>
    /// <param name="index"></param>
    private void DrawLine(VertexHelper vh, Vector3[] poses)
    {
        //画线
        UIVertex[] newVertexs = GetQuad(poses[0], poses[1], _lineColor, _lineWidth);
        vh.AddUIVertexQuad(newVertexs);
    }

    /// <summary>
    /// 获取一个弧度的两个顶点
    /// </summary>
    /// <param name="index"></param>
    /// <returns></returns>
    private Vector3[] GetVertex(int index)
    {
        int nextIndex = index + 1 >= _datas.Length ? 0 : index + 1;
        float radian1 = index * _perRadian + 90 * Mathf.Deg2Rad;
        float radian2 = nextIndex * _perRadian + 90 * Mathf.Deg2Rad;
        float radius1 = _datas[index].Rate * _radius;
        float radius2 = _datas[nextIndex].Rate * _radius;
        //两边顶点
        Vector3 p1 = new Vector3(radius1 * Mathf.Cos(radian1), radius1 * Mathf.Sin(radian1));
        Vector3 p2 = new Vector3(radius2 * Mathf.Cos(radian2), radius2 * Mathf.Sin(radian2));
        return new Vector3[] { p1, p2 };
    }

    /// <summary>
    /// 获取一条线的四个顶点
    /// </summary>
    /// <param name="startPos"></param>
    /// <param name="endPos"></param>
    /// <returns></returns>
    private UIVertex[] GetQuad(Vector2 startPos, Vector2 endPos, Color color, float width)
    {
        float dis = Vector2.Distance(startPos, endPos);
        float x = width / 2 * (endPos.y - startPos.y) / dis;//sin
        float y = width / 2 * (endPos.x - startPos.x) / dis;//cos
        if (y <= 0) y = -y;
        else x = -x;
        UIVertex[] vertex = new UIVertex[4];
        vertex[0].position = new Vector3(startPos.x + x, startPos.y + y);
        vertex[1].position = new Vector3(endPos.x + x, endPos.y + y);
        vertex[2].position = new Vector3(endPos.x - x, endPos.y - y);
        vertex[3].position = new Vector3(startPos.x - x, startPos.y - y);
        for (int i = 0; i < vertex.Length; i++)
            vertex[i].color = color;
        return vertex;
    }
  • 4.完整代码和使用示例

上面有GitHub地址,就不在这里占用太长的篇幅了。
emmm下面是一些无关紧要的话。
这篇博客的表述可能没那么清晰,我也是一边看去年的代码,一边想怎么解释,一句话删了,写了删,主要原因还是我表达能力太弱,啧,去年刚开始写博客也是为了能提高自己的表述能力,唉,加油!

  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值