Skip to content
This repository was archived by the owner on Apr 9, 2021. It is now read-only.

Add Scatter Chart to statistics fragment #345

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,20 +1,30 @@
package org.odk.share.views.ui.instance.fragment;

import android.content.SharedPreferences;
import android.database.Cursor;
import android.graphics.Typeface;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import com.github.mikephil.charting.charts.BarChart;
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.BarData;
import com.github.mikephil.charting.data.BarDataSet;
import com.github.mikephil.charting.data.BarEntry;
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.formatter.IAxisValueFormatter;
import com.github.mikephil.charting.formatter.LargeValueFormatter;
import com.github.mikephil.charting.interfaces.datasets.IScatterDataSet;
import com.github.mikephil.charting.utils.ColorTemplate;

import org.odk.collect.android.dao.InstancesDao;
import org.odk.collect.android.dto.Instance;
Expand All @@ -23,10 +33,16 @@
import org.odk.share.dao.TransferDao;
import org.odk.share.dto.TransferInstance;
import org.odk.share.views.ui.common.injectable.InjectableFragment;
import org.odk.share.views.ui.settings.PreferenceKeys;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.inject.Inject;

Expand All @@ -50,6 +66,8 @@ public class StatisticsFragment extends InjectableFragment {
TextView subtitle;
@BindView(R.id.chart)
BarChart chart;
@BindView(R.id.detailed_chart)
ScatterChart detailedChart;

@Inject
InstancesDao instancesDao;
Expand All @@ -59,6 +77,8 @@ public class StatisticsFragment extends InjectableFragment {
private String formVersion;
private String formId;
private String formName;
private DateFormat df = new SimpleDateFormat("dd/MM/YYYY");
private SharedPreferences prefs;

public StatisticsFragment() {

Expand Down Expand Up @@ -105,34 +125,68 @@ public void onResume() {
Cursor transferCursor = transferDao.getSentInstancesCursor();
List<TransferInstance> transferInstances = transferDao.getInstancesFromCursor(transferCursor);
int sentCount = 0;
HashMap<String, Integer> sentDates = new HashMap<>();
for (TransferInstance instance : transferInstances) {
if (instanceMap.containsKey(instance.getInstanceId())) {
sentCount++;
String date = df.format(instance.getLastStatusChangeDate());
if (sentDates.containsKey(date)) {
Integer count = sentDates.get(date);
sentDates.put(date, count + 1);
} else {
sentDates.put(date, 1);
}
}
}

transferCursor = transferDao.getReceiveInstancesCursor();
transferInstances = transferDao.getInstancesFromCursor(transferCursor);
int receiveCount = 0;
HashMap<String, Integer> receiveDates = new HashMap<>();
for (TransferInstance instance : transferInstances) {
if (instanceMap.containsKey(instance.getInstanceId())) {
receiveCount++;
String date = df.format(instance.getLastStatusChangeDate());
if (receiveDates.containsKey(date)) {
Integer count = receiveDates.get(date);
receiveDates.put(date, count + 1);
} else {
receiveDates.put(date, 1);
}
}
}

transferCursor = transferDao.getReviewedInstancesCursor();
transferInstances = transferDao.getInstancesFromCursor(transferCursor);
int reviewCount = 0;
HashMap<String, Integer> reviewDates = new HashMap<>();
for (TransferInstance instance : transferInstances) {
if (instanceMap.containsKey(instance.getInstanceId())) {
reviewCount++;
String date = df.format(instance.getLastStatusChangeDate());
if (reviewDates.containsKey(date)) {
Integer count = reviewDates.get(date);
reviewDates.put(date, count + 1);
} else {
reviewDates.put(date, 1);
}
}
}
drawGraph(sentCount, receiveCount, reviewCount);

prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
boolean detailedStatisticsEnabled = prefs.getBoolean(PreferenceKeys.KEY_DETAILED_STATISTICS, false);
if (detailedStatisticsEnabled) {
drawScatterGraph(sentDates, receiveDates, reviewDates);
chart.setVisibility(View.GONE);
} else {
drawBarGraph(sentCount, receiveCount, reviewCount);
detailedChart.setVisibility(View.GONE);
}

super.onResume();
}

public void drawGraph(int sentCount, int receiveCount, int reviewCount) {
public void drawBarGraph(int sentCount, int receiveCount, int reviewCount) {
List<BarEntry> entries = new ArrayList<>();
entries.add(new BarEntry(0, sentCount));
entries.add(new BarEntry(1, receiveCount));
Expand Down Expand Up @@ -187,4 +241,121 @@ public void drawGraph(int sentCount, int receiveCount, int reviewCount) {
chart.setDescription(null);
chart.invalidate(); // refresh
}


public void drawScatterGraph(HashMap<String, Integer> sentDates, HashMap<String, Integer> receiveDates, HashMap<String, Integer> reviewDates) {
List<Entry> sentEntries = new ArrayList<>();
List<Entry> receiveEntries = new ArrayList<>();
List<Entry> reviewEntries = new ArrayList<>();
int maxValue = 0;

// Map from indices to corresponding date for last 30 days
Calendar cal = Calendar.getInstance();
HashMap<Integer, String> indicesToDateMap = new HashMap<>();
for (int i = 0; i < 30; i++) {
cal.setTimeInMillis(System.currentTimeMillis());
cal.add(Calendar.DATE, -29 + i);
indicesToDateMap.put(i, df.format(cal.getTimeInMillis()));
}

Iterator mapIterator = indicesToDateMap.entrySet().iterator();
while (mapIterator.hasNext()) {
Map.Entry map = (Map.Entry) mapIterator.next();
if (sentDates.containsKey(map.getValue())) {
sentEntries.add(new Entry((Integer) map.getKey(), sentDates.get(map.getValue())));
maxValue = sentDates.get(map.getValue()) > maxValue ? sentDates.get(map.getValue()) : maxValue;
} else {
sentEntries.add(new Entry((Integer) map.getKey(), -1));
}

if (receiveDates.containsKey(map.getValue())) {
receiveEntries.add(new Entry((Integer) map.getKey(), receiveDates.get(map.getValue())));
maxValue = receiveDates.get(map.getValue()) > maxValue ? receiveDates.get(map.getValue()) : maxValue;
} else {
receiveEntries.add(new Entry((Integer) map.getKey(), -1));
}

if (reviewDates.containsKey(map.getValue())) {
reviewEntries.add(new Entry((Integer) map.getKey(), reviewDates.get(map.getValue())));
maxValue = reviewDates.get(map.getValue()) > maxValue ? reviewDates.get(map.getValue()) : maxValue;
} else {
reviewEntries.add(new Entry((Integer) map.getKey(), -1));
}
}

ScatterDataSet sentSet = new ScatterDataSet(sentEntries, "Sent");
sentSet.setScatterShape(ScatterChart.ScatterShape.SQUARE);
sentSet.setColor(ColorTemplate.COLORFUL_COLORS[0]);

ScatterDataSet receiveSet = new ScatterDataSet(receiveEntries, "Received");
receiveSet.setScatterShape(ScatterChart.ScatterShape.TRIANGLE);
receiveSet.setColor(ColorTemplate.COLORFUL_COLORS[1]);

ScatterDataSet reviewSet = new ScatterDataSet(reviewEntries, "Reviewed");
reviewSet.setScatterShape(ScatterChart.ScatterShape.CROSS);
reviewSet.setColor(ColorTemplate.COLORFUL_COLORS[2]);

sentSet.setScatterShapeSize(24f);
sentSet.setDrawValues(false);
receiveSet.setScatterShapeSize(22f);
receiveSet.setDrawValues(false);
reviewSet.setScatterShapeSize(24f);
reviewSet.setDrawValues(false);

XAxis axisX = detailedChart.getXAxis();
YAxis axisYR = detailedChart.getAxisRight();
YAxis axisYL = detailedChart.getAxisLeft();
axisYL.setTypeface(Typeface.DEFAULT_BOLD);
axisX.setTypeface(Typeface.DEFAULT_BOLD);
axisYR.setEnabled(false);
axisX.setPosition(XAxis.XAxisPosition.BOTTOM);
axisX.setDrawGridLines(false);
axisYR.setDrawGridLines(false);
axisX.setDrawLabels(true);
axisYL.setAxisMinimum(0);
axisYL.setValueFormatter(new LargeValueFormatter());
int granularity = 1;
if (maxValue <= 5) {
axisYL.setAxisMaximum(5);
} else {
int axisMax;
maxValue += 1;
if (maxValue % 5 != 0) {
granularity = (maxValue / 5) + 1;
axisMax = 5 * granularity;
} else {
axisMax = maxValue;
granularity = maxValue / 5;
}
axisYL.setAxisMaximum(axisMax);
}
axisYL.setGranularity(granularity);
axisYL.setLabelCount(6, true);
axisX.setLabelCount(30);
axisX.setGranularity(1);

axisX.setValueFormatter(new IAxisValueFormatter() {
@Override
public String getFormattedValue(float value, AxisBase axis) {
String[] dateStrings = indicesToDateMap.get((int) value).split("/");
return dateStrings[0] + "/" + dateStrings[1];
}
});

ArrayList<IScatterDataSet> dataSets = new ArrayList<>();
dataSets.add(sentSet); // add the data sets
dataSets.add(receiveSet);
dataSets.add(reviewSet);

// create a data object with the data sets
ScatterData data = new ScatterData(dataSets);

detailedChart.setData(data);
detailedChart.setDoubleTapToZoomEnabled(false);
detailedChart.setPinchZoom(false);
detailedChart.setDescription(null);
detailedChart.setHorizontalScrollBarEnabled(true);
detailedChart.setVisibleXRangeMaximum(8);
detailedChart.moveViewToX(22);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public class PreferenceKeys {
public static final String KEY_DEFAULT_TRANSFER_METHOD = "default_transfer_method";
public static final String KEY_BLUETOOTH_NAME = "bluetooth_name";
public static final String KEY_BLUETOOTH_SECURE_MODE = "bluetooth_secure_mode";
public static final String KEY_DETAILED_STATISTICS = "detailed_statistics";

private PreferenceKeys() {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public class SettingsActivity extends PreferenceActivity {
Preference hotspotPasswordPreference;
CheckBoxPreference passwordRequirePreference;
CheckBoxPreference btSecureModePreference;
CheckBoxPreference statisticsPreference;
EditTextPreference odkDestinationDirPreference;
ListPreference defaultMethodPreference;
private SharedPreferences prefs;
Expand Down Expand Up @@ -75,6 +76,7 @@ private void addPreferences() {
passwordRequirePreference = (CheckBoxPreference) findPreference(PreferenceKeys.KEY_HOTSPOT_PWD_REQUIRE);
btSecureModePreference = (CheckBoxPreference) findPreference(PreferenceKeys.KEY_BLUETOOTH_SECURE_MODE);
odkDestinationDirPreference = (EditTextPreference) findPreference(PreferenceKeys.KEY_ODK_DESTINATION_DIR);
statisticsPreference = (CheckBoxPreference) findPreference(PreferenceKeys.KEY_DETAILED_STATISTICS);

prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());

Expand All @@ -89,11 +91,12 @@ private void addPreferences() {
boolean isPasswordSet = prefs.getBoolean(PreferenceKeys.KEY_HOTSPOT_PWD_REQUIRE, false);
odkDestinationDirPreference.setSummary(prefs.getString(PreferenceKeys.KEY_ODK_DESTINATION_DIR,
getString(R.string.default_odk_destination_dir)));
boolean isSecureMode = prefs.getBoolean(PreferenceKeys.KEY_BLUETOOTH_SECURE_MODE, true);

hotspotPasswordPreference.setEnabled(isPasswordSet);
passwordRequirePreference.setChecked(isPasswordSet);
boolean isSecureMode = prefs.getBoolean(PreferenceKeys.KEY_BLUETOOTH_SECURE_MODE, true);
btSecureModePreference.setChecked(isSecureMode);
boolean detailedStatisticsEnabled = prefs.getBoolean(PreferenceKeys.KEY_DETAILED_STATISTICS, false);
statisticsPreference.setChecked(detailedStatisticsEnabled);

hotspotNamePreference.setOnPreferenceChangeListener(preferenceChangeListener());
bluetoothNamePreference.setOnPreferenceChangeListener(preferenceChangeListener());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M3.5,18.49l6,-6.01 4,4L22,6.92l-1.41,-1.41 -7.09,7.97 -4,-4L2,16.99z"/>
</vector>
6 changes: 6 additions & 0 deletions skunkworks_crow/src/main/res/layout/fragment_stats.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@
android:layout_height="184dp"
android:layout_gravity="center" />

<com.github.mikephil.charting.charts.ScatterChart
android:id="@+id/detailed_chart"
android:layout_width="362dp"
android:layout_height="184dp"
android:layout_gravity="center" />

<View
style="@style/MatchParentWidth"
android:layout_weight="1.0" />
Expand Down
1 change: 1 addition & 0 deletions skunkworks_crow/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
<string name="title_bluetooth_name">Bluetooth Name</string>
<string name="title_hotspot_ssid">Hotspot SSID</string>
<string name="title_hotspot_password">Set Hotspot password</string>
<string name="title_select_chart">Show Detailed Statistics</string>
<string name="summary_security_bluetooth">Use secure transfer (needs pairing code, controlled by receiver)</string>
<string name="set_password">Set Password</string>
<string name="secure_mode">Enable Secure Mode</string>
Expand Down
6 changes: 6 additions & 0 deletions skunkworks_crow/src/main/res/xml/preferences_menu.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@
android:summary="@string/preference_default_method_summary"
android:title="@string/preference_default_method" />

<CheckBoxPreference
android:defaultValue="false"
android:icon="@drawable/ic_show_chart_black_24dp"
android:key="detailed_statistics"
android:title="@string/title_select_chart" />

</PreferenceCategory>

<PreferenceCategory android:title="Hotspot Settings">
Expand Down