diff --git a/pdb-plotting/src/main/java/org/lucares/pdb/plot/api/AxisScale.java b/pdb-plotting/src/main/java/org/lucares/pdb/plot/api/AxisScale.java index 4baf4f9..fe5923b 100644 --- a/pdb-plotting/src/main/java/org/lucares/pdb/plot/api/AxisScale.java +++ b/pdb-plotting/src/main/java/org/lucares/pdb/plot/api/AxisScale.java @@ -1,5 +1,5 @@ package org.lucares.pdb.plot.api; public enum AxisScale { - LINEAR, LOG10, LOG2 + LINEAR, LOG10 } diff --git a/pdb-plotting/src/main/java/org/lucares/recommind/logs/DataSeries.java b/pdb-plotting/src/main/java/org/lucares/recommind/logs/DataSeries.java index 768436f..e827d54 100644 --- a/pdb-plotting/src/main/java/org/lucares/recommind/logs/DataSeries.java +++ b/pdb-plotting/src/main/java/org/lucares/recommind/logs/DataSeries.java @@ -1,5 +1,6 @@ package org.lucares.recommind.logs; +import java.util.Collection; import java.util.Comparator; import java.util.LinkedHashMap; import java.util.List; @@ -107,4 +108,14 @@ public interface DataSeries { } } + public static long maxValue(final Collection dataSeries) { + long result = 0; + + for (final DataSeries series : dataSeries) { + result = Math.max(result, series.getMaxValue()); + } + + return result; + } + } diff --git a/pdb-plotting/src/main/java/org/lucares/recommind/logs/GnuplotFileGenerator.java b/pdb-plotting/src/main/java/org/lucares/recommind/logs/GnuplotFileGenerator.java index 6b66c88..fe53212 100644 --- a/pdb-plotting/src/main/java/org/lucares/recommind/logs/GnuplotFileGenerator.java +++ b/pdb-plotting/src/main/java/org/lucares/recommind/logs/GnuplotFileGenerator.java @@ -1,11 +1,23 @@ package org.lucares.recommind.logs; +import static java.util.concurrent.TimeUnit.DAYS; +import static java.util.concurrent.TimeUnit.HOURS; +import static java.util.concurrent.TimeUnit.MINUTES; +import static java.util.concurrent.TimeUnit.SECONDS; + +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; +import java.util.List; +import java.util.concurrent.TimeUnit; import org.lucares.pdb.plot.api.AxisScale; public class GnuplotFileGenerator { + private static final int KEY_FONT_SIZE = 10; + private static final int TICKS_FONT_SIZE = 12; + public String generate(final GnuplotSettings settings, final Collection dataSeries) { final StringBuilder result = new StringBuilder(); @@ -14,6 +26,10 @@ public class GnuplotFileGenerator { settings.getHeight()); appendfln(result, "set datafile separator \"%s\"", settings.getDatafileSeparator()); + // appendln(result, "set locale \"de_DE.UTF-8\""); + // appendln(result, "set format y \"%.0f\""); + appendfln(result, "set tics font \",%d\"", TICKS_FONT_SIZE); + appendln(result, computeYTicks(settings, dataSeries)); settings.getAggregate().addGnuplotDefinitions(result, settings.getDatafileSeparator(), dataSeries); @@ -37,16 +53,12 @@ public class GnuplotFileGenerator { appendfln(result, "set yrange [\"" + graphOffset + "\":]"); } - appendfln(result, "set ylabel \"%s\"", settings.getYlabel()); switch (settings.getYAxisScale()) { case LINEAR: break; case LOG10: appendfln(result, "set logscale y"); break; - case LOG2: - appendfln(result, "set logscale y 2"); - break; } appendfln(result, "set grid"); @@ -62,7 +74,7 @@ public class GnuplotFileGenerator { if (settings.isKeyOutside()) { appendfln(result, "set key outside"); } - appendfln(result, "set key font \",10\""); + appendfln(result, "set key font \",%d\"", KEY_FONT_SIZE); if (!settings.isRenderLabels()) { appendfln(result, "set format x \"\""); @@ -82,7 +94,7 @@ public class GnuplotFileGenerator { // (horizontal: 1 unit = 10px; vertical: 1 unit = 19px) appendln(result, "set lmargin 11"); // margin 11 -> 110px appendln(result, "set rmargin 11"); // margin 11 -> 110px - appendln(result, "set tmargin 3"); // margin 3 -> 57px + appendln(result, "set tmargin 3"); // margin 3 -> 57px - marker (1) appendln(result, "set bmargin 4"); // margin 4 -> 76 } @@ -109,4 +121,158 @@ public class GnuplotFileGenerator { builder.append(String.format(format, args)); } + private String computeYTicks(final GnuplotSettings settings, final Collection dataSeries) { + String result = ""; + + final long yRangeMax; + final long yRangeMin; + if (settings.hasYRange()) { + yRangeMax = settings.getYRangeMax(); + yRangeMin = settings.getYRangeMin(); + } else { + yRangeMax = DataSeries.maxValue(dataSeries); + yRangeMin = 0; + } + final int height = settings.getHeight(); + + switch (settings.getYAxisScale()) { + case LINEAR: + result = computeLinearYTicks(height, yRangeMin, yRangeMax); + break; + case LOG10: + result = computeLog10YTicks(height, yRangeMin, yRangeMax); + break; + default: + // use the default + } + return result; + } + + private String computeLog10YTicks(final int height, final long yRangeMin, final long yRangeMax) { + final StringBuilder result = new StringBuilder(); + appendfln(result, "set ylabel \"%s\"", "Duration"); + + final List ticsLabels = Arrays.asList(// + "\"1ms\" 1", // + "\"2ms\" 2", // + "\"5ms\" 5", // + "\"10ms\" 10", // + "\"20ms\" 20", // + "\"50ms\" 50", // + "\"100ms\" 100", // + "\"200ms\" 200", // + "\"500ms\" 500", // + "\"1s\" 1000", // + "\"2s\" 2000", // + "\"5s\" 5000", // + "\"10s\" 10000", // + "\"30s\" 30000", // + "\"1m\" 60000", // + "\"2m\" 120000", // + "\"5m\" 300000", // + "\"10m\" 600000", // + "\"30m\" 1800000", // + "\"1h\" 3600000", // + "\"2h\" 7200000", // + "\"4h\" 14400000", // + "\"8h\" 28800000", // + "\"16h\" 57600000", // + "\"1d\" 86400000", // + "\"2d\" 172800000", // + "\"1 week\" 604800000", // + "\"2 week\" 1209600000.0", // + "\"4 week\" 2419200000.0" // + ); + + result.append("set ytics ("); + result.append(String.join(", ", ticsLabels)); + result.append(")\n"); + + return result.toString(); + } + + private String computeLinearYTicks(final long height, final long yRangeMin, final long yRangeMax) { + final StringBuilder result = new StringBuilder(); + appendfln(result, "set ylabel \"%s\"", "Duration"); + + final long plotHeight = height - 133; // sum of top/bottom margin, see marker (1) + final long maxLabels = plotHeight / (TICKS_FONT_SIZE * 5); + System.out.println("labels: " + maxLabels); + + final long range = yRangeMax - yRangeMin; + final long msPerLabel = roundToLinearLabelSteps(range / maxLabels); + final long digits = (long) Math.ceil(Math.log10(msPerLabel)); + + System.out.println("range: " + range); + System.out.println("msPerLabel: " + msPerLabel); + System.out.println("digits: " + digits); + + final List ticsLabels = new ArrayList<>(); + for (long i = yRangeMin; i < yRangeMax; i += msPerLabel) { + ticsLabels.add("\"" + msToTic(i, msPerLabel) + "\" " + i); + } + ticsLabels.add("\"" + msToTic(yRangeMax, msPerLabel) + "\" " + yRangeMax); + + result.append("set ytics ("); + result.append(String.join(", ", ticsLabels)); + result.append(")\n"); + return result.toString(); + } + + private long roundToLinearLabelSteps(final long msPerLabel) { + final List steps = Arrays.asList(2L, 5L, 10L, 20L, 50L, 100L, 200L, 500L, 1000L, 2000L, 5000L, 10_000L, + 20_000L, MINUTES.toMillis(1), MINUTES.toMillis(2), MINUTES.toMillis(5), MINUTES.toMillis(10), + MINUTES.toMillis(15), MINUTES.toMillis(30), HOURS.toMillis(1), HOURS.toMillis(2), HOURS.toMillis(5), + HOURS.toMillis(10), HOURS.toMillis(12), DAYS.toMillis(1), DAYS.toMillis(2), DAYS.toMillis(5), + DAYS.toMillis(7)); + + for (final Long step : steps) { + if (msPerLabel < step) { + return step; + } + } + + return msPerLabel; + } + + private String msToTic(final long ms, final double msPerLabel) { + + if (ms < 1000) { + return ms + "ms"; + } else if (ms < MINUTES.toMillis(1)) { + if (msPerLabel % 1000 == 0) { + return String.format("%ds", ms / 1_000); + } else { + return String.format("%.1fs", ms / 1_000.0); + } + } else if (ms < TimeUnit.HOURS.toMillis(1)) { + + final long sec = (ms % MINUTES.toMillis(1)) / SECONDS.toMillis(1); + final long min = ms / MINUTES.toMillis(1); + if (msPerLabel % MINUTES.toMillis(1) == 0) { + return min + "m "; + } else { + return min + "m " + sec + "s"; + } + } else if (ms < DAYS.toMillis(1)) { + // ms is a multiple of 1 hour, see roundToLinearLabelSteps + final long hour = (ms % DAYS.toMillis(1)) / HOURS.toMillis(1); + final long min = (ms % HOURS.toMillis(1)) / MINUTES.toMillis(1); + final long sec = (ms % MINUTES.toMillis(1)) / SECONDS.toMillis(1); + + if (msPerLabel % MINUTES.toMillis(1) == 0) { + return hour + "h " + min + "m "; + } else if (msPerLabel % HOURS.toMillis(1) == 0) { + return hour + "h "; + } else { + return hour + "h " + min + "m " + sec + "s"; + } + } else { + // ms is a multiple of 1 day, see roundToLinearLabelSteps + final long day = ms / DAYS.toMillis(1); + final long hour = (ms % DAYS.toMillis(1)) / HOURS.toMillis(1); + + return day + "d " + hour + "h "; + } + } } diff --git a/pdb-plotting/src/main/java/org/lucares/recommind/logs/GnuplotSettings.java b/pdb-plotting/src/main/java/org/lucares/recommind/logs/GnuplotSettings.java index d7543b6..33e9973 100644 --- a/pdb-plotting/src/main/java/org/lucares/recommind/logs/GnuplotSettings.java +++ b/pdb-plotting/src/main/java/org/lucares/recommind/logs/GnuplotSettings.java @@ -14,9 +14,6 @@ public class GnuplotSettings { // set datafile separator private String datafileSeparator = ","; - // set ylabel - private String ylabel = "Duration in ms"; - // set output "datausage.png" private final Path output; @@ -81,14 +78,6 @@ public class GnuplotSettings { this.datafileSeparator = datafileSeparator; } - public String getYlabel() { - return ylabel; - } - - public void setYlabel(final String ylabel) { - this.ylabel = ylabel; - } - public Path getOutput() { return output; } diff --git a/pdb-ui/src/main/resources/resources/js/ui.js b/pdb-ui/src/main/resources/resources/js/ui.js index 5aef973..42c2bf6 100644 --- a/pdb-ui/src/main/resources/resources/js/ui.js +++ b/pdb-ui/src/main/resources/resources/js/ui.js @@ -770,8 +770,7 @@ Vue.component('search-bar', { @@ -811,7 +810,7 @@ Vue.component('search-bar', { - +