Linechart的简单使用

折线图的绘制

折线图可以说是绘制最频繁的了,我们从官方的demo中看看LineChart可以怎么玩?

构建数据

不管画什么,我们总得有个数据集合吧。后端的同志们给我们的是一个List集合,那LineChart是如何封装数据集合的呢?我们来瞧瞧代码:

private void setData(int count, float range) {
        ArrayList values = new ArrayList();
        for (int i = 0; i < count; i++) {
            float val = (float) (Math.random() * range) + 3;
            values.add(new Entry(i, val));
        }
        LineDataSet set1 = new LineDataSet(values, "DataSet 1");
        set1.setDrawIcons(false);
        // set the line to be drawn like this "- - - - - -"
            set1.enableDashedLine(10f, 5f, 0f);
            set1.enableDashedHighlightLine(10f, 5f, 0f);
            // 设置线的颜色
            set1.setColor(Color.BLACK);
            //设置数据点圆的颜色
            set1.setCircleColor(Color.BLACK);
            //设置线的宽度
            set1.setLineWidth(1f);
            //设置数据点圆的半径
            set1.setCircleRadius(3f);
            //设置数据点圆是否空心
            set1.setDrawCircleHole(true);
            //设置数据点值得字的大小
            set1.setValueTextSize(9f);
            //设置是否要折线下面部分铺满颜色
            set1.setDrawFilled(true);
            //设置图例线的宽度
            set1.setFormLineWidth(1f);
            //设置图例线的样式
            set1.setFormLineDashEffect(new DashPathEffect(new float[]{10f, 5f}, 0f));
            //设置图例的文字大小
            set1.setFormSize(15.f);
        ArrayList dataSets = new ArrayList();
        dataSets.add(set1); // add the datasets
        // create a data object with the datasets
        LineData data = new LineData(dataSets);
        // set data
        mChart.setData(data);
        }
    }

从这段代码中可以看到一个for循环,循环中做的一件事就是在 new Entry(i, val)。可以知道这是一个一个的点,通过这些点,就可以连成一条线,即 LineDataSet(values, “DataSet 1”);那LineData就包含很多条线了。LineDataSet有很多的set方法,这些作用看注释,或者亲自动手改改代码,看效果,就知道是干啥的了,应该很容易理解的。数据是有了,可以由点成线了。那把点画到哪个地方去呢?那就是要先有个坐标轴了。

坐标轴

找了下整个项目,发现了两个类,横坐标轴:XAxis,纵坐标轴:YAxis。他们共有的一个父类AxisBase。AxisBase中定义了很多属性,我列举一些常用的属性:

/**
     * custom formatter that is used instead of the auto-formatter if set
     */
    protected IAxisValueFormatter mAxisValueFormatter;
    protected IAxisEntryFormatter mAxisEntryFormatter;
    //坐标轴内的网格颜色
    private int mGridColor = Color.GRAY;
    //坐标轴内的网格宽度
    private float mGridLineWidth = 1f;
   //坐标轴颜色
    private int mAxisLineColor = Color.GRAY;
    //坐标轴宽度
    private float mAxisLineWidth = 1f;

    /**
     * the number of entries the legend contains
     */
    public int mEntryCount;

    /**
     * if true, the set number of y-labels will be forced
     */
    protected boolean mForceLabels = false;

    /**
     * flag indicating if the grid lines for this axis should be drawn
     */
    protected boolean mDrawGridLines = true;

    /**
     * flag that indicates if the line alongside the axis is drawn or not
     */
    protected boolean mDrawAxisLine = true;

    /**
     * flag that indicates of the labels of this axis should be drawn or not
     */
    protected boolean mDrawLabels = true;

    /**
     * array of limit lines that can be set for the axis
     */
    protected List mLimitLines;

    /**
     * flag indicating the limit lines layer depth
     */
    protected boolean mDrawLimitLineBehindData = false;

    /**
     * Extra spacing for `axisMinimum` to be added to automatically calculated `axisMinimum`
     */
    protected float mSpaceMin = 0.f;

    /**
     * Extra spacing for `axisMaximum` to be added to automatically calculated `axisMaximum`
     */
    protected float mSpaceMax = 0.f;
    /**
     * don't touch this direclty, use setter 
     * 最大的那个点是第几个
     */
    public float mAxisMaximum = 0f;

    /**
     * don't touch this directly, use setter
     */
    public float mAxisMinimum = 0f;

    /**
     * the total range of values this axis covers
     */
    public float mAxisRange = 0f;

