diff --git a/.gitignore b/.gitignore index 04d47c56d6b887c18d9c38c1dd029ecbb1e9c277..007d64268841d3fb3282a82e2376522174a3d543 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ local.properties .idea bugfender.properties /app/app.iml +gradle.properties \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index bc39a1084d08f077d7b3212257ff6b73dbe50000..bf1a8b2ede019c027433d26b51362306cd0ac1e7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -154,8 +154,7 @@ dependencies { compile 'com.mikepenz:google-material-typeface:2.2.0.1.original@aar' compile 'uk.co.chrisjenx:calligraphy:2.2.0' compile 'com.bugfender.sdk:android:0.6.2' - compile 'com.github.PhilJay:MPAndroidChart-Realm:v2.0.2@aar' - compile 'com.github.PhilJay:MPAndroidChart:v3.0.1' + compile 'com.jjoe64:graphview:4.2.1' compile 'com.android.support:support-v4:23.4.0' compile 'com.google.code.gson:gson:2.7' compile 'com.squareup.retrofit2:retrofit:2.1.0' diff --git a/app/src/main/java/info/nightscout/android/medtronic/MainActivity.java b/app/src/main/java/info/nightscout/android/medtronic/MainActivity.java index a16375d180e4bc9f250490bb4425cce173ee0ed2..421368b960d470437517a980649ca8c48ad2cf65 100644 --- a/app/src/main/java/info/nightscout/android/medtronic/MainActivity.java +++ b/app/src/main/java/info/nightscout/android/medtronic/MainActivity.java @@ -12,6 +12,7 @@ import android.content.SharedPreferences; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.Paint; import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbManager; import android.net.Uri; @@ -34,28 +35,19 @@ import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; -import android.view.MotionEvent; import android.view.View; import android.widget.TextView; import android.widget.TextView.BufferType; - -import com.github.mikephil.charting.charts.ScatterChart; -import com.github.mikephil.charting.components.AxisBase; -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.ScatterData; -import com.github.mikephil.charting.data.ScatterDataSet; -import com.github.mikephil.charting.data.realm.implementation.RealmScatterDataSet; -import com.github.mikephil.charting.formatter.IAxisValueFormatter; -import com.github.mikephil.charting.formatter.IValueFormatter; -import com.github.mikephil.charting.interfaces.datasets.IScatterDataSet; -import com.github.mikephil.charting.listener.ChartTouchListener; -import com.github.mikephil.charting.listener.OnChartGestureListener; -import com.github.mikephil.charting.renderer.ScatterChartRenderer; -import com.github.mikephil.charting.renderer.scatter.IShapeRenderer; -import com.github.mikephil.charting.utils.Transformer; -import com.github.mikephil.charting.utils.ViewPortHandler; +import android.widget.Toast; + +import com.jjoe64.graphview.DefaultLabelFormatter; +import com.jjoe64.graphview.GraphView; +import com.jjoe64.graphview.Viewport; +import com.jjoe64.graphview.series.DataPoint; +import com.jjoe64.graphview.series.DataPointInterface; +import com.jjoe64.graphview.series.OnDataPointTapListener; +import com.jjoe64.graphview.series.PointsGraphSeries; +import com.jjoe64.graphview.series.Series; import com.mikepenz.google_material_typeface_library.GoogleMaterial; import com.mikepenz.materialdrawer.AccountHeaderBuilder; import com.mikepenz.materialdrawer.Drawer; @@ -65,9 +57,8 @@ import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem; import java.text.DateFormat; import java.text.DecimalFormat; -import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; -import java.util.List; import java.util.Locale; import java.util.Queue; import java.util.concurrent.ArrayBlockingQueue; @@ -83,7 +74,6 @@ import info.nightscout.android.model.medtronicNg.PumpInfo; import info.nightscout.android.model.medtronicNg.PumpStatusEvent; import info.nightscout.android.settings.SettingsActivity; import info.nightscout.android.upload.nightscout.NightscoutUploadIntentService; -import io.realm.DynamicRealmObject; import io.realm.Realm; import io.realm.RealmChangeListener; import io.realm.RealmResults; @@ -99,12 +89,14 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc public static long lowBatteryPollInterval = MedtronicCnlIntentService.LOW_BATTERY_POLL_PERIOD_MS; private static long activePumpMac; + private int chartZoom = 3; + private boolean hasZoomedChart = false; boolean mEnableCgmService = true; SharedPreferences prefs = null; private PumpInfo mActivePump; private TextView mTextViewLog; // This will eventually move to a status page. - private ScatterChart mChart; + private GraphView mChart; private Intent mNightscoutUploadService; private Handler mUiRefreshHandler = new Handler(); private Runnable mUiRefreshRunnable = new RefreshDisplayRunnable(); @@ -271,118 +263,46 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc .build(); mTextViewLog = (TextView) findViewById(R.id.textview_log); - mChart = (ScatterChart) findViewById(R.id.chart); - - mChart.setDescription(null); // Hide the description - - mChart.setTouchEnabled(true); - mChart.setPinchZoom(true); - mChart.setHighlightPerDragEnabled(false); - mChart.setHighlightPerTapEnabled(false); - mChart.setOnChartGestureListener(new OnChartGestureListener() { - - @Override - public void onChartGestureStart(MotionEvent me, ChartTouchListener.ChartGesture lastPerformedGesture) {} + mChart = (GraphView) findViewById(R.id.chart); - @Override - public void onChartGestureEnd(MotionEvent me, ChartTouchListener.ChartGesture lastPerformedGesture) {} + //mChart.setDescription(null); // Hide the description + mChart.getViewport().setScalable(true); + mChart.getViewport().setScrollable(true); + mChart.getViewport().setXAxisBoundsManual(true); + mChart.getViewport().setOnXAxisBoundsChangedListener(new Viewport.OnXAxisBoundsChangedListener() { @Override - public void onChartLongPressed(MotionEvent me) { - mChart.fitScreen(); + public void onXAxisBoundsChanged(double minX, double maxX, Reason reason) { + double rightX = mChart.getSeries().get(0).getHighestValueX(); + hasZoomedChart = (rightX != maxX || rightX - chartZoom * 60 * 60 * 1000 != minX); } - - @Override - public void onChartDoubleTapped(MotionEvent me) {} - - @Override - public void onChartSingleTapped(MotionEvent me) {} - - @Override - public void onChartFling(MotionEvent me1, MotionEvent me2, float velocityX, float velocityY) {} - - @Override - public void onChartScale(MotionEvent me, float scaleX, float scaleY) {} - - @Override - public void onChartTranslate(MotionEvent me, float dX, float dY) {} }); - XAxis xAxis = mChart.getXAxis(); - xAxis.setPosition(XAxis.XAxisPosition.BOTTOM); - xAxis.setTextSize(10f); - xAxis.setTextColor(Color.WHITE); - xAxis.setDrawAxisLine(true); - xAxis.setDrawGridLines(true); - xAxis.setDrawLabels(true); - xAxis.setValueFormatter(new IAxisValueFormatter() { - private DateFormat mFormat = DateFormat.getTimeInstance(DateFormat.SHORT); - + mChart.setOnLongClickListener(new View.OnLongClickListener() { @Override - public String getFormattedValue(float value, AxisBase axis) { - return mFormat.format(new Date((long) value)); + public boolean onLongClick(View v) { + double rightX = mChart.getSeries().get(0).getHighestValueX(); + mChart.getViewport().setMaxX(rightX); + mChart.getViewport().setMinX(rightX - chartZoom * 60 * 60 * 1000); + hasZoomedChart = false; + return true; } }); + mChart.getGridLabelRenderer().setNumHorizontalLabels(6); + mChart.getGridLabelRenderer().setHumanRounding(false); - // left axis - mChart.getAxisLeft().setDrawLabels(false); - - // right axis - YAxis yAxis = mChart.getAxisRight(); - yAxis.setTextSize(10f); - yAxis.setTextColor(Color.WHITE); - - mChart.getLegend().setEnabled(false); // Hide the legend - - // TODO: remove if if "coloring bug" in MPAndroidChart is fixed - // see: https://github.com/PhilJay/MPAndroidChart/issues/2682 - mChart.setRenderer(new ScatterChartRenderer(mChart, mChart.getAnimator(), mChart.getViewPortHandler()) { - - float[] mPixelBuffer = new float[2]; - + mChart.getGridLabelRenderer().setLabelFormatter(new DefaultLabelFormatter() { + DateFormat mFormat = DateFormat.getTimeInstance(DateFormat.SHORT); @Override - protected void drawDataSet(Canvas c, IScatterDataSet dataSet) { - - ViewPortHandler viewPortHandler = mViewPortHandler; - - Transformer trans = mChart.getTransformer(dataSet.getAxisDependency()); - - float phaseY = mAnimator.getPhaseY(); - - IShapeRenderer renderer = dataSet.getShapeRenderer(); - if (renderer == null) { - Log.i("MISSING", "There's no IShapeRenderer specified for ScatterDataSet"); - return; - } - - int max = (int)(Math.min( - Math.ceil((float)dataSet.getEntryCount() * mAnimator.getPhaseX()), - (float)dataSet.getEntryCount())); - - for (int i = 0; i < max; i++) { - - Entry e = dataSet.getEntryForIndex(i); - - mPixelBuffer[0] = e.getX(); - mPixelBuffer[1] = e.getY() * phaseY; - - trans.pointValuesToPixel(mPixelBuffer); - - if (!viewPortHandler.isInBoundsRight(mPixelBuffer[0])) - break; - - if (!viewPortHandler.isInBoundsLeft(mPixelBuffer[0]) - || !viewPortHandler.isInBoundsY(mPixelBuffer[1])) - continue; - - mRenderPaint.setColor(dataSet.getColor(i)); - renderer.renderShape( - c, dataSet, mViewPortHandler, - mPixelBuffer[0], mPixelBuffer[1], - mRenderPaint); + public String formatLabel(double value, boolean isValueX) { + if (isValueX) { + return mFormat.format(new Date((long) value)); + } else { + // show currency for y values + return super.formatLabel(value, false); //nf.format(value); } - } - }); + }} + ); } @Override @@ -553,6 +473,14 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc Long.toString(MedtronicCnlIntentService.LOW_BATTERY_POLL_PERIOD_MS))); } else if (key.equals("doublePollOnPumpAway")) { MainActivity.reducePollOnPumpAway = sharedPreferences.getBoolean("doublePollOnPumpAway", false); + } else if (key.equals("doublePollOnPumpAway")) { + chartZoom = Integer.parseInt(sharedPreferences.getString("chartZoom", "3")); + hasZoomedChart = false; + + long now = (long) mChart.getSeries().get(0).getHighestValueX(), + left = now - chartZoom * 60 * 60 * 1000; + mChart.getViewport().setMinX(left); + mChart.getViewport().setMaxX(now); } } @@ -825,77 +753,95 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc private void updateChart(RealmResults<PumpStatusEvent> results) { int size = results.size(); - if (size == 0) return; + if (size == 0) { + return; + } - List<Entry> entries = new ArrayList<Entry>(size); - int[] colors = new int[size]; // getColor is called with (i/2) + DataPoint[] entries = new DataPoint[size]; + final long left = System.currentTimeMillis() - chartZoom * 60 * 60 * 1000; + + final DecimalFormat df; + final boolean mmolxl = prefs.getBoolean("mmolxl", false); + + if (mmolxl) { + if (prefs.getBoolean("mmolDecimals", false)) + df = new DecimalFormat("0.00"); + else + df = new DecimalFormat("0.0"); + } else { + df = new DecimalFormat("0"); + } + int pos = 0; for (PumpStatusEvent pumpStatus: results) { // turn your data into Entry objects - int sgv = pumpStatus.getSgv(), - pos = entries.size(); - - entries.add(new Entry(pumpStatus.getEventDate().getTime(), pumpStatus.getSgv())); - //TODO: need to be configurable - if (sgv < 80) - colors[pos] = Color.RED; - else if (sgv <= 180) - colors[pos] = Color.GREEN; - else if (sgv <= 260) - colors[pos] = Color.YELLOW; - else - colors[pos] = Color.RED; + int sgv = pumpStatus.getSgv(); + + if (mmolxl) { + entries[pos++] = new DataPoint(pumpStatus.getEventDate(), pumpStatus.getSgv() / 18.016f); + } else { + entries[pos++] = new DataPoint(pumpStatus.getEventDate(), pumpStatus.getSgv()); + } } - if (mChart.getData() == null) { - mChart.setMinimumHeight(200); - ScatterDataSet dataSet = new ScatterDataSet(entries, null); + if (mChart.getSeries().size() == 0) { +// long now = System.currentTimeMillis(); +// entries = new DataPoint[1000]; +// int j = 0; +// for(long i = now - 24*60*60*1000; i < now - 30*60*1000; i+= 5*60*1000) { +// entries[j++] = new DataPoint(i, (float) (Math.random()*200 + 89)); +// } +// entries = Arrays.copyOfRange(entries, 0, j); + + PointsGraphSeries sgvSerie = new PointsGraphSeries(entries); +// sgvSerie.setSize(3.6f); +// sgvSerie.setColor(Color.LTGRAY); + sgvSerie.setOnDataPointTapListener(new OnDataPointTapListener() { + DateFormat mFormat = DateFormat.getTimeInstance(DateFormat.MEDIUM); - dataSet.setColors(colors); // disabled tue to a bug(??) in MPAndroid Chart - //dataSet.setColor(Color.LTGRAY); - dataSet.setValueTextColor(Color.WHITE); - dataSet.setScatterShape(ScatterChart.ScatterShape.CIRCLE); - dataSet.setScatterShapeSize(7.2f); - dataSet.setValueFormatter(new IValueFormatter() { @Override - public String getFormattedValue(float value, Entry entry, int dataSetIndex, ViewPortHandler viewPortHandler) { - DecimalFormat df; + public void onTap(Series series, DataPointInterface dataPoint) { + double sgv = dataPoint.getY(); - if (prefs.getBoolean("mmolxl", false)) { - if (prefs.getBoolean("mmolDecimals", false)) - df = new DecimalFormat("0.00"); - else - df = new DecimalFormat("0.0"); - - return df.format(value / 18.016f); + StringBuilder sb = new StringBuilder(mFormat.format(new Date((long) dataPoint.getX())) + ": "); + if (mmolxl) { + sb.append(df.format(sgv / 18.016f)); } else { - return new DecimalFormat("0").format(value); + sb.append(df.format(sgv)); } + Toast.makeText(getBaseContext(), sb.toString(), Toast.LENGTH_SHORT).show(); } }); - ArrayList<IScatterDataSet> dataSets = new ArrayList<IScatterDataSet>(); - dataSets.add(dataSet); + sgvSerie.setCustomShape(new PointsGraphSeries.CustomShape() { + @Override + public void draw(Canvas canvas, Paint paint, float x, float y, DataPointInterface dataPoint) { + double sgv = dataPoint.getY(); + if (sgv < 80) + paint.setColor(Color.RED); + else if (sgv <= 180) + paint.setColor(Color.GREEN); + else if (sgv <= 260) + paint.setColor(Color.YELLOW); + else + paint.setColor(Color.RED); + canvas.drawCircle(x, y, 3.6f, paint); + } + }); - ScatterData lineData = new ScatterData(dataSets); - mChart.setData(lineData); + mChart.addSeries(sgvSerie); } else { - ((ScatterDataSet)mChart.getScatterData().getDataSets().get(0)).setValues(entries); - ((ScatterDataSet)mChart.getScatterData().getDataSets().get(0)).setColors(colors); // disabled tue to a bug(??) in MPAndroid Chart + if (entries.length > 0) { + ((PointsGraphSeries) mChart.getSeries().get(0)).resetData(entries); + } } - //TODO: make the display timespan configurable - //long now = System.currentTimeMillis(); - - //Log.d(TAG, "Graph limits: " + (new Date(now - 24 * 60 * 60 * 1000) + " - " + (new Date(now)))); - //mChart.setVisibleXRangeMaximum(12); - //mChart.setVisibleXRangeMinimum(0); - //mChart.getXAxis().setAxisMaximum((now + 0*60*1000)); - //mChart.getXAxis().setAxisMinimum(now - 35 * 60 * 1000); - - //mChart.moveViewToX(now - 35*60*1000); // - 24 * 60 * 60 * 1000); - //mChart.invalidate(); - mChart.postInvalidate(); + // set vieport to latest SGV + long lastSGVTimestamp = (long) mChart.getSeries().get(0).getHighestValueX(); + if (!hasZoomedChart) { + mChart.getViewport().setMaxX(lastSGVTimestamp); + mChart.getViewport().setMinX(lastSGVTimestamp - chartZoom * 60 * 60 * 1000); + } } } @@ -964,31 +910,4 @@ public class MainActivity extends AppCompatActivity implements OnSharedPreferenc } } - private class PumsStatusDataSet extends RealmScatterDataSet<PumpStatusEvent> { - - public PumsStatusDataSet(RealmResults<PumpStatusEvent> result, String yValuesField) { - super(result, yValuesField); - } - - public PumsStatusDataSet(RealmResults<PumpStatusEvent> result, String xValuesField, String yValuesField) { - super(result, xValuesField, yValuesField); - } - - public Entry buildEntryFromResultObject(PumpStatusEvent realmObject, float x) { - DynamicRealmObject dynamicObject = new DynamicRealmObject(realmObject); - float xFloat, yFloat; - - if (mXValuesField == null) { - xFloat = x; - } else { - xFloat = dynamicObject.getDate(mXValuesField).getTime(); - } - yFloat = dynamicObject.getInt(mYValuesField); - - return new Entry(mXValuesField == null ? x : xFloat, yFloat); - } - - } - - } diff --git a/app/src/main/java/info/nightscout/android/model/medtronicNg/PumpStatusEvent.java b/app/src/main/java/info/nightscout/android/model/medtronicNg/PumpStatusEvent.java index 60ad396bfba9043268d5e65486dee173f711d6ee..cfe52c2cf6dd3ea601171455a3b5ca1adf67b851 100644 --- a/app/src/main/java/info/nightscout/android/model/medtronicNg/PumpStatusEvent.java +++ b/app/src/main/java/info/nightscout/android/model/medtronicNg/PumpStatusEvent.java @@ -1,7 +1,5 @@ package info.nightscout.android.model.medtronicNg; -import com.github.mikephil.charting.interfaces.datasets.IScatterDataSet; - import java.util.Date; import io.realm.RealmObject; diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 1360a2b54f3cdb032af6023402f818de2b39e7f6..95148622918e6cc996d1ce7e5c3075d7ff3e399a 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -119,11 +119,10 @@ </LinearLayout> - <com.github.mikephil.charting.charts.ScatterChart - android:id="@+id/chart" + <com.jjoe64.graphview.GraphView android:layout_width="match_parent" - android:layout_height="wrap_content" - android:visibility="visible" /> + android:layout_height="100dip" + android:id="@+id/chart" /> <ScrollView android:id="@+id/scrollView"