屋~~~斯~(・ωー)~☆

今天要來聊聊"如何在手機(Android)裝置上繪製圖表"~

講講自己當初學這個的原因

主管:欸欸OO(我的名字),這個專案啊...客人希望可以做一個圖表,可以嗎?

我:喔...我找看看資料,等我(OS: 草..有這種東西?)(゚д゚;)

maxresdefault

沒錯。還真的有!只是是利用第三方函式庫(third-party-code)完成的功能

而我當初搜尋到的就是今天要介紹的這個函式庫-MPAndroidChart

資源在此->https://github.com/PhilJay/MPAndroidChart

當然,還是要特別提一下,能夠繪製圖表的函式庫也不僅此一家

我另外還在網路上找到了也同是繪製圖表的第三方函式庫-HelloCharts

資源在此->https://github.com/lecho/hellocharts-android

不過因為MPChart似乎比較知名~因此就學了MPChart了(´・з・)

 

OK,那今天的主題就是要來完成以下樣式

圖表初步完成

Screenshot_20200125-185214_LineChartExample

GitHub:

https://github.com/thumbb13555/LineChartExample/tree/master

開始!


0. 程式架構、載入函式庫與製作流程介紹

0-1 程式架構

首先來提一下我整個程式的架構

今天的範例用了兩個類別,增加了兩個Layout以及一張圖片

如下

截圖 2020-01-25 下午9.11.47

png的資源這裡可以載

https://github.com/thumbb13555/LineChartExample/blob/master/app/src/main/res/drawable-v24/marker2.png

以及兩個Layout的介面在此,直接複製就好

activity_main.xml

https://github.com/thumbb13555/LineChartExample/blob/master/app/src/main/res/layout/activity_main.xml

custom_marker_view.xml

https://github.com/thumbb13555/LineChartExample/blob/master/app/src/main/res/layout/custom_marker_view.xml

 

關於custom_marker_view還有那張圖片是為了要做點擊標籤用的(・∀・)

Screenshot_20200125-185222_LineChartExample拷貝

最後是兩個類別裡的副程式架構

MainActivity

截圖 2020-01-25 下午9.08.21

MyMarkerView

截圖 2020-01-25 下午9.08.40

0-2 載入函式庫

首先先到MPChart的官方GitHub

https://github.com/PhilJay/MPAndroidChart

拉到這邊

https://github.com/PhilJay/MPAndroidChart#quick-start-chart_with_upwards_trend

截圖 2020-01-25 下午9.27.56

 

首先複製repositories的部分

repositories {
    maven { url 'https://jitpack.io' }
}

然後貼在Android studio的build.gradle內(所有大括號外)

然後複製這個

dependencies {
    implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'
}

一樣貼在Android studio的build.gradledependencies

截圖 2020-01-25 下午9.37.33

最後一樣按下Sync,完成!

截圖 2019-12-29 上午1.30.39

0-3 製作流程

  1. 製作假資料
  2. 設定圖表X、Y軸
  3. 載入圖表數值
  4. 自定義X軸
  5. 加入點擊標籤
  6. 重置圖表

 

1. 製作假資料

這部分就不多做解釋了,直接將副程式放進程式中即可

private ArrayList<HashMap<String,String>> makeFakeData(){//製造假資料
        ArrayList<HashMap<String,String>> arrayList = new ArrayList<>();
        for (int i=0;i<100;i++){
            HashMap<String,String> hashMap = new HashMap<>();
            hashMap.put("FirstData", "25.6");
            hashMap.put("SecondData", "50.2");
            arrayList.add(hashMap);

        }
        for (int i=0;i<100;i++){
            HashMap<String,String> hashMap = new HashMap<>();
            hashMap.put("FirstData", "26.3");
            hashMap.put("SecondData", "50.9");
            arrayList.add(hashMap);

        }
        for (int i=0;i<100;i++){
            HashMap<String,String> hashMap = new HashMap<>();
            hashMap.put("FirstData", "28.1");
            hashMap.put("SecondData", "62.2");
            arrayList.add(hashMap);

        }
        for (int i=0;i<100;i++){
            HashMap<String,String> hashMap = new HashMap<>();
            hashMap.put("FirstData", "30.4");
            hashMap.put("SecondData", "60.0");
            arrayList.add(hashMap);

        }
        for (int i=0;i<100;i++){
            HashMap<String,String> hashMap = new HashMap<>();
            hashMap.put("FirstData", "30.0");
            hashMap.put("SecondData", "58.3");
            arrayList.add(hashMap);

        }
        return arrayList;
    }

 

