New labels/axis settings

This commit is contained in:
Andrea Cavalli 2022-09-15 18:32:48 +02:00
parent 817a70a938
commit bccf1f5823
7 changed files with 126 additions and 64 deletions

View File

@ -6,7 +6,7 @@
<groupId>it.cavallium</groupId> <groupId>it.cavallium</groupId>
<artifactId>jlinegraph</artifactId> <artifactId>jlinegraph</artifactId>
<version>1.0-SNAPSHOT</version> <version>1.1-SNAPSHOT</version>
<properties> <properties>
<maven.compiler.source>17</maven.compiler.source> <maven.compiler.source>17</maven.compiler.source>
@ -31,4 +31,4 @@
<url>https://mvn.mchv.eu/repository/mchv-snapshot</url> <url>https://mvn.mchv.eu/repository/mchv-snapshot</url>
</snapshotRepository> </snapshotRepository>
</distributionManagement> </distributionManagement>
</project> </project>

View File

@ -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(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 SeriesStyle(new Color(1f, 1f, 0.7f, 1f), 1.5, 2, 0, 1d)
), ),
new GraphAxisStyle("X axis", true, "%.2fs"::formatted), new GraphAxisStyle("X axis", false, AxisMode.SHOW_WITH_VALUES, "%.2fs"::formatted),
new GraphAxisStyle("Y axis", true, "%.2fm"::formatted), new GraphAxisStyle("Y axis", false, AxisMode.SHOW_WITH_VALUES, "%.2fm"::formatted),
GRAPH_COLOR, GRAPH_COLOR,
new GraphFonts(10f, 18f, 12f, 12f), new GraphFonts(10f, 18f, 12f, 12f),
2f, 2f,
true true,
1
)); ));
var r = new AWTGraphRenderer(); var r = new AWTGraphRenderer();
r.renderGraph(g, new RasterSize(w, h)).drawTo(g2d); r.renderGraph(g, new RasterSize(w, h)).drawTo(g2d);

View File

