雷达图(Radar Chart),又可称为戴布拉图、蜘蛛网图(Spider Chart),是财务分析报表的一种。即将一个公司的各项财务分析所得的数字或比率,就其比较重要的项目集中划在一个圆形的图表上,来表现一个公司各项财务比率的情况,使用者能一目了然的了解公司各项财务指标的变动情形及其好坏趋向。到了今天,在很多互联网的产品在做个体多方面数据的分析时也经常会用到雷达图。说了这么多,其实这玩意儿长成这个样的:
用这种图显示数据的好处呢就是可以很直观地看出被分析的个体各个方面的情况,如果蓝色线围成的多边形的面积大,也可以从侧面反映该个体的整体水平。
除了财务报表上经常用到雷达图,在现在很多互联网产品也使用了雷达图,举个栗子——max+(我真的没有给max+打广告,如果各位认为我在打广告,max+你快给我广告费→_→),他里面有个地方用到了
这个id不是我的!
这个id不是我的!
这个id不是我的!
好了,说了那么多,还是回到正题吧。
先分析一下这个雷达图的内容——主题是一个蜘蛛网,蜘蛛网的六个顶点旁边有文字说明和数值,最后还有一个是蜘蛛网上面的真实数据。
先看一下这几部分应该怎么做?
我们在实现自定义View的时候,如果Android框架中已经有功能相似的可以直接继承已存在的view,这样可以简化工作量,但是这个雷达图是六边形的,顶点旁边的文字虽然可以用TextView来组合,但是官方并没有给我们提供六边形的控件,所以我们需要重新画一个。
中间的蜘蛛网我们可以通过旋转画布6次,然后画一组线,而中间的那个数据填充区域可以使用路径(Path)来完成,顶点出的文字使用TextPaint来进行绘制。
创建一个RadarView继承于android.view.View,实现一个参数和两个参数的构造器,重写onDraw方法,下面是RadarView的全部代码:
public class RadarView extends View {
private Paint mPaint = new Paint();
private TextPaint tPaint = new TextPaint();
private float[] mData = new float[]{0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f};
//外边颜色
private final int OUT_BORDER_COLOR = Color.parseColor("#919AA4");
//内边颜色
private final int IN_BORDER_COLOR = Color.parseColor("#E0E0E0");
//数字文字颜色
private final int TEXT_NUMBER_COLOR = Color.parseColor("#647D91");
//汉字文字颜色
private final int TEXT_COLOR = Color.parseColor("#3B454E");
//填充颜色
private final int FILL_COLOR = Color.parseColor("#CED6DC");
//文字与图的间距
private final int SPACE = 18;
public RadarView(Context context) {
this(context, null);
}
public RadarView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
mPaint.setStyle(Paint.Style.STROKE);
tPaint.setTextSize(40f);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int maxRound = (int) (Math.min(getWidth(), getHeight()) / 2 * 0.75);
//绘制蜘蛛网
canvas.save();
canvas.translate(getWidth() / 2, getHeight() / 2);
for(int i = 0; i < 6; i++) {
mPaint.setColor(IN_BORDER_COLOR);
mPaint.setStrokeWidth(2f);
canvas.drawLine(0, 0, maxRound, 0, mPaint);
for(int j = 3; j >= 1; j--) {
canvas.drawLine((float)(maxRound * j / 4.0), 0, (float)(maxRound / 2.0 * j / 4.0), (float)(maxRound * Math.sqrt(3) / 2 * j / 4.0), mPaint);
}
mPaint.setColor(OUT_BORDER_COLOR);
mPaint.setStrokeWidth(4f);
canvas.drawLine(maxRound, 0, maxRound/2, (float) (-Math.sqrt(3)/2*maxRound-0.5), mPaint);
canvas.rotate(60);
}
canvas.restore();
//绘制文字
canvas.save();
canvas.translate(getWidth() / 2, getHeight() / 2);
tPaint.setColor(TEXT_COLOR);
canvas.drawText("发育", maxRound + SPACE, 0, tPaint);
tPaint.setColor(TEXT_NUMBER_COLOR);
canvas.drawText(mData[0] + "", maxRound + SPACE, tPaint.getTextSize() + SPACE / 4, tPaint);
tPaint.setColor(TEXT_COLOR);
canvas.drawText("推进", maxRound / 2 + SPACE, (float) (maxRound * Math.sqrt(3) / 2), tPaint);
tPaint.setColor(TEXT_NUMBER_COLOR);
canvas.drawText(mData[1] + "", maxRound / 2 + SPACE, (float) (maxRound * Math.sqrt(3) / 2 + tPaint.getTextSize() + SPACE / 4), tPaint);
tPaint.setColor(TEXT_COLOR);
canvas.drawText("生存", -maxRound / 2 - SPACE - tPaint.getTextSize() * 2, (float) (maxRound * Math.sqrt(3) / 2), tPaint);
tPaint.setColor(TEXT_NUMBER_COLOR);
canvas.drawText(mData[2] + "", -maxRound / 2 - SPACE - tPaint.getTextSize() * 2, (float) (maxRound * Math.sqrt(3) / 2 + tPaint.getTextSize() + SPACE / 4), tPaint);
tPaint.setColor(TEXT_COLOR);
canvas.drawText("输出", -maxRound - SPACE - tPaint.getTextSize() * 2, 0, tPaint);
tPaint.setColor(TEXT_NUMBER_COLOR);
canvas.drawText(mData[3] + "", -maxRound - SPACE - tPaint.getTextSize() * 2, tPaint.getTextSize() + SPACE / 4, tPaint);
tPaint.setColor(TEXT_COLOR);
canvas.drawText("综合", -maxRound / 2 - SPACE * 2 - tPaint.getTextSize() * 2, -(float) (maxRound * Math.sqrt(3) / 2), tPaint);
tPaint.setColor(TEXT_NUMBER_COLOR);
canvas.drawText(mData[4] + "", -maxRound / 2 - SPACE * 2 - tPaint.getTextSize() * 2, -(float) (maxRound * Math.sqrt(3) / 2 - tPaint.getTextSize() + SPACE / 4), tPaint);
tPaint.setColor(TEXT_COLOR);
canvas.drawText("KDA", maxRound / 2 + SPACE * 2, -(float) (maxRound * Math.sqrt(3) / 2), tPaint);
tPaint.setColor(TEXT_NUMBER_COLOR);
canvas.drawText(mData[5] + "", maxRound / 2 + SPACE * 2, -(float) (maxRound * Math.sqrt(3) / 2 - tPaint.getTextSize() + SPACE / 4), tPaint);
canvas.restore();
//绘制内容区域
canvas.save();
canvas.translate(getWidth() / 2, getHeight() / 2);
Paint paint = new Paint();
paint.setColor(FILL_COLOR);
paint.setAlpha(0x88);
paint.setStyle(Paint.Style.FILL);
Path path = new Path();
path.moveTo(mData[0] / 100 * maxRound, 0f);
path.lineTo(mData[1] / 100 / 2 * maxRound, (float) (mData[1] / 100 * Math.sqrt(3) / 2 * maxRound));
path.lineTo(-mData[2] / 100 / 2 * maxRound, (float) (mData[2] / 100 * Math.sqrt(3) / 2 * maxRound));
path.lineTo(-mData[3] / 100 * maxRound, 0f);
path.lineTo(-mData[4] / 100 / 2 * maxRound, (float) (-mData[4] / 100 * Math.sqrt(3) / 2 * maxRound));
path.lineTo(mData[5] / 100 / 2 * maxRound, (float) (-mData[5] / 100 * Math.sqrt(3) / 2 * maxRound));
path.close();
canvas.drawPath(path, paint);
canvas.restore();
}
//设置数据,需要在ui线程中调用
public void setData(float[] data) {
if(6 != data.length) {
return;
}
this.mData = data;
invalidate();
}
}
说明一下,因为这个View我是要尽量仿照max+来做的,所以各个地方的颜色包括文字的小大等我都按照max+来做了,如果有需要让用户自己修改,把可变的属性提取到一个attrs.xml中,比如雷达图的边数(该例子是6),各个地方的颜色,大小等等,然后在构造方法中获取这些属性,并且动态去设置颜色,旋转角度等等。这里就不给出了。
另外还要给用户提供一些设置属性的方法,让用户可以在代码中设置属性的值,记得设置好后调用invalidate()方法重新绘制。
最后给出两张测试的图:
以上就是本次雷达图的实现。
其实在图二中的下面还有一张是折线统计图(我没截出来),下次(ruguo youkong)我会继续给大家带来折线图的绘制,原理和雷达图类似,不过(ruguo youkong)我会加一些可变的属性让控件更加自由定制。
祝各位中秋节快乐!