LimitLine又是什么呢?这个字面意思上来理解就是限制线,是一条警戒线,这条线一画,就知道哪些数据点超过了这条线。这条线可以是横向的也可以是竖向的,因为它是写在父类中。再来看看XAxis有哪些属性吧:

/**
     * width of the x-axis labels in pixels - this is automatically
     * calculated by the computeSize() methods in the renderers
     * 坐标轴对应的坐标值的宽度,由computeSize()方法根据字体大小计算
     */
    public int mLabelWidth = 1;

    /**
     * height of the x-axis labels in pixels - this is automatically
     * calculated by the computeSize() methods in the renderers
     * 坐标轴对应的坐标值的高度,由computeSize()方法根据字体大小计算
     */
    public int mLabelHeight = 1;

    /**
     * width of the (rotated) x-axis labels in pixels - this is automatically
     * calculated by the computeSize() methods in the renderers
     * 旋转坐标值后的宽度,并不会旋转坐标轴
     */
    public int mLabelRotatedWidth = 1;

    /**
     * height of the (rotated) x-axis labels in pixels - this is automatically
     * calculated by the computeSize() methods in the renderers
     * 旋转坐标值后的高度,并不会旋转坐标轴
     */
    public int mLabelRotatedHeight = 1;

    /**
     * This is the angle for drawing the X axis labels (in degrees)
     * 旋转坐标值的角度
     */
    protected float mLabelRotationAngle = 0f;

    /**
     * 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
     */
    private boolean mAvoidFirstLastClipping = false;

    /**
     * the position of the x-labels relative to the chart
     */
    private XAxisPosition mPosition = XAxisPosition.TOP;

    /**
     * enum for the position of the x-labels relative to the chart
     */
    public enum XAxisPosition {
        TOP, BOTTOM, BOTH_SIDED, TOP_INSIDE, BOTTOM_INSIDE
    }

上面有个枚举,XAxisPosition,有TOP, BOTTOM, BOTH_SIDED, TOP_INSIDE, BOTTOM_INSIDE这么多种类型。我设置运行了一下。原来这个是表示横坐标轴的位置。

  • TOP - - - - - - - - - - - - 坐标轴位于chart的顶端,坐标值位于坐标的外部。
  • TOP_INSIDE - - - - - -坐标轴位于chart的顶端,但坐标值位于坐标的内(下)部。
  • BOTTOM - - - - - - - - -坐标轴位于chart的底部,坐标值位于坐标的外部。
  • BOTTOM_INSIDE - - 坐标轴位于chart的底部,坐标值位于坐标的内(上)部。
  • BOTH_SIDED, - - - - -坐标轴上下两端都有,坐标值位于坐标轴的外部。

再来看看YAxis:

