1
package com.example.communication;

import androidx.appcompat.app.AppCompatActivity;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.widget.TextView;
import androidx.core.content.ContextCompat;
import com.github.mikephil.charting.charts.LineChart;
import com.github.mikephil.charting.components.Legend;
import com.github.mikephil.charting.components.XAxis;
import com.github.mikephil.charting.components.YAxis;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.data.LineDataSet;
import com.github.mikephil.charting.interfaces.datasets.ILineDataSet;

public class MainActivity extends AppCompatActivity implements UdpReceiver.OnMessageReceivedListener {
    private static final String TAG = "MainActivity";
    private TextView messageTextView;
    private UdpReceiver udpReceiver;

    private LineChart chart;
    private final int count = 0;
    private LineDataSet dataSet;
    private float lastX = 0;

    private static final String SERVER_IP = "0.0.0.0"; // Replace with your tablet IP
    private static final int SERVER_PORT = 8888;

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

        messageTextView = findViewById(R.id.messageTextView);

        chart = findViewById(R.id.chart);

        // enable description text
        chart.getDescription().setEnabled(true);
        chart.getDescription().setText("Beats Per Minute");

        // enable touch gestures
        chart.setTouchEnabled(true);

        // enable scaling and dragging
        chart.setDragEnabled(true);
        chart.setScaleEnabled(true);

        // if disabled, scaling can be done on x- and y-axis separately
        chart.setPinchZoom(true);

        // set an empty data set
        chart.setData(new LineData());

        // create data set and assign to dataSet variable
        dataSet = createSet();

        // add dataSet to chart
        chart.getData().addDataSet(dataSet);

        // customize x-axis
        XAxis x = chart.getXAxis();
        x.setAxisMinimum(0);
        x.setAxisMaximum(dataSet.getEntryCount()); // to increase the max
        x.setDrawGridLines(false);
        x.setDrawLabels(false);

        // customize left y-axis
        YAxis leftAxis = chart.getAxisLeft();
        leftAxis.setAxisMinimum(0);
        leftAxis.setAxisMaximum(220);
        leftAxis.setDrawGridLines(true);
        leftAxis.setDrawZeroLine(true);
        leftAxis.setZeroLineColor(Color.GRAY);
        leftAxis.setGranularityEnabled(true);

        // customize right y-axis
        chart.getAxisRight().setEnabled(false);

        // customize legend
        Legend legend = chart.getLegend();
        legend.setForm(Legend.LegendForm.LINE);

        // add gradient fill to line chart
        Drawable drawable = ContextCompat.getDrawable(this, R.drawable.gradient_fill);
        chart.setBackground(drawable);

        // Refresh the chart
        chart.invalidate();

        udpReceiver = new UdpReceiver(this);
        udpReceiver.startReceiver(SERVER_IP, SERVER_PORT);
    }


    @Override
    protected void onDestroy() {
        super.onDestroy();

        udpReceiver.stopReceiver();
    }



    private void addEntry(final float bpm) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                LineData data = chart.getData();

                if (data == null) {
                    data = new LineData();
                    chart.setData(data);
                }

                ILineDataSet set = data.getDataSetByIndex(0);

                if (set == null) {
                    set = createSet();
                    data.addDataSet(set);
                }

                // Add the new entry to the chart
                data.addEntry(new Entry(set.getEntryCount(), bpm), 0);

                // Set the X-axis range based on the number of entries in the chart
                chart.setVisibleXRangeMaximum(dataSet.getEntryCount()); //to change max
                chart.setVisibleXRangeMinimum(20);
                chart.moveViewToX(data.getEntryCount() - 1);

                // Notify the chart that the data has changed
                data.notifyDataChanged();
                chart.notifyDataSetChanged();
            }
        });
    }




    private LineDataSet createSet() {
        LineDataSet set = new LineDataSet(null, "Beats Per Minute");
        set.setLineWidth(2f);
        set.setDrawCircles(false);
        set.setDrawValues(false);
        set.setColor(Color.WHITE);
        set.setMode(LineDataSet.Mode.LINEAR);
        return set;
    }


    @Override
    public void onMessageReceived(String message) {
        //String[] data = message.split(",");
        String[] data = new String[2];
        data[0] = "";
        data[1] = "";
        if (message.contains(",")) {
            data = message.split(",");
        }
        int irValue = 0;
        float beatsPerMinute = 0;
        int beatAvg = 0;


        for (String item : data) {
            if (item.startsWith("IR=")) {
                irValue = Integer.parseInt(item.substring(3));
            } else if (item.startsWith("BPM=")) {
                beatsPerMinute = Float.parseFloat(item.substring(4));
                if (beatsPerMinute >= 60 && beatsPerMinute <= 100) {
                    addEntry(beatsPerMinute);
                }
            }
                /*
            } else if (item.startsWith("Avg BPM=")) {
                beatAvg = Integer.parseInt(item.substring(4));
            }
                 */

            final int finalIrValue = irValue;
            final float finalBeatsPerMinute = beatsPerMinute;
            final int finalBeatAvg = beatAvg;

            runOnUiThread(() -> {
                messageTextView.setText("IR Value: " + finalIrValue +
                        "\nBeats Per Minute: " + finalBeatsPerMinute +
                        "\nBeat Average: " + finalBeatAvg);

                // Add the new data to the chart
                LineData chartData = chart.getData();

                if (chartData != null) {
                    ILineDataSet set = chartData.getDataSetByIndex(0);

                    if (set == null) {
                        set = createSet();
                        chartData.addDataSet(set);
                    }


                    chartData.addEntry(new Entry(set.getEntryCount(), finalBeatsPerMinute), 0);

                    // Limit the number of visible entries
                    int maxVisibleEntries = 10;
                    if (chartData.getEntryCount() > maxVisibleEntries) {
                        chartData.removeEntry(0, 0);
                    }

                    // Notify chart that data has changed
                    chartData.notifyDataChanged();
                    chart.notifyDataSetChanged();
                    chart.setVisibleXRangeMaximum(maxVisibleEntries);
                    chart.moveViewToX(chartData.getEntryCount() - 1);
                }
            });
        }

    }

}


