Merge branch 'master' of i2pgit.org:i2p-hackers/i2p.i2p

This commit is contained in:
eyedeekay
2024-10-26 23:22:14 -04:00
25 changed files with 1357 additions and 1020 deletions

View File

@@ -352,7 +352,7 @@ public class Archive implements RrdUpdater<Archive> {
long startTime = getStartTime();
for (int i = 0; i < rows.get(); i++) {
long time = startTime + i * getArcStep();
writer.writeComment(Util.getDate(time) + " / " + time);
writer.writeComment(writer.formatTimestamp(time) + " / " + time);
writer.startTag("row");
for (Robin robin : robins) {
writer.writeTag("v", robin.getValue(i));

View File

@@ -406,39 +406,43 @@ public class FetchData {
public void exportXml(OutputStream outputStream) {
//No auto flush for XmlWriter, it will be flushed once, when export is finished
try (XmlWriter writer = new XmlWriter(outputStream, false)) {
writer.startTag("fetch_data");
writer.startTag("request");
writer.writeTag("file", request.getParentDb().getPath());
writer.writeComment(Util.getDate(request.getFetchStart()));
writer.writeTag("start", request.getFetchStart());
writer.writeComment(Util.getDate(request.getFetchEnd()));
writer.writeTag("end", request.getFetchEnd());
writer.writeTag("resolution", request.getResolution());
writer.writeTag("cf", request.getConsolFun());
writer.closeTag(); // request
writer.startTag("datasources");
for (String dsName : dsNames) {
writer.writeTag("name", dsName);
}
writer.closeTag(); // datasources
writer.startTag("data");
for (int i = 0; i < timestamps.length; i++) {
writer.startTag("row");
writer.writeComment(Util.getDate(timestamps[i]));
writer.writeTag("timestamp", timestamps[i]);
writer.startTag("values");
for (int j = 0; j < dsNames.length; j++) {
writer.writeTag("v", values[j][i]);
}
writer.closeTag(); // values
writer.closeTag(); // row
}
writer.closeTag(); // data
writer.closeTag(); // fetch_data
writer.flush();
exportXml(writer);
}
}
public void exportXml(XmlWriter writer) {
writer.startTag("fetch_data");
writer.startTag("request");
writer.writeTag("file", request.getParentDb().getPath());
writer.writeComment(request.getFetchStart());
writer.writeTag("start", request.getFetchStart());
writer.writeComment(request.getFetchEnd());
writer.writeTag("end", request.getFetchEnd());
writer.writeTag("resolution", request.getResolution());
writer.writeTag("cf", request.getConsolFun());
writer.closeTag(); // request
writer.startTag("datasources");
for (String dsName : dsNames) {
writer.writeTag("name", dsName);
}
writer.closeTag(); // datasources
writer.startTag("data");
for (int i = 0; i < timestamps.length; i++) {
writer.startTag("row");
writer.writeComment(timestamps[i]);
writer.writeTag("timestamp", timestamps[i]);
writer.startTag("values");
for (int j = 0; j < dsNames.length; j++) {
writer.writeTag("v", values[j][i]);
}
writer.closeTag(); // values
writer.closeTag(); // row
}
writer.closeTag(); // data
writer.closeTag(); // fetch_data
writer.flush();
}
/**
* Dumps fetch data to file in XML format.
*

View File

@@ -173,7 +173,7 @@ public class Header implements RrdUpdater<Header> {
writer.writeTag("version", RRDTOOL_VERSION3);
writer.writeComment("Seconds");
writer.writeTag("step", step.get());
writer.writeComment(Util.getDate(lastUpdateTime.get()));
writer.writeComment(lastUpdateTime.get());
writer.writeTag("lastupdate", lastUpdateTime.get());
}

View File

@@ -214,7 +214,7 @@ public abstract class RrdBackendFactory implements Closeable {
/**
* Return the current active factories as a stream.
* @return
* @return the Stream
* @since 3.7
*/
public static synchronized Stream<RrdBackendFactory> getActiveFactories() {

View File

@@ -212,7 +212,7 @@ public class RrdDb implements RrdUpdater<RrdDb>, Closeable {
/**
* Internal method used to memorize the pool, without generating a loop
* @param pool
* @return
* @return the Builder
*/
Builder setPoolInternal(RrdDbPool pool) {
this.pool = pool;
@@ -1150,15 +1150,14 @@ public class RrdDb implements RrdUpdater<RrdDb>, Closeable {
}
/**
* Writes the RRD content to OutputStream using XML format. This format
* Writes the RRD content to {@link XmlWriter} using XML format. This format
* is fully compatible with RRDTool's XML dump format and can be used for conversion
* purposes or debugging.
*
* @param destination Output stream to receive XML data
* @param writer {@link XmlWriter} to receive XML data
* @throws java.io.IOException Thrown in case of I/O related error
*/
public synchronized void dumpXml(OutputStream destination) throws IOException {
XmlWriter writer = new XmlWriter(destination);
public synchronized void dumpXml(XmlWriter writer) throws IOException {
writer.startTag("rrd");
// dump header
header.appendXml(writer);
@@ -1174,6 +1173,18 @@ public class RrdDb implements RrdUpdater<RrdDb>, Closeable {
writer.flush();
}
/**
* Writes the RRD content to {@link OutputStream} using XML format. This format
* is fully compatible with RRDTool's XML dump format and can be used for conversion
* purposes or debugging.
*
* @param destination Output stream to receive XML data
* @throws java.io.IOException Thrown in case of I/O related error
*/
public synchronized void dumpXml(OutputStream destination) throws IOException {
dumpXml(new XmlWriter(destination));
}
/**
* This method is just an alias for {@link #dumpXml(OutputStream) dumpXml} method.
*
@@ -1193,9 +1204,10 @@ public class RrdDb implements RrdUpdater<RrdDb>, Closeable {
* @throws java.io.IOException Thrown in case of I/O related error
*/
public synchronized String getXml() throws IOException {
ByteArrayOutputStream destination = new ByteArrayOutputStream(XML_BUFFER_CAPACITY);
dumpXml(destination);
return destination.toString();
try (ByteArrayOutputStream destination = new ByteArrayOutputStream(XML_BUFFER_CAPACITY)) {
dumpXml(destination);
return destination.toString();
}
}
/**

View File

@@ -633,38 +633,52 @@ public class RrdDef {
*/
public void exportXmlTemplate(OutputStream out, boolean compatible) {
try (XmlWriter xml = new XmlWriter(out)) {
xml.startTag("rrd_def");
if (compatible) {
xml.writeTag("path", getPath());
} else {
xml.writeTag("uri", getUri());
}
xml.writeTag("step", getStep());
xml.writeTag("start", getStartTime());
// datasources
DsDef[] dsDefs = getDsDefs();
for (DsDef dsDef : dsDefs) {
xml.startTag("datasource");
xml.writeTag("name", dsDef.getDsName());
xml.writeTag("type", dsDef.getDsType());
xml.writeTag("heartbeat", dsDef.getHeartbeat());
xml.writeTag("min", dsDef.getMinValue(), "U");
xml.writeTag("max", dsDef.getMaxValue(), "U");
xml.closeTag(); // datasource
}
ArcDef[] arcDefs = getArcDefs();
for (ArcDef arcDef : arcDefs) {
xml.startTag("archive");
xml.writeTag("cf", arcDef.getConsolFun());
xml.writeTag("xff", arcDef.getXff());
xml.writeTag("steps", arcDef.getSteps());
xml.writeTag("rows", arcDef.getRows());
xml.closeTag(); // archive
}
xml.closeTag(); // rrd_def
exportXmlTemplate(xml, compatible);
}
}
/**
* Exports RrdDef object to output stream in XML format. Generated XML code can be parsed
* with {@link org.rrd4j.core.RrdDefTemplate} class.
* <p>If <code>compatible</code> is set to true, it returns an XML compatible with previous RRD4J's versions, using
* a path, instead of an URI.</p>
*
* @param xml XML writer
* @param compatible Compatible with previous versions.
*/
public void exportXmlTemplate(XmlWriter xml, boolean compatible) {
xml.startTag("rrd_def");
if (compatible) {
xml.writeTag("path", getPath());
} else {
xml.writeTag("uri", getUri());
}
xml.writeTag("step", getStep());
xml.writeTag("start", getStartTime());
// datasources
DsDef[] dsDefs = getDsDefs();
for (DsDef dsDef : dsDefs) {
xml.startTag("datasource");
xml.writeTag("name", dsDef.getDsName());
xml.writeTag("type", dsDef.getDsType());
xml.writeTag("heartbeat", dsDef.getHeartbeat());
xml.writeTag("min", dsDef.getMinValue(), "U");
xml.writeTag("max", dsDef.getMaxValue(), "U");
xml.closeTag(); // datasource
}
ArcDef[] arcDefs = getArcDefs();
for (ArcDef arcDef : arcDefs) {
xml.startTag("archive");
xml.writeTag("cf", arcDef.getConsolFun());
xml.writeTag("xff", arcDef.getXff());
xml.writeTag("steps", arcDef.getSteps());
xml.writeTag("rows", arcDef.getRows());
xml.closeTag(); // archive
}
xml.closeTag(); // rrd_def
xml.flush();
}
/**
* <p>Exports RrdDef object to string in XML format. Generated XML string can be parsed
* with {@link org.rrd4j.core.RrdDefTemplate} class.</p>

View File

@@ -1,16 +1,16 @@
package org.rrd4j.core;
import java.awt.Color;
import java.awt.Font;
import java.awt.*;
import java.io.File;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Deque;
import java.util.LinkedList;
import java.util.Locale;
import java.util.TimeZone;
/**
* Extremely simple utility class used to create XML documents.
@@ -18,22 +18,37 @@ import java.util.TimeZone;
public class XmlWriter implements AutoCloseable {
static final String INDENT_STR = " ";
private static final String STYLE = "style";
private static final SimpleDateFormat ISOLIKE = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSZ", Locale.ENGLISH);
static {
ISOLIKE.setTimeZone(TimeZone.getTimeZone("UTC"));
private static final DateTimeFormatter ISOLIKE = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSZ")
.withLocale(Locale.ENGLISH)
.withZone(ZoneId.of("UTC"));
private static final String DEFAULT_NAN_STRING = Double.toString(Double.NaN);
@FunctionalInterface
public interface DoubleFormater {
String format(double value, String nanString);
}
private final PrintWriter writer;
private final StringBuilder indent = new StringBuilder();
private final Deque<String> openTags = new LinkedList<>();
private final DateTimeFormatter timeFormatter;
private final DoubleFormater doubleFormatter;
private XmlWriter(PrintWriter writer, DateTimeFormatter timeFormatter, DoubleFormater doubleFormatter) {
this.writer = writer;
this.timeFormatter = timeFormatter;
this.doubleFormatter = doubleFormatter;
}
/**
* Creates XmlWriter with the specified output stream to send XML code to.
* Creates XmlWriter with the specified {@link OutputStream} to send XML code to.
*
* @param stream Output stream which receives XML code
* @param stream {@link OutputStream} which receives XML code
*/
public XmlWriter(OutputStream stream) {
writer = new PrintWriter(stream, true);
timeFormatter = ISOLIKE;
this.doubleFormatter = (d, n) -> Util.formatDouble(d, n,true);
}
/**
@@ -44,6 +59,44 @@ public class XmlWriter implements AutoCloseable {
*/
public XmlWriter(OutputStream stream, boolean autoFlush) {
writer = new PrintWriter(stream, autoFlush);
timeFormatter = ISOLIKE;
this.doubleFormatter = (d, n) -> Util.formatDouble(d, n,true);
}
/**
* Creates XmlWriter with the specified {@link PrintWriter} to send XML code to.
*
* @param stream {@link PrintWriter} which receives XML code
*/
public XmlWriter(PrintWriter stream) {
writer = stream;
timeFormatter = ISOLIKE;
this.doubleFormatter = (d, n) -> Util.formatDouble(d, n,true);
}
/**
* Return a new {@link XmlWriter} that will format time stamp as ISO 8601 with this explicit time zone {@link ZoneId}
* @param zid
* @return the XmlWriter
*/
public XmlWriter withTimeZone(ZoneId zid) {
if (indent.length() != 0 || !openTags.isEmpty()) {
throw new IllegalStateException("Can't be used on a already used XmlWriter");
}
DateTimeFormatter dtf = this.timeFormatter.withZone(zid);
return new XmlWriter(writer, dtf, doubleFormatter);
}
/**
* Return a new {@link XmlWriter} that will format time stamp using this {@link ZoneId}
* @param doubleFormatter
* @return the XmlWriter
*/
public XmlWriter withDoubleFormatter(DoubleFormater doubleFormatter) {
if (indent.length() != 0 || !openTags.isEmpty()) {
throw new IllegalStateException("Can't be used on a already used XmlWriter");
}
return new XmlWriter(writer, timeFormatter, doubleFormatter);
}
/**
@@ -110,7 +163,7 @@ public class XmlWriter implements AutoCloseable {
* @param nanString a {@link java.lang.String} object.
*/
public void writeTag(String tag, double value, String nanString) {
writeTag(tag, Util.formatDouble(value, nanString, true));
writeTag(tag, doubleFormatter.format(value, nanString));
}
/**
@@ -120,7 +173,7 @@ public class XmlWriter implements AutoCloseable {
* @param value value to be placed between <code>&lt;tag&gt;</code> and <code>&lt;/tag&gt;</code>
*/
public void writeTag(String tag, double value) {
writeTag(tag, Util.formatDouble(value, true));
writeTag(tag, doubleFormatter.format(value, DEFAULT_NAN_STRING));
}
/**
@@ -193,14 +246,28 @@ public class XmlWriter implements AutoCloseable {
* @param comment comment string
*/
public void writeComment(Object comment) {
if (comment instanceof Date) {
synchronized (ISOLIKE) {
comment = ISOLIKE.format((Date) comment);
}
}
writer.println(indent + "<!-- " + escape(comment.toString()) + " -->");
}
/**
* Writes a timestamp using the configured {@link DateTimeFormatter} as an XML comment to output stream
*
* @param timestamp
*/
public void writeComment(long timestamp) {
writer.println(indent + "<!-- " + escape(formatTimestamp(timestamp)) + " -->");
}
/**
* Format a timestamp using the configured {@link DateTimeFormatter}
*
* @param timestamp
* @return the formatted timestamp
*/
public String formatTimestamp(long timestamp) {
return timeFormatter.format(Instant.ofEpochSecond(timestamp));
}
private static String escape(String s) {
return s.replace("<", "&lt;").replace(">", "&gt;");
}

View File

@@ -33,7 +33,7 @@ abstract class Source {
/**
* @param tStart
* @param tEnd
* @return
* @return the Aggregates
* @deprecated This method is deprecated. Uses instance of {@link org.rrd4j.data.Variable}, used with {@link org.rrd4j.data.DataProcessor#addDatasource(String, String, Variable)}
*/
@Deprecated
@@ -46,7 +46,7 @@ abstract class Source {
* @param tStart
* @param tEnd
* @param percentile
* @return
* @return the percentile
* @deprecated This method is deprecated. Uses instance of {@link org.rrd4j.data.Variable.PERCENTILE}, used with {@link org.rrd4j.data.DataProcessor#addDatasource(String, String, Variable)}
*/
@Deprecated

View File

@@ -0,0 +1,25 @@
package org.rrd4j.graph;
class FindUnit {
private static final char UNIT_UNKNOWN = '?';
private static final char[] UNIT_SYMBOLS = {'y', 'z', 'a', 'f', 'p', 'n', 'µ', 'm', ' ', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'};
private static final int SYMBOLS_CENTER = 8;
private FindUnit() {
}
public static char resolveSymbol(double digits) {
if (Double.isNaN(digits)) {
return UNIT_UNKNOWN;
} else {
if (((digits + SYMBOLS_CENTER) < UNIT_SYMBOLS.length) && ((digits + SYMBOLS_CENTER) >= 0)) {
return UNIT_SYMBOLS[(int) digits + SYMBOLS_CENTER];
} else {
return UNIT_UNKNOWN;
}
}
}
}

View File

@@ -1,5 +1,7 @@
package org.rrd4j.graph;
import java.util.function.DoubleUnaryOperator;
class ImageParameters {
long start, end;
double minval, maxval;
@@ -18,5 +20,5 @@ class ImageParameters {
int yorigin;
int unitslength;
int xgif, ygif;
String unit;
DoubleUnaryOperator log;
}

View File

@@ -15,16 +15,16 @@ class LegendComposer implements RrdGraphConstants {
private final double smallLeading;
private final double boxSpace;
LegendComposer(RrdGraph rrdGraph, int legX, int legY, int legWidth) {
this.gdef = rrdGraph.gdef;
this.worker = rrdGraph.worker;
LegendComposer(RrdGraphGenerator generator, int legX, int legY, int legWidth) {
this.gdef = generator.gdef;
this.worker = generator.worker;
this.legX = legX;
this.legY = legY;
this.legWidth = legWidth;
interLegendSpace = rrdGraph.getInterlegendSpace();
leading = rrdGraph.getLeading();
smallLeading = rrdGraph.getSmallLeading();
boxSpace = rrdGraph.getBoxSpace();
interLegendSpace = generator.getInterlegendSpace();
leading = generator.getLeading();
smallLeading = generator.getSmallLeading();
boxSpace = generator.getBoxSpace();
}
int placeComments() {

View File

@@ -0,0 +1,57 @@
package org.rrd4j.graph;
import java.util.function.DoubleUnaryOperator;
class LogService {
static DoubleUnaryOperator resolve(ImageParameters im) {
boolean sameSign = Math.signum(im.minval) == Math.signum(im.maxval);
double absMinVal = Math.min(Math.abs(im.minval), Math.abs(im.maxval));
double absMaxVal = Math.max(Math.abs(im.minval), Math.abs(im.maxval));
if (! sameSign) {
return LogService::log10;
} else if (absMinVal == 0 && absMaxVal < 1) {
double correction = 1.0 / absMaxVal;
return v -> log10(v, correction, absMinVal);
} else if (absMinVal == 0) {
return LogService::log10;
} else if (absMinVal < 1) {
double correction = 1.0 / absMinVal;
return v -> log10(v, correction, absMinVal);
} else {
return LogService::log10;
}
}
private LogService() {
}
/**
* Compute logarithm for the purposes of y-axis.
*/
private static double log10(double v, double correction, double minval) {
if (v == minval) {
return 0.0;
} else {
double lv = Math.log10(Math.abs(v) * correction);
if (lv < 0 || Double.isNaN(lv)) {
// Don't cross the sign line, round to 0 if that's the case
return 0.0;
} else {
return Math.copySign(lv, v);
}
}
}
private static double log10(double v) {
double lv = Math.log10(Math.abs(v));
if (lv < 0) {
// Don't cross the sign line, round to 0 if that's the case
return 0.0;
} else {
return Math.copySign(lv, v);
}
}
}

View File

@@ -5,18 +5,6 @@ class Mapper {
private final ImageParameters im;
private final double pixieX, pixieY;
Mapper(RrdGraph rrdGraph) {
this.gdef = rrdGraph.gdef;
this.im = rrdGraph.im;
pixieX = (double) im.xsize / (double) (im.end - im.start);
if (!gdef.logarithmic) {
pixieY = im.ysize / (im.maxval - im.minval);
}
else {
pixieY = im.ysize / (ValueAxisLogarithmic.log10(im.maxval) - ValueAxisLogarithmic.log10(im.minval));
}
}
Mapper(RrdGraphDef gdef, ImageParameters im) {
this.gdef = gdef;
this.im = im;
@@ -25,7 +13,7 @@ class Mapper {
pixieY = im.ysize / (im.maxval - im.minval);
}
else {
pixieY = im.ysize / (Math.log10(im.maxval) - Math.log10(im.minval));
pixieY = im.ysize / (im.log.applyAsDouble(im.maxval) - im.log.applyAsDouble(im.minval));
}
}
@@ -43,7 +31,7 @@ class Mapper {
yval = im.yorigin;
}
else {
yval = im.yorigin - pixieY * (ValueAxisLogarithmic.log10(value) - ValueAxisLogarithmic.log10(im.minval)) + 0.5;
yval = im.yorigin - pixieY * (im.log.applyAsDouble(value) - im.log.applyAsDouble(im.minval)) + 0.5;
}
}
if (!gdef.rigid) {

View File

@@ -1,51 +1,23 @@
package org.rrd4j.graph;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Paint;
import java.awt.Stroke;
import java.awt.Transparency;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.function.Supplier;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.swing.ImageIcon;
import org.rrd4j.core.Util;
import org.rrd4j.data.DataProcessor;
import org.rrd4j.graph.DownSampler.DataSet;
/**
* Class which actually creates Rrd4j graphs (does the hard work).
*/
public class RrdGraph implements RrdGraphConstants {
private static final double[] SENSIBLE_VALUES = {
1000.0, 900.0, 800.0, 750.0, 700.0,
600.0, 500.0, 400.0, 300.0, 250.0,
200.0, 125.0, 100.0, 90.0, 80.0,
75.0, 70.0, 60.0, 50.0, 40.0, 30.0,
25.0, 20.0, 10.0, 9.0, 8.0,
7.0, 6.0, 5.0, 4.0, 3.5, 3.0,
2.5, 2.0, 1.8, 1.5, 1.2, 1.0,
0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.0, -1
};
private static final int SYMBOLS_CENTER = 8;
private static final char[] SYMBOLS = {'y', 'z', 'a', 'f', 'p', 'n', 'µ', 'm', ' ', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'};
final RrdGraphDef gdef;
final ImageParameters im;
private DataProcessor dproc;
ImageWorker worker;
Mapper mapper;
private final RrdGraphInfo info = new RrdGraphInfo();
private final String signature;
private final RrdGraphInfo info;
/**
* Creates graph from the corresponding {@link org.rrd4j.graph.RrdGraphDef} object.
@@ -54,19 +26,7 @@ public class RrdGraph implements RrdGraphConstants {
* @throws java.io.IOException Thrown in case of I/O error
*/
public RrdGraph(RrdGraphDef gdef) throws IOException {
this.gdef = gdef;
signature = gdef.getSignature();
im = new ImageParameters();
worker = BufferedImageWorker.getBuilder().setGdef(gdef).build();
try {
createGraph();
}
finally {
worker.dispose();
worker = null;
dproc = null;
}
this(gdef, () -> RrdGraph.generateImageWorker(gdef));
}
/**
@@ -76,21 +36,9 @@ public class RrdGraph implements RrdGraphConstants {
* @throws IOException
*/
public RrdGraph(RrdGraphDef gdef, ImageWorker worker) throws IOException {
this.gdef = gdef;
signature = gdef.getSignature();
im = new ImageParameters();
this.worker = worker;
try {
createGraph();
}
finally {
worker.dispose();
this.worker = null;
dproc = null;
}
this(gdef, () -> worker);
}
/**
* <p>Creates graph from the corresponding {@link org.rrd4j.graph.RrdGraphDef} object.</p>
* <p>The graph will be created using customs {@link javax.imageio.ImageWriter} and {@link javax.imageio.ImageWriteParam} given.</p>
@@ -103,18 +51,28 @@ public class RrdGraph implements RrdGraphConstants {
* @since 3.5
*/
public RrdGraph(RrdGraphDef gdef, ImageWriter writer, ImageWriteParam param) throws IOException {
this(gdef, () -> RrdGraph.generateImageWorker(gdef, writer, param));
}
private RrdGraph(RrdGraphDef gdef, Supplier<ImageWorker> worker) throws IOException {
this.gdef = gdef;
signature = gdef.getSignature();
im = new ImageParameters();
worker = BufferedImageWorker.getBuilder().setGdef(gdef).setWriter(writer).setImageWriteParam(param).build();
RrdGraphGenerator generator = new RrdGraphGenerator(gdef, worker.get(), new DataProcessor(gdef.startTime, gdef.endTime));
try {
createGraph();
generator.createGraph();
}
finally {
worker.dispose();
worker = null;
dproc = null;
generator.worker.dispose();
}
info = generator.info;
im = generator.im;
}
private static ImageWorker generateImageWorker(RrdGraphDef gdef, ImageWriter writer, ImageWriteParam param) {
return BufferedImageWorker.getBuilder().setGdef(gdef).setWriter(writer).setImageWriteParam(param).build();
}
private static ImageWorker generateImageWorker(RrdGraphDef gdef) {
return BufferedImageWorker.getBuilder().setGdef(gdef).build();
}
/**
@@ -126,704 +84,6 @@ public class RrdGraph implements RrdGraphConstants {
return info;
}
private void createGraph() throws IOException {
boolean lazy = lazyCheck();
if (!lazy || gdef.printStatementCount() != 0) {
fetchData();
resolveTextElements();
if (gdef.shouldPlot() && !lazy) {
initializeLimits();
calculatePlotValues();
findMinMaxValues();
identifySiUnit();
expandValueRange();
removeOutOfRangeRules();
removeOutOfRangeSpans();
mapper = new Mapper(this);
placeLegends();
createImageWorker();
drawBackground();
drawData();
drawGrid();
drawAxis();
drawText();
drawLegend();
drawRulesAndSpans();
gator();
drawOverlay();
saveImage();
}
}
collectInfo();
}
private void collectInfo() {
info.filename = gdef.filename;
info.width = im.xgif;
info.height = im.ygif;
for (CommentText comment : gdef.comments) {
if (comment instanceof PrintText) {
PrintText pt = (PrintText) comment;
if (pt.isPrint()) {
info.addPrintLine(pt.resolvedText);
}
}
}
if (gdef.imageInfo != null) {
info.imgInfo = Util.sprintf(gdef.locale, gdef.imageInfo, gdef.filename, im.xgif, im.ygif);
}
}
private void saveImage() throws IOException {
if (! RrdGraphConstants.IN_MEMORY_IMAGE.equals(gdef.filename)) {
Path imgpath = Paths.get(gdef.filename);
worker.saveImage(gdef.filename);
info.bytesSource = () -> {
try {
return Files.readAllBytes(imgpath);
} catch (IOException e) {
throw new IllegalStateException("Unable to read image bytes", e);
}
};
info.bytesCount = () -> {
try {
return (int) Files.size(imgpath);
} catch (IOException e) {
throw new IllegalStateException("Unable to read image informations", e);
}
};
}
else {
byte[] content = worker.getImageBytes();
info.bytesSource = () -> Arrays.copyOf(content, content.length);
info.bytesCount = () -> content.length;
}
}
private void drawOverlay() throws IOException {
if (gdef.overlayImage != null) {
worker.loadImage(gdef.overlayImage, 0, 0, im.xgif, im.ygif);
}
}
private void gator() {
if (!gdef.onlyGraph && gdef.showSignature) {
worker.setTextAntiAliasing(gdef.textAntiAliasing);
Font font = gdef.getFont(FONTTAG_WATERMARK);
int x = (int) (im.xgif - 2 - worker.getFontAscent(font));
int y = 4;
worker.transform(x, y, Math.PI / 2);
worker.drawString(signature, 0, 0, font, Color.LIGHT_GRAY);
worker.reset();
worker.setTextAntiAliasing(false);
}
}
private void drawRulesAndSpans() {
boolean found = false;
for (PlotElement pe : gdef.plotElements) {
if (pe instanceof HRule) {
HRule hr = (HRule) pe;
if (hr.value >= im.minval && hr.value <= im.maxval) {
int y = mapper.ytr(hr.value);
if (!found) {
worker.clip(im.xorigin + 1, im.yorigin - gdef.height - 1, gdef.width - 1, gdef.height + 2);
found = true;
}
worker.drawLine(im.xorigin, y, im.xorigin + im.xsize, y, hr.color, hr.stroke);
}
}
else if (pe instanceof VRule) {
VRule vr = (VRule) pe;
if (vr.timestamp >= im.start && vr.timestamp <= im.end) {
int x = mapper.xtr(vr.timestamp);
if (!found) {
worker.clip(im.xorigin + 1, im.yorigin - gdef.height - 1, gdef.width - 1, gdef.height + 2);
found = true;
}
worker.drawLine(x, im.yorigin, x, im.yorigin - im.ysize, vr.color, vr.stroke);
}
}
else if (pe instanceof HSpan) {
HSpan hr = (HSpan) pe;
int ys = mapper.ytr(hr.start);
int ye = mapper.ytr(hr.end);
int height = ys - ye;
if (!found) {
worker.clip(im.xorigin + 1, im.yorigin - gdef.height - 1, gdef.width - 1, gdef.height + 2);
found = true;
}
worker.fillRect(im.xorigin, ys - height, im.xsize, height, hr.color);
}
else if (pe instanceof VSpan) {
VSpan vr = (VSpan) pe;
int xs = mapper.xtr(vr.start);
int xe = mapper.xtr(vr.end);
if (!found) {
worker.clip(im.xorigin + 1, im.yorigin - gdef.height - 1, gdef.width - 1, gdef.height + 2);
found = true;
}
worker.fillRect(xs, im.yorigin - im.ysize, xe - xs, im.ysize, vr.color);
}
}
if (found)
worker.reset();
}
private void drawText() {
if (!gdef.onlyGraph) {
worker.setTextAntiAliasing(gdef.textAntiAliasing);
if (gdef.title != null) {
// I2P truncate on the right only
//int x = im.xgif / 2 - (int) (worker.getStringWidth(gdef.title, gdef.getFont(FONTTAG_TITLE)) / 2);
int x = Math.max(2, im.xgif / 2 - (int) (worker.getStringWidth(gdef.title, gdef.getFont(FONTTAG_TITLE)) / 2));
// I2P a little less padding on top and more on the bottom
//int y = PADDING_TOP + (int) worker.getFontAscent(gdef.getFont(FONTTAG_TITLE));
int y = PADDING_TOP * 2 / 3 + (int) worker.getFontAscent(gdef.getFont(FONTTAG_TITLE));
worker.drawString(gdef.title, x, y, gdef.getFont(FONTTAG_TITLE), gdef.getColor(ElementsNames.font));
}
if (gdef.verticalLabel != null) {
int y = im.yorigin - im.ysize / 2 + (int) worker.getStringWidth(gdef.verticalLabel, gdef.getFont(FONTTAG_UNIT)) / 2;
int ascent = (int) worker.getFontAscent(gdef.getFont(FONTTAG_UNIT));
worker.transform(PADDING_LEFT, y, -Math.PI / 2);
worker.drawString(gdef.verticalLabel, 0, ascent, gdef.getFont(FONTTAG_UNIT), gdef.getColor(ElementsNames.font));
worker.reset();
}
worker.setTextAntiAliasing(false);
}
}
private void drawGrid() {
if (!gdef.onlyGraph) {
worker.setTextAntiAliasing(gdef.textAntiAliasing);
Paint shade1 = gdef.getColor(ElementsNames.shadea);
Paint shade2 = gdef.getColor(ElementsNames.shadeb);
Stroke borderStroke = new BasicStroke(1);
worker.drawLine(0, 0, im.xgif - 1, 0, shade1, borderStroke);
worker.drawLine(1, 1, im.xgif - 2, 1, shade1, borderStroke);
worker.drawLine(0, 0, 0, im.ygif - 1, shade1, borderStroke);
worker.drawLine(1, 1, 1, im.ygif - 2, shade1, borderStroke);
worker.drawLine(im.xgif - 1, 0, im.xgif - 1, im.ygif - 1, shade2, borderStroke);
worker.drawLine(0, im.ygif - 1, im.xgif - 1, im.ygif - 1, shade2, borderStroke);
worker.drawLine(im.xgif - 2, 1, im.xgif - 2, im.ygif - 2, shade2, borderStroke);
worker.drawLine(1, im.ygif - 2, im.xgif - 2, im.ygif - 2, shade2, borderStroke);
if (gdef.drawXGrid) {
new TimeAxis(this).draw();
}
if (gdef.drawYGrid) {
boolean ok;
if (gdef.altYMrtg) {
ok = new ValueAxisMrtg(this).draw();
}
else if (gdef.logarithmic) {
ok = new ValueAxisLogarithmic(this).draw();
}
else {
ok = new ValueAxis(this).draw();
}
if (!ok) {
String msg = "No Data Found";
worker.drawString(msg,
im.xgif / 2 - (int) worker.getStringWidth(msg, gdef.getFont(FONTTAG_TITLE)) / 2,
(2 * im.yorigin - im.ysize) / 2,
gdef.getFont(FONTTAG_TITLE), gdef.getColor(ElementsNames.font));
}
}
worker.setTextAntiAliasing(false);
}
}
private void drawData() {
worker.setAntiAliasing(gdef.antiAliasing);
worker.clip(im.xorigin, im.yorigin - gdef.height - 1, gdef.width, gdef.height + 2);
double areazero = mapper.ytr((im.minval > 0.0) ? im.minval : (im.maxval < 0.0) ? im.maxval : 0.0);
double[] x = gdef.downsampler == null ? xtr(dproc.getTimestamps()) : null;
double[] lastY = null;
// draw line, area and stack
for (PlotElement plotElement : gdef.plotElements) {
if (plotElement instanceof SourcedPlotElement) {
SourcedPlotElement source = (SourcedPlotElement) plotElement;
double[] y;
if (gdef.downsampler != null) {
DataSet set = gdef.downsampler.downsize(dproc.getTimestamps(), source.getValues());
x = xtr(set.timestamps);
y = ytr(set.values);
} else {
y = ytr(source.getValues());
}
if (Line.class.isAssignableFrom(source.getClass())) {
worker.drawPolyline(x, y, source.color, ((Line)source).stroke );
}
else if (Area.class.isAssignableFrom(source.getClass())) {
if(source.parent == null) {
worker.fillPolygon(x, areazero, y, source.color);
}
else {
worker.fillPolygon(x, lastY, y, source.color);
worker.drawPolyline(x, lastY, source.getParentColor(), new BasicStroke(0));
}
}
else if (source instanceof Stack) {
Stack stack = (Stack) source;
float width = stack.getParentLineWidth();
if (width >= 0F) {
// line
worker.drawPolyline(x, y, stack.color, new BasicStroke(width));
}
else {
// area
worker.fillPolygon(x, lastY, y, stack.color);
worker.drawPolyline(x, lastY, stack.getParentColor(), new BasicStroke(0));
}
}
else {
// should not be here
throw new IllegalStateException("Unknown plot source: " + source.getClass().getName());
}
lastY = y;
}
}
worker.reset();
worker.setAntiAliasing(false);
}
private void drawAxis() {
if (!gdef.onlyGraph) {
Paint gridColor = gdef.getColor(ElementsNames.grid);
Paint xaxisColor = gdef.getColor(ElementsNames.xaxis);
Paint yaxisColor = gdef.getColor(ElementsNames.yaxis);
Paint arrowColor = gdef.getColor(ElementsNames.arrow);
Stroke stroke = new BasicStroke(1);
worker.drawLine(im.xorigin + im.xsize, im.yorigin, im.xorigin + im.xsize, im.yorigin - im.ysize,
gridColor, stroke);
worker.drawLine(im.xorigin, im.yorigin - im.ysize, im.xorigin + im.xsize, im.yorigin - im.ysize,
gridColor, stroke);
worker.drawLine(im.xorigin - 4, im.yorigin, im.xorigin + im.xsize + 4, im.yorigin,
xaxisColor, stroke);
worker.drawLine(im.xorigin, im.yorigin + 4, im.xorigin, im.yorigin - im.ysize - 4,
yaxisColor, stroke);
// I2P skip arrowheads if transparent
if (((Color)arrowColor).getAlpha() == 0)
return;
//Do X axis arrow
double[] Xarrow_x = {
im.xorigin + im.xsize + 4,
im.xorigin + im.xsize + 9,
im.xorigin + im.xsize + 4,
};
double[] Xarrow_y = {
im.yorigin - 3, im.yorigin,
im.yorigin + 3,
};
worker.fillPolygon(Xarrow_x, im.yorigin + 3.0, Xarrow_y, arrowColor);
//Do y axis arrow
double[] Yarrow_x = {
im.xorigin - 3,
im.xorigin,
im.xorigin + 3,
};
double[] Yarrow_y = {
im.yorigin - im.ysize - 4,
im.yorigin - im.ysize - 9,
im.yorigin - im.ysize - 4,
};
worker.fillPolygon(Yarrow_x, im.yorigin - im.ysize - 4.0, Yarrow_y, arrowColor);
}
}
private void drawBackground() throws IOException {
worker.fillRect(0, 0, im.xgif, im.ygif, gdef.getColor(ElementsNames.back));
if (gdef.backgroundImage != null) {
worker.loadImage(gdef.backgroundImage, 0, 0, im.xgif, im.ygif);
}
if (gdef.canvasImage != null) {
worker.loadImage(gdef.canvasImage, im.xorigin, im.yorigin - im.ysize, im.xsize, im.ysize);
}
worker.fillRect(im.xorigin, im.yorigin - im.ysize, im.xsize, im.ysize, gdef.getColor(ElementsNames.canvas));
}
private void createImageWorker() {
worker.resize(im.xgif, im.ygif);
}
private void placeLegends() {
if (!gdef.noLegend && !gdef.onlyGraph) {
int border = (int) (getFontCharWidth(FontTag.LEGEND) * PADDING_LEGEND);
LegendComposer lc = new LegendComposer(this, border, im.ygif, im.xgif - 2 * border);
im.ygif = lc.placeComments() + PADDING_BOTTOM;
}
}
private void initializeLimits() {
im.xsize = gdef.width;
im.ysize = gdef.height;
im.unitslength = gdef.unitsLength;
if (gdef.onlyGraph) {
im.xorigin = 0;
}
else {
im.xorigin = (int) (PADDING_LEFT + im.unitslength * getFontCharWidth(FONTTAG_AXIS));
}
if (!gdef.onlyGraph && gdef.verticalLabel != null) {
im.xorigin += getFontHeight(FONTTAG_UNIT);
}
if (gdef.onlyGraph) {
im.yorigin = im.ysize;
}
else {
im.yorigin = PADDING_TOP + im.ysize;
}
if (!gdef.onlyGraph && gdef.title != null) {
im.yorigin += getFontHeight(FONTTAG_TITLE) + PADDING_TITLE;
}
if (gdef.onlyGraph) {
im.xgif = im.xsize;
im.ygif = im.yorigin;
}
else {
im.xgif = PADDING_RIGHT + im.xsize + im.xorigin;
im.ygif = im.yorigin + (int) (PADDING_PLOT * getFontHeight(FONTTAG_AXIS));
}
}
private void removeOutOfRangeRules() {
for (PlotElement plotElement : gdef.plotElements) {
if (plotElement instanceof HRule) {
((HRule) plotElement).setLegendVisibility(im.minval, im.maxval, gdef.forceRulesLegend);
}
else if (plotElement instanceof VRule) {
((VRule) plotElement).setLegendVisibility(im.start, im.end, gdef.forceRulesLegend);
}
}
}
private void removeOutOfRangeSpans() {
for (PlotElement plotElement : gdef.plotElements) {
if (plotElement instanceof HSpan) {
((HSpan) plotElement).setLegendVisibility(im.minval, im.maxval, gdef.forceRulesLegend);
}
else if (plotElement instanceof VSpan) {
((VSpan) plotElement).setLegendVisibility(im.start, im.end, gdef.forceRulesLegend);
}
}
}
private void expandValueRange() {
im.ygridstep = (gdef.valueAxisSetting != null) ? gdef.valueAxisSetting.gridStep : Double.NaN;
im.ylabfact = (gdef.valueAxisSetting != null) ? gdef.valueAxisSetting.labelFactor : 0;
if (!gdef.rigid && !gdef.logarithmic) {
double scaled_min, scaled_max, adj;
if (Double.isNaN(im.ygridstep)) {
if (gdef.altYMrtg) { /* mrtg */
im.decimals = Math.ceil(Math.log10(Math.max(Math.abs(im.maxval), Math.abs(im.minval))));
im.quadrant = 0;
if (im.minval < 0) {
im.quadrant = 2;
if (im.maxval <= 0) {
im.quadrant = 4;
}
}
switch (im.quadrant) {
case 2:
im.scaledstep = Math.ceil(50 * Math.pow(10, -(im.decimals)) * Math.max(Math.abs(im.maxval),
Math.abs(im.minval))) * Math.pow(10, im.decimals - 2);
scaled_min = -2 * im.scaledstep;
scaled_max = 2 * im.scaledstep;
break;
case 4:
im.scaledstep = Math.ceil(25 * Math.pow(10,
-(im.decimals)) * Math.abs(im.minval)) * Math.pow(10, im.decimals - 2);
scaled_min = -4 * im.scaledstep;
scaled_max = 0;
break;
default: /* quadrant 0 */
im.scaledstep = Math.ceil(25 * Math.pow(10, -(im.decimals)) * im.maxval) *
Math.pow(10, im.decimals - 2);
scaled_min = 0;
scaled_max = 4 * im.scaledstep;
break;
}
im.minval = scaled_min;
im.maxval = scaled_max;
}
else if (gdef.altAutoscale || (gdef.altAutoscaleMin && gdef.altAutoscaleMax)) {
/* measure the amplitude of the function. Make sure that
graph boundaries are slightly higher then max/min vals
so we can see amplitude on the graph */
double delt, fact;
delt = im.maxval - im.minval;
adj = delt * 0.1;
fact = 2.0 * Math.pow(10.0,
Math.floor(Math.log10(Math.max(Math.abs(im.minval), Math.abs(im.maxval)))) - 2);
if (delt < fact) {
adj = (fact - delt) * 0.55;
}
im.minval -= adj;
im.maxval += adj;
}
else if (gdef.altAutoscaleMin) {
/* measure the amplitude of the function. Make sure that
graph boundaries are slightly lower than min vals
so we can see amplitude on the graph */
adj = (im.maxval - im.minval) * 0.1;
im.minval -= adj;
}
else if (gdef.altAutoscaleMax) {
/* measure the amplitude of the function. Make sure that
graph boundaries are slightly higher than max vals
so we can see amplitude on the graph */
adj = (im.maxval - im.minval) * 0.1;
im.maxval += adj;
}
else {
scaled_min = im.minval / im.magfact;
scaled_max = im.maxval / im.magfact;
for (int i = 1; SENSIBLE_VALUES[i] > 0; i++) {
if (SENSIBLE_VALUES[i - 1] >= scaled_min && SENSIBLE_VALUES[i] <= scaled_min) {
im.minval = SENSIBLE_VALUES[i] * im.magfact;
}
if (-SENSIBLE_VALUES[i - 1] <= scaled_min && -SENSIBLE_VALUES[i] >= scaled_min) {
im.minval = -SENSIBLE_VALUES[i - 1] * im.magfact;
}
if (SENSIBLE_VALUES[i - 1] >= scaled_max && SENSIBLE_VALUES[i] <= scaled_max) {
im.maxval = SENSIBLE_VALUES[i - 1] * im.magfact;
}
if (-SENSIBLE_VALUES[i - 1] <= scaled_max && -SENSIBLE_VALUES[i] >= scaled_max) {
im.maxval = -SENSIBLE_VALUES[i] * im.magfact;
}
}
}
}
else {
im.minval = im.ylabfact * im.ygridstep *
Math.floor(im.minval / (im.ylabfact * im.ygridstep));
im.maxval = im.ylabfact * im.ygridstep *
Math.ceil(im.maxval / (im.ylabfact * im.ygridstep));
}
}
}
private void identifySiUnit() {
im.unitsexponent = gdef.unitsExponent;
im.base = gdef.base;
if (!gdef.logarithmic) {
double digits;
if (im.unitsexponent != Integer.MAX_VALUE) {
digits = Math.floor(im.unitsexponent / 3.0);
}
else {
digits = Math.floor(Math.log(Math.max(Math.abs(im.minval), Math.abs(im.maxval))) / Math.log(im.base));
}
im.magfact = Math.pow(im.base, digits);
if (((digits + SYMBOLS_CENTER) < SYMBOLS.length) && ((digits + SYMBOLS_CENTER) >= 0)) {
im.symbol = SYMBOLS[(int) digits + SYMBOLS_CENTER];
}
else {
im.symbol = '?';
}
}
}
private void findMinMaxValues() {
double minval = Double.NaN, maxval = Double.NaN;
for (PlotElement pe : gdef.plotElements) {
if (pe instanceof SourcedPlotElement) {
minval = Util.min(((SourcedPlotElement) pe).getMinValue(), minval);
maxval = Util.max(((SourcedPlotElement) pe).getMaxValue(), maxval);
}
}
if (Double.isNaN(minval)) {
minval = 0D;
}
if (Double.isNaN(maxval)) {
maxval = 1D;
}
im.minval = gdef.minValue;
im.maxval = gdef.maxValue;
/* adjust min and max values */
if (Double.isNaN(im.minval) || ((!gdef.logarithmic && !gdef.rigid) && im.minval > minval)) {
im.minval = minval;
}
if (Double.isNaN(im.maxval) || (!gdef.rigid && im.maxval < maxval)) {
if (gdef.logarithmic) {
im.maxval = maxval * 1.1;
}
else {
im.maxval = maxval;
}
}
/* make sure min is smaller than max */
if (im.minval > im.maxval) {
im.minval = 0.99 * im.maxval;
}
/* make sure min and max are not equal */
if (Math.abs(im.minval - im.maxval) < .0000001) {
im.maxval *= 1.01;
if (!gdef.logarithmic) {
im.minval *= 0.99;
}
/* make sure min and max are not both zero */
if (im.maxval == 0.0) {
im.maxval = 1.0;
}
}
}
private void calculatePlotValues() {
for (PlotElement pe : gdef.plotElements) {
if (pe instanceof SourcedPlotElement) {
((SourcedPlotElement) pe).assignValues(dproc);
}
}
}
private void resolveTextElements() {
ValueScaler valueScaler = new ValueScaler(gdef.base);
for (CommentText comment : gdef.comments) {
comment.resolveText(gdef.locale, dproc, valueScaler);
}
}
private void fetchData() throws IOException {
dproc = new DataProcessor(gdef.startTime, gdef.endTime);
dproc.setPixelCount(gdef.width);
if (gdef.poolUsed) {
dproc.setPoolUsed(gdef.poolUsed);
dproc.setPool(gdef.getPool());
}
dproc.setTimeZone(gdef.tz);
if (gdef.step > 0) {
dproc.setStep(gdef.step);
dproc.setFetchRequestResolution(gdef.step);
}
for (Source src : gdef.sources) {
src.requestData(dproc);
}
dproc.processData();
im.start = gdef.startTime;
im.end = gdef.endTime;
}
private boolean lazyCheck() throws IOException {
// redraw if lazy option is not set or file does not exist
if (!gdef.lazy || !Util.fileExists(gdef.filename)) {
return false; // 'false' means 'redraw'
}
// redraw if not enough time has passed
long secPerPixel = (gdef.endTime - gdef.startTime) / gdef.width;
long elapsed = Util.getTimestamp() - Util.getLastModifiedTime(gdef.filename);
return elapsed <= secPerPixel;
}
private void drawLegend() {
if (!gdef.onlyGraph && !gdef.noLegend) {
worker.setTextAntiAliasing(gdef.textAntiAliasing);
int ascent = (int) worker.getFontAscent(gdef.getFont(FONTTAG_LEGEND));
int box = (int) getBox(), boxSpace = (int) (getBoxSpace());
for (CommentText c : gdef.comments) {
if (c.isValidGraphElement()) {
int x = c.x, y = c.y + ascent;
if (c instanceof LegendText) {
// draw with BOX
worker.fillRect(x, y - box, box, box, gdef.getColor(ElementsNames.frame));
Paint bc = gdef.getColor(ElementsNames.back);
Paint lc = ((LegendText) c).legendColor;
boolean bt = bc.getTransparency() != Transparency.OPAQUE;
boolean lt = lc.getTransparency() != Transparency.OPAQUE;
// no use drawing unless both the two on top have some transparency
if (bt && lt)
worker.fillRect(x + 1, y - box + 1, box - 2, box - 2, gdef.getColor(ElementsNames.canvas));
// no use drawing unless the one on top has some transparency
if (lt)
worker.fillRect(x + 1, y - box + 1, box - 2, box - 2, bc);
worker.fillRect(x + 1, y - box + 1, box - 2, box - 2, lc);
worker.drawString(c.resolvedText, x + boxSpace, y, gdef.getFont(FONTTAG_LEGEND), gdef.getColor(ElementsNames.font));
}
else {
worker.drawString(c.resolvedText, x, y, gdef.getFont(FONTTAG_LEGEND), gdef.getColor(ElementsNames.font));
}
}
}
worker.setTextAntiAliasing(false);
}
}
// helper methods
double getFontHeight(FontTag fonttag) {
return worker.getFontHeight(gdef.getFont(fonttag));
}
double getFontCharWidth(FontTag fonttag) {
return worker.getStringWidth("a", gdef.getFont(fonttag));
}
@Deprecated
double getSmallFontHeight() {
return getFontHeight(FONTTAG_LEGEND);
}
double getTitleFontHeight() {
return getFontHeight(FONTTAG_TITLE);
}
double getInterlegendSpace() {
return getFontCharWidth(FONTTAG_LEGEND) * LEGEND_INTERSPACING;
}
double getLeading() {
return getFontHeight(FONTTAG_LEGEND) * LEGEND_LEADING;
}
double getSmallLeading() {
return getFontHeight(FONTTAG_LEGEND) * LEGEND_LEADING_SMALL;
}
double getBoxSpace() {
return Math.ceil(getFontHeight(FONTTAG_LEGEND) * LEGEND_BOX_SPACE);
}
private double getBox() {
return getFontHeight(FONTTAG_LEGEND) * LEGEND_BOX;
}
private double[] xtr(long[] timestamps) {
double[] timestampsDev = new double[2 * timestamps.length - 1];
for (int i = 0, j = 0; i < timestamps.length; i += 1, j += 2) {
timestampsDev[j] = mapper.xtr(timestamps[i]);
if (i < timestamps.length - 1) {
timestampsDev[j + 1] = timestampsDev[j];
}
}
return timestampsDev;
}
private double[] ytr(double[] values) {
double[] valuesDev = new double[2 * values.length - 1];
for (int i = 0, j = 0; i < values.length; i += 1, j += 2) {
if (Double.isNaN(values[i])) {
valuesDev[j] = Double.NaN;
}
else {
valuesDev[j] = mapper.ytr(values[i]);
}
if (j > 0) {
valuesDev[j - 1] = valuesDev[j];
}
}
return valuesDev;
}
/**
* Renders this graph onto graphing device
*
@@ -834,4 +94,5 @@ public class RrdGraph implements RrdGraphConstants {
ImageIcon image = new ImageIcon(imageData);
image.paintIcon(null, g, 0, 0);
}
}

View File

@@ -15,7 +15,9 @@ import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.TimeZone;
import java.util.function.Function;
import javax.imageio.ImageIO;
@@ -108,7 +110,8 @@ public class RrdGraphDef implements RrdGraphConstants, DataHolder {
String filename = RrdGraphConstants.IN_MEMORY_IMAGE; // ok
long startTime, endTime; // ok
TimeAxisSetting timeAxisSetting = null; // ok
TimeLabelFormat timeLabelFormat = null; // ok
TimeLabelFormat timeLabelFormat = null;
Function<TimeUnit, Optional<TimeLabelFormat>> formatProvider = s -> Optional.empty();
ValueAxisSetting valueAxisSetting = null; // ok
boolean altYGrid = false; // ok
boolean noMinorGrid = false; // ok
@@ -396,6 +399,54 @@ public class RrdGraphDef implements RrdGraphConstants, DataHolder {
timeLabelFormat = format;
}
/**
* <p>This allows to keep the default major and minor grid unit, but with changing only the label formatting,
* that will be formatted differently according to {@link TimeUnit} chosen for the time axis.</p>
* <p>If the returned {@link Optional} is empty, the default formatting will be kept</p>
* <table border="1">
* <caption>Default formatting</caption>
* <thead>
* <tr>
* <th>{@link TimeUnit}</th>
* <th>Default pattern</th>
* </tr>
* </thead>
* <tbody>
* <tr>
* <td>MINUTE</td>
* <td>HH:mm</td>
* </tr>
* <tr>
* <td>HOUR</td>
* <td>HH:mm</td>
* </tr>
* <tr>
* <td>DAY</td>
* <td>EEE dd</td>
* </tr>
* <tr>
* <td>WEEK</td>
* <td>'Week 'w</td>
* </tr>
* <tr>
* <td>MONTH</td>
* <td>MMM</td>
* </tr>
* <tr>
* <td>YEAR</td>
* <td>yy</td>
* </tr>
* </tbody>
* </table>
*
*
* @param formatProvider An {@link Optional} holding the {@link TimeLabelFormat} to use or empy to keep the default.
* @since 3.10
*/
public void setTimeLabelFormatter(Function<TimeUnit, Optional<TimeLabelFormat>> formatProvider) {
this.formatProvider = formatProvider;
}
/**
* Sets vertical axis grid and labels. Makes vertical grid lines appear
* at gridStep interval. Every labelFactor*gridStep, a major grid line is printed,
@@ -1939,4 +1990,11 @@ public class RrdGraphDef implements RrdGraphConstants, DataHolder {
return this.step;
}
/**
* @since 3.10
*/
boolean drawTicks() {
return tickStroke != null && ((! (tickStroke instanceof BasicStroke)) || ((BasicStroke)tickStroke).getLineWidth() > 0);
}
}

View File

@@ -0,0 +1,725 @@
package org.rrd4j.graph;
import java.awt.*;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import org.rrd4j.core.Util;
import org.rrd4j.data.DataProcessor;
class RrdGraphGenerator {
private static final double[] SENSIBLE_VALUES = {
1000.0, 900.0, 800.0, 750.0, 700.0,
600.0, 500.0, 400.0, 300.0, 250.0,
200.0, 125.0, 100.0, 90.0, 80.0,
75.0, 70.0, 60.0, 50.0, 40.0, 30.0,
25.0, 20.0, 10.0, 9.0, 8.0,
7.0, 6.0, 5.0, 4.0, 3.5, 3.0,
2.5, 2.0, 1.8, 1.5, 1.2, 1.0,
0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.0, -1
};
private static final int SYMBOLS_CENTER = 8;
private static final char[] SYMBOLS = {'y', 'z', 'a', 'f', 'p', 'n', 'µ', 'm', ' ', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'};
final RrdGraphDef gdef;
final ImageWorker worker;
private final DataProcessor dproc;
Mapper mapper;
final RrdGraphInfo info = new RrdGraphInfo();
private final String signature;
final ImageParameters im;
RrdGraphGenerator(RrdGraphDef gdef, ImageWorker worker, DataProcessor dproc) {
this.gdef = gdef;
this.worker = worker;
this.dproc = dproc;
signature = gdef.getSignature();
im = new ImageParameters();
}
void createGraph() throws IOException {
boolean lazy = lazyCheck();
if (!lazy || gdef.printStatementCount() != 0) {
fetchData();
resolveTextElements();
if (gdef.shouldPlot() && !lazy) {
initializeLimits();
calculatePlotValues();
findMinMaxValues();
identifySiUnit();
expandValueRange();
removeOutOfRangeRules();
removeOutOfRangeSpans();
mapper = new Mapper(gdef, im);
placeLegends();
createImageWorker();
drawBackground();
drawData();
drawGrid();
drawAxis();
drawText();
drawLegend();
drawRulesAndSpans();
gator();
drawOverlay();
saveImage();
}
}
collectInfo();
}
private void collectInfo() {
info.filename = gdef.filename;
info.width = im.xgif;
info.height = im.ygif;
for (CommentText comment : gdef.comments) {
if (comment instanceof PrintText) {
PrintText pt = (PrintText) comment;
if (pt.isPrint()) {
info.addPrintLine(pt.resolvedText);
}
}
}
if (gdef.imageInfo != null) {
info.imgInfo = Util.sprintf(gdef.locale, gdef.imageInfo, gdef.filename, im.xgif, im.ygif);
}
}
private void saveImage() throws IOException {
if (!RrdGraphConstants.IN_MEMORY_IMAGE.equals(gdef.filename)) {
Path imgpath = Paths.get(gdef.filename);
worker.saveImage(gdef.filename);
info.bytesSource = () -> {
try {
return Files.readAllBytes(imgpath);
} catch (IOException e) {
throw new IllegalStateException("Unable to read image bytes", e);
}
};
info.bytesCount = () -> {
try {
return (int) Files.size(imgpath);
} catch (IOException e) {
throw new IllegalStateException("Unable to read image informations", e);
}
};
} else {
byte[] content = worker.getImageBytes();
info.bytesSource = () -> Arrays.copyOf(content, content.length);
info.bytesCount = () -> content.length;
}
}
private void drawOverlay() throws IOException {
if (gdef.overlayImage != null) {
worker.loadImage(gdef.overlayImage, 0, 0, im.xgif, im.ygif);
}
}
private void gator() {
if (!gdef.onlyGraph && gdef.showSignature) {
worker.setTextAntiAliasing(gdef.textAntiAliasing);
Font font = gdef.getFont(RrdGraphConstants.FONTTAG_WATERMARK);
int x = (int) (im.xgif - 2 - worker.getFontAscent(font));
int y = 4;
worker.transform(x, y, Math.PI / 2);
worker.drawString(signature, 0, 0, font, Color.LIGHT_GRAY);
worker.reset();
worker.setTextAntiAliasing(false);
}
}
private void drawRulesAndSpans() {
boolean found = false;
for (PlotElement pe : gdef.plotElements) {
if (pe instanceof HRule) {
HRule hr = (HRule) pe;
if (hr.value >= im.minval && hr.value <= im.maxval) {
int y = mapper.ytr(hr.value);
if (!found) {
worker.clip(im.xorigin + 1, im.yorigin - gdef.height - 1, gdef.width - 1, gdef.height + 2);
found = true;
}
worker.drawLine(im.xorigin, y, im.xorigin + im.xsize, y, hr.color, hr.stroke);
}
} else if (pe instanceof VRule) {
VRule vr = (VRule) pe;
if (vr.timestamp >= im.start && vr.timestamp <= im.end) {
int x = mapper.xtr(vr.timestamp);
if (!found) {
worker.clip(im.xorigin + 1, im.yorigin - gdef.height - 1, gdef.width - 1, gdef.height + 2);
found = true;
}
worker.drawLine(x, im.yorigin, x, im.yorigin - im.ysize, vr.color, vr.stroke);
}
}
else if (pe instanceof HSpan) {
HSpan hr = (HSpan) pe;
int ys = mapper.ytr(hr.start);
int ye = mapper.ytr(hr.end);
int height = ys - ye;
if (!found) {
worker.clip(im.xorigin + 1, im.yorigin - gdef.height - 1, gdef.width - 1, gdef.height + 2);
found = true;
}
worker.fillRect(im.xorigin, ys - height, im.xsize, height, hr.color);
} else if (pe instanceof VSpan) {
VSpan vr = (VSpan) pe;
int xs = mapper.xtr(vr.start);
int xe = mapper.xtr(vr.end);
if (!found) {
worker.clip(im.xorigin + 1, im.yorigin - gdef.height - 1, gdef.width - 1, gdef.height + 2);
found = true;
}
worker.fillRect(xs, im.yorigin - im.ysize, xe - xs, im.ysize, vr.color);
}
}
if (found)
worker.reset();
}
private void drawText() {
if (!gdef.onlyGraph) {
worker.setTextAntiAliasing(gdef.textAntiAliasing);
if (gdef.title != null) {
// I2P truncate on the right only
//int x = im.xgif / 2 - (int) (worker.getStringWidth(gdef.title, gdef.getFont(RrdGraphConstants.FONTTAG_TITLE)) / 2);
int x = Math.max(2, im.xgif / 2 - (int) (worker.getStringWidth(gdef.title, gdef.getFont(RrdGraphConstants.FONTTAG_TITLE)) / 2));
// I2P a little less padding on top and more on the bottom
//int y = PADDING_TOP + (int) worker.getFontAscent(gdef.getFont(FONTTAG_TITLE));
int y = RrdGraphConstants.PADDING_TOP * 2 / 3 + (int) worker.getFontAscent(gdef.getFont(RrdGraphConstants.FONTTAG_TITLE));
worker.drawString(gdef.title, x, y, gdef.getFont(RrdGraphConstants.FONTTAG_TITLE), gdef.getColor(ElementsNames.font));
}
if (gdef.verticalLabel != null) {
int y = im.yorigin - im.ysize / 2 + (int) worker.getStringWidth(gdef.verticalLabel, gdef.getFont(RrdGraphConstants.FONTTAG_UNIT)) / 2;
int ascent = (int) worker.getFontAscent(gdef.getFont(RrdGraphConstants.FONTTAG_UNIT));
worker.transform(RrdGraphConstants.PADDING_LEFT, y, -Math.PI / 2);
worker.drawString(gdef.verticalLabel, 0, ascent,
gdef.getFont(RrdGraphConstants.FONTTAG_UNIT),
gdef.getColor(ElementsNames.font));
worker.reset();
}
worker.setTextAntiAliasing(false);
}
}
private void drawGrid() {
if (!gdef.onlyGraph) {
worker.setTextAntiAliasing(gdef.textAntiAliasing);
Paint shade1 = gdef.getColor(ElementsNames.shadea);
Paint shade2 = gdef.getColor(ElementsNames.shadeb);
Stroke borderStroke = new BasicStroke(1);
worker.drawLine(0, 0, im.xgif - 1, 0, shade1, borderStroke);
worker.drawLine(1, 1, im.xgif - 2, 1, shade1, borderStroke);
worker.drawLine(0, 0, 0, im.ygif - 1, shade1, borderStroke);
worker.drawLine(1, 1, 1, im.ygif - 2, shade1, borderStroke);
worker.drawLine(im.xgif - 1, 0, im.xgif - 1, im.ygif - 1, shade2, borderStroke);
worker.drawLine(0, im.ygif - 1, im.xgif - 1, im.ygif - 1, shade2, borderStroke);
worker.drawLine(im.xgif - 2, 1, im.xgif - 2, im.ygif - 2, shade2, borderStroke);
worker.drawLine(1, im.ygif - 2, im.xgif - 2, im.ygif - 2, shade2, borderStroke);
if (gdef.drawXGrid) {
new TimeAxis(this).draw();
}
if (gdef.drawYGrid) {
boolean ok;
if (gdef.altYMrtg) {
ok = new ValueAxisMrtg(this).draw();
} else if (gdef.logarithmic) {
ok = new ValueAxisLogarithmic(this, worker, gdef.locale).draw();
} else {
ok = new ValueAxis(this).draw();
}
if (!ok) {
String msg = "No Data Found";
worker.drawString(msg, im.xgif / 2 - (int) worker.getStringWidth(msg,
gdef.getFont(RrdGraphConstants.FONTTAG_TITLE)) / 2,
(2 * im.yorigin - im.ysize) / 2,
gdef.getFont(RrdGraphConstants.FONTTAG_TITLE), gdef.getColor(ElementsNames.font));
}
}
worker.setTextAntiAliasing(false);
}
}
private void drawData() {
worker.setAntiAliasing(gdef.antiAliasing);
worker.clip(im.xorigin, im.yorigin - gdef.height - 1, gdef.width,
gdef.height + 2);
double areaZero = mapper.ytr((im.minval > 0.0) ? im.minval : Math.min(im.maxval, 0.0));
double[] x = gdef.downsampler == null ? xtr(dproc.getTimestamps()) : null;
double[] lastY = null;
// draw line, area and stack
for (PlotElement plotElement : gdef.plotElements) {
if (plotElement instanceof SourcedPlotElement) {
SourcedPlotElement source = (SourcedPlotElement) plotElement;
double[] y;
if (gdef.downsampler != null) {
DownSampler.DataSet set = gdef.downsampler.downsize(dproc.getTimestamps(),
source.getValues());
x = xtr(set.timestamps);
y = ytr(set.values);
} else {
y = ytr(source.getValues());
}
if (Line.class.isAssignableFrom(source.getClass())) {
worker.drawPolyline(x, y, source.color, ((Line) source).stroke);
} else if (Area.class.isAssignableFrom(source.getClass())) {
if (source.parent == null) {
worker.fillPolygon(x, areaZero, y, source.color);
} else {
worker.fillPolygon(x, lastY, y, source.color);
worker.drawPolyline(x, lastY, source.getParentColor(), new BasicStroke(0));
}
} else if (source instanceof Stack) {
Stack stack = (Stack) source;
float width = stack.getParentLineWidth();
if (width >= 0F) {
// line
worker.drawPolyline(x, y, stack.color, new BasicStroke(width));
} else {
// area
worker.fillPolygon(x, lastY, y, stack.color);
worker.drawPolyline(x, lastY, stack.getParentColor(), new BasicStroke(0));
}
} else {
// should not be here
throw new IllegalStateException("Unknown plot source: " + source.getClass().getName());
}
lastY = y;
}
}
worker.reset();
worker.setAntiAliasing(false);
}
private void drawAxis() {
if (!gdef.onlyGraph) {
Paint gridColor = gdef.getColor(ElementsNames.grid);
Paint xaxisColor = gdef.getColor(ElementsNames.xaxis);
Paint yaxisColor = gdef.getColor(ElementsNames.yaxis);
Paint arrowColor = gdef.getColor(ElementsNames.arrow);
Stroke stroke = new BasicStroke(1);
worker.drawLine(im.xorigin + im.xsize, im.yorigin,
im.xorigin + im.xsize, im.yorigin - im.ysize, gridColor,
stroke);
worker.drawLine(im.xorigin, im.yorigin - im.ysize,
im.xorigin + im.xsize, im.yorigin - im.ysize, gridColor,
stroke);
worker.drawLine(im.xorigin - 4, im.yorigin, im.xorigin + im.xsize + 4,
im.yorigin, xaxisColor, stroke);
worker.drawLine(im.xorigin, im.yorigin + 4, im.xorigin,
im.yorigin - im.ysize - 4, yaxisColor, stroke);
// I2P skip arrowheads if transparent
if (((Color)arrowColor).getAlpha() == 0)
return;
//Do X axis arrow
double[] xArrowX = { im.xorigin + im.xsize + 4,
im.xorigin + im.xsize + 9, im.xorigin + im.xsize + 4, };
double[] xArrowY = { im.yorigin - 3, im.yorigin, im.yorigin + 3, };
worker.fillPolygon(xArrowX, im.yorigin + 3.0, xArrowY, arrowColor);
//Do y axis arrow
double[] yArrowX = { im.xorigin - 3, im.xorigin, im.xorigin + 3, };
double[] yArrowY = { im.yorigin - im.ysize - 4,
im.yorigin - im.ysize - 9, im.yorigin - im.ysize - 4, };
worker.fillPolygon(yArrowX, im.yorigin - im.ysize - 4.0, yArrowY, arrowColor);
}
}
private void drawBackground() throws IOException {
worker.fillRect(0, 0, im.xgif, im.ygif, gdef.getColor(ElementsNames.back));
if (gdef.backgroundImage != null) {
worker.loadImage(gdef.backgroundImage, 0, 0, im.xgif, im.ygif);
}
if (gdef.canvasImage != null) {
worker.loadImage(gdef.canvasImage, im.xorigin, im.yorigin - im.ysize, im.xsize, im.ysize);
}
worker.fillRect(im.xorigin, im.yorigin - im.ysize, im.xsize, im.ysize, gdef.getColor(ElementsNames.canvas));
}
private void createImageWorker() {
worker.resize(im.xgif, im.ygif);
}
private void placeLegends() {
if (!gdef.noLegend && !gdef.onlyGraph) {
int border = (int) (getFontCharWidth(RrdGraphConstants.FontTag.LEGEND) * RrdGraphConstants.PADDING_LEGEND);
LegendComposer lc = new LegendComposer(this, border, im.ygif, im.xgif - 2 * border);
im.ygif = lc.placeComments() + RrdGraphConstants.PADDING_BOTTOM;
}
}
private void initializeLimits() {
im.xsize = gdef.width;
im.ysize = gdef.height;
im.unitslength = gdef.unitsLength;
if (gdef.onlyGraph) {
im.xorigin = 0;
} else {
im.xorigin = (int) (RrdGraphConstants.PADDING_LEFT + im.unitslength * getFontCharWidth(
RrdGraphConstants.FONTTAG_AXIS));
}
if (!gdef.onlyGraph && gdef.verticalLabel != null) {
im.xorigin += (int) getFontHeight(RrdGraphConstants.FONTTAG_UNIT);
}
if (gdef.onlyGraph) {
im.yorigin = im.ysize;
} else {
im.yorigin = RrdGraphConstants.PADDING_TOP + im.ysize;
}
if (!gdef.onlyGraph && gdef.title != null) {
im.yorigin += (int) (getFontHeight(RrdGraphConstants.FONTTAG_TITLE) + RrdGraphConstants.PADDING_TITLE);
}
if (gdef.onlyGraph) {
im.xgif = im.xsize;
im.ygif = im.yorigin;
} else {
im.xgif = RrdGraphConstants.PADDING_RIGHT + im.xsize + im.xorigin;
im.ygif = im.yorigin + (int) (RrdGraphConstants.PADDING_PLOT * getFontHeight(
RrdGraphConstants.FONTTAG_AXIS));
}
}
private void removeOutOfRangeRules() {
for (PlotElement plotElement : gdef.plotElements) {
if (plotElement instanceof HRule) {
((HRule) plotElement).setLegendVisibility(im.minval, im.maxval, gdef.forceRulesLegend);
} else if (plotElement instanceof VRule) {
((VRule) plotElement).setLegendVisibility(im.start, im.end, gdef.forceRulesLegend);
}
}
}
private void removeOutOfRangeSpans() {
for (PlotElement plotElement : gdef.plotElements) {
if (plotElement instanceof HSpan) {
((HSpan) plotElement).setLegendVisibility(im.minval, im.maxval, gdef.forceRulesLegend);
} else if (plotElement instanceof VSpan) {
((VSpan) plotElement).setLegendVisibility(im.start, im.end, gdef.forceRulesLegend);
}
}
}
private void expandValueRange() {
im.ygridstep = (gdef.valueAxisSetting != null) ?
gdef.valueAxisSetting.gridStep :
Double.NaN;
im.ylabfact = (gdef.valueAxisSetting != null) ?
gdef.valueAxisSetting.labelFactor :
0;
if (!gdef.rigid && !gdef.logarithmic) {
double scaledMin;
double scaledMax;
double adj;
if (Double.isNaN(im.ygridstep)) {
if (gdef.altYMrtg) { /* mrtg */
im.decimals = Math.ceil(
Math.log10(Math.max(Math.abs(im.maxval), Math.abs(im.minval))));
im.quadrant = 0;
if (im.minval < 0) {
im.quadrant = 2;
if (im.maxval <= 0) {
im.quadrant = 4;
}
}
switch (im.quadrant) {
case 2:
im.scaledstep = Math.ceil(
50 * Math.pow(10, -(im.decimals)) * Math.max(Math.abs(im.maxval),
Math.abs(im.minval))) * Math.pow(10, im.decimals - 2);
scaledMin = -2 * im.scaledstep;
scaledMax = 2 * im.scaledstep;
break;
case 4:
im.scaledstep = Math.ceil(
25 * Math.pow(10, -(im.decimals)) * Math.abs(im.minval)) * Math.pow(
10, im.decimals - 2);
scaledMin = -4 * im.scaledstep;
scaledMax = 0;
break;
default: /* quadrant 0 */
im.scaledstep = Math.ceil(
25 * Math.pow(10, -(im.decimals)) * im.maxval) * Math.pow(10,
im.decimals - 2);
scaledMin = 0;
scaledMax = 4 * im.scaledstep;
break;
}
im.minval = scaledMin;
im.maxval = scaledMax;
} else if (gdef.altAutoscale || (gdef.altAutoscaleMin && gdef.altAutoscaleMax)) {
/* measure the amplitude of the function. Make sure that
graph boundaries are slightly higher than max/min vals,
so we can see amplitude on the graph */
double delt;
double fact;
delt = im.maxval - im.minval;
adj = delt * 0.1;
fact = 2.0 * Math.pow(10.0, Math.floor(
Math.log10(Math.max(Math.abs(im.minval), Math.abs(im.maxval)))) - 2);
if (delt < fact) {
adj = (fact - delt) * 0.55;
}
im.minval -= adj;
im.maxval += adj;
} else if (gdef.altAutoscaleMin) {
/* measure the amplitude of the function. Make sure that
graph boundaries are slightly lower than min vals,
so we can see amplitude on the graph */
adj = (im.maxval - im.minval) * 0.1;
im.minval -= adj;
} else if (gdef.altAutoscaleMax) {
/* measure the amplitude of the function. Make sure that
graph boundaries are slightly higher than max vals,
so we can see amplitude on the graph */
adj = (im.maxval - im.minval) * 0.1;
im.maxval += adj;
} else {
scaledMin = im.minval / im.magfact;
scaledMax = im.maxval / im.magfact;
for (int i = 1; SENSIBLE_VALUES[i] > 0; i++) {
if (SENSIBLE_VALUES[i - 1] >= scaledMin && SENSIBLE_VALUES[i] <= scaledMin) {
im.minval = SENSIBLE_VALUES[i] * im.magfact;
}
if (-SENSIBLE_VALUES[i - 1] <= scaledMin && -SENSIBLE_VALUES[i] >= scaledMin) {
im.minval = -SENSIBLE_VALUES[i - 1] * im.magfact;
}
if (SENSIBLE_VALUES[i - 1] >= scaledMax && SENSIBLE_VALUES[i] <= scaledMax) {
im.maxval = SENSIBLE_VALUES[i - 1] * im.magfact;
}
if (-SENSIBLE_VALUES[i - 1] <= scaledMax && -SENSIBLE_VALUES[i] >= scaledMax) {
im.maxval = -SENSIBLE_VALUES[i] * im.magfact;
}
}
}
} else {
im.minval = im.ylabfact * im.ygridstep * Math.floor(
im.minval / (im.ylabfact * im.ygridstep));
im.maxval = im.ylabfact * im.ygridstep * Math.ceil(
im.maxval / (im.ylabfact * im.ygridstep));
}
}
}
private void identifySiUnit() {
im.unitsexponent = gdef.unitsExponent;
im.base = gdef.base;
if (!gdef.logarithmic) {
double digits;
if (im.unitsexponent != Integer.MAX_VALUE) {
digits = Math.floor(im.unitsexponent / 3.0);
} else {
digits = Math.floor(
Math.log(Math.max(Math.abs(im.minval), Math.abs(im.maxval))) / Math.log(
im.base));
}
im.magfact = Math.pow(im.base, digits);
if (((digits + SYMBOLS_CENTER) < SYMBOLS.length) && ((digits + SYMBOLS_CENTER) >= 0)) {
im.symbol = SYMBOLS[(int) digits + SYMBOLS_CENTER];
} else {
im.symbol = '?';
}
}
}
private void findMinMaxValues() {
double minval = Double.NaN;
double maxval = Double.NaN;
for (PlotElement pe : gdef.plotElements) {
if (pe instanceof SourcedPlotElement) {
minval = Util.min(((SourcedPlotElement) pe).getMinValue(), minval);
maxval = Util.max(((SourcedPlotElement) pe).getMaxValue(), maxval);
}
}
if (Double.isNaN(minval)) {
minval = 0D;
}
if (Double.isNaN(maxval)) {
maxval = 1D;
}
im.minval = gdef.minValue;
im.maxval = gdef.maxValue;
/* adjust min and max values */
if (Double.isNaN(
im.minval) || ((!gdef.logarithmic && !gdef.rigid) && im.minval > minval)) {
im.minval = minval;
}
if (Double.isNaN(im.maxval) || (!gdef.rigid && im.maxval < maxval)) {
if (gdef.logarithmic) {
im.maxval = maxval * 1.1;
} else {
im.maxval = maxval;
}
}
/* make sure min is smaller than max */
if (im.minval > im.maxval) {
im.minval = 0.99 * im.maxval;
}
/* make sure min and max are not equal */
if (Math.abs(im.minval - im.maxval) < .0000001) {
im.maxval *= 1.01;
if (!gdef.logarithmic) {
im.minval *= 0.99;
}
/* make sure min and max are not both zero */
if (im.maxval == 0.0) {
im.maxval = 1.0;
}
}
if (gdef.logarithmic) {
im.log = LogService.resolve(im);
}
}
private void calculatePlotValues() {
for (PlotElement pe : gdef.plotElements) {
if (pe instanceof SourcedPlotElement) {
((SourcedPlotElement) pe).assignValues(dproc);
}
}
}
private void resolveTextElements() {
ValueScaler valueScaler = new ValueScaler(gdef.base);
for (CommentText comment : gdef.comments) {
comment.resolveText(gdef.locale, dproc, valueScaler);
}
}
private void fetchData() throws IOException {
dproc.setPixelCount(gdef.width);
if (gdef.poolUsed) {
dproc.setPoolUsed(true);
dproc.setPool(gdef.getPool());
}
dproc.setTimeZone(gdef.tz);
if (gdef.step > 0) {
dproc.setStep(gdef.step);
dproc.setFetchRequestResolution(gdef.step);
}
for (Source src : gdef.sources) {
src.requestData(dproc);
}
dproc.processData();
im.start = gdef.startTime;
im.end = gdef.endTime;
}
private boolean lazyCheck() throws IOException {
// redraw if lazy option is not set or file does not exist
if (!gdef.lazy || !Util.fileExists(gdef.filename)) {
return false; // 'false' means 'redraw'
}
// redraw if not enough time has passed
long secPerPixel = (gdef.endTime - gdef.startTime) / gdef.width;
long elapsed = Util.getTimestamp() - Util.getLastModifiedTime(gdef.filename);
return elapsed <= secPerPixel;
}
private void drawLegend() {
if (!gdef.onlyGraph && !gdef.noLegend) {
worker.setTextAntiAliasing(gdef.textAntiAliasing);
int ascent = (int) worker.getFontAscent(gdef.getFont(RrdGraphConstants.FONTTAG_LEGEND));
int box = (int) getBox();
int boxSpace = (int) (getBoxSpace());
for (CommentText c : gdef.comments) {
if (c.isValidGraphElement()) {
int x = c.x;
int y = c.y + ascent;
if (c instanceof LegendText) {
// draw with BOX
worker.fillRect(x, y - box, box, box, gdef.getColor(ElementsNames.frame));
Paint bc = gdef.getColor(ElementsNames.back);
Paint lc = ((LegendText) c).legendColor;
boolean bt = bc.getTransparency() != Transparency.OPAQUE;
boolean lt = lc.getTransparency() != Transparency.OPAQUE;
// no use drawing unless both the two on top have some transparency
if (bt && lt)
worker.fillRect(x + 1, y - box + 1, box - 2, box - 2, gdef.getColor(ElementsNames.canvas));
// no use drawing unless the one on top has some transparency
if (lt)
worker.fillRect(x + 1, y - box + 1, box - 2, box - 2, bc);
worker.fillRect(x + 1, y - box + 1, box - 2, box - 2, lc);
worker.drawString(c.resolvedText, x + boxSpace, y,
gdef.getFont(RrdGraphConstants.FONTTAG_LEGEND),
gdef.getColor(ElementsNames.font));
} else {
worker.drawString(c.resolvedText, x, y, gdef.getFont(RrdGraphConstants.FONTTAG_LEGEND),
gdef.getColor(ElementsNames.font));
}
}
}
worker.setTextAntiAliasing(false);
}
}
// helper methods
double getFontHeight(RrdGraphConstants.FontTag fonttag) {
return worker.getFontHeight(gdef.getFont(fonttag));
}
double getFontCharWidth(RrdGraphConstants.FontTag fonttag) {
return worker.getStringWidth("a", gdef.getFont(fonttag));
}
double getInterlegendSpace() {
return getFontCharWidth(RrdGraphConstants.FONTTAG_LEGEND) * RrdGraphConstants.LEGEND_INTERSPACING;
}
double getLeading() {
return getFontHeight(RrdGraphConstants.FONTTAG_LEGEND) * RrdGraphConstants.LEGEND_LEADING;
}
double getSmallLeading() {
return getFontHeight(RrdGraphConstants.FONTTAG_LEGEND) * RrdGraphConstants.LEGEND_LEADING_SMALL;
}
double getBoxSpace() {
return Math.ceil(getFontHeight(RrdGraphConstants.FONTTAG_LEGEND) * RrdGraphConstants.LEGEND_BOX_SPACE);
}
private double getBox() {
return getFontHeight(RrdGraphConstants.FONTTAG_LEGEND) * RrdGraphConstants.LEGEND_BOX;
}
private double[] xtr(long[] timestamps) {
double[] timestampsDev = new double[2 * timestamps.length - 1];
for (int i = 0, j = 0; i < timestamps.length; i += 1, j += 2) {
timestampsDev[j] = mapper.xtr(timestamps[i]);
if (i < timestamps.length - 1) {
timestampsDev[j + 1] = timestampsDev[j];
}
}
return timestampsDev;
}
private double[] ytr(double[] values) {
double[] valuesDev = new double[2 * values.length - 1];
for (int i = 0, j = 0; i < values.length; i += 1, j += 2) {
if (Double.isNaN(values[i])) {
valuesDev[j] = Double.NaN;
} else {
valuesDev[j] = mapper.ytr(values[i]);
}
if (j > 0) {
valuesDev[j - 1] = valuesDev[j];
}
}
return valuesDev;
}
}

View File

@@ -8,21 +8,20 @@ import java.util.Date;
class TimeAxis extends Axis {
private static final TimeAxisSetting[] tickSettings = {
new TimeAxisSetting(0, SECOND, 30, MINUTE, 5, MINUTE, 5, 0, HH_MM),
new TimeAxisSetting(2, MINUTE, 1, MINUTE, 5, MINUTE, 5, 0, HH_MM),
new TimeAxisSetting(5, MINUTE, 2, MINUTE, 10, MINUTE, 10, 0, HH_MM),
new TimeAxisSetting(10, MINUTE, 5, MINUTE, 20, MINUTE, 20, 0, HH_MM),
new TimeAxisSetting(30, MINUTE, 10, HOUR, 1, HOUR, 1, 0, HH_MM),
new TimeAxisSetting(60, MINUTE, 30, HOUR, 2, HOUR, 2, 0, HH_MM),
new TimeAxisSetting(180, HOUR, 1, HOUR, 6, HOUR, 6, 0, HH_MM),
new TimeAxisSetting(600, HOUR, 6, DAY, 1, DAY, 1, 24 * 3600, "EEE dd"),
new TimeAxisSetting(1800, HOUR, 12, DAY, 1, DAY, 2, 24 * 3600, "EEE dd"),
new TimeAxisSetting(3600, DAY, 1, WEEK, 1, WEEK, 1, 7 * 24 * 3600, "'Week 'w"),
new TimeAxisSetting(3 * 3600L, WEEK, 1, MONTH, 1, WEEK, 2, 7 * 24 * 3600, "'Week 'w"),
new TimeAxisSetting(6 * 3600L, MONTH, 1, MONTH, 1, MONTH, 1, 30 * 24 * 3600, "MMM"),
new TimeAxisSetting(48 * 3600L, MONTH, 1, MONTH, 3, MONTH, 3, 30 * 24 * 3600, "MMM"),
new TimeAxisSetting(10 * 24 * 3600L, YEAR, 1, YEAR, 1, YEAR, 1, 365 * 24 * 3600, "yy"),
new TimeAxisSetting(-1, MONTH, 0, MONTH, 0, MONTH, 0, 0, "")
new TimeAxisSetting(0, TimeUnit.SECOND, 30, TimeUnit.MINUTE, 5, TimeUnit.MINUTE, 5, 0),
new TimeAxisSetting(2, TimeUnit.MINUTE, 1, TimeUnit.MINUTE, 5, TimeUnit.MINUTE, 5, 0),
new TimeAxisSetting(5, TimeUnit.MINUTE, 2, TimeUnit.MINUTE, 10, TimeUnit.MINUTE, 10, 0),
new TimeAxisSetting(10, TimeUnit.MINUTE, 5, TimeUnit.MINUTE, 20, TimeUnit.MINUTE, 20, 0),
new TimeAxisSetting(30, TimeUnit.MINUTE, 10, TimeUnit.HOUR, 1, TimeUnit.HOUR, 1, 0),
new TimeAxisSetting(60, TimeUnit.MINUTE, 30, TimeUnit.HOUR, 2, TimeUnit.HOUR, 2, 0),
new TimeAxisSetting(180, TimeUnit.HOUR, 1, TimeUnit.HOUR, 6, TimeUnit.HOUR, 6, 0),
new TimeAxisSetting(600, TimeUnit.HOUR, 6, TimeUnit.DAY, 1, TimeUnit.DAY, 1, 24 * 3600),
new TimeAxisSetting(1800, TimeUnit.HOUR, 12, TimeUnit.DAY, 1, TimeUnit.DAY, 2, 24 * 3600),
new TimeAxisSetting(3600, TimeUnit.DAY, 1, TimeUnit.WEEK, 1, TimeUnit.WEEK, 1, 7 * 24 * 3600),
new TimeAxisSetting(3 * 3600L, TimeUnit.WEEK, 1, TimeUnit.MONTH, 1, TimeUnit.WEEK, 2, 7 * 24 * 3600),
new TimeAxisSetting(6 * 3600L, TimeUnit.MONTH, 1, TimeUnit.MONTH, 1, TimeUnit.MONTH, 1, 30 * 24 * 3600),
new TimeAxisSetting(48 * 3600L, TimeUnit.MONTH, 1, TimeUnit.MONTH, 3, TimeUnit.MONTH, 3, 30 * 24 * 3600),
new TimeAxisSetting(10 * 24 * 3600L, TimeUnit.YEAR, 1, TimeUnit.YEAR, 1, TimeUnit.YEAR, 1, 365 * 24 * 3600),
};
private final ImageParameters im;
@@ -34,18 +33,27 @@ class TimeAxis extends Axis {
private final double secPerPix;
private final Calendar calendar;
TimeAxis(RrdGraph rrdGraph) {
this(rrdGraph, rrdGraph.worker);
}
/**
* Used for test
* Used for tests
*
* @param rrdGraph
* @param worker
*/
TimeAxis(RrdGraph rrdGraph, ImageWorker worker) {
this.im = rrdGraph.im;
this.worker = worker;
this.gdef = rrdGraph.gdef;
this.mapper = rrdGraph.mapper;
this.mapper = new Mapper(this.gdef, this.im);
this.secPerPix = (im.end - im.start) / (double) im.xsize;
this.calendar = Calendar.getInstance(gdef.tz, gdef.locale);
this.calendar.setFirstDayOfWeek(gdef.firstDayOfWeek);
}
TimeAxis(RrdGraphGenerator generator) {
this.im = generator.im;
this.worker = generator.worker;
this.gdef = generator.gdef;
this.mapper = generator.mapper;
this.secPerPix = (im.end - im.start) / (double) im.xsize;
this.calendar = Calendar.getInstance(gdef.tz, gdef.locale);
this.calendar.setFirstDayOfWeek(gdef.firstDayOfWeek);
@@ -67,8 +75,6 @@ class TimeAxis extends Axis {
private void drawMinor() {
if (!gdef.noMinorGrid) {
// I2P skip ticks if zero width
boolean ticks = ((BasicStroke)gdef.tickStroke).getLineWidth() > 0;
adjustStartingTime(tickSetting.minorUnit, tickSetting.minorUnitCount);
Paint color = gdef.getColor(ElementsNames.grid);
int y0 = im.yorigin, y1 = y0 - im.ysize;
@@ -76,8 +82,10 @@ class TimeAxis extends Axis {
if (status == 0) {
long time = calendar.getTime().getTime() / 1000L;
int x = mapper.xtr(time);
if (ticks)
// skip ticks if zero width
if (gdef.drawTicks()) {
worker.drawLine(x, y0 - 1, x, y0 + 1, color, gdef.tickStroke);
}
worker.drawLine(x, y0, x, y1, color, gdef.gridStroke);
}
findNextTime(tickSetting.minorUnit, tickSetting.minorUnitCount);
@@ -86,8 +94,6 @@ class TimeAxis extends Axis {
}
private void drawMajor() {
// I2P skip ticks if zero width
boolean ticks = ((BasicStroke)gdef.tickStroke).getLineWidth() > 0;
adjustStartingTime(tickSetting.majorUnit, tickSetting.majorUnitCount);
Paint color = gdef.getColor(ElementsNames.mgrid);
int y0 = im.yorigin, y1 = y0 - im.ysize;
@@ -95,8 +101,10 @@ class TimeAxis extends Axis {
if (status == 0) {
long time = calendar.getTime().getTime() / 1000L;
int x = mapper.xtr(time);
if (ticks)
// skip ticks if zero width
if (gdef.drawTicks()) {
worker.drawLine(x, y0 - 2, x, y0 + 2, color, gdef.tickStroke);
}
worker.drawLine(x, y0, x, y1, color, gdef.gridStroke);
}
findNextTime(tickSetting.majorUnit, tickSetting.majorUnitCount);
@@ -122,7 +130,7 @@ class TimeAxis extends Axis {
}
}
private void findNextTime(int timeUnit, int timeUnitCount) {
private void findNextTime(TimeUnit timeUnit, int timeUnitCount) {
switch (timeUnit) {
case SECOND:
calendar.add(Calendar.SECOND, timeUnitCount);
@@ -153,7 +161,7 @@ class TimeAxis extends Axis {
return (time < im.start) ? -1 : (time > im.end) ? +1 : 0;
}
private void adjustStartingTime(int timeUnit, int timeUnitCount) {
private void adjustStartingTime(TimeUnit timeUnit, int timeUnitCount) {
calendar.setTime(new Date(im.start * 1000L));
switch (timeUnit) {
case SECOND:
@@ -204,14 +212,19 @@ class TimeAxis extends Axis {
private void chooseTickSettings() {
if (gdef.timeAxisSetting != null) {
tickSetting = new TimeAxisSetting(gdef.timeAxisSetting);
}
else {
for (int i = 0; tickSettings[i].secPerPix >= 0 && secPerPix > tickSettings[i].secPerPix; i++) {
tickSetting = tickSettings[i];
} else {
for (TimeAxisSetting i: tickSettings) {
if (secPerPix < i.secPerPix) {
break;
} else {
tickSetting = i;
}
}
}
if (gdef.timeLabelFormat != null) {
tickSetting = tickSetting.withLabelFormat(gdef.timeLabelFormat);
} else {
gdef.formatProvider.apply(tickSetting.labelUnit).ifPresent(f -> tickSetting = tickSetting.withLabelFormat(f));
}
}

View File

@@ -2,12 +2,17 @@ package org.rrd4j.graph;
class TimeAxisSetting {
final long secPerPix;
final int minorUnit, minorUnitCount, majorUnit, majorUnitCount;
final int labelUnit, labelUnitCount, labelSpan;
final TimeUnit majorUnit;
final int majorUnitCount;
final TimeUnit minorUnit;
final int minorUnitCount;
final TimeUnit labelUnit;
final int labelUnitCount;
final int labelSpan;
final TimeLabelFormat format;
TimeAxisSetting(long secPerPix, int minorUnit, int minorUnitCount, int majorUnit, int majorUnitCount,
int labelUnit, int labelUnitCount, int labelSpan, TimeLabelFormat format) {
TimeAxisSetting(long secPerPix, TimeUnit minorUnit, int minorUnitCount, TimeUnit majorUnit, int majorUnitCount,
TimeUnit labelUnit, int labelUnitCount, int labelSpan, TimeLabelFormat format) {
this.secPerPix = secPerPix;
this.minorUnit = minorUnit;
this.minorUnitCount = minorUnitCount;
@@ -19,6 +24,19 @@ class TimeAxisSetting {
this.format = format;
}
TimeAxisSetting(long secPerPix, TimeUnit minorUnit, int minorUnitCount, TimeUnit majorUnit, int majorUnitCount,
TimeUnit labelUnit, int labelUnitCount, int labelSpan) {
this.secPerPix = secPerPix;
this.minorUnit = minorUnit;
this.minorUnitCount = minorUnitCount;
this.majorUnit = majorUnit;
this.majorUnitCount = majorUnitCount;
this.labelUnit = labelUnit;
this.labelUnitCount = labelUnitCount;
this.labelSpan = labelSpan;
this.format = new SimpleTimeLabelFormat(labelUnit.getLabel());
}
TimeAxisSetting(TimeAxisSetting s) {
this.secPerPix = s.secPerPix;
this.minorUnit = s.minorUnit;
@@ -33,20 +51,8 @@ class TimeAxisSetting {
TimeAxisSetting(int minorUnit, int minorUnitCount, int majorUnit, int majorUnitCount,
int labelUnit, int labelUnitCount, int labelSpan, TimeLabelFormat format) {
this(0, minorUnit, minorUnitCount, majorUnit, majorUnitCount,
labelUnit, labelUnitCount, labelSpan, format);
}
TimeAxisSetting(long secPerPix, int minorUnit, int minorUnitCount, int majorUnit, int majorUnitCount,
int labelUnit, int labelUnitCount, int labelSpan, String format) {
this(secPerPix, minorUnit, minorUnitCount, majorUnit, majorUnitCount,
labelUnit, labelUnitCount, labelSpan, new SimpleTimeLabelFormat(format));
}
TimeAxisSetting(int minorUnit, int minorUnitCount, int majorUnit, int majorUnitCount,
int labelUnit, int labelUnitCount, int labelSpan, String format) {
this(0, minorUnit, minorUnitCount, majorUnit, majorUnitCount,
labelUnit, labelUnitCount, labelSpan, new SimpleTimeLabelFormat(format));
this(0, TimeUnit.resolveUnit(minorUnit), minorUnitCount, TimeUnit.resolveUnit(majorUnit), majorUnitCount,
TimeUnit.resolveUnit(labelUnit), labelUnitCount, labelSpan, format);
}
TimeAxisSetting withLabelFormat(TimeLabelFormat f) {

View File

@@ -0,0 +1,71 @@
package org.rrd4j.graph;
import java.util.Calendar;
import static org.rrd4j.graph.RrdGraphConstants.HH_MM;
public enum TimeUnit {
SECOND {
@Override
public String getLabel() {
return "s";
}
},
MINUTE {
@Override
public String getLabel() {
return HH_MM;
}
},
HOUR {
@Override
public String getLabel() {
return HH_MM;
}
},
DAY {
@Override
public String getLabel() {
return "EEE dd";
}
},
WEEK {
@Override
public String getLabel() {
return "'Week 'w";
}
},
MONTH {
@Override
public String getLabel() {
return "MMM";
}
},
YEAR {
@Override
public String getLabel() {
return "yy";
}
};
public abstract String getLabel();
public static TimeUnit resolveUnit(int unitKey) {
switch (unitKey) {
case Calendar.SECOND:
return SECOND;
case Calendar.MINUTE:
return MINUTE;
case Calendar.HOUR_OF_DAY:
return HOUR;
case Calendar.DAY_OF_MONTH:
return DAY;
case Calendar.WEEK_OF_YEAR:
return WEEK;
case Calendar.MONTH:
return MONTH;
case Calendar.YEAR:
return YEAR;
default:
throw new IllegalArgumentException("Unidentified key " + unitKey);
}
}
}

View File

@@ -37,15 +37,24 @@ class ValueAxis extends Axis {
private final RrdGraphDef gdef;
private final Mapper mapper;
ValueAxis(RrdGraph rrdGraph) {
this(rrdGraph, rrdGraph.worker);
}
/**
* Used for tests
*
* @param rrdGraph
* @param worker
*/
ValueAxis(RrdGraph rrdGraph, ImageWorker worker) {
this.im = rrdGraph.im;
this.gdef = rrdGraph.gdef;
this.worker = worker;
this.mapper = rrdGraph.mapper;
this.mapper = new Mapper(this.gdef, this.im);
}
ValueAxis(RrdGraphGenerator generator) {
this.im = generator.im;
this.gdef = generator.gdef;
this.worker = generator.worker;
this.mapper = generator.mapper;
}
boolean draw() {
@@ -124,8 +133,6 @@ class ValueAxis extends Axis {
int egrid = (int) (im.maxval / gridstep + 1);
double scaledstep = gridstep / im.magfact;
boolean fractional = isFractional(scaledstep, labfact);
// I2P skip ticks if zero width
boolean ticks = ((BasicStroke)gdef.tickStroke).getLineWidth() > 0;
for (int i = sgrid; i <= egrid; i++) {
int y = mapper.ytr(gridstep * i);
if (y >= im.yorigin - im.ysize && y <= im.yorigin) {
@@ -157,14 +164,16 @@ class ValueAxis extends Axis {
}
int length = (int) (worker.getStringWidth(graph_label, font));
worker.drawString(graph_label, x0 - length - PADDING_VLABEL, y + labelOffset, font, fontColor);
if (ticks) {
// skip ticks if zero width
if (gdef.drawTicks()) {
worker.drawLine(x0 - 2, y, x0 + 2, y, mGridColor, gdef.tickStroke);
worker.drawLine(x1 - 2, y, x1 + 2, y, mGridColor, gdef.tickStroke);
}
worker.drawLine(x0, y, x1, y, mGridColor, gdef.gridStroke);
}
else if (!(gdef.noMinorGrid)) {
if (ticks) {
// skip ticks if zero width
if (gdef.drawTicks()) {
worker.drawLine(x0 - 1, y, x0 + 1, y, gridColor, gdef.tickStroke);
worker.drawLine(x1 - 1, y, x1 + 1, y, gridColor, gdef.tickStroke);
}

View File

@@ -1,8 +1,7 @@
package org.rrd4j.graph;
import org.rrd4j.core.Util;
import java.awt.*;
import java.util.Locale;
class ValueAxisLogarithmic extends Axis {
private static final double[][] yloglab = {
@@ -15,22 +14,52 @@ class ValueAxisLogarithmic extends Axis {
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
};
@FunctionalInterface
private interface IntDoubleLabelConsumer {
void accept(int a, double b, String formatPattern);
default void accept(int a, double b) {
accept(a, b, "%.0e");
}
}
@FunctionalInterface
private interface IntDoubleLineConsumer {
void accept(int a, double b, Paint color);
}
private final ImageParameters im;
private final ImageWorker worker;
private final RrdGraphDef gdef;
private final int fontHeight;
private final Mapper mapper;
private final Locale locale;
ValueAxisLogarithmic(RrdGraph rrdGraph) {
this(rrdGraph, rrdGraph.worker);
/**
* Used for tests
*
* @param rrdGraph
* @param worker
*/
ValueAxisLogarithmic(RrdGraph rrdGraph, ImageWorker worker, Locale locale) {
this.im = rrdGraph.im;
this.gdef = rrdGraph.gdef;
this.worker = worker;
this.fontHeight = (int) Math.ceil(worker.getFontHeight(gdef.getFont(FONTTAG_AXIS)));
this.mapper = new Mapper(this.gdef, this.im);
this.locale = locale;
}
ValueAxisLogarithmic(RrdGraph rrdGraph, ImageWorker worker) {
ValueAxisLogarithmic(RrdGraphGenerator rrdGraph, ImageWorker worker, Locale locale) {
this.im = rrdGraph.im;
this.gdef = rrdGraph.gdef;
this.worker = worker;
this.fontHeight = (int) Math.ceil(worker.getFontHeight(gdef.getFont(FONTTAG_AXIS)));
this.mapper = rrdGraph.mapper;
this.locale = locale;
}
private double findStart(double positive, int idx) {
return Math.pow(10, im.log.applyAsDouble(positive) - im.log.applyAsDouble(positive) % im.log.applyAsDouble(yloglab[idx][0]));
}
boolean draw() {
@@ -43,21 +72,23 @@ class ValueAxisLogarithmic extends Axis {
if (im.maxval == im.minval) {
return false;
}
double pixpex = im.ysize / (log10(im.maxval) - log10(im.minval));
double pixpex = im.ysize / (im.log.applyAsDouble(im.maxval) - im.log.applyAsDouble(im.minval));
if (Double.isNaN(pixpex)) {
return false;
}
double minstep, pixperstep;
int minoridx = 0, majoridx = 0;
int minoridx = 0;
int majoridx = 0;
// Find the index in yloglab for major and minor grid
for (int i = 0; yloglab[i][0] > 0; i++) {
minstep = log10(yloglab[i][0]);
double minstep = Math.log10(yloglab[i][0]);
for (int ii = 1; yloglab[i][ii + 1] > 0; ii++) {
if (yloglab[i][ii + 2] == 0) {
minstep = log10(yloglab[i][ii + 1]) - log10(yloglab[i][ii]);
minstep = Math.log10(yloglab[i][ii + 1]) - Math.log10(yloglab[i][ii]);
break;
}
}
pixperstep = pixpex * minstep;
double pixperstep = pixpex * minstep;
if (pixperstep > 5) {
minoridx = i;
}
@@ -66,14 +97,32 @@ class ValueAxisLogarithmic extends Axis {
}
}
// Draw minor grid for positive values
double positiveMin = (im.minval > 0.0) ? im.minval : 0.0;
int x0 = im.xorigin, x1 = x0 + im.xsize;
double positiveMin = Math.max(im.minval, 0.0);
int x0 = im.xorigin;
int x1 = x0 + im.xsize;
if (yloglab[minoridx][0] == 0 || yloglab[majoridx][0] == 0) {
return false;
}
for (double value = Math.pow(10, log10(positiveMin)
- log10(positiveMin) % log10(yloglab[minoridx][0]));
String zeroFormatted = String.format(locale, "%.0e", 0.0);
IntDoubleLabelConsumer drawAxisLabel = (y, v, f) -> {
String graphLabel = String.format(locale, f, v);
if (zeroFormatted.equals(graphLabel)) {
graphLabel = String.format(locale, "%.0f", v);
}
int length = (int) (worker.getStringWidth(graphLabel, font));
worker.drawString(graphLabel, x0 - length - PADDING_VLABEL, y + labelOffset, font, fontColor);
};
IntDoubleLineConsumer drawAxisLines = (y, v, p) -> {
if (gdef.drawTicks()) {
worker.drawLine(x0 - 1, y, x0 + 1, y, p, gdef.tickStroke);
worker.drawLine(x1 - 1, y, x1 + 1, y, p, gdef.tickStroke);
}
worker.drawLine(x0, y, x1, y, p, gdef.gridStroke);
};
// Draw minor grid for positive values
for (double value = findStart(positiveMin, minoridx);
value <= im.maxval;
value *= yloglab[minoridx][0]) {
if (value < positiveMin) continue;
@@ -83,16 +132,13 @@ class ValueAxisLogarithmic extends Axis {
if (y <= im.yorigin - im.ysize) {
break;
}
worker.drawLine(x0 - 1, y, x0 + 1, y, gridColor, gdef.tickStroke);
worker.drawLine(x1 - 1, y, x1 + 1, y, gridColor, gdef.tickStroke);
worker.drawLine(x0, y, x1, y, gridColor, gdef.gridStroke);
drawAxisLines.accept(y, value, gridColor);
}
}
// Draw minor grid for negative values
double negativeMin = -1.0 * ((im.maxval < 0.0) ? im.maxval : 0.0);
for (double value = Math.pow(10, log10(negativeMin)
- log10(negativeMin) % log10(yloglab[minoridx][0]));
double negativeMin = -1.0 * (Math.min(im.maxval, 0.0));
for (double value = findStart(negativeMin, minoridx);
value <= -1.0 * im.minval;
value *= yloglab[minoridx][0]) {
if (value < negativeMin) continue;
@@ -102,9 +148,7 @@ class ValueAxisLogarithmic extends Axis {
if (y <= im.yorigin - im.ysize) {
break;
}
worker.drawLine(x0 - 1, y, x0 + 1, y, gridColor, gdef.tickStroke);
worker.drawLine(x1 - 1, y, x1 + 1, y, gridColor, gdef.tickStroke);
worker.drawLine(x0, y, x1, y, gridColor, gdef.gridStroke);
drawAxisLines.accept(y, value, gridColor);
}
}
@@ -113,18 +157,14 @@ class ValueAxisLogarithmic extends Axis {
if (im.minval < 0.0 && im.maxval > 0.0) {
skipFirst = true;
int y = mapper.ytr(0.0);
worker.drawLine(x0 - 2, y, x0 + 2, y, mGridColor, gdef.tickStroke);
worker.drawLine(x1 - 2, y, x1 + 2, y, mGridColor, gdef.tickStroke);
worker.drawLine(x0, y, x1, y, mGridColor, gdef.gridStroke);
String graph_label = Util.sprintf(gdef.locale, "%3.0e", 0.0);
int length = (int) (worker.getStringWidth(graph_label, font));
worker.drawString(graph_label, x0 - length - PADDING_VLABEL, y + labelOffset, font, fontColor);
drawAxisLines.accept(y, 0.0, mGridColor);
drawAxisLabel.accept(y, 0.0);
}
// Draw major grid for positive values
int iter = 0;
for (double value = Math.pow(10, log10(positiveMin)
- (log10(positiveMin) % log10(yloglab[majoridx][0])));
int lasty = Integer.MAX_VALUE;
for (double value = findStart(positiveMin, majoridx);
value <= im.maxval;
value *= yloglab[majoridx][0]) {
if (value < positiveMin) {
@@ -140,19 +180,18 @@ class ValueAxisLogarithmic extends Axis {
if (y <= im.yorigin - im.ysize) {
break;
}
worker.drawLine(x0 - 2, y, x0 + 2, y, mGridColor, gdef.tickStroke);
worker.drawLine(x1 - 2, y, x1 + 2, y, mGridColor, gdef.tickStroke);
worker.drawLine(x0, y, x1, y, mGridColor, gdef.gridStroke);
String graph_label = Util.sprintf(gdef.locale, "%3.0e", value * yloglab[majoridx][i]);
int length = (int) (worker.getStringWidth(graph_label, font));
worker.drawString(graph_label, x0 - length - PADDING_VLABEL, y + labelOffset, font, fontColor);
// Avoid collision of labels
if ((lasty - y) > fontHeight) {
drawAxisLines.accept(y, value, mGridColor);
drawAxisLabel.accept(y, value * yloglab[majoridx][i]);
lasty = y;
}
}
}
// Draw major grid for negative values
iter = 0;
for (double value = Math.pow(10, log10(negativeMin)
- (log10(negativeMin) % log10(yloglab[majoridx][0])));
for (double value = findStart(negativeMin, majoridx);
value <= -1.0 * im.minval;
value *= yloglab[majoridx][0]) {
if (value < negativeMin) {
@@ -168,28 +207,12 @@ class ValueAxisLogarithmic extends Axis {
if (y <= im.yorigin - im.ysize) {
break;
}
worker.drawLine(x0 - 2, y, x0 + 2, y, mGridColor, gdef.tickStroke);
worker.drawLine(x1 - 2, y, x1 + 2, y, mGridColor, gdef.tickStroke);
worker.drawLine(x0, y, x1, y, mGridColor, gdef.gridStroke);
String graph_label = Util.sprintf(gdef.locale, "%3.0e", -1.0 * value * yloglab[majoridx][i]);
int length = (int) (worker.getStringWidth(graph_label, font));
worker.drawString(graph_label, x0 - length - PADDING_VLABEL, y + labelOffset, font, fontColor);
drawAxisLines.accept(y, value, mGridColor);
drawAxisLabel.accept(y, -1.0 * value * yloglab[majoridx][i]);
}
}
return true;
}
/**
* Compute logarithm for the purposes of y-axis.
*/
static double log10(double v) {
double lv = Math.log10(Math.abs(v));
if (lv < 0) {
// Don't cross the sign line, round to 0 if that's the case
return 0.0;
} else {
return Math.copySign(lv, v);
}
}
}

View File

@@ -10,15 +10,22 @@ class ValueAxisMrtg extends Axis {
private final ImageWorker worker;
private final RrdGraphDef gdef;
ValueAxisMrtg(RrdGraph rrdGraph) {
this(rrdGraph, rrdGraph.worker);
}
/**
* Used for tests
*
* @param rrdGraph
* @param worker
*/
ValueAxisMrtg(RrdGraph rrdGraph, ImageWorker worker) {
this.im = rrdGraph.im;
this.gdef = rrdGraph.gdef;
this.worker = worker;
im.unit = gdef.unit;
}
ValueAxisMrtg(RrdGraphGenerator generator) {
this.im = generator.im;
this.gdef = generator.gdef;
this.worker = generator.worker;
}
boolean draw() {
@@ -40,14 +47,14 @@ class ValueAxisMrtg extends Axis {
else {
labfmt = Util.sprintf(gdef.locale, "%%4.%df", 1 - ((im.scaledstep / im.magfact > 10.0 || Math.ceil(im.scaledstep / im.magfact) == im.scaledstep / im.magfact) ? 1 : 0));
}
if (im.symbol != ' ' || im.unit != null) {
if (im.symbol != ' ' || gdef.unit != null) {
labfmt += " ";
}
if (im.symbol != ' ') {
labfmt += Character.toString(im.symbol);
}
if (im.unit != null) {
labfmt += im.unit;
if (gdef.unit != null) {
labfmt += gdef.unit;
}
for (int i = 0; i <= 4; i++) {
int y = im.yorigin - im.ysize * i / 4;
@@ -55,8 +62,10 @@ class ValueAxisMrtg extends Axis {
String graph_label = Util.sprintf(gdef.locale, labfmt, im.scaledstep / im.magfact * (i - im.quadrant));
int length = (int) (worker.getStringWidth(graph_label, font));
worker.drawString(graph_label, xLeft - length - PADDING_VLABEL, y + labelOffset, font, fontColor);
worker.drawLine(xLeft - 2, y, xLeft + 2, y, mGridColor, gdef.tickStroke);
worker.drawLine(xRight - 2, y, xRight + 2, y, mGridColor, gdef.tickStroke);
if (gdef.drawTicks()) {
worker.drawLine(xLeft - 2, y, xLeft + 2, y, mGridColor, gdef.tickStroke);
worker.drawLine(xRight - 2, y, xRight + 2, y, mGridColor, gdef.tickStroke);
}
worker.drawLine(xLeft, y, xRight, y, mGridColor, gdef.gridStroke);
}
}

View File

@@ -1,11 +1,6 @@
package org.rrd4j.graph;
class ValueScaler {
static final String UNIT_UNKNOWN = "?";
static final String[] UNIT_SYMBOLS = {
"a", "f", "p", "n", "µ", "m", " ", "K", "M", "G", "T", "P", "E"
};
static final int SYMB_CENTER = 6;
private final double base;
private double magfact = -1; // nothing scaled before, rescale
@@ -48,16 +43,11 @@ class ValueScaler {
sindex = (int) (Math.floor(Math.log(Math.abs(value)) / Math.log(base)));
magfact = Math.pow(base, sindex);
}
if (sindex <= SYMB_CENTER && sindex >= -SYMB_CENTER) {
unit = UNIT_SYMBOLS[sindex + SYMB_CENTER];
// I2P show 0.xxx instead of xxx m
if (unit.equals("m")) {
unit = "";
magfact *= 1000;
}
}
else {
unit = UNIT_UNKNOWN;
unit = String.valueOf(FindUnit.resolveSymbol(sindex));
// I2P show 0.xxx instead of xxx m
if (unit.equals("m")) {
unit = "";
magfact *= 1000;
}
return new Scaled(value / magfact, unit);
}

View File

@@ -1,4 +1,7 @@
2024-08-07 2.7.0 (API 0.9.64) released
2024-10-22 zzz
* Console: Merge in more upstream rrd4j changes for 3.10
2024-10-08 2.7.0 (API 0.9.64) released
2024-10-08 idk
* Fix failing tests
@@ -38,7 +41,7 @@
2024-08-09 zzz
* i2psnark: Reduce minimum banwdith, reduce max connections if low bandwidth
* i2psnark, susimail: Normailze strings when searching (Gitlab #488)
* i2psnark, susimail: Normalize strings when searching (Gitlab #488)
* susimail: Fix searches for multiple terms
* Tunnels: Do not select ElG routers for tunnels
* Util: Reduce number of PRNG output buffers

View File

@@ -20,7 +20,7 @@ public class RouterVersion {
public final static String VERSION = CoreVersion.VERSION;
/** for example: "beta", "alpha", "rc" */
public final static String QUALIFIER = "";
public final static long BUILD = 0;
public final static long BUILD = 1;
/** for example "-test" */
public final static String EXTRA = "";
public final static String FULL_VERSION = VERSION + "-" + BUILD + QUALIFIER + EXTRA;