最近項目中有展示折線圖和圓形圖的需求,決定使用helloChart。這篇博客主要記錄直線圖和圓形圖的使用,和在使用過程中遇到的問題。
找了很多偏博客,這篇博客的API是最全的:開源圖標庫hellocharts常見API總結
- 折線圖的使用
- 解決折線圖使用過程中發現的問題
a.修改折線圖標點的樣式(這裏將其改爲了空心)
b.解決折線圖座標點全爲相同(包括全爲0)時折線不展示的問題
c.怎樣隔點展示座標
d.怎樣固定折線圖X軸展示個數,改爲x軸固定個數並能滑動
先擺上效果圖:
- 折線圖的使用
這塊API中都有很詳細的解釋,直接上代碼吧
xml文件
<lecho.lib.hellocharts.view.LineChartView
android:id="@+id/lineChartView_trend"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="@dimen/x10" />
activity(我這裏畫了兩個折線圖,還有些其它邏輯,具體看三個方法就OK:
getAxisXLables();//獲取x軸的標註
getAxisPoints();//獲取座標點
initLineChart();//初始化
)
public class VisitTrendFragment extends BaseMvpFragment<IVisitTrendView, IVisitTrendPresenter> implements IVisitTrendView, OnDateSelectListener {
@BindView(R.id.tv_trend)
TextView tvTrend;
@BindView(R.id.tv_trendDate)
TextView tvTrendDate;
@BindView(R.id.tv_visitShopNum)
TextView tvVisitShopNum;
@BindView(R.id.tv_reachNum)
TextView tvReachNum;
@BindView(R.id.tv_orderNum)
TextView tvOrderNum;
@BindView(R.id.tv_visitShopAvg)
TextView tvVisitShopAvg;
@BindView(R.id.tv_reachAvg)
TextView tvReachAvg;
@BindView(R.id.tv_orderAvg)
TextView tvOrderAvg;
@BindView(R.id.lineChartView_trend)
LineChartView lineChartViewTrend;
@BindView(R.id.ll_trend)
LinearLayout llTrend;
@BindView(R.id.tv_custom)
TextView tvCustom;
@BindView(R.id.tv_customDate)
TextView tvCustomDate;
@BindView(R.id.tv_newCustomNum)
TextView tvNewCustomNum;
@BindView(R.id.tv_overDateNum)
TextView tvOverDateNum;
@BindView(R.id.tv_customAllNum)
TextView tvCustomAllNum;
@BindView(R.id.ll_custom)
LinearLayout llCustom;
@BindView(R.id.tv_trendPercent)
TextView tv_trendPercent;
@BindView(R.id.lineChartView_custom)
LineChartView lineChartView_custom;
@BindView(R.id.tv_salesManNum)
TextView tv_salesManNum;
@BindView(R.id.ll_avg)
LinearLayout ll_avg;
private int spaceNum = 3;
//點
private List<PointValue> mPointValues = new ArrayList<PointValue>();
//x
private List<AxisValue> mAxisXValues = new ArrayList<AxisValue>();
private LineChartData lineChartData;
//客戶統計數據
private List<PointValue> mPointCustom = new ArrayList<>();
private List<AxisValue> mAxisXCustom = new ArrayList<>();
private LineChartData lineChartDataCustom;
private String chooseDate = DateUtil.getNowDate_24();
//拜訪統計數據
private List<VisitBean> visitBeanList = new ArrayList<>();
private List<CustomBean> customBeanList = new ArrayList<>();
@Override
public IVisitTrendPresenter initPresenter() {
return new IVisitTrendPresenter();
}
@Override
public int setContentView() {
return R.layout.fra_visit_trend;
}
@Override
public void initView() {
lineChartData = new LineChartData();
lineChartDataCustom = new LineChartData();
tvCustomDate.setText(chooseDate);
tvTrendDate.setText(chooseDate);
if (!"1".equals(Tools.getLoginUser().getIsManager())) {
tv_salesManNum.setVisibility(View.GONE);
ll_avg.setVisibility(View.GONE);
}
//切換時不再重新刷新數據
presenter.salesmanVisitTrend(chooseDate);
presenter.salesmanVisitTrendBranchCount(chooseDate);
}
//獲取x的顯示
private void getAxisXLables() {
mAxisXValues.clear();
int dateNum = visitBeanList.size();
int remainderNum = (visitBeanList.size() - 1) % spaceNum;
for (int i = 0; i < dateNum; i++) {
//x的集合
if (i % spaceNum == remainderNum) {
mAxisXValues.add(new AxisValue(i).setLabel(visitBeanList.get(i).getTime()));
} else {
mAxisXValues.add(new AxisValue(i).setLabel(""));
}
}
}
//圖表的每個點的顯示
private void getAxisPoints() {
mPointValues.clear();
for (int i = 0; i < visitBeanList.size(); i++) {
//點的橫縱座標
mPointValues.add(new PointValue(i, visitBeanList.get(i).getBranchQty()));
}
}
private void initLineChart() {
//創建一條線,將點放入線中
Line line = new Line(mPointValues).setColor(getResources().getColor(R.color.primary_color));
line.setShape(ValueShape.CIRCLE);//設置折線圖上每個數據點的形狀 ValueShape.DIAMOND CIRCLE SQUARE
line.setCubic(false);//折線是否平滑,即曲線還是折線
line.setFilled(false);//是否填充
line.setHasLabels(true);//曲線的數據座標是否加上備註
// line.setHasLabelsOnlyForSelected(true);//點擊數據座標提示數據(設置了這個line.setHasLabels(true);就無效)
line.setHasLabels(true);//是否用線表示,爲false則只有線沒有點
line.setHasPoints(true);//是否顯示原點 如果爲false,則沒有原點只有點的顯示
line.setStrokeWidth(2);
line.setPointRadius(3);
//創建線的集合
List<Line> lines = new ArrayList<>();
lines.add(line);
//創建數據,把線放入集合中
lineChartData.setLines(lines);
lineChartData.setValueLabelBackgroundEnabled(false);
lineChartData.setValueLabelsTextColor(getResources().getColor(R.color.gray_999));
//座標軸,創建軸
Axis axisX = new Axis();//x軸
axisX.setHasTiltedLabels(false);//x軸座標是否是斜的
axisX.setTextColor(getResources().getColor(R.color.gray_999));//設置字體顏色
axisX.setTextSize(10);//設置字體大小
axisX.setMaxLabelChars(0);//最多設置幾個x軸座標,意思就是你的縮放讓X軸上數據的個數7<=x<=mAxisXValues.length
axisX.setValues(mAxisXValues);
// data.setAxisXTop(axisX);//x軸在頂部
lineChartData.setAxisXBottom(axisX);//X軸在底部
axisX.setHasLines(false);//x軸分割線
//設置行爲屬性,支持縮放、滑動以及平移
lineChartViewTrend.setInteractive(true);
lineChartViewTrend.setZoomType(ZoomType.HORIZONTAL);
lineChartViewTrend.setMaxZoom((float) 2);//設置最大方法比例
//支持橫向滑動
lineChartViewTrend.setContainerScrollEnabled(true, ContainerScrollType.HORIZONTAL);
//改變繪製畫筆,將線的大小,點的展示修改
lineChartViewTrend.setChartRenderer(new MyLineChartRenderer(getContext(), lineChartViewTrend, lineChartViewTrend)); //改變繪製畫筆,座標點默認樣式只有三種:DIAMOND CIRCLE SQUARE 這邊我改爲了空心圓
lineChartViewTrend.setLineChartData(lineChartData);
lineChartViewTrend.startDataAnimation();
lineChartViewTrend.setVisibility(View.VISIBLE);
/**注:下面的7,10只是代表一個數字去類比而已
* 當時是爲了解決X軸固定數據個數。見(http://forum.xda-developers.com/tools/programming/library-hellocharts-charting-library-t2904456/page2);
*/
Viewport v = new Viewport(lineChartViewTrend.getMaximumViewport());
v.bottom = 0f;
v.top = 100f;
//固定Y軸的範圍,如果沒有這個,Y軸的範圍會根據數據的最大值和最小值決定,這不是我想要的
lineChartViewTrend.setMaximumViewport(v);
//這2個屬性的設置一定要在lineChart.setMaximumViewport(v);這個方法之後,不然顯示的座標數據是不能左右滑動查看更多數據的
v.left = visitBeanList.size() - 8;
v.right = visitBeanList.size() - 1;
lineChartViewTrend.setCurrentViewport(v);
}
//獲取x的顯示
private void getAxisXLablesCustom() {
mAxisXCustom.clear();
int dateNum = customBeanList.size();
int remainderNum = (customBeanList.size() - 1) % spaceNum;
for (int i = 0; i < dateNum; i++) {
//x的集合
mAxisXCustom.add(new AxisValue(i).setLabel(customBeanList.get(i).getTime()));
}
}
//圖表的每個點的顯示
private void getAxisPointsCustom() {
mPointCustom.clear();
for (int i = 0; i < customBeanList.size(); i++) {
//點的橫縱座標
mPointCustom.add(new PointValue(i, customBeanList.get(i).getAddBranchQty()));
}
}
private void initLineChartCustom() {
//創建一條線,將點放入線中
Line line = new Line(mPointCustom).setColor(getResources().getColor(R.color.yellow_color));
line.setShape(ValueShape.CIRCLE);//設置折線圖上每個數據點的形狀
line.setCubic(false);//折線是否平滑,即曲線還是折線
line.setFilled(false);//是否填充
line.setHasLabels(true);//曲線的數據座標是否加上備註
// line.setHasLabelsOnlyForSelected(true);//點擊數據座標提示數據(設置了這個line.setHasLabels(true);就無效)
line.setHasLabels(true);//是否用線表示,爲false則只有線沒有點
line.setHasPoints(true);//是否顯示原點 如果爲false,則沒有原點只有點的顯示
line.setStrokeWidth(2);
line.setPointRadius(3);
//創建線的集合
List<Line> lines = new ArrayList<>();
lines.add(line);
//創建數據,把線放入集合中
lineChartDataCustom.setLines(lines);
//座標軸,創建軸
Axis axisX = new Axis();//x軸
axisX.setHasTiltedLabels(false);//x軸座標是否是斜的
axisX.setTextColor(getResources().getColor(R.color.gray_666));//設置字體顏色
axisX.setTextSize(10);//設置字體大小
axisX.setValues(mAxisXCustom);
lineChartDataCustom.setAxisXBottom(axisX);//X軸在底部
lineChartDataCustom.setAxisYLeft(axisY);
//設置行爲屬性,支持縮放、滑動以及平移
lineChartView_custom.setInteractive(true);
lineChartView_custom.setZoomType(ZoomType.HORIZONTAL);
lineChartView_custom.setMaxZoom(2);//設置最大方法比例
//支持橫向滑動
lineChartView_custom.setContainerScrollEnabled(true, ContainerScrollType.HORIZONTAL);
//改變繪製畫筆,將線的大小,點的展示修改
lineChartView_custom.setChartRenderer(new MyLineChartRenderer(getContext(), lineChartView_custom, lineChartView_custom)); //改變繪製畫筆
lineChartView_custom.setLineChartData(lineChartDataCustom);
lineChartView_custom.startDataAnimation();
lineChartView_custom.setVisibility(View.VISIBLE);
}
@Override
protected void onFragmentVisibleChange(boolean isVisible) {
}
@Override
public void onOffSiteLogin() {
}
@Override
public void onSetLoadingStatus(boolean status, String message) {
if (status) {
showLoading();
} else {
hideLoading(message);
}
}
//拜訪統計數據
@Override
public void setVisitTrendData(VisitTrend visitTrend, List<VisitBean> trendList) {
tvVisitShopNum.setText("" + visitTrend.getVisitQty());
tvReachNum.setText("" + visitTrend.getBranchQty());
tvOrderNum.setText("" + visitTrend.getSheetQty());
tvVisitShopAvg.setText("" + visitTrend.getVisitRate());
tvReachAvg.setText("" + visitTrend.getBranchRate());
tvOrderAvg.setText("" + visitTrend.getSheetRate());
tv_trendPercent.setText("達成率" + visitTrend.getReachRate());
tv_salesManNum.setText("業務員" + visitTrend.getSaleManQty() + "人");
this.visitBeanList.clear();
this.visitBeanList.addAll(trendList);
getAxisXLables();//獲取x軸的標註
getAxisPoints();//獲取座標點
initLineChart();//初始化
}
//客戶統計
@Override
public void setCustomData(int addBranchQty, int notVisitBranchQty, int branchQty, List<CustomBean> customList) {
if (addBranchQty > 0)
tvNewCustomNum.setText("" + addBranchQty);
tvOverDateNum.setText("" + notVisitBranchQty);
tvCustomAllNum.setText("" + branchQty);
customBeanList.clear();
customBeanList.addAll(customList);
getAxisXLablesCustom();
getAxisPointsCustom();
initLineChartCustom();
}
@OnClick({R.id.tv_trendDate, R.id.tv_customDate})
public void onViewClicked(View view) {
switch (view.getId()) {
case R.id.tv_trendDate:
DateUtil.createTimePick(getContext(), this, chooseDate, true, tvTrendDate, tvCustomDate);
break;
case R.id.tv_customDate:
DateUtil.createTimePick(getContext(), this, chooseDate, true, tvTrendDate, tvCustomDate);
break;
}
}
@Override
public void ondataSelectSuccessd(String date) {
if (!StringUtil.isEmpty(date)) {
tvTrendDate.setText(date);
tvCustomDate.setText(date);
chooseDate = date;
presenter.salesmanVisitTrend(chooseDate);
presenter.salesmanVisitTrendBranchCount(chooseDate);
}
}
}
MyLineChartRenderer
public class MyLineChartRenderer extends AbstractChartRenderer {
private static final float LINE_SMOOTHNESS = 0.16f;
private static final int DEFAULT_LINE_STROKE_WIDTH_DP = 3;
private static final int DEFAULT_TOUCH_TOLERANCE_MARGIN_DP = 4;
private static final int MODE_DRAW = 0;
private static final int MODE_HIGHLIGHT = 1;
private LineChartDataProvider dataProvider;
private int checkPrecision;
private float baseValue;
private int touchToleranceMargin;
private Path path = new Path();
private Paint linePaint = new Paint();
private Paint pointPaint = new Paint();
private Paint pointStrokePaint = new Paint();
private Bitmap softwareBitmap;
private Canvas softwareCanvas = new Canvas();
private Viewport tempMaximumViewport = new Viewport();
public MyLineChartRenderer(Context context, Chart chart, LineChartDataProvider dataProvider) {
super(context, chart);
this.dataProvider = dataProvider;
touchToleranceMargin = ChartUtils.dp2px(density, DEFAULT_TOUCH_TOLERANCE_MARGIN_DP);
linePaint.setAntiAlias(true);
linePaint.setStyle(Paint.Style.STROKE);
linePaint.setStrokeCap(Cap.ROUND);
linePaint.setStrokeWidth(ChartUtils.dp2px(density, DEFAULT_LINE_STROKE_WIDTH_DP));
pointPaint.setAntiAlias(true);
pointPaint.setStyle(Paint.Style.FILL);
pointStrokePaint.setAntiAlias(true);
pointStrokePaint.setColor(Color.WHITE);
pointStrokePaint.setStyle(Paint.Style.FILL);
checkPrecision = ChartUtils.dp2px(density, 2);
}
public void onChartSizeChanged() {
final int internalMargin = calculateContentRectInternalMargin();
computator.insetContentRectByInternalMargins(internalMargin, internalMargin,
internalMargin, internalMargin);
if (computator.getChartWidth() > 0 && computator.getChartHeight() > 0) {
softwareBitmap = Bitmap.createBitmap(computator.getChartWidth(), computator.getChartHeight(),
Bitmap.Config.ARGB_8888);
softwareCanvas.setBitmap(softwareBitmap);
}
}
@Override
public void onChartDataChanged() {
super.onChartDataChanged();
final int internalMargin = calculateContentRectInternalMargin();
computator.insetContentRectByInternalMargins(internalMargin, internalMargin,
internalMargin, internalMargin);
baseValue = dataProvider.getLineChartData().getBaseValue();
onChartViewportChanged();
}
@Override
public void onChartViewportChanged() {
if (isViewportCalculationEnabled) {
calculateMaxViewport();
computator.setMaxViewport(tempMaximumViewport);
computator.setCurrentViewport(computator.getMaximumViewport());
}
}
@Override
public void draw(Canvas canvas) {
final LineChartData data = dataProvider.getLineChartData();
final Canvas drawCanvas;
// softwareBitmap can be null if chart is rendered in layout editor. In that case use default canvas and not
// softwareCanvas.
if (null != softwareBitmap) {
drawCanvas = softwareCanvas;
drawCanvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);
} else {
drawCanvas = canvas;
}
for (Line line : data.getLines()) {
if (line.hasLines()) {
if (line.isCubic()) {
drawSmoothPath(drawCanvas, line);
} else if (line.isSquare()) {
drawSquarePath(drawCanvas, line);
} else {
drawPath(drawCanvas, line);
}
}
}
if (null != softwareBitmap) {
canvas.drawBitmap(softwareBitmap, 0, 0, null);
}
}
@Override
public void drawUnclipped(Canvas canvas) {
final LineChartData data = dataProvider.getLineChartData();
int lineIndex = 0;
for (Line line : data.getLines()) {
if (checkIfShouldDrawPoints(line)) {
drawPoints(canvas, line, lineIndex, MODE_DRAW);
}
++lineIndex;
}
if (isTouched()) {
// Redraw touched point to bring it to the front
highlightPoints(canvas);
}
}
private boolean checkIfShouldDrawPoints(Line line) {
return line.hasPoints() || line.getValues().size() == 1;
}
@Override
public boolean checkTouch(float touchX, float touchY) {
selectedValue.clear();
final LineChartData data = dataProvider.getLineChartData();
int lineIndex = 0;
for (Line line : data.getLines()) {
if (checkIfShouldDrawPoints(line)) {
int pointRadius = ChartUtils.dp2px(density, line.getPointRadius());
int valueIndex = 0;
for (PointValue pointValue : line.getValues()) {
final float rawValueX = computator.computeRawX(pointValue.getX());
final float rawValueY = computator.computeRawY(pointValue.getY());
if (isInArea(rawValueX, rawValueY, touchX, touchY, pointRadius + touchToleranceMargin)) {
selectedValue.set(lineIndex, valueIndex, SelectedValueType.LINE);
}
++valueIndex;
}
}
++lineIndex;
}
return isTouched();
}
private void calculateMaxViewport() {
tempMaximumViewport.set(Float.MAX_VALUE, Float.MIN_VALUE, Float.MIN_VALUE, Float.MAX_VALUE);
LineChartData data = dataProvider.getLineChartData();
for (Line line : data.getLines()) {
// Calculate max and min for viewport.
for (PointValue pointValue : line.getValues()) {
if (pointValue.getX() < tempMaximumViewport.left) {
tempMaximumViewport.left = pointValue.getX();
}
if (pointValue.getX() > tempMaximumViewport.right) {
tempMaximumViewport.right = pointValue.getX();
}
if (pointValue.getY() < tempMaximumViewport.bottom) {
tempMaximumViewport.bottom = pointValue.getY();
}
if (pointValue.getY() > tempMaximumViewport.top) {
tempMaximumViewport.top = pointValue.getY();
}
}
}
if (tempMaximumViewport.top == tempMaximumViewport.bottom) {//解決最大值最小值相等時,圖不能展示問題
tempMaximumViewport.top = tempMaximumViewport.top * 2;
tempMaximumViewport.bottom = 0;
}
if ((tempMaximumViewport.top + "").equals(1.4E-45 + "")) {//解決全爲0時,圖不展示的問題。
tempMaximumViewport.top = 1;
tempMaximumViewport.bottom = 0;
}
}
private int calculateContentRectInternalMargin() {
int contentAreaMargin = 0;
final LineChartData data = dataProvider.getLineChartData();
for (Line line : data.getLines()) {
if (checkIfShouldDrawPoints(line)) {
int margin = line.getPointRadius() + DEFAULT_TOUCH_TOLERANCE_MARGIN_DP;
if (margin > contentAreaMargin) {
contentAreaMargin = margin;
}
}
}
return ChartUtils.dp2px(density, contentAreaMargin);
}
/**
* Draws lines, uses path for drawing filled area on software canvas. Line is drawn with canvas.drawLines() method.
*/
private void drawPath(Canvas canvas, final Line line) {
prepareLinePaint(line);
int valueIndex = 0;
for (PointValue pointValue : line.getValues()) {
final float rawX = computator.computeRawX(pointValue.getX());
final float rawY = computator.computeRawY(pointValue.getY());
if (valueIndex == 0) {
path.moveTo(rawX, rawY);
} else {
path.lineTo(rawX, rawY);
}
++valueIndex;
}
canvas.drawPath(path, linePaint);
if (line.isFilled()) {
drawArea(canvas, line);
}
path.reset();
}
private void drawSquarePath(Canvas canvas, final Line line) {
prepareLinePaint(line);
int valueIndex = 0;
float previousRawY = 0;
for (PointValue pointValue : line.getValues()) {
final float rawX = computator.computeRawX(pointValue.getX());
final float rawY = computator.computeRawY(pointValue.getY());
if (valueIndex == 0) {
path.moveTo(rawX, rawY);
} else {
path.lineTo(rawX, previousRawY);
path.lineTo(rawX, rawY);
}
previousRawY = rawY;
++valueIndex;
}
canvas.drawPath(path, linePaint);
if (line.isFilled()) {
drawArea(canvas, line);
}
path.reset();
}
private void drawSmoothPath(Canvas canvas, final Line line) {
prepareLinePaint(line);
final int lineSize = line.getValues().size();
float prePreviousPointX = Float.NaN;
float prePreviousPointY = Float.NaN;
float previousPointX = Float.NaN;
float previousPointY = Float.NaN;
float currentPointX = Float.NaN;
float currentPointY = Float.NaN;
float nextPointX = Float.NaN;
float nextPointY = Float.NaN;
for (int valueIndex = 0; valueIndex < lineSize; ++valueIndex) {
if (Float.isNaN(currentPointX)) {
PointValue linePoint = line.getValues().get(valueIndex);
currentPointX = computator.computeRawX(linePoint.getX());
currentPointY = computator.computeRawY(linePoint.getY());
}
if (Float.isNaN(previousPointX)) {
if (valueIndex > 0) {
PointValue linePoint = line.getValues().get(valueIndex - 1);
previousPointX = computator.computeRawX(linePoint.getX());
previousPointY = computator.computeRawY(linePoint.getY());
} else {
previousPointX = currentPointX;
previousPointY = currentPointY;
}
}
if (Float.isNaN(prePreviousPointX)) {
if (valueIndex > 1) {
PointValue linePoint = line.getValues().get(valueIndex - 2);
prePreviousPointX = computator.computeRawX(linePoint.getX());
prePreviousPointY = computator.computeRawY(linePoint.getY());
} else {
prePreviousPointX = previousPointX;
prePreviousPointY = previousPointY;
}
}
// nextPoint is always new one or it is equal currentPoint.
if (valueIndex < lineSize - 1) {
PointValue linePoint = line.getValues().get(valueIndex + 1);
nextPointX = computator.computeRawX(linePoint.getX());
nextPointY = computator.computeRawY(linePoint.getY());
} else {
nextPointX = currentPointX;
nextPointY = currentPointY;
}
if (valueIndex == 0) {
// Move to start point.
path.moveTo(currentPointX, currentPointY);
} else {
// Calculate control points.
final float firstDiffX = (currentPointX - prePreviousPointX);
final float firstDiffY = (currentPointY - prePreviousPointY);
final float secondDiffX = (nextPointX - previousPointX);
final float secondDiffY = (nextPointY - previousPointY);
final float firstControlPointX = previousPointX + (LINE_SMOOTHNESS * firstDiffX);
final float firstControlPointY = previousPointY + (LINE_SMOOTHNESS * firstDiffY);
final float secondControlPointX = currentPointX - (LINE_SMOOTHNESS * secondDiffX);
final float secondControlPointY = currentPointY - (LINE_SMOOTHNESS * secondDiffY);
path.cubicTo(firstControlPointX, firstControlPointY, secondControlPointX, secondControlPointY,
currentPointX, currentPointY);
}
// Shift values by one back to prevent recalculation of values that have
// been already calculated.
prePreviousPointX = previousPointX;
prePreviousPointY = previousPointY;
previousPointX = currentPointX;
previousPointY = currentPointY;
currentPointX = nextPointX;
currentPointY = nextPointY;
}
canvas.drawPath(path, linePaint);
if (line.isFilled()) {
drawArea(canvas, line);
}
path.reset();
}
private void prepareLinePaint(final Line line) {
linePaint.setStrokeWidth(ChartUtils.dp2px(density, line.getStrokeWidth()));
linePaint.setColor(line.getColor());
linePaint.setPathEffect(line.getPathEffect());
}
// TODO Drawing points can be done in the same loop as drawing lines but it
// may cause problems in the future with
// implementing point styles.
private void drawPoints(Canvas canvas, Line line, int lineIndex, int mode) {
pointPaint.setColor(line.getPointColor());
int valueIndex = 0;
for (PointValue pointValue : line.getValues()) {
int pointRadius = ChartUtils.dp2px(density, line.getPointRadius());
final float rawX = computator.computeRawX(pointValue.getX());
final float rawY = computator.computeRawY(pointValue.getY());
if (computator.isWithinContentRect(rawX, rawY, checkPrecision)) {
// Draw points only if they are within contentRectMinusAllMargins, using contentRectMinusAllMargins
// instead of viewport to avoid some
// float rounding problems.
if (MODE_DRAW == mode) {
drawPoint(canvas, line, pointValue, rawX, rawY, pointRadius);
if (line.hasLabels()) {
drawLabel(canvas, line, pointValue, rawX, rawY, pointRadius + labelOffset);
}
} else if (MODE_HIGHLIGHT == mode) {
highlightPoint(canvas, line, pointValue, rawX, rawY, lineIndex, valueIndex);
} else {
throw new IllegalStateException("Cannot process points in mode: " + mode);
}
}
++valueIndex;
}
}
private void drawPoint(Canvas canvas, Line line, PointValue pointValue, float rawX, float rawY,
float pointRadius) {
if (ValueShape.SQUARE.equals(line.getShape())) {
canvas.drawRect(rawX - pointRadius, rawY - pointRadius, rawX + pointRadius, rawY + pointRadius,
pointPaint);
} else if (ValueShape.CIRCLE.equals(line.getShape())) {
canvas.drawCircle(rawX, rawY, pointRadius, pointPaint);
canvas.drawCircle(rawX, rawY, pointRadius - 3, pointStrokePaint);//加入空心座標描邊
} else if (ValueShape.DIAMOND.equals(line.getShape())) {
canvas.save();
canvas.rotate(45, rawX, rawY);
canvas.drawRect(rawX - pointRadius, rawY - pointRadius, rawX + pointRadius, rawY + pointRadius,
pointPaint);
canvas.restore();
} else {
throw new IllegalArgumentException("Invalid point shape: " + line.getShape());
}
}
private void highlightPoints(Canvas canvas) {
int lineIndex = selectedValue.getFirstIndex();
Line line = dataProvider.getLineChartData().getLines().get(lineIndex);
drawPoints(canvas, line, lineIndex, MODE_HIGHLIGHT);
}
private void highlightPoint(Canvas canvas, Line line, PointValue pointValue, float rawX, float rawY, int lineIndex,
int valueIndex) {
if (selectedValue.getFirstIndex() == lineIndex && selectedValue.getSecondIndex() == valueIndex) {
int pointRadius = ChartUtils.dp2px(density, line.getPointRadius());
pointPaint.setColor(line.getDarkenColor());
drawPoint(canvas, line, pointValue, rawX, rawY, pointRadius + touchToleranceMargin);
if (line.hasLabels() || line.hasLabelsOnlyForSelected()) {
drawLabel(canvas, line, pointValue, rawX, rawY, pointRadius + labelOffset);
}
}
}
private void drawLabel(Canvas canvas, Line line, PointValue pointValue, float rawX, float rawY, float offset) {
final Rect contentRect = computator.getContentRectMinusAllMargins();
final int numChars = line.getFormatter().formatChartValue(labelBuffer, pointValue);
if (numChars == 0) {
// No need to draw empty label
return;
}
final float labelWidth = labelPaint.measureText(labelBuffer, labelBuffer.length - numChars, numChars);
final int labelHeight = Math.abs(fontMetrics.ascent);
float left = rawX - labelWidth / 2 - labelMargin;
float right = rawX + labelWidth / 2 + labelMargin;
float top;
float bottom;
if (pointValue.getY() >= baseValue) {
top = rawY - offset - labelHeight - labelMargin * 2;
bottom = rawY - offset;
} else {
top = rawY + offset;
bottom = rawY + offset + labelHeight + labelMargin * 2;
}
if (top < contentRect.top) {
top = rawY + offset;
bottom = rawY + offset + labelHeight + labelMargin * 2;
}
if (bottom > contentRect.bottom) {
top = rawY - offset - labelHeight - labelMargin * 2;
bottom = rawY - offset;
}
if (left < contentRect.left) {
left = rawX;
right = rawX + labelWidth + labelMargin * 2;
}
if (right > contentRect.right) {
left = rawX - labelWidth - labelMargin * 2;
right = rawX;
}
labelBackgroundRect.set(left, top, right, bottom);
drawLabelTextAndBackground(canvas, labelBuffer, labelBuffer.length - numChars, numChars,
line.getDarkenColor());
}
private void drawArea(Canvas canvas, Line line) {
final int lineSize = line.getValues().size();
if (lineSize < 2) {
//No point to draw area for one point or empty line.
return;
}
final Rect contentRect = computator.getContentRectMinusAllMargins();
final float baseRawValue = Math.min(contentRect.bottom, Math.max(computator.computeRawY(baseValue),
contentRect.top));
//That checks works only if the last point is the right most one.
final float left = Math.max(computator.computeRawX(line.getValues().get(0).getX()), contentRect.left);
final float right = Math.min(computator.computeRawX(line.getValues().get(lineSize - 1).getX()),
contentRect.right);
path.lineTo(right, baseRawValue);
path.lineTo(left, baseRawValue);
path.close();
linePaint.setStyle(Paint.Style.FILL);
linePaint.setAlpha(line.getAreaTransparency());
canvas.drawPath(path, linePaint);
linePaint.setStyle(Paint.Style.STROKE);
}
private boolean isInArea(float x, float y, float touchX, float touchY, float radius) {
float diffX = touchX - x;
float diffY = touchY - y;
return Math.pow(diffX, 2) + Math.pow(diffY, 2) <= 2 * Math.pow(radius, 2);
}
}
- 解決折線圖使用過程中發現的問題
a.修改折線圖標點的樣式(這裏將其改爲了空心)
關注留心源碼會發現如下方法,CharRenderer就是負責具體的線和節點繪製的:
mLineChartView.setChartRenderer(new MyLineChartRenderer(this, mLineChartView, mLineChartView));
找到了根源現在我們就會想怎麼去複寫自己的CharRenderer替換掉框架自帶的繪製效果
private Paint pointPaint = new Paint();
看到這行代碼我們就明白這個畫筆是用來繪製節點的,那麼我們能不能模仿這個畫筆繪製出我們想要的節點呢
private Paint pointStrokePaint = new Paint();
好我們也新建一個畫筆,代碼往下走,接着模仿pointPaint的屬性設置
pointPaint.setAntiAlias(true);
pointPaint.setStyle(Paint.Style.FILL);
pointStrokePaint.setAntiAlias(true);
pointStrokePaint.setColor(Color.WHITE);
pointStrokePaint.setStyle(Paint.Style.FILL);
到此畫筆建好了,屬性也設置了,接下來看看pointPaint的具體實現
private void drawPoint(Canvas canvas, Line line, PointValue pointValue, float rawX, float rawY,
float pointRadius) {
if (ValueShape.SQUARE.equals(line.getShape())) {
canvas.drawRect(rawX - pointRadius, rawY - pointRadius, rawX + pointRadius, rawY + pointRadius,
pointPaint);
} else if (ValueShape.CIRCLE.equals(line.getShape())) {
canvas.drawCircle(rawX, rawY, pointRadius, pointPaint);
canvas.drawCircle(rawX, rawY, pointRadius - 3, pointStrokePaint);//加入空心座標描邊
} else if (ValueShape.DIAMOND.equals(line.getShape())) {
canvas.save();
canvas.rotate(45, rawX, rawY);
canvas.drawRect(rawX - pointRadius, rawY - pointRadius, rawX + pointRadius, rawY + pointRadius,
pointPaint);
canvas.restore();
} else {
throw new IllegalArgumentException("Invalid point shape: " + line.getShape());
}
}
仔細看這個方法,這裏判定了傳入的節點樣式,然後通過傳入的樣式判斷應該繪製的效果
我們可以在實心圓的上面在繪製一個半徑略小的白底實心圓,然後以白色充當整個背景,就營造出了空心圓的效果
@X、Y座標軸刻度值賦值
Axis axisX = new Axis(); //X軸
Axis axisY = new Axis().setHasLines(true); //Y軸
ArrayList<AxisValue> axisValuesX = new ArrayList<AxisValue>();//定義X軸刻度值的數據集合
for (int i = 0; i < 12; i++) {
String str;
if (i * 2 < 10)
str = "0" + i * 2 + ":00";
else
str = i * 2 + ":00";
axisValuesX.add(new AxisValue(i).setValue(i * 2).setLabel(str));
}
axisX.setValues(axisValuesX);//爲X軸顯示的刻度值設置數據集合
ArrayList<AxisValue> axisValuesY = new ArrayList<AxisValue>();//定義Y軸刻度值的數據集合
for (int i = 0; i < 8; i++) {
axisValuesY.add(new AxisValue(i).setValue((float) (i * 2.5)).setLabel(i * 2.5 + "K"));
}
axisY.setValues(axisValuesY);
axisX.setTextColor(Color.GRAY); //X軸灰色
axisX.setTextSize(8);
axisY.setTextColor(Color.GRAY); //Y軸灰色
axisY.setTextSize(8);
axisX.setHasLines(true);// 是否顯示X軸網格線
axisY.setHasLines(true);// 是否顯示Y軸網格線
//setLineColor():此方法是設置圖表的網格線顏色 並不是軸本身顏色
mLineData.setAxisXBottom(axisX); //設置X軸位置 下方
mLineData.setAxisYLeft(axisY); //設置Y軸位置 左邊
參考博文:hellocharts-android圖表庫使用詳解
b.解決折線圖座標點全爲相同(包括全爲0)時折線不展示的問題
這裏參照大神debug的方法給出座標全爲相同時不展示的原因:
當所有數據一樣時:
通過debug調試發現:在計算RawX,RawY會出現pixelOffset爲NaN的問題。
/**
* Translates chart value into raw pixel value. Returned value is absolute pixel X coordinate. If this method
* return
* 0 that means left most pixel of the screen.
*/
public float computeRawX(float valueX) {
// TODO: (contentRectMinusAllMargins.width() / currentViewport.width()) can be recalculated only when viewport
// change.
final float pixelOffset = (valueX - currentViewport.left) * (contentRectMinusAllMargins.width() /
currentViewport.width());
return contentRectMinusAllMargins.left + pixelOffset;
}
/**
* Translates chart value into raw pixel value. Returned value is absolute pixel Y coordinate. If this method
* return
* 0 that means top most pixel of the screen.
*/
public float computeRawY(float valueY) {
final float pixelOffset = (valueY - currentViewport.bottom) * (contentRectMinusAllMargins.height() /
currentViewport.height());
return contentRectMinusAllMargins.bottom - pixelOffset;
}
當所有點都一樣的時候:computeRawY會爲NaN,原因是:
private void calculateMaxViewport() {
tempMaximumViewport.set(Float.MAX_VALUE, Float.MIN_VALUE, Float.MIN_VALUE, Float.MAX_VALUE);
LineChartData data = dataProvider.getLineChartData();
for (Line line : data.getLines()) {
// Calculate max and min for viewport.
for (PointValue pointValue : line.getValues()) {
if (pointValue.getX() < tempMaximumViewport.left) {
tempMaximumViewport.left = pointValue.getX();
}
if (pointValue.getX() > tempMaximumViewport.right) {
tempMaximumViewport.right = pointValue.getX();
}
if (pointValue.getY() < tempMaximumViewport.bottom) {
tempMaximumViewport.bottom = pointValue.getY();
}
if (pointValue.getY() > tempMaximumViewport.top) {
tempMaximumViewport.top = pointValue.getY();
}
}
}
}
這個方法計算Y軸最大值最小值,當所有數據一樣,計算出的bottom和top相等,導致
computeRawY中currentViewport.height()獲取的值爲0;所以修改如下:
private void calculateMaxViewport() {
tempMaximumViewport.set(Float.MAX_VALUE, Float.MIN_VALUE, Float.MIN_VALUE, Float.MAX_VALUE);
LineChartData data = dataProvider.getLineChartData();
for (Line line : data.getLines()) {
// Calculate max and min for viewport.
for (PointValue pointValue : line.getValues()) {
if (pointValue.getX() < tempMaximumViewport.left) {
tempMaximumViewport.left = pointValue.getX();
}
if (pointValue.getX() > tempMaximumViewport.right) {
tempMaximumViewport.right = pointValue.getX();
}
if (pointValue.getY() < tempMaximumViewport.bottom) {
tempMaximumViewport.bottom = pointValue.getY();
}
if (pointValue.getY() > tempMaximumViewport.top) {
tempMaximumViewport.top = pointValue.getY();
}
}
}
if (tempMaximumViewport.top == tempMaximumViewport.bottom) {//解決最大值最小值相等時,圖不能展示問題
tempMaximumViewport.top = tempMaximumViewport.top * 2;
tempMaximumViewport.bottom = 0;
}
}
參考博文:Android學習筆記-解決hellocharts折線圖由於特殊數據不能展示的問題
上邊的代碼基本上能解決大部分問題了,但是全爲0時,發現並不行,再debug,發現tempMaximumViewport.top爲1.4E-45,好吧,再次修改:(最終版)
private void calculateMaxViewport() {
tempMaximumViewport.set(Float.MAX_VALUE, Float.MIN_VALUE, Float.MIN_VALUE, Float.MAX_VALUE);
LineChartData data = dataProvider.getLineChartData();
for (Line line : data.getLines()) {
// Calculate max and min for viewport.
for (PointValue pointValue : line.getValues()) {
if (pointValue.getX() < tempMaximumViewport.left) {
tempMaximumViewport.left = pointValue.getX();
}
if (pointValue.getX() > tempMaximumViewport.right) {
tempMaximumViewport.right = pointValue.getX();
}
if (pointValue.getY() < tempMaximumViewport.bottom) {
tempMaximumViewport.bottom = pointValue.getY();
}
if (pointValue.getY() > tempMaximumViewport.top) {
tempMaximumViewport.top = pointValue.getY();
}
}
}
if (tempMaximumViewport.top == tempMaximumViewport.bottom) {//解決最大值最小值相等時,圖不能展示問題
tempMaximumViewport.top = tempMaximumViewport.top * 2;
tempMaximumViewport.bottom = 0;
}
if ((tempMaximumViewport.top + "").equals(1.4E-45 + "")) {//解決全爲0時,圖不展示的問題。
tempMaximumViewport.top = 1;
tempMaximumViewport.bottom = 0;
}
}
d.怎樣隔點展示座標
項目需求是每隔3個點展示一次橫座標, mAxisXValues.add(new AxisValue(i).setLabel(""));是添加橫座標的代碼,setLabel的值就是展示的橫座標的值,所以就用以下方式解決。
int dateNum = visitBeanList.size();
int remainderNum = (visitBeanList.size() - 1) % spaceNum;
for (int i = 0; i < dateNum; i++) {
//x的集合
if (i % spaceNum == remainderNum) {
mAxisXValues.add(new AxisValue(i).setLabel(visitBeanList.get(i).getTime()));
} else {
mAxisXValues.add(new AxisValue(i).setLabel(""));
}
}
e.怎樣固定x軸展示的座標個數,x軸滑動展示,不讓全一次性展示。
這個是我找效果找的時間最長的一個,具體參考文章:hellocharts實現y軸固定和x軸滑動效果
重點代碼在setLineChartData之後設置setMaximumViewport和setCurrentViewport,注意順序!!!
/**注:下面的7,10只是代表一個數字去類比而已
* 當時是爲了解決X軸固定數據個數。見(http://forum.xda-developers.com/tools/programming/library-hellocharts-charting-library-t2904456/page2);
*/
Viewport v = new Viewport(lineChartViewTrend.getMaximumViewport());
v.bottom = 0f;
v.top = 100f;
//固定Y軸的範圍,如果沒有這個,Y軸的範圍會根據數據的最大值和最小值決定,這不是我想要的
lineChartViewTrend.setMaximumViewport(v);
//這2個屬性的設置一定要在lineChart.setMaximumViewport(v);這個方法之後,不然顯示的座標數據是不能左右滑動查看更多數據的
v.left = visitBeanList.size() - 8;
v.right = visitBeanList.size() - 1;
lineChartViewTrend.setCurrentViewport(v);
效果: