From c591f173868ff7561a3a3a3dbcf86ee0655dcc3a Mon Sep 17 00:00:00 2001 From: Richeeyyy <richard.schmidt@mailbox.tu-dresden.de> Date: Fri, 5 Jul 2019 20:12:08 +0200 Subject: [PATCH] Implement CSV parser, bugfixes TODO --- build.gradle | 6 + parser_bar.csv | 11 + parser_line.csv | 13 + parser_scatter.csv | 14 + .../java/CsvDataImporter/CsvDataImporter.java | 46 --- .../de/tudresden/inf/mci/brailleplot/App.java | 21 +- src/main/java/diagrams/BarChart.java | 12 + src/main/java/diagrams/Diagram.java | 23 ++ src/main/java/diagrams/LinePlot.java | 11 + src/main/java/diagrams/ScatterPlot.java | 11 + src/main/java/parser/Axis.java | 151 ++++++++++ .../java/parser/CategorialPointListList.java | 69 +++++ src/main/java/parser/Constants.java | 46 +++ src/main/java/parser/CoordinateSystem.java | 284 ++++++++++++++++++ src/main/java/parser/CsvDotParser.java | 130 ++++++++ src/main/java/parser/CsvOrientation.java | 34 +++ src/main/java/parser/CsvParseAlgorithm.java | 49 +++ src/main/java/parser/CsvParser.java | 71 +++++ src/main/java/parser/CsvType.java | 52 ++++ .../parser/CsvXAlignedCategoriesParser.java | 134 +++++++++ src/main/java/parser/CsvXAlignedParser.java | 144 +++++++++ src/main/java/parser/MetricAxis.java | 132 ++++++++ src/main/java/parser/NominalAxis.java | 88 ++++++ src/main/java/parser/Point.java | 242 +++++++++++++++ src/main/java/parser/PointListList.java | 245 +++++++++++++++ src/main/java/parser/Range.java | 117 ++++++++ src/main/java/parser/SvgTools.java | 111 +++++++ src/main/java/parser/XType.java | 8 + src/main/java/parser/package-info.java | 4 + 29 files changed, 2232 insertions(+), 47 deletions(-) create mode 100644 parser_bar.csv create mode 100644 parser_line.csv create mode 100644 parser_scatter.csv delete mode 100644 src/main/java/CsvDataImporter/CsvDataImporter.java create mode 100644 src/main/java/diagrams/BarChart.java create mode 100644 src/main/java/diagrams/Diagram.java create mode 100644 src/main/java/diagrams/LinePlot.java create mode 100644 src/main/java/diagrams/ScatterPlot.java create mode 100644 src/main/java/parser/Axis.java create mode 100644 src/main/java/parser/CategorialPointListList.java create mode 100644 src/main/java/parser/Constants.java create mode 100644 src/main/java/parser/CoordinateSystem.java create mode 100644 src/main/java/parser/CsvDotParser.java create mode 100644 src/main/java/parser/CsvOrientation.java create mode 100644 src/main/java/parser/CsvParseAlgorithm.java create mode 100644 src/main/java/parser/CsvParser.java create mode 100644 src/main/java/parser/CsvType.java create mode 100644 src/main/java/parser/CsvXAlignedCategoriesParser.java create mode 100644 src/main/java/parser/CsvXAlignedParser.java create mode 100644 src/main/java/parser/MetricAxis.java create mode 100644 src/main/java/parser/NominalAxis.java create mode 100644 src/main/java/parser/Point.java create mode 100644 src/main/java/parser/PointListList.java create mode 100644 src/main/java/parser/Range.java create mode 100644 src/main/java/parser/SvgTools.java create mode 100644 src/main/java/parser/XType.java create mode 100644 src/main/java/parser/package-info.java diff --git a/build.gradle b/build.gradle index d498333b..4fba8316 100644 --- a/build.gradle +++ b/build.gradle @@ -30,6 +30,12 @@ dependencies { // Use JUnit test framework testImplementation('org.junit.jupiter:junit-jupiter:5.4.2') compile "com.opencsv:opencsv:4.0" + // https://mvnrepository.com/artifact/com.beust/jcommander + compile group: 'com.beust', name: 'jcommander', version: '1.64' + // https://mvnrepository.com/artifact/org.slf4j/slf4j-api + compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.24' + + } test { diff --git a/parser_bar.csv b/parser_bar.csv new file mode 100644 index 00000000..17d972b6 --- /dev/null +++ b/parser_bar.csv @@ -0,0 +1,11 @@ +{\rtf1\ansi\ansicpg1252\cocoartf1671\cocoasubrtf500 +{\fonttbl\f0\fswiss\fcharset0 Helvetica;} +{\colortbl;\red255\green255\blue255;} +{\*\expandedcolortbl;;} +\paperw11900\paperh16840\margl1440\margr1440\vieww10800\viewh8400\viewkind0 +\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural\partightenfactor0 + +\f0\fs24 \cf0 , Reihe a , Reihe b , Reihe c\ +Kat.1 ,3 ,"2 ,5" ,1\ +Kat.2 ,4 ,3 ,2\ +Kat.3 ,"4 ,5" ,3 ,1} \ No newline at end of file diff --git a/parser_line.csv b/parser_line.csv new file mode 100644 index 00000000..b13fd356 --- /dev/null +++ b/parser_line.csv @@ -0,0 +1,13 @@ +{\rtf1\ansi\ansicpg1252\cocoartf1671\cocoasubrtf500 +{\fonttbl\f0\fswiss\fcharset0 Helvetica;} +{\colortbl;\red255\green255\blue255;} +{\*\expandedcolortbl;;} +\paperw11900\paperh16840\margl1440\margr1440\vieww10800\viewh8400\viewkind0 +\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural\partightenfactor0 + +\f0\fs24 \cf0 Linie1, ,1,7,9,2,10\ + ,1,2,5,4,10\ +Linie2, ,0,2,7,9,1,4\ + ,3,9,4,2,5,7\ +\ +} \ No newline at end of file diff --git a/parser_scatter.csv b/parser_scatter.csv new file mode 100644 index 00000000..bd69a608 --- /dev/null +++ b/parser_scatter.csv @@ -0,0 +1,14 @@ +{\rtf1\ansi\ansicpg1252\cocoartf1671\cocoasubrtf500 +{\fonttbl\f0\fswiss\fcharset0 Helvetica;} +{\colortbl;\red255\green255\blue255;} +{\*\expandedcolortbl;;} +\paperw11900\paperh16840\margl1440\margr1440\vieww10800\viewh8400\viewkind0 +\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural\partightenfactor0 + +\f0\fs24 \cf0 Erste Gruppe,"2,14577671155962","3,83957088149638","2,74381418592686","4,44040259535766","3,05835335915677","3,51347910532925","4,50752129340682","3,10684847076423"\ +,"1,86189289220467","0,845650622246947","1,62491581811196","0,716873892169058","1,07960644510134","1,25420442725184","0,868213570630901","0,3131242131342"\ +Zweite Gruppe,"2,13687514878477","1,0900468273911","0,202664216808808","-0,117994978447507","1,91197730783847","0,472235461102831","1,01858439429409","1,54912208686837"\ +,"0,983807817453276","2,34845526278731","0,785524371519466","0,220854402806683","1,77117365732932","3,01226487117459","-0,327141232914824","-0,210388758733043"\ +Dritte Gruppe,"4,11271848986168","4,99244908180646","5,98316851481515","4,95765061866595","4,00636404848764","5,49799175180235","4,34309770180236","5,36675690932105"\ +,"5,09261289722065","4,92838187744164","4,47540951345638","3,45104975190378","5,64998407245105","4,88914941630864","5,92876353824873","6,67452703834106"\ +} \ No newline at end of file diff --git a/src/main/java/CsvDataImporter/CsvDataImporter.java b/src/main/java/CsvDataImporter/CsvDataImporter.java deleted file mode 100644 index da1f71c1..00000000 --- a/src/main/java/CsvDataImporter/CsvDataImporter.java +++ /dev/null @@ -1,46 +0,0 @@ -package CsvDataImporter; - -import com.opencsv.CSVReader; - -import java.io.IOException; -import java.io.Reader; -import java.util.ArrayList; -import java.util.Arrays; -import java.io.FileReader; - - -/** - * parser for compliant CSV files, creates specified representation of diagram information - * @author Richard Schmidt - */ -public class CsvDataImporter { - - private ArrayList<ArrayList<String>> csvData; - - /** - * copied from svgplot - * Initiates the parser. The parser reads from the specified {@code reader} - * and populates {@link #csvData} with lines of data. - * - * @param reader - * a reader, like {@link FileReader}, with CSV path - * @throws IOException - * if the {@link CSVReader} has problems parsing - */ - public CsvDataImporter(Reader reader) throws IOException { - CSVReader csvReader = new CSVReader(reader); - - csvData = new ArrayList<>(); - - String[] nextLine; - while ((nextLine = csvReader.readNext()) != null) { - csvData.add(new ArrayList<String>(Arrays.asList(nextLine))); - } - - csvReader.close(); - } - - // TODO - // parse csvData, return a List of points; - // svgplot parser may be reused -} diff --git a/src/main/java/de/tudresden/inf/mci/brailleplot/App.java b/src/main/java/de/tudresden/inf/mci/brailleplot/App.java index 7c59be79..5890abbf 100644 --- a/src/main/java/de/tudresden/inf/mci/brailleplot/App.java +++ b/src/main/java/de/tudresden/inf/mci/brailleplot/App.java @@ -1,7 +1,16 @@ package de.tudresden.inf.mci.brailleplot; +import parser.CsvOrientation; +import parser.CsvParser; +import parser.CsvType; +import parser.PointListList; +import parser.Point; + +import java.io.FileReader; +import java.io.IOException; import java.util.concurrent.ConcurrentLinkedDeque; + /** * Main class. * Set up the application and run it. @@ -14,8 +23,18 @@ public final class App { * Instantiate application and execute it. * @param args Command line parameters. */ - public static void main(final String[] args) { + public static void main(final String[] args) throws IOException { App app = App.getInstance(); + String csvPath = "parser_line.csv"; + CsvType csvType = CsvType.X_ALIGNED; + CsvOrientation csvOrientation = CsvOrientation.HORIZONTAL; + CsvParser parser = new CsvParser(new FileReader(csvPath), ',', '"'); + PointListList points = parser.parse(csvType, csvOrientation); + for (PointListList.PointList l : points) { + for (Point p : l) { + System.out.println(p.getX() + p.getY() + p.getName()); + } + } System.exit(app.run(args)); } diff --git a/src/main/java/diagrams/BarChart.java b/src/main/java/diagrams/BarChart.java new file mode 100644 index 00000000..33ddc3da --- /dev/null +++ b/src/main/java/diagrams/BarChart.java @@ -0,0 +1,12 @@ +package diagrams; + +import parser.PointListList; + +public class BarChart extends Diagram { + + public BarChart(PointListList p) { + this.p = p; + p.updateMinMax(); + } + +} diff --git a/src/main/java/diagrams/Diagram.java b/src/main/java/diagrams/Diagram.java new file mode 100644 index 00000000..0cc57b27 --- /dev/null +++ b/src/main/java/diagrams/Diagram.java @@ -0,0 +1,23 @@ +package diagrams; + +import parser.PointListList; + +public class Diagram { + protected PointListList p; + + public double getMinX() { + return p.getMinX(); + } + + public double getMaxX() { + return p.getMaxX(); + } + + public double getMinY() { + return p.getMinY(); + } + + public double getMaxY() { + return p.getMaxY(); + } +} diff --git a/src/main/java/diagrams/LinePlot.java b/src/main/java/diagrams/LinePlot.java new file mode 100644 index 00000000..b312694f --- /dev/null +++ b/src/main/java/diagrams/LinePlot.java @@ -0,0 +1,11 @@ +package diagrams; + +import parser.PointListList; + +public class LinePlot extends Diagram { + + public LinePlot(PointListList p) { + this.p = p; + p.updateMinMax(); + } +} diff --git a/src/main/java/diagrams/ScatterPlot.java b/src/main/java/diagrams/ScatterPlot.java new file mode 100644 index 00000000..ddce1d1c --- /dev/null +++ b/src/main/java/diagrams/ScatterPlot.java @@ -0,0 +1,11 @@ +package diagrams; + +import parser.PointListList; + +public class ScatterPlot extends Diagram { + + public ScatterPlot(PointListList p) { + this.p = p; + p.updateMinMax(); + } +} diff --git a/src/main/java/parser/Axis.java b/src/main/java/parser/Axis.java new file mode 100644 index 00000000..969cd1d0 --- /dev/null +++ b/src/main/java/parser/Axis.java @@ -0,0 +1,151 @@ +package parser; + +import java.text.DecimalFormat; +import java.util.NoSuchElementException; + +public abstract class Axis { + + // The following offsets can and shall be overwritten by child classes + /** X offset of horizontal axis labels */ + public final double labelOffsetHorizontalX; + /** Y offset of horizontal axis labels */ + public final double labelOffsetHorizontalY; + /** X offset of vertical axis labels */ + public final double labelOffsetVerticalX; + /** Y offset of vertical axis labels */ + public final double labelOffsetVerticalY; + + protected double ticInterval; + protected Range ticRange; + protected double gridInterval; + protected Range range; + protected double labelInterval; + protected Range labelRange; + protected final DecimalFormat decimalFormat = (DecimalFormat) DecimalFormat.getInstance(Constants.locale); + + protected String unit; + protected String title; + + /** How much the point position shall be shifted - used for nominal axes.*/ + protected final double pointOffset; + + /** + * Constructor setting the label and point offsets. + * @param labelOffsetHorizontalX + * @param labelOffsetHorizontalY + * @param labelOffsetVerticalX + * @param labelOffsetVerticalY + * @param pointOffset + */ + public Axis(double labelOffsetHorizontalX, double labelOffsetHorizontalY, double labelOffsetVerticalX, + double labelOffsetVerticalY, double pointOffset, String title, String unit) { + this.labelOffsetHorizontalX = labelOffsetHorizontalX; + this.labelOffsetHorizontalY = labelOffsetHorizontalY; + this.labelOffsetVerticalX = labelOffsetVerticalX; + this.labelOffsetVerticalY = labelOffsetVerticalY; + this.pointOffset = pointOffset; + this.title = title; + this.unit = unit; + } + + public AxisIterator ticLines() { + return new AxisIterator(ticRange, ticInterval); + } + + public AxisIterator gridLines() { + return new AxisIterator(range, gridInterval); + } + + public AxisIterator labelPositions() { + return new AxisIterator(labelRange, labelInterval); + } + + public abstract String formatForAxisLabel(double value); + + public abstract String formatForAxisAudioLabel(double value); + + public abstract String formatForSymbolAudioLabel(double value); + + public static class AxisIterator implements java.util.Iterator<Double>, Iterable<Double> { + + private Range range; + private double interval; + private double current; + + protected AxisIterator(Range range, double interval) { + this(range, interval, 0); + } + + protected AxisIterator(Range range, double interval, double offset) { + this.range = range; + this.interval = interval; + current = range.getFrom() + offset; + } + + /** + * Get the next axis value. There used to be a check for skipping the + * zero value, but now it is not skipped anymore, because there are axis + * configurations where the zero tics and gridlines are needed. + */ + @Override + public boolean hasNext() { + return current <= range.getTo(); + } + + @Override + public Double next() { + if (!hasNext()) + throw new NoSuchElementException(); + double nextCurrent = this.current; + this.current += interval; + return nextCurrent; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + @Override + public AxisIterator iterator() { + return this; + } + + } + + public double getTicInterval() { + return ticInterval; + } + + public Range getTicRange() { + return ticRange; + } + + public double getGridInterval() { + return gridInterval; + } + + public Range getRange() { + return range; + } + + public double getLabelInterval() { + return labelInterval; + } + + public Range getLabelRange() { + return labelRange; + } + + public String getUnit() { + return unit; + } + + public String getTitle() { + return title; + } + + public double getPointOffset() { + return pointOffset; + } +} \ No newline at end of file diff --git a/src/main/java/parser/CategorialPointListList.java b/src/main/java/parser/CategorialPointListList.java new file mode 100644 index 00000000..01d428e9 --- /dev/null +++ b/src/main/java/parser/CategorialPointListList.java @@ -0,0 +1,69 @@ +package parser; + +import java.util.ArrayList; +import java.util.List; + +/** + * A {@link PointListList} storing a list of category names. The x values of the + * added points should correspond to the index of their category in the category + * list. + */ +public class CategorialPointListList extends PointListList { + + private static final long serialVersionUID = -1291194891140659342L; + + private List<String> categoryNames; + private double maxYSum = Double.NEGATIVE_INFINITY; + + public XType getXType() { + return XType.CATEGORIAL; + } + + public CategorialPointListList() { + categoryNames = new ArrayList<>(); + } + + public void addCategory(String name) { + categoryNames.add(name); + } + + public String getCategoryName(int index) { + try { + return categoryNames.get(index); + } catch (Exception e) { + return ""; + } + } + + public int getCategoryCount() { + return categoryNames.size(); + } + + public void setCategoryNames(List<String> categoryNames) { + this.categoryNames = categoryNames; + } + + public List<String> getCategoryNames() { + return categoryNames; + } + + public double getCategorySum(int index) { + double sum = 0; + for(PointList pointList : this) { + if(pointList.size() > index) + sum += pointList.get(index).getY(); + } + return sum; + } + + @Override + public void updateMinMax() { + super.updateMinMax(); + for(int i = 0; i < categoryNames.size(); i++) + maxYSum = Math.max(maxYSum, getCategorySum(i)); + } + + public double getMaxYSum() { + return maxYSum; + } +} diff --git a/src/main/java/parser/Constants.java b/src/main/java/parser/Constants.java new file mode 100644 index 00000000..2b1ab988 --- /dev/null +++ b/src/main/java/parser/Constants.java @@ -0,0 +1,46 @@ +package parser; + +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.text.NumberFormat; +import java.util.*; + +public class Constants { + public static final String PROPERTIES_FILENAME = "svgplot.properties"; + public static final Locale locale = new Locale("de"); + public static final NumberFormat numberFormat = NumberFormat.getInstance(locale); + public static final ResourceBundle bundle = ResourceBundle.getBundle("Bundle"); + public static final double STROKE_WIDTH = 0.5; + public static final List<Integer> MARGIN = Collections.unmodifiableList(Arrays.asList(15, 10, 15, 10)); + /** List of letters for function naming */ + public static final List<String> FN_LIST = Collections + .unmodifiableList(Arrays.asList("f", "g", "h", "i", "j", "k", "l", "m", "o", "p", "q", "r")); + /** List of letters for point naming */ + public static final List<String> PN_LIST = Collections.unmodifiableList( + Arrays.asList("A", "B", "C", "D", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "T")); + public static final String SPACER_CSS_CLASS = "poi_symbol_bg"; + public static final DecimalFormat decimalFormat = getSvgDecimalFormat(); + /** The minimal distance of grid lines in mm */ + public static final int MIN_GRID_DISTANCE = 10; + public static final int MIN_LINE_LENGTH = 30; + public static final Point titlePosition = new Point(Constants.MARGIN.get(3), Constants.MARGIN.get(0) + 10); + public static final double CHAR_WIDTH = 6.5; + public static final double TEXTURE_BORDER_DISTANCE = 2; + public static final double TEXTURE_MIN_HEIGHT = 7; + public static final double TEXTURE_MIN_WIDTH = 20; + public static final double HALF_BAR_DISTANCE = 3; + + // Used for double comparisons + public static final double EPSILON = 1E-10; + + private Constants() { + } + + private static final DecimalFormat getSvgDecimalFormat() { + DecimalFormat decimalFormat = new DecimalFormat("0.###"); + DecimalFormatSymbols dfs = new DecimalFormatSymbols(); + dfs.setDecimalSeparator('.'); + decimalFormat.setDecimalFormatSymbols(dfs); + return decimalFormat; + } +} diff --git a/src/main/java/parser/CoordinateSystem.java b/src/main/java/parser/CoordinateSystem.java new file mode 100644 index 00000000..ed98f37a --- /dev/null +++ b/src/main/java/parser/CoordinateSystem.java @@ -0,0 +1,284 @@ +package parser; + +import java.util.List; + + +/** + * + * @author Gregor Harlan, Jens Bornschein Idea and supervising by Jens + * Bornschein jens.bornschein@tu-dresden.de Copyright by Technische + * Universität Dresden / MCI 2014 + * + */ +public class CoordinateSystem { + + public final Axis xAxis; + public final Axis yAxis; + + public final boolean pi; + + /** Origin of the real coordinate system (left upper corner) */ + private final Point origin; + + /** Size of the drawing area excluding margins */ + private final Point size; + + /** + * Constructor for a coordinate system with a nominal x axis. TODO replace + * by a factory in order to avoid code duplication + * + * @param xCategories + * @param yRange + * @param size + * @param diagramContentMargin + */ + public CoordinateSystem(List<String> xCategories, Range yRange, Point size, List<Integer> diagramContentMargin, String xUnit, String yUnit) { + origin = new Point(diagramContentMargin.get(3), diagramContentMargin.get(0)); + + this.size = new Point(size); + this.size.setX(this.size.getX() - (diagramContentMargin.get(1) + diagramContentMargin.get(3))); + this.size.setY(this.size.getY() - (diagramContentMargin.get(0) + diagramContentMargin.get(2))); + // this.size.x = Math.min(this.size.x, this.size.y); + // this.size.y = this.size.x; + + xAxis = new NominalAxis(xCategories, this.size.getX(), xUnit); + yAxis = new MetricAxis(yRange, this.size.getY(), yRange.getName(), yUnit); + + this.pi = false; + } + + public CoordinateSystem(Range xRange, Range yRange, Point size, List<Integer> margin, String xUnit, String yUnit) { + this(xRange, yRange, size, margin, false, xUnit, yUnit); + } + + /** + * Constructor for a coordinate system with metric axes. TODO replace by a + * factory in order to avoid code duplication + * + * @param xRange + * @param yRange + * @param size + * @param diagramContentMargin + * @param pi + */ + public CoordinateSystem(Range xRange, Range yRange, Point size, List<Integer> diagramContentMargin, boolean pi, String xUnit, String yUnit) { + origin = new Point(diagramContentMargin.get(3), diagramContentMargin.get(0)); + + this.size = new Point(size); + this.size.setX(this.size.getX() - (diagramContentMargin.get(1) + diagramContentMargin.get(3))); + this.size.setY(this.size.getY() - (diagramContentMargin.get(0) + diagramContentMargin.get(2))); + // this.size.x = Math.min(this.size.x, this.size.y); + // this.size.y = this.size.x; + + xAxis = new MetricAxis(xRange, this.size.getX(), xRange.getName(), xUnit); + yAxis = new MetricAxis(yRange, this.size.getY(), yRange.getName(), yUnit); + + this.pi = pi; + } + + /** + * Converts a point from virtual to real coordinates. + * + * @param x + * | virtual x coordinate + * @param y + * | virtual y coordinate + * @return real point + */ + public Point convert(double x, double y) { + double newX = origin.getX() + + (x - xAxis.range.getFrom()) * size.getX() / (xAxis.range.getTo() - xAxis.range.getFrom()); + double newY = origin.getY() + size.getY() + - ((y - yAxis.range.getFrom()) * size.getY() / (yAxis.range.getTo() - yAxis.range.getFrom())); + return new Point(newX, newY); + } + + /** + * Converts a point from virtual to real coordinates using an offset from the axes. + * + * @param x + * | virtual x coordinate + * @param y + * | virtual y coordinate + * @return real point + */ + public Point convertWithOffset(double x, double y) { + return convert(x + xAxis.getPointOffset(), y + yAxis.getPointOffset()); + } + + /** + * Converts a point from virtual to real coordinates. + * + * @param point + * | virtual coordinates + * @return real point + */ + public Point convert(Point point) { + return convert(point.getX(), point.getY()); + } + + /** + * Converts a point from virtual to real coordinates using an offset from the axes. + * + * @param point + * | virtual coordinates + * @return real point + */ + public Point convertWithOffset(Point point) { + return convertWithOffset(point.getX(), point.getY()); + } + + /** + * Converts a point from virtual coordinates and translates it in real + * space. + * + * @param x + * | virtual x coordinate + * @param y + * | virtual y coordinate + * @param dx + * | real x transformation + * @param dy + * | real y transformation + * @return real point + */ + public Point convert(double x, double y, double dx, double dy) { + Point real = convert(x, y); + real.translate(dx, dy); + return real; + } + + /** + * Converts a point from virtual coordinates and translates it in real + * space. + * + * @param point + * | virtual coordinates + * @param dx + * | real x transformation + * @param dy + * | real y transformation + * @return real point + */ + public Point convert(Point point, double dx, double dy) { + return convert(point.getX(), point.getY(), dx, dy); + } + + /** + * Converts a distance on the x axis from virtual to real. + * + * @param distance + * | virtual distance + * @return real distance + */ + public double convertXDistance(double distance) { + return distance * size.getX() / (xAxis.range.getTo() - xAxis.range.getFrom()); + } + + /** + * Converts a distance on the y axis from virtual to real. + * + * @param distance + * | virtual distance + * @return real distance + */ + public double convertYDistance(double distance) { + return distance * size.getY() / (yAxis.range.getTo() - yAxis.range.getFrom()); + } + + /** + * Converts two virtual points and calculates their real distance + * + * @param point1 + * @param point2 + * @return real distance + */ + public double convertDistance(Point point1, Point point2) { + return convert(point1).distance(convert(point2)); + } + + /** + * Formats the x value of a point with respect to if Pi is set in the + * coordinate system. + * + * @param x + * x-value + * @return formated string for the point + */ + public String formatX(double x) { + String str = xAxis.formatForAxisLabel(x); + if (pi && !"0".equals(str)) { + str += " pi"; + } + return str; + } + + /** + * Formats the x value of a point with respect to if Pi is set in the + * coordinate system, for axis audio labels. + * + * @param x + * x-value + * @return formated string for the point + */ + public String formatXForAxisSpeech(double x) { + String str = xAxis.formatForAxisAudioLabel(x); + if (pi && !"0".equals(str)) { + str += " pi"; + } + return str; + } + + /** + * Formats the x value of a point with respect to if Pi is set in the + * coordinate system, for symbol audio labels. + * + * @param x + * x-value + * @return formated string for the point + */ + public String formatXForSymbolSpeech(double x) { + String str = xAxis.formatForSymbolAudioLabel(x); + if (pi && !"0".equals(str)) { + str += " pi"; + } + return str; + } + + /** + * Formats the y value of a point. + * + * @param y + * y-value + * @return formated string for the point + */ + public String formatY(double y) { + return yAxis.formatForAxisLabel(y); + } + + /** + * Formats a Point that it is optimized for speech output. E.g. (x / y) + * + * @param point + * The point that should be transformed into a textual + * representation + * @return formated string for the point with '/' as delimiter + */ + public String formatForSpeech(Point point) { + return ((point.getName() != null && !point.getName().isEmpty()) ? point.getName() + " " : "") + + formatXForSymbolSpeech(point.getX()) + " / " + formatY(point.getY()); + } + + /** + * Formats a Point that it is optimized for speech output for an axis audio label. + * + * @param point + * The point that should be transformed into a textual + * representation + * @return formated string for the point with '/' as delimiter + */ + public String formatForAxisSpeech(Point point) { + return ((point.getName() != null && !point.getName().isEmpty()) ? point.getName() + " " : "") + + formatXForAxisSpeech(point.getX()) + " / " + formatY(point.getY()); + } +} diff --git a/src/main/java/parser/CsvDotParser.java b/src/main/java/parser/CsvDotParser.java new file mode 100644 index 00000000..5294a3c8 --- /dev/null +++ b/src/main/java/parser/CsvDotParser.java @@ -0,0 +1,130 @@ +package parser; + +import parser.PointListList.PointList; + +import java.text.ParseException; +import java.util.Iterator; +import java.util.List; + +public class CsvDotParser extends CsvParseAlgorithm { + + /** + * Parses scattered point data in horizontal data sets, alternating x and y. The + * first column contains the row name in the x row. + * + * @return the parsed data + */ + public PointListList parseAsHorizontalDataSets(List<? extends List<String>> csvData) { + int row = 0; + + PointListList pointListList = new PointListList(); + + // Continue as long as there are at least two further rows left + while (csvData.size() >= row + 2) { + PointList rowPoints = new PointList(); + + Iterator<String> xRowIterator = csvData.get(row).iterator(); + Iterator<String> yRowIterator = csvData.get(row + 1).iterator(); + + row += 2; + + // Get the row name + if (xRowIterator.hasNext() && yRowIterator.hasNext()) { + rowPoints.setName(xRowIterator.next()); + yRowIterator.next(); + } else { + continue; + } + + // Get the row values + while (xRowIterator.hasNext() && yRowIterator.hasNext()) { + Number xValue; + Number yValue; + try { + xValue = Constants.numberFormat.parse(xRowIterator.next()); + yValue = Constants.numberFormat.parse(yRowIterator.next()); + } catch (ParseException e) { + continue; + } + Point newPoint = new Point(xValue.doubleValue(), yValue.doubleValue()); + rowPoints.insertSorted(newPoint); + } + + // If there were no points found, do not add the row to the list + if (!rowPoints.isEmpty()) + pointListList.add(rowPoints); + } + + return pointListList; + } + + /** + * Parses scattered point data in vertical data sets, alternating x and y. The + * first row contains the column name in the x column. + * + * @return the parsed data + */ + @Override + public PointListList parseAsVerticalDataSets(List<? extends List<String>> csvData) { + int row = 0; + + PointListList pointListList = new PointListList(); + + if (csvData.isEmpty()) + return pointListList; + + // Iterate over the first row in order to get the headers + int col = 0; + for (String header : csvData.get(0)) { + if (col % 2 == 0) { + PointList pointList = new PointList(); + pointList.setName(header); + pointListList.add(pointList); + } + col++; + } + + row++; + + // Continue as long as there is at least one further rows left + while (csvData.size() >= row + 1) { + List<String> fields = csvData.get(row); + Iterator<String> fieldIterator = fields.iterator(); + + col = -1; + + while (fieldIterator.hasNext()) { + String xRaw = fieldIterator.next(); + String yRaw; + + col++; + + if (!fieldIterator.hasNext()) + break; + + yRaw = fieldIterator.next(); + + Number xValue; + Number yValue; + + try { + xValue = Constants.numberFormat.parse(xRaw); + yValue = Constants.numberFormat.parse(yRaw); + } catch (ParseException e) { + col++; + continue; + } + + Point point = new Point(xValue.doubleValue(), yValue.doubleValue()); + + addPointToPointListList(pointListList, col / 2, point); + + col++; + } + + row++; + } + + return pointListList; + } +} diff --git a/src/main/java/parser/CsvOrientation.java b/src/main/java/parser/CsvOrientation.java new file mode 100644 index 00000000..45bc2dcd --- /dev/null +++ b/src/main/java/parser/CsvOrientation.java @@ -0,0 +1,34 @@ +package parser; + +import com.beust.jcommander.IStringConverter; + +public enum CsvOrientation { + + HORIZONTAL, VERTICAL; + + public static CsvOrientation fromString(String code) { + if(code.equals("vertical") || code.equals("v")) + return CsvOrientation.VERTICAL; + else + return CsvOrientation.HORIZONTAL; + } + + @Override + public String toString() { + return super.toString().toLowerCase(); + } + + public static class CsvOrientationConverter implements IStringConverter<CsvOrientation> { + + public CsvOrientationConverter() { + super(); + } + + @Override + public CsvOrientation convert(String value) { + CsvOrientation convertedValue = CsvOrientation.fromString(value); + return convertedValue; + } + + } +} diff --git a/src/main/java/parser/CsvParseAlgorithm.java b/src/main/java/parser/CsvParseAlgorithm.java new file mode 100644 index 00000000..878738e7 --- /dev/null +++ b/src/main/java/parser/CsvParseAlgorithm.java @@ -0,0 +1,49 @@ +package parser; + +import parser.PointListList.PointList; + +import java.util.List; + +/** + * An algorithm for parsing CSV data. Contains implementations for two + * orientations of the data in the file. + */ +public abstract class CsvParseAlgorithm { + /** + * If the data sets are oriented horizontally, i.e. in rows, parse the rows + * into {@link PointList PointLists}. + * + * @param csvData + * @return + */ + public abstract PointListList parseAsHorizontalDataSets(List<? extends List<String>> csvData); + + /** + * If the data sets are oriented vertically, i.e. in columns, parse the + * columns into {@link PointList PointLists}. + * + * @param csvData + * @return + */ + public abstract PointListList parseAsVerticalDataSets(List<? extends List<String>> csvData); + + /** + * Adds a {@code point} to a {@link PointList} in a {@link PointListList}, + * specified by {@code listIndex}. Adds more {@link PointList PointLists} if + * needed. + * + * @param pointListList + * the {@link PointListList} to which the point shall be added + * @param listIndex + * the index of the list to which the point shall be added + * @param point + * the point which shall be added + */ + protected void addPointToPointListList(PointListList pointListList, int listIndex, Point point) { + while (pointListList.size() < listIndex) { + pointListList.add(new PointList()); + } + + pointListList.get(listIndex).insertSorted(point); + } +} diff --git a/src/main/java/parser/CsvParser.java b/src/main/java/parser/CsvParser.java new file mode 100644 index 00000000..8cf432b1 --- /dev/null +++ b/src/main/java/parser/CsvParser.java @@ -0,0 +1,71 @@ +package parser; + +import com.opencsv.CSVReader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.FileReader; +import java.io.IOException; +import java.io.Reader; +import java.util.ArrayList; +import java.util.Arrays; + +public class CsvParser { + + static final Logger log = LoggerFactory.getLogger(CsvParser.class); + + private ArrayList<ArrayList<String>> csvData; + + /** + * Initiates the parser. The parser reads from the specified {@code reader} + * and populates {@link #csvData}. + * + * @param reader + * a reader, like {@link FileReader} + * @param separator + * @param quoteChar + * @throws IOException + * if the {@link CSVReader} has problems parsing + */ + public CsvParser(Reader reader, char separator, char quoteChar) throws IOException { + CSVReader csvReader = new CSVReader(reader, separator, quoteChar); + + csvData = new ArrayList<>(); + + String[] nextLine; + while ((nextLine = csvReader.readNext()) != null) { + csvData.add(new ArrayList<String>(Arrays.asList(nextLine))); + } + + csvReader.close(); + } + + public PointListList parse(CsvType csvType, CsvOrientation csvOrientation) { + CsvParseAlgorithm csvParseAlgorithm; + + log.info("Parse die Daten als \"{}\", Orientierung \"{}\"", csvType, csvOrientation); + + switch (csvType) { + case DOTS: + csvParseAlgorithm = new CsvDotParser(); + break; + case X_ALIGNED: + csvParseAlgorithm = new CsvXAlignedParser(); + break; + case X_ALIGNED_CATEGORIES: + csvParseAlgorithm = new CsvXAlignedCategoriesParser(); + break; + default: + return null; + } + + switch (csvOrientation) { + case HORIZONTAL: + return csvParseAlgorithm.parseAsHorizontalDataSets(csvData); + case VERTICAL: + return csvParseAlgorithm.parseAsVerticalDataSets(csvData); + default: + return null; + } + } +} diff --git a/src/main/java/parser/CsvType.java b/src/main/java/parser/CsvType.java new file mode 100644 index 00000000..6288a67f --- /dev/null +++ b/src/main/java/parser/CsvType.java @@ -0,0 +1,52 @@ +package parser; + +import com.beust.jcommander.IStringConverter; + +/** + * Determines what data is represented how by the CSV file. The values are + * structural properties, whereas the {@link XType} held by every value + * determines whether the x values are metric or categorial. + */ +public enum CsvType { + DOTS(XType.METRIC), X_ALIGNED(XType.METRIC), X_ALIGNED_CATEGORIES(XType.CATEGORIAL); + + public final XType xType; + + private CsvType(XType xType) { + this.xType = xType; + } + + public static CsvType fromString(String value) { + switch (value.toLowerCase()) { + case "x_aligned": + case "xa": + return CsvType.X_ALIGNED; + case "x_aligned_categories": + case "xac": + return CsvType.X_ALIGNED_CATEGORIES; + case "dots": + case "d": + default: + return DOTS; + } + } + + @Override + public String toString() { + return super.toString().toLowerCase(); + } + + public static class CsvTypeConverter implements IStringConverter<CsvType> { + + public CsvTypeConverter() { + super(); + } + + @Override + public CsvType convert(String value) { + CsvType convertedValue = CsvType.fromString(value); + return convertedValue; + } + + } +} diff --git a/src/main/java/parser/CsvXAlignedCategoriesParser.java b/src/main/java/parser/CsvXAlignedCategoriesParser.java new file mode 100644 index 00000000..8429acc3 --- /dev/null +++ b/src/main/java/parser/CsvXAlignedCategoriesParser.java @@ -0,0 +1,134 @@ +package parser; + +import parser.PointListList.PointList; + +import java.text.ParseException; +import java.util.Iterator; +import java.util.List; + + +public class CsvXAlignedCategoriesParser extends CsvParseAlgorithm { + + @Override + public PointListList parseAsHorizontalDataSets(List<? extends List<String>> csvData) { + CategorialPointListList pointListList = new CategorialPointListList(); + + Iterator<? extends List<String>> rowIterator = csvData.iterator(); + + if(!rowIterator.hasNext()) + return pointListList; + + Iterator<String> lineIterator = rowIterator.next().iterator(); + + // Move the iterator to the first category name + if(!lineIterator.hasNext()) + return pointListList; + lineIterator.next(); + if(!lineIterator.hasNext()) + return pointListList; + + // Store all categories + while(lineIterator.hasNext()) { + pointListList.addCategory(lineIterator.next()); + } + + // Store each row's data set + while(rowIterator.hasNext()) { + lineIterator = rowIterator.next().iterator(); + + // Create a PointList with the title of the data set + if(!lineIterator.hasNext()) + continue; + PointList pointList = new PointList(); + pointList.setName(lineIterator.next()); + pointListList.add(pointList); + + // Add all the points + int colPosition = 0; + while (lineIterator.hasNext()) { + if(colPosition >= pointListList.getCategoryCount()) + break; + + // Find out the y value + Number yValue; + try { + yValue = Constants.numberFormat.parse(lineIterator.next()); + } catch (ParseException e) { + colPosition++; + continue; + } + + // Add the new point + Point newPoint = new Point(colPosition, yValue.doubleValue()); + pointList.insertSorted(newPoint); + colPosition++; + } + } + + return pointListList; + } + + @Override + public PointListList parseAsVerticalDataSets(List<? extends List<String>> csvData) { + CategorialPointListList pointListList = new CategorialPointListList(); + Iterator<? extends List<String>> rowIterator = csvData.iterator(); + + + if(!rowIterator.hasNext()) + return pointListList; + + Iterator<String> lineIterator = rowIterator.next().iterator(); + + // Move the iterator to the first title + if(!lineIterator.hasNext()) + return pointListList; + + lineIterator.next(); + + if(!lineIterator.hasNext()) + {System.out.print("1"); + return pointListList;} + + // Add a PointList for each title + while(lineIterator.hasNext()) { + PointList pointList = new PointList(); + pointList.setName(lineIterator.next()); + pointListList.add(pointList); + } + + // Add the data + int categoryCounter = 0; + while(rowIterator.hasNext()) { + lineIterator = rowIterator.next().iterator(); + if(!lineIterator.hasNext()) { + categoryCounter++; + continue; + } + + // Find out the category title + String currentCategory = lineIterator.next(); + pointListList.addCategory(currentCategory); + + // Find out the y values and add the points to the respective lists + int currentDataSet = 0; + while(lineIterator.hasNext()) { + Number yValue; + try { + yValue = Constants.numberFormat.parse(lineIterator.next()); + } catch (ParseException e) { + currentDataSet++; + continue; + } + + Point newPoint = new Point(categoryCounter, yValue.doubleValue()); + addPointToPointListList(pointListList, currentDataSet, newPoint); + currentDataSet++; + } + + categoryCounter++; + } + + return pointListList; + } + +} diff --git a/src/main/java/parser/CsvXAlignedParser.java b/src/main/java/parser/CsvXAlignedParser.java new file mode 100644 index 00000000..7ac6c46d --- /dev/null +++ b/src/main/java/parser/CsvXAlignedParser.java @@ -0,0 +1,144 @@ +package parser; + +import parser.PointListList.PointList; + +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + + +public class CsvXAlignedParser extends CsvParseAlgorithm { + + @Override + public PointListList parseAsHorizontalDataSets(List<? extends List<String>> csvData) { + PointListList pointListList = new PointListList(); + List<Number> xValues = new ArrayList<>(); + Iterator<? extends List<String>> rowIterator = csvData.iterator(); + + if(!rowIterator.hasNext()) + return pointListList; + + Iterator<String> lineIterator = rowIterator.next().iterator(); + + // Move the iterator to the x value + if(!lineIterator.hasNext()) + return pointListList; + lineIterator.next(); + if(!lineIterator.hasNext()) + {System.out.print("1"); + return pointListList;} + + // Store all x values, if one is not specified store NaN + while(lineIterator.hasNext()) { + Number xValue; + try { + xValue = Constants.numberFormat.parse(lineIterator.next()); + } catch (ParseException e) { + xValue = Double.NaN; + } + xValues.add(xValue); + } + + // Store each row's data set + while(rowIterator.hasNext()) { + lineIterator = rowIterator.next().iterator(); + + // Create a PointList with the title of the data set + if(!lineIterator.hasNext()) + continue; + PointList pointList = new PointList(); + pointList.setName(lineIterator.next()); + pointListList.add(pointList); + + // Add all the points + int colPosition = 0; + while (lineIterator.hasNext()) { + if(colPosition >= xValues.size()) + break; + Number xValue = xValues.get(colPosition); + if(xValue.equals(Double.NaN)) { + lineIterator.next(); + colPosition++; + continue; + } + + // Find out the y value + Number yValue; + try { + yValue = Constants.numberFormat.parse(lineIterator.next()); + } catch (ParseException e) { + colPosition++; + continue; + } + + // Add the new point + Point newPoint = new Point(xValue.doubleValue(), yValue.doubleValue()); + pointList.insertSorted(newPoint); + colPosition++; + } + } + + return pointListList; + } + + @Override + public PointListList parseAsVerticalDataSets(List<? extends List<String>> csvData) { + PointListList pointListList = new PointListList(); + Iterator<? extends List<String>> rowIterator = csvData.iterator(); + + if(!rowIterator.hasNext()) + return pointListList; + + Iterator<String> lineIterator = rowIterator.next().iterator(); + + // Move the iterator to the first title + if(!lineIterator.hasNext()) + return pointListList; + lineIterator.next(); + if(!lineIterator.hasNext()) + return pointListList; + + // Add a PointList for each title + while(lineIterator.hasNext()) { + PointList pointList = new PointList(); + pointList.setName(lineIterator.next()); + pointListList.add(pointList); + } + + // Add the data + while(rowIterator.hasNext()) { + lineIterator = rowIterator.next().iterator(); + if(!lineIterator.hasNext()) + continue; + + // Find out the x value + Number xValue; + try { + xValue = Constants.numberFormat.parse(lineIterator.next()); + } catch (ParseException e) { + continue; + } + + // Find out the y values and add the points to the respective lists + int currentDataSet = 0; + while(lineIterator.hasNext()) { + Number yValue; + try { + yValue = Constants.numberFormat.parse(lineIterator.next()); + } catch (ParseException e) { + currentDataSet++; + continue; + } + + Point newPoint = new Point(xValue.doubleValue(), yValue.doubleValue()); + addPointToPointListList(pointListList, currentDataSet, newPoint); + currentDataSet++; + } + + } + + return pointListList; + } + +} diff --git a/src/main/java/parser/MetricAxis.java b/src/main/java/parser/MetricAxis.java new file mode 100644 index 00000000..f806a3d1 --- /dev/null +++ b/src/main/java/parser/MetricAxis.java @@ -0,0 +1,132 @@ +package parser; + +import java.util.ArrayList; +import java.util.List; + +/** + * + * @author Gregor Harlan, Jens Bornschein Idea and supervising by Jens + * Bornschein jens.bornschein@tu-dresden.de Copyright by Technische + * Universität Dresden / MCI 2014 + * + */ +public class MetricAxis extends Axis { + public final double atom; + public final int atomCount; + public final List<Double> intervalSteps; + + public MetricAxis(Range axisRange, double size, String title, String unit) { + + // Set the label offsets + super(-5, 20, -10, 5, 0, title, unit); + + boolean finished = false; + double interval = 0; + range = new Range(0, 0); + range.setName(axisRange.getName()); + int dimensionExp; + double dimension; + double factor; + do { + /* + * Calculate how many tics there can maximally be without violating + * the minimal distance of grid lines constraint. + */ + int maxTics = (int) (size / Constants.MIN_GRID_DISTANCE); + // Calculate which interval (virtual) the tics must minimally have. + interval = axisRange.distance() / maxTics; + dimensionExp = 0; + int direction = interval < 1 ? -1 : 1; + while (direction * 0.5 * Math.pow(10, dimensionExp) < direction * interval) { + dimensionExp += direction; + } + if (direction == 1) { + dimensionExp--; + } + dimension = Math.pow(10, dimensionExp); + factor = getFactorForIntervalAndDimension(interval, dimension); + finished = true; + interval = factor * dimension * 2; + range.setFrom(((int) (axisRange.getFrom() / interval)) * interval); + range.setTo(((int) (axisRange.getTo() / interval)) * interval); + if (range.getFrom() > axisRange.getFrom()) { + axisRange.setFrom(range.getFrom() - interval); + finished = false; + } + if (range.getTo() < axisRange.getTo()) { + axisRange.setTo(range.getTo() + interval); + finished = false; + } + } while (!finished); + + gridInterval = interval; + + ticInterval = interval; // TODO set this to 2 * interval if needed, maybe create an option + ticRange = new Range(Math.ceil(range.getFrom() / ticInterval) * ticInterval, + Math.floor(range.getTo() / ticInterval) * ticInterval); + + labelRange = ticRange; + labelInterval = ticInterval * 2; + + decimalFormat.setMaximumFractionDigits(Math.max(0, -dimensionExp + 2)); + + atom = dimension / 100; + atomCount = (int) (range.distance() / atom + 1); + + intervalSteps = new ArrayList<>(); + calculateIntervalSteps(dimension, factor); + } + + @Override + public String formatForAxisLabel(double value) { + String str = decimalFormat.format(value); + return "-0".equals(str) ? "0" : str; + } + + @Override + public String formatForAxisAudioLabel(double value) { + return formatForAxisLabel(value); + } + + @Override + public String formatForSymbolAudioLabel(double value) { + return formatForAxisLabel(value); + } + + /** + * @param dimension + * @param factor + */ + private void calculateIntervalSteps(double dimension, double factor) { + int i = 0; + if (Math.abs(factor - 2.5) < Constants.EPSILON) { + intervalSteps.add(i++, 2.5 * dimension); + intervalSteps.add(i++, dimension); + } else if (Math.abs(factor - 1) < Constants.EPSILON) { + intervalSteps.add(i++, dimension); + } + + intervalSteps.add(i++, 0.5 * dimension); + intervalSteps.add(i++, 0.1 * dimension); + intervalSteps.add(i++, 0.05 * dimension); + intervalSteps.add(i, 0.01 * dimension); + } + + /** + * @param interval + * @param dimension + * @return the calculated factor + */ + private double getFactorForIntervalAndDimension(double interval, double dimension) { + double factor; + if (interval > dimension) { + factor = 2.5; + } else if (interval > 0.5 * dimension) { + factor = 1; + } else { + factor = 0.5; + } + return factor; + } + +} diff --git a/src/main/java/parser/NominalAxis.java b/src/main/java/parser/NominalAxis.java new file mode 100644 index 00000000..2762d5f3 --- /dev/null +++ b/src/main/java/parser/NominalAxis.java @@ -0,0 +1,88 @@ +package parser; + +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * Axis for displaying nominals. + */ +public class NominalAxis extends Axis { + + static final Logger log = LoggerFactory.getLogger(NominalAxis.class); + + /** + * The categories displayed on the Axis in order. + */ + protected List<String> categories; + + /** + * The nominal axis gets constructed so that each tic corresponds to one + * category. The tics are however shown + * + * @param categories + * @param size + * @param unit + */ + public NominalAxis(List<String> categories, double size, String unit) { + // TODO: upon implementation of vertical nominal axes set the values + // correctly + super(Constants.CHAR_WIDTH, 15, -10, -Constants.CHAR_WIDTH, 0.5, null, unit); + + this.categories = categories; + + double categorySize = size / categories.size(); + int maxLabelLength = 0; + for (String label : categories) + if (label.length() > maxLabelLength) + maxLabelLength = label.length(); + if (categorySize < Constants.CHAR_WIDTH * (maxLabelLength + 1)) { + log.warn( + "Der Platz reicht nicht aus, um Achsenbeschriftungen darzustellen. Die längste Beschriftung hat eine Länge von " + + maxLabelLength + " Zeichen."); + } + + // Subdivide the axis into sections for each category + ticInterval = 1; + gridInterval = 1; + labelInterval = 1; + + ticRange = new Range(0, categories.size()); + range = ticRange; + labelRange = new Range(0, categories.size() - 1); + + } + + @Override + public String formatForAxisLabel(double value) { + int categoryNumber = (int) Math.round(value); + if (categories.size() <= categoryNumber || categoryNumber < 0) + return null; + // throw new IllegalArgumentException("The selected category does not + // exist"); + return categories.get(categoryNumber); + } + + @Override + public String formatForAxisAudioLabel(double value) { + int categoryNumber = (int) Math.round(value); + + if (categoryNumber == 0) { + return "|" + categories.get(categoryNumber); + } else if (categoryNumber == categories.size()) { + return categories.get(categoryNumber - 1) + "|"; + } else if (categoryNumber > 0 && categoryNumber < categories.size()) { + return categories.get(categoryNumber - 1) + "|" + categories.get(categoryNumber); + } else { + return null; + } + } + + @Override + public String formatForSymbolAudioLabel(double value) { + return formatForAxisLabel(value); + } + +} diff --git a/src/main/java/parser/Point.java b/src/main/java/parser/Point.java new file mode 100644 index 00000000..8add0af1 --- /dev/null +++ b/src/main/java/parser/Point.java @@ -0,0 +1,242 @@ +package parser; + +import org.w3c.dom.Element; + +import com.beust.jcommander.IStringConverter; + + +/** + * A point in a coordinate system specified by an x and y coordinate. Can also + * have a name and an SVG symbol. Provides helper methods, e.g. for calculating + * the distance between two points. + * + * @author Gregor Harlan Idea and supervising by Jens Bornschein + * jens.bornschein@tu-dresden.de Copyright by Technische Universität + * Dresden / MCI 2014 + * + */ +public class Point implements Comparable<Point> { + + protected double x; + protected double y; + protected String name; + protected Element symbol; + + /** + * Copy constructor + * + * @param otherPoint + * the point to copy + */ + public Point(Point otherPoint) { + this(otherPoint.getX(), otherPoint.getY(), otherPoint.getName(), + otherPoint.getSymbol() != null ? (Element) otherPoint.getSymbol().cloneNode(true) : null); + } + + /** + * Represents a two dimensional Point in the plot + * + * @param x + * | x (horizontal) position of the point + * @param y + * | y (vertical) position of the point + */ + public Point(double x, double y) { + this(x, y, "", null); + } + + /** + * Represents a two dimensional Point in the plot + * + * @param x + * | x (horizontal) position of the point + * @param y + * | y (vertical) position of the point + * @param name + * | the name of the point + */ + public Point(double x, double y, String name) { + this(x, y, name, null); + } + + /** + * Represents a two dimensional Point in the plot + * + * @param x + * | x (horizontal) position of the point + * @param y + * | y (vertical) position of the point + * @param symbol + * | the symbol to use for the point + */ + public Point(double x, double y, Element symbol) { + this(x, y, "", symbol); + } + + /** + * Represents a two dimensional Point in the plot + * + * @param x + * | x (horizontal) position of the point + * @param y + * | y (vertical) position of the point + * @param name + * | the name of the point + * @param symbol + * | the symbol to use for the point + */ + public Point(double x, double y, String name, Element symbol) { + this.setX(x); + this.setY(y); + this.setName(name); + this.setSymbol(symbol); + } + + /** + * Move the point + * + * @param dx + * | movement in x (horizontal) direction + * @param dy + * | movement in y (vertical) direction + */ + public void translate(double dx, double dy) { + setX(getX() + dx); + setY(getY() + dy); + } + + /** + * formats the x value as an svg compatible decimal value. + * + * @return + */ + public String x() { + return SvgTools.format2svg(getX()); + } + + /** + * formats the y value as an svg compatible decimal value. + * + * @return + */ + public String y() { + return SvgTools.format2svg(getY()); + } + + @Override + /** + * formats the x and y values as svg compatible decimal values and combine + * them by a comma. + * + * @return x,y + */ + public String toString() { + return x() + "," + y(); + } + + /** + * computes the two dimensional euclidean distance of two points. + * + * @param other + * | second point + * @return the two dimensional euclidean distance between this and the other + * point. + */ + public double distance(Point other) { + return Math.sqrt(Math.pow(other.getX() - getX(), 2) + Math.pow(other.getY() - getY(), 2)); + } + + public static class Converter implements IStringConverter<Point> { + /** + * Convert a formatted string to a point. The format is: + * {@code [<x>][,<y>]} Omitted values will default to 0. + * + * @param value + * | formatted string + */ + @Override + public Point convert(String value) { + String[] s = value.split(","); + return new Point(s.length > 0 ? Double.parseDouble(s[0]) : 0, s.length > 1 ? Double.parseDouble(s[1]) : 0); + } + } + + /** + * Compares with x priority. Returns -1 if p2 is null. + * + * @param p2 + * | other point + * @return + */ + @Override + public int compareTo(Point p2) { + if (p2 != null) { + if (Math.abs(p2.getX() - getX()) < Constants.EPSILON) { + return getY() < p2.getY() ? -1 : 1; + } else + return getX() < p2.getX() ? -1 : 1; + } + return -1; + } + + /** + * Compare the y values of two points. Returns -1 if p2 is null. + * + * @param p2 + * | other point + * @return + */ + public int compareToY(Point p2) { + if (p2 != null) { + return getY() < p2.getY() ? -1 : 1; + } + return -1; + } + + /** + * Compare the x values of two points. Returns -1 if p2 is null. + * + * @param p2 + * | other point + * @return + */ + public int compareToX(Point p2) { + if (p2 != null) { + return getX() < p2.getX() ? -1 : 1; + } + return -1; + } + + public double getX() { + return x; + } + + public void setX(double x) { + this.x = x; + } + + public double getY() { + return y; + } + + public void setY(double y) { + this.y = y; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Element getSymbol() { + return symbol; + } + + public void setSymbol(Element symbol) { + this.symbol = symbol; + } + +} diff --git a/src/main/java/parser/PointListList.java b/src/main/java/parser/PointListList.java new file mode 100644 index 00000000..aed9db9f --- /dev/null +++ b/src/main/java/parser/PointListList.java @@ -0,0 +1,245 @@ +package parser; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import com.beust.jcommander.IStringConverter; + +/** + * + * @author Gregor Harlan, Jens Bornschein Idea and supervising by Jens + * Bornschein jens.bornschein@tu-dresden.de Copyright by Technische + * Universität Dresden / MCI 2014 + * + */ +public class PointListList extends ArrayList<PointListList.PointList> { + + private static final long serialVersionUID = 6902232865786868851L; + protected Double maxX = Double.NEGATIVE_INFINITY; + protected Double maxY = Double.NEGATIVE_INFINITY; + protected Double minX = Double.POSITIVE_INFINITY; + protected Double minY = Double.POSITIVE_INFINITY; + + public XType getXType() { + return XType.METRIC; + } + + public PointListList() { + this(""); + } + + public PointListList(String pointLists) { + + if (pointLists == null || pointLists.isEmpty()) + return; + + // TODO: load from file + + // pointLists = pointLists.replaceAll("[^\\d.,^\\s+,^\\{^\\}^-]", ""); + String[] lists = pointLists.split("\\}"); + for (String l : lists) { + PointList pl = new PointList(l); + if (!pl.isEmpty()) { + this.add(pl); + } + } + } + + @Override + public boolean add(PointList pl) { + boolean success = super.add(pl); + updateMinMax(); + return success; + } + + public boolean add(List<Point> points) { + PointList pl = new PointList(points); + return add(pl); + } + + public void updateMinMax() { + for(PointList checkPl : this) { + maxX = Math.max(getMaxX(), checkPl.getMaxX()); + maxY = Math.max(getMaxY(), checkPl.getMaxY()); + minX = Math.min(getMinX(), checkPl.getMinX()); + minY = Math.min(getMinY(), checkPl.getMinY()); + } + } + + public double getMaxX() { + return maxX; + } + + public double getMaxY() { + return maxY; + } + + public double getMinX() { + return minX; + } + + public double getMinY() { + return minY; + } + + public boolean hasValidMinMaxValues() { + return maxX > minX && maxY > minY; + } + + public static class Converter implements IStringConverter<PointListList> { + @Override + public PointListList convert(String value) { + return new PointListList(value); + } + } + + /** + * List of Points including max values + * + * @author Jens Bornschein + * + */ + public static class PointList extends ArrayList<Point> { + + private static final long serialVersionUID = -2318768874799315111L; + private Double maxX = Double.NEGATIVE_INFINITY; + private Double maxY = Double.NEGATIVE_INFINITY; + private Double minX = Double.POSITIVE_INFINITY; + private Double minY = Double.POSITIVE_INFINITY; + private String name = ""; + + public PointList(List<Point> points) { + if (points != null && !points.isEmpty()) { + for (Point p : points) { + this.insertSorted(p); + } + } + } + + public PointList(String points) { + if (points == null || points.isEmpty()) + return; + + String[] pl = points.split("::"); + + if (pl != null && pl.length > 0) { + + String pts; + if (pl.length > 1) { + setName(pl[0].trim()); + pts = pl[1].replaceAll("[^\\d.,^\\s+,^-]", ""); + } else { + pts = pl[0].replaceAll("[^\\d.,^\\s+,^-]", ""); + } + String[] s = pts.split("\\s+"); + + for (String string : s) { + if (string != null && !string.isEmpty()) { + Point p = (new Point.Converter()).convert(string); + this.insertSorted(p); + } + } + } + } + + public PointList() { + this(""); + } + + public boolean insertSorted(Point p) { + maxX = Math.max(getMaxX(), p.getX()); + maxY = Math.max(getMaxY(), p.getY()); + minX = Math.min(getMinX(), p.getX()); + minY = Math.min(getMinY(), p.getY()); + boolean returnVal = super.add(p); + + Comparable<Point> cmp = (Comparable<Point>) p; + for (int i = size()-1; i > 0 && cmp.compareTo(get(i-1)) < 0; i--) + Collections.swap(this, i, i-1); + return returnVal; + } + + + @Deprecated + public void add(int index, Point element) { +// throw new UnsupportedOperationException("Only insertions via insertSorted are allowed"); + this.insertSorted(element); + } + + @Deprecated + public boolean add(Point e) { +// throw new UnsupportedOperationException("Only insertions via insertSorted are allowed"); + return this.insertSorted(e); + } + + public double getMaxX() { + return maxX; + } + + public double getMaxY() { + return maxY; + } + + public double getMinX() { + return minX; + } + + public double getMinY() { + return minY; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + /** + * Gets the first maximum of the data set. + * TODO implement multiple maxima with a proper string representation + * @return first maximum point + */ + public Point getFirstMaximum() { + if(this.isEmpty()) + return null; + + Point maxPoint = get(0); + + for(Point p : this) { + if(maxPoint.getY() < p.getY()) + maxPoint = p; + } + + return maxPoint; + } + + /** + * Gets the first minimum of the data set. + * TODO implement multiple minimum with a proper string representation + * @return first minimum point + */ + public Point getFirstMinimum() { + if(this.isEmpty()) + return null; + + Point minPoint = get(0); + + for(Point p : this) { + if(minPoint.getY() > p.getY()) + minPoint = p; + } + + return minPoint; + } + + public class Converter implements IStringConverter<PointList> { + @Override + public PointList convert(String value) { + return new PointList(value.trim()); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/parser/Range.java b/src/main/java/parser/Range.java new file mode 100644 index 00000000..ddd99be3 --- /dev/null +++ b/src/main/java/parser/Range.java @@ -0,0 +1,117 @@ +package parser; + +import com.beust.jcommander.IStringConverter; +/** + * + * @author Gregor Harlan, Jens Bornschein + * Idea and supervising by Jens Bornschein jens.bornschein@tu-dresden.de + * Copyright by Technische Universität Dresden / MCI 2014 + * + */ +public class Range { + /** Start of the range */ + private double from; + /** End of the range */ + private double to; + + private String name; + + /** + * Constructor with name. + * @param from | start of the range + * @param to | end of the range + * @param name + */ + public Range(double from, double to, String name) { + this.from = from; + this.to = to; + this.name = name; + } + + /** + * Constructor without name. + * @param from | start of the range + * @param to | end of the range + */ + public Range(double from, double to) { + this(from, to, ""); + } + + /** + * Calculates the distance covered by the range. + * @return distance + */ + public double distance() { + return to - from; + } + + @Override + public String toString() { + return name + " " + from + ":" + to; + } + + /** + * Converter class for parsing ranges from strings. + */ + public static class Converter implements IStringConverter<Range> { + /** + * Converts a range specified by a string to a {@link Range} instance. + * The syntax is: {@code [["]<name>["]::]<from>:<to>[:<name>]}. + * The second name parameter is preferred. + * The from and to parameters should be parsable as Double. + * + * @param value | correctly formatted range string + */ + @Override + public Range convert(String value) { + String[] parts = value.split("::"); + String[] s; + String name = ""; + + // Extract the name if specified and remove quotations + if(parts.length > 1){ + name = parts[0].replace("\"", "").trim(); + s = parts[1].split(":"); + }else{ + s = parts[0].split(":"); + } + + // There were not enough parameters specified. + if (s.length < 2) + return new Range(-8, 8); + + /* + * If there are two parameters, use the first name string, + * if there are more, use the second one. + */ + return s.length > 2 ? new Range(Double.parseDouble(s[0]), + Double.parseDouble(s[1]), s[2]) : new Range( + Double.parseDouble(s[0]), Double.parseDouble(s[1]), name); + } + + } + + public double getFrom() { + return from; + } + + public void setFrom(double from) { + this.from = from; + } + + public double getTo() { + return to; + } + + public void setTo(double to) { + this.to = to; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/src/main/java/parser/SvgTools.java b/src/main/java/parser/SvgTools.java new file mode 100644 index 00000000..881ef6b3 --- /dev/null +++ b/src/main/java/parser/SvgTools.java @@ -0,0 +1,111 @@ +package parser; + +import java.text.MessageFormat; + +public class SvgTools { + private SvgTools() { + } + + /** + * Format a number for svg usage according to the constant decimalFormat + * + * @param value + * @return + */ + public static String format2svg(double value) { + return Constants.decimalFormat.format(value); + } + + /** + * Formats an additional Name of an object. Checks if the name is set. If + * name is set, the name is packed into brackets and prepend with an + * whitespace + * + * @param name + * | optional name of an object or NULL + * @return empty string or the name of the object packed into brackets and + * prepend with a whitespace e.g. ' (optional name)' + */ + public static String formatName(String name) { + return (name == null || name.isEmpty()) ? "" : " (" + name + ")"; + } + + /** + * Try to translate a key in the localized version defined in the + * PropertyResourceBundle file. + * + * @param key + * | PropertyResourceBundle key + * @param arguments + * | arguments that should fill the placeholder in the returned + * PropertyResourceBundle value + * @return a localized string for the given PropertyResourceBundle key, + * filled with the set arguments + */ + public static String translate(String key, Object... arguments) { + return MessageFormat.format(Constants.bundle.getString(key), arguments); + } + + /** + * Try to translate a key in the localized version defined in the + * PropertyResourceBundle file. This function is optimized for differing + * sentences depending on the amount of results. + * + * @param key + * | PropertyResourceBundle key + * @param arguments + * | arguments that should fill the placeholder in the returned + * PropertyResourceBundle value. The last argument gives the + * count and decide which value will be returned. + * @return a localized string for the given amount depending + * PropertyResourceBundle key, filled with the set arguments + */ + public static String translateN(String key, Object... arguments) { + int last = (int) arguments[arguments.length - 1]; + String suffix = last == 0 ? "_0" : last == 1 ? "_1" : "_n"; + return translate(key + suffix, arguments); + } + + /** + * Formats a Point that it is optimized for textual output and packed into + * the caption with brackets. E.g. E(x | y) + * + * @param cs + * the coordinate system + * @param point + * The point that should be transformed into a textual + * representation + * @param cap + * The caption string without brackets + * @return formated string for the point with '/' as delimiter if now + * caption is set, otherwise packed in the caption with brackets and + * the '|' as delimiter + */ + public static String formatForText(CoordinateSystem cs, Point point, String cap) { + String p = cs.formatX(point.getX()) + " | " + cs.formatY(point.getY()); + String capTrimmed = cap.trim(); + return (capTrimmed != null && !capTrimmed.isEmpty()) ? capTrimmed + "(" + p + ")" : p; + } + + /** + * Try to translate the function index into a continuous literal starting + * with the char 'f'. If the given index is not valid it returns the name as + * a combination of 'f' + the given number. + * + * @param f + * | the index if the function + * @return a literal representation to the given function index e.g. 'f', + * 'g', 'h' or 'f1000'. + */ + public static String getFunctionName(int f) { + if (f < 0 || f > (Constants.FN_LIST.size() - 1)) + return "f" + f; + return Constants.FN_LIST.get(f); + } + + public static String getPointName(int p) { + if (p < 0 || p > (Constants.PN_LIST.size() - 1)) + return "P" + p; + return Constants.PN_LIST.get(p); + } +} diff --git a/src/main/java/parser/XType.java b/src/main/java/parser/XType.java new file mode 100644 index 00000000..a162d519 --- /dev/null +++ b/src/main/java/parser/XType.java @@ -0,0 +1,8 @@ +package parser; + +/** + * Determines whether the x values are metric or categorial. + */ +public enum XType { + METRIC, CATEGORIAL +} \ No newline at end of file diff --git a/src/main/java/parser/package-info.java b/src/main/java/parser/package-info.java new file mode 100644 index 00000000..c0234d3b --- /dev/null +++ b/src/main/java/parser/package-info.java @@ -0,0 +1,4 @@ +/** + * CSV parsing + */ +package parser; \ No newline at end of file -- GitLab