這篇再來寫一次圖表!

不過這次的內容是"實時"圖表~

什麼是"實時"圖表呢?

就是指此圖表在某些變數產生的當下會瞬間反應成曲線的圖表,常見的功能像是音量感測、光線感測等等

那麼像我待的這種成天在搞Sensor的公司來說

除了要有什麼溫濕曲線圖之外,實時的噪音曲線開發或是光感曲線開發也是hen重要滴(-‿◦)

好喔,那麼廢話也不講太多,來看看範例就知道囉

 

Github在這邊

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

 


 

1. 載入庫/畫介面

 

今天的功能是採用一款名為"MPChart"的第三方函式庫完成的功能

我在之前的文章中有淺談過這個UI框架,可以去看看

碼農日常-『Android studio』使用MPChart第三方資料庫在Android裝置上繪製圖表

在這篇內容中,我主要展示的是基本的如何使用這個MPChart製作一個圖表

而在當時我也已經說過了我未來可能~或許~Maybe~おそらく~かも知れない

會寫一篇關於"即時更新"圖表的文章

所以,今天就來拆掉這個Flag了(;´д`)ゞ

 

1-1 載入庫

好,那就一樣來載入函式庫吧

首先這是MPChart的Github

https://github.com/PhilJay/MPAndroidChart

 

然後,請開啟Android Projects 中的build.gradle

截圖 2021-03-14 上午2.07.04

 

載入以下粉底白字內容

apply plugin: 'com.android.application'

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.3"

    defaultConfig {
        applicationId "com.noahliu.realtimechartdemo"
        minSdkVersion 23
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}
repositories {
    maven { url 'https://jitpack.io' }
}

dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
    implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'

}

 

注意⚠️

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

這個一定要加入哦,不然會無法編譯

最後一樣按下Sync now 完成

截圖 2021-03-14 上午2.10.03

 

1-2 畫介面

 

這次的介面如下

Screenshot_1615689428

 

基本上就是一個圖表元件+三個按鈕

我就直接貼囉

 

activity_main.xml

 

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.github.mikephil.charting.charts.LineChart
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:id="@+id/lineChart"
        app:layout_constraintBottom_toTopOf="@+id/guideline"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.9" />

    <Button
        android:id="@+id/button_RunData"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="16dp"
        android:text="跑隨機數據"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="@+id/guideline" />

    <Button
        android:id="@+id/button_Stop"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="8dp"
        android:text="停止"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/button_RunData"
        app:layout_constraintTop_toTopOf="@+id/guideline" />

    <Button
        android:id="@+id/button_Reset"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="16dp"
        android:text="重置"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/button_Stop"
        app:layout_constraintTop_toTopOf="@+id/guideline" />
</androidx.constraintlayout.widget.ConstraintLayout>

 

 


 

2. 初始化圖表

 

Okay,旁枝末節都搞定了

接下來就來搞定圖表本身吧(・ω・)b

簡單地講述一下實現邏輯,在開始之前首先可以看一下這張圖

講戒

 

這是我們在整個圖表中所會用到的所有的類別

而整體動態圖表的實現邏輯大約如下

 

1. 初始化一個圖表,並在初始化同時設置一條new一個LineData

2. 獲取即時資料後,將資料先製作成Entry類

3. 最後將製作好的Entry類加入到LineData類,並執行圖表更新

 

好的,接著就來看一看實現的方式吧

首先來到MainActivity

先來初始化圖表,如下

 

public class MainActivity extends AppCompatActivity {

    private boolean isRunning = false;
    private LineChart chart;
    private Thread thread;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        chart = findViewById(R.id.lineChart);
        /**載入圖表*/
        initChart();

        Button btStop,btStart,btReset;
        btStart = findViewById(R.id.button_RunData);
        btStop = findViewById(R.id.button_Stop);
        btReset = findViewById(R.id.button_Reset);
        /**開始跑圖表*/
        btStart.setOnClickListener(v->{
            
        });
        /**停止跑圖表*/
        btStop.setOnClickListener(v->{
            isRunning = false;
        });
        /**重置圖表*/
        btReset.setOnClickListener(v->{
            chart.clear();
            initChart();
        });
    }
    
    /**載入圖表*/
    private void initChart(){
        chart.getDescription().setEnabled(false);//設置不要圖表標籤
        chart.setTouchEnabled(false);//設置不可觸碰
        chart.setDragEnabled(false);//設置不可互動
        //設置單一線數據
        LineData data = new LineData();
        data.setValueTextColor(Color.BLACK);
        chart.setData(data);
        //設置左下角標籤
        Legend l =  chart.getLegend();
        l.setForm(Legend.LegendForm.LINE);
        l.setTextColor(Color.BLACK);

        //設置X軸
        XAxis x =  chart.getXAxis();
        x.setTextColor(Color.BLACK);
        x.setDrawGridLines(true);//畫X軸線
        x.setPosition(XAxis.XAxisPosition.BOTTOM);//把標籤放底部
        x.setLabelCount(5,true);//設置顯示5個標籤
        //設置X軸標籤內容物
        x.setValueFormatter(new ValueFormatter() {
            @Override
            public String getFormattedValue(float value) {
                return "No. "+Math.round(value);
            }
        });
        //
        YAxis y = chart.getAxisLeft();
        y.setTextColor(Color.BLACK);
        y.setDrawGridLines(true);
        y.setAxisMaximum(100);//最高100
        y.setAxisMinimum(0);//最低0
        chart.getAxisRight().setEnabled(false);//右邊Y軸不可視
        chart.setVisibleXRange(0,50);//設置顯示範圍
    }
}

 

至此可按下執行,就可以看得到有圖表出現了(・ωー)~☆

 


 

3. 填入數值

 

接下來是第二步驟-填入數值

填入數值其實邏輯上簡單,不過因為牽涉到使用非同步執行緒的緣故

所以感覺會有點複雜Σ(・口・)

不過這邊會盡可能一步一步做,那麼開始吧

 

首先我們先來決定數據線的樣式

請先輸入以下內容

/**設置數據線的樣式*/
private LineDataSet createSet() {
    LineDataSet set = new LineDataSet(null, "隨機數據");
    set.setAxisDependency(YAxis.AxisDependency.LEFT);
    set.setColor(Color.GRAY);
    set.setLineWidth(2);
    set.setDrawCircles(false);
    set.setFillColor(Color.RED);
    set.setFillAlpha(50);
    set.setDrawFilled(true);
    set.setValueTextColor(Color.BLACK);
    set.setDrawValues(false);
    return set;
}

 

這裡是設置數據線要長什麼樣子,包含顏色、寬度等等資訊

再來寫關於新增數據的程式,請寫以下內容

/**新增資料*/
private void addData(float inputData){
    LineData data =  chart.getData();//取得原數據
    ILineDataSet set = data.getDataSetByIndex(0);//取得曲線(因為只有一條,故為0,若有多條則需指定)
    if (set == null){
        set = createSet();
        data.addDataSet(set);//如果是第一次跑則需要載入數據
    }
    data.addEntry(new Entry(set.getEntryCount(),inputData),0);//新增數據點
    //更新圖表
    data.notifyDataChanged();
    chart.notifyDataSetChanged();
    chart.setVisibleXRange(0,50);//設置可見範圍
    chart.moveViewToX(data.getEntryCount());//將可視焦點放在最新一個數據,使圖表可移動
}

 

倒數第二步,是執行的程式

/**開始跑圖表*/
    private void startRun(){
        if (isRunning)return;
        if (thread != null) thread.interrupt();
//            Runnable runnable = new Runnable() {@Override public void run() {}};
        //簡略寫法
        isRunning = true;
        Runnable runnable  = ()->{
            //取亂數
            addData((int)(Math.random()*(60-40+1))+40);
        };
//            thread = new Thread(new Runnable()
//            {@Override public void run() {runOnUiThread(runnable);}});
        //簡略寫法
        thread =  new Thread(()->{
            while (isRunning) {
                runOnUiThread(runnable);
                if (!isRunning)break;
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread.start();
    }

特別注意這裡有非同步執行緒的寫法

詳情可參考這篇

碼農日常-『Android studio』Android背景執行之Thread、Handler及AsyncTask的基礎用法

 

最後,使用它吧

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    chart = findViewById(R.id.lineChart);
    /**載入圖表*/
    initChart();

    Button btStop,btStart,btReset;
    btStart = findViewById(R.id.button_RunData);
    btStop = findViewById(R.id.button_Stop);
    btReset = findViewById(R.id.button_Reset);
    /**開始跑圖表*/
    btStart.setOnClickListener(v->{
        startRun();
    });
    /**停止跑圖表*/
    btStop.setOnClickListener(v->{
        isRunning = false;
    });
    /**重置圖表*/
    btReset.setOnClickListener(v->{
        chart.clear();
        initChart();
    });
}

 

以下是全部程式

 


 

MainAvtivity.java

public class MainActivity extends AppCompatActivity {

    private boolean isRunning = false;
    private LineChart chart;
    private Thread thread;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        chart = findViewById(R.id.lineChart);
        /**載入圖表*/
        initChart();

        Button btStop,btStart,btReset;
        btStart = findViewById(R.id.button_RunData);
        btStop = findViewById(R.id.button_Stop);
        btReset = findViewById(R.id.button_Reset);
        /**開始跑圖表*/
        btStart.setOnClickListener(v->{
            startRun();
        });
        /**停止跑圖表*/
        btStop.setOnClickListener(v->{
            isRunning = false;
        });
        /**重置圖表*/
        btReset.setOnClickListener(v->{
            chart.clear();
            initChart();
        });
    }
    /**開始跑圖表*/
    private void startRun(){
        if (isRunning)return;
        if (thread != null) thread.interrupt();
//            Runnable runnable = new Runnable() {@Override public void run() {}};
        //簡略寫法
        isRunning = true;
        Runnable runnable  = ()->{
            //取亂數
            addData((int)(Math.random()*(60-40+1))+40);
        };
//            thread = new Thread(new Runnable()
//            {@Override public void run() {runOnUiThread(runnable);}});
        //簡略寫法
        thread =  new Thread(()->{
            while (isRunning) {
                runOnUiThread(runnable);
                if (!isRunning)break;
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread.start();
    }
    /**載入圖表*/
    private void initChart(){
        chart.getDescription().setEnabled(false);//設置不要圖表標籤
        chart.setTouchEnabled(false);//設置不可觸碰
        chart.setDragEnabled(false);//設置不可互動
        //設置單一線數據
        LineData data = new LineData();
        data.setValueTextColor(Color.BLACK);
        chart.setData(data);
        //設置左下角標籤
        Legend l =  chart.getLegend();
        l.setForm(Legend.LegendForm.LINE);
        l.setTextColor(Color.BLACK);

        //設置X軸
        XAxis x =  chart.getXAxis();
        x.setTextColor(Color.BLACK);
        x.setDrawGridLines(true);//畫X軸線
        x.setPosition(XAxis.XAxisPosition.BOTTOM);//把標籤放底部
        x.setLabelCount(5,true);//設置顯示5個標籤
        //設置X軸標籤內容物
        x.setValueFormatter(new ValueFormatter() {
            @Override
            public String getFormattedValue(float value) {
                return "No. "+Math.round(value);
            }
        });
        //
        YAxis y = chart.getAxisLeft();
        y.setTextColor(Color.BLACK);
        y.setDrawGridLines(true);
        y.setAxisMaximum(100);//最高100
        y.setAxisMinimum(0);//最低0
        chart.getAxisRight().setEnabled(false);//右邊Y軸不可視
        chart.setVisibleXRange(0,50);//設置顯示範圍
    }
    /**新增資料*/
    private void addData(float inputData){
        LineData data =  chart.getData();//取得原數據
        ILineDataSet set = data.getDataSetByIndex(0);//取得曲線(因為只有一條,故為0,若有多條則需指定)
        if (set == null){
            set = createSet();
            data.addDataSet(set);//如果是第一次跑則需要載入數據
        }
        data.addEntry(new Entry(set.getEntryCount(),inputData),0);//新增數據點
        //
        data.notifyDataChanged();
        chart.notifyDataSetChanged();
        chart.setVisibleXRange(0,50);//設置可見範圍
        chart.moveViewToX(data.getEntryCount());//將可視焦點放在最新一個數據,使圖表可移動
    }
    /**設置數據線的樣式*/
    private LineDataSet createSet() {
        LineDataSet set = new LineDataSet(null, "隨機數據");
        set.setAxisDependency(YAxis.AxisDependency.LEFT);
        set.setColor(Color.GRAY);
        set.setLineWidth(2);
        set.setDrawCircles(false);
        set.setFillColor(Color.RED);
        set.setFillAlpha(50);
        set.setDrawFilled(true);
        set.setValueTextColor(Color.BLACK);
        set.setDrawValues(false);
        return set;
    }
}

 


 

其實前陣子有人留言給我,說是要做一個即時更新的圖表

而也就這麼剛好~我這陣子正在寫一個噪音偵測的軟體

所以就非~常順便地將實時圖表寫進去了

然後也因為最近沒有什麼靈感,所以就拿來寫了XD

 

那麼今天的文章到這,希望本文對你有幫助

TK

arrow
arrow

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