For the code above, I am using MPandroid to display the real time data using UDP from a heart rate sensor on a line chart, the app is receiving the BPM but not displaying. The chart is visible, but the BPM (Beats Per Minute) line on the chart is not shown, but the data has been received successfully on the UDP channel, but missing the line on the chart to display the BPM value. What would've been the issue?

Tyler V
  • 9,694
  • 3
  • 26
  • 52
Nirvana
  • 11
  • 1

1 Answers1

0

To get this to work, you need to remove the calls that set the x axis limits in onCreate, these do not appear to play nicely with the realtime chart.

// customize x-axis
XAxis x = chart.getXAxis();

// Remove these
//x.setAxisMinimum(0);
//x.setAxisMaximum(dataSet.getEntryCount());

x.setDrawGridLines(false);
x.setDrawLabels(false);

With those calls removed, the chart updates for me (testing with a mock provider of the BPM data, shown below).

I also modified the addEntry method to correctly remove entries as well, otherwise when you get to maxVisibleEntries it just keeps adding entries at the same x value. Instead, you should add them at set.getXMax()+1, like this:

private void addEntry(final float bpm) {
    runOnUiThread(() -> {

        LineData data = chart.getData();

        if (data == null) {
            data = new LineData();
            chart.setData(data);
        }

        LineDataSet set = (LineDataSet)data.getDataSetByIndex(0);

        if (set == null) {
            set = createSet();
            data.addDataSet(set);
        }
        
        // Add the new entry to the chart - do not use
        // set.getEntryCount() for the x value here or it just
        // stays constant once you hit maxVisibleEntries
        float x = set.getEntryCount() == 0 ? 0 : set.getXMax()+1;
        data.addEntry(new Entry(x, bpm), 0);

        int maxVisibleEntries = 30;
        if (data.getEntryCount() > maxVisibleEntries) {
            data.removeEntry(0, 0);
        }

        // Set the X-axis range based on the number of entries 
        // you want to show
        chart.setVisibleXRangeMaximum(maxVisibleEntries);
        chart.setVisibleXRangeMinimum(10);

        // Although it happens to work here, you don't want 
        // to use getEntryCount() here either. This sets the 
        // X position of the left side of the viewport, but once
        // you hit maxVisibleEntries getEntryCount() is a constant
        // even as the chart x value continues to change.
        chart.moveViewToX(x - maxVisibleEntries);

        // Notify the chart that the data has changed
        data.notifyDataChanged();
        chart.notifyDataSetChanged();
    });
}

I replaced the Udp dependency with the following mock to test the chart behavior with realtime data (a good idea to do when asking a question like this so someone else could replicate your problem with minimal dependencies):

private final Handler handler = new Handler();
private final Random random = new Random();
private final Runnable postBeat = new Runnable() {
    @Override
    public void run() {
        float bpm = 60 + 30*random.nextFloat();
        addEntry(bpm);
        handler.postDelayed(postBeat, 500);
    }
};

@Override
protected void onResume() {
    super.onResume();
    handler.postDelayed(postBeat, 500);
}

@Override
protected void onPause() {
    super.onPause();
    handler.removeCallbacksAndMessages(null);
}
Tyler V
  • 9,694
  • 3
  • 26
  • 52