Merge branch 'master' of i2pgit.org:i2p-hackers/i2p.i2p
This commit is contained in:
@@ -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));
|
||||
|
@@ -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.
|
||||
*
|
||||
|
@@ -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());
|
||||
}
|
||||
|
||||
|
@@ -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() {
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -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>
|
||||
|
@@ -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><tag></code> and <code></tag></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("<", "<").replace(">", ">");
|
||||
}
|
||||
|
@@ -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
|
||||
|
25
apps/jrobin/java/src/org/rrd4j/graph/FindUnit.java
Normal file
25
apps/jrobin/java/src/org/rrd4j/graph/FindUnit.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -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;
|
||||
}
|
||||
|
@@ -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() {
|
||||
|
57
apps/jrobin/java/src/org/rrd4j/graph/LogService.java
Normal file
57
apps/jrobin/java/src/org/rrd4j/graph/LogService.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -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) {
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
725
apps/jrobin/java/src/org/rrd4j/graph/RrdGraphGenerator.java
Normal file
725
apps/jrobin/java/src/org/rrd4j/graph/RrdGraphGenerator.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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) {
|
||||
|
71
apps/jrobin/java/src/org/rrd4j/graph/TimeUnit.java
Normal file
71
apps/jrobin/java/src/org/rrd4j/graph/TimeUnit.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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;
|
||||
|
Reference in New Issue
Block a user