1-1 準備X軸會用到的陣列

再來要來準備X軸會用到的資料

一般來說,多數圖表X軸都會放時間軸比較多...但是我暫時不去搞時間的陣列,我先放筆數就好

因此加入這樣

ArrayList<HashMap<String,String>> mData = makeFakeData();
        for (int i=0;i<mData.size();i++){//自定義X軸標籤(一般為時間)
            customxLable.add("第"+(i+1)+"筆");
        }

備註:customxLable是宣告在全域變數中的ArrayList<String>,要記得放喔

全域變數內容:

public class MainActivity extends AppCompatActivity {
    public static final String TAG = MainActivity.class.getSimpleName()+"My";
    LineChart chart;
    ArrayList<String> customxLable = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        chart = (LineChart)findViewById(R.id.linechart);
        
        setChart();
     
    }//onCreate
     private void setChart(){
       ArrayList<HashMap<String,String>> mData = makeFakeData();
       for (int i=0;i<mData.size();i++){//自定義X軸標籤(一般為時間)
           customxLable.add("第"+(i+1)+"筆"); 
        }
    }
}

 

2. 設置圖表X軸與Y軸畫面配置

在MPChart中有很多API可供只用,因此像是顯示格線、調整文字樣式等等多數都可以一行就搞定

那我就針對本篇欲實現的樣式來設置

        /**設定圖表框架↓*/
        YAxis leftAxis = chart.getAxisLeft();//設置Y軸(左)
        YAxis rightAxis = chart.getAxisRight();//設置Y軸(右)
        rightAxis.setEnabled(false);//讓右邊Y消失
        XAxis xAxis = chart.getXAxis();//設定X軸
        xAxis.setPosition(XAxis.XAxisPosition.BOTTOM);//將x軸表示字移到底下
        xAxis.setLabelCount(3,false);//設定X軸上要有幾個標籤
        chart.getDescription().setEnabled(false);//讓右下角文字消失
//      xAxis.setEnabled(false);//去掉X軸數值
//      xAxis.setLabelRotationAngle(-45f);//讓字變成斜的
        xAxis.setDrawGridLines(false);//將X軸格子消失掉
        /**設定圖表框架↑*/

 

3. 載入圖表數值

接著要來載入圖表數值

首先原理是:可以理解為每一條線就是一個集合陣列

故需求為兩條陣列,就必須新增兩個ArrayList

其陣列載入值為:Entry

        /**載入資料↓*/

        ArrayList<Entry> yValues1 = new ArrayList<>();
        ArrayList<Entry> yValues2 = new ArrayList<>();

再來將前面陣列的值載入ArrayList<Entry>集合中

        /**載入資料↓*/

        ArrayList<Entry> yValues1 = new ArrayList<>();
        ArrayList<Entry> yValues2 = new ArrayList<>();

        for (int i=0;i<mData.size();i++){
            float getFirst = Float.parseFloat(mData.get(i).get("FirstData"));
            float getSecond = Float.parseFloat(mData.get(i).get("SecondData"));
            yValues1.add(new Entry(i,getFirst));
            yValues2.add(new Entry(i,getSecond));

        }

注意:值僅能以Float浮點數操作,因此在載入值時請注意將數字轉型為浮點數(Float)

再來在下面新增這些程式

        LineDataSet set1 = new LineDataSet(yValues1, "溫度");
        LineDataSet set2 = new LineDataSet(yValues2, "濕度");
        setChartImage(set1,1);//設置圖表線1
        setChartImage(set2,2);//設置圖表線2

        chart.animateX(2000);

        ArrayList<ILineDataSet> dataSets = new ArrayList<>();
        leftAxis.setAxisMaximum(100f);//設置上限
        leftAxis.setAxisMinimum(0f);//設置下限
        //其實上下限可以不用刻意設置,圖表會自動依數值調整到最適合的範圍
        dataSets.add(set1);
        dataSets.add(set2);
        LineData lineData = new LineData(dataSets);
        chart.setData(lineData);
        /**載入資料↑*/

複製上去的話,紅色部分會報錯

請將以下的副程式加在與setChart同一階層

      private void setChartImage(LineDataSet set,int i){//設置圖表線
        if (i == 1){
            set.setColor(Color.parseColor("#F85353"));//紅色
            set.setCircleColor(Color.parseColor("#F85353"));//調整小圈圈的顏色
        }else if (i==2){
            set.setColor(Color.parseColor("#1E88A8"));//藍色
            set.setCircleColor(Color.parseColor("#1E88A8"));//調整小圈圈的顏色
        }
        set.setCircleRadius(0.5f);
        set.setLineWidth(1.5f);//條粗細
        set.setValueTextSize(9f);

        LineData dataText = new LineData(set);
        dataText.setDrawValues(false);//不要再點上顯示值
    }

