From 7129b9b5b61d99d8e2ddeb652ae5cedd600b4e2f Mon Sep 17 00:00:00 2001 From: hneemann Date: Mon, 3 Jul 2017 11:28:46 +0200 Subject: [PATCH] created a data plotter --- .../de/neemann/digital/data/DataPlotter.java | 138 ++++++++++++++++++ .../java/de/neemann/digital/data/Value.java | 4 +- .../de/neemann/digital/data/ValueTable.java | 80 +++++++++- .../neemann/digital/data/ValueTableTest.java | 47 ++++++ 4 files changed, 261 insertions(+), 8 deletions(-) create mode 100644 src/main/java/de/neemann/digital/data/DataPlotter.java create mode 100644 src/test/java/de/neemann/digital/data/ValueTableTest.java diff --git a/src/main/java/de/neemann/digital/data/DataPlotter.java b/src/main/java/de/neemann/digital/data/DataPlotter.java new file mode 100644 index 000000000..0c08711e6 --- /dev/null +++ b/src/main/java/de/neemann/digital/data/DataPlotter.java @@ -0,0 +1,138 @@ +package de.neemann.digital.data; + +import de.neemann.digital.draw.graphics.Graphic; +import de.neemann.digital.draw.graphics.Orientation; +import de.neemann.digital.draw.graphics.Style; +import de.neemann.digital.draw.graphics.Vector; +import de.neemann.digital.draw.shapes.Drawable; + +/** + * The dataSet stores the collected DataSamples. + * Every DataSample contains the values of al signals at a given time. + * + * @author hneemann + */ +public class DataPlotter implements Drawable { + private final ValueTable data; + private final int maxTextLength; + private double size = SIZE; + + /** + * Creates a simple dummy DataSet used for creating the DataShape + */ + public DataPlotter() { + this(new ValueTable("A", "B", "C") + .add(new Value[]{new Value(0), new Value(0), new Value(0)}) + .add(new Value[]{new Value(0), new Value(1), new Value(0)}) + .add(new Value[]{new Value(0), new Value(1), new Value(0)}) + ); + } + + /** + * Creates a new instance + * + * @param data the signals used to collect DataSamples + */ + public DataPlotter(ValueTable data) { + this.data = data; + int tl = 0; + for (int i = 0; i < data.getColumns(); i++) { + String text = data.getColumnName(i); + int w = text.length(); + if (w > tl) tl = w; + } + maxTextLength = tl; + } + + private static final int BORDER = 10; + private static final int SIZE = 25; + private static final int SEP2 = 5; + private static final int SEP = SEP2 * 2; + + /** + * Fits the data in the visible area + * + * @param width width of the frame + */ + public void fitInside(int width) { + size = ((double) (width - getTextBorder())) / data.getRows(); + } + + /** + * Apply a scaling factor + * + * @param f the factor + */ + public void scale(double f) { + size *= f; + if (size < Style.NORMAL.getThickness()) size = Style.NORMAL.getThickness(); + if (size > SIZE * 4) size = SIZE * 4; + } + + @Override + synchronized public void drawTo(Graphic g, Style highLight) { + int x = getTextBorder(); + + int yOffs = SIZE / 2; + int y = BORDER; + int signals = data.getColumns(); + for (int i = 0; i < signals; i++) { + String text = data.getColumnName(i); + g.drawText(new Vector(x - 2, y + yOffs), new Vector(x + 1, y + yOffs), text, Orientation.RIGHTCENTER, Style.NORMAL); + g.drawLine(new Vector(x, y - SEP2), new Vector(x + (int) (size * data.getRows()), y - SEP2), Style.DASH); + y += SIZE + SEP; + } + g.drawLine(new Vector(x, y - SEP2), new Vector(x + (int) (size * data.getRows()), y - SEP2), Style.DASH); + + + int[] lastRy = new int[signals]; + boolean first = true; + double pos = 0; + for (Value[] s : data) { + int xx = (int) (pos + x); + g.drawLine(new Vector(xx, BORDER - SEP2), new Vector(xx, (SIZE + SEP) * signals + BORDER - SEP2), Style.DASH); + y = BORDER; + for (int i = 0; i < signals; i++) { + + long width = data.getMax(i); + if (width == 0) width = 1; + int ry = (int) (SIZE - (SIZE * s[i].getValue()) / width); + g.drawLine(new Vector(xx, y + ry), new Vector((int) (xx + size), y + ry), Style.NORMAL); + if (!first && ry != lastRy[i]) + g.drawLine(new Vector(xx, y + lastRy[i]), new Vector(xx, y + ry), Style.NORMAL); + + lastRy[i] = ry; + y += SIZE + SEP; + } + first = false; + pos += size; + } + g.drawLine(new Vector(x, BORDER - SEP2), new Vector(x, (SIZE + SEP) * signals + BORDER - SEP2), Style.DASH); + } + + private int getTextBorder() { + return maxTextLength * Style.NORMAL.getFontSize() / 2 + BORDER + SEP; + } + + /** + * @return the preferred width of the graphical representation + */ + public int getGraphicWidth() { + return getTextBorder() + data.getRows() * SIZE; + } + + /** + * @return the preferred height of the graphical representation + */ + public int getGraphicHeight() { + return data.getColumns() * (SIZE + SEP) + 2 * BORDER; + } + + /** + * @return the current width of the graphical representation + */ + public int getCurrentGraphicWidth() { + return getTextBorder() + (int) (data.getRows() * size); + } + +} diff --git a/src/main/java/de/neemann/digital/data/Value.java b/src/main/java/de/neemann/digital/data/Value.java index 43b20f3fe..af756e6f3 100644 --- a/src/main/java/de/neemann/digital/data/Value.java +++ b/src/main/java/de/neemann/digital/data/Value.java @@ -68,7 +68,7 @@ public class Value { * * @param val the value */ - public Value(int val) { + public Value(long val) { this.value = val; this.type = Type.NORMAL; } @@ -92,7 +92,7 @@ public class Value { * @param val the string */ public Value(String val) { - val = val.trim(); + val = val.trim().toUpperCase(); switch (val) { case "X": value = 0; diff --git a/src/main/java/de/neemann/digital/data/ValueTable.java b/src/main/java/de/neemann/digital/data/ValueTable.java index 6528c75c6..57af7af12 100644 --- a/src/main/java/de/neemann/digital/data/ValueTable.java +++ b/src/main/java/de/neemann/digital/data/ValueTable.java @@ -1,24 +1,37 @@ package de.neemann.digital.data; +import java.io.*; import java.util.ArrayList; +import java.util.Iterator; /** * Stores values in a table * Created by hneemann on 03.07.17. */ -public class ValueTable { +public class ValueTable implements Iterable { - private final ArrayList names; + private final String[] names; private final ArrayList values; + private final long[] max; /** * Creates a new table. * - * @param names the cignal names + * @param names the signal names */ public ValueTable(ArrayList names) { + this(names.toArray(new String[names.size()])); + } + + /** + * Creates a new table. + * + * @param names the signal names + */ + public ValueTable(String... names) { this.names = names; values = new ArrayList<>(); + max = new long[names.length]; } /** @@ -32,9 +45,13 @@ public class ValueTable { * add values without copying them * * @param row a row to insert, values are not copied! + * @return this for chained calls */ - public void add(Value[] row) { + public ValueTable add(Value[] row) { values.add(row); + for (int i = 0; i < row.length; i++) + if (max[i] < row[i].getValue()) max[i] = row[i].getValue(); + return this; } /** @@ -50,18 +67,69 @@ public class ValueTable { /** * the number of signals + * * @return the column count */ public int getColumns() { - return names.size(); + return names.length; } /** * Returns the column names + * * @param col the column * @return the name */ public String getColumnName(int col) { - return names.get(col); + return names[col]; } + + @Override + public Iterator iterator() { + return values.iterator(); + } + + /** + * Returns the max value stored in the given column + * + * @param col the column + * @return the max value + */ + public long getMax(int col) { + return max[col]; + } + + /** + * Stores the data in csv file + * + * @param file the file + * @throws IOException IOException + */ + public void saveCSV(File file) throws IOException { + saveCSV(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), "UTF-8"))); + } + + /** + * Stores the data in csv file + * + * @param w the writer + * @throws IOException IOException + */ + public void saveCSV(BufferedWriter w) throws IOException { + try { + w.write("\"step\""); + for (String s : names) + w.write(",\"" + s + '"'); + w.write("\n"); + int row = 0; + for (Value[] s : this) { + w.write("\"" + (row++) + "\""); + for (Value value : s) w.write(",\"" + value + "\""); + w.write("\n"); + } + } finally { + w.close(); + } + } + } diff --git a/src/test/java/de/neemann/digital/data/ValueTableTest.java b/src/test/java/de/neemann/digital/data/ValueTableTest.java new file mode 100644 index 000000000..32afcdf1f --- /dev/null +++ b/src/test/java/de/neemann/digital/data/ValueTableTest.java @@ -0,0 +1,47 @@ +package de.neemann.digital.data; + +import junit.framework.TestCase; + +import java.io.BufferedWriter; +import java.io.StringWriter; + + +/** + * Created by hneemann on 03.07.17. + */ +public class ValueTableTest extends TestCase { + private ValueTable t = new ValueTable("A", "B", "C") + .add(new Value[]{new Value(0), new Value(0), new Value(0)}) + .add(new Value[]{new Value(0), new Value(1), new Value("z")}); + + + public void testGeneral() throws Exception { + assertEquals(3, t.getColumns()); + assertEquals(2, t.getRows()); + assertEquals("A", t.getColumnName(0)); + assertEquals("B", t.getColumnName(1)); + assertEquals("C", t.getColumnName(2)); + assertTrue(new Value(0).isEqualTo(t.getValue(0, 0))); + assertTrue(new Value(1).isEqualTo(t.getValue(1, 1))); + assertTrue(new Value("Z").isEqualTo(t.getValue(1, 2))); + } + + public void testCSV() throws Exception { + StringWriter sw = new StringWriter(); + t.saveCSV(new BufferedWriter(sw)); + assertEquals("\"step\",\"A\",\"B\",\"C\"\n" + + "\"0\",\"0\",\"0\",\"0\"\n" + + "\"1\",\"0\",\"1\",\"Z\"\n", sw.toString()); + } + + public void testMax() { + ValueTable t = new ValueTable("A", "B", "C") + .add(new Value[]{new Value(0), new Value(4), new Value(1)}) + .add(new Value[]{new Value(1), new Value(0), new Value(3)}) + .add(new Value[]{new Value(2), new Value(0), new Value(1)}) + .add(new Value[]{new Value(1), new Value(0), new Value(1)}); + assertEquals(2,t.getMax(0)); + assertEquals(4,t.getMax(1)); + assertEquals(3,t.getMax(2)); + } +} \ No newline at end of file