Skip to content
Snippets Groups Projects
Commit 8f9481a1 authored by Georg Graßnick's avatar Georg Graßnick :thinking:
Browse files

Merge branch 'feat/scatterplot_rasterizer-34' into 'master'

Feat/scatterplot rasterizer 34

See merge request !43
parents 113314a3 f93f5e03
No related branches found
No related tags found
1 merge request!43Feat/scatterplot rasterizer 34
...@@ -11,6 +11,7 @@ import de.tudresden.inf.mci.brailleplot.datacontainers.PointListContainer; ...@@ -11,6 +11,7 @@ import de.tudresden.inf.mci.brailleplot.datacontainers.PointListContainer;
import de.tudresden.inf.mci.brailleplot.datacontainers.SimpleCategoricalPointListContainerImpl; import de.tudresden.inf.mci.brailleplot.datacontainers.SimpleCategoricalPointListContainerImpl;
import de.tudresden.inf.mci.brailleplot.diagrams.CategoricalBarChart; import de.tudresden.inf.mci.brailleplot.diagrams.CategoricalBarChart;
import de.tudresden.inf.mci.brailleplot.diagrams.Diagram; import de.tudresden.inf.mci.brailleplot.diagrams.Diagram;
import de.tudresden.inf.mci.brailleplot.diagrams.ScatterPlot;
import de.tudresden.inf.mci.brailleplot.diagrams.LineChart; import de.tudresden.inf.mci.brailleplot.diagrams.LineChart;
import de.tudresden.inf.mci.brailleplot.layout.AbstractCanvas; import de.tudresden.inf.mci.brailleplot.layout.AbstractCanvas;
import de.tudresden.inf.mci.brailleplot.layout.PlotCanvas; import de.tudresden.inf.mci.brailleplot.layout.PlotCanvas;
...@@ -207,7 +208,8 @@ public final class App { ...@@ -207,7 +208,8 @@ public final class App {
switch (settingsReader.getSetting(SettingType.DIAGRAM_TYPE).orElse("")) { switch (settingsReader.getSetting(SettingType.DIAGRAM_TYPE).orElse("")) {
case "ScatterPlot": case "ScatterPlot":
PointListContainer<PointList> scatterPlotContainer = csvParser.parse(CsvType.DOTS, csvOrientation); PointListContainer<PointList> scatterPlotContainer = csvParser.parse(CsvType.DOTS, csvOrientation);
throw new UnsupportedOperationException("Scatter Plots coming soon."); // TODO: integrate scatter plots diagram = new ScatterPlot(scatterPlotContainer);
break;
case "LineChart": case "LineChart":
PointListContainer<PointList> lineChartContainer = csvParser.parse(CsvType.DOTS, csvOrientation); PointListContainer<PointList> lineChartContainer = csvParser.parse(CsvType.DOTS, csvOrientation);
diagram = new LineChart(lineChartContainer); diagram = new LineChart(lineChartContainer);
......
package de.tudresden.inf.mci.brailleplot.diagrams; package de.tudresden.inf.mci.brailleplot.diagrams;
import de.tudresden.inf.mci.brailleplot.datacontainers.Named; import de.tudresden.inf.mci.brailleplot.datacontainers.Named;
import de.tudresden.inf.mci.brailleplot.datacontainers.PointContainer;
import de.tudresden.inf.mci.brailleplot.datacontainers.PointList; import de.tudresden.inf.mci.brailleplot.datacontainers.PointList;
import de.tudresden.inf.mci.brailleplot.datacontainers.PointListContainer; import de.tudresden.inf.mci.brailleplot.datacontainers.PointListContainer;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
...@@ -15,11 +13,9 @@ import java.util.stream.Collectors; ...@@ -15,11 +13,9 @@ import java.util.stream.Collectors;
* @version 2019.09.02 * @version 2019.09.02
*/ */
public class BarChart extends Diagram { public class BarChart extends Diagram {
private PointListContainer<PointList> mData;
public BarChart(final PointListContainer<PointList> data) { public BarChart(final PointListContainer<PointList> data) {
Objects.requireNonNull(data); super(data);
mData = data;
} }
/** /**
...@@ -28,7 +24,7 @@ public class BarChart extends Diagram { ...@@ -28,7 +24,7 @@ public class BarChart extends Diagram {
* @return list with category names as strings * @return list with category names as strings
*/ */
public List<String> getCategoryNames() { public List<String> getCategoryNames() {
return mData.stream() return getDataSet().stream()
.map(Named::getName) .map(Named::getName)
.collect(Collectors.toUnmodifiableList()); .collect(Collectors.toUnmodifiableList());
} }
...@@ -39,7 +35,7 @@ public class BarChart extends Diagram { ...@@ -39,7 +35,7 @@ public class BarChart extends Diagram {
* @return double minimum y-value * @return double minimum y-value
*/ */
public double getMinY() { public double getMinY() {
return mData.getMinY(); return getDataSet().getMinY();
} }
/** /**
...@@ -48,15 +44,8 @@ public class BarChart extends Diagram { ...@@ -48,15 +44,8 @@ public class BarChart extends Diagram {
* @return double maximum y-value * @return double maximum y-value
*/ */
public double getMaxY() { public double getMaxY() {
return mData.getMaxY(); return getDataSet().getMaxY();
} }
/**
* Getter for a list with x-y-Pairs: x is the index (always just counts from 0 up), y is the value.
* @return PointList with the corresponding data set
*/
public PointContainer<PointList> getDataSet() {
return mData;
}
} }
package de.tudresden.inf.mci.brailleplot.diagrams; package de.tudresden.inf.mci.brailleplot.diagrams;
import de.tudresden.inf.mci.brailleplot.datacontainers.PointList;
import de.tudresden.inf.mci.brailleplot.datacontainers.PointListContainer;
import de.tudresden.inf.mci.brailleplot.rendering.Renderable; import de.tudresden.inf.mci.brailleplot.rendering.Renderable;
import java.util.Objects; import java.util.Objects;
...@@ -11,11 +13,24 @@ import java.util.Objects; ...@@ -11,11 +13,24 @@ import java.util.Objects;
* @author Richard Schmidt, Leonard Kupper * @author Richard Schmidt, Leonard Kupper
* @version 2019-09-12 * @version 2019-09-12
*/ */
public class Diagram implements Renderable { public abstract class Diagram implements Renderable {
private String mTitle; private String mTitle;
private String mXAxisName; private String mXAxisName;
private String mYAxisName; private String mYAxisName;
private PointListContainer<PointList> mData;
public Diagram(final PointListContainer<PointList> data) {
mData = Objects.requireNonNull(data);
}
/**
* Returns the underlying data set.
* @return The underlying data set.
*/
public PointListContainer<PointList> getDataSet() {
return mData;
}
public final String getTitle() { public final String getTitle() {
return mTitle; return mTitle;
......
...@@ -3,8 +3,6 @@ package de.tudresden.inf.mci.brailleplot.diagrams; ...@@ -3,8 +3,6 @@ package de.tudresden.inf.mci.brailleplot.diagrams;
import de.tudresden.inf.mci.brailleplot.datacontainers.PointList; import de.tudresden.inf.mci.brailleplot.datacontainers.PointList;
import de.tudresden.inf.mci.brailleplot.datacontainers.PointListContainer; import de.tudresden.inf.mci.brailleplot.datacontainers.PointListContainer;
import java.util.Objects;
/** /**
* Representation of a line chart with basic data functions. Implements Renderable. * Representation of a line chart with basic data functions. Implements Renderable.
* @author Andrey Ruzhanskiy * @author Andrey Ruzhanskiy
...@@ -19,8 +17,7 @@ public class LineChart extends Diagram { ...@@ -19,8 +17,7 @@ public class LineChart extends Diagram {
* @param data The container, which holds the information about the datapoints. * @param data The container, which holds the information about the datapoints.
*/ */
public LineChart(final PointListContainer<PointList> data) { public LineChart(final PointListContainer<PointList> data) {
Objects.requireNonNull(data); super(data);
mData = data;
} }
/** /**
......
package de.tudresden.inf.mci.brailleplot.diagrams; package de.tudresden.inf.mci.brailleplot.diagrams;
import de.tudresden.inf.mci.brailleplot.datacontainers.PointListList; import de.tudresden.inf.mci.brailleplot.datacontainers.PointList;
import de.tudresden.inf.mci.brailleplot.datacontainers.PointListContainer;
/** /**
* Representation for scatter plots. Inherits from Diagram. * Representation for scatter plots. Inherits from Diagram.
* @author Richard Schmidt * @author Richard Schmidt, Georg Graßnick
* @version 2019.08.26
*/ */
public class ScatterPlot extends Diagram { public class ScatterPlot extends Diagram {
public ScatterPlot(final PointListList p) { public ScatterPlot(final PointListContainer<PointList> container) {
this.mP = p; super(container);
p.updateMinMax();
} }
} }
package de.tudresden.inf.mci.brailleplot.rendering; package de.tudresden.inf.mci.brailleplot.rendering;
import de.tudresden.inf.mci.brailleplot.diagrams.ScatterPlot;
import de.tudresden.inf.mci.brailleplot.configparser.Representation; import de.tudresden.inf.mci.brailleplot.configparser.Representation;
import de.tudresden.inf.mci.brailleplot.diagrams.CategoricalBarChart; import de.tudresden.inf.mci.brailleplot.diagrams.CategoricalBarChart;
import de.tudresden.inf.mci.brailleplot.layout.InsufficientRenderingAreaException; import de.tudresden.inf.mci.brailleplot.layout.InsufficientRenderingAreaException;
...@@ -45,11 +46,13 @@ public final class MasterRenderer { ...@@ -45,11 +46,13 @@ public final class MasterRenderer {
Rasterizer<CategoricalBarChart> barChartRasterizer = new BarChartRasterizer(); Rasterizer<CategoricalBarChart> barChartRasterizer = new BarChartRasterizer();
Rasterizer<Image> linearImageMapping = new ImageRasterizer(); Rasterizer<Image> linearImageMapping = new ImageRasterizer();
Rasterizer<ScatterPlot> scatter = new ScatterPlotRasterizer();
Rasterizer<LineChart> lineChart = new LineChartRasterizer(); Rasterizer<LineChart> lineChart = new LineChartRasterizer();
mLogger.trace("Registering default rasterizers"); mLogger.trace("Registering default rasterizers");
renderingBase.registerRasterizer(new FunctionalRasterizer<CategoricalBarChart>(CategoricalBarChart.class, barChartRasterizer)); renderingBase.registerRasterizer(new FunctionalRasterizer<CategoricalBarChart>(CategoricalBarChart.class, barChartRasterizer));
renderingBase.registerRasterizer(new FunctionalRasterizer<Image>(Image.class, linearImageMapping)); renderingBase.registerRasterizer(new FunctionalRasterizer<Image>(Image.class, linearImageMapping));
renderingBase.registerRasterizer(new FunctionalRasterizer<ScatterPlot>(ScatterPlot.class, scatter));
renderingBase.registerRasterizer(new FunctionalRasterizer<LineChart>(LineChart.class, lineChart)); renderingBase.registerRasterizer(new FunctionalRasterizer<LineChart>(LineChart.class, lineChart));
//renderingBase.registerRasterizer(new FunctionalRasterizer<ScatterPlot>(ScatterPlot.class, ScatterPlotRasterizing::fooRasterizing)); //renderingBase.registerRasterizer(new FunctionalRasterizer<ScatterPlot>(ScatterPlot.class, ScatterPlotRasterizing::fooRasterizing));
//... //...
......
package de.tudresden.inf.mci.brailleplot.rendering;
import de.tudresden.inf.mci.brailleplot.datacontainers.PointList;
import de.tudresden.inf.mci.brailleplot.datacontainers.PointListContainer;
import de.tudresden.inf.mci.brailleplot.diagrams.ScatterPlot;
import de.tudresden.inf.mci.brailleplot.layout.InsufficientRenderingAreaException;
import de.tudresden.inf.mci.brailleplot.layout.RasterCanvas;
import de.tudresden.inf.mci.brailleplot.layout.Rectangle;
import de.tudresden.inf.mci.brailleplot.point.Point2DDouble;
import de.tudresden.inf.mci.brailleplot.printabledata.MatrixData;
import de.tudresden.inf.mci.brailleplot.rendering.language.BrailleLanguage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
/**
* A rasterizer for Scatterplots.
*/
public class ScatterPlotRasterizer implements Rasterizer<ScatterPlot> {
private static final int X_AXIS_WIDTH = 2; // Minimum width of the x axis [cells]
private static final int Y_AXIS_WIDTH = 3; // Minimum width of the y axis [cells]
private static final int TOKEN_AXIS_OFFSET = 1; // Offset of actual plotting area to axis, increased to match cell size [dots]
private static final int X_AXIS_STEP_WIDTH = 3; // The distance between two tick marks on the x axis [cells]
private static final int Y_AXIS_STEP_WIDTH = 3; // The distance between two tick marks on the y axis [cells]
private static final int AXIS_TICK_SIZE = 1; // The length of the ticks on the axis [dots]
private static final Locale NUMBER_LOCALE = new Locale("en", "US");
private static final char LEGEND_TICK_START_CHAR = 'a';
private static final Logger LOG = LoggerFactory.getLogger(ScatterPlotRasterizer.class);
public ScatterPlotRasterizer() {
}
@Override
@SuppressWarnings("checkstyle:MethodLength")
public void rasterize(final ScatterPlot scatterPlot, final RasterCanvas canvas) throws InsufficientRenderingAreaException {
Objects.requireNonNull(scatterPlot);
Objects.requireNonNull(canvas);
PointListContainer<PointList> data = scatterPlot.getDataSet();
final int cellWidth = canvas.getCellWidth();
final int cellHeight = canvas.getCellHeight();
final int xAxisStepWidth = cellWidth * X_AXIS_STEP_WIDTH;
final int yAxisStepWidth = cellHeight * Y_AXIS_STEP_WIDTH;
final BrailleLanguage.Language language = BrailleLanguage.Language.DE_BASISSCHRIFT;
final String title = scatterPlot.getTitle();
final String titleToDataSetSeparator = " - ";
final String legendTitle = title;
final String axisExplanationGroupName = "Achsenbeschriftungen";
final String axisColumnGroupTitle = "Achsenwerte";
final String xAxisLegendGroupName = "x-Achse";
final String yAxisLegendGroupName = "y-Achse";
final String xAxisLabel = "x-Achse";
final String xAxisLabelValue = scatterPlot.getXAxisName();
final String yAxisLabel = "y-Achse";
final String yAxisLabelValue = scatterPlot.getYAxisName();
Rectangle completeArea = canvas.getCellRectangle();
completeArea = completeArea.scaledBy(cellWidth, cellHeight);
Rectangle printableArea = new Rectangle(completeArea);
// --------------------------------------------------------------------
// Layout shared for all data sets
// --------------------------------------------------------------------
// 1.a Reserve space for diagram title
LiblouisBrailleTextRasterizer textRasterizer = new LiblouisBrailleTextRasterizer(canvas.getPrinter());
Rectangle titleArea;
try {
// Calculate the minimum Height for the title out of all data sets
int titleBarHeight = calcTitleHeight(title.concat(titleToDataSetSeparator), data, textRasterizer, printableArea.intWrapper().getWidth(), canvas, language);
titleArea = printableArea.removeFromTop(titleBarHeight * cellHeight);
// Spacing
printableArea.removeFromTop(cellHeight);
} catch (final Rectangle.OutOfSpaceException e) {
throw new InsufficientRenderingAreaException("Not enough space to construct title layout", e);
}
// 2.a Reserve space for axis
Rectangle xAxisArea, yAxisArea;
try {
int xAxisHeight = X_AXIS_WIDTH * cellHeight;
int yAxisWidth = Y_AXIS_WIDTH * cellWidth;
xAxisArea = printableArea.removeFromBottom(xAxisHeight);
yAxisArea = printableArea.removeFromLeft(yAxisWidth);
xAxisArea.removeFromLeft(yAxisWidth);
} catch (Exception e) {
throw new InsufficientRenderingAreaException("Not enough space to construct axis layout", e);
}
// 2.b Some spacing to distinguish tokens from axis, in whole cells
try {
printableArea.removeFromBottom(toWholeCells(TOKEN_AXIS_OFFSET, cellHeight));
printableArea.removeFromLeft(toWholeCells(TOKEN_AXIS_OFFSET, cellWidth));
} catch (Rectangle.OutOfSpaceException e) {
throw new InsufficientRenderingAreaException("Not enough space for plot and axis border space");
}
// 2.c Initialize axis+
final Rectangle.IntWrapper printableAreaInt = printableArea.intWrapper();
Axis xAxis = new Axis(Axis.Type.X_AXIS, printableAreaInt.getX(), xAxisArea.intWrapper().getY(), xAxisStepWidth, AXIS_TICK_SIZE);
Axis yAxis = new Axis(Axis.Type.Y_AXIS, yAxisArea.intWrapper().getRight(), printableAreaInt.getBottom(), yAxisStepWidth, -AXIS_TICK_SIZE);
xAxis.setBoundary(xAxisArea);
yAxis.setBoundary(yAxisArea);
// 3. Calculate scaling
Rectangle plotArea = printableArea;
int xDots = plotArea.intWrapper().getWidth();
int yDots = plotArea.intWrapper().getHeight();
int xMin = ((int) Math.ceil(data.getMinX()));
int yMin = ((int) Math.ceil(data.getMinY()));
int xMax = ((int) Math.ceil(data.getMaxX()));
int yMax = ((int) Math.ceil(data.getMaxY()));
int xRange = Math.abs(xMax - xMin);
int yRange = Math.abs(yMax - yMin);
double xRatio = Math.floor((double) xDots / (double) xRange);
double yRatio = Math.floor((double) yDots / (double) yRange);
int xOrigin = plotArea.intWrapper().getX();
int yOrigin = plotArea.intWrapper().getY() + plotArea.intWrapper().getHeight();
LOG.debug("Complete printable Area: ({},{})", completeArea.intWrapper().getWidth(), completeArea.intWrapper().getHeight());
LOG.debug("Printable Area for actual plot: ({},{}) with global offset: ({},{})", plotArea.intWrapper().getWidth(), plotArea.intWrapper().getHeight(), plotArea.intWrapper().getX(), plotArea.intWrapper().getY());
LOG.debug("xMin; {}, xMax: {}, xRange: {}, xRatio: {}", xMin, xMax, xRange, xRatio);
LOG.debug("yMin; {}, yMax: {}, yRange: {}, yRatio: {}", yMin, yMax, yRange, yRatio);
LOG.debug("PlotOrigin: ({},{})", xOrigin, yOrigin);
// 4. Add tick mark labels to axis and to legend
LinearMappingAxisRasterizer axisRasterizer = new LinearMappingAxisRasterizer();
Legend legend = new Legend(legendTitle);
Map<String, String> axisLabels = new HashMap<>();
axisLabels.put(xAxisLabel, xAxisLabelValue);
axisLabels.put(yAxisLabel, yAxisLabelValue);
legend.addSymbolExplanationGroup(axisExplanationGroupName, axisLabels);
// X axis
Map<String, String> xAxisLegendSymbols = new HashMap<>();
// TODO Transfer to separate method
Map<Integer, String> xAxisLabels = new HashMap<>();
xAxis.setLabels(xAxisLabels);
final int xAxisTickCount = xDots / xAxisStepWidth;
char label = LEGEND_TICK_START_CHAR;
for (int x = 0; x < xAxisTickCount; x++) {
xAxisLabels.put(x, String.valueOf(label));
int tickPos = x * xAxisStepWidth;
double val = tickPos / xRatio;
LOG.trace("Adding x axis label {{},{}} for tick #{}", label, val, x);
xAxisLegendSymbols.put(String.valueOf(label), formatDouble(val));
label++;
}
// Y axis
Map<String, String> yAxisLegendSymbols = new HashMap<>();
Map<Integer, String> yAxisLabels = new HashMap<>();
yAxis.setLabels(yAxisLabels);
final int yAxisTickCount = yDots / yAxisStepWidth;
label = getStartChar(yAxisTickCount);
for (int y = 0; y < yAxisTickCount; y++) {
yAxisLabels.put(y, String.valueOf(label));
int tickPos = y * yAxisStepWidth;
double val = tickPos / yRatio;
LOG.trace("Adding y axis label {{},{}} for tick #{}", label, val, y);
yAxisLegendSymbols.put(String.valueOf(label), formatDouble(val));
label--;
}
legend.addColumn(xAxisLegendGroupName, xAxisLegendSymbols);
legend.addColumn(yAxisLegendGroupName, yAxisLegendSymbols);
legend.setColumnViewTitle(axisColumnGroupTitle);
// --------------------------------------------------------------------
// Rendering per data set
// --------------------------------------------------------------------
for (PointList l : data) {
MatrixData<Boolean> mat = canvas.getNewPage();
// 1. Set diagram title
BrailleText diagramTitle = new BrailleText(title + titleToDataSetSeparator + l.getName(), titleArea);
// 2. Render actual tokens
for (Point2DDouble p : l) {
int x = (int) Math.round(Math.abs((p.getX() - xMin) * xRatio));
int y = (int) Math.round(Math.abs((p.getY() - yMin) * yRatio));
if (x < 0 || x > xDots || y < 0 || y > yDots) {
throw new RuntimeException("Calculated position not in bounds: (" + x + "," + y + "), (" + xDots + "," + yDots + ")");
}
final int xGlobal = xOrigin + x;
final int yGlobal = yOrigin - y - 1;
LOG.trace("Placing token at local: ({},{}), global: ({},{}), rational: ({},{}) for data point: ({},{})", x, y, xGlobal, yGlobal, ((double) x) / ((double) xDots), ((double) y) / ((double) yDots), p.getX(), p.getY());
mat.setValue(yGlobal, xGlobal, true);
}
// 5. Render title and axis, or draw layout (for debugging purposes)
final boolean printLayout = false;
if (!printLayout) {
textRasterizer.rasterize(diagramTitle, canvas);
axisRasterizer.rasterize(xAxis, canvas);
axisRasterizer.rasterize(yAxis, canvas);
} else {
Rasterizer.rectangle(titleArea, mat, true);
Rasterizer.rectangle(xAxisArea, mat, true);
Rasterizer.rectangle(yAxisArea, mat, true);
Rasterizer.rectangle(printableArea, mat, true);
}
}
// --------------------------------------------------------------------
// Render Legend once
// --------------------------------------------------------------------
// 6. Render the legend
LegendRasterizer legendRasterizer = new LegendRasterizer();
legendRasterizer.rasterize(legend, canvas);
}
private static int toWholeCells(final int dots, final int cellDots) {
int diff = dots % cellDots;
if (diff != 0) {
diff = cellDots - diff;
}
int result = dots + diff;
LOG.trace("Stretching {} to {}, cellSize: {}", dots, result, cellDots);
return result;
}
private static String formatDouble(final double d) {
return String.format(NUMBER_LOCALE, "%.2f", d);
}
private static int calcTitleHeight(final String title, final PointListContainer<PointList> data, final LiblouisBrailleTextRasterizer textRasterizer, final int width, final RasterCanvas canvas, final BrailleLanguage.Language lang) {
int minHeight = Integer.MIN_VALUE;
for (PointList l : data) {
final String s = title + l.getName();
int height = textRasterizer.calculateRequiredHeight(s, width, canvas, lang);
minHeight = Math.max(minHeight, height);
}
return minHeight;
}
@SuppressWarnings("checkstyle:MagicNumber")
// char 'a' = 97 -> set to 96 so the lowest element is set to 'a' itself
private static char getStartChar(final int elemCount) {
return (char) (LEGEND_TICK_START_CHAR - 1 + elemCount);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment