helloChart的使用及常見問題解決(折線圖)

最近項目中有展示折線圖和圓形圖的需求,決定使用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);

效果:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章