公司做金融的,之前項目用的MPAndroidChart來完成分時圖和K線圖圖表,不過之前的項目用的MPAndroidChart版本很老很老了,我也只好自己去嘗試最新版本的MPAndroidChart了,雖然我也是剛接觸MPAndroidChart不久,如果MPAndroidChart 之LineChart不懂基本屬性的設置也可以看看MPAndroidChart3.0之LineChart。
MPAndroidChart系列:
MPAndroidChart之LineChart(2)MarkerView
MPAndroidChart之LinChart(3)scale縮放
本文MPAndroidChart使用版本:(3的版本了)
compile 'com.github.PhilJay:MPAndroidChart:v3.0.2'
我沒有使用gradle依賴,而是把代碼拉下來依賴的,因爲:
1、在使用過程中發現了好多舊版本有的api,新版本的不知道丟哪裏去了;
2、另外挺多使用的問題,比如縮放,重新設置數據後錯亂問題....
但是還是值得使用的,因爲大部分的基本圖表它都能滿足,也靈活,https://github.com/PhilJay/MPAndroidChart上的星星說明了一切。
現在先看下下面使用MPAndroidChart 之LineChart實現的效果:
上面gif是實現的效果,下面截圖是我認爲使用LineChart畫折線圖要知道的步驟,我用1、2...數字標出來了。
gif圖裏的左右和下邊的markerview暫時不提,先把要畫的折線圖基本東西能隨便使用在說。
我們都知道在畫圖的時候肯定是把(x,y)座標傳進去,然後根據座標數據進行計算然後在畫出圖來,MPAndroidChart怎麼計算怎麼畫的,不是特例或研究源碼我們就不會太關心,但是有幾個在使用時我們是比較關心的,也就是我標註出來的1到4的4個數字。
1、這根線的屬性設置(相當於一根攔截線或水位線一樣,專業名稱叫“基線”);
2、左邊Y軸的數據(我們想要設置什麼數據就什麼數據);
3、右邊Y軸的數據(我們想要設置什麼數據就什麼數據);
4、底部X軸的數據(我們想設置多少個數據也就是label數或什麼數據就設置什麼數據)。
1、基線和基本設置
效果圖:
代碼
package com.mpandroidchartcsdn;
import android.graphics.Color;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import com.github.mikephil.charting.components.Description;
import com.github.mikephil.charting.components.Legend;
import com.github.mikephil.charting.components.LimitLine;
import com.github.mikephil.charting.components.XAxis;
import com.github.mikephil.charting.components.YAxis;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.data.LineDataSet;
import com.github.mikephil.charting.interfaces.datasets.ILineDataSet;
import com.mpandroidchartcsdn.mychart.MyLineChart;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private MyLineChart mLineChart;
private ArrayList<Entry> pointValues;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mLineChart = (MyLineChart) findViewById(R.id.lineChart);
initChart();
initData();
}
//設置chart基本屬性
private void initChart() {
//描述信息
Description description = new Description();
description.setText("我是描述信息");
//設置描述信息
mLineChart.setDescription(description);
//設置沒有數據時顯示的文本
mLineChart.setNoDataText("沒有數據喔~~");
//設置是否繪製chart邊框的線
mLineChart.setDrawBorders(true);
//設置chart邊框線顏色
mLineChart.setBorderColor(Color.GRAY);
//設置chart邊框線寬度
mLineChart.setBorderWidth(1f);
//設置chart是否可以觸摸
mLineChart.setTouchEnabled(true);
//設置是否可以拖拽
mLineChart.setDragEnabled(true);
//設置是否可以縮放 x和y,默認true
mLineChart.setScaleEnabled(false);
//設置是否可以通過雙擊屏幕放大圖表。默認是true
mLineChart.setDoubleTapToZoomEnabled(false);
//設置chart動畫
mLineChart.animateXY(1000, 1000);
//=========================設置圖例=========================
// 像"□ xxx"就是圖例
Legend legend = mLineChart.getLegend();
//設置圖例顯示在chart那個位置 setPosition建議放棄使用了
//設置垂直方向上還是下或中
legend.setVerticalAlignment(Legend.LegendVerticalAlignment.TOP);
//設置水平方向是左邊還是右邊或中
legend.setHorizontalAlignment(Legend.LegendHorizontalAlignment.RIGHT);
//設置所有圖例位置排序方向
legend.setOrientation(Legend.LegendOrientation.HORIZONTAL);
//設置圖例的形狀 有圓形、正方形、線
legend.setForm(Legend.LegendForm.CIRCLE);
//是否支持自動換行 目前只支持BelowChartLeft, BelowChartRight, BelowChartCenter
legend.setWordWrapEnabled(true);
//=======================設置X軸顯示效果==================
XAxis xAxis = mLineChart.getXAxis();
//是否啓用X軸
xAxis.setEnabled(true);
//是否繪製X軸線
xAxis.setDrawAxisLine(true);
//設置X軸上每個豎線是否顯示
xAxis.setDrawGridLines(true);
//設置是否繪製X軸上的對應值(標籤)
xAxis.setDrawLabels(true);
//設置X軸顯示位置
xAxis.setPosition(XAxis.XAxisPosition.BOTTOM);
//設置豎線爲虛線樣式
// xAxis.enableGridDashedLine(10f, 10f, 0f);
//設置x軸標籤數
xAxis.setLabelCount(5, true);
//圖表第一個和最後一個label數據不超出左邊和右邊的Y軸
// xAxis.setAvoidFirstLastClipping(true);
//設置限制線 12代表某個該軸某個值,也就是要畫到該軸某個值上
LimitLine limitLine = new LimitLine(12);
//設置限制線的寬
limitLine.setLineWidth(1f);
//設置限制線的顏色
limitLine.setLineColor(Color.RED);
//設置基線的位置
limitLine.setLabelPosition(LimitLine.LimitLabelPosition.LEFT_TOP);
limitLine.setLabel("馬丹我是基線,也可以叫我水位線");
//設置限制線爲虛線
limitLine.enableDashedLine(10f, 10f, 0f);
//左邊Y軸添加限制線
axisLeft.addLimitLine(limitLine);
//=================設置左邊Y軸===============
YAxis axisLeft = mLineChart.getAxisLeft();
//是否啓用左邊Y軸
axisLeft.setEnabled(true);
//設置最小值(這裏就按demo裏固死的寫)
axisLeft.setAxisMinimum(1);
//設置最大值(這裏就按demo裏固死的寫了)
axisLeft.setAxisMaximum(20);
//設置橫向的線爲虛線
axisLeft.enableGridDashedLine(10f, 10f, 0f);
//axisLeft.setDrawLimitLinesBehindData(true);
//====================設置右邊的Y軸===============
YAxis axisRight = mLineChart.getAxisRight();
//是否啓用右邊Y軸
axisRight.setEnabled(true);
//設置最小值(這裏按demo裏的數據固死寫了)
axisRight.setAxisMinimum(1);
//設置最大值(這裏按demo裏的數據固死寫了)
axisRight.setAxisMaximum(20);
//設置橫向的線爲虛線
axisRight.enableGridDashedLine(10f, 10f, 0f);
}
//設置數據
private void initData() {
//每個點的座標,自己隨便搞點(x,y)座標就可以了
pointValues = new ArrayList<>();
for (int i = 1; i < 19; i++) {
int y = (int)( Math.random() * 20);
pointValues.add(new Entry(i, y));
}
//點構成的某條線
LineDataSet lineDataSet = new LineDataSet(pointValues, "該線標籤1");
//設置該線的顏色
lineDataSet.setColor(Color.RED);
//設置每個點的顏色
lineDataSet.setCircleColor(Color.YELLOW);
//設置該線的寬度
lineDataSet.setLineWidth(1f);
//設置每個座標點的圓大小
//lineDataSet.setCircleRadius(1f);
//設置是否畫圓
lineDataSet.setDrawCircles(false);
// 設置平滑曲線模式
// lineDataSet.setMode(LineDataSet.Mode.CUBIC_BEZIER);
//設置線一面部分是否填充顏色
lineDataSet.setDrawFilled(true);
//設置填充的顏色
lineDataSet.setFillColor(Color.BLUE);
//設置是否顯示點的座標值
lineDataSet.setDrawValues(false);
//線的集合(可單條或多條線)
List<ILineDataSet> dataSets = new ArrayList<>();
dataSets.add(lineDataSet);
//把要畫的所有線(線的集合)添加到LineData裏
LineData lineData = new LineData(dataSets);
//把最終的數據setData
mLineChart.setData(lineData);
}
}
2、X軸label設置
先不管怎麼樣,LineChart圖表的基本設置上面那些屬性夠用了,但是,要用到項目上,呵呵~~,對比一下上面的截圖和之前我標註了1、2、3、4的截圖,我們要提個問題,像標誌的第二張圖裏的左右Y軸和X軸,我想設置自己想要的數據怎麼辦呢?在MPAndroidChart有兩種方法。
第一種設置X軸Label方法:
在3.0.2裏是通過setValueFormatter()方法設置的。setValueFormatter()方法裏getFormattedValue裏的參數value我們必須知道它是我們座標點的X軸的數字通過MPAndroidChart內部計算後要畫上去的座標值。對於每個不同軸的value,如果還不清楚是什麼的,建議多換換座標值打印打印然後和線的座標對比對比,肯定會知道並且找到規律的。
現在我要在上面的基礎上設置字符串作爲X軸的數據也就是label
代碼
Map<Integer, String> xMap = new HashMap<>();
final String[] valueArry = {"字符串1", "字符串2", "字符串3", "字符串4", "字符串5"};
//下邊if判斷是爲了使setLabelCount起作用:座標數量/x軸標籤數量 = 每個標籤座標數量,如果最後兩個標籤座標數量相差大於對於5
// setLabelCount就顯示不正常,也就是每個label要大概平均分配setLabelCount才能正常顯示設置的標籤數量
for (int i = 0; i < pointValues.size(); i++) {
if (i < 4) {
xMap.put((int) pointValues.get(i).getX(), valueArry[0]);
} else if (i < 8) {
xMap.put((int) pointValues.get(i).getX(), valueArry[1]);
} else if (i < 12) {
xMap.put((int) pointValues.get(i).getX(), valueArry[2]);
} else if (i < 16) {
xMap.put((int) pointValues.get(i).getX(), valueArry[3]);
} else if (i < 18) {
xMap.put((int) pointValues.get(i).getX(), valueArry[4]);
}
}
//自定義x軸標籤數據
xAxis.setValueFormatter(new IAxisValueFormatter() {
@Override
public String getFormattedValue(float value, AxisBase axis) {
return xMap.get((int)value);
}
});
對於使用setLabelCount控制x軸的label數還是要說一下,之前調用setLabelCount方法不起作用,後面測試好多次才發現要使setLabelCount起作用:“座標數量/x軸標籤數量 = 每個標籤座標數量,如果最後兩個標籤座標數量相差大於等於5,setLabelCount就顯示不正常,也就是每個label要大概平均分配setLabelCount才能正常顯示設置的標籤數量,否則不起作用” 對於這個是否這樣,我建議大家去試試,這個真心蛋疼。
加了上面代碼後效果圖
第二種設置X軸Label方法:
MPAndroidChart有個類XAxisRenderer,這個類主要是繪製X軸的一些屬性或者X軸其他的東西(具體可以看官方doc)。
1、寫個類MyXAxisRenderer繼承XAxisRenderer
代碼:
package com.mpandroidchartcsdn.mychart;
import android.graphics.Canvas;
import com.github.mikephil.charting.components.XAxis;
import com.github.mikephil.charting.renderer.XAxisRenderer;
import com.github.mikephil.charting.utils.MPPointF;
import com.github.mikephil.charting.utils.Transformer;
import com.github.mikephil.charting.utils.Utils;
import com.github.mikephil.charting.utils.ViewPortHandler;
public class MyXAxisRenderer extends XAxisRenderer {
//這裏爲了方便把X軸標籤放到這裏了,其實可以通過其他方式能拿到要設置的X軸標籤就可以
private String[] xLable = {"字符串1", "字符串2", "字符串3", "字符串4", "字符串5"};
public MyXAxisRenderer(ViewPortHandler viewPortHandler, XAxis xAxis, Transformer trans) {
super(viewPortHandler, xAxis, trans);
}
//重寫drawLabels
@Override
protected void drawLabels(Canvas c, float pos, MPPointF anchor) {
//把源碼代碼複製過來
final float labelRotationAngleDegrees = mXAxis.getLabelRotationAngle();
boolean centeringEnabled = mXAxis.isCenterAxisLabelsEnabled();
float[] positions = new float[mXAxis.mEntryCount * 2];
for (int i = 0; i < positions.length; i += 2) {
// only fill x values
if (centeringEnabled) {
positions[i] = mXAxis.mCenteredEntries[i / 2];
} else {
positions[i] = mXAxis.mEntries[i / 2];
}
}
mTrans.pointValuesToPixel(positions);
for (int i = 0; i < positions.length; i += 2) {
float x = positions[i];
if (mViewPortHandler.isInBoundsX(x)) {
//修改源碼 這裏添加要設置的X軸的label
String label = xLable[i / 2];
// String label = mXAxis.getValueFormatter().getFormattedValue(mXAxis.mEntries[i / 2], mXAxis);
if (mXAxis.isAvoidFirstLastClippingEnabled()) {
// avoid clipping of the last mXAxis.mEntryCount - 1爲x軸座標的標籤數
if (i == mXAxis.mEntryCount - 1 && mXAxis.mEntryCount > 1) {
float width = Utils.calcTextWidth(mAxisLabelPaint, label);
if (width > mViewPortHandler.offsetRight() * 2
&& x + width > mViewPortHandler.getChartWidth())
x -= width / 2;
// avoid clipping of the first
} else if (i == 0) {
float width = Utils.calcTextWidth(mAxisLabelPaint, label);
x += width / 2;
}
}
drawLabel(c, label, x, pos, anchor, labelRotationAngleDegrees);
}
}
}
}
2、在寫個MyLineChart繼承LineChart
代碼:
package com.mpandroidchartcsdn.mychart;
import android.content.Context;
import android.util.AttributeSet;
import com.github.mikephil.charting.charts.LineChart;
/**
* Created by tujingwu on 2017/5/4
* .
*/
public class MyLineChart extends LineChart {
public MyLineChart(Context context) {
super(context);
}
public MyLineChart(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void init() {
super.init();
mXAxisRenderer = new MyXAxisRenderer(mViewPortHandler, mXAxis, mLeftAxisTransformer);
}
}
3、像使用LineChart一樣使用MyLineChart就可以了
既然我們x軸數據都知道怎麼定義了,Y軸的數據也同理,這裏就以第一種方式(當然你也可以以第二種方式實現),通過setValueFormatter()方法來設置你要定義的Y軸的數據,比如我想要左邊Y軸數據按現在的數據1.5倍顯示,Y軸右邊按0.5倍,然後以百分數顯示。
代碼
final DecimalFormat decimalFormat = new DecimalFormat("#0.00");
axisLeft.setValueFormatter(new IAxisValueFormatter() {
@Override
public String getFormattedValue(float value, AxisBase axis) {
return decimalFormat.format(value * 1.5);
}
});
final DecimalFormat decimalFormat2 = new DecimalFormat("#0.00%");
axisRight.setValueFormatter(new IAxisValueFormatter() {
@Override
public String getFormattedValue(float value, AxisBase axis) {
return decimalFormat2.format((value * 0.5) / 100);
}
});
效果圖
ok,現在想怎麼設就怎麼設軸的數據了,之前我提的1、2、3、4都實現了,但是當中還有一個問題,就是上圖中的x軸數據,“字符串1”和“字符串5” 是不是超出了Y軸的左右兩邊的軸線?,我們現在想要變成下圖這樣子
先找找看看有沒有api,恩,找到一個方法setAvoidFirstLastClipping(),從名字上就可以看出大概意思,官方api介紹是“if set to true, the chart will avoid that the first and last label entry in the chart "clip" off the edge of the chart or the screen”;官方文檔地址我英語差,但大概意思是“圖表第一個和最後一個label數據不超出左邊和右邊的Y軸”,我們使用該方法設置爲true;
效果圖
呃呃呃,又喜又驚,什麼鬼,說好的第一個和最後一個的呢,我們順着setAvoidFirstLastClipping()方法找到源碼對應的位置,也就是源碼XAxisRenderer類中drawLabels方法
可能有些人頭疼,看5分鐘看不懂,看20分鐘也看不懂,看30分鐘也看不懂,但是爲了解決上面的問題還是要硬着頭皮看下去
drawLabels源碼
/**
* draws the x-labels on the specified y-position
*
* @param pos
*/
protected void drawLabels(Canvas c, float pos, MPPointF anchor) {
final float labelRotationAngleDegrees = mXAxis.getLabelRotationAngle();
boolean centeringEnabled = mXAxis.isCenterAxisLabelsEnabled();
float[] positions = new float[mXAxis.mEntryCount * 2];
for (int i = 0; i < positions.length; i += 2) {
// only fill x values
if (centeringEnabled) {
positions[i] = mXAxis.mCenteredEntries[i / 2];
} else {
positions[i] = mXAxis.mEntries[i / 2];
}
}
mTrans.pointValuesToPixel(positions);
for (int i = 0; i < positions.length; i += 2) {
float x = positions[i];
if (mViewPortHandler.isInBoundsX(x)) {
String label = mXAxis.getValueFormatter().getFormattedValue(mXAxis.mEntries[i / 2], mXAxis);
if (mXAxis.isAvoidFirstLastClippingEnabled()) {
// avoid clipping of the last
if (i == mXAxis.mEntryCount - 1 && mXAxis.mEntryCount > 1) {
float width = Utils.calcTextWidth(mAxisLabelPaint, label);
if (width > mViewPortHandler.offsetRight() * 2
&& x + width > mViewPortHandler.getChartWidth())
x -= width / 2;
// avoid clipping of the first
} else if (i == 0) {
float width = Utils.calcTextWidth(mAxisLabelPaint, label);
x += width / 2;
}
}
drawLabel(c, label, x, pos, anchor, labelRotationAngleDegrees);
}
}
}
OK,我們在使用方法setAvoidFirstLastClipping後,只有第一個“字符串1”縮進去了,最後一個“字符串5”沒有縮進去,上面的源碼我們注意這段代碼
if (mXAxis.isAvoidFirstLastClippingEnabled()) {
// avoid clipping of the last mXAxis.mEntryCount - 1爲x軸座標的標籤數
if (i == mXAxis.mEntryCount - 1 && mXAxis.mEntryCount > 1) {
float width = Utils.calcTextWidth(mAxisLabelPaint, label);
if (width > mViewPortHandler.offsetRight() * 2
&& x + width > mViewPortHandler.getChartWidth())
x -= width / 2;
// avoid clipping of the first
} else if (i == 0) {
float width = Utils.calcTextWidth(mAxisLabelPaint, label);
x += width / 2;
}
}
恩,沒錯,就是它了,一看第一個判斷不就是指最後一個標籤,第二個判斷指第一個標籤嗎,可是爲什麼“字符串1”縮進去了,“字符串5”沒有縮進去呢?打印一下就清清楚楚了,原來i它爲“x軸標籤數乘以2” 也就是爲什麼第一個“字符串1”縮進去了,“字符串5”沒有縮進去呢,因爲第一個判斷不是X軸最後一個標籤,X軸最後一個標籤應該爲“i==(mXAxis.mEntryCount-1)*2” ,"i==
0"當然是X軸第一個標籤拉,所以爲什麼我們設置了setAvoidFirstLastClipping後只有第一個起作用,既然這樣,我們把上面的判斷修改成下面那樣
if (mXAxis.isAvoidFirstLastClippingEnabled()) {
// avoid clipping of the last mXAxis.mEntryCount-1爲x軸標籤數
if (i == mXAxis.mEntryCount - 1 && mXAxis.mEntryCount > 1) {
float width = Utils.calcTextWidth(mAxisLabelPaint, label);
if (width > mViewPortHandler.offsetRight() * 2
&& x + width > mViewPortHandler.getChartWidth())
x -= width / 2;
// avoid clipping of the first
} else if (i == 0) {
float width = Utils.calcTextWidth(mAxisLabelPaint, label);
x += width / 2;
//重寫該方法 添加的代碼,x軸最後一個標籤縮進
} else if (i == (mXAxis.mEntryCount - 1) * 2) {
float width = Utils.calcTextWidth(mAxisLabelPaint, label);
x -= width / 2;
}
}
可是有個問題,如果不算是bug(這個應該不算吧,哈哈),我們最好不要亂修改源碼是不啦,那重寫該方法,把代碼拷過去就好了
繼承重寫的代碼
package com.mpandroidchartcsdn.mychart;
import android.graphics.Canvas;
import com.github.mikephil.charting.components.XAxis;
import com.github.mikephil.charting.renderer.XAxisRenderer;
import com.github.mikephil.charting.utils.MPPointF;
import com.github.mikephil.charting.utils.Transformer;
import com.github.mikephil.charting.utils.Utils;
import com.github.mikephil.charting.utils.ViewPortHandler;
public class MyXAxisRenderer extends XAxisRenderer {
public MyXAxisRenderer(ViewPortHandler viewPortHandler, XAxis xAxis, Transformer trans) {
super(viewPortHandler, xAxis, trans);
}
@Override
protected void drawLabels(Canvas c, float pos, MPPointF anchor) {
//把代碼複製過來
final float labelRotationAngleDegrees = mXAxis.getLabelRotationAngle();
boolean centeringEnabled = mXAxis.isCenterAxisLabelsEnabled();
float[] positions = new float[mXAxis.mEntryCount * 2];
for (int i = 0; i < positions.length; i += 2) {
// only fill x values
if (centeringEnabled) {
positions[i] = mXAxis.mCenteredEntries[i / 2];
} else {
positions[i] = mXAxis.mEntries[i / 2];
}
}
mTrans.pointValuesToPixel(positions);
for (int i = 0; i < positions.length; i += 2) {
float x = positions[i];
if (mViewPortHandler.isInBoundsX(x)) {
String label = mXAxis.getValueFormatter().getFormattedValue(mXAxis.mEntries[i / 2], mXAxis);
if (mXAxis.isAvoidFirstLastClippingEnabled()) {
// avoid clipping of the last mXAxis.mEntryCount-1爲x軸標籤數
if (i == mXAxis.mEntryCount - 1 && mXAxis.mEntryCount > 1) {
float width = Utils.calcTextWidth(mAxisLabelPaint, label);
if (width > mViewPortHandler.offsetRight() * 2
&& x + width > mViewPortHandler.getChartWidth())
x -= width / 2;
// avoid clipping of the first
} else if (i == 0) {
float width = Utils.calcTextWidth(mAxisLabelPaint, label);
x += width / 2;
//重寫該方法 添加的代碼,x軸最後一個標籤縮進
} else if (i == (mXAxis.mEntryCount - 1) * 2) {
float width = Utils.calcTextWidth(mAxisLabelPaint, label);
x -= width / 2;
}
}
drawLabel(c, label, x, pos, anchor, labelRotationAngleDegrees);
}
}
}
}
重寫好了,在哪裏使用呢?既然都看到這裏,別急嘛,在往下看。
我們順着XAxisRenderer找啊找啊,從BarLineChartBase->int()方法裏面就有mXAxisRenderer,然後順着這個方法找到了LineChart,LineChart->int()方法也有類似的XAxisRenderrer
Ok,那我們繼承LineChart,然後重寫int()就行了吧?
代碼
package com.mpandroidchartcsdn.mychart;
import android.content.Context;
import android.util.AttributeSet;
import com.github.mikephil.charting.charts.LineChart;
/**
* Created by tujingwu on 2017/5/4
* .
*/
public class MyLineChart extends LineChart {
public MyLineChart(Context context) {
super(context);
}
public MyLineChart(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void init() {
super.init();
mXAxisRenderer = new MyXAxisRenderer(mViewPortHandler, mXAxis, mLeftAxisTransformer);
}
}
然後我們像使用LineChart,使用MyLineChart看看
結果
恩,沒什麼問題了看起來(這只是一個簡單的demo,醜不醜的別再意,客官們),對於開頭gif裏的左右和底部的markerview在下一篇實現。