Skip to content

Instantly share code, notes, and snippets.

@vasyl91
Created February 2, 2019 21:39
Show Gist options
  • Save vasyl91/0bc4ceb7b1fd78a2623fc3ae980f0be8 to your computer and use it in GitHub Desktop.
Save vasyl91/0bc4ceb7b1fd78a2623fc3ae980f0be8 to your computer and use it in GitHub Desktop.
GraphView React-Native CSV reader module
import android.graphics.Color;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.uimanager.SimpleViewManager;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.jjoe64.graphview.GraphView;
import com.jjoe64.graphview.GridLabelRenderer;
import com.jjoe64.graphview.helper.DateAsXAxisLabelFormatter;
import com.jjoe64.graphview.series.DataPoint;
import com.jjoe64.graphview.series.LineGraphSeries;
import com.jjoe64.graphview.Viewport;
import com.univocity.parsers.csv.CsvParser;
import com.univocity.parsers.csv.CsvParserSettings;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileFilter;
import java.io.FileReader;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.annotation.Nullable;
import org.apache.commons.lang3.time.DurationFormatUtils;
import org.apache.commons.io.comparator.LastModifiedFileComparator;
import org.apache.commons.io.filefilter.WildcardFileFilter;
// Created by Vasyl 25/01/2019
/*
** NOTE: Even though I implemented fastest csv parser on the market, GraphView still struggles to display correctly data greater than 200-300 lines.
** Optimally save to .csv not often than every 3 minutes (assuming really long night sleep: 20 logs per hour x 10 hours of sleep = 200 lines)
*/
// If you want to use it with RN add this file as module.
public class CSVGraph extends SimpleViewManager<GraphView> {
private final static String REACT_CLASS = "CSV_GRAPH";
public @Nullable int number;
ThemedReactContext appContext;
// Count files in provided directory
public int getFilesCount(File file) {
File[] files = file.listFiles();
int count = 0;
for (File f : files)
if (f.isDirectory())
count += getFilesCount(f);
else
count++;
return count;
}
@Override
public String getName() {
return REACT_CLASS;
}
@Override
public GraphView createViewInstance(ThemedReactContext reactContext) {
appContext = reactContext;
GraphView graphView = new GraphView(reactContext);
return graphView;
}
private void sendData(String eventName, String fileData) {
appContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName, fileData);
}
@ReactProp(name = "graphNumber")
public void setData(GraphView graphView, @Nullable int number){
// Get chosen .csv file and if it exists and is not empty, read two first columns: timestamp,value
// Depending on 'graphNumber' in JS file: the newest file - 0; second - 1; third - 2; ...tenth - 9
String directoryString = "/storage/emulated/0/SleepTracker/nights/";
File directory = new File(directoryString);
graphView.removeAllSeries();
if (getFilesCount(directory) > 0) {
FileFilter fileFilter = new WildcardFileFilter("*.csv");
File[] files = directory.listFiles(fileFilter);
Arrays.sort(files, LastModifiedFileComparator.LASTMODIFIED_REVERSE);
String stringFile = directoryString + files[number].getName();
File file = new File(stringFile);
if (file.length() > 0) {
List<String[]> data = new ArrayList<>();
String[] content = null;
CsvParserSettings settings = new CsvParserSettings();
CsvParser parser = new CsvParser(settings);
String[] rows;
parser.beginParsing(file);
String[] firstRow = parser.parseNext();
while ((rows = parser.parseNext()) != null) {
String lastRow = rows[0];
content = rows;
data.add(content);
DataPoint[] points = new DataPoint[data.size()];
for (int i = 0; i < data.size(); i++){
rows = data.get(i);
points[i] = new DataPoint(Long.parseLong(rows[0]), Integer.parseInt(rows[1]));
}
LineGraphSeries<DataPoint> series = new LineGraphSeries<>(points);
graphView.getViewport().setXAxisBoundsManual(true);
graphView.getViewport().setMinX(Long.parseLong(firstRow[0]));
graphView.getViewport().setMaxX(Long.parseLong(lastRow));
SimpleDateFormat hoursFormat = new SimpleDateFormat("HH':00'");
graphView.getGridLabelRenderer().setLabelFormatter(new DateAsXAxisLabelFormatter(appContext, hoursFormat));
graphView.getGridLabelRenderer().setNumHorizontalLabels(4);
graphView.getGridLabelRenderer().setHorizontalLabelsAngle(135);
graphView.getViewport().setScalableY(true);
series.setColor(Color.parseColor("#587F4A"));
graphView.addSeries(series);
// Change time format depending on scale
graphView.getViewport().setOnXAxisBoundsChangedListener(new Viewport.OnXAxisBoundsChangedListener() {
@Override
public void onXAxisBoundsChanged(double minX, double maxX, Viewport.OnXAxisBoundsChangedListener.Reason reason) {
maxX = graphView.getViewport().getMaxX(false);
minX = graphView.getViewport().getMinX(false);
Long view = (Double.valueOf(maxX).longValue() / 1000) - (Double.valueOf(minX).longValue() / 1000);
SimpleDateFormat hoursFormat;
if (view < 100) {
hoursFormat = new SimpleDateFormat("HH:mm");
} else {
hoursFormat = new SimpleDateFormat("HH':00'");
}
graphView.getGridLabelRenderer().setLabelFormatter(new DateAsXAxisLabelFormatter(appContext, hoursFormat));
}
});
// Send data info
String eventName = "INFO_" + String.valueOf(number);
String duration = DurationFormatUtils.formatDuration((Long.parseLong(lastRow) - Long.parseLong(firstRow[0])), "HH'h' mm'm'");
sendData(eventName, duration);
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment