diff --git a/pom.xml b/pom.xml index c2a4906..97e2980 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ it.cavallium jlinegraph - 1.0-SNAPSHOT + 1.1-SNAPSHOT 17 @@ -31,4 +31,4 @@ https://mvn.mchv.eu/repository/mchv-snapshot - \ No newline at end of file + diff --git a/src/main/java/it/cavallium/jlinegraph/AWTGraphExample.java b/src/main/java/it/cavallium/jlinegraph/AWTGraphExample.java index 834a426..662a72b 100644 --- a/src/main/java/it/cavallium/jlinegraph/AWTGraphExample.java +++ b/src/main/java/it/cavallium/jlinegraph/AWTGraphExample.java @@ -95,12 +95,13 @@ public class AWTGraphExample { new SeriesStyle(new Color(0.5f, 1f, 0.5f, 1f), 0, 1, 0.3, 1d), new SeriesStyle(new Color(1f, 1f, 0.7f, 1f), 1.5, 2, 0, 1d) ), - new GraphAxisStyle("X axis", true, "%.2fs"::formatted), - new GraphAxisStyle("Y axis", true, "%.2fm"::formatted), + new GraphAxisStyle("X axis", false, AxisMode.SHOW_WITH_VALUES, "%.2fs"::formatted), + new GraphAxisStyle("Y axis", false, AxisMode.SHOW_WITH_VALUES, "%.2fm"::formatted), GRAPH_COLOR, new GraphFonts(10f, 18f, 12f, 12f), 2f, - true + true, + 1 )); var r = new AWTGraphRenderer(); r.renderGraph(g, new RasterSize(w, h)).drawTo(g2d); diff --git a/src/main/java/it/cavallium/jlinegraph/AWTGraphRenderer.java b/src/main/java/it/cavallium/jlinegraph/AWTGraphRenderer.java index 6d7e290..414600f 100644 --- a/src/main/java/it/cavallium/jlinegraph/AWTGraphRenderer.java +++ b/src/main/java/it/cavallium/jlinegraph/AWTGraphRenderer.java @@ -48,25 +48,31 @@ public class AWTGraphRenderer implements IGraphRenderer { var valuesFontMetrics = graphics2D.getFontMetrics(valuesFont); var axisNameFontMetrics = graphics2D.getFontMetrics(axisNameFont); + var paddingMultiplier = graph.style().paddingMultiplier(); var graphBounds = graph.data().bounds(); var x = graph.style().x(); var y = graph.style().y(); + var padding = defaultFontMetrics.getHeight() * paddingMultiplier; var scaleX = new NiceScale(graphBounds.minX(), graphBounds.maxX()); scaleX.setMaxTicks(20); var scaleY = new NiceScale(graphBounds.minY(), graphBounds.maxY()); scaleY.setMaxTicks(20); - var topPadding = defaultFontMetrics.getHeight() + valuesFontMetrics.getHeight() / 2d; - var leftPadding = defaultFontMetrics.getHeight(); - var rightPadding = defaultFontMetrics.getHeight() - + valuesFontMetrics.stringWidth(y.valueFormat().apply(graphBounds.maxX())) / 2d; - var bottomPadding = defaultFontMetrics.getHeight(); - var xValueLineLength = valuesFontMetrics.getHeight(); - var yValueLineLength = valuesFontMetrics.getHeight(); - var xValuesHeight = valuesFontMetrics.getHeight(); - var xValuesToXAxisNamePadding = (x.show() ? valuesFontMetrics.getHeight() : 0); - var xAxisNameHeight = (x.show() ? axisNameFontMetrics.getHeight() : 0); - var yAxisNameWidth = (y.show() ? axisNameFontMetrics.getHeight() : 0); - var yValuesToYAxisNamePadding = (y.show() ? valuesFontMetrics.getHeight() : 0); + var halfMaxXLabelWidth = x.mode().showLabels() + ? (valuesFontMetrics.stringWidth(y.valueFormat().apply(graphBounds.maxX())) / 2d) : 0; + var halfMaxYLabelHeight = (y.mode().showLabels() ? valuesFontMetrics.getHeight() / 2d : 0); + var topPadding = padding + halfMaxYLabelHeight; + var leftPadding = padding + + ((y.mode() == AxisMode.HIDE && !y.showName()) ? halfMaxXLabelWidth : 0); + var rightPadding = padding + (x.mode().showLabels() ? halfMaxXLabelWidth : 0); + var bottomPadding = padding + + ((x.mode() == AxisMode.HIDE && !x.showName()) ? halfMaxYLabelHeight : 0); + var xValueLineLength = x.mode().showRuler() ? valuesFontMetrics.getHeight() : 0; + var yValueLineLength = y.mode().showRuler() ? valuesFontMetrics.getHeight() : 0; + var xValuesHeight = x.mode().showLabels() ? valuesFontMetrics.getHeight() : 0; + var xValuesToXAxisNamePadding = (x.showName() ? valuesFontMetrics.getHeight() / 2 : 0); + var xAxisNameHeight = (x.showName() ? axisNameFontMetrics.getHeight() : 0); + var yAxisNameWidth = (y.showName() ? axisNameFontMetrics.getHeight() : 0); + var yValuesToYAxisNamePadding = (y.showName() ? valuesFontMetrics.getHeight() / 2 : 0); var graphHeight // Start with total height @@ -87,7 +93,7 @@ public class AWTGraphRenderer implements IGraphRenderer { var xValueLineOffset = topPadding + graphHeight; var yLabels = getYLabels(graph, graphHeight, valuesFontMetrics, scaleY); - RasterSize yLabelsAreaSize = computeYLabelsAreaSize(graphHeight, valuesFontMetrics, yLabels); + RasterSize yLabelsAreaSize = computeYLabelsAreaSize(y.mode(), graphHeight, valuesFontMetrics, yLabels); var yValuesWidth = yLabelsAreaSize.width(); var yValueLineOffset = leftPadding + yAxisNameWidth + yValuesToYAxisNamePadding + yValuesWidth; @@ -129,9 +135,9 @@ public class AWTGraphRenderer implements IGraphRenderer { var xLabels = getXLabels(graph, graphWidth, valuesFontMetrics, scaleX); - RasterSize yAxisNameCenterOffset = new RasterSize(valuesFontMetrics.getHeight(), valuesFontMetrics.getHeight() + RasterSize yAxisNameCenterOffset = new RasterSize(leftPadding + valuesFontMetrics.getHeight() / 2d, valuesFontMetrics.getHeight() // Add half of graph height - + graphHeight / 2); + + graphHeight / 2 + topPadding); RasterSize yValuesOffset = new RasterSize(leftPadding // Add y axis name "90deg height" @@ -182,11 +188,11 @@ public class AWTGraphRenderer implements IGraphRenderer { return; } - renderGraphBorders(graphics2D, graph, graphOffset, graphSize, defaultStroke); - if (y.show()) { + renderGraphBorders(graphics2D, graph, graphOffset, graphSize, defaultStroke, totalSize); + if (y.showName()) { renderYAxisName(graphics2D, graph, yAxisNameCenterOffset, axisNameFont, axisNameFontMetrics); } - if (x.show()) { + if (x.showName()) { renderXAxisName(graphics2D, graph, xAxisNameCenterOffset, axisNameFont, axisNameFontMetrics); } renderYAxisValueLabels(graphics2D, @@ -198,7 +204,9 @@ public class AWTGraphRenderer implements IGraphRenderer { yLabels, yLabelsAreaSize, yValuesOffset, - defaultStroke + defaultStroke, + y.mode().showRuler(), + y.mode().showLabels() ); renderXAxisValueLabels(graphics2D, graph, @@ -207,7 +215,9 @@ public class AWTGraphRenderer implements IGraphRenderer { xValueLineOffset, xValueLineLength, xLabels, xValuesOffset, - defaultStroke + defaultStroke, + x.mode().showRuler(), + x.mode().showLabels() ); @@ -615,25 +625,33 @@ public class AWTGraphRenderer implements IGraphRenderer { List yLabels, RasterSize yLabelsAreaSize, RasterSize yValuesOffset, - BasicStroke defaultStroke) { - graphics2D.setFont(valuesFont); - graphics2D.setStroke(defaultStroke); - graphics2D.setColor(graph.style().colors().foreground().toColor()); - yLabels.forEach(label -> { - var lineStartOffsetY = yValuesOffset.height() + label.rasterOffset(); - var currentLineOffsetX = label.formattedText().isBlank() ? yValueLineLength / 3d : 0; - var currentLineLength = yValueLineLength + (label.formattedText().isBlank() ? -yValueLineLength / 3d : 0); - graphics2D.draw(new Line2D.Double(yValueLineOffset + currentLineOffsetX, - lineStartOffsetY, - yValueLineOffset + currentLineOffsetX + currentLineLength, - lineStartOffsetY - )); - graphics2D.fill(generateShapeFromText(graphics2D, - label.formattedText(), - yValuesOffset.width() + yLabelsAreaSize.width() - valuesFontMetrics.stringWidth(label.formattedText()), - yValuesOffset.height() + label.rasterOffset() + valuesFontMetrics.getHeight() / 2d - valuesFontMetrics.getDescent() - )); - }); + BasicStroke defaultStroke, + boolean showRulerTicks, + boolean showRulerLabels) { + if ((yValueLineLength > 0 && showRulerTicks) || showRulerLabels) { + graphics2D.setFont(valuesFont); + graphics2D.setStroke(defaultStroke); + graphics2D.setColor(graph.style().colors().foreground().toColor()); + yLabels.forEach(label -> { + if (showRulerTicks) { + var lineStartOffsetY = yValuesOffset.height() + label.rasterOffset(); + var currentLineOffsetX = label.formattedText().isBlank() ? yValueLineLength / 3d : 0; + var currentLineLength = yValueLineLength + (label.formattedText().isBlank() ? -yValueLineLength / 3d : 0); + graphics2D.draw(new Line2D.Double(yValueLineOffset + currentLineOffsetX, + lineStartOffsetY, + yValueLineOffset + currentLineOffsetX + currentLineLength, + lineStartOffsetY + )); + } + if (showRulerLabels) { + graphics2D.fill(generateShapeFromText(graphics2D, + label.formattedText(), + yValuesOffset.width() + yLabelsAreaSize.width() - valuesFontMetrics.stringWidth(label.formattedText()), + yValuesOffset.height() + label.rasterOffset() + valuesFontMetrics.getHeight() / 2d - valuesFontMetrics.getDescent() + )); + } + }); + } } private static void renderXAxisName(Graphics2D graphics2D, @@ -662,28 +680,44 @@ public class AWTGraphRenderer implements IGraphRenderer { int xValueLineLength, List xLabels, RasterSize xValuesOffset, - BasicStroke defaultStroke) { - graphics2D.setFont(valuesFont); - graphics2D.setStroke(defaultStroke); - graphics2D.setColor(graph.style().colors().foreground().toColor()); - xLabels.forEach(label -> { - var lineStartOffsetX = xValuesOffset.width() + label.rasterOffset(); - var currentLineLength = label.formattedText().isBlank() ? xValueLineLength / 1.5d : xValueLineLength; - //noinspection SuspiciousNameCombination - graphics2D.draw(new Line2D.Double(lineStartOffsetX, xValueLineOffset, lineStartOffsetX, xValueLineOffset + currentLineLength)); - graphics2D.fill(generateShapeFromText(graphics2D, - label.formattedText(), - xValuesOffset.width() + label.rasterOffset() - valuesFontMetrics.stringWidth(label.formattedText()) / 2d, - xValuesOffset.height() + valuesFontMetrics.getHeight() - )); - }); + BasicStroke defaultStroke, + boolean showRulerTicks, + boolean showRulerLabels) { + if ((xValueLineLength > 0 && showRulerTicks) || showRulerLabels) { + graphics2D.setFont(valuesFont); + graphics2D.setStroke(defaultStroke); + graphics2D.setColor(graph.style().colors().foreground().toColor()); + xLabels.forEach(label -> { + var lineStartOffsetX = xValuesOffset.width() + label.rasterOffset(); + if (showRulerTicks) { + var currentLineLength = label.formattedText().isBlank() ? xValueLineLength / 1.5d : xValueLineLength; + //noinspection SuspiciousNameCombination + graphics2D.draw(new Line2D.Double(lineStartOffsetX, xValueLineOffset, lineStartOffsetX, xValueLineOffset + currentLineLength)); + } + if (showRulerLabels) { + graphics2D.fill(generateShapeFromText(graphics2D, + label.formattedText(), + xValuesOffset.width() + label.rasterOffset() - valuesFontMetrics.stringWidth(label.formattedText()) / 2d, + xValuesOffset.height() + valuesFontMetrics.getHeight() + )); + } + }); + } } private static void renderGraphBorders(Graphics2D graphics2D, Graph graph, RasterSize graphOffset, RasterSize graphSize, - BasicStroke defaultStroke) { + BasicStroke defaultStroke, + RasterSize totalSize) { + // Do not draw the border if the graph is fullscreen + if (graphOffset.width() <= 0 + && graphOffset.height() <= 0 + && graphSize.width() >= totalSize.width() + && graphSize.height() >= totalSize.height()) { + return; + } var fgColor = graph.style().colors().foreground().toColor(); graphics2D.setColor(fgColor); graphics2D.setStroke(defaultStroke); @@ -694,7 +728,11 @@ public class AWTGraphRenderer implements IGraphRenderer { )); } - private static RasterSize computeYLabelsAreaSize(double graphHeight, FontMetrics valuesFontMetrics, List yLabels) { + private static RasterSize computeYLabelsAreaSize(AxisMode axisMode, double graphHeight, FontMetrics valuesFontMetrics, + List yLabels) { + if (!axisMode.showLabels()) { + return RasterSize.EMPTY; + } double maxLabelWidth = 0d; for (LabelWithOffset yLabel : yLabels) { var currentMaxLabelWidth = valuesFontMetrics.stringWidth(yLabel.formattedText); diff --git a/src/main/java/it/cavallium/jlinegraph/AxisMode.java b/src/main/java/it/cavallium/jlinegraph/AxisMode.java new file mode 100644 index 0000000..3c8e592 --- /dev/null +++ b/src/main/java/it/cavallium/jlinegraph/AxisMode.java @@ -0,0 +1,21 @@ +package it.cavallium.jlinegraph; + +public enum AxisMode { + SHOW_WITH_VALUES, + SHOW_RULER_ONLY, + HIDE; + + public boolean showLabels() { + return switch (this) { + case SHOW_WITH_VALUES -> true; + default -> false; + }; + } + + public boolean showRuler() { + return switch (this) { + case SHOW_WITH_VALUES, SHOW_RULER_ONLY -> true; + default -> false; + }; + } +} diff --git a/src/main/java/it/cavallium/jlinegraph/GraphAxisStyle.java b/src/main/java/it/cavallium/jlinegraph/GraphAxisStyle.java index 366f0ac..9048886 100644 --- a/src/main/java/it/cavallium/jlinegraph/GraphAxisStyle.java +++ b/src/main/java/it/cavallium/jlinegraph/GraphAxisStyle.java @@ -2,4 +2,4 @@ package it.cavallium.jlinegraph; import java.util.function.Function; -public record GraphAxisStyle(String title, boolean show, Function valueFormat) {} +public record GraphAxisStyle(String title, boolean showName, AxisMode mode, Function valueFormat) {} diff --git a/src/main/java/it/cavallium/jlinegraph/GraphStyle.java b/src/main/java/it/cavallium/jlinegraph/GraphStyle.java index 76512fd..8311224 100644 --- a/src/main/java/it/cavallium/jlinegraph/GraphStyle.java +++ b/src/main/java/it/cavallium/jlinegraph/GraphStyle.java @@ -3,4 +3,4 @@ package it.cavallium.jlinegraph; import java.util.List; public record GraphStyle(List seriesStyles, GraphAxisStyle x, GraphAxisStyle y, GraphColors colors, - GraphFonts fonts, double strokeWidth, boolean showLegend) {} + GraphFonts fonts, double strokeWidth, boolean showLegend, double paddingMultiplier) {} diff --git a/src/main/java/it/cavallium/jlinegraph/RasterSize.java b/src/main/java/it/cavallium/jlinegraph/RasterSize.java index c0fcec9..b3d9e6a 100644 --- a/src/main/java/it/cavallium/jlinegraph/RasterSize.java +++ b/src/main/java/it/cavallium/jlinegraph/RasterSize.java @@ -1,3 +1,5 @@ package it.cavallium.jlinegraph; -public record RasterSize(double width, double height) {} +public record RasterSize(double width, double height) { + public static final RasterSize EMPTY = new RasterSize(0, 0); +}