Autoscale
This commit is contained in:
parent
685436a316
commit
6a25e7501b
@ -1,6 +1,7 @@
|
||||
package de.ph87.data.series.graph;
|
||||
|
||||
import de.ph87.data.series.*;
|
||||
import de.ph87.data.value.*;
|
||||
import jakarta.annotation.*;
|
||||
import lombok.*;
|
||||
|
||||
@ -59,6 +60,8 @@ public class Graph {
|
||||
|
||||
public final int maxLabelWidth;
|
||||
|
||||
private final Autoscale autoscale;
|
||||
|
||||
public Graph(@NonNull final SeriesDto series, @NonNull final List<GraphPoint> points, @NonNull final Aligned begin, @NonNull final Aligned end, final int width, final int height, final int border) {
|
||||
this.series = series;
|
||||
this.begin = begin;
|
||||
@ -67,21 +70,31 @@ public class Graph {
|
||||
this.height = height;
|
||||
this.border = border;
|
||||
|
||||
// find bounds
|
||||
double vSum = 0;
|
||||
double vMin = Double.MAX_VALUE;
|
||||
double vMax = Double.MIN_VALUE;
|
||||
int maxLabelWidth = 80;
|
||||
final FontMetrics fontMetrics = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB).getGraphics().getFontMetrics();
|
||||
for (final GraphPoint point : points) {
|
||||
vMin = Math.min(vMin, point.getValue());
|
||||
vMax = max(vMax, point.getValue());
|
||||
vSum += point.getValue();
|
||||
maxLabelWidth = max(maxLabelWidth, fontMetrics.stringWidth(series.format(point.getValue())));
|
||||
}
|
||||
this.valueAvg = vSum / points.size();
|
||||
this.maxLabelWidth = maxLabelWidth;
|
||||
|
||||
widthInner = width - 3 * border - maxLabelWidth;
|
||||
// auto scale
|
||||
autoscale = new Autoscale(series.unit, vMin, vMax);
|
||||
vMin *= autoscale.factor;
|
||||
vMax *= autoscale.factor;
|
||||
vSum *= autoscale.factor;
|
||||
|
||||
// find max label width
|
||||
int __maxLabelWidth = 80;
|
||||
final FontMetrics fontMetrics = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB).getGraphics().getFontMetrics();
|
||||
for (final GraphPoint point : points) {
|
||||
__maxLabelWidth = max(__maxLabelWidth, fontMetrics.stringWidth(autoscale.format(point.getValue() * autoscale.factor)));
|
||||
}
|
||||
this.maxLabelWidth = __maxLabelWidth;
|
||||
|
||||
widthInner = width - 3 * border - this.maxLabelWidth;
|
||||
heightInner = height - 2 * border;
|
||||
|
||||
minuteMin = begin.date.toEpochSecond() / 60;
|
||||
@ -91,12 +104,14 @@ public class Graph {
|
||||
|
||||
valueMin = vMin;
|
||||
valueMax = vMax;
|
||||
valueRange = vMax - vMin;
|
||||
valueAvg = vSum / points.size();
|
||||
valueRange = valueMax - valueMin;
|
||||
valueScale = heightInner / valueRange;
|
||||
|
||||
this.points = points.stream().map(toPoint(minuteMin, minuteScale, vMin, valueScale)).toList();
|
||||
this.points = points.stream().map(toPoint()).toList();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public BufferedImage draw() {
|
||||
final BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
|
||||
final Graphics2D g = (Graphics2D) image.getGraphics();
|
||||
@ -130,7 +145,7 @@ public class Graph {
|
||||
}
|
||||
|
||||
private void yLabel(@NonNull final Graphics2D g, final double value, @Nullable final Stroke stroke, @Nullable final Color color) {
|
||||
final String string = series.format(value);
|
||||
final String string = autoscale.format(value);
|
||||
final int offset = maxLabelWidth - g.getFontMetrics().stringWidth(string);
|
||||
final int y = height - ((int) Math.round((value - valueMin) * valueScale) + border);
|
||||
g.setColor(color);
|
||||
@ -142,14 +157,14 @@ public class Graph {
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Function<GraphPoint, Point> toPoint(final long minuteMin, final double minuteScale, final double valueMin, final double valueScale) {
|
||||
private Function<GraphPoint, Point> toPoint() {
|
||||
return point -> {
|
||||
final long minuteEpoch = point.getDate().toEpochSecond() / 60;
|
||||
final long minuteRelative = minuteEpoch - minuteMin;
|
||||
final double minuteScaled = minuteRelative * minuteScale;
|
||||
final int x = (int) Math.round(minuteScaled);
|
||||
|
||||
final double valueRelative = point.getValue() - valueMin;
|
||||
final double valueRelative = point.getValue() * autoscale.factor - valueMin;
|
||||
final double valueScaled = valueRelative * valueScale;
|
||||
final int y = (int) Math.round(valueScaled);
|
||||
|
||||
|
||||
32
src/main/java/de/ph87/data/value/Autoscale.java
Normal file
32
src/main/java/de/ph87/data/value/Autoscale.java
Normal file
@ -0,0 +1,32 @@
|
||||
package de.ph87.data.value;
|
||||
|
||||
import lombok.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@Data
|
||||
public class Autoscale {
|
||||
|
||||
public static final String[] SI_PREFIX = new String[]{"f", "n,", "µ", "m", "", "k", "M", "G", "T"};
|
||||
|
||||
public final double factor;
|
||||
|
||||
@NonNull
|
||||
public final String unit;
|
||||
|
||||
public Autoscale(@NonNull final Unit sourceUnit, final double... values) {
|
||||
final double abs = sourceUnit.factor * Arrays.stream(values).map(Math::abs).max().orElse(0);
|
||||
final double exp = Math.max(0, Math.log10(abs));
|
||||
final int group = (int) Math.floor(exp / 3);
|
||||
this.factor = sourceUnit.factor * Math.pow(10, group * 3);
|
||||
|
||||
final int index = (SI_PREFIX.length - 1) / 2 + group;
|
||||
this.unit = SI_PREFIX[index] + sourceUnit.base.unit;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String format(final double value) {
|
||||
return "%.1f %s".formatted(value, unit);
|
||||
}
|
||||
|
||||
}
|
||||
@ -9,21 +9,34 @@ import java.util.*;
|
||||
|
||||
public enum Unit {
|
||||
TEMPERATURE_C("°C"),
|
||||
PRESSURE_HPA("hPa"),
|
||||
|
||||
PRESSURE_PA("Pa"),
|
||||
PRESSURE_HPA("hPa", 100, PRESSURE_PA),
|
||||
|
||||
HUMIDITY_RELATIVE_PERCENT("%"),
|
||||
HUMIDITY_ABSOLUTE_MGL("mg/L"),
|
||||
HUMIDITY_ABSOLUTE_GM3("g/m³", 1, HUMIDITY_ABSOLUTE_MGL),
|
||||
|
||||
HUMIDITY_ABSOLUTE_GM3("g/m³"),
|
||||
HUMIDITY_ABSOLUTE_MGL("mg/L", 1, HUMIDITY_ABSOLUTE_GM3),
|
||||
|
||||
ILLUMINANCE_LUX("lux"),
|
||||
|
||||
RESISTANCE_OHMS("Ω"),
|
||||
|
||||
ALTITUDE_M("m"),
|
||||
ALTITUDE_KM("km", 1000, ALTITUDE_M),
|
||||
|
||||
POWER_W("W"),
|
||||
POWER_KW("kW", 1000, POWER_W),
|
||||
ENERGY_WH("W"),
|
||||
|
||||
ENERGY_WH("Wh"),
|
||||
ENERGY_KWH("kWh", 1000, ENERGY_WH),
|
||||
|
||||
IAQ("IAQ"),
|
||||
IAQ_CO2_EQUIVALENT("ppm"),
|
||||
IAQ_VOC_EQUIVALENT("ppm"),
|
||||
|
||||
SUN_DC("Δ°C"),
|
||||
|
||||
UNIT_PERCENT("%"),
|
||||
;
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user