/**
     * indicates if the bottom y-label entry is drawn or not
     * 是否要画Y轴的最下面的那个坐标值
     */
    private boolean mDrawBottomYLabelEntry = true;

    /**
     * indicates if the top y-label entry is drawn or not
     * 是否要画Y轴的最上面的那个坐标值
     */
    private boolean mDrawTopYLabelEntry = true;

    /**
     * flag that indicates if the axis is inverted or not
     * Y轴是否反向颠倒
     */
    protected boolean mInverted = false;

    /**
     * flag that indicates if the zero-line should be drawn regardless of other grid lines
     * 是否要画出数据为0的那条线
     */
    protected boolean mDrawZeroLine = false;

    /**
     * Color of the zero line
     */
    protected int mZeroLineColor = Color.GRAY;

    /**
     * Width of the zero line in pixels
     */
    protected float mZeroLineWidth = 1f;

    /**
     * axis space from the largest value to the top in percent of the total axis range
     * 留一部分空白的空间,让最大值可以不贴近屏幕最上方,方便用户操作。
     */
    protected float mSpacePercentTop = 10f;

    /**
     * axis space from the smallest value to the bottom in percent of the total axis range
     * 留一部分空白的空间,让最小值可以不贴近屏幕最下方,方便用户操作。
     */
    protected float mSpacePercentBottom = 10f;

    /**
     * the position of the y-labels relative to the chart
     */
    private YAxisLabelPosition mPosition = YAxisLabelPosition.OUTSIDE_CHART;

    /**
     * enum for the position of the y-labels relative to the chart
     * 这个容易理解,坐标值在坐标轴的外部还是内部
     */
    public enum YAxisLabelPosition {
        OUTSIDE_CHART, INSIDE_CHART
    }

    /**
     * the side this axis object represents
     */
    private AxisDependency mAxisDependency;

    /**
     * the minimum width that the axis should take (in dp).
     * 

* default: 0.0 */ protected float mMinWidth = 0.f; /** * the maximum width that the axis can take (in dp). * use Inifinity for disabling the maximum * default: Float.POSITIVE_INFINITY (no maximum specified) */ protected float mMaxWidth = Float.POSITIVE_INFINITY; /** * Enum that specifies the axis a DataSet should be plotted against, either LEFT or RIGHT. * * @author Philipp Jahoda */ public enum AxisDependency { LEFT, RIGHT }

上面代码的最后几行有一个枚举AxisDependency,即坐标依赖。居然还分两个左右坐标轴哈。平常嘛,一个坐标轴就搞定了啊。啥时候会用到两个Y坐标轴呢?我在画股票分时图的时候用到过。数据,坐标都有了,如果没有其他特殊需求,设置好数据和坐标的属性,就可以调用方法画图,完事了。

        XAxis xAxis = mChart.getXAxis();
        xAxis.enableGridDashedLine(10f, 10f, 0f);

        LimitLine ll1 = new LimitLine(150f, "Upper Limit");
        ll1.setLineWidth(4f);
        ll1.enableDashedLine(10f, 10f, 0f);
        ll1.setLabelPosition(LimitLabelPosition.RIGHT_TOP);
        ll1.setTextSize(10f);

        LimitLine ll2 = new LimitLine(-30f, "Lower Limit");
        ll2.setLineWidth(4f);
        ll2.enableDashedLine(10f, 10f, 0f);
        ll2.setLabelPosition(LimitLabelPosition.RIGHT_BOTTOM);
        ll2.setTextSize(10f);

        YAxis leftAxis = mChart.getAxisLeft();
        // reset all limit lines to avoid overlapping lines
        leftAxis.removeAllLimitLines(); 
        leftAxis.addLimitLine(ll1);
        leftAxis.addLimitLine(ll2);
        leftAxis.setAxisMaximum(200f);
        leftAxis.setAxisMinimum(-50f);
        leftAxis.enableGridDashedLine(10f, 10f, 0f);
        leftAxis.setDrawZeroLine(false);
        // limit lines are drawn behind data (and not on top)
        leftAxis.setDrawLimitLinesBehindData(true);
        mChart.getAxisRight().setEnabled(false);
        // add data
        setData(45, 100);

