记录工作中使用到的MPAndroidChart的一些点
github地址:https://github.com/PhilJay/MPAndroidChart
wiki:https://github.com/PhilJay/MPAndroidChart/wiki
需求如下:
compile 'com.github.PhilJay:MPAndroidChart:v3.0.3'
<com.github.mikephil.charting.charts.LineChart
android:id="@+id/line_chart"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_300"/>
常用属性:
LineChart:
XAxis:
YAxis:
Y轴相较于X轴分为AxisRight和AxisLeft,但属性都是一样的。
LineDataSet:
进入正题:
1. 选中显示自定义marker
ChemicalsTrendMarker marker = new ChemicalsTrendMarker(MyApplication.getApplication(), getString(R.string.chemicals_trend_interval_marker_title));
marker.setChartView(lineChart);
lineChart.setMarker(marker);
public class ChemicalsTrendMarker extends MarkerView {
private String title;
/**
* Constructor. Sets up the MarkerView with a custom layout resource.
*
* @param context
*/
public ChemicalsTrendMarker(Context context, String title) {
super(context, R.layout.marker_fuel_trend);
this.title = title;
}
@Override
public void refreshContent(Entry e, Highlight highlight) {
if (e.getData() instanceof ChemicalsTrendModel) {
ChemicalsTrendModel model = (ChemicalsTrendModel) e.getData();
((TextView) findViewById(R.id.tvTitle)).setText(title);
((TextView) findViewById(R.id.tvContext)).setText(model.releaseDate + " : " + model.price);
}
super.refreshContent(e, highlight);
}
@Override
public MPPointF getOffset() {
return new MPPointF(-(getWidth() / 2), getHeight()/2);
}
}
2、纵坐标从0开始
YAxis leftAxis = lineChart.getAxisLeft();
leftAxis.setAxisMinimum(0);//纵坐标从0开始
3、横坐标自定义标签
int size = xVals.size();
XAxis xAxis = lineChart.getXAxis();
//控制横坐标标签个数
if(size <= 4 ){
xAxis.setLabelCount(size, false);
}else {
xAxis.setLabelCount(4, true);//强制
}
xAxis.setValueFormatter(new IAxisValueFormatter() {
@Override
public String getFormattedValue(float value, AxisBase axis) {
int index = (int) value;
if(index >= 0 && index < size ){
return xVals.get(index);
}else {
return "";
}
}
});
注意:当size>4时,设置强制,size<=4时设置为不强制,这样可以避免当数据小于4时,标签数据显示出现异常,如下比较两图横坐标标签:
4、当数据很多时,选取控制圆圈显示的个数
先上一张未处理前的图表:
在这里仅仅对XAxis进行格式化是不够的。MPAndroidChart将X轴Y轴及数据绘制线条分开进行设置,但是我们在对LineDataSet的CircleRadius等属性设置时,并找不到可以对圆圈集合进行控制的方法,那么我们可以通过查看源码的方式,查看LineChart是如何绘制圆的。
package com.github.mikephil.charting.charts;
import android.content.Context;
import android.util.AttributeSet;
import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.interfaces.dataprovider.LineDataProvider;
import com.github.mikephil.charting.renderer.LineChartRenderer;
/**
* Chart that draws lines, surfaces, circles, ...
*
* @author Philipp Jahoda
*/
public class LineChart extends BarLineChartBase<LineData> implements LineDataProvider {
public LineChart(Context context) {
super(context);
}
public LineChart(Context context, AttributeSet attrs) {
super(context, attrs);
}
public LineChart(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void init() {
super.init();
mRenderer = new LineChartRenderer(this, mAnimator, mViewPortHandler);
}
@Override
public LineData getLineData() {
return mData;
}
@Override
protected void onDetachedFromWindow() {
// releases the bitmap in the renderer to avoid oom error
if (mRenderer != null && mRenderer instanceof LineChartRenderer) {
((LineChartRenderer) mRenderer).releaseBitmap();
}
super.onDetachedFromWindow();
}
}
可以看到LineChart内部主要是初始化了一个LineChartRenderer,那么LineChart主要的方法就应该在这里面了。果不其然,很容易就能找到LineChart绘制Circle的方法。
protected void drawCircles(Canvas c) {
mRenderPaint.setStyle(Paint.Style.FILL);
float phaseY = mAnimator.getPhaseY();
mCirclesBuffer[0] = 0;
mCirclesBuffer[1] = 0;
List dataSets = mChart.getLineData().getDataSets();
for (int i = 0; i < dataSets.size(); i++) {
ILineDataSet dataSet = dataSets.get(i);
if (!dataSet.isVisible() || !dataSet.isDrawCirclesEnabled() ||
dataSet.getEntryCount() == 0)
continue;
mCirclePaintInner.setColor(dataSet.getCircleHoleColor());
Transformer trans = mChart.getTransformer(dataSet.getAxisDependency());
mXBounds.set(mChart, dataSet);
float circleRadius = dataSet.getCircleRadius();
float circleHoleRadius = dataSet.getCircleHoleRadius();
boolean drawCircleHole = dataSet.isDrawCircleHoleEnabled() &&
circleHoleRadius < circleRadius &&
circleHoleRadius > 0.f;
boolean drawTransparentCircleHole = drawCircleHole &&
dataSet.getCircleHoleColor() == ColorTemplate.COLOR_NONE;
DataSetImageCache imageCache;
if (mImageCaches.containsKey(dataSet)) {
imageCache = mImageCaches.get(dataSet);
} else {
imageCache = new DataSetImageCache();
mImageCaches.put(dataSet, imageCache);
}
boolean changeRequired = imageCache.init(dataSet);
// only fill the cache with new bitmaps if a change is required
if (changeRequired) {
imageCache.fill(dataSet, drawCircleHole, drawTransparentCircleHole);
}
int boundsRangeCount = mXBounds.range + mXBounds.min;
for (int j = mXBounds.min; j <= boundsRangeCount; j++) {
Entry e = dataSet.getEntryForIndex(j);
if (e == null) break;
mCirclesBuffer[0] = e.getX();
mCirclesBuffer[1] = e.getY() * phaseY;
trans.pointValuesToPixel(mCirclesBuffer);
if (!mViewPortHandler.isInBoundsRight(mCirclesBuffer[0]))
break;
if (!mViewPortHandler.isInBoundsLeft(mCirclesBuffer[0]) ||
!mViewPortHandler.isInBoundsY(mCirclesBuffer[1]))
continue;
Bitmap circleBitmap = imageCache.getBitmap(j);
if (circleBitmap != null) {
c.drawBitmap(circleBitmap, mCirclesBuffer[0] - circleRadius, mCirclesBuffer[1] - circleRadius, null);
}
}
}
}
List dataSets = mChart.getLineData().getDataSets(); 获取到LineChart中绘制的线条集合。
ILineDataSet dataSet = dataSets.get(i); 获取某一条线条的点集合。
Entry e = dataSet.getEntryForIndex(j); 获取到具体一个点的数据。
到此,我们已经获取到LineChart如何获取到线条集合进行绘制圆圈的了,这样我们就可以在绘制圆之前进行判断,当前点是否为我们需要绘制的点,如果不是,则不进行绘制。当然这只是提供一种方法思路。
因此,我们可以自定义一个Renderer,继承自LineChartRenderer并对drawCircles方法进行重写。
下面的例子是结合需求,只显示4个点(首尾两点+中间部分去平均值得到2个点),供参考
public class BaseLineChartRenderer extends LineChartRenderer{
private int mMaxCount = 4;//最多绘制点个数
private float[] mCirclesBuffer = new float[2];
private HashMap mImageCaches = new HashMap<>();
public BaseLineChartRenderer(int maxCount ,LineDataProvider chart, ChartAnimator animator, ViewPortHandler viewPortHandler) {
super(chart, animator, viewPortHandler);
mMaxCount = maxCount;
}
@Override
protected void drawCircles(Canvas c) {
mRenderPaint.setStyle(Paint.Style.FILL);
float phaseY = mAnimator.getPhaseY();
mCirclesBuffer[0] = 0;
mCirclesBuffer[1] = 0;
List dataSets = mChart.getLineData().getDataSets();
for (int i = 0; i < dataSets.size(); i++) {
ILineDataSet dataSet = dataSets.get(i);
----------
//要画出点的数组
int size = dataSet.getEntryCount();;
int avg = size / (mMaxCount - 1);//平均数
int[] indexArr = new int[mMaxCount];
//首尾两点
indexArr[0] = 0;
indexArr[mMaxCount - 1] = size - 1;
//中间平分点
for(int j = 1; j < mMaxCount - 1; j++){
indexArr[j] = avg + indexArr[j - 1];
}
int index = 0;
----------
if (!dataSet.isVisible() || !dataSet.isDrawCirclesEnabled() ||
dataSet.getEntryCount() == 0) {
continue;
}
mCirclePaintInner.setColor(dataSet.getCircleHoleColor());
Transformer trans = mChart.getTransformer(dataSet.getAxisDependency());
mXBounds.set(mChart, dataSet);
float circleRadius = dataSet.getCircleRadius();
float circleHoleRadius = dataSet.getCircleHoleRadius();
boolean drawCircleHole = dataSet.isDrawCircleHoleEnabled() &&
circleHoleRadius < circleRadius &&
circleHoleRadius > 0.f;
boolean drawTransparentCircleHole = drawCircleHole &&
dataSet.getCircleHoleColor() == ColorTemplate.COLOR_NONE;
DataSetImageCache imageCache;
if (mImageCaches.containsKey(dataSet)) {
imageCache = mImageCaches.get(dataSet);
} else {
imageCache = new DataSetImageCache();
mImageCaches.put(dataSet, imageCache);
}
boolean changeRequired = imageCache.init(dataSet);
// only fill the cache with new bitmaps if a change is required
if (changeRequired) {
imageCache.fill(dataSet, drawCircleHole, drawTransparentCircleHole);
}
int boundsRangeCount = mXBounds.range + mXBounds.min;
for (int j = mXBounds.min; j <= boundsRangeCount; j++) {
----------
//在绘制前拦截判断当前点是否为要画的点
if(index < mMaxCount){
if( j != indexArr[index]){
continue;//不是要绘制的点
}else {
index ++;//需要绘制的点
}
}else {
break;//点全都绘制完成
}
----------
Entry e = dataSet.getEntryForIndex(j);
if (e == null) {
break;
}
mCirclesBuffer[0] = e.getX();
mCirclesBuffer[1] = e.getY() * phaseY;
trans.pointValuesToPixel(mCirclesBuffer);
if (!mViewPortHandler.isInBoundsRight(mCirclesBuffer[0])) {
break;
}
if (!mViewPortHandler.isInBoundsLeft(mCirclesBuffer[0]) ||
!mViewPortHandler.isInBoundsY(mCirclesBuffer[1])) {
continue;
}
Bitmap circleBitmap = imageCache.getBitmap(j);
if (circleBitmap != null) {
c.drawBitmap(circleBitmap, mCirclesBuffer[0] - circleRadius, mCirclesBuffer[1] - circleRadius, null);
}
}
}
}
private class DataSetImageCache {...}
最后将自定义的Renderer设置给LineChart。
//控制数据绘制圆圈个数
lineChart.setRenderer(new BaseLineChartRenderer(4, lineChart, lineChart.getAnimator(), lineChart.getViewPortHandler()));
最后,将图1图2设置的属性贴在下面。
@Override
public void initChart() {
//图表描述
Description description = new Description();
description.setText("");
lineChart.setDescription(description);
lineChart.setDragEnabled(true);
lineChart.setScaleEnabled(false);
lineChart.setDoubleTapToZoomEnabled(false);
ChemicalsTrendMarker marker = new ChemicalsTrendMarker(MyApplication.getApplication(), getString(R.string.chemicals_trend_interval_marker_title));
marker.setChartView(lineChart);
lineChart.setMarker(marker);
//控制数据绘制圆圈个数
lineChart.setRenderer(new BaseLineChartRenderer(4, lineChart, lineChart.getAnimator(), lineChart.getViewPortHandler()));
XAxis xAxis = lineChart.getXAxis();
xAxis.setAvoidFirstLastClipping(true);//坐标最后一个点
xAxis.setPosition(XAxis.XAxisPosition.BOTTOM);
xAxis.setLabelRotationAngle(0f);
//隐藏x对应垂直线
xAxis.setDrawGridLines(false);
//xAxis.setAxisMaximum(4);
xAxis.setTextColor(ContextCompat.getColor(MyApplication.getApplication(), R.color.gray_666));
YAxis rightAxis = lineChart.getAxisRight();
rightAxis.setEnabled(false);
YAxis leftAxis = lineChart.getAxisLeft();
leftAxis.setTextColor(ContextCompat.getColor(MyApplication.getApplication(), R.color.gray_666));
leftAxis.setAxisMinimum(0);//纵坐标从0开始
Legend l = lineChart.getLegend();//图例
l.setEnabled(false);
}
@Override
public void refreshLineChart(ArrayList yVals, ArrayList xVals) {
int size = xVals.size();
XAxis xAxis = lineChart.getXAxis();
//控制横坐标标签个数
if(size <= 4 ){
xAxis.setLabelCount(size, false);
}else {
xAxis.setLabelCount(4, true);//强制
}
xAxis.setValueFormatter(new IAxisValueFormatter() {
@Override
public String getFormattedValue(float value, AxisBase axis) {
int index = (int) value;
if(index >= 0 && index < size ){
return xVals.get(index);
}else {
return "";
}
}
});
LineDataSet dataSet = new LineDataSet(yVals, "化工品价格走势图");
dataSet.setColors(ContextCompat.getColor(MyApplication.getApplication(), R.color.colorPrimary));
dataSet.setLineWidth(2);
dataSet.setDrawValues(false);
dataSet.setHighLightColor(ContextCompat.getColor(MyApplication.getApplication(), R.color.gray_333));
dataSet.setDrawFilled(true);
dataSet.setFillDrawable(ContextCompat.getDrawable(MyApplication.getApplication(), R.drawable.shape_gradual_blue));
//点上的数据
dataSet.setDrawValues(false);
dataSet.setDrawCircles(true);
dataSet.setCircleRadius(DensityUtil.dip2px(MyApplication.getApplication(), 2));
dataSet.setCircleColorHole(ContextCompat.getColor(MyApplication.getApplication(), R.color.white));
dataSet.setCircleHoleRadius(DensityUtil.dip2px(MyApplication.getApplication(), 1.5f));
dataSet.setCircleColor(ContextCompat.getColor(MyApplication.getApplication(), R.color.colorPrimary));
lineChart.setData(new LineData(dataSet));
lineChart.animateX(1000);
}
@Override
public void initChart() {
//图表描述
Description description = new Description();
description.setText("");
mLineChart.setDescription(description);
mLineChart.setDragEnabled(true);
mLineChart.setScaleEnabled(false);
mLineChart.setDoubleTapToZoomEnabled(false);
XAxis xAxis = mLineChart.getXAxis();
xAxis.setAvoidFirstLastClipping(false);//坐标最后一个点
xAxis.setPosition(XAxis.XAxisPosition.BOTTOM);
xAxis.setLabelCount(9);
xAxis.setLabelRotationAngle(-30f);
YAxis rightAxis=mLineChart.getAxisRight();
rightAxis.setEnabled(false);
Legend l = mLineChart.getLegend();//图例
l.setEnabled(false);
l.setPosition(Legend.LegendPosition.RIGHT_OF_CHART_INSIDE);//设置图例的位置
l.setTextSize(10f);//设置文字大小
l.setForm(Legend.LegendForm.CIRCLE);//正方形,圆形或线
l.setFormSize(10f); // 设置Form的大小
l.setWordWrapEnabled(true);//是否支持自动换行 目前只支持BelowChartLeft, BelowChartRight, BelowChartCenter
l.setFormLineWidth(10f);//设置Form的宽度
}
@Override
public void refreshLineChart(ArrayList yVals, ArrayList xVals) {
mTvNoData.setVisibility(View.GONE);
mLlTable.setVisibility(View.VISIBLE);
mTvLineChartTitle.setText((!TextUtils.isEmpty(mTidePortName) ? mTidePortName + " " : "") + getString(R.string.tide_search_chart_title));
int size = xVals.size();//防止数组越界
mLineChart.getXAxis().setValueFormatter(new IAxisValueFormatter() {
@Override
public String getFormattedValue(float value, AxisBase axis) {
return xVals.get((int) value < size ? (int) value : size - 1);
}
});
TideMarker marker = new TideMarker(MyApplication.getApplication(), mTidePortName);
marker.setChartView(mLineChart);
mLineChart.setMarker(marker);
LineDataSet dataSet = new LineDataSet(yVals, "潮汐表曲线图");
dataSet.setColors(ContextCompat.getColor(MyApplication.getApplication(), R.color.colorPrimary));
dataSet.setCircleColor(ContextCompat.getColor(MyApplication.getApplication(), R.color.colorPrimary));
dataSet.setLineWidth(3);
dataSet.setDrawValues(false);
dataSet.setHighLightColor(ContextCompat.getColor(MyApplication.getApplication(), R.color.colorPrimary));
mLineChart.setData(new LineData(dataSet));
mLineChart.animateX(1000);
}