雷達圖(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)我會加一些可變的屬性讓控件更加自由定製。
祝各位中秋節快樂!