到這邊為止,我們試著執行看看,就可以跑出基本圖表囉(`・ω・´)+

圖表完成全部

這是目前的程式:

onCreate部分

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        chart = (LineChart)findViewById(R.id.linechart);

        setChart();
        

    }

setChart部分

private void setChart() {

        ArrayList<HashMap<String,String>> mData = makeFakeData();
        for (int i=0;i<mData.size();i++){//自定義X軸標籤(一般為時間)
            customxLable.add("第"+(i+1)+"筆");
        }

        /**設定圖表框架↓*/
        YAxis leftAxis = chart.getAxisLeft();//設置Y軸(左)
        YAxis rightAxis = chart.getAxisRight();//設置Y軸(右)
        rightAxis.setEnabled(false);//讓右邊Y消失
        XAxis xAxis = chart.getXAxis();//設定X軸
        xAxis.setPosition(XAxis.XAxisPosition.BOTTOM);//將x軸表示字移到底下
        xAxis.setLabelCount(3,false);//設定X軸上要有幾個標籤
        chart.getDescription().setEnabled(false);//讓右下角文字消失
//      xAxis.setEnabled(false);//去掉X軸數值
//      xAxis.setLabelRotationAngle(-45f);//讓字變成斜的
        xAxis.setDrawGridLines(false);//將X軸格子消失掉
       
        /**設定圖表框架↑*/

        /**載入資料↓*/

        ArrayList<Entry> yValues1 = new ArrayList<>();
        ArrayList<Entry> yValues2 = new ArrayList<>();

        for (int i=0;i<mData.size();i++){
            float getFirst = Float.parseFloat(mData.get(i).get("FirstData"));
            float getSecond = Float.parseFloat(mData.get(i).get("SecondData"));
            yValues1.add(new Entry(i,getFirst));
            yValues2.add(new Entry(i,getSecond));

        }
        LineDataSet set1 = new LineDataSet(yValues1, "溫度");
        LineDataSet set2 = new LineDataSet(yValues2, "濕度");
        setChartImage(set1,1);//設置圖表線1
        setChartImage(set2,2);//設置圖表線2

        chart.animateX(2000);

        ArrayList<ILineDataSet> dataSets = new ArrayList<>();
        leftAxis.setAxisMaximum(100f);//設置上限
        leftAxis.setAxisMinimum(0f);//設置下限
        //其實上下限可以不用刻意設置,圖表會自動依數值調整到最適合的範圍
        dataSets.add(set1);
        dataSets.add(set2);
        LineData lineData = new LineData(dataSets);
        chart.setData(lineData);
        /**載入資料↑*/
    }

setChartImage部分

private void setChartImage(LineDataSet set,int i){//設置圖表線
        if (i == 1){
            set.setColor(Color.parseColor("#F85353"));//紅色
            set.setCircleColor(Color.parseColor("#F85353"));//調整小圈圈的顏色
        }else if (i==2){
            set.setColor(Color.parseColor("#1E88A8"));//藍色
            set.setCircleColor(Color.parseColor("#1E88A8"));//調整小圈圈的顏色
        }
        set.setCircleRadius(0.5f);
        set.setLineWidth(1.5f);//條粗細
        set.setValueTextSize(9f);

        LineData dataText = new LineData(set);
        dataText.setDrawValues(false);//不要再點上顯示值
    }

 

4. 自定義X軸

自定義X軸到部份,首先要了解設置X軸所使用的副程式是繼承ValueFormatter使用

實作方法為:在與setChart()同一階層內,設置一個私用的class

截圖 2020-01-25 下午11.30.38

框架為

private class MyValueFormatter extends ValueFormatter{
        @Override
        public String getFormattedValue(float value) {
            return super.getFormattedValue(value);
        }
    }

首先在使用前要特別注意

首先設個複寫方法所回傳的value是原先x軸的數值

也因此如果轉型為int的話,該值就可以當作是陣列索引使用

並且每次若有滑動、放大、縮小等操作,該複寫就會被執行

因此,在這邊不建議將連同製造陣列的程式一起寫在內,否則圖表執行起來會卡頓(´・_・`)

OK,廢話這麼多那倒底怎麼處理

