Linechart的簡單使用

折線圖的繪製

折線圖可以說是繪製最頻繁的了,我們從官方的demo中看看LineChart可以怎麼玩?

構建數據

不管畫什麼,我們總得有個數據集合吧。後端的同志們給我們的是一個List集合,那LineChart是如何封裝數據集合的呢?我們來瞧瞧代碼:

private void setData(int count, float range) {
        ArrayList<Entry> values = new ArrayList<Entry>();
        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<ILineDataSet> dataSets = new ArrayList<ILineDataSet>();
        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<LimitLine> 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).
     * <p/>
     * 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吧。

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