zoukankan      html  css  js  c++  java
  • 六边形战士—雷达图实现

        相信大家都看了之前的新闻,世乒赛日本直播版,中二爆表,马龙的六边形战力图全满。

      图是这样的。

          

        于是乎想实现一个自定义view实现类似的效果。 这种图正式名称叫雷达图(Radar Chart),又可称为戴布拉图、蜘蛛网图(Spider Chart),是财务分析  报表的一种。但是现在已经应用到很多领域,特别是竞技体育方面对队伍或者选手的实力分析。

      整理了一下思路和查询了一下相关知识,结合前人的代码,实现了自定义雷达图。

      下面写一下实现思路:首先我把雷达图分为底层蜘蛛网+内容区,底层蜘蛛网的六个属性和内容区的六个点分别从2个数组去获取数值,接来下只要依次绘制两层图即可。

        1.初始化

      

     private int count=6;  //六边形,数据个数6
        private float angle= (float) (Math.PI/3);  //60度
        private double[] data={50,50,50,50,50,50,50}; //默认数据
        private float maxValue=100;     //默认最大值
        private String[] titles={"a","b","c","d","e","f"};  //默认标题
    
        private Paint radarPaint;                //蜘蛛网画笔
        private  Paint valuePaint;               //内容区画笔
        private Paint textPaint;                 //文字画笔
    
        private float radius;                   //网格最大半径
        private int centerX;                  //中心X
        private int centerY;                  //中心Y
    
    
        public MyRadar(Context context) {
            this(context,null);
        }
        public MyRadar(Context context, AttributeSet attrs) {
            this(context, attrs,0);
        }
    
        public MyRadar(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            init();
        }
        private void init() {
    
        radarPaint=newPaint();
    
        radarPaint.setAntiAlias(true);
    
        radarPaint.setColor(Color.GRAY);
    
        radarPaint.setStyle(Paint.Style.STROKE);
    
        valuePaint=newPaint();
    
        valuePaint.setAntiAlias(true);
    
        valuePaint.setColor(Color.BLUE);
    
        valuePaint.setStyle(Paint.Style.FILL_AND_STROKE);
    
        textPaint=newPaint();
    
        textPaint.setTextSize(sp2px(15));
    
        textPaint.setAntiAlias(true);
    
        textPaint.setStyle(Paint.Style.FILL);
    
        textPaint.setColor(Color.BLACK);
    
        }
        @Override
    
        protected voidonSizeChanged(intw,inth,intoldw,intoldh) {
    
        radius= Math.min(h, w)/2*0.65f;      //得到半径
    
        centerX= w/2;                          //得到中心点
    
        centerY= h/2;
    
        postInvalidate();
    
        super.onSizeChanged(w, h, oldw, oldh);
    
        }

      2,重点的绘图过程来了,第一步,绘制蜘蛛网图  ,绘图之前我们先复习下数学的知识。

      首先,一个正六边形是圆的内接正六边形,每个边对应的圆心角是六十度。

      其次,Android中View的坐标系是我们数学课是不一样的!(很容易被忽视)

      这里的1,2,3,4代表的是象限,因为y的方向不同,导致了象限与数学书中的不同

      

        

      首先,利用三角函数的知识绘制蜘蛛网图

      cosX对应映射在X轴上长度,sinX对应映射在Y轴上长度。所以可以通过每次X加上60度(1/3PI)去得到边角点。

      

     private void drawHexagon(Canvas canvas) {
            Path path=new Path();
            float r=radius/(count-1);
            for (int i = 0; i <count ; i++) {
    
                float R=r*i;
                path.reset();
                for (int j=0;j<count;j++){
                    if(j==0){
                        path.moveTo(centerX+R,centerY);
                    }
                    else{
                        float x= (float) (centerX+R*Math.cos(angle*j));
                        float y= (float) (centerY+R*Math.sin(angle*j));
                        path.lineTo(x,y);
                    }
                }
    
                path.close();
                canvas.drawPath(path,radarPaint);
    
            }
            for (int i = 0; i <count ; i++) {
                path.reset();
                path.moveTo(centerX,centerY);
                float x= (float) (centerX+radius*Math.cos(angle*i));
                float y= (float) (centerY+radius*Math.sin(angle*i));
                path.lineTo(x,y);
                canvas.drawPath(path,radarPaint);
            }
    
    }

       效果:

          

      接来下绘制标题,我们想要的效果是这样的  标题离边角有一定距离,且呈现对称效果。

      

       这时的解决方案是将以比半径稍大的长度作为新的半径,这样可以在六个角外面得到相应的六个点,再在这六个点处绘制标题。

       这里能否直接以六个点为坐标依次绘制文字? 答案是否定的。原因如下图:

        

        没错,绘制文字时是将坐标作为文字的左下角,如果不在不同的象限做出处理,文字将无法实现对称。如下:

        

        代码:

        

     private void drawText(Canvas canvas) {
    
            Paint.FontMetrics fontMetrics = textPaint.getFontMetrics();
            float fontHeight = fontMetrics.descent - fontMetrics.ascent;
            for (int i = 0; i <count ; i++) {
                float x= (float) (centerX+(radius+fontHeight/2)*Math.cos(angle*i));
                float y= (float) (centerY+(radius+fontHeight/2)*Math.sin(angle*i));
    
                if(angle*i>=0&&angle*i<=Math.PI/2){
                    canvas.drawText(titles[i], x,y+fontHeight/2,textPaint);
                } else if(angle*i>Math.PI/2&&angle*i<=Math.PI){
                    float dis = textPaint.measureText(titles[i]);
                    canvas.drawText(titles[i], x-dis,y+fontHeight/2,textPaint);
                }
                else if(angle*i>=Math.PI&&angle*i<3*Math.PI/2){
                    float dis = textPaint.measureText(titles[i]);
                    canvas.drawText(titles[i], x-dis,y,textPaint);
                }else if(angle*i>=3*Math.PI/2&&angle*i<=Math.PI*2){
                    canvas.drawText(titles[i], x,y,textPaint);
                }
    
    
            }
        }

        最后绘制内容区域也不难:

      

    private void drawRegion(Canvas canvas) {
            Path path = new Path();
            valuePaint.setAlpha(255);
            for(int i=0;i<count;i++){
                double percent = data[i]/maxValue;
                float x = (float) (centerX+radius*Math.cos(angle*i)*percent);
                float y = (float) (centerY+radius*Math.sin(angle*i)*percent);
                if(i==0){
                    path.moveTo(x, centerY);
                }else{
                    path.lineTo(x,y);
                }
                //绘制小圆点
                canvas.drawCircle(x,y,5,valuePaint);
            }
            valuePaint.setStyle(Paint.Style.STROKE);
            canvas.drawPath(path, valuePaint);
            valuePaint.setAlpha(127);
            //绘制填充区域
            valuePaint.setStyle(Paint.Style.FILL_AND_STROKE);
            canvas.drawPath(path, valuePaint);
        }

        完整的draw方法

      

     @Override
        protected void onDraw(Canvas canvas) {
            drawHexagon(canvas);
            drawText(canvas);
            drawRegion(canvas);
        }

       到这里差不多就结束了,后续就是对外暴露一些方法,以及wrapcontent设置默认大小

      

     //设置数值
        public void setData(double[] data) {
            this.data = data;
        }
    
    
        public float getMaxValue() {
            return maxValue;
        }
    
        //设置最大数值
        public void setMaxValue(float maxValue) {
            this.maxValue = maxValue;
        }
        //设置标题颜色
        public void setTextPaintColor(int color){
            textPaint.setColor(color);
        }
    
        //设置覆盖局域颜色
        public void setValuePaintColor(int color){
            valuePaint.setColor(color);
    
        }
        //设置雷达图颜色
        public void setMainPaintColor(int color){
            radarPaint.setColor(color);
        }

      

    @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    
            int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
            int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
            int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
            int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
    
            if(widthSpecMode==MeasureSpec.AT_MOST&&heightSpecMode==MeasureSpec.AT_MOST){
                setMeasuredDimension(sp2px(300),sp2px(300));
            }
            else if (widthMeasureSpec==MeasureSpec.AT_MOST){
                setMeasuredDimension(sp2px(250),heightSpecSize);
            }else if (heightSpecMode==MeasureSpec.AT_MOST){
                setMeasuredDimension(widthSpecSize,sp2px(250));
            }
        }

      

        最终在acitivty设置数据和标题,最终效果:

    public class MainActivity extends AppCompatActivity {
        private MyRadar mRadar;
        double[] data={100,100,100,100,50,100,20};
        String[] titles={"发球","经验","防守","技巧","速度","力量"};
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mRadar= (MyRadar) findViewById(R.id.radar);
    
            mRadar.setData(data);
            mRadar.setTitles(titles);
    
        }
    }

            

      源码在github:https://github.com/xurui1995/Radar

  • 相关阅读:
    OpenGL入门学习
    linux下安装sqlite3
    SQLite 之 C#版 System.Data.SQLite 使用
    .net程序运行流程
    一种简单,轻量,灵活的C#对象转Json对象的方案
    C# 获取Windows系统:Cpu使用率,内存使用率,Mac地址,磁盘使用率
    WPF中选择文件及文件夹
    要想创业成功,千万不能在这十个方面走弯路
    [译]Quartz.Net 框架 教程(中文版)2.2.x 之第三课 更多关于Jobs和JobDetails
    [译]Quartz 框架 教程(中文版)2.2.x 之第二课 Quartz API,Jobs和Triggers简介
  • 原文地址:https://www.cnblogs.com/xurui1995/p/5917218.html
Copyright © 2011-2022 走看看