From: elserj Date: Wed, 25 Jul 2012 20:35:38 +0000 (+0000) Subject: Added HeatChart.java and modified it from original jar file to print the species... X-Git-Url: http://gitweb.planteome.org/?a=commitdiff_plain;h=d9231e116601a57a027cd22572032cc536006e29;p=old-jaiswallab-svn%2F.git Added HeatChart.java and modified it from original jar file to print the species on top. Also added some color and other options to the heatchart drawing. svn path=/; revision=369 --- diff --git a/Personnel/miles/2/src/HeatChart.java b/Personnel/miles/2/src/HeatChart.java new file mode 100644 index 0000000..7d6334d --- /dev/null +++ b/Personnel/miles/2/src/HeatChart.java @@ -0,0 +1,1782 @@ +/* + * Copyright 2010 Tom Castle (www.tc33.org) + * Licensed under GNU Lesser General Public License + * + * This file is part of JHeatChart - the heat maps charting api for Java. + * + * JHeatChart is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * JHeatChart 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. + * + * You should have received a copy of the GNU Lesser General Public License + * along with JHeatChart. If not, see . + */ +//package heatchart; + + +import java.awt.*; +import java.awt.geom.AffineTransform; +import java.awt.image.BufferedImage; +import java.io.*; +import java.util.Iterator; + +import javax.imageio.*; +import javax.imageio.stream.FileImageOutputStream; + +/** + * The HeatChart class describes a chart which can display + * 3-dimensions of values - x,y and z, where x and y are the usual 2-dimensional + * axis and z is portrayed by colour intensity. Heat charts are sometimes known + * as heat maps. + * + *

+ * Use of this chart would typically involve 3 steps: + *

    + *
  1. Construction of a new instance, providing the necessary z-values.
  2. + *
  3. Configure the visual settings.
  4. + *
  5. A call to either getChartImage() or saveToFile(String).
  6. + *
+ * + *

Instantiation

+ *

+ * Construction of a new HeatChart instance is through its one + * constructor which takes a 2-dimensional array of doubles which + * should contain the z-values for the chart. Consider this array to be + * the grid of values which will instead be represented as colours in the chart. + * + *

+ * Setting of the x-values and y-values which are displayed along the + * appropriate axis is optional, and by default will simply display the values + * 0 to n-1, where n is the number of rows or columns. Otherwise, the x/y axis + * values can be set with the setXValues and setYValues + * methods. Both methods are overridden with two forms: + * + *

Object axis values

+ * + *

+ * The simplist way to set the axis values is to use the methods which take an + * array of Object[]. This array must have the same length as the number of + * columns for setXValues and same as the number of rows for setYValues. The + * string representation of the objects will then be used as the axis values. + * + *

Offset and Interval

+ * + *

+ * This is convenient way of defining numerical values along the axis. One of + * the two methods takes an interval and an offset for either the + * x or y axis. These parameters supply the necessary information to describe + * the values based upon the z-value indexes. The quantity of x-values and + * y-values is already known from the lengths of the z-values array dimensions. + * Then the offset parameters indicate what the first value will be, with the + * intervals providing the increment from one column or row to the next. + * + *

+ * Consider an example: + *

+ * double[][] zValues = new double[][]{
+ * 		{1.2, 1.3, 1.5},
+ * 		{1.0, 1.1, 1.6},
+ * 		{0.7, 0.9, 1.3}
+ * };
+ * 
+ * double xOffset = 1.0;
+ * double yOffset = 0.0;
+ * double xInterval = 1.0;
+ * double yInterval = 2.0;
+ * 
+ * chart.setXValues(xOffset, xInterval);
+ * chart.setYValues(yOffset, yInterval);
+ * 
+ * + *

In this example, the z-values range from 0.7 to 1.6. The x-values range + * from the xOffset value 1.0 to 4.0, which is calculated as the number of x-values + * multiplied by the xInterval, shifted by the xOffset of 1.0. The y-values are + * calculated in the same way to give a range of values from 0.0 to 6.0. + * + *

Configuration

+ *

+ * This step is optional. By default the heat chart will be generated without a + * title or labels on the axis, and the colouring of the heat map will be in + * grayscale. A large range of configuration options are available to customise + * the chart. All customisations are available through simple accessor methods. + * See the javadoc of each of the methods for more information. + * + *

Output

+ *

+ * The generated heat chart can be obtained in two forms, using the following + * methods: + *

