Graph.java
package ac.essex.graphing.plotting;
import ac.essex.graphing.plotting.Plotter;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.Vector;
/**
* <p/>
* Graph.java<br />
* Renders graphs using the Java 2D API
* </p>
* <p/>
* All customisable settings are defined in PlotSettings.java
* </p>
* <p/>
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version,
* provided that any use properly credits the author.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details at http://www.gnu.org
* </p>
*
* @author Olly Oechsle, University of Essex
* @see ac.essex.graphing.plotting.PlotSettings
* @version 1.1
*/
public class Graph {
public static final String VERSION = "Java Plot 1.1";
/**
* A graph may plot as many functions as it wants
* These may all be of different types.
*/
public Vector<Plotter> functions;
/**
* The area and general settings of the graph are all defined
* by a PlotArea object.
*/
public PlotSettings plotSettings;
/**
* Initialises the graph with the plot settings to use.
*/
public Graph(PlotSettings p) {
this.functions = new Vector<Plotter>(5);
this.plotSettings = p;
}
protected double plotRangeX, plotRangeY;
/**
* How many pixels are there available to use in the graph?
* This is the size of the image minus the border size.
*/
protected int chartWidth, chartHeight;
protected double unitsPerPixelX, unitsPerPixelY;
/**
* Draws the graph using a graphics object.
*
* Note, X axis labels come from the first function (this only applies to discrete functions)
*
* @param g The graphics context on which to draw
* @param width The width to make the graph
* @param height The height to make the graph
*/
public void draw(Graphics g, int width, int height) {
/**
* Draw the title
*/
if (plotSettings.title != null) {
g.setColor(plotSettings.fontColor);
// ensure the border top can accommodate the title
if (plotSettings.marginTop < g.getFontMetrics().getHeight() + 20) {
plotSettings.marginTop = g.getFontMetrics().getHeight() + 20;
}
int titleXPosition = (width / 2) - ((g.getFontMetrics().stringWidth(plotSettings.title)) / 2);
g.drawString(plotSettings.title, titleXPosition, 10 + g.getFontMetrics().getHeight());
}
/**
* Calculate the plot range
*/
plotRangeX = Math.abs(plotSettings.maxX - plotSettings.minX);
plotRangeY = Math.abs(plotSettings.maxY - plotSettings.minY);
/*
First we need to know how many pixels there are across the panel
And we can divide that number between the range that we've been assigned.
*/
chartWidth = width - (plotSettings.marginLeft + plotSettings.marginRight);
chartHeight = height - (plotSettings.marginTop + plotSettings.marginBottom);
/*
Calculate the number of units per pixel
*/
unitsPerPixelX = plotRangeX / chartWidth;
unitsPerPixelY = plotRangeY / chartHeight;
/**
* Set the background colour
*/
g.setColor(plotSettings.backgroundColor);
g.fillRect(plotSettings.marginLeft, plotSettings.marginTop, chartWidth - 1, chartHeight - 1);
int columnIndex = 0;
/**
* Draw X Axis Notches
*/
double firstGridXLocation = ((int) (plotSettings.getMinX() / plotSettings.getGridSpacingX())) * plotSettings.getGridSpacingX();
for (double px = firstGridXLocation; px <= plotSettings.getMaxX(); px += plotSettings.getGridSpacingX()) {
if (px < plotSettings.getMinX()) continue;
// find the position of each point and draw a line
int plotX = getPlotX(px);
int plotY = plotSettings.marginTop + chartHeight;
// vertical grid lines
if (plotSettings.verticalGridVisible) {
g.setColor(plotSettings.gridColor);
g.drawLine(plotX, plotSettings.marginTop, plotX, plotY);
}
// and draw a notch on the X axis.
g.setColor(plotSettings.axisColor);
g.drawLine(plotX, plotY, plotX, plotY + plotSettings.notchLength);
// work out the value at this point and draw
String value;
int labelXPosition;
// Note: X Axis labels come from the first function
Plotter function = functions.elementAt(0);
if (function instanceof DiscreteFunctionPlotter) {
DiscreteFunctionPlotter discrete = (DiscreteFunctionPlotter) function;
value = discrete.getLabel(columnIndex);
int columnWidth = chartWidth / discrete.getColumnCount();
int columnCenterX = (columnIndex * columnWidth) + (columnWidth / 2);
labelXPosition = columnCenterX - ((g.getFontMetrics().stringWidth(value)) / 2) + plotSettings.marginLeft;
} else {
//value = plotSettings.numberFormatter.format(plotSettings.minX + (plotRangeX * labelX));
value = plotSettings.numberFormatter.format(px);
labelXPosition = plotX - (g.getFontMetrics().stringWidth(value)) / 2;
}
// draw the value underneath the notch
g.setColor(plotSettings.fontColor);
g.drawString(value, labelXPosition, plotY + plotSettings.notchLength + g.getFontMetrics().getHeight() - 1 + plotSettings.notchGap);
columnIndex++;
}
/**
* Draw Y Axis Notches and Labels
*/
double firstGridYLocation = ((int) (plotSettings.getMinY() / plotSettings.getGridSpacingY())) * plotSettings.getGridSpacingY();
for (double py = firstGridYLocation; py <= plotSettings.getMaxY(); py += plotSettings.getGridSpacingY()) {
if (py < plotSettings.getMinY()) continue;
// find the position of each point and draw a line
int plotX = plotSettings.marginLeft;
int plotY = getPlotY(py);
// horizontal gridColor lines
if (plotSettings.horizontalGridVisible) {
g.setColor(plotSettings.gridColor);
g.drawLine(plotSettings.marginLeft, plotY, plotSettings.marginLeft + chartWidth - 1, plotY);
}
// draw a notch on the Y axis
g.setColor(plotSettings.axisColor);
g.drawLine(plotX, plotY, plotX - plotSettings.notchLength, plotY);
// work out the value at this point and draw
String value = plotSettings.numberFormatter.format(py);
// work out how wide this string is
int textXOffset = (g.getFontMetrics().stringWidth(value));
g.setColor(plotSettings.fontColor);
g.drawString(value, plotX - plotSettings.notchLength - textXOffset - plotSettings.notchGap, plotY + (g.getFontMetrics().getHeight() / 2) - 1);
}
/**
* Draw a box around the whole graph to delimit the Axes
*/
g.setColor(plotSettings.axisColor);
g.drawRect(plotSettings.marginLeft, plotSettings.marginTop, chartWidth, chartHeight);
/**
* Draw the horizontal and vertical axes that go through the point at 0,0.
*/
int yEqualsZero = getPlotY(0) + 0;
if (0 > plotSettings.getMinY() && 0 < plotSettings.getMaxY())
g.drawLine(plotSettings.marginLeft, yEqualsZero, plotSettings.marginLeft + chartWidth - 1, yEqualsZero);
int xEqualsZero = getPlotX(0) + 0;
if (0 > plotSettings.getMinX() && 0 < plotSettings.getMaxX())
g.drawLine(xEqualsZero, plotSettings.marginTop, xEqualsZero, plotSettings.marginTop + chartHeight);
/**
* And finally - draw the results of the function onto the chart.
*/
for (int i = 0; i < functions.size(); i++) {
Plotter function = functions.elementAt(i);
g.setColor(plotSettings.getPlotColor());
function.plot(this, g, chartWidth, chartHeight);
}
}
/**
* Uses the numeric value of Y (as returned by a function) and
* figures out which pixel on screen this relates to.
*/
public int getPlotY(double y) {
/**
* Convert Y into pixel coordinates again
*/
int pixelY = ((int) ((y - plotSettings.minY) / unitsPerPixelY));
/**
* We also need to flip the Y axis because Y is counted from the top
* and not the bottom. Add the various borders
*/
return ((chartHeight - pixelY) + plotSettings.marginTop);
}
/**
* Uses the numeric value of X, and figures out which pixel on the screen
* this relates to.
*/
public int getPlotX(double x) {
return (int) (((x - plotSettings.minX) / unitsPerPixelX) + plotSettings.marginLeft);
}
/**
* Takes a numeric distance and calculates how many actual pixels high that is.
*/
public double getActualHeight(double height) {
return height / unitsPerPixelY;
}
/**
* Takes a numeric distance and calculates how many actual pixels wide that is.
*/
public double getActualWidth(double width) {
return width / unitsPerPixelX;
}
/**
* Takes a set number of actual pixels on the screen (in the Y direction)
* And returns how long they are, if plotted on the graph.
*/
public double getPlotHeight(double height) {
return height * unitsPerPixelY;
}
/**
* Takes a set number of actual pixels on the screen (in the X direction)
* And returns how long they are, if plotted on the graph.
*/
public double getPlotWidth(double width) {
return width * unitsPerPixelX;
}
public double getActualX(int pixelX) {
return plotSettings.minX + (pixelX * unitsPerPixelX);
}
/**
* Plots a line between two sets of values.
*
* @param g Graphics context upon which to write
* @param x1 First point X
* @param y1 First point Y
* @param x2 Second point X
* @param y2 Second point Y
*/
public void drawLine(Graphics g, double x1, double y1, double x2, double y2) {
g.drawLine(getPlotX(x1), getPlotY(y1), getPlotX(x2), getPlotY(y2));
}
/**
* Draws a bar
*
* @param g Graphics context upon which to write
* @param totalColumns How many columns are there in total?
* @param columnIndex The index of the column, starting at zero (determines which bar to draw)
* @param height How high should the bar be
* @param fill What colour should the bar be?
*/
public void drawBar(Graphics g, double columnWidth, int columnIndex, double height, Color fill) {
/**
* The gap on each side of the column
*/
final double hgap = 0.1;
/**
* The actual height of the bar
*/
int barHeight = (int) getActualHeight(height);
/**
* The Y position at the top of the bar.
*/
int maxPlotY = getPlotY(height);
/**
* Where to start drawing the bar in the X direction
*/
int columnStartX = getPlotX(columnIndex * columnWidth);
int gap = (int) +(getActualWidth(columnWidth) * hgap);
// Draw the bar:
g.setColor(fill);
g.fillRect(columnStartX + gap, maxPlotY, (int) getActualWidth(columnWidth) - (gap * 2), barHeight);
g.setColor(Color.BLACK);
g.drawRect(columnStartX + gap, maxPlotY, (int) getActualWidth(columnWidth) - (gap * 2), barHeight);
}
/**
* Draws a candlestick plot, which I've chosen to define as a set of three values: high, mean and low.
*
* @param g The graphics context upon which to draw
* @param columnWidth How many units wide is a column?
* @param columnIndex The index of the column, starting at zero (determines which bar to draw)
* @param high The highest value
* @param mean The average value
* @param low The lowest value
*/
public void drawCandleStick(Graphics g, double columnWidth, int columnIndex, double high, double mean, double low, Color lineColor, Color backgroundColor) {
/**
* Find out how much room there is for each "column"
*/
int halfColumnWidth = (int) getActualWidth(columnWidth / 2);
final int bigNotchWidth = halfColumnWidth / 2;
final int smallNotchWidth = halfColumnWidth / 3;
/**
* Where to start drawing the candlestick in the X direction
*/
int columnX = getPlotX(columnIndex * columnWidth) + halfColumnWidth;
// get screen coordinates
int maxPlotY = getPlotY(high);
int meanPlotY = getPlotY(mean);
int minPlotY = getPlotY(low);
// the vertical line
if (backgroundColor != null) {
g.setColor(backgroundColor);
g.fillRect(columnX - smallNotchWidth, maxPlotY, smallNotchWidth * 2, (int) getActualHeight(high - low) + 1);
}
g.setColor(lineColor);
g.drawLine(columnX, minPlotY, columnX, maxPlotY);
// notch at top for the high value
g.drawLine(columnX - bigNotchWidth, minPlotY, columnX + bigNotchWidth, minPlotY);
// notch at the middle for the mean value
g.drawLine(columnX - smallNotchWidth, meanPlotY, columnX + smallNotchWidth, meanPlotY);
// notch at the bottom for the low value
g.drawLine(columnX - bigNotchWidth, maxPlotY, columnX + bigNotchWidth, maxPlotY);
}
/**
* Returns the graph as an image so that it can be saved.
*/
public BufferedImage getImage(int width, int height) {
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics g = image.getGraphics();
g.setColor(plotSettings.backgroundColor);
g.fillRect(0, 0, width, height);
draw(g, width, height);
return image;
}
}