package net.iponweb.disthene.reader.graph;
import net.iponweb.disthene.reader.beans.TimeSeries;
import net.iponweb.disthene.reader.beans.TimeSeriesOption;
import net.iponweb.disthene.reader.exceptions.LogarithmicScaleNotAllowed;
import net.iponweb.disthene.reader.graphite.utils.GraphiteUtils;
import net.iponweb.disthene.reader.handler.parameters.ImageParameters;
import net.iponweb.disthene.reader.handler.parameters.RenderParameters;
import org.apache.log4j.Logger;
import org.joda.time.DateTime;
import org.joda.time.Seconds;
import org.joda.time.format.DateTimeFormat;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* @author Andrei Ivanov
* <p/>
* This class and those below in hierarchy are pure translations from graphite-web Python code.
* This will probably changed some day. But for now reverse engineering the logic is too comaplicated.
*/
public abstract class Graph {
final static Logger logger = Logger.getLogger(Graph.class);
private static final double[] PRETTY_VALUES = {0.1, 0.2, 0.25, 0.5, 1.0, 1.2, 1.25, 1.5, 2.0, 2.25, 2.5};
protected ImageParameters imageParameters;
protected RenderParameters renderParameters;
protected List<DecoratedTimeSeries> data = new ArrayList<>();
protected List<DecoratedTimeSeries> dataLeft = new ArrayList<>();
protected List<DecoratedTimeSeries> dataRight = new ArrayList<>();
protected boolean secondYAxis = false;
protected int xMin;
protected int xMax;
protected int yMin;
protected int yMax;
protected int graphWidth;
protected int graphHeight;
protected long startTime = Long.MAX_VALUE;
protected long endTime = Long.MIN_VALUE;
protected DateTime startDateTime;
protected DateTime endDateTime;
protected double yStep;
protected double yBottom;
protected double yTop;
protected double ySpan;
protected double yScaleFactor;
protected double yStepL;
protected double yStepR;
protected double yBottomL;
protected double yBottomR;
protected double yTopL;
protected double yTopR;
protected double ySpanL;
protected double ySpanR;
protected double yScaleFactorL;
protected double yScaleFactorR;
protected List<Double> yLabelValues;
protected List<String> yLabels;
protected int yLabelWidth;
protected List<Double> yLabelValuesL;
protected List<Double> yLabelValuesR;
protected List<String> yLabelsL;
protected List<String> yLabelsR;
protected int yLabelWidthL;
protected int yLabelWidthR;
protected double xScaleFactor;
protected XAxisConfig xAxisConfig;
protected long xLabelStep;
protected long xMinorGridStep;
protected long xMajorGridStep;
protected BufferedImage image;
protected Graphics2D g2d;
public static Graph getInstance(GraphType type, RenderParameters renderParameters, List<TimeSeries> data) {
if (type.equals(GraphType.PIE)) {
return new PieGraph(renderParameters, data);
} else {
return new LineGraph(renderParameters, data);
}
}
public Graph(RenderParameters renderParameters, List<TimeSeries> data) {
this.renderParameters = renderParameters;
this.imageParameters = renderParameters.getImageParameters();
for (TimeSeries ts : data) {
this.data.add(new DecoratedTimeSeries(ts));
}
xMin = imageParameters.getMargin() + 10;
xMax = imageParameters.getWidth() - imageParameters.getMargin();
yMin = imageParameters.getMargin();
yMax = imageParameters.getHeight() - imageParameters.getMargin();
image = new BufferedImage(imageParameters.getWidth(), imageParameters.getHeight(), BufferedImage.TYPE_INT_ARGB);
g2d = image.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
g2d.setPaint(imageParameters.getBackgroundColor());
g2d.fillRect(0, 0, imageParameters.getWidth(), imageParameters.getHeight());
}
public abstract byte[] drawGraph() throws LogarithmicScaleNotAllowed;
protected byte[] getBytes() {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
ImageIO.write(image, "png", baos);
return baos.toByteArray();
} catch (IOException e) {
logger.error(e);
return new byte[0];
}
}
protected void drawText(int x, int y, String text, HorizontalAlign horizontalAlign, VerticalAlign verticalAlign) {
drawText(x, y, text, imageParameters.getFont(), imageParameters.getForegroundColor(), horizontalAlign, verticalAlign, 0);
}
protected void drawText(int x, int y, String text, Font font, Color color, HorizontalAlign horizontalAlign, VerticalAlign verticalAlign) {
drawText(x, y, text, font, color, horizontalAlign, verticalAlign, 0);
}
protected void drawText(int x, int y, String text, Font font, Color color, HorizontalAlign horizontalAlign, VerticalAlign verticalAlign, double rotate) {
g2d.setPaint(color);
g2d.setFont(font);
FontMetrics fontMetrics = g2d.getFontMetrics(font);
int textWidth = fontMetrics.stringWidth(text);
int horizontal, vertical;
switch (horizontalAlign) {
case RIGHT:
horizontal = textWidth;
break;
case CENTER:
horizontal = textWidth / 2;
break;
default:
horizontal = 0;
break;
}
switch (verticalAlign) {
case MIDDLE:
vertical = fontMetrics.getHeight() / 2 - fontMetrics.getDescent();
break;
case BOTTOM:
vertical = -fontMetrics.getDescent();
break;
case BASELINE:
vertical = 0;
break;
default:
vertical = fontMetrics.getAscent();
}
double angle = Math.toRadians(rotate);
AffineTransform orig = g2d.getTransform();
// g2d.rotate(angle, x, y);
g2d.rotate(angle, x - Math.sin(Math.toRadians(angle) * vertical), y + Math.cos(Math.toRadians(angle) * vertical));
g2d.drawString(text, x - horizontal, y + vertical);
g2d.setTransform(orig);
}
protected void drawVerticalTitle(Boolean alignRight) {
Font font = new Font(imageParameters.getFont().getName(), imageParameters.getFont().getStyle(),
(int) (imageParameters.getFont().getSize() + Math.log(imageParameters.getFont().getSize())));
FontMetrics fontMetrics = g2d.getFontMetrics(font);
int lineHeight = fontMetrics.getHeight();
if (alignRight) {
int x = xMax - lineHeight;
int y = imageParameters.getHeight() / 2;
String[] split = imageParameters.getVerticalTitle().split("\n");
for (String line : split) {
drawText(x, y, line, font, imageParameters.getForegroundColor(), HorizontalAlign.CENTER, VerticalAlign.BASELINE, -90);
x -= lineHeight;
}
xMax = x - imageParameters.getMargin() - lineHeight;
} else {
int x = xMin + lineHeight;
int y = imageParameters.getHeight() / 2;
String[] split = imageParameters.getVerticalTitle().split("\n");
for (String line : split) {
drawText(x, y, line, font, imageParameters.getForegroundColor(), HorizontalAlign.CENTER, VerticalAlign.BASELINE, -90);
x += lineHeight;
}
xMin = x + imageParameters.getMargin() + lineHeight;
}
}
protected void drawTitle() {
int y = yMin;
int x = imageParameters.getWidth() / 2;
Font font = new Font(imageParameters.getFont().getName(), imageParameters.getFont().getStyle(),
(int) (imageParameters.getFont().getSize() + Math.log(imageParameters.getFont().getSize())));
FontMetrics fontMetrics = g2d.getFontMetrics(font);
int lineHeight = fontMetrics.getHeight();
String[] split = imageParameters.getTitle().split("\n");
for (String line : split) {
drawText(x, y, line, font, imageParameters.getForegroundColor(), HorizontalAlign.CENTER, VerticalAlign.TOP);
y += lineHeight;
}
if (imageParameters.getyAxisSide().equals(ImageParameters.Side.RIGHT)) {
yMin = y;
} else {
yMin = y + imageParameters.getMargin();
}
}
protected void drawLegend(List<String> legends, List<Color> colors, List<Boolean> secondYAxes, boolean uniqueLegend) {
// remove duplicate names
List<String> legendsUnique = new ArrayList<>();
List<Color> colorsUnique = new ArrayList<>();
List<Boolean> secondYAxesUnique = new ArrayList<>();
if (uniqueLegend) {
for (int i = 0; i < legends.size(); i++) {
if (!legendsUnique.contains(legends.get(i))) {
legendsUnique.add(legends.get(i));
colorsUnique.add(colors.get(i));
secondYAxesUnique.add(secondYAxes.get(i));
}
}
legends = legendsUnique;
colors = colorsUnique;
secondYAxes = secondYAxesUnique;
}
FontMetrics fontMetrics = g2d.getFontMetrics(imageParameters.getFont());
// Check if there's enough room to use two columns
boolean rightSideLabels = false;
int padding = 5;
String longestLegend = Collections.max(legends, new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return s1.length() - s2.length();
}
});
// Double it to check if there's enough room for 2 columns
String testSizeName = longestLegend + " " + longestLegend;
int testBoxSize = fontMetrics.getHeight() - 1;
int testWidth = fontMetrics.stringWidth(testSizeName) + 2 * (testBoxSize + padding);
if (testWidth + 50 < imageParameters.getWidth()) {
rightSideLabels = true;
}
if (secondYAxis && rightSideLabels) {
int boxSize = fontMetrics.getHeight() - 1;
int lineHeight = fontMetrics.getHeight() + 1;
int labelWidth = fontMetrics.stringWidth(longestLegend) + 2 * (boxSize + padding);
int columns = (int) Math.max(1, Math.floor((imageParameters.getWidth() - xMin) / labelWidth));
int numRight = 0;
for (Boolean b : secondYAxes) {
if (b) numRight++;
}
int numberOfLines = Math.max(legends.size() - numRight, numRight);
columns = (int) Math.floor(columns / 2.0);
if (columns < 1) columns = 1;
int legendHeight = (int) (Math.max(1, ((double) numberOfLines / columns)) * (lineHeight + padding));
yMax -= legendHeight;
int x = xMin;
int y = yMax + 2 * padding;
int n = 0;
int xRight = xMax - xMin;
int yRight = y;
int nRight = 0;
for (int i = 0; i < legends.size(); i++) {
g2d.setPaint(colors.get(i));
if (secondYAxes.get(i)) {
nRight++;
g2d.fillRect(xRight - padding, yRight, boxSize, boxSize);
g2d.setPaint(ColorTable.DARK_GRAY);
g2d.drawRect(xRight - padding, yRight, boxSize, boxSize);
drawText(xRight - boxSize, yRight, legends.get(i), imageParameters.getFont(), imageParameters.getForegroundColor(), HorizontalAlign.RIGHT, VerticalAlign.TOP);
xRight -= labelWidth;
if (nRight % columns == 0) {
xRight = xMax - xMin;
yRight += lineHeight;
}
} else {
n++;
g2d.fillRect(x, y, boxSize, boxSize);
g2d.setPaint(ColorTable.DARK_GRAY);
g2d.drawRect(x, y, boxSize, boxSize);
drawText(x + boxSize + padding, y, legends.get(i), imageParameters.getFont(), imageParameters.getForegroundColor(), HorizontalAlign.LEFT, VerticalAlign.TOP);
x += labelWidth;
if (n % columns == 0) {
x = xMin;
y += lineHeight;
}
}
}
} else {
int boxSize = fontMetrics.getHeight() - 1;
int lineHeight = fontMetrics.getHeight() + 1;
int labelWidth = fontMetrics.stringWidth(longestLegend) + 2 * (boxSize + padding);
int columns = (int) Math.floor(imageParameters.getWidth() / labelWidth);
if (columns < 1) columns = 1;
int numberOfLines = (int) Math.ceil((double) legends.size() / columns);
int legendHeight = numberOfLines * (lineHeight + padding);
yMax -= legendHeight;
g2d.setStroke(new BasicStroke(1f));
int x = xMin;
int y = yMax + (2 * padding);
for (int i = 0; i < legends.size(); i++) {
if (secondYAxes.get(i)) {
g2d.setPaint(colors.get(i));
g2d.fillRect(x + labelWidth + padding, y, boxSize, boxSize);
g2d.setPaint(ColorTable.DARK_GRAY);
g2d.drawRect(x + labelWidth + padding, y, boxSize, boxSize);
drawText(x + labelWidth, y, legends.get(i), imageParameters.getFont(), imageParameters.getForegroundColor(), HorizontalAlign.RIGHT, VerticalAlign.TOP);
x += labelWidth;
} else {
g2d.setPaint(colors.get(i));
g2d.fillRect(x, y, boxSize, boxSize);
g2d.setPaint(ColorTable.DARK_GRAY);
g2d.drawRect(x, y, boxSize, boxSize);
drawText(x + boxSize + padding, y, legends.get(i), imageParameters.getFont(), imageParameters.getForegroundColor(), HorizontalAlign.LEFT, VerticalAlign.TOP);
x += labelWidth;
}
if ((i + 1) % columns == 0) {
x = xMin;
y += lineHeight;
}
}
}
}
protected void consolidateDataPoints() {
int numberOfPixels = (int) (xMax - xMin - imageParameters.getLineWidth() - 1);
graphWidth = (int) (xMax - xMin - imageParameters.getLineWidth() - 1);
for (DecoratedTimeSeries ts : data) {
double numberOfDataPoints = ts.getValues().length;
double divisor = ts.getValues().length - 1;
double bestXStep = numberOfPixels / divisor;
if (bestXStep < imageParameters.getMinXStep()) {
int drawableDataPoints = numberOfPixels / imageParameters.getMinXStep();
double pointsPerPixel = Math.ceil(numberOfDataPoints / drawableDataPoints);
ts.setValuesPerPoint((int) pointsPerPixel);
ts.setxStep((numberOfPixels * pointsPerPixel) / numberOfDataPoints);
} else {
ts.setxStep(bestXStep);
}
}
}
protected void setupTwoYAxes() throws LogarithmicScaleNotAllowed {
List<DecoratedTimeSeries> seriesWithMissingValuesL = new ArrayList<>();
List<DecoratedTimeSeries> seriesWithMissingValuesR = new ArrayList<>();
for (DecoratedTimeSeries ts : dataLeft) {
for (Double value : ts.getValues()) {
if (value == null) {
seriesWithMissingValuesL.add(ts);
break;
}
}
}
for (DecoratedTimeSeries ts : dataRight) {
for (Double value : ts.getValues()) {
if (value == null) {
seriesWithMissingValuesR.add(ts);
break;
}
}
}
double yMinValueL = Double.POSITIVE_INFINITY;
double yMinValueR = Double.POSITIVE_INFINITY;
double yMaxValueL;
double yMaxValueR;
if (imageParameters.isDrawNullAsZero() && seriesWithMissingValuesL.size() > 0) {
yMinValueL = 0;
} else {
for (DecoratedTimeSeries ts : dataLeft) {
if (!ts.hasOption(TimeSeriesOption.DRAW_AS_INFINITE)) {
double mm = GraphUtils.safeMin(ts);
yMinValueL = mm < yMinValueL ? mm : yMinValueL;
}
}
}
if (imageParameters.isDrawNullAsZero() && seriesWithMissingValuesR.size() > 0) {
yMinValueR = 0;
} else {
for (DecoratedTimeSeries ts : dataRight) {
if (!ts.hasOption(TimeSeriesOption.DRAW_AS_INFINITE)) {
double mm = GraphUtils.safeMin(ts);
yMinValueR = mm < yMinValueR ? mm : yMinValueR;
}
}
}
yMaxValueL = GraphUtils.safeMax(dataLeft);
yMaxValueR = GraphUtils.safeMax(dataRight);
/*
if (getStackedData(dataLeft).size() > 0) {
yMaxValueL = GraphUtils.maxSum(dataLeft);
} else {
yMaxValueL = GraphUtils.safeMax(dataLeft);
}
*/
/*
if (getStackedData(dataRight).size() > 0) {
yMaxValueR = GraphUtils.maxSum(dataRight);
} else {
yMaxValueR = GraphUtils.safeMax(dataRight);
}
*/
if (yMinValueL == Double.POSITIVE_INFINITY) {
yMinValueL = 0.0;
}
if (yMinValueR == Double.POSITIVE_INFINITY) {
yMinValueR = 0.0;
}
if (imageParameters.getyMaxLeft() < Double.POSITIVE_INFINITY) {
yMaxValueL = imageParameters.getyMaxLeft();
}
if (imageParameters.getyMaxRight() < Double.POSITIVE_INFINITY) {
yMaxValueR = imageParameters.getyMaxRight();
}
if (imageParameters.getyMinLeft() > Double.NEGATIVE_INFINITY) {
yMinValueL = imageParameters.getyMinLeft();
}
if (imageParameters.getyMinRight() > Double.NEGATIVE_INFINITY) {
yMinValueR = imageParameters.getyMinRight();
}
if (yMaxValueL <= yMinValueL) {
yMaxValueL = yMinValueL + 1;
}
if (yMaxValueR <= yMinValueR) {
yMaxValueR = yMinValueR + 1;
}
double yVarianceL = yMaxValueL - yMinValueL;
double yVarianceR = yMaxValueR - yMinValueR;
double orderL = Math.log10(yVarianceL);
double orderR = Math.log10(yVarianceR);
double orderFactorL = Math.pow(10, Math.floor(orderL));
double orderFactorR = Math.pow(10, Math.floor(orderR));
double vL = yVarianceL / orderFactorL;
double vR = yVarianceR / orderFactorR;
double distance = Double.POSITIVE_INFINITY;
double prettyValueL = PRETTY_VALUES[0];
double prettyValueR = PRETTY_VALUES[0];
for (int i = 0; i < imageParameters.getyDivisors().size(); i++) {
double q = vL / imageParameters.getyDivisors().get(i);
double p = GraphUtils.closest(q, PRETTY_VALUES);
if (Math.abs(q - p) < distance) {
distance = Math.abs(q - p);
prettyValueL = p;
}
}
distance = Double.POSITIVE_INFINITY;
for (int i = 0; i < imageParameters.getyDivisors().size(); i++) {
double q = vR / imageParameters.getyDivisors().get(i);
double p = GraphUtils.closest(q, PRETTY_VALUES);
if (Math.abs(q - p) < distance) {
distance = Math.abs(q - p);
prettyValueR = p;
}
}
yStepL = prettyValueL * orderFactorL;
yStepR = prettyValueR * orderFactorR;
if (imageParameters.getyStepLeft() < Double.POSITIVE_INFINITY) {
yStepL = imageParameters.getyStepLeft();
}
if (imageParameters.getyStepRight() < Double.POSITIVE_INFINITY) {
yStepR = imageParameters.getyStepRight();
}
yBottomL = yStepL * Math.floor(yMinValueL / yStepL);
yBottomR = yStepR * Math.floor(yMinValueR / yStepR);
yTopL = yStepL * Math.ceil(yMaxValueL / yStepL);
yTopR = yStepR * Math.ceil(yMaxValueR / yStepR);
if (imageParameters.getLogBase() != 0 && yMaxValueL > 0) {
yBottomL = Math.pow(imageParameters.getLogBase(), Math.floor(Math.log(yMinValueL) / Math.log(imageParameters.getLogBase())));
yTopL = Math.pow(imageParameters.getLogBase(), Math.ceil(Math.log(yMaxValueL) / Math.log(imageParameters.getLogBase())));
} else if (imageParameters.getLogBase() != 0 && yMinValueL <= 0) {
throw new LogarithmicScaleNotAllowed("Logarithmic scale specified with a dataset with a minimum value less than or equal to zero");
}
if (imageParameters.getLogBase() != 0 && yMaxValueR > 0) {
yBottomR = Math.pow(imageParameters.getLogBase(), Math.floor(Math.log(yMinValueR) / Math.log(imageParameters.getLogBase())));
yTopR = Math.pow(imageParameters.getLogBase(), Math.ceil(Math.log(yMaxValueR) / Math.log(imageParameters.getLogBase())));
} else if (imageParameters.getLogBase() != 0 && yMinValueR <= 0) {
throw new LogarithmicScaleNotAllowed("Logarithmic scale specified with a dataset with a minimum value less than or equal to zero");
}
if (imageParameters.getyMaxLeft() < Double.POSITIVE_INFINITY) {
yTopL = imageParameters.getyMaxLeft();
}
if (imageParameters.getyMaxRight() < Double.POSITIVE_INFINITY) {
yTopR = imageParameters.getyMaxRight();
}
if (imageParameters.getyMinLeft() > Double.NEGATIVE_INFINITY) {
yBottomL = imageParameters.getyMinLeft();
}
if (imageParameters.getyMinRight() > Double.NEGATIVE_INFINITY) {
yBottomR = imageParameters.getyMinRight();
}
ySpanL = yTopL - yBottomL;
ySpanR = yTopR - yBottomR;
if (ySpanL == 0) {
yTopL++;
ySpanL++;
}
if (ySpanR == 0) {
yTopR++;
ySpanR++;
}
graphHeight = yMax - yMin;
yScaleFactorL = graphHeight / ySpanL;
yScaleFactorR = graphHeight / ySpanR;
// Round the values a bit
yBottomR = GraphiteUtils.magicRound(yBottomR);
yTopR = GraphiteUtils.magicRound(yTopR);
yStepR = GraphiteUtils.magicRound(yStepR);
yBottomL = GraphiteUtils.magicRound(yBottomL);
yTopL = GraphiteUtils.magicRound(yTopL);
yStepL = GraphiteUtils.magicRound(yStepL);
yLabelValuesL = getYLabelValues(yBottomL, yTopL, yStepL);
yLabelValuesR = getYLabelValues(yBottomR, yTopR, yStepR);
FontMetrics fontMetrics = g2d.getFontMetrics(imageParameters.getFont());
yLabelsL = new ArrayList<>();
yLabelsR = new ArrayList<>();
for (Double value : yLabelValuesL) {
String label = makeLabel(value, yStepL, ySpanL);
yLabelsL.add(label);
if (fontMetrics.stringWidth(label) > yLabelWidthL) yLabelWidthL = fontMetrics.stringWidth(label);
}
for (Double value : yLabelValuesR) {
String label = makeLabel(value, yStepR, ySpanR);
yLabelsR.add(label);
if (fontMetrics.stringWidth(label) > yLabelWidthR) yLabelWidthR = fontMetrics.stringWidth(label);
}
int xxMin = (int) (imageParameters.getMargin() + (yLabelWidthL * 1.15));
if (xMin < xxMin) {
xMin = xxMin;
}
int xxMax = (int) (imageParameters.getWidth() - (yLabelWidthR * 1.15));
if (xMax >= xxMax) {
xMax = xxMax;
}
}
protected void setupYAxis() throws LogarithmicScaleNotAllowed {
List<DecoratedTimeSeries> seriesWithMissingValues = new ArrayList<>();
for (DecoratedTimeSeries ts : data) {
for (Double value : ts.getValues()) {
if (value == null) {
seriesWithMissingValues.add(ts);
break;
}
}
}
double yMinValue = Double.POSITIVE_INFINITY;
for (DecoratedTimeSeries ts : data) {
if (!ts.hasOption(TimeSeriesOption.DRAW_AS_INFINITE)) {
double mm = GraphUtils.safeMin(ts);
yMinValue = mm < yMinValue ? mm : yMinValue;
}
}
if (yMinValue > 0 && imageParameters.isDrawNullAsZero() && seriesWithMissingValues.size() > 0) {
yMinValue = 0;
}
double yMaxValue;
yMaxValue = GraphUtils.safeMax(data);
/*
if (getStackedData(data).size() > 0) {
yMaxValue = GraphUtils.maxSum(data);
} else {
yMaxValue = GraphUtils.safeMax(data);
}
*/
if (yMaxValue < 0 && imageParameters.isDrawNullAsZero() && seriesWithMissingValues.size() > 0) {
yMaxValue = 0;
}
if (yMinValue == Double.POSITIVE_INFINITY) {
yMinValue = 0;
}
if (yMaxValue == Double.NEGATIVE_INFINITY) {
yMaxValue = 0;
}
if (imageParameters.getyMax() != Double.POSITIVE_INFINITY) {
yMaxValue = imageParameters.getyMax();
}
if (imageParameters.getyMin() != Double.NEGATIVE_INFINITY) {
yMinValue = imageParameters.getyMin();
}
if (yMaxValue <= yMinValue) {
yMaxValue = yMinValue + 1;
}
double yVariance = yMaxValue - yMinValue;
double order;
double orderFactor;
order = Math.log10(yVariance);
orderFactor = Math.pow(10, Math.floor(order));
double v = yVariance / orderFactor;
double distance = Double.POSITIVE_INFINITY;
double prettyValue = PRETTY_VALUES[0];
for (int i = 0; i < imageParameters.getyDivisors().size(); i++) {
double q = v / imageParameters.getyDivisors().get(i);
double p = GraphUtils.closest(q, PRETTY_VALUES);
if (Math.abs(q - p) < distance) {
distance = Math.abs(q - p);
prettyValue = p;
}
}
yStep = prettyValue * orderFactor;
if (imageParameters.getyStep() < Double.POSITIVE_INFINITY) {
yStep = imageParameters.getyStep();
}
yBottom = yStep * Math.floor(yMinValue / yStep);
yTop = yStep * Math.ceil(yMaxValue / yStep);
if (imageParameters.getLogBase() != 0 && yMaxValue > 0) {
yBottom = Math.pow(imageParameters.getLogBase(), Math.floor(Math.log(yMinValue) / Math.log(imageParameters.getLogBase())));
yTop = Math.pow(imageParameters.getLogBase(), Math.ceil(Math.log(yMaxValue) / Math.log(imageParameters.getLogBase())));
} else if (imageParameters.getLogBase() != 0 && yMinValue <= 0) {
throw new LogarithmicScaleNotAllowed("Logarithmic scale specified with a dataset with a minimum value less than or equal to zero");
}
if (imageParameters.getyMax() != Double.POSITIVE_INFINITY) {
yTop = imageParameters.getyMax();
}
if (imageParameters.getyMin() != Double.NEGATIVE_INFINITY) {
yBottom = imageParameters.getyMin();
}
ySpan = yTop - yBottom;
if (ySpan == 0) {
yTop++;
ySpan++;
}
graphHeight = yMax - yMin;
yScaleFactor = graphHeight / ySpan;
// Round the values a bit
yBottom = GraphiteUtils.magicRound(yBottom);
yTop = GraphiteUtils.magicRound(yTop);
yStep = GraphiteUtils.magicRound(yStep);
if (!imageParameters.isHideAxes()) {
yLabelValues = getYLabelValues(yBottom, yTop, yStep);
yLabels = new ArrayList<>();
yLabelWidth = 0;
FontMetrics fontMetrics = g2d.getFontMetrics(imageParameters.getFont());
for (Double value : yLabelValues) {
String label = makeLabel(value);
yLabels.add(label);
if (fontMetrics.stringWidth(label) > yLabelWidth) yLabelWidth = fontMetrics.stringWidth(label);
}
if (!imageParameters.isHideYAxis()) {
if (imageParameters.getyAxisSide().equals(ImageParameters.Side.LEFT)) {
int xxMin = (int) (imageParameters.getMargin() + yLabelWidth * 1.15);
if (xMin < xxMin) {
xMin = xxMin;
}
} else {
int xxMax = imageParameters.getWidth() - imageParameters.getMargin() - (int) (yLabelWidth * 1.15);
if (xMax >= xxMax) {
xMax = xxMax;
}
}
}
} else {
yLabelValues = new ArrayList<>();
yLabels = new ArrayList<>();
yLabelWidth = 0;
}
}
protected void setupXAxis() {
startDateTime = new DateTime(startTime * 1000, renderParameters.getTz());
endDateTime = new DateTime(endTime * 1000, renderParameters.getTz());
double secondsPerPixel = (endTime - startTime) / (double) graphWidth;
xScaleFactor = (double) graphWidth / (endTime - startTime);
xAxisConfig = XAxisConfigProvider.getXAxisConfig(secondsPerPixel, endTime - startTime);
xLabelStep = xAxisConfig.getLabelUnit() * xAxisConfig.getLabelStep();
xMinorGridStep = (long) (xAxisConfig.getMinorGridUnit() * xAxisConfig.getMinorGridStep());
xMajorGridStep = xAxisConfig.getMajorGridUnit() * xAxisConfig.getMajorGridStep();
}
protected void drawLabels() {
// Draw the Y-labels
if (!imageParameters.isHideYAxis()) {
if (!secondYAxis) {
for (int i = 0; i < yLabelValues.size(); i++) {
int x;
if (imageParameters.getyAxisSide().equals(ImageParameters.Side.LEFT)) {
x = (int) (xMin - (yLabelWidth * 0.15));
} else {
x = (int) (xMax + (yLabelWidth * 0.15));
}
int y = getYCoord(yLabelValues.get(i));
if (y < 0) y = 0;
if (imageParameters.getyAxisSide().equals(ImageParameters.Side.LEFT)) {
drawText(x, y, yLabels.get(i), HorizontalAlign.RIGHT, VerticalAlign.MIDDLE);
} else {
drawText(x, y, yLabels.get(i), HorizontalAlign.LEFT, VerticalAlign.MIDDLE);
}
}
} else {
for (int i = 0; i < yLabelValuesL.size(); i++) {
int x = (int) (xMin - (yLabelWidthL * 0.15));
int y = getYCoordLeft(yLabelValuesL.get(i));
if (y < 0) y = 0;
drawText(x, y, yLabelsL.get(i), HorizontalAlign.RIGHT, VerticalAlign.MIDDLE);
}
for (int i = 0; i < yLabelValuesR.size(); i++) {
int x = (int) (xMax + (yLabelWidthR * 0.15));
int y = getYCoordRight(yLabelValuesR.get(i));
if (y < 0) y = 0;
drawText(x, y, yLabelsR.get(i), HorizontalAlign.LEFT, VerticalAlign.MIDDLE);
}
}
}
// Draw the X-labels
long labelDt = 0;
long labelDelta = 1;
if (xAxisConfig.getLabelUnit() == XAxisConfigProvider.SEC) {
labelDt = startTime - startTime % xAxisConfig.getLabelStep();
labelDelta = (long) xAxisConfig.getLabelStep();
} else if (xAxisConfig.getLabelUnit() == XAxisConfigProvider.MIN) {
DateTime tdt = new DateTime(startTime * 1000, renderParameters.getTz());
labelDt = tdt.withSecondOfMinute(0).withMinuteOfHour(tdt.getMinuteOfHour() - (tdt.getMinuteOfHour() % xAxisConfig.getLabelStep())).getMillis() / 1000;
labelDelta = (long) xAxisConfig.getLabelStep() * 60;
} else if (xAxisConfig.getLabelUnit() == XAxisConfigProvider.HOUR) {
DateTime tdt = new DateTime(startTime * 1000, renderParameters.getTz());
labelDt = tdt.withSecondOfMinute(0).withMinuteOfHour(0).withHourOfDay(tdt.getHourOfDay() - (tdt.getHourOfDay() % xAxisConfig.getLabelStep())).getMillis() / 1000;
labelDelta = (long) xAxisConfig.getLabelStep() * 60 * 60;
} else if (xAxisConfig.getLabelUnit() == XAxisConfigProvider.DAY) {
DateTime tdt = new DateTime(startTime * 1000, renderParameters.getTz());
labelDt = tdt.withSecondOfMinute(0).withMinuteOfHour(0).withHourOfDay(0).getMillis() / 1000;
labelDelta = (long) xAxisConfig.getLabelStep() * 60 * 60 * 24;
}
while (labelDt < startTime) labelDt += labelDelta;
DateTime ddt = new DateTime(labelDt * 1000, renderParameters.getTz());
FontMetrics fontMetrics = g2d.getFontMetrics(imageParameters.getFont());
while (ddt.isBefore(endDateTime)) {
String label = ddt.toString(DateTimeFormat.forPattern(xAxisConfig.getFormat()));
int x = (int) (xMin + (Seconds.secondsBetween(startDateTime, ddt).getSeconds() * xScaleFactor));
int y = yMax + fontMetrics.getMaxAscent();
drawText(x, y, label, HorizontalAlign.CENTER, VerticalAlign.TOP);
ddt = ddt.plusSeconds((int) labelDelta);
}
}
protected void drawGridLines() {
g2d.setStroke(new BasicStroke(0f));
//Horizontal grid lines
int leftSide = xMin;
int rightSide = xMax;
List<Double> labelValues = secondYAxis ? yLabelValuesL : yLabelValues;
for (int i = 0; i < labelValues.size(); i++) {
g2d.setColor(imageParameters.getMajorGridLineColor());
int y = secondYAxis ? getYCoordLeft(labelValues.get(i)) : getYCoord(labelValues.get(i));
if (y < 0) continue;
g2d.drawLine(leftSide, y, rightSide, y);
// draw minor gridlines if this isn't the last label
g2d.setColor(imageParameters.getMinorGridLineColor());
if (imageParameters.getMinorY() >= 1 && i < (labelValues.size() - 1)) {
double distance = ((labelValues.get(i + 1) - labelValues.get(i)) / (1 + imageParameters.getMinorY()));
for (int minor = 0; minor < imageParameters.getMinorY(); minor++) {
double minorValue = (labelValues.get(i) + ((1 + minor) * distance));
int yTopFactor = imageParameters.getLogBase() != 0 ? (int) (imageParameters.getLogBase() * imageParameters.getLogBase()) : 1;
if (secondYAxis) {
if (minorValue > yTopFactor * yTopL) continue;
} else {
if (minorValue > yTopFactor * yTop) continue;
}
int yMinor = secondYAxis ? getYCoordLeft(minorValue) : getYCoord(minorValue);
if (yMinor < 0) continue;
g2d.drawLine(leftSide, yMinor, rightSide, yMinor);
}
}
}
// Vertical grid lines
int top = yMin;
int bottom = yMax;
long dt = 0;
long delta = 1;
if (xAxisConfig.getMinorGridUnit() == XAxisConfigProvider.SEC) {
dt = startTime - (long) (startTime % xAxisConfig.getMinorGridStep());
delta = (long) xAxisConfig.getMinorGridStep();
} else if (xAxisConfig.getMinorGridUnit() == XAxisConfigProvider.MIN) {
DateTime tdt = new DateTime(startTime * 1000, renderParameters.getTz());
dt = tdt.withSecondOfMinute(0).withMinuteOfHour((int) (tdt.getMinuteOfHour() - (tdt.getMinuteOfHour() % xAxisConfig.getMinorGridStep()))).getMillis() / 1000;
delta = (long) xAxisConfig.getMinorGridStep() * 60;
} else if (xAxisConfig.getMinorGridUnit() == XAxisConfigProvider.HOUR) {
DateTime tdt = new DateTime(startTime * 1000, renderParameters.getTz());
dt = tdt.withSecondOfMinute(0).withMinuteOfHour(0).withHourOfDay((int) (tdt.getHourOfDay() - (tdt.getHourOfDay() % xAxisConfig.getMinorGridStep()))).getMillis() / 1000;
delta = (long) xAxisConfig.getMinorGridStep() * 60 * 60;
} else if (xAxisConfig.getMinorGridUnit() == XAxisConfigProvider.DAY) {
DateTime tdt = new DateTime(startTime * 1000, renderParameters.getTz());
dt = tdt.withSecondOfMinute(0).withMinuteOfHour(0).withHourOfDay(0).getMillis() / 1000;
delta = (long) xAxisConfig.getMinorGridStep() * 60 * 60 * 24;
}
while (dt < startTime) dt += delta;
DateTime ddt = new DateTime(dt * 1000, renderParameters.getTz());
g2d.setColor(imageParameters.getMinorGridLineColor());
while (ddt.isBefore(endDateTime)) {
int x = (int) (xMin + (Seconds.secondsBetween(startDateTime, ddt).getSeconds() * xScaleFactor));
if (x < xMax) {
g2d.drawLine(x, bottom, x, top);
}
ddt = ddt.plusSeconds((int) delta);
}
// Now we do the major grid lines
g2d.setColor(imageParameters.getMajorGridLineColor());
long majorDt = 0;
long majorDelta = 1;
if (xAxisConfig.getMajorGridUnit() == XAxisConfigProvider.SEC) {
majorDt = startTime - startTime % xAxisConfig.getMajorGridStep();
majorDelta = (long) xAxisConfig.getMajorGridStep();
} else if (xAxisConfig.getMajorGridUnit() == XAxisConfigProvider.MIN) {
DateTime tdt = new DateTime(startTime * 1000, renderParameters.getTz());
majorDt = tdt.withSecondOfMinute(0).withMinuteOfHour(tdt.getMinuteOfHour() - (tdt.getMinuteOfHour() % xAxisConfig.getMajorGridStep())).getMillis() / 1000;
majorDelta = (long) xAxisConfig.getMajorGridStep() * 60;
} else if (xAxisConfig.getMajorGridUnit() == XAxisConfigProvider.HOUR) {
DateTime tdt = new DateTime(startTime * 1000, renderParameters.getTz());
majorDt = tdt.withSecondOfMinute(0).withMinuteOfHour(0).withHourOfDay(tdt.getHourOfDay() - (tdt.getHourOfDay() % xAxisConfig.getMajorGridStep())).getMillis() / 1000;
majorDelta = (long) xAxisConfig.getMajorGridStep() * 60 * 60;
} else if (xAxisConfig.getMajorGridUnit() == XAxisConfigProvider.DAY) {
DateTime tdt = new DateTime(startTime * 1000, renderParameters.getTz());
majorDt = tdt.withSecondOfMinute(0).withMinuteOfHour(0).withHourOfDay(0).getMillis() / 1000;
majorDelta = (long) xAxisConfig.getMajorGridStep() * 60 * 60 * 24;
}
while (majorDt < startTime) majorDt += majorDelta;
ddt = new DateTime(majorDt * 1000, renderParameters.getTz());
while (ddt.isBefore(endDateTime)) {
int x = (int) (xMin + (Seconds.secondsBetween(startDateTime, ddt).getSeconds() * xScaleFactor));
if (x < xMax) {
g2d.drawLine(x, bottom, x, top);
}
ddt = ddt.plusSeconds((int) majorDelta);
}
//Draw side borders for our graph area
g2d.drawLine(xMax, bottom, xMax, top);
g2d.drawLine(xMin, bottom, xMin, top);
}
private void drawLines(List<DecoratedTimeSeries> timeSeriesList) {
Rectangle rectangle = new Rectangle(xMin, yMin, xMax - xMin + 1, yMax - yMin + 1);
g2d.clip(rectangle);
for (DecoratedTimeSeries ts : timeSeriesList) {
g2d.setStroke(getStroke(ts));
g2d.setColor(getColor(ts));
GeneralPath path = new GeneralPath();
double x = xMin;
int y;
Double[] values = ts.getConsolidatedValues();
int consecutiveNulls = 0;
boolean allNullsSoFar = true;
for (Double value : values) {
Double adjustedValue = value;
if (adjustedValue == null && imageParameters.isDrawNullAsZero()) adjustedValue = 0.;
if (adjustedValue == null) {
/*
if (consecutiveNulls == 0) {
path.lineTo(x, y);
}
*/
x += ts.getxStep();
consecutiveNulls++;
continue;
}
if (secondYAxis) {
if (ts.hasOption(TimeSeriesOption.SECOND_Y_AXIS)) {
y = getYCoordRight(adjustedValue);
} else {
y = getYCoordLeft(adjustedValue);
}
} else {
y = getYCoord(adjustedValue);
}
y = y < 0 ? 0 : y;
if (path.getCurrentPoint() == null) {
path.moveTo(x, y);
}
if (ts.hasOption(TimeSeriesOption.DRAW_AS_INFINITE) && adjustedValue > 0) {
path.moveTo((int) x, yMax);
path.lineTo((int) x, yMin);
x += ts.getxStep();
continue;
}
if (imageParameters.getLineMode().equals(ImageParameters.LineMode.SLOPE)) {
if (consecutiveNulls > 0) {
path.moveTo(x, y);
}
path.lineTo(x, y);
} else if (imageParameters.getLineMode().equals(ImageParameters.LineMode.STAIRCASE)) {
if (consecutiveNulls > 0) {
path.moveTo(x, y);
} else {
path.lineTo(x, y);
}
path.lineTo(x + ts.getxStep(), y);
} else if (imageParameters.getLineMode().equals(ImageParameters.LineMode.CONNECTED)) {
if (consecutiveNulls > imageParameters.getConnectedLimit() || allNullsSoFar) {
path.moveTo(x, y);
allNullsSoFar = false;
}
path.lineTo((int) x, y);
}
consecutiveNulls = 0;
x += ts.getxStep();
}
g2d.draw(path);
}
}
private void drawStacked(List<DecoratedTimeSeries> timeSeriesList) {
if (timeSeriesList.size() == 0) return;
Shape savedClip = g2d.getClip();
g2d.clip(new Rectangle(xMin, yMin, xMax - xMin, yMax - yMin));
for (DecoratedTimeSeries ts : timeSeriesList) {
// We will be constructing general path for each time series
GeneralPath path = new GeneralPath();
path.moveTo(xMin, yMax);
g2d.setPaint(getPaint(ts));
double x = xMin;
double startX = x;
int y = yMax;
Double[] values = ts.getConsolidatedValues();
int consecutiveNulls = 0;
boolean allNullsSoFar = true;
for (Double value : values) {
Double adjustedValue = value;
if (value == null && imageParameters.isDrawNullAsZero()) adjustedValue = 0.;
if (adjustedValue == null) {
if (consecutiveNulls == 0) {
path.lineTo(x, y);
if (secondYAxis) {
if (ts.hasOption(TimeSeriesOption.SECOND_Y_AXIS)) {
fillAreaAndClip(path, x, startX, getYCoordRight(0));
} else {
fillAreaAndClip(path, x, startX, getYCoordLeft(0));
}
} else {
fillAreaAndClip(path, x, startX, getYCoord(0));
}
}
x += ts.getxStep();
consecutiveNulls++;
} else {
if (secondYAxis) {
if (ts.hasOption(TimeSeriesOption.SECOND_Y_AXIS)) {
y = getYCoordRight(adjustedValue);
} else {
y = getYCoordLeft(adjustedValue);
}
} else {
y = getYCoord(adjustedValue);
}
y = y < 0 ? 0 : y;
if (consecutiveNulls > 0) startX = x;
if (imageParameters.getLineMode().equals(ImageParameters.LineMode.STAIRCASE)) {
if (consecutiveNulls > 0) {
path.moveTo(x, y);
} else {
path.lineTo(x, y);
}
x += ts.getxStep();
path.lineTo(x, y);
} else if (imageParameters.getLineMode().equals(ImageParameters.LineMode.SLOPE)) {
if (consecutiveNulls > 0) {
path.moveTo(x, y);
}
path.lineTo(x, y);
x += ts.getxStep();
} else if (imageParameters.getLineMode().equals(ImageParameters.LineMode.CONNECTED)) {
if (consecutiveNulls > imageParameters.getConnectedLimit() || allNullsSoFar) {
path.moveTo(x, y);
allNullsSoFar = false;
}
path.lineTo(x, y);
x += ts.getxStep();
}
consecutiveNulls = 0;
}
}
double xPos;
if (imageParameters.getLineMode().equals(ImageParameters.LineMode.STAIRCASE)) {
xPos = x;
} else {
xPos = x - ts.getxStep();
}
if (consecutiveNulls == 0) {
if (secondYAxis) {
if (ts.hasOption(TimeSeriesOption.SECOND_Y_AXIS)) {
fillAreaAndClip(path, xPos, startX, getYCoordRight(0));
} else {
fillAreaAndClip(path, xPos, startX, getYCoordLeft(0));
}
} else {
fillAreaAndClip(path, xPos, startX, getYCoord(0));
}
}
}
g2d.setClip(savedClip);
}
protected void fillAreaAndClip(GeneralPath path, double x, double startX, int yTo) {
GeneralPath pattern = new GeneralPath(path);
path.lineTo(x, yTo);
path.lineTo(startX, yTo);
path.closePath();
g2d.fill(path);
pattern.lineTo(x, yTo);
pattern.lineTo(xMax, yTo);
pattern.lineTo(xMax, yMin);
pattern.lineTo(xMin, yMin);
pattern.lineTo(xMin, yTo);
pattern.lineTo(startX, yTo);
pattern.lineTo(x, yTo);
pattern.lineTo(xMax, yTo);
pattern.lineTo(xMax, yMax);
pattern.lineTo(xMin, yMax);
pattern.lineTo(xMin, yTo);
pattern.lineTo(startX, yTo);
pattern.closePath();
g2d.clip(pattern);
}
protected void drawData() {
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
drawStacked(getStackedData(data));
drawLines(getLineData(data));
}
private List<DecoratedTimeSeries> getLineData(List<DecoratedTimeSeries> data) {
List<DecoratedTimeSeries> result = new ArrayList<>();
for (DecoratedTimeSeries ts : data) {
if (!ts.hasOption(TimeSeriesOption.STACKED)) {
result.add(ts);
}
}
return result;
}
protected List<DecoratedTimeSeries> getStackedData(List<DecoratedTimeSeries> data) {
List<DecoratedTimeSeries> result = new ArrayList<>();
for (DecoratedTimeSeries ts : data) {
if (ts.hasOption(TimeSeriesOption.STACKED)) {
result.add(ts);
}
}
return result;
}
private int getYCoordRight(double value) {
double highestValue = yLabelValuesR.size() > 0 ? Collections.max(yLabelValuesR) : yTopR;
double lowestValue = yLabelValuesR.size() > 0 ? Collections.min(yLabelValuesR) : yBottomR;
int pixelRange = yMax - yMin;
double relativeValue = value - lowestValue;
double valueRange = highestValue - lowestValue;
if (imageParameters.getLogBase() != 0) {
if (value < 0) {
return -1;
}
relativeValue = Math.log(value) / Math.log(imageParameters.getLogBase()) - Math.log(lowestValue) / Math.log(imageParameters.getLogBase());
valueRange = Math.log(highestValue) / Math.log(imageParameters.getLogBase()) - Math.log(lowestValue) / Math.log(imageParameters.getLogBase());
}
double pixelToValueRatio = pixelRange / valueRange;
double valueInPixels = pixelToValueRatio * relativeValue;
return (int) (yMax - valueInPixels);
}
private int getYCoordLeft(double value) {
double highestValue = yLabelValuesL.size() > 0 ? Collections.max(yLabelValuesL) : yTopL;
double lowestValue = yLabelValuesL.size() > 0 ? Collections.min(yLabelValuesL) : yBottomL;
int pixelRange = yMax - yMin;
double relativeValue = value - lowestValue;
double valueRange = highestValue - lowestValue;
if (imageParameters.getLogBase() != 0) {
if (value < 0) {
return -1;
}
relativeValue = Math.log(value) / Math.log(imageParameters.getLogBase()) - Math.log(lowestValue) / Math.log(imageParameters.getLogBase());
valueRange = Math.log(highestValue) / Math.log(imageParameters.getLogBase()) - Math.log(lowestValue) / Math.log(imageParameters.getLogBase());
}
double pixelToValueRatio = pixelRange / valueRange;
double valueInPixels = pixelToValueRatio * relativeValue;
return (int) (yMax - valueInPixels);
}
private int getYCoord(double value) {
double highestValue = yLabelValues.size() > 0 ? Collections.max(yLabelValues) : yTop;
double lowestValue = yLabelValues.size() > 0 ? Collections.min(yLabelValues) : yBottom;
int pixelRange = yMax - yMin;
double relativeValue = value - lowestValue;
double valueRange = highestValue - lowestValue;
if (imageParameters.getLogBase() != 0) {
if (value < 0) {
return -1;
}
relativeValue = Math.log(value) / Math.log(imageParameters.getLogBase()) - Math.log(lowestValue) / Math.log(imageParameters.getLogBase());
valueRange = Math.log(highestValue) / Math.log(imageParameters.getLogBase()) - Math.log(lowestValue) / Math.log(imageParameters.getLogBase());
}
double pixelToValueRatio = pixelRange / valueRange;
double valueInPixels = pixelToValueRatio * relativeValue;
return (int) (yMax - valueInPixels);
}
private List<Double> getYLabelValues(double min, double max, double step) {
if (imageParameters.getLogBase() != 0) {
return logRange(imageParameters.getLogBase(), min, max);
} else {
return fRange(step, min, max);
}
}
protected String makeLabel(double value, double step, double span) {
double tmpValue = GraphiteUtils.formatUnitValue(value, step, imageParameters.getyUnitSystem());
String prefix = GraphiteUtils.formatUnitPrefix(value, step, imageParameters.getyUnitSystem());
double ySpan = GraphiteUtils.formatUnitValue(span, step, imageParameters.getyUnitSystem());
String spanPrefix = GraphiteUtils.formatUnitPrefix(span, step, imageParameters.getyUnitSystem());
value = tmpValue;
if (value < 0.1) {
return value + " " + prefix;
} else if (value < 1.0) {
return String.format("%.2f %s", value, prefix);
}
if (ySpan > 10 || !spanPrefix.equals(prefix)) {
return String.format("%s %s", value, prefix);
} else if (ySpan > 3) {
return String.format("%.1f %s", value, prefix);
} else if (ySpan > 0.1) {
return String.format("%.2f %s", value, prefix);
} else {
return value + prefix;
}
}
protected String makeLabel(double value) {
return makeLabel(value, yStep, ySpan);
}
private List<Double> logRange(double base, double min, double max) {
List<Double> result = new ArrayList<>();
double current = min;
if (min > 0) {
current = Math.floor(Math.log(min) / Math.log(base));
}
double factor = current;
while (current < max) {
current = Math.pow(base, factor);
result.add(current);
factor++;
}
return result;
}
// todo: this "magic rounding" is a complete atrocity - fix it!
private List<Double> fRange(double step, double min, double max) {
List<Double> result = new ArrayList<>();
BigDecimal bf = BigDecimal.valueOf(min);
BigDecimal bMax = BigDecimal.valueOf(max);
BigDecimal bMin = BigDecimal.valueOf(min);
BigDecimal bStep = BigDecimal.valueOf(step);
while (bf.compareTo(bMax) <= 0) {
result.add(GraphiteUtils.magicRound(bf).doubleValue());
bf = bf.add(bStep);
if (bf.compareTo(bMin) == 0) {
result.add(max);
break;
}
}
return result;
}
private Color getColor(DecoratedTimeSeries timeSeries) {
if (timeSeries.hasOption(TimeSeriesOption.INVISIBLE)) {
return ColorTable.INVISIBLE;
}
Color c = (Color) timeSeries.getOption(TimeSeriesOption.COLOR);
return new Color(c.getRed(), c.getGreen(), c.getBlue(), timeSeries.hasOption(TimeSeriesOption.ALPHA) ? (int) ((Float) timeSeries.getOption(TimeSeriesOption.ALPHA) * 255) : 255);
}
private Color getPaint(DecoratedTimeSeries timeSeries) {
if (timeSeries.hasOption(TimeSeriesOption.INVISIBLE)) {
return ColorTable.INVISIBLE;
}
Color c = (Color) timeSeries.getOption(TimeSeriesOption.COLOR);
return new Color(c.getRed(), c.getGreen(), c.getBlue(), (int)((float) (imageParameters.getAreaAlpha() * 255)));
}
private Stroke getStroke(DecoratedTimeSeries timeSeries) {
float lineWidth;
if (timeSeries.hasOption(TimeSeriesOption.LINE_WIDTH)) {
lineWidth = (float) timeSeries.getOption(TimeSeriesOption.LINE_WIDTH);
} else {
lineWidth = imageParameters.getLineWidth().floatValue();
}
boolean isDashed = false;
float dashLength = 0f;
if (timeSeries.hasOption(TimeSeriesOption.DASHED)) {
isDashed = true;
dashLength = (float) timeSeries.getOption(TimeSeriesOption.DASHED);
}
if (isDashed) {
return new BasicStroke(lineWidth, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, new float[]{dashLength, dashLength}, 0.0f);
} else {
return new BasicStroke(lineWidth, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);
}
}
//todo: move enums to separate classes?
protected enum HorizontalAlign {
LEFT, CENTER, RIGHT
}
protected enum VerticalAlign {
TOP, MIDDLE, BOTTOM, BASELINE
}
public enum GraphType {
LINE, PIE
}
public enum PieMode {
AVERAGE, MAXIMUM, MINIMUM
}
public enum PieLabelsStyle {
PERCENT, NUMBER, NONE
}
public enum PieLabelsOrientation {
HORIZONTAL, ROTATED
}
}