+ * + * Note: The chart image will not actually be created until + * either saveToFile(File) or getChartImage() are called, and will be + * regenerated on each successive call. + */ +public class HeatChart { + + /** + * A basic logarithmic scale value of 0.3. + */ + public static final double SCALE_LOGARITHMIC = 0.3; + + /** + * The linear scale value of 1.0. + */ + public static final double SCALE_LINEAR = 1.0; + + /** + * A basic exponential scale value of 3.0. + */ + public static final double SCALE_EXPONENTIAL = 3; + + // x, y, z data values. + private double[][] zValues; + private Object[] xValues; + private Object[] yValues; + + private boolean xValuesHorizontal; + private boolean yValuesHorizontal; + + // General chart settings. + private Dimension cellSize; + private Dimension chartSize; + private int margin; + private Color backgroundColour; + + // Title settings. + private String title; + private Font titleFont; + private Color titleColour; + private Dimension titleSize; + private int titleAscent; + + // Axis settings. + private int axisThickness; + private Color axisColour; + private Font axisLabelsFont; + private Color axisLabelColour; + private String xAxisLabel; + private String yAxisLabel; + private int yAxisLabelOffset = 25; + private Color axisValuesColour; + private Font axisValuesFont; // The font size will be considered the maximum font size - it may be smaller if needed to fit in. + private int xAxisValuesFrequency; + private int yAxisValuesFrequency; + private boolean showXAxisValues; + private boolean showYAxisValues; + + // Generated axis properties. + private int xAxisValuesHeight; + private int xAxisValuesWidthMax; + + private int yAxisValuesHeight; + private int yAxisValuesAscent; + private int yAxisValuesWidthMax; + + private Dimension xAxisLabelSize; + private int xAxisLabelDescent; + + private Dimension yAxisLabelSize; + private int yAxisLabelAscent; + private int yAxisOffset = 150; + + // Heat map colour settings. + private Color highValueColour; + private Color lowValueColour; + + // How many RGB steps there are between the high and low colours. + private int colourValueDistance; + + private double lowValue; + private double highValue; + + // Key co-ordinate positions. + private Point heatMapTL; + private Point heatMapBR; + private Point heatMapC; + + // Heat map dimensions. + private Dimension heatMapSize; + + // Control variable for mapping z-values to colours. + private double colourScale; + + /** + * Constructs a heatmap for the given z-values against x/y-values that by + * default will be the values 0 to n-1, where n is the number of columns or + * rows. + * + * @param zValues the z-values, where each element is a row of z-values + * in the resultant heat chart. + */ + public HeatChart(double[][] zValues) { + this(zValues, min(zValues), max(zValues)); + } + + /** + * Constructs a heatmap for the given z-values against x/y-values that by + * default will be the values 0 to n-1, where n is the number of columns or + * rows. + * + * @param zValues the z-values, where each element is a row of z-values + * in the resultant heat chart. + * @param low the minimum possible value, which may or may not appear in the + * z-values. + * @param high the maximum possible value, which may or may not appear in + * the z-values. + */ + public HeatChart(double[][] zValues, double low, double high) { + this.zValues = zValues; + this.lowValue = low; + this.highValue = high; + + // Default x/y-value settings. + setXValues(0, 1); + setYValues(0, 1); + + // Default chart settings. + this.cellSize = new Dimension(20, 20); + this.margin = 20; + this.backgroundColour = Color.WHITE; + + // Default title settings. + this.title = null; + this.titleFont = new Font("Sans-Serif", Font.BOLD, 16); + this.titleColour = Color.BLACK; + + // Default axis settings. + this.xAxisLabel = null; + this.yAxisLabel = null; + this.axisThickness = 2; + this.axisColour = Color.BLACK; + this.axisLabelsFont = new Font("Sans-Serif", Font.PLAIN, 12); + this.axisLabelColour = Color.BLACK; + this.axisValuesColour = Color.BLACK; + this.axisValuesFont = new Font("Sans-Serif", Font.PLAIN, 10); + this.xAxisValuesFrequency = 1; + this.xAxisValuesHeight = 0; + this.xValuesHorizontal = false; + this.showXAxisValues = true; + this.showYAxisValues = true; + this.yAxisValuesFrequency = 1; + this.yAxisValuesHeight = 0; + this.yValuesHorizontal = true; + + // Default heatmap settings. + this.highValueColour = Color.BLACK; + this.lowValueColour = Color.WHITE; + this.colourScale = SCALE_LINEAR; + + updateColourDistance(); + } + + /** + * Returns the low value. This is the value at which the low value colour + * will be applied. + * + * @return the low value. + */ + public double getLowValue() { + return lowValue; + } + + /** + * Returns the high value. This is the value at which the high value colour + * will be applied. + * + * @return the high value. + */ + public double getHighValue() { + return highValue; + } + + /** + * Returns the 2-dimensional array of z-values currently in use. Each + * element is a double array which represents one row of the heat map, or + * all the z-values for one y-value. + * + * @return an array of the z-values in current use, that is, those values + * which will define the colour of each cell in the resultant heat map. + */ + public double[][] getZValues() { + return zValues; + } + + /** + * Replaces the z-values array. See the + * {@link #setZValues(double[][], double, double)} method for an example of + * z-values. The smallest and largest values in the array are used as the + * minimum and maximum values respectively. + * @param zValues the array to replace the current array with. The number + * of elements in each inner array must be identical. + */ + public void setZValues(double[][] zValues) { + setZValues(zValues, min(zValues), max(zValues)); + } + + /** + * Replaces the z-values array. The number of elements should match the + * number of y-values, with each element containing a double array with + * an equal number of elements that matches the number of x-values. Use this + * method where the minimum and maximum values possible are not contained + * within the dataset. + * + *

Example

+ * + *
+	 * new double[][]{
+	 *   {1.0,1.2,1.4},
+	 *   {1.2,1.3,1.5},
+	 *   {0.9,1.3,1.2},
+	 *   {0.8,1.6,1.1}
+	 * };
+	 * 
+ * + * The above zValues array is equivalent to: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
y
1.01.21.4
1.21.31.5
0.91.31.2
0.81.61.1
x
+ * + * @param zValues the array to replace the current array with. The number + * of elements in each inner array must be identical. + * @param low the minimum possible value, which may or may not appear in the + * z-values. + * @param high the maximum possible value, which may or may not appear in + * the z-values. + */ + public void setZValues(double[][] zValues, double low, double high) { + this.zValues = zValues; + this.lowValue = low; + this.highValue = high; + } + + /** + * Sets the x-values which are plotted along the x-axis. The x-values are + * calculated based upon the indexes of the z-values array: + * + *
+	 * x-value = x-offset + (column-index * x-interval)
+	 * 
+ * + *

The x-interval defines the gap between each x-value and the x-offset + * is applied to each value to offset them all from zero. + * + *

Alternatively the x-values can be set more directly with the + * setXValues(Object[]) method. + * + * @param xOffset an offset value to be applied to the index of each z-value + * element. + * @param xInterval an interval that will separate each x-value item. + */ + public void setXValues(double xOffset, double xInterval) { + // Update the x-values according to the offset and interval. + xValues = new Object[zValues[0].length]; + for (int i=0; i

+	 * y-value = y-offset + (column-index * y-interval)
+	 * 
+ * + *

The y-interval defines the gap between each y-value and the y-offset + * is applied to each value to offset them all from zero. + * + *

Alternatively the y-values can be set more directly with the + * setYValues(Object[]) method. + * + * @param yOffset an offset value to be applied to the index of each z-value + * element. + * @param yInterval an interval that will separate each y-value item. + */ + public void setYValues(double yOffset, double yInterval) { + // Update the y-values according to the offset and interval. + yValues = new Object[zValues.length]; + for (int i=0; isetXValues(Object[]) or that was generated from the offset + * and interval that were given to setXValues(double, double), + * in which case the object type of each element will be Double. + * + * @return an array of the values that are to be displayed along the x-axis. + */ + public Object[] getXValues() { + return xValues; + } + + /** + * Returns the y-values which are currently set to display along the y-axis. + * The array that is returned is either that which was explicitly set with + * setYValues(Object[]) or that was generated from the offset + * and interval that were given to setYValues(double, double), + * in which case the object type of each element will be Double. + * + * @return an array of the values that are to be displayed along the y-axis. + */ + public Object[] getYValues() { + return yValues; + } + + /** + * Sets whether the text of the values along the x-axis should be drawn + * horizontally left-to-right, or vertically top-to-bottom. + * + * @param xValuesHorizontal true if x-values should be drawn horizontally, + * false if they should be drawn vertically. + */ + public void setXValuesHorizontal(boolean xValuesHorizontal) { + this.xValuesHorizontal = xValuesHorizontal; + } + + /** + * Returns whether the text of the values along the x-axis are to be drawn + * horizontally left-to-right, or vertically top-to-bottom. + * + * @return true if the x-values will be drawn horizontally, false if they + * will be drawn vertically. + */ + public boolean isXValuesHorizontal() { + return xValuesHorizontal; + } + + /** + * Sets whether the text of the values along the y-axis should be drawn + * horizontally left-to-right, or vertically top-to-bottom. + * + * @param yValuesHorizontal true if y-values should be drawn horizontally, + * false if they should be drawn vertically. + */ + public void setYValuesHorizontal(boolean yValuesHorizontal) { + this.yValuesHorizontal = yValuesHorizontal; + } + + /** + * Returns whether the text of the values along the y-axis are to be drawn + * horizontally left-to-right, or vertically top-to-bottom. + * + * @return true if the y-values will be drawn horizontally, false if they + * will be drawn vertically. + */ + public boolean isYValuesHorizontal() { + return yValuesHorizontal; + } + + /** + * Sets the width of each individual cell that constitutes a value in x,y,z + * data space. By setting the cell width, any previously set chart width + * will be overwritten with a value calculated based upon this value and the + * number of cells in there are along the x-axis. + * + * @param cellWidth the new width to use for each individual data cell. + * @deprecated As of release 0.6, replaced by {@link #setCellSize(Dimension)} + */ + @Deprecated + public void setCellWidth(int cellWidth) { + setCellSize(new Dimension(cellWidth, cellSize.height)); + } + + /** + * Returns the width of each individual data cell that constitutes a value + * in the x,y,z space. + * + * @return the width of each cell. + * @deprecated As of release 0.6, replaced by {@link #getCellSize} + */ + @Deprecated + public int getCellWidth() { + return cellSize.width; + } + + /** + * Sets the height of each individual cell that constitutes a value in x,y,z + * data space. By setting the cell height, any previously set chart height + * will be overwritten with a value calculated based upon this value and the + * number of cells in there are along the y-axis. + * + * @param cellHeight the new height to use for each individual data cell. + * @deprecated As of release 0.6, replaced by {@link #setCellSize(Dimension)} + */ + @Deprecated + public void setCellHeight(int cellHeight) { + setCellSize(new Dimension(cellSize.width, cellHeight)); + } + + /** + * Returns the height of each individual data cell that constitutes a value + * in the x,y,z space. + * + * @return the height of each cell. + * @deprecated As of release 0.6, replaced by {@link #getCellSize()} + */ + @Deprecated + public int getCellHeight() { + return cellSize.height; + } + + /** + * Sets the size of each individual cell that constitutes a value in x,y,z + * data space. By setting the cell size, any previously set chart size will + * be overwritten with a value calculated based upon this value and the + * number of cells along each axis. + * + * @param cellSize the new size to use for each individual data cell. + * @since 0.6 + */ + public void setCellSize(Dimension cellSize) { + this.cellSize = cellSize; + } + + /** + * Returns the size of each individual data cell that constitutes a value in + * the x,y,z space. + * + * @return the size of each individual data cell. + * @since 0.6 + */ + public Dimension getCellSize() { + return cellSize; + } + + /** + * Returns the width of the chart in pixels as calculated according to the + * cell dimensions, chart margin and other size settings. + * + * @return the width in pixels of the chart image to be generated. + * @deprecated As of release 0.6, replaced by {@link #getChartSize()} + */ + @Deprecated + public int getChartWidth() { + return chartSize.width; + } + + /** + * Returns the height of the chart in pixels as calculated according to the + * cell dimensions, chart margin and other size settings. + * + * @return the height in pixels of the chart image to be generated. + * @deprecated As of release 0.6, replaced by {@link #getChartSize()} + */ + @Deprecated + public int getChartHeight() { + return chartSize.height; + } + + /** + * Returns the size of the chart in pixels as calculated according to the + * cell dimensions, chart margin and other size settings. + * + * @return the size in pixels of the chart image to be generated. + * @since 0.6 + */ + public Dimension getChartSize() { + return chartSize; + } + + /** + * Returns the String that will be used as the title of any successive + * calls to generate a chart. + * + * @return the title of the chart. + */ + public String getTitle() { + return title; + } + + /** + * Sets the String that will be used as the title of any successive + * calls to generate a chart. The title will be displayed centralised + * horizontally at the top of any generated charts. + * + *

+ * If the title is set to null then no title will be displayed. + * + *

+ * Defaults to null. + * + * @param title the chart title to set. + */ + public void setTitle(String title) { + this.title = title; + } + + /** + * Returns the String that will be displayed as a description of the + * x-axis in any generated charts. + * + * @return the display label describing the x-axis. + */ + public String getXAxisLabel() { + return xAxisLabel; + } + + /** + * Sets the String that will be displayed as a description of the + * x-axis in any generated charts. The label will be displayed + * horizontally central of the x-axis bar. + * + *

+ * If the xAxisLabel is set to null then no label will be + * displayed. + * + *

+ * Defaults to null. + * + * @param xAxisLabel the label to be displayed describing the x-axis. + */ + public void setXAxisLabel(String xAxisLabel) { + this.xAxisLabel = xAxisLabel; + } + + /** + * Returns the String that will be displayed as a description of the + * y-axis in any generated charts. + * + * @return the display label describing the y-axis. + */ + public String getYAxisLabel() { + return yAxisLabel; + } + + /** + * Sets the String that will be displayed as a description of the + * y-axis in any generated charts. The label will be displayed + * horizontally central of the y-axis bar. + * + *

+ * If the yAxisLabel is set to null then no label will be + * displayed. + * + *

+ * Defaults to null. + * + * @param yAxisLabel the label to be displayed describing the y-axis. + */ + public void setYAxisLabel(String yAxisLabel) { + this.yAxisLabel = yAxisLabel; + } + + /** + * Returns the width of the margin in pixels to be left as empty space + * around the heat map element. + * + * @return the size of the margin to be left blank around the edge of the + * chart. + */ + public int getChartMargin() { + return margin; + } + + /** + * Sets the width of the margin in pixels to be left as empty space around + * the heat map element. If a title is set then half the margin will be + * directly above the title and half directly below it. Where axis labels + * are set then the axis labels may sit partially in the margin. + * + *

+ * Defaults to 20 pixels. + * + * @param margin the new margin to be left as blank space around the heat + * map. + */ + public void setChartMargin(int margin) { + this.margin = margin; + } + + /** + * Returns an object that represents the colour to be used as the + * background for the whole chart. + * + * @return the colour to be used to fill the chart background. + */ + public Color getBackgroundColour() { + return backgroundColour; + } + + /** + * Sets the colour to be used on the background of the chart. A transparent + * background can be set by setting a background colour with an alpha value. + * The transparency will only be effective when the image is saved as a png + * or gif. + * + *

+ * Defaults to Color.WHITE. + * + * @param backgroundColour the new colour to be set as the background fill. + */ + public void setBackgroundColour(Color backgroundColour) { + if (backgroundColour == null) { + backgroundColour = Color.WHITE; + } + + this.backgroundColour = backgroundColour; + } + + /** + * Returns the Font that describes the visual style of the + * title. + * + * @return the Font that will be used to render the title. + */ + public Font getTitleFont() { + return titleFont; + } + + /** + * Sets a new Font to be used in rendering the chart's title + * String. + * + *

+ * Defaults to Sans-Serif, BOLD, 16 pixels. + * + * @param titleFont the Font that should be used when rendering the chart + * title. + */ + public void setTitleFont(Font titleFont) { + this.titleFont = titleFont; + } + + /** + * Returns the Color that represents the colour the title text + * should be painted in. + * + * @return the currently set colour to be used in painting the chart title. + */ + public Color getTitleColour() { + return titleColour; + } + + /** + * Sets the Color that describes the colour to be used for the + * chart title String. + * + *

+ * Defaults to Color.BLACK. + * + * @param titleColour the colour to paint the chart's title String. + */ + public void setTitleColour(Color titleColour) { + this.titleColour = titleColour; + } + + /** + * Returns the width of the axis bars in pixels. Both axis bars have the + * same thickness. + * + * @return the thickness of the axis bars in pixels. + */ + public int getAxisThickness() { + return axisThickness; + } + + /** + * Sets the width of the axis bars in pixels. Both axis bars use the same + * thickness. + * + *

+ * Defaults to 2 pixels. + * + * @param axisThickness the thickness to use for the axis bars in any newly + * generated charts. + */ + public void setAxisThickness(int axisThickness) { + this.axisThickness = axisThickness; + } + + /** + * Returns the colour that is set to be used for the axis bars. Both axis + * bars use the same colour. + * + * @return the colour in use for the axis bars. + */ + public Color getAxisColour() { + return axisColour; + } + + /** + * Sets the colour to be used on the axis bars. Both axis bars use the same + * colour. + * + *

+ * Defaults to Color.BLACK. + * + * @param axisColour the colour to be set for use on the axis bars. + */ + public void setAxisColour(Color axisColour) { + this.axisColour = axisColour; + } + + /** + * Returns the font that describes the visual style of the labels of the + * axis. Both axis' labels use the same font. + * + * @return the font used to define the visual style of the axis labels. + */ + public Font getAxisLabelsFont() { + return axisLabelsFont; + } + + /** + * Sets the font that describes the visual style of the axis labels. Both + * axis' labels use the same font. + * + *

+ * Defaults to Sans-Serif, PLAIN, 12 pixels. + * + * @param axisLabelsFont the font to be used to define the visual style of + * the axis labels. + */ + public void setAxisLabelsFont(Font axisLabelsFont) { + this.axisLabelsFont = axisLabelsFont; + } + + /** + * Returns the current colour of the axis labels. Both labels use the same + * colour. + * + * @return the colour of the axis label text. + */ + public Color getAxisLabelColour() { + return axisLabelColour; + } + + /** + * Sets the colour of the text displayed as axis labels. Both labels use + * the same colour. + * + *

+ * Defaults to Color.BLACK. + * + * @param axisLabelColour the colour to use for the axis label text. + */ + public void setAxisLabelColour(Color axisLabelColour) { + this.axisLabelColour = axisLabelColour; + } + + /** + * Returns the font which describes the visual style of the axis values. + * The axis values are those values displayed alongside the axis bars at + * regular intervals. Both axis use the same font. + * + * @return the font in use for the axis values. + */ + public Font getAxisValuesFont() { + return axisValuesFont; + } + + /** + * Sets the font which describes the visual style of the axis values. The + * axis values are those values displayed alongside the axis bars at + * regular intervals. Both axis use the same font. + * + *

+ * Defaults to Sans-Serif, PLAIN, 10 pixels. + * + * @param axisValuesFont the font that should be used for the axis values. + */ + public void setAxisValuesFont(Font axisValuesFont) { + this.axisValuesFont = axisValuesFont; + } + + /** + * Returns the colour of the axis values as they will be painted along the + * axis bars. Both axis use the same colour. + * + * @return the colour of the values displayed along the axis bars. + */ + public Color getAxisValuesColour() { + return axisValuesColour; + } + + /** + * Sets the colour to be used for the axis values as they will be painted + * along the axis bars. Both axis use the same colour. + * + *

+ * Defaults to Color.BLACK. + * + * @param axisValuesColour the new colour to be used for the axis bar values. + */ + public void setAxisValuesColour(Color axisValuesColour) { + this.axisValuesColour = axisValuesColour; + } + + /** + * Returns the frequency of the values displayed along the x-axis. The + * frequency is how many columns in the x-dimension have their value + * displayed. A frequency of 2 would mean every other column has a value + * shown and a frequency of 3 would mean every third column would be given a + * value. + * + * @return the frequency of the values displayed against columns. + */ + public int getXAxisValuesFrequency() { + return xAxisValuesFrequency; + } + + /** + * Sets the frequency of the values displayed along the x-axis. The + * frequency is how many columns in the x-dimension have their value + * displayed. A frequency of 2 would mean every other column has a value and + * a frequency of 3 would mean every third column would be given a value. + * + *

+ * Defaults to 1. Every column is given a value. + * + * @param axisValuesFrequency the frequency of the values displayed against + * columns, where 1 is every column and 2 is every other column. + */ + public void setXAxisValuesFrequency(int axisValuesFrequency) { + this.xAxisValuesFrequency = axisValuesFrequency; + } + + /** + * Returns the frequency of the values displayed along the y-axis. The + * frequency is how many rows in the y-dimension have their value displayed. + * A frequency of 2 would mean every other row has a value and a frequency + * of 3 would mean every third row would be given a value. + * + * @return the frequency of the values displayed against rows. + */ + public int getYAxisValuesFrequency() { + return yAxisValuesFrequency; + } + + /** + * Sets the frequency of the values displayed along the y-axis. The + * frequency is how many rows in the y-dimension have their value displayed. + * A frequency of 2 would mean every other row has a value and a frequency + * of 3 would mean every third row would be given a value. + * + *

+ * Defaults to 1. Every row is given a value. + * + * @param axisValuesFrequency the frequency of the values displayed against + * rows, where 1 is every row and 2 is every other row. + */ + public void setYAxisValuesFrequency(int axisValuesFrequency) { + yAxisValuesFrequency = axisValuesFrequency; + } + + /** + * Returns whether axis values are to be shown at all for the x-axis. + * + *

+ * If axis values are not shown then more space is allocated to the heat + * map. + * + * @return true if the x-axis values will be displayed, false otherwise. + */ + public boolean isShowXAxisValues() { + //TODO Could get rid of these flags and use a frequency of -1 to signal no values. + return showXAxisValues; + } + + /** + * Sets whether axis values are to be shown at all for the x-axis. + * + *

+ * If axis values are not shown then more space is allocated to the heat + * map. + * + *

+ * Defaults to true. + * + * @param showXAxisValues true if x-axis values should be displayed, false + * if they should be hidden. + */ + public void setShowXAxisValues(boolean showXAxisValues) { + this.showXAxisValues = showXAxisValues; + } + + /** + * Returns whether axis values are to be shown at all for the y-axis. + * + *

+ * If axis values are not shown then more space is allocated to the heat + * map. + * + * @return true if the y-axis values will be displayed, false otherwise. + */ + public boolean isShowYAxisValues() { + return showYAxisValues; + } + + /** + * Sets whether axis values are to be shown at all for the y-axis. + * + *

+ * If axis values are not shown then more space is allocated to the heat + * map. + * + *

+ * Defaults to true. + * + * @param showYAxisValues true if y-axis values should be displayed, false + * if they should be hidden. + */ + public void setShowYAxisValues(boolean showYAxisValues) { + this.showYAxisValues = showYAxisValues; + } + + /** + * Returns the colour that is currently to be displayed for the heat map + * cells with the highest z-value in the dataset. + * + *

+ * The full colour range will go through each RGB step between the high + * value colour and the low value colour. + * + * @return the colour in use for cells of the highest z-value. + */ + public Color getHighValueColour() { + return highValueColour; + } + + /** + * Sets the colour to be used to fill cells of the heat map with the + * highest z-values in the dataset. + * + *

+ * The full colour range will go through each RGB step between the high + * value colour and the low value colour. + * + *

+ * Defaults to Color.BLACK. + * + * @param highValueColour the colour to use for cells of the highest + * z-value. + */ + public void setHighValueColour(Color highValueColour) { + this.highValueColour = highValueColour; + + updateColourDistance(); + } + + /** + * Returns the colour that is currently to be displayed for the heat map + * cells with the lowest z-value in the dataset. + * + *

+ * The full colour range will go through each RGB step between the high + * value colour and the low value colour. + * + * @return the colour in use for cells of the lowest z-value. + */ + public Color getLowValueColour() { + return lowValueColour; + } + + /** + * Sets the colour to be used to fill cells of the heat map with the + * lowest z-values in the dataset. + * + *

+ * The full colour range will go through each RGB step between the high + * value colour and the low value colour. + * + *

+ * Defaults to Color.WHITE. + * + * @param lowValueColour the colour to use for cells of the lowest + * z-value. + */ + public void setLowValueColour(Color lowValueColour) { + this.lowValueColour = lowValueColour; + + updateColourDistance(); + } + + /** + * Returns the scale that is currently in use to map z-value to colour. A + * value of 1.0 will give a linear scale, which will + * spread the distribution of colours evenly amoungst the full range of + * represented z-values. A value of greater than 1.0 will give an + * exponential scale that will produce greater emphasis + * for the separation between higher values and a value between 0.0 and 1.0 + * will provide a logarithmic scale, with greater + * separation of low values. + * + * @return the scale factor that is being used to map from z-value to colour. + */ + public double getColourScale() { + return colourScale; + } + + /** + * Sets the scale that is currently in use to map z-value to colour. A + * value of 1.0 will give a linear scale, which will + * spread the distribution of colours evenly amoungst the full range of + * represented z-values. A value of greater than 1.0 will give an + * exponential scale that will produce greater emphasis + * for the separation between higher values and a value between 0.0 and 1.0 + * will provide a logarithmic scale, with greater + * separation of low values. Values of 0.0 or less are illegal. + * + *

+ * Defaults to a linear scale value of 1.0. + * + * @param colourScale the scale that should be used to map from z-value to + * colour. + */ + public void setColourScale(double colourScale) { + this.colourScale = colourScale; + } + + /* + * Calculate and update the field for the distance between the low colour + * and high colour. The distance is the number of steps between one colour + * and the other using an RGB coding with 0-255 values for each of red, + * green and blue. So the maximum colour distance is 255 + 255 + 255. + */ + private void updateColourDistance() { + int r1 = lowValueColour.getRed(); + int g1 = lowValueColour.getGreen(); + int b1 = lowValueColour.getBlue(); + int r2 = highValueColour.getRed(); + int g2 = highValueColour.getGreen(); + int b2 = highValueColour.getBlue(); + + colourValueDistance = Math.abs(r1 - r2); + colourValueDistance += Math.abs(g1 - g2); + colourValueDistance += Math.abs(b1 - b2); + } + + /** + * Generates a new chart Image based upon the currently held + * settings and then attempts to save that image to disk, to the location + * provided as a File parameter. The image type of the saved file will + * equal the extension of the filename provided, so it is essential that a + * suitable extension be included on the file name. + * + *

+ * All supported ImageIO file types are supported, including + * PNG, JPG and GIF. + * + *

+ * No chart will be generated until this or the related + * getChartImage() method are called. All successive calls + * will result in the generation of a new chart image, no caching is used. + * + * @param outputFile the file location that the generated image file should + * be written to. The File must have a suitable filename, with an extension + * of a valid image format (as supported by ImageIO). + * @throws IOException if the output file's filename has no extension or + * if there the file is unable to written to. Reasons for this include a + * non-existant file location (check with the File exists() method on the + * parent directory), or the permissions of the write location may be + * incorrect. + */ + public void saveToFile(File outputFile) throws IOException { + String filename = outputFile.getName(); + + int extPoint = filename.lastIndexOf('.'); + + if (extPoint < 0) { + throw new IOException("Illegal filename, no extension used."); + } + + // Determine the extension of the filename. + String ext = filename.substring(extPoint + 1); + + // Handle jpg without transparency. + if (ext.toLowerCase().equals("jpg") || ext.toLowerCase().equals("jpeg")) { + BufferedImage chart = (BufferedImage) getChartImage(false); + + // Save our graphic. + saveGraphicJpeg(chart, outputFile, 1.0f); + } else { + BufferedImage chart = (BufferedImage) getChartImage(true); + + ImageIO.write(chart, ext, outputFile); + } + } + + private void saveGraphicJpeg(BufferedImage chart, File outputFile, float quality) throws IOException { + // Setup correct compression for jpeg. + Iterator iter = ImageIO.getImageWritersByFormatName("jpeg"); + ImageWriter writer = (ImageWriter) iter.next(); + ImageWriteParam iwp = writer.getDefaultWriteParam(); + iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); + iwp.setCompressionQuality(quality); + + // Output the image. + FileImageOutputStream output = new FileImageOutputStream(outputFile); + writer.setOutput(output); + IIOImage image = new IIOImage(chart, null, null); + writer.write(null, image, iwp); + writer.dispose(); + + } + + /** + * Generates and returns a new chart Image configured + * according to this object's currently held settings. The given parameter + * determines whether transparency should be enabled for the generated + * image. + * + *

+ * No chart will be generated until this or the related + * saveToFile(File) method are called. All successive calls + * will result in the generation of a new chart image, no caching is used. + * + * @param alpha whether to enable transparency. + * @return A newly generated chart Image. The returned image + * is a BufferedImage. + */ + public Image getChartImage(boolean alpha) { + // Calculate all unknown dimensions. + measureComponents(); + updateCoordinates(); + + // Determine image type based upon whether require alpha or not. + // Using BufferedImage.TYPE_INT_ARGB seems to break on jpg. + int imageType = (alpha ? BufferedImage.TYPE_4BYTE_ABGR : BufferedImage.TYPE_3BYTE_BGR); + + // Create our chart image which we will eventually draw everything on. + BufferedImage chartImage = new BufferedImage(chartSize.width, chartSize.height, imageType); + Graphics2D chartGraphics = chartImage.createGraphics(); + + // Use anti-aliasing where ever possible. + chartGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + + // Set the background. + chartGraphics.setColor(backgroundColour); + chartGraphics.fillRect(0, 0, chartSize.width, chartSize.height); + + // Draw the title. + drawTitle(chartGraphics); + + // Draw the heatmap image. + drawHeatMap(chartGraphics, zValues); + + // Draw the axis labels. + drawXLabel(chartGraphics); + drawYLabel(chartGraphics); + + // Draw the axis bars. + drawAxisBars(chartGraphics); + + // Draw axis values. + drawXValues(chartGraphics); + drawYValues(chartGraphics); + + return chartImage; + } + + /** + * Generates and returns a new chart Image configured + * according to this object's currently held settings. By default the image + * is generated with no transparency. + * + *

+ * No chart will be generated until this or the related + * saveToFile(File) method are called. All successive calls + * will result in the generation of a new chart image, no caching is used. + * + * @return A newly generated chart Image. The returned image + * is a BufferedImage. + */ + public Image getChartImage() { + return getChartImage(false); + } + + /* + * Calculates all unknown component dimensions. + */ + private void measureComponents() { + //TODO This would be a good place to check that all settings have sensible values or throw illegal state exception. + + //TODO Put this somewhere so it only gets created once. + BufferedImage chartImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB); + Graphics2D tempGraphics = chartImage.createGraphics(); + + // Calculate title dimensions. + if (title != null) { + tempGraphics.setFont(titleFont); + FontMetrics metrics = tempGraphics.getFontMetrics(); + titleSize = new Dimension(metrics.stringWidth(title), metrics.getHeight()); + titleAscent = metrics.getAscent(); + } else { + titleSize = new Dimension(0, 0); + } + + // Calculate x-axis label dimensions. + if (xAxisLabel != null) { + tempGraphics.setFont(axisLabelsFont); + FontMetrics metrics = tempGraphics.getFontMetrics(); + xAxisLabelSize = new Dimension(metrics.stringWidth(xAxisLabel), metrics.getHeight()); + xAxisLabelDescent = metrics.getDescent(); + } else { + xAxisLabelSize = new Dimension(0, 0); + } + + // Calculate y-axis label dimensions. + if (yAxisLabel != null) { + tempGraphics.setFont(axisLabelsFont); + FontMetrics metrics = tempGraphics.getFontMetrics(); + yAxisLabelSize = new Dimension(metrics.stringWidth(yAxisLabel), metrics.getHeight()); + yAxisLabelAscent = metrics.getAscent(); + } else { + yAxisLabelSize = new Dimension(0, 0); + } + + // Calculate x-axis value dimensions. + if (showXAxisValues) { + tempGraphics.setFont(axisValuesFont); + FontMetrics metrics = tempGraphics.getFontMetrics(); + xAxisValuesHeight = metrics.getHeight(); + xAxisValuesWidthMax = 0; + for (Object o: xValues) { + int w = metrics.stringWidth(o.toString()); + if (w > xAxisValuesWidthMax) { + xAxisValuesWidthMax = w; + } + } + } else { + xAxisValuesHeight = 0; + } + + // Calculate y-axis value dimensions. + if (showYAxisValues) { + tempGraphics.setFont(axisValuesFont); + FontMetrics metrics = tempGraphics.getFontMetrics(); + yAxisValuesHeight = metrics.getHeight(); + yAxisValuesAscent = metrics.getAscent(); + yAxisValuesWidthMax = 0; + for (Object o: yValues) { + int w = metrics.stringWidth(o.toString()); + if (w > yAxisValuesWidthMax) { + yAxisValuesWidthMax = w; + } + } + } else { + yAxisValuesHeight = 0; + } + + // Calculate heatmap dimensions. + int heatMapWidth = (zValues[0].length * cellSize.width); + int heatMapHeight = (zValues.length * cellSize.height); + heatMapSize = new Dimension(heatMapWidth, heatMapHeight); + + int yValuesHorizontalSize = 0; + if (yValuesHorizontal) { + yValuesHorizontalSize = yAxisValuesWidthMax; + } else { + yValuesHorizontalSize = yAxisValuesHeight; + } + + int xValuesVerticalSize = 0; + if (xValuesHorizontal) { + xValuesVerticalSize = xAxisValuesHeight; + } else { + xValuesVerticalSize = xAxisValuesWidthMax; + } + + // Calculate chart dimensions. + int chartWidth = heatMapWidth + (2 * margin) + yAxisLabelSize.height + yValuesHorizontalSize + axisThickness; + int chartHeight = heatMapHeight + (2 * margin) + xAxisLabelSize.height + xValuesVerticalSize + titleSize.height + axisThickness; + chartSize = new Dimension(chartWidth, chartHeight); + } + + /* + * Calculates the co-ordinates of some key positions. + */ + private void updateCoordinates() { + // Top-left of heat map. + int x = margin + axisThickness + yAxisLabelSize.height; + x += (yValuesHorizontal ? yAxisValuesWidthMax : yAxisValuesHeight); + int y = titleSize.height + margin + yAxisOffset; + heatMapTL = new Point(x, y); + + // Top-right of heat map. + x = heatMapTL.x + heatMapSize.width; + y = heatMapTL.y + heatMapSize.height; + heatMapBR = new Point(x, y); + + // Centre of heat map. + x = heatMapTL.x + (heatMapSize.width / 2); + y = heatMapTL.y + (heatMapSize.height / 2); + heatMapC = new Point(x, y); + } + + /* + * Draws the title String on the chart if title is not null. + */ + private void drawTitle(Graphics2D chartGraphics) { + if (title != null) { + // Strings are drawn from the baseline position of the leftmost char. + int yTitle = (margin/2) + titleAscent; + int xTitle = (chartSize.width/2) - (titleSize.width/2); + + chartGraphics.setFont(titleFont); + chartGraphics.setColor(titleColour); + chartGraphics.drawString(title, xTitle, yTitle); + } + } + + /* + * Creates the actual heatmap element as an image, that can then be drawn + * onto a chart. + */ + private void drawHeatMap(Graphics2D chartGraphics, double[][] data) { + // Calculate the available size for the heatmap. + int noYCells = data.length; + int noXCells = data[0].length; + + //double dataMin = min(data); + //double dataMax = max(data); + + BufferedImage heatMapImage = new BufferedImage(heatMapSize.width, heatMapSize.height, BufferedImage.TYPE_INT_ARGB); + Graphics2D heatMapGraphics = heatMapImage.createGraphics(); + + for (int x=0; x 0) { + chartGraphics.setColor(axisColour); + + // Draw x-axis. + int x = heatMapTL.x - axisThickness; + int y = heatMapTL.y; + //int y = 40; + + int width = heatMapSize.width + axisThickness; + int height = axisThickness; + chartGraphics.fillRect(x, y, width, height); + + // Draw y-axis. + x = heatMapTL.x - axisThickness; + y = heatMapTL.y; + width = axisThickness; + height = heatMapSize.height; + chartGraphics.fillRect(x, y, width, height); + } + } + + /* + * Draws the x-values onto the x-axis if showXAxisValues is set to true. + */ + private void drawXValues(Graphics2D chartGraphics) { + if (!showXAxisValues) { + return; + } + + chartGraphics.setColor(axisValuesColour); + + for (int i=0; i= Math.abs(gDistance)) + && (Math.abs(rDistance) >= Math.abs(bDistance))) { + // Red must be the largest. + r = changeColourValue(r, rDistance); + } else if (Math.abs(gDistance) >= Math.abs(bDistance)) { + // Green must be the largest. + g = changeColourValue(g, gDistance); + } else { + // Blue must be the largest. + b = changeColourValue(b, bDistance); + } + } + + return new Color(r, g, b); + } + + /* + * Returns how many colour shifts are required from the lowValueColour to + * get to the correct colour position. The result will be different + * depending on the colour scale used: LINEAR, LOGARITHMIC, EXPONENTIAL. + */ + private int getColourPosition(double percentPosition) { + return (int) Math.round(colourValueDistance * Math.pow(percentPosition, colourScale)); + } + + private int changeColourValue(int colourValue, int colourDistance) { + if (colourDistance < 0) { + return colourValue+1; + } else if (colourDistance > 0) { + return colourValue-1; + } else { + // This shouldn't actually happen here. + return colourValue; + } + } + + /** + * Finds and returns the maximum value in a 2-dimensional array of doubles. + * + * @return the largest value in the array. + */ + public static double max(double[][] values) { + double max = 0; + for (int i=0; i max) ? values[i][j] : max; + } + } + return max; + } + + /** + * Finds and returns the minimum value in a 2-dimensional array of doubles. + * + * @return the smallest value in the array. + */ + public static double min(double[][] values) { + double min = Double.MAX_VALUE; + for (int i=0; i