draw better dashboard images
Scaling big plots to small thumbnails results in bad images that barely show any details. We solve this by calling gnuplot a second time to generate the thumbnails. They don't have any labels and are rendered in the required size, so that not scaling is necessary. Thumbnails have to be requested explicitly, because it can be expensive to compute them.
This commit is contained in:
@@ -13,31 +13,29 @@ public class GnuplotFileGenerator {
|
||||
appendfln(result, "set terminal %s noenhanced size %d,%d", settings.getTerminal(), settings.getWidth(),
|
||||
settings.getHeight());
|
||||
|
||||
|
||||
|
||||
appendfln(result, "set datafile separator \"%s\"", settings.getDatafileSeparator());
|
||||
|
||||
|
||||
settings.getAggregate().addGnuplotDefinitions(result, settings.getDatafileSeparator(), dataSeries);
|
||||
|
||||
|
||||
appendfln(result, "set timefmt '%s'", settings.getTimefmt());
|
||||
|
||||
final XAxisSettings xAxis = settings.getxAxisSettings();
|
||||
if (xAxis.isxDataTime()){
|
||||
if (xAxis.isxDataTime()) {
|
||||
appendfln(result, "set xdata time");
|
||||
}
|
||||
appendfln(result, "set xtics nomirror rotate by %d", xAxis.getRotateXAxisLabel());
|
||||
appendfln(result, "set format x \"%s\"", xAxis.getFormatX());
|
||||
appendfln(result, "set xlabel \"%s\"", xAxis.getXlabel());
|
||||
appendfln(result, "set xtics nomirror rotate by %d", xAxis.getRotateXAxisLabel());
|
||||
appendfln(result, "set xrange [\"%s\":\"%s\"]", xAxis.getFrom(), xAxis.getTo());
|
||||
|
||||
|
||||
appendfln(result, "set x2label \"Percentile\"");
|
||||
appendln(result, "set format x2 \"%.0f%%\"");
|
||||
appendfln(result, "set x2tics");
|
||||
appendfln(result, "set x2range [\"0\":\"100\"]");
|
||||
|
||||
|
||||
final long graphOffset = settings.getYAxisScale() == AxisScale.LINEAR ? 0 : 1;
|
||||
appendfln(result, "set yrange [\""+graphOffset+"\":]");
|
||||
|
||||
appendfln(result, "set yrange [\"" + graphOffset + "\":]");
|
||||
|
||||
appendfln(result, "set ylabel \"%s\"", settings.getYlabel());
|
||||
switch (settings.getYAxisScale()) {
|
||||
case LINEAR:
|
||||
@@ -52,20 +50,33 @@ public class GnuplotFileGenerator {
|
||||
|
||||
appendfln(result, "set grid");
|
||||
appendfln(result, "set output \"%s\"", settings.getOutput().toAbsolutePath().toString().replace("\\", "/"));
|
||||
|
||||
|
||||
// marker lines that show which area will be zoomed
|
||||
final long minDate = Long.parseLong(settings.getxAxisSettings().getFrom());
|
||||
final long maxDate = Long.parseLong(settings.getxAxisSettings().getTo());
|
||||
appendfln(result, "set arrow from "+(minDate + (maxDate-minDate)*0.25)+","+graphOffset+" rto graph 0,1 lt 3 lc rgb \"#EEEEEE\" nohead");
|
||||
appendfln(result, "set arrow from "+(minDate + (maxDate-minDate)*0.75)+","+graphOffset+" rto graph 0,1 lc rgb \"#EEEEEE\" nohead");
|
||||
|
||||
if (settings.isKeyOutside()){
|
||||
appendfln(result, "set arrow from " + (minDate + (maxDate - minDate) * 0.25) + "," + graphOffset
|
||||
+ " rto graph 0,1 lt 3 lc rgb \"#EEEEEE\" nohead");
|
||||
appendfln(result, "set arrow from " + (minDate + (maxDate - minDate) * 0.75) + "," + graphOffset
|
||||
+ " rto graph 0,1 lc rgb \"#EEEEEE\" nohead");
|
||||
|
||||
if (settings.isKeyOutside()) {
|
||||
appendfln(result, "set key outside");
|
||||
}
|
||||
appendfln(result, "set key font \",10\"");
|
||||
|
||||
|
||||
if (!settings.isRenderLabels()) {
|
||||
appendfln(result, "set format x \"\"", xAxis.getFormatX());
|
||||
appendfln(result, "set xlabel \"\"", xAxis.getXlabel());
|
||||
appendfln(result, "set x2label \"\"");
|
||||
appendln(result, "set format x2 \"\"");
|
||||
|
||||
appendfln(result, "set ylabel \"\"", settings.getYlabel());
|
||||
appendln(result, "set format y \"\"");
|
||||
appendln(result, "set nokey");
|
||||
}
|
||||
|
||||
appendf(result, "plot ");
|
||||
|
||||
|
||||
for (final DataSeries dataSerie : dataSeries) {
|
||||
appendfln(result, dataSerie.getGnuplotPlotDefinition());
|
||||
}
|
||||
@@ -77,10 +88,9 @@ public class GnuplotFileGenerator {
|
||||
private void appendfln(final StringBuilder builder, final String format, final Object... args) {
|
||||
builder.append(String.format(format + "\n", args));
|
||||
}
|
||||
|
||||
|
||||
private void appendln(final StringBuilder builder, final String string) {
|
||||
builder.append(string+"\n");
|
||||
builder.append(string + "\n");
|
||||
}
|
||||
|
||||
private void appendf(final StringBuilder builder, final String format, final Object... args) {
|
||||
|
||||
@@ -11,11 +11,9 @@ public class GnuplotSettings {
|
||||
private int width = 1600;
|
||||
private String timefmt = "%s"; // time as unix epoch, but as double
|
||||
|
||||
|
||||
// set datafile separator
|
||||
private String datafileSeparator = ",";
|
||||
|
||||
|
||||
// set ylabel
|
||||
private String ylabel = "Duration in ms";
|
||||
|
||||
@@ -25,27 +23,22 @@ public class GnuplotSettings {
|
||||
private AxisScale yAxisScale;
|
||||
private AggregateHandler aggregate;
|
||||
private boolean keyOutside = false;
|
||||
|
||||
|
||||
private XAxisSettings xAxisSettings = new XAxisSettings();
|
||||
private boolean renderLabels = true;
|
||||
|
||||
public GnuplotSettings(final Path output) {
|
||||
this.output = output;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public XAxisSettings getxAxisSettings() {
|
||||
return xAxisSettings;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void setxAxisSettings(XAxisSettings xAxisSettings) {
|
||||
public void setxAxisSettings(final XAxisSettings xAxisSettings) {
|
||||
this.xAxisSettings = xAxisSettings;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public String getTerminal() {
|
||||
return terminal;
|
||||
}
|
||||
@@ -78,7 +71,6 @@ public class GnuplotSettings {
|
||||
this.timefmt = timefmt;
|
||||
}
|
||||
|
||||
|
||||
public String getDatafileSeparator() {
|
||||
return datafileSeparator;
|
||||
}
|
||||
@@ -107,15 +99,15 @@ public class GnuplotSettings {
|
||||
return yAxisScale;
|
||||
}
|
||||
|
||||
public void setAggregate(AggregateHandler aggregate) {
|
||||
public void setAggregate(final AggregateHandler aggregate) {
|
||||
this.aggregate = aggregate;
|
||||
}
|
||||
|
||||
|
||||
public AggregateHandler getAggregate() {
|
||||
return aggregate;
|
||||
}
|
||||
|
||||
public void setKeyOutside(boolean keyOutside) {
|
||||
|
||||
public void setKeyOutside(final boolean keyOutside) {
|
||||
this.keyOutside = keyOutside;
|
||||
}
|
||||
|
||||
@@ -123,6 +115,14 @@ public class GnuplotSettings {
|
||||
return keyOutside;
|
||||
}
|
||||
|
||||
public void renderLabels(final boolean renderLabels) {
|
||||
this.renderLabels = renderLabels;
|
||||
}
|
||||
|
||||
public boolean isRenderLabels() {
|
||||
return renderLabels;
|
||||
}
|
||||
|
||||
// plot 'sample.txt' using 1:2 title 'Bytes' with linespoints 2
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package org.lucares.recommind.logs;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
@@ -23,8 +21,6 @@ import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import org.lucares.pdb.api.Entry;
|
||||
import org.lucares.pdb.api.GroupResult;
|
||||
import org.lucares.pdb.api.Result;
|
||||
@@ -111,19 +107,36 @@ public class ScatterPlot implements ConcretePlotter {
|
||||
DataSeries.setColors(dataSeries);
|
||||
|
||||
final Path outputFile = Files.createTempFile(outputDir, "", ".png");
|
||||
final Gnuplot gnuplot = new Gnuplot(tmpBaseDir);
|
||||
final GnuplotSettings gnuplotSettings = new GnuplotSettings(outputFile);
|
||||
gnuplotSettings.setHeight(height);
|
||||
gnuplotSettings.setWidth(width);
|
||||
defineXAxis(gnuplotSettings, plotSettings.dateFrom(), plotSettings.dateTo());
|
||||
{
|
||||
final Gnuplot gnuplot = new Gnuplot(tmpBaseDir);
|
||||
final GnuplotSettings gnuplotSettings = new GnuplotSettings(outputFile);
|
||||
gnuplotSettings.setHeight(height);
|
||||
gnuplotSettings.setWidth(width);
|
||||
defineXAxis(gnuplotSettings, plotSettings.dateFrom(), plotSettings.dateTo());
|
||||
|
||||
gnuplotSettings.setYAxisScale(plotSettings.getYAxisScale());
|
||||
gnuplotSettings.setAggregate(plotSettings.getAggregate());
|
||||
gnuplotSettings.setKeyOutside(plotSettings.isKeyOutside());
|
||||
gnuplot.plot(gnuplotSettings, dataSeries);
|
||||
gnuplotSettings.setYAxisScale(plotSettings.getYAxisScale());
|
||||
gnuplotSettings.setAggregate(plotSettings.getAggregate());
|
||||
gnuplotSettings.setKeyOutside(plotSettings.isKeyOutside());
|
||||
gnuplot.plot(gnuplotSettings, dataSeries);
|
||||
}
|
||||
|
||||
final Path thumbnail = createOptionalThumbnail(outputFile, plotSettings.getThumbnailMaxWidth(),
|
||||
plotSettings.getThumbnailMaxHeight());
|
||||
final Path thumbnail;
|
||||
if (plotSettings.isGenerateThumbnail()) {
|
||||
thumbnail = Files.createTempFile(outputDir, "", ".png");
|
||||
final Gnuplot gnuplot = new Gnuplot(tmpBaseDir);
|
||||
final GnuplotSettings gnuplotSettings = new GnuplotSettings(thumbnail);
|
||||
gnuplotSettings.setHeight(plotSettings.getThumbnailMaxHeight());
|
||||
gnuplotSettings.setWidth(plotSettings.getThumbnailMaxWidth());
|
||||
defineXAxis(gnuplotSettings, plotSettings.dateFrom(), plotSettings.dateTo());
|
||||
|
||||
gnuplotSettings.setYAxisScale(plotSettings.getYAxisScale());
|
||||
gnuplotSettings.setAggregate(plotSettings.getAggregate());
|
||||
gnuplotSettings.setKeyOutside(false);
|
||||
gnuplotSettings.renderLabels(false);
|
||||
gnuplot.plot(gnuplotSettings, dataSeries);
|
||||
} else {
|
||||
thumbnail = null;
|
||||
}
|
||||
|
||||
return new PlotResult(outputFile, dataSeries, thumbnail);
|
||||
} catch (final InterruptedException e) {
|
||||
@@ -137,43 +150,6 @@ public class ScatterPlot implements ConcretePlotter {
|
||||
}
|
||||
}
|
||||
|
||||
private static BufferedImage resizeImage(final BufferedImage originalImage, final Integer img_width,
|
||||
final Integer img_height) {
|
||||
final int type = originalImage.getType() == 0 ? BufferedImage.TYPE_INT_ARGB : originalImage.getType();
|
||||
final BufferedImage resizedImage = new BufferedImage(img_width, img_height, type);
|
||||
final Graphics2D g = resizedImage.createGraphics();
|
||||
g.drawImage(originalImage, 0, 0, img_width, img_height, null);
|
||||
g.dispose();
|
||||
|
||||
return resizedImage;
|
||||
}
|
||||
|
||||
private Path createOptionalThumbnail(final Path originalImage, final int thumbnailMaxWidth,
|
||||
final int thumbnailMaxHeight) {
|
||||
|
||||
Path result;
|
||||
if (thumbnailMaxWidth > 0 && thumbnailMaxHeight > 0 && Files.exists(originalImage)) {
|
||||
try {
|
||||
final long start = System.nanoTime();
|
||||
final BufferedImage image = ImageIO.read(originalImage.toFile());
|
||||
|
||||
final BufferedImage thumbnail = resizeImage(image, thumbnailMaxWidth, thumbnailMaxHeight);
|
||||
final Path thumbnailPath = Files.createTempFile(outputDir, "", ".png");
|
||||
|
||||
ImageIO.write(thumbnail, "png", thumbnailPath.toFile());
|
||||
LOGGER.info("thumbnail creation: " + (System.nanoTime() - start) / 1_000_000.0 + "ms");
|
||||
result = thumbnailPath;
|
||||
} catch (final IOException | RuntimeException e) {
|
||||
LOGGER.warn("failed to scale image", e);
|
||||
result = null;
|
||||
}
|
||||
} else {
|
||||
result = null;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void defineXAxis(final GnuplotSettings gnuplotSettings, final OffsetDateTime minDate,
|
||||
final OffsetDateTime maxDate) {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user