diff --git a/android/ConductorsWatch/app/src/main/java/de/tonifetzer/conductorswatch/BpmEstimator.java b/android/ConductorsWatch/app/src/main/java/de/tonifetzer/conductorswatch/BpmEstimator.java
index 658b0fe..df414fd 100644
--- a/android/ConductorsWatch/app/src/main/java/de/tonifetzer/conductorswatch/BpmEstimator.java
+++ b/android/ConductorsWatch/app/src/main/java/de/tonifetzer/conductorswatch/BpmEstimator.java
@@ -6,12 +6,8 @@ import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Handler;
-import android.util.Log;
-
-import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.ThreadLocalRandom;
import de.tonifetzer.conductorswatch.utilities.Utils;
diff --git a/java/.gitignore b/java/.gitignore
new file mode 100644
index 0000000..39fb081
--- /dev/null
+++ b/java/.gitignore
@@ -0,0 +1,9 @@
+*.iml
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+/captures
+.externalNativeBuild
diff --git a/java/pom.xml b/java/pom.xml
new file mode 100644
index 0000000..69a2dfb
--- /dev/null
+++ b/java/pom.xml
@@ -0,0 +1,20 @@
+
+
+ 4.0.0
+
+ de.toni.bpm
+ bpm
+ 1.0-SNAPSHOT
+
+
+ com.github.wendykierp
+ JTransforms
+ 3.1
+ with-dependencies
+
+
+
+
+
\ No newline at end of file
diff --git a/java/src/main/java/Main.java b/java/src/main/java/Main.java
new file mode 100644
index 0000000..43ae67b
--- /dev/null
+++ b/java/src/main/java/Main.java
@@ -0,0 +1,97 @@
+import java.awt.*;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.stream.IntStream;
+
+/**
+ * Created by toni on 04/12/17.
+ */
+public class Main {
+
+ public static void main(String [ ] args) {
+ File folder = new File("/home/toni/Documents/programme/dirigent/measurements/wearR");
+ File[] listOfFiles = folder.listFiles();
+ Utils.ShowPNG windowRaw = new Utils.ShowPNG();
+ Utils.ShowPNG windowAuto = new Utils.ShowPNG();
+
+ // iterate trough files in measurements folder
+ for (File file : listOfFiles) {
+ if (file.isFile() && file.getName().contains(".csv")) {
+
+ Utils.AccelerometerWindowBuffer accWindowBuffer = new Utils.AccelerometerWindowBuffer(4096, 256);
+
+
+ //read the file line by line
+ try (BufferedReader br = new BufferedReader(new FileReader(file))) {
+ for (String line; (line = br.readLine()) != null; ) {
+ // process the line.
+ String[] measurement = line.split(";");
+
+ //if linear acc
+ if(measurement[1].equals("2")){
+ long ts = Long.parseLong(measurement[0]);
+ float x = Float.parseFloat(measurement[2]);
+ float y = Float.parseFloat(measurement[3]);
+ float z = Float.parseFloat(measurement[4]);
+ accWindowBuffer.add(new Utils.AccelerometerData(ts, x, y, z));
+ }
+
+ //do calculation stuff
+ if(accWindowBuffer.isNextWindowReady()){
+
+ //print raw x,y,z
+ double[] dTs = IntStream.range(0, accWindowBuffer.getTs().length).mapToDouble(i -> accWindowBuffer.getTs()[i]).toArray();
+ double[] dX = IntStream.range(0, accWindowBuffer.getX().length).mapToDouble(i -> accWindowBuffer.getX()[i]).toArray();
+ double[] dY = IntStream.range(0, accWindowBuffer.getY().length).mapToDouble(i -> accWindowBuffer.getY()[i]).toArray();
+ double[] dZ = IntStream.range(0, accWindowBuffer.getZ().length).mapToDouble(i -> accWindowBuffer.getZ()[i]).toArray();
+
+ Plot plotRaw = Plot.plot(Plot.plotOpts().
+ title("Raw Acc Data").
+ legend(Plot.LegendFormat.BOTTOM)).
+ series("x", Plot.data().xy(dTs, dX), Plot.seriesOpts().color(Color.RED)).
+ series("y", Plot.data().xy(dTs, dY), Plot.seriesOpts().color(Color.BLUE)).
+ series("z", Plot.data().xy(dTs, dZ), Plot.seriesOpts().color(Color.GREEN));
+
+ windowRaw.set(plotRaw.draw());
+
+ //auto corr
+ float[] xAutoCorr = Utils.fftAutoCorrelation(accWindowBuffer.getX(), 1024);
+ float[] yAutoCorr = Utils.fftAutoCorrelation(accWindowBuffer.getY(), 1024);
+ float[] zAutoCorr = Utils.fftAutoCorrelation(accWindowBuffer.getZ(), 1024);
+
+ //print autocorr
+ int[] tmp = IntStream.rangeClosed(-((xAutoCorr.length - 1)/2), ((xAutoCorr.length - 1)/2)).toArray();
+ double[] rangeAuto = IntStream.range(0, tmp.length).mapToDouble(i -> tmp[i]).toArray();
+ double[] dXAuto = IntStream.range(0, xAutoCorr.length).mapToDouble(i -> xAutoCorr[i]).toArray();
+ double[] dYAuto = IntStream.range(0, yAutoCorr.length).mapToDouble(i -> yAutoCorr[i]).toArray();
+ double[] dZAuto = IntStream.range(0, zAutoCorr.length).mapToDouble(i -> zAutoCorr[i]).toArray();
+
+ Plot plotCorr = Plot.plot(Plot.plotOpts().
+ title("Auto Correlation").
+ legend(Plot.LegendFormat.BOTTOM)).
+ series("x", Plot.data().xy(rangeAuto, dXAuto), Plot.seriesOpts().color(Color.RED)).
+ series("y", Plot.data().xy(rangeAuto, dYAuto), Plot.seriesOpts().color(Color.BLUE)).
+ series("z", Plot.data().xy(rangeAuto, dZAuto), Plot.seriesOpts().color(Color.GREEN));
+
+ windowAuto.set(plotCorr.draw());
+
+ //find peaks
+
+ //fill hols improve peaks
+
+ //estimate bpm between detected peaks
+ }
+
+
+ }
+ // line is not visible here.
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ }
+ }
+ }
+}
diff --git a/java/src/main/java/Plot.java b/java/src/main/java/Plot.java
new file mode 100644
index 0000000..93cce70
--- /dev/null
+++ b/java/src/main/java/Plot.java
@@ -0,0 +1,1027 @@
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Graphics2D;
+import java.awt.Point;
+import java.awt.Polygon;
+import java.awt.Rectangle;
+import java.awt.Stroke;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.imageio.ImageIO;
+
+/**
+ * Simple implementation of plot. Minimal features, no dependencies besides standard libraries.
+ * Options are self-descriptive, see also samples.
+ *
+ * @author Yuriy Guskov
+ */
+public class Plot {
+
+ public enum Line { NONE, SOLID, DASHED };
+ public enum Marker { NONE, CIRCLE, SQUARE, DIAMOND, COLUMN, BAR };
+ public enum AxisFormat { NUMBER, NUMBER_KGM, NUMBER_INT, TIME_HM, TIME_HMS, DATE, DATETIME_HM, DATETIME_HMS }
+ public enum LegendFormat { NONE, TOP, RIGHT, BOTTOM }
+
+ private enum HorizAlign { LEFT, CENTER, RIGHT }
+ private enum VertAlign { TOP, CENTER, BOTTOM }
+
+ private PlotOptions opts = new PlotOptions();
+
+ private Rectangle boundRect;
+ private PlotArea plotArea;
+ private Map xAxes = new HashMap(3);
+ private Map yAxes = new HashMap(3);
+ private Map dataSeriesMap = new LinkedHashMap(5);
+
+ public static Plot plot(PlotOptions opts) {
+ return new Plot(opts);
+ }
+
+ public static PlotOptions plotOpts() {
+ return new PlotOptions();
+ }
+
+ public static class PlotOptions {
+
+ private String title = "";
+ private int width = 800;
+ private int height = 600;
+ private Color backgroundColor = Color.WHITE;
+ private Color foregroundColor = Color.BLACK;
+ private Font titleFont = new Font("Arial", Font.BOLD, 16);
+ private int padding = 10; // padding for the entire image
+ private int plotPadding = 5; // padding for plot area (to have min and max values padded)
+ private int labelPadding = 10;
+ private int defaultLegendSignSize = 10;
+ private int legendSignSize = 10;
+ private Point grids = new Point(10 ,10); // grid lines by x and y
+ private Color gridColor = Color.GRAY;
+ private Stroke gridStroke = new BasicStroke(1.0f, BasicStroke.CAP_BUTT,
+ BasicStroke.JOIN_MITER, 10.0f, new float[] { 5.0f }, 0.0f);
+ private int tickSize = 5;
+ private Font labelFont = new Font("Arial", 0, 12);
+ private LegendFormat legend = LegendFormat.NONE;
+
+ private PlotOptions() {}
+
+ public PlotOptions title(String title) {
+ this.title = title;
+ return this;
+ }
+
+ public PlotOptions width(int width) {
+ this.width = width;
+ return this;
+ }
+
+ public PlotOptions height(int height) {
+ this.height = height;
+ return this;
+ }
+
+ public PlotOptions bgColor(Color color) {
+ this.backgroundColor = color;
+ return this;
+ }
+
+ public PlotOptions fgColor(Color color) {
+ this.foregroundColor = color;
+ return this;
+ }
+
+ public PlotOptions titleFont(Font font) {
+ this.titleFont = font;
+ return this;
+ }
+
+ public PlotOptions padding(int padding) {
+ this.padding = padding;
+ return this;
+ }
+
+ public PlotOptions plotPadding(int padding) {
+ this.plotPadding = padding;
+ return this;
+ }
+
+ public PlotOptions labelPadding(int padding) {
+ this.labelPadding = padding;
+ return this;
+ }
+
+ public PlotOptions labelFont(Font font) {
+ this.labelFont = font;
+ return this;
+ }
+
+ public PlotOptions grids(int byX, int byY) {
+ this.grids = new Point(byX, byY);
+ return this;
+ }
+
+ public PlotOptions gridColor(Color color) {
+ this.gridColor = color;
+ return this;
+ }
+
+ public PlotOptions gridStroke(Stroke stroke) {
+ this.gridStroke = stroke;
+ return this;
+ }
+
+ public PlotOptions tickSize(int value) {
+ this.tickSize = value;
+ return this;
+ }
+
+ public PlotOptions legend(LegendFormat legend) {
+ this.legend = legend;
+ return this;
+ }
+
+ }
+
+ private Plot(PlotOptions opts) {
+ if (opts != null)
+ this.opts = opts;
+ boundRect = new Rectangle(0, 0, this.opts.width, this.opts.height);
+ plotArea = new PlotArea();
+ }
+
+ public PlotOptions opts() {
+ return opts;
+ }
+
+ public Plot xAxis(String name, AxisOptions opts) {
+ xAxes.put(name, new Axis(name, opts));
+ return this;
+ }
+
+ public Plot yAxis(String name, AxisOptions opts) {
+ yAxes.put(name, new Axis(name, opts));
+ return this;
+ }
+
+ public Plot series(String name, Data data, DataSeriesOptions opts) {
+ DataSeries series = dataSeriesMap.get(name);
+ if (opts != null)
+ opts.setPlot(this);
+ if (series == null) {
+ series = new DataSeries(name, data, opts);
+ dataSeriesMap.put(name, series);
+ } else {
+ series.data = data;
+ series.opts = opts;
+ }
+ return this;
+ }
+
+ public Plot series(String name, DataSeriesOptions opts) {
+ DataSeries series = dataSeriesMap.get(name);
+ if (opts != null)
+ opts.setPlot(this);
+ if (series != null)
+ series.opts = opts;
+ return this;
+ }
+
+ private void calc(Graphics2D g) {
+ plotArea.calc(g);
+ }
+
+ private void clear() {
+ plotArea.clear();
+ for (DataSeries series : dataSeriesMap.values())
+ series.clear();
+ }
+
+ public BufferedImage draw() {
+ BufferedImage image = new BufferedImage(opts.width, opts.height, BufferedImage.TYPE_INT_RGB);
+ Graphics2D g = image.createGraphics();
+ try {
+ calc(g);
+ drawBackground(g);
+ plotArea.draw(g);
+ for (DataSeries series : dataSeriesMap.values())
+ series.draw(g);
+ return image;
+ } finally {
+ g.dispose();
+ }
+ }
+
+ private void drawBackground(Graphics2D g) {
+ g.setColor(opts.backgroundColor);
+ g.fillRect(0, 0, opts.width, opts.height);
+ }
+
+ public void save(String fileName, String type) throws IOException {
+ clear();
+ BufferedImage bi = draw();
+ File outputFile = new File(fileName + "." + type);
+ ImageIO.write(bi, type, outputFile);
+ }
+
+ private class Legend {
+ Rectangle rect;
+ Rectangle2D labelRect;
+ public int entryWidth;
+ public int entryWidthPadded;
+ public int entryCount;
+ public int xCount;
+ public int yCount;
+ }
+
+ private class PlotArea {
+
+ private Rectangle plotBorderRect = new Rectangle(); // boundRect | labels/legend | plotBorderRect | plotPadding | plotRect/clipRect
+ private Rectangle plotRect = new Rectangle();
+ private Rectangle plotClipRect = new Rectangle();
+ private Legend legend = new Legend();
+
+ private Range xPlotRange = new Range(0, 0);
+ private Range yPlotRange = new Range(0, 0);
+
+ public PlotArea() {
+ clear();
+ }
+
+ private void clear() {
+ plotBorderRect.setBounds(boundRect);
+ plotRectChanged();
+ }
+
+ private void offset(int dx, int dy, int dw, int dh) {
+ plotBorderRect.translate(dx, dy);
+ plotBorderRect.setSize(plotBorderRect.width - dx - dw, plotBorderRect.height - dy - dh);
+ plotRectChanged();
+ }
+
+ private void plotRectChanged() {
+ plotRect.setBounds(plotBorderRect.x + opts.plotPadding, plotBorderRect.y + opts.plotPadding,
+ plotBorderRect.width - opts.plotPadding * 2, plotBorderRect.height - opts.plotPadding * 2);
+ xPlotRange.setMin(plotRect.getX());
+ xPlotRange.setMax(plotRect.getX() + plotRect.getWidth());
+ yPlotRange.setMin(plotRect.getY());
+ yPlotRange.setMax(plotRect.getY() + plotRect.getHeight());
+
+ plotClipRect.setBounds(plotBorderRect.x + 1, plotBorderRect.y + 1, plotBorderRect.width - 1, plotBorderRect.height - 1);
+ }
+
+ private void calc(Graphics2D g) {
+ calcAxes(g);
+ calcRange(true);
+ calcRange(false);
+ calcAxisLabels(g, true);
+ calcAxisLabels(g, false);
+ g.setFont(opts.titleFont);
+ FontMetrics fm = g.getFontMetrics();
+ Rectangle2D titleRect = fm.getStringBounds(opts.title, g);
+ g.setFont(opts.labelFont);
+ fm = g.getFontMetrics();
+ int xAxesHeight = 0, xAxesHalfWidth = 0;
+ for (Map.Entry entry : xAxes.entrySet()) {
+ Axis xAxis = entry.getValue();
+ xAxesHeight += toInt(xAxis.labelRect.getHeight()) + opts.labelPadding * 2;
+ if (xAxis.labelRect.getWidth() > xAxesHalfWidth)
+ xAxesHalfWidth = toInt(xAxis.labelRect.getWidth());
+ }
+ int yAxesWidth = 0;
+ for (Map.Entry entry : yAxes.entrySet())
+ yAxesWidth += toInt(entry.getValue().labelRect.getWidth()) + opts.labelPadding * 2;
+ int dx = opts.padding + yAxesWidth;
+ int dy = opts.padding + toInt(titleRect.getHeight() + opts.labelPadding);
+ int dw = opts.padding;
+ if (opts.legend != LegendFormat.RIGHT)
+ dw += xAxesHalfWidth; // half of label goes beyond a plot in right bottom corner
+ int dh = opts.padding + xAxesHeight;
+ // offset for legend
+ Rectangle temp = new Rectangle(plotBorderRect); // save plotRect
+ offset(dx, dy, dw, dh);
+ calcLegend(g); // use plotRect
+ plotBorderRect.setBounds(temp); // restore plotRect
+ switch (opts.legend) {
+ case TOP: dy += legend.rect.height + opts.labelPadding; break;
+ case RIGHT: dw += legend.rect.width + opts.labelPadding; break;
+ case BOTTOM: dh += legend.rect.height; break;
+ default:
+ }
+ offset(dx, dy, dw, dh);
+ }
+
+ private void draw(Graphics2D g) {
+ drawPlotArea(g);
+ drawGrid(g);
+ drawAxes(g);
+ drawLegend(g);
+ // if check needed that content is inside padding
+ //g.setColor(Color.GRAY);
+ //g.drawRect(boundRect.x + opts.padding, boundRect.y + opts.padding, boundRect.width - opts.padding * 2, boundRect.height - opts.padding * 2);
+ }
+
+ private void drawPlotArea(Graphics2D g) {
+ g.setColor(opts.foregroundColor);
+ g.drawRect(plotBorderRect.x, plotBorderRect.y, plotBorderRect.width, plotBorderRect.height);
+ g.setFont(opts.titleFont);
+ drawLabel(g, opts.title, plotBorderRect.x + toInt(plotBorderRect.getWidth() / 2), opts.padding, HorizAlign.CENTER, VertAlign.TOP);
+ }
+
+ private void drawGrid(Graphics2D g) {
+ Stroke stroke = g.getStroke();
+ g.setStroke(opts.gridStroke);
+ g.setColor(opts.gridColor);
+
+ int leftX = plotBorderRect.x + 1;
+ int rightX = plotBorderRect.x + plotBorderRect.width - 1;
+ int topY = plotBorderRect.y + 1;
+ int bottomY = plotBorderRect.y + plotBorderRect.height - 1;
+
+ for (int i = 0; i < opts.grids.x + 1; i++) {
+ int x = toInt(plotRect.x + (plotRect.getWidth() / opts.grids.x) * i);
+ g.drawLine(x, topY, x, bottomY);
+ }
+
+ for (int i = 0; i < opts.grids.y + 1; i++) {
+ int y = toInt(plotRect.y + (plotRect.getHeight() / opts.grids.y) * i);
+ g.drawLine(leftX, y, rightX, y);
+ }
+
+ g.setStroke(stroke);
+ }
+
+ private void calcAxes(Graphics2D g) {
+ Axis xAxis = xAxes.isEmpty() ? new Axis("", null) : xAxes.values().iterator().next();
+ Axis yAxis = yAxes.isEmpty() ? new Axis("", null) : yAxes.values().iterator().next();
+ int xCount = 0, yCount = 0;
+ for (DataSeries series : dataSeriesMap.values()) {
+ if (series.opts.xAxis == null) {
+ series.opts.xAxis = xAxis;
+ xCount++;
+ }
+ if (series.opts.yAxis == null) {
+ series.opts.yAxis = yAxis;
+ yCount++;
+ }
+ series.addAxesToName();
+ }
+ if (xAxes.isEmpty() && xCount > 0)
+ xAxes.put("x", xAxis);
+ if (yAxes.isEmpty() && yCount > 0)
+ yAxes.put("y", yAxis);
+ }
+
+ private void calcAxisLabels(Graphics2D g, boolean isX) {
+ FontMetrics fm = g.getFontMetrics();
+ Rectangle2D rect = null;
+ double w = 0, h = 0;
+ Map axes = isX ? xAxes : yAxes;
+ int grids = isX ? opts.grids.x : opts.grids.y;
+ for (Map.Entry entry : axes.entrySet()) {
+ Axis axis = entry.getValue();
+ axis.labels = new String[grids + 1];
+ axis.labelRect = fm.getStringBounds("", g);
+ double xStep = axis.opts.range.diff / grids;
+ for (int j = 0; j < grids + 1; j++) {
+ axis.labels[j] = formatDouble(axis.opts.range.min + xStep * j, axis.opts.format);
+ rect = fm.getStringBounds(axis.labels[j], g);
+ if (rect.getWidth() > w)
+ w = rect.getWidth();
+ if (rect.getHeight() > h)
+ h = rect.getHeight();
+ }
+ axis.labelRect.setRect(0, 0, w, h);
+ }
+ }
+
+ private void calcRange(boolean isX) {
+ for (DataSeries series : dataSeriesMap.values()) {
+ Axis axis = isX ? series.opts.xAxis : series.opts.yAxis;
+ if (axis.opts.dynamicRange) {
+ Range range = isX ? series.xRange() : series.yRange();
+ if (axis.opts.range == null)
+ axis.opts.range = range;
+ else {
+ if (range.max > axis.opts.range.max)
+ axis.opts.range.setMax(range.max);
+ if (range.min < axis.opts.range.min)
+ axis.opts.range.setMin(range.min);
+ }
+ }
+ }
+ Map axes = isX ? xAxes : yAxes;
+ for (Iterator it = axes.values().iterator(); it.hasNext(); ) {
+ Axis axis = it.next();
+ if (axis.opts.range == null)
+ it.remove();
+ }
+ }
+
+ private void drawAxes(Graphics2D g) {
+ g.setFont(opts.labelFont);
+ g.setColor(opts.foregroundColor);
+
+ int leftXPadded = plotBorderRect.x - opts.labelPadding;
+ int rightX = plotBorderRect.x + plotBorderRect.width;
+ int bottomY = plotBorderRect.y + plotBorderRect.height;
+ int bottomYPadded = bottomY + opts.labelPadding;
+
+ int axisOffset = 0;
+ for (Map.Entry entry : xAxes.entrySet()) {
+ Axis axis = entry.getValue();
+ double xStep = axis.opts.range.diff / opts.grids.x;
+
+ drawLabel(g, axis.name, rightX + opts.labelPadding, bottomY + axisOffset, HorizAlign.LEFT, VertAlign.CENTER);
+ g.drawLine(plotRect.x, bottomY + axisOffset, plotRect.x + plotRect.width, bottomY + axisOffset);
+
+ for (int j = 0; j < opts.grids.x + 1; j++) {
+ int x = toInt(plotRect.x + (plotRect.getWidth() / opts.grids.x) * j);
+ drawLabel(g, formatDouble(axis.opts.range.min + xStep * j, axis.opts.format), x, bottomYPadded + axisOffset, HorizAlign.CENTER, VertAlign.TOP);
+ g.drawLine(x, bottomY + axisOffset, x, bottomY + opts.tickSize + axisOffset);
+ }
+ axisOffset += toInt(axis.labelRect.getHeight() + opts.labelPadding * 2);
+ }
+
+ axisOffset = 0;
+ for (Map.Entry entry : yAxes.entrySet()) {
+ Axis axis = entry.getValue();
+ double yStep = axis.opts.range.diff / opts.grids.y;
+
+ drawLabel(g, axis.name, leftXPadded - axisOffset, plotBorderRect.y - toInt(axis.labelRect.getHeight() + opts.labelPadding), HorizAlign.RIGHT, VertAlign.CENTER);
+ g.drawLine(plotBorderRect.x - axisOffset, plotRect.y + plotRect.height, plotBorderRect.x - axisOffset, plotRect.y);
+
+ for (int j = 0; j < opts.grids.y + 1; j++) {
+ int y = toInt(plotRect.y + (plotRect.getHeight() / opts.grids.y) * j);
+ drawLabel(g, formatDouble(axis.opts.range.max - yStep * j, axis.opts.format), leftXPadded - axisOffset, y, HorizAlign.RIGHT, VertAlign.CENTER);
+ g.drawLine(plotBorderRect.x - axisOffset, y, plotBorderRect.x - opts.tickSize - axisOffset, y);
+ }
+ axisOffset += toInt(axis.labelRect.getWidth() + opts.labelPadding * 2);
+ }
+ }
+
+ private void calcLegend(Graphics2D g) {
+ legend.rect = new Rectangle(0, 0);
+ if (opts.legend == LegendFormat.NONE)
+ return;
+ int size = dataSeriesMap.size();
+ if (size == 0)
+ return;
+
+ FontMetrics fm = g.getFontMetrics();
+ Iterator it = dataSeriesMap.values().iterator();
+ legend.labelRect = fm.getStringBounds(it.next().nameWithAxes, g);
+ int legendSignSize = opts.defaultLegendSignSize;
+ while (it.hasNext()) {
+ DataSeries series = it.next();
+ Rectangle2D rect = fm.getStringBounds(series.nameWithAxes, g);
+ if (rect.getWidth() > legend.labelRect.getWidth())
+ legend.labelRect.setRect(0, 0, rect.getWidth(), legend.labelRect.getHeight());
+ if (rect.getHeight() > legend.labelRect.getHeight())
+ legend.labelRect.setRect(0, 0, legend.labelRect.getWidth(), rect.getHeight());
+ switch (series.opts.marker) {
+ case CIRCLE: case SQUARE:
+ if (series.opts.markerSize + opts.defaultLegendSignSize > legendSignSize)
+ legendSignSize = series.opts.markerSize + opts.defaultLegendSignSize;
+ break;
+ case DIAMOND:
+ if (series.getDiagMarkerSize() + opts.defaultLegendSignSize > legendSignSize)
+ legendSignSize = series.getDiagMarkerSize() + opts.defaultLegendSignSize;
+ break;
+ default:
+ }
+ }
+ opts.legendSignSize = legendSignSize;
+
+ legend.entryWidth = legendSignSize + opts.labelPadding + toInt(legend.labelRect.getWidth());
+ legend.entryWidthPadded = legend.entryWidth + opts.labelPadding;
+
+ switch (opts.legend) {
+ case TOP: case BOTTOM:
+ legend.entryCount = (int) Math.floor((double) (plotBorderRect.width - opts.labelPadding) / legend.entryWidthPadded);
+ legend.xCount = size <= legend.entryCount ? size : legend.entryCount;
+ legend.yCount = size <= legend.entryCount ? 1 : (int) Math.ceil((double) size / legend.entryCount);
+ legend.rect.width = opts.labelPadding + (legend.xCount * legend.entryWidthPadded);
+ legend.rect.height = opts.labelPadding + toInt(legend.yCount * (opts.labelPadding + legend.labelRect.getHeight()));
+ legend.rect.x = plotBorderRect.x + (plotBorderRect.width - legend.rect.width) / 2;
+ if (opts.legend == LegendFormat.TOP)
+ legend.rect.y = plotBorderRect.y;
+ else
+ legend.rect.y = boundRect.height - legend.rect.height - opts.padding;
+ break;
+ case RIGHT:
+ legend.rect.width = opts.labelPadding * 3 + legendSignSize + toInt(legend.labelRect.getWidth());
+ legend.rect.height = opts.labelPadding * (size + 1) + toInt(legend.labelRect.getHeight() * size);
+ legend.rect.x = boundRect.width - legend.rect.width - opts.padding;
+ legend.rect.y = plotBorderRect.y + plotBorderRect.height / 2 - legend.rect.height / 2;
+ break;
+ default:
+ }
+ }
+
+ private void drawLegend(Graphics2D g) {
+ if (opts.legend == LegendFormat.NONE)
+ return;
+
+ g.drawRect(legend.rect.x, legend.rect.y, legend.rect.width, legend.rect.height);
+ int labelHeight = toInt(legend.labelRect.getHeight());
+ int x = legend.rect.x + opts.labelPadding;
+ int y = legend.rect.y + opts.labelPadding + labelHeight / 2;
+
+ switch (opts.legend) {
+ case TOP: case BOTTOM:
+ int i = 0;
+ for (DataSeries series : dataSeriesMap.values()) {
+ drawLegendEntry(g, series, x, y);
+ x += legend.entryWidthPadded;
+ if ((i + 1) % legend.xCount == 0) {
+ x = legend.rect.x + opts.labelPadding;
+ y += opts.labelPadding + labelHeight;
+ }
+ i++;
+ }
+ break;
+ case RIGHT:
+ for (DataSeries series : dataSeriesMap.values()) {
+ drawLegendEntry(g, series, x, y);
+ y += opts.labelPadding + labelHeight;
+ }
+ break;
+ default:
+ }
+ }
+
+ private void drawLegendEntry(Graphics2D g, DataSeries series, int x, int y) {
+ series.fillArea(g, x, y, x + opts.legendSignSize, y, y + opts.legendSignSize / 2);
+ series.drawLine(g, x, y, x + opts.legendSignSize, y);
+ series.drawMarker(g, x + opts.legendSignSize / 2, y, x, y + opts.legendSignSize / 2);
+ g.setColor(opts.foregroundColor);
+ drawLabel(g, series.nameWithAxes, x + opts.legendSignSize + opts.labelPadding, y, HorizAlign.LEFT, VertAlign.CENTER);
+ }
+
+ }
+
+ public static class Range {
+
+ private double min;
+ private double max;
+ private double diff;
+
+ public Range(double min, double max) {
+ this.min = min;
+ this.max = max;
+ this.diff = max - min;
+ }
+
+ public Range(Range range) {
+ this.min = range.min;
+ this.max = range.max;
+ this.diff = max - min;
+ }
+
+ public void setMin(double min) {
+ this.min = min;
+ this.diff = max - min;
+ }
+
+ public void setMax(double max) {
+ this.max = max;
+ this.diff = max - min;
+ }
+
+ @Override
+ public String toString() {
+ return "Range [min=" + min + ", max=" + max + "]";
+ }
+
+ }
+
+ public static AxisOptions axisOpts() {
+ return new AxisOptions();
+ }
+
+ public static class AxisOptions {
+
+ private AxisFormat format = AxisFormat.NUMBER;
+ private boolean dynamicRange = true;
+ private Range range;
+
+ public AxisOptions format(AxisFormat format) {
+ this.format = format;
+ return this;
+ }
+
+ public AxisOptions range(double min, double max) {
+ this.range = new Range(min, max);
+ this.dynamicRange = false;
+ return this;
+ }
+
+ }
+
+ private class Axis {
+
+ private String name;
+ private AxisOptions opts = new AxisOptions();
+ private Rectangle2D labelRect;
+ private String[] labels;
+
+ public Axis(String name, AxisOptions opts) {
+ this.name = name;
+ if (opts != null)
+ this.opts = opts;
+ }
+
+ @Override
+ public String toString() {
+ return "Axis [name=" + name + ", opts=" + opts + "]";
+ }
+
+ }
+
+ public static DataSeriesOptions seriesOpts() {
+ return new DataSeriesOptions();
+ }
+
+ public static class DataSeriesOptions {
+
+ private Color seriesColor = Color.BLUE;
+ private Line line = Line.SOLID;
+ private int lineWidth = 2;
+ private float[] lineDash = new float[] { 3.0f, 3.0f };
+ private Marker marker = Marker.NONE;
+ private int markerSize = 10;
+ private Color markerColor = Color.WHITE;
+ private Color areaColor = null;
+ private String xAxisName;
+ private String yAxisName;
+ private Axis xAxis;
+ private Axis yAxis;
+
+ public DataSeriesOptions color(Color seriesColor) {
+ this.seriesColor = seriesColor;
+ return this;
+ }
+
+ public DataSeriesOptions line(Line line) {
+ this.line = line;
+ return this;
+ }
+
+ public DataSeriesOptions lineWidth(int width) {
+ this.lineWidth = width;
+ return this;
+ }
+
+ public DataSeriesOptions lineDash(float[] dash) {
+ this.lineDash = dash;
+ return this;
+ }
+
+ public DataSeriesOptions marker(Marker marker) {
+ this.marker = marker;
+ return this;
+ }
+
+ public DataSeriesOptions markerSize(int markerSize) {
+ this.markerSize = markerSize;
+ return this;
+ }
+
+ public DataSeriesOptions markerColor(Color color) {
+ this.markerColor = color;
+ return this;
+ }
+
+ public DataSeriesOptions areaColor(Color color) {
+ this.areaColor = color;
+ return this;
+ }
+
+ public DataSeriesOptions xAxis(String name) {
+ this.xAxisName = name;
+ return this;
+ }
+
+ public DataSeriesOptions yAxis(String name) {
+ this.yAxisName = name;
+ return this;
+ }
+
+ private void setPlot(Plot plot) {
+ if (plot != null)
+ this.xAxis = plot.xAxes.get(xAxisName);
+ if (plot != null)
+ this.yAxis = plot.yAxes.get(yAxisName);
+ }
+
+ }
+
+ public static Data data() {
+ return new Data();
+ }
+
+ public static class Data {
+
+ private double[] x1;
+ private double[] y1;
+ private List x2;
+ private List y2;
+
+ private Data() {}
+
+ public Data xy(double[] x, double[] y) {
+ this.x1 = x;
+ this.y1 = y;
+ return this;
+ }
+
+ public Data xy(double x, double y) {
+ if (this.x2 == null || this.y2 == null) {
+ this.x2 = new ArrayList(10);
+ this.y2 = new ArrayList(10);
+ }
+ x2.add(x);
+ y2.add(y);
+ return this;
+ }
+
+ public Data xy(List x, List y) {
+ this.x2 = x;
+ this.y2 = y;
+ return this;
+ }
+
+ public int size() {
+ if (x1 != null)
+ return x1.length;
+ if (x2 != null)
+ return x2.size();
+ return 0;
+ }
+
+ public double x(int i) {
+ if (x1 != null)
+ return x1[i];
+ if (x2 != null)
+ return x2.get(i);
+ return 0;
+ }
+
+ public double y(int i) {
+ if (y1 != null)
+ return y1[i];
+ if (y2 != null)
+ return y2.get(i);
+ return 0;
+ }
+
+ }
+
+ public class DataSeries {
+
+ private String name;
+ private String nameWithAxes;
+ private DataSeriesOptions opts = new DataSeriesOptions();
+ private Data data;
+
+ public DataSeries(String name, Data data, DataSeriesOptions opts) {
+ if (opts != null)
+ this.opts = opts;
+ this.name = name;
+ this.data = data;
+ if (this.data == null)
+ this.data = data();
+ }
+
+ public void clear() {
+ }
+
+ private void addAxesToName() {
+ this.nameWithAxes = this.name + " (" + opts.yAxis.name + "/" + opts.xAxis.name + ")";
+ }
+
+ private Range xRange() {
+ Range range = new Range(0, 0);
+ if (data != null && data.size() > 0) {
+ range = new Range(data.x(0), data.x(0));
+ for (int i = 1; i < data.size(); i++) {
+ if (data.x(i) > range.max)
+ range.setMax(data.x(i));
+ if (data.x(i) < range.min)
+ range.setMin(data.x(i));
+ }
+ }
+ return range;
+ }
+
+ private Range yRange() {
+ Range range = new Range(0, 0);
+ if (data != null && data.size() > 0) {
+ range = new Range(data.y(0), data.y(0));
+ for (int i = 1; i < data.size(); i++) {
+ if (data.y(i) > range.max)
+ range.setMax(data.y(i));
+ if (data.y(i) < range.min)
+ range.setMin(data.y(i));
+ }
+ }
+ return range;
+ }
+
+ private void draw(Graphics2D g) {
+ g.setClip(plotArea.plotClipRect);
+ if (data != null) {
+ double x1 = 0, y1 = 0;
+ int size = data.size();
+ if (opts.line != Line.NONE)
+ for (int j = 0; j < size; j++) {
+ double x2 = x2x(data.x(j), opts.xAxis.opts.range, plotArea.xPlotRange);
+ double y2 = y2y(data.y(j), opts.yAxis.opts.range, plotArea.yPlotRange);
+ int ix1 = toInt(x1), iy1 = toInt(y1), ix2 = toInt(x2), iy2 = toInt(y2);
+ int iy3 = plotArea.plotRect.y + plotArea.plotRect.height;
+ // special case for the case when only the first point present
+ if (size == 1) {
+ ix1 = ix2;
+ iy1 = iy2;
+ }
+ if (j != 0 || size == 1) {
+ fillArea(g, ix1, iy1, ix2, iy2, iy3);
+ drawLine(g, ix1, iy1, ix2, iy2);
+ }
+ x1 = x2;
+ y1 = y2;
+ }
+
+ int halfMarkerSize = opts.markerSize / 2;
+ int halfDiagMarkerSize = getDiagMarkerSize() / 2;
+ g.setStroke(new BasicStroke(2));
+ if (opts.marker != Marker.NONE)
+ for (int j = 0; j < size; j++) {
+ double x2 = x2x(data.x(j), opts.xAxis.opts.range, plotArea.xPlotRange);
+ double y2 = y2y(data.y(j), opts.yAxis.opts.range, plotArea.yPlotRange);
+ drawMarker(g, halfMarkerSize, halfDiagMarkerSize, x2, y2,
+ plotArea.plotRect.x, plotArea.plotRect.y + plotArea.plotRect.height);
+ }
+ }
+ }
+
+ private int getDiagMarkerSize() {
+ return (int) Math.round(Math.sqrt(2 * opts.markerSize * opts.markerSize));
+ }
+
+ private void fillArea(Graphics2D g, int ix1, int iy1, int ix2, int iy2, int iy3) {
+ if (opts.areaColor != null) {
+ g.setColor(opts.areaColor);
+ g.fill(new Polygon(
+ new int[] { ix1, ix2, ix2, ix1 },
+ new int[] { iy1, iy2, iy3, iy3 },
+ 4));
+ g.setColor(opts.seriesColor);
+ }
+ }
+
+ private void drawLine(Graphics2D g, int ix1, int iy1, int ix2, int iy2) {
+ if (opts.line != Line.NONE) {
+ g.setColor(opts.seriesColor);
+ setStroke(g);
+ g.drawLine(ix1, iy1, ix2, iy2);
+ }
+ }
+
+ private void setStroke(Graphics2D g) {
+ switch (opts.line) {
+ case SOLID:
+ g.setStroke(new BasicStroke(opts.lineWidth));
+ break;
+ case DASHED:
+ g.setStroke(new BasicStroke(opts.lineWidth, BasicStroke.CAP_ROUND,
+ BasicStroke.JOIN_ROUND, 10.0f, opts.lineDash, 0.0f));
+ break;
+ default:
+ }
+ }
+
+ private void drawMarker(Graphics2D g, int x2, int y2, int x3, int y3) {
+ int halfMarkerSize = opts.markerSize / 2;
+ int halfDiagMarkerSize = getDiagMarkerSize() / 2;
+ g.setStroke(new BasicStroke(2));
+ drawMarker(g, halfMarkerSize, halfDiagMarkerSize, x2, y2, x3, y3);
+ }
+
+ private void drawMarker(Graphics2D g, int halfMarkerSize, int halfDiagMarkerSize, double x2, double y2, double x3, double y3) {
+ switch (opts.marker) {
+ case CIRCLE:
+ g.setColor(opts.markerColor);
+ g.fillOval(toInt(x2 - halfMarkerSize), toInt(y2 - halfMarkerSize), opts.markerSize, opts.markerSize);
+ g.setColor(opts.seriesColor);
+ g.drawOval(toInt(x2 - halfMarkerSize), toInt(y2 - halfMarkerSize), opts.markerSize, opts.markerSize);
+ break;
+ case SQUARE:
+ g.setColor(opts.markerColor);
+ g.fillRect(toInt(x2 - halfMarkerSize), toInt(y2 - halfMarkerSize), opts.markerSize, opts.markerSize);
+ g.setColor(opts.seriesColor);
+ g.drawRect(toInt(x2 - halfMarkerSize), toInt(y2 - halfMarkerSize), opts.markerSize, opts.markerSize);
+ break;
+ case DIAMOND:
+ int[] xpts = { toInt(x2), toInt(x2 + halfDiagMarkerSize), toInt(x2), toInt(x2 - halfDiagMarkerSize) };
+ int[] ypts = { toInt(y2 - halfDiagMarkerSize), toInt(y2), toInt(y2 + halfDiagMarkerSize), toInt(y2) };
+ g.setColor(opts.markerColor);
+ g.fillPolygon(xpts, ypts, 4);
+ g.setColor(opts.seriesColor);
+ g.drawPolygon(xpts, ypts, 4);
+ break;
+ case COLUMN:
+ g.setColor(opts.markerColor);
+ g.fillRect(toInt(x2), toInt(y2), opts.markerSize, toInt(y3 - y2));
+ g.setColor(opts.seriesColor);
+ g.drawRect(toInt(x2), toInt(y2), opts.markerSize, toInt(y3 - y2));
+ break;
+ case BAR:
+ g.setColor(opts.markerColor);
+ g.fillRect(toInt(x3), toInt(y2), toInt(x2 - x3), opts.markerSize);
+ g.setColor(opts.seriesColor);
+ g.drawRect(toInt(x3), toInt(y2), toInt(x2 - x3), opts.markerSize);
+ break;
+ default:
+ }
+ }
+
+ }
+
+ private static void drawLabel(Graphics2D g, String s, int x, int y, HorizAlign hAlign, VertAlign vAlign) {
+ FontMetrics fm = g.getFontMetrics();
+ Rectangle2D rect = fm.getStringBounds(s, g);
+
+ // by default align by left
+ if (hAlign == HorizAlign.RIGHT)
+ x -= rect.getWidth();
+ else if (hAlign == HorizAlign.CENTER)
+ x -= rect.getWidth() / 2;
+
+ // by default align by bottom
+ if (vAlign == VertAlign.TOP)
+ y += rect.getHeight();
+ else if (vAlign == VertAlign.CENTER)
+ y += rect.getHeight() / 2;
+
+ g.drawString(s, x, y);
+ }
+
+ public static String formatDouble(double d, AxisFormat format) {
+ switch (format) {
+ case TIME_HM: return String.format("%tR", new java.util.Date((long) d));
+ case TIME_HMS: return String.format("%tT", new java.util.Date((long) d));
+ case DATE: return String.format("%tF", new java.util.Date((long) d));
+ case DATETIME_HM: return String.format("%tF %1$tR", new java.util.Date((long) d));
+ case DATETIME_HMS: return String.format("%tF %1$tT", new java.util.Date((long) d));
+ case NUMBER_KGM: return formatDoubleAsNumber(d, true);
+ case NUMBER_INT: return Integer.toString((int) d);
+ default: return formatDoubleAsNumber(d, false);
+ }
+ }
+
+ private static String formatDoubleAsNumber(double d, boolean useKGM) {
+ if (useKGM && d > 1000 && d < 1000000000000l) {
+ long[] numbers = new long[] { 1000l, 1000000l, 1000000000l };
+ char[] suffix = new char[] { 'K', 'M', 'G' };
+
+ int i = 0;
+ double r = 0;
+ for (long number : numbers) {
+ r = d / number;
+ if (r < 1000)
+ break;
+ i++;
+ }
+ if (i == suffix.length)
+ i--;
+ return String.format("%1$,.2f%2$c", r, suffix[i]);
+ }
+ else
+ return String.format("%1$.3G", d);
+ }
+
+ private static double x2x(double x, Range xr1, Range xr2) {
+ return xr1.diff == 0 ? xr2.min + xr2.diff / 2 : xr2.min + (x - xr1.min) / xr1.diff * xr2.diff;
+ }
+
+ // y axis is reverse in Graphics
+ private static double y2y(double x, Range xr1, Range xr2) {
+ return xr1.diff == 0 ? xr2.min + xr2.diff / 2 : xr2.max - (x - xr1.min) / xr1.diff * xr2.diff;
+ }
+
+ private static int toInt(double d) {
+ return (int) Math.round(d);
+ }
+
+}
diff --git a/java/src/main/java/Utils.java b/java/src/main/java/Utils.java
new file mode 100644
index 0000000..6beb05f
--- /dev/null
+++ b/java/src/main/java/Utils.java
@@ -0,0 +1,210 @@
+import org.jtransforms.fft.FloatFFT_1D;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+
+public class Utils {
+ public static float getDistance(float x1, float y1, float x2, float y2) {
+ return (float) Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
+ }
+
+ public static class AccelerometerData {
+
+ public float x,y,z;
+ public long ts;
+
+ public AccelerometerData(long ts, float x, float y, float z){
+ this.ts = ts;
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ }
+ }
+
+ public static class AccelerometerWindowBuffer extends ArrayList {
+
+ private int mWindowSize;
+ private int mOverlapSize;
+ private int mOverlapCounter;
+ private float[] mX;
+ private float[] mY;
+ private float[] mZ;
+ private long[] mTs;
+
+ public AccelerometerWindowBuffer(int windowSize, int overlap){
+ this.mWindowSize = windowSize;
+ this.mOverlapSize = overlap;
+ mOverlapCounter = 1;
+
+ mX = new float[this.mWindowSize];
+ mY = new float[this.mWindowSize];
+ mZ = new float[this.mWindowSize];
+ mTs = new long[this.mWindowSize];
+ }
+
+ public boolean add(AccelerometerData ad){
+ boolean r = super.add(ad);
+ if (size() > mWindowSize){
+ removeRange(0, size() - mWindowSize);
+ }
+
+ //update the double arrays.
+ for (int i = 0; i < size(); ++i) {
+ mX[i] = get(i).x;
+ mY[i] = get(i).y;
+ mZ[i] = get(i).z;
+ mTs[i] = get(i).ts;
+ }
+
+ ++mOverlapCounter;
+ return r;
+ }
+
+ public boolean isNextWindowReady(){
+ if(size() == mWindowSize && mOverlapCounter > mOverlapSize){
+ mOverlapCounter = 1;
+ return true;
+ }
+ return false;
+ }
+
+ public AccelerometerData getYongest() {
+ return get(size() - 1);
+ }
+
+ public AccelerometerData getOldest() {
+ return get(0);
+ }
+
+ public float[] getX(){
+ return mX;
+ }
+
+ public float[] getY(){
+ return mY;
+ }
+
+ public float[] getZ(){
+ return mZ;
+ }
+
+ public long[] getTs(){
+ return mTs;
+ }
+ }
+
+
+ public static float sqr(float x) {
+ return x * x;
+ }
+
+ public static int nextPow2(int a){
+ return a == 0 ? 0 : 32 - Integer.numberOfLeadingZeros(a - 1);
+ }
+
+ public static float mean(float[] data){
+ float sum = 0;
+ for (int i = 0; i < data.length; i++) {
+ sum += data[i];
+ }
+ return sum / data.length;
+ }
+
+ public static float[] removeZero(float[] array){
+ int j = 0;
+ for( int i=0; i