设置数据刷新就好了,可作者是怎么去画的呢?我们先想想,如果是我们自己来绘制的话,我们应该先绘制什么,后绘制什么?我们先绘制背景,再绘制坐标轴,再绘制数据点,连线,然后绘制坐标值,图例,等等。因为后面绘制的东西会覆盖在已经绘制好的上面。我们来仔细瞧瞧。这个画图的代码在BarLineChartBase中的OnDraw()方法中,我把计算时间的,无效代码去掉了。

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (mData == null)
            return;
        //绘制背景
        drawGridBackground(canvas);
        //根据设置好的数据调整间距Chart大小
        if (mAutoScaleMinMaxEnabled) {
            autoScale();
        }
        //根据设置好的数据调整坐标轴
        if (mAxisLeft.isEnabled())
            mAxisRendererLeft.computeAxis(mAxisLeft.mAxisMinimum, mAxisLeft.mAxisMaximum, mAxisLeft.isInverted());
        if (mAxisRight.isEnabled())
            mAxisRendererRight.computeAxis(mAxisRight.mAxisMinimum, mAxisRight.mAxisMaximum, mAxisRight.isInverted());
        if (mXAxis.isEnabled())
            mXAxisRenderer.computeAxis(mXAxis.mAxisMinimum, mXAxis.mAxisMaximum, false);
        //画坐标轴
        mXAxisRenderer.renderAxisLine(canvas);
        mAxisRendererLeft.renderAxisLine(canvas);
        mAxisRendererRight.renderAxisLine(canvas);
        //画坐标轴内的网格线
        mXAxisRenderer.renderGridLines(canvas);
        mAxisRendererLeft.renderGridLines(canvas);
        mAxisRendererRight.renderGridLines(canvas);
        //满足条件后画限制线或者说是警戒线
        if (mXAxis.isEnabled() && mXAxis.isDrawLimitLinesBehindDataEnabled())
            mXAxisRenderer.renderLimitLines(canvas);
        if (mAxisLeft.isEnabled() && mAxisLeft.isDrawLimitLinesBehindDataEnabled())
            mAxisRendererLeft.renderLimitLines(canvas);
        if (mAxisRight.isEnabled() && mAxisRight.isDrawLimitLinesBehindDataEnabled())
            mAxisRendererRight.renderLimitLines(canvas);
        // make sure the data cannot be drawn outside the content-rect 

        int clipRestoreCount = canvas.save();
        canvas.clipRect(mViewPortHandler.getContentRect());
        //画数据
        mRenderer.drawData(canvas);

        // if highlighting is enabled 画高亮线
        if (valuesToHighlight())
            mRenderer.drawHighlighted(canvas, mIndicesToHighlight);

        // Removes clipping rectangle
        canvas.restoreToCount(clipRestoreCount);
        //画其他额外的
        mRenderer.drawExtras(canvas);
        //满足条件后画限制线或者说是警戒线
        if (mXAxis.isEnabled() && !mXAxis.isDrawLimitLinesBehindDataEnabled())
            mXAxisRenderer.renderLimitLines(canvas);
        if (mAxisLeft.isEnabled() && !mAxisLeft.isDrawLimitLinesBehindDataEnabled())
            mAxisRendererLeft.renderLimitLines(canvas);
        if (mAxisRight.isEnabled() && !mAxisRight.isDrawLimitLinesBehindDataEnabled())
            mAxisRendererRight.renderLimitLines(canvas);
        //画坐标值
        mXAxisRenderer.renderAxisLabels(canvas);
        mAxisRendererLeft.renderAxisLabels(canvas);
        mAxisRendererRight.renderAxisLabels(canvas);
        //画数据点的值
        if (isClipValuesToContentEnabled()) {
            clipRestoreCount = canvas.save();
            canvas.clipRect(mViewPortHandler.getContentRect());
            mRenderer.drawValues(canvas);
            canvas.restoreToCount(clipRestoreCount);
        } else {
            mRenderer.drawValues(canvas);
        }
        //画图例
        mLegendRenderer.renderLegend(canvas);
        //画描述
        drawDescription(canvas);
       //画标记值
        drawMarkers(canvas);      
    }

果然和我们想的差不多哈。但我们看到绘制限制线或者说是警戒线的代码写了两次,看了if判断,就应该知道,这个限制线可以绘制在数据点的下面,也可以绘制在数据点的上面。看你喜好了。你可能不能理解drawHighlighted(画高亮线),drawMarkers(画标记值)是在绘制什么呢?啥时候需要绘制他们呢?

当你们的产品继续给你提需求的时候,你可能就慢慢的理解了。你以为把数据展示出来就好了,他们却和你说,数据太多了,看不到每一个数据的值,能不能在手指触摸到哪个数据的时候,在这个数据旁边展示出数据的值呢?所以呢,高亮线,就是触摸屏幕的时候,你手指触摸的那个点为中心,画出的十字交叉线,数据旁边展示出数据的值,就是标记值。MPAndroidChart已经为你封装好了这部分的需求,你只需要设置监听,自定义自己的Marker,实现起来,就没有太大问题。

这一片博客要接近尾声了,写太长了,大家注意力会慢慢下降的。在下一篇博客中,我再和大家分享drawHighlighted和drawMarkers吧。

你可能感兴趣的:(Android)