其實很簡單,在前面不是有先寫過一個customxLable的陣列嗎?

我們就是要用這個去實踐

因此程式為

    private class MyValueFormatter extends ValueFormatter{//設置X軸
        @Override
        public String getFormattedValue(float value) {
            return customxLable.get((int) value);
        }
    }

最後在副程式setChart()內隨便一個地方加入(要在XAxis xAxis = chart.getXAxis();  底下歐)

xAxis.setValueFormatter(new MyValueFormatter());//設置X軸

到這邊再按一次執行,就可以看到x軸是我們想要的形狀囉(・ω・)b

5. 設置點擊標籤

再來我們要來完成這個功能

Screenshot_20200125-185222_LineChartExample拷貝

點擊下去後要有標籤

首先下載我提供的圖檔以及Layout

png :

https://github.com/thumbb13555/LineChartExample/blob/master/app/src/main/res/drawable-v24/marker2.png

custom_marker_view.xml

https://github.com/thumbb13555/LineChartExample/blob/master/app/src/main/res/layout/custom_marker_view.xml

以及檢查增加一個類別叫MyMarkerView (名稱自行定義)

再來在MyMarkerView底下寫入這些程式(直接複製就好)

package com.jetec.linechartexample;

import android.annotation.SuppressLint;
import android.content.Context;
import android.widget.TextView;
import com.github.mikephil.charting.components.MarkerView;
import com.github.mikephil.charting.data.CandleEntry;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.highlight.Highlight;
import com.github.mikephil.charting.utils.MPPointF;
import com.github.mikephil.charting.utils.Utils;

/**
 * Custom implementation of the MarkerView.
 *
 * @author Philipp Jahoda
 */
@SuppressLint("ViewConstructor")
public class MyMarkerView extends MarkerView {

    private final TextView tvContent;

    public MyMarkerView(Context context, int layoutResource) {
        super(context, layoutResource);

        tvContent = findViewById(R.id.tvContent);//custom_marker_view.xml內的元件
    }

    // runs every time the MarkerView is redrawn, can be used to update the
    // content (user-interface)
    @Override
    public void refreshContent(Entry e, Highlight highlight) {//取得圖表相關值

        if (e instanceof CandleEntry) {

            CandleEntry ce = (CandleEntry) e;

            tvContent.setText(Utils.formatNumber(ce.getHigh(), 1, true));//設定
        } else {

            tvContent.setText(Utils.formatNumber(e.getY(), 1, true));//設定
        }

        super.refreshContent(e, highlight);
    }

    @Override
    public MPPointF getOffset() {
        return new MPPointF(-(getWidth() / 2), -getHeight());
    }
}

再回到MainActivity,在副程式setChart()內隨便一個地方加入以下程式

MyMarkerView mv = new MyMarkerView(this, R.layout.custom_marker_view);//設置點擊標籤
chart.setMarker(mv);//設置點擊標籤

按下執行!搞定!(●`・皿・)

6. 重置圖表

其實MPChart是可以支援即時更新的,但是礙於難度我想今天就先不做┐(´д`)┌

我這邊只是想展示如何讓圖表重跑

簡單暴力就是:消滅現有圖表,再讓跑圖表的副程式重新跑一次XD

我在onCreate設置了一個按鈕,然後加入以下程式

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        chart = (LineChart)findViewById(R.id.linechart);

        setChart();
        Button btReset = findViewById(R.id.button_reSet);
        btReset.setOnClickListener((v)->{
            chart.clear();
            setChart();
        });

    }

即完成(´υ`)


結論

其實當時我接到這項功能的時候,身為菜逼八的我真的是壓力山大RRR

同時也真的很感謝這世界上有這麼多很棒的人願意免費的函式庫供我們享用

這個MPChart函式庫應該是一個小團隊一起寫的

如果覺得他們寫得好,記得斗內一下他們哦(っ・∀・)っ

連結在此->https://github.com/PhilJay/MPAndroidChart#donations-heart

本人我窮光蛋,我只斗內了5塊美金QQ

另外我基於工作緣故,我有測試過圖表大概支援多少筆資料

我當時的專案是兩條曲線,當時每一條是6000筆

如果是以三星A7(2019款)規格的話,他大概跑到4500筆左右會開始頓(注意是兩條曲線)

我主管是旗艦機,跑8000筆左右會小頓(兩條曲線)

那今天的文章到這,感謝您點進來為我增加流量(喂ノಠ_ಠノ

594042-2

arrow
arrow

    碼農日常 發表在 痞客邦 留言(6) 人氣()