@ -48,25 +48,31 @@ public class AWTGraphRenderer implements IGraphRenderer<AWTDrawer> {
var valuesFontMetrics = graphics2D.getFontMetrics(valuesFont); var valuesFontMetrics = graphics2D.getFontMetrics(valuesFont);
var axisNameFontMetrics = graphics2D.getFontMetrics(axisNameFont); var axisNameFontMetrics = graphics2D.getFontMetrics(axisNameFont);
var paddingMultiplier = graph.style().paddingMultiplier();
var graphBounds = graph.data().bounds(); var graphBounds = graph.data().bounds();
var x = graph.style().x(); var x = graph.style().x();
var y = graph.style().y(); var y = graph.style().y();
var padding = defaultFontMetrics.getHeight() * paddingMultiplier;
var scaleX = new NiceScale(graphBounds.minX(), graphBounds.maxX()); var scaleX = new NiceScale(graphBounds.minX(), graphBounds.maxX());
scaleX.setMaxTicks(20); scaleX.setMaxTicks(20);
var scaleY = new NiceScale(graphBounds.minY(), graphBounds.maxY()); var scaleY = new NiceScale(graphBounds.minY(), graphBounds.maxY());
scaleY.setMaxTicks(20); scaleY.setMaxTicks(20);
var topPadding = defaultFontMetrics.getHeight() + valuesFontMetrics.getHeight() / 2d; var halfMaxXLabelWidth = x.mode().showLabels()
var leftPadding = defaultFontMetrics.getHeight(); ? (valuesFontMetrics.stringWidth(y.valueFormat().apply(graphBounds.maxX())) / 2d) : 0;
var rightPadding = defaultFontMetrics.getHeight() var halfMaxYLabelHeight = (y.mode().showLabels() ? valuesFontMetrics.getHeight() / 2d : 0);
+ valuesFontMetrics.stringWidth(y.valueFormat().apply(graphBounds.maxX())) / 2d; var topPadding = padding + halfMaxYLabelHeight;
var bottomPadding = defaultFontMetrics.getHeight(); var leftPadding = padding
var xValueLineLength = valuesFontMetrics.getHeight(); + ((y.mode() == AxisMode.HIDE && !y.showName()) ? halfMaxXLabelWidth : 0);
var yValueLineLength = valuesFontMetrics.getHeight(); var rightPadding = padding + (x.mode().showLabels() ? halfMaxXLabelWidth : 0);
var xValuesHeight = valuesFontMetrics.getHeight(); var bottomPadding = padding
var xValuesToXAxisNamePadding = (x.show() ? valuesFontMetrics.getHeight() : 0); + ((x.mode() == AxisMode.HIDE && !x.showName()) ? halfMaxYLabelHeight : 0);
var xAxisNameHeight = (x.show() ? axisNameFontMetrics.getHeight() : 0); var xValueLineLength = x.mode().showRuler() ? valuesFontMetrics.getHeight() : 0;
var yAxisNameWidth = (y.show() ? axisNameFontMetrics.getHeight() : 0); var yValueLineLength = y.mode().showRuler() ? valuesFontMetrics.getHeight() : 0;
var yValuesToYAxisNamePadding = (y.show() ? 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 var graphHeight
// Start with total height // Start with total height
@ -87,7 +93,7 @@ public class AWTGraphRenderer implements IGraphRenderer<AWTDrawer> {
var xValueLineOffset = topPadding + graphHeight; var xValueLineOffset = topPadding + graphHeight;
var yLabels = getYLabels(graph, graphHeight, valuesFontMetrics, scaleY); 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 yValuesWidth = yLabelsAreaSize.width();
var yValueLineOffset = leftPadding + yAxisNameWidth + yValuesToYAxisNamePadding + yValuesWidth; var yValueLineOffset = leftPadding + yAxisNameWidth + yValuesToYAxisNamePadding + yValuesWidth;
@ -129,9 +135,9 @@ public class AWTGraphRenderer implements IGraphRenderer<AWTDrawer> {
var xLabels = getXLabels(graph, graphWidth, valuesFontMetrics, scaleX); 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 // Add half of graph height
+ graphHeight / 2); + graphHeight / 2 + topPadding);
RasterSize yValuesOffset = new RasterSize(leftPadding RasterSize yValuesOffset = new RasterSize(leftPadding
// Add y axis name "90deg height" // Add y axis name "90deg height"
@ -182,11 +188,11 @@ public class AWTGraphRenderer implements IGraphRenderer<AWTDrawer> {
return; return;
} }
renderGraphBorders(graphics2D, graph, graphOffset, graphSize, defaultStroke); renderGraphBorders(graphics2D, graph, graphOffset, graphSize, defaultStroke, totalSize);
if (y.show()) { if (y.showName()) {
renderYAxisName(graphics2D, graph, yAxisNameCenterOffset, axisNameFont, axisNameFontMetrics); renderYAxisName(graphics2D, graph, yAxisNameCenterOffset, axisNameFont, axisNameFontMetrics);
} }
if (x.show()) { if (x.showName()) {
renderXAxisName(graphics2D, graph, xAxisNameCenterOffset, axisNameFont, axisNameFontMetrics); renderXAxisName(graphics2D, graph, xAxisNameCenterOffset, axisNameFont, axisNameFontMetrics);
} }
renderYAxisValueLabels(graphics2D, renderYAxisValueLabels(graphics2D,
@ -198,7 +204,9 @@ public class AWTGraphRenderer implements IGraphRenderer<AWTDrawer> {
yLabels, yLabels,
yLabelsAreaSize, yLabelsAreaSize,
yValuesOffset, yValuesOffset,
defaultStroke defaultStroke,
y.mode().showRuler(),
y.mode().showLabels()
); );
renderXAxisValueLabels(graphics2D, renderXAxisValueLabels(graphics2D,
graph, graph,
@ -207,7 +215,9 @@ public class AWTGraphRenderer implements IGraphRenderer<AWTDrawer> {
xValueLineOffset, xValueLineOffset,
xValueLineLength, xValueLineLength,
xLabels, xValuesOffset, xLabels, xValuesOffset,
defaultStroke defaultStroke,
x.mode().showRuler(),
x.mode().showLabels()
); );
@ -615,25 +625,33 @@ public class AWTGraphRenderer implements IGraphRenderer<AWTDrawer> {
List<LabelWithOffset> yLabels, List<LabelWithOffset> yLabels,
RasterSize yLabelsAreaSize, RasterSize yLabelsAreaSize,
RasterSize yValuesOffset, RasterSize yValuesOffset,
BasicStroke defaultStroke) { BasicStroke defaultStroke,
graphics2D.setFont(valuesFont); boolean showRulerTicks,
graphics2D.setStroke(defaultStroke); boolean showRulerLabels) {
graphics2D.setColor(graph.style().colors().foreground().toColor()); if ((yValueLineLength > 0 && showRulerTicks) || showRulerLabels) {
yLabels.forEach(label -> { graphics2D.setFont(valuesFont);
var lineStartOffsetY = yValuesOffset.height() + label.rasterOffset(); graphics2D.setStroke(defaultStroke);
var currentLineOffsetX = label.formattedText().isBlank() ? yValueLineLength / 3d : 0; graphics2D.setColor(graph.style().colors().foreground().toColor());
var currentLineLength = yValueLineLength + (label.formattedText().isBlank() ? -yValueLineLength / 3d : 0); yLabels.forEach(label -> {
graphics2D.draw(new Line2D.Double(yValueLineOffset + currentLineOffsetX, if (showRulerTicks) {
lineStartOffsetY, var lineStartOffsetY = yValuesOffset.height() + label.rasterOffset();
yValueLineOffset + currentLineOffsetX + currentLineLength, var currentLineOffsetX = label.formattedText().isBlank() ? yValueLineLength / 3d : 0;
lineStartOffsetY var currentLineLength = yValueLineLength + (label.formattedText().isBlank() ? -yValueLineLength / 3d : 0);
)); graphics2D.draw(new Line2D.Double(yValueLineOffset + currentLineOffsetX,
graphics2D.fill(generateShapeFromText(graphics2D, lineStartOffsetY,
label.formattedText(), yValueLineOffset + currentLineOffsetX + currentLineLength,
yValuesOffset.width() + yLabelsAreaSize.width() - valuesFontMetrics.stringWidth(label.formattedText()), lineStartOffsetY
yValuesOffset.height() + label.rasterOffset() + valuesFontMetrics.getHeight() / 2d - valuesFontMetrics.getDescent() ));
)); }
}); 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, private static void renderXAxisName(Graphics2D graphics2D,
@ -662,28 +680,44 @@ public class AWTGraphRenderer implements IGraphRenderer<AWTDrawer> {
int xValueLineLength, int xValueLineLength,
List<LabelWithOffset> xLabels, List<LabelWithOffset> xLabels,
RasterSize xValuesOffset, RasterSize xValuesOffset,
BasicStroke defaultStroke) { BasicStroke defaultStroke,
graphics2D.setFont(valuesFont); boolean showRulerTicks,
graphics2D.setStroke(defaultStroke); boolean showRulerLabels) {
graphics2D.setColor(graph.style().colors().foreground().toColor()); if ((xValueLineLength > 0 && showRulerTicks) || showRulerLabels) {
xLabels.forEach(label -> { graphics2D.setFont(valuesFont);
var lineStartOffsetX = xValuesOffset.width() + label.rasterOffset(); graphics2D.setStroke(defaultStroke);
var currentLineLength = label.formattedText().isBlank() ? xValueLineLength / 1.5d : xValueLineLength; graphics2D.setColor(graph.style().colors().foreground().toColor());
//noinspection SuspiciousNameCombination xLabels.forEach(label -> {
graphics2D.draw(new Line2D.Double(lineStartOffsetX, xValueLineOffset, lineStartOffsetX, xValueLineOffset + currentLineLength)); var lineStartOffsetX = xValuesOffset.width() + label.rasterOffset();
graphics2D.fill(generateShapeFromText(graphics2D, if (showRulerTicks) {
label.formattedText(), var currentLineLength = label.formattedText().isBlank() ? xValueLineLength / 1.5d : xValueLineLength;
xValuesOffset.width() + label.rasterOffset() - valuesFontMetrics.stringWidth(label.formattedText()) / 2d, //noinspection SuspiciousNameCombination
xValuesOffset.height() + valuesFontMetrics.getHeight() 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, private static void renderGraphBorders(Graphics2D graphics2D,
Graph graph, Graph graph,
RasterSize graphOffset, RasterSize graphOffset,
RasterSize graphSize, 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(); var fgColor = graph.style().colors().foreground().toColor();
graphics2D.setColor(fgColor); graphics2D.setColor(fgColor);
graphics2D.setStroke(defaultStroke); graphics2D.setStroke(defaultStroke);
@ -694,7 +728,11 @@ public class AWTGraphRenderer implements IGraphRenderer<AWTDrawer> {
)); ));
} }
private static RasterSize computeYLabelsAreaSize(double graphHeight, FontMetrics valuesFontMetrics, List<LabelWithOffset> yLabels) { private static RasterSize computeYLabelsAreaSize(AxisMode axisMode, double graphHeight, FontMetrics valuesFontMetrics,
List<LabelWithOffset> yLabels) {
if (!axisMode.showLabels()) {
return RasterSize.EMPTY;
}
double maxLabelWidth = 0d; double maxLabelWidth = 0d;
for (LabelWithOffset yLabel : yLabels) { for (LabelWithOffset yLabel : yLabels) {
var currentMaxLabelWidth = valuesFontMetrics.stringWidth(yLabel.formattedText); var currentMaxLabelWidth = valuesFontMetrics.stringWidth(yLabel.formattedText);

View File

@ -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;
};
}
}

View File

@ -2,4 +2,4 @@ package it.cavallium.jlinegraph;
import java.util.function.Function; import java.util.function.Function;
public record GraphAxisStyle(String title, boolean show, Function<Number, String> valueFormat) {} public record GraphAxisStyle(String title, boolean showName, AxisMode mode, Function<Number, String> valueFormat) {}

View File

@ -3,4 +3,4 @@ package it.cavallium.jlinegraph;
import java.util.List; import java.util.List;
public record GraphStyle(List<SeriesStyle> seriesStyles, GraphAxisStyle x, GraphAxisStyle y, GraphColors colors, public record GraphStyle(List<SeriesStyle> seriesStyles, GraphAxisStyle x, GraphAxisStyle y, GraphColors colors,
GraphFonts fonts, double strokeWidth, boolean showLegend) {} GraphFonts fonts, double strokeWidth, boolean showLegend, double paddingMultiplier) {}

View File

@ -1,3 +1,5 @@
package it.cavallium.jlinegraph; 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);
}