org.hyperic.image.chart.Chart.java Source code

Java tutorial

Introduction

Here is the source code for org.hyperic.image.chart.Chart.java

Source

/*
 * NOTE: This copyright does *not* cover user programs that use HQ
 * program services by normal system calls through the application
 * program interfaces provided as part of the Hyperic Plug-in Development
 * Kit or the Hyperic Client Development Kit - this is merely considered
 * normal use of the program, and does *not* fall under the heading of
 * "derived work".
 * 
 * Copyright (C) [2004-2008], Hyperic, Inc.
 * This file is part of HQ.
 * 
 * HQ is free software; you can redistribute it and/or modify
 * it under the terms version 2 of the GNU General Public License as
 * published by the Free Software Foundation. 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.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA.
 */

package org.hyperic.image.chart;

import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

import org.hyperic.image.WebImage;
import org.hyperic.util.data.IDisplayDataPoint;
import org.hyperic.util.data.IHighLowDataPoint;
import org.hyperic.util.data.IStackedDataPoint;
import org.hyperic.util.units.FormattedNumber;
import org.hyperic.util.units.UnitsConstants;
import org.hyperic.util.units.UnitsFormat;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * Chart is an abstract base class for the Covalent charting library. Chart uses
 * Java AWT drawing to draw charts dynamically into a Java Image which can be
 * then be used to display the chart in an application. The most common use
 * of the charting library is to draw the chart image and then convert the
 * image to a GIF to send to a Web Browser.
 *
 * Chart is responsible for all of the background drawing of the Chart
 * the Chart frame, text labels, data point crossing lines, etc. Sublcasses
 * of Chart draw on top of the image produce by the Chart class to draw the
 * actual lines, columns, etc. that represent the data points of the chart.
 *
 * To create a chart you instantiate a subclass of Chart (e.g., LineChart,
 * ColumnChart, etc.), call getData() to retrieve a collection, poplulate
 * the collection and then call Chart.getImage() to get a java.awt.Image
 * object that represents the drawn chart.
 *
 * The chart class is very configurable through get/set methods including
 * configuring things like the text for labels, fonts, line colors, etc.
 * After changing one or more options you call Chart.getImage() again to
 * get a new image based on your options.
 *
 * The chart should have at least three datums in its Datum collection to
 * display without errors.
 *
 * @see java.awt.Image
 */
public abstract class Chart extends WebImage {
    private Log log = LogFactory.getLog(Chart.class.getName());

    ////////////////////////////////////////////////
    // Static Variables

    public static final String EMPTY_STRING = "";

    protected static final String ARG_MUST_BE_ZERO_OR_GREATER = "Argument value must be zero or greater";

    protected static final String AVG = "Average";
    protected static final String BASELINE = "Baseline";
    protected static final String LOW = "Low";
    protected static final String PEAK = "Peak";

    protected static final String DEFAULT_UNIT_LEGEND = "TIME";
    protected static final String DEFAULT_VALUE_LEGEND = "VALUE";
    private static final String NO_DATA = "No Metric Data Available";

    private static final int DEFAULT_LINE_WIDTH = 1;
    private static final int DEFAULT_VALUE_INDENT = 0;
    private static final int DEFAULT_VALUE_LINES = 11;
    private static final int DEFAULT_TEXT_WHITESPACE = 3;
    private static final int DEFAULT_TICK_MARK_HEIGHT = 6;

    protected static final int VARIABLE_HEIGHT = Integer.MIN_VALUE;
    protected static final int VARIABLE_WIDTH = Integer.MIN_VALUE;

    private static final Color DEFAULT_FRAME_COLOR = new Color(0xE6, 0xE5, 0xE5); //new Color(0xCC, 0xCC, 0xCC);
    private static final Color DEFAULT_LEGEND_TEXT_COLOR = Color.BLACK;
    private static final Color DEFAULT_X_LINE_COLOR = new Color(0xE6, 0xE5, 0xE5);
    private static final Color DEFAULT_TEXT_COLOR = new Color(0x80, 0x80, 0x80);

    private static final Color DEFAULT_AVERAGE_COLOR = new Color(0x8C, 0xBF, 0x73);
    private static final Color DEFAULT_BASELINE_COLOR = new Color(0xBF, 0xB2, 0x73);
    private static final Color DEFAULT_LOW_COLOR = new Color(0x73, 0xBF, 0xB3);
    private static final Color DEFAULT_PEAK_COLOR = new Color(0xD9, 0x98, 0x98);

    private static final Color DEFAULT_HIGH_RANGE_COLOR = new Color(0xF7, 0xEB, 0xEB);
    private static final Color DEFAULT_LOW_RANGE_COLOR = new Color(0xEB, 0xF7, 0xF6);

    protected static final Font DEFAULT_LABEL_FONT = new Font("Helvetica", Font.PLAIN, 10);
    protected static final Font DEFAULT_LEGEND_PLAIN = new Font("Helvetica", Font.PLAIN, 11);
    protected static final Font DEFAULT_LEGEND_FONT = new Font("Helvetica", Font.BOLD, 11);

    protected FontMetrics m_metricsLabel;
    protected FontMetrics m_metricsLegend;

    ////////////////////////////////////////////////
    // Instance Variables

    private String m_strUnitLegend = DEFAULT_UNIT_LEGEND;
    private String m_strValueLegend = DEFAULT_VALUE_LEGEND;

    private boolean m_useAbsTimeLabels = false;
    private SmartLabelMaker m_smartLabelMaker = null;

    private Color m_clrFrame = DEFAULT_FRAME_COLOR;
    private Color m_clrLegendText = DEFAULT_LEGEND_TEXT_COLOR;

    protected int m_fmtType;
    protected int m_fmtScale;

    protected double m_dAvgValue = 0;
    protected double m_dLowValue = Double.POSITIVE_INFINITY;
    protected double m_dPeakValue = Double.NEGATIVE_INFINITY;

    protected double[] m_adRangeMarks;
    protected double m_floor;
    protected boolean m_bNoData;

    private String m_strTitle = EMPTY_STRING;

    private ArrayList m_collDataPointColl = new ArrayList(1);
    private ArrayList m_collEvtPointColl = new ArrayList(1);

    private String m_strNoData = NO_DATA;

    public int yTopLegend;
    public int yBottomLegend;
    public int xVertLegend;
    public int yHorzLabels;
    public int x2VertLabels;
    public int xRLabel;
    public int xVertMarks;
    public int x2VertMarks;
    public int xLabelsSkip = 4; // Default to label every 4 ticks

    ////////////////////////////////////////////////
    // Property Variables

    /**
     * Color of the average data point horizontal line.
     */
    public Color averageLineColor = DEFAULT_AVERAGE_COLOR;

    /**
     * Color of the baseline horizontal line.
     */
    public Color baselineColor = DEFAULT_BASELINE_COLOR;

    /**
     * Top number for the left axis of the chart.
     */
    public double ceiling = 0;

    /**
     * Retrieves the chart's interior background color.
     */
    public Color chartColor = Color.WHITE;

    /**
     * Bottom number for the left axis of the chart.
     */
    public double floor = 0;

    /**
     * Baseline in the chart.
     */
    public double baseline = 0;

    /**
     * High range in the chart.
     */
    public double highRange = Double.NaN;

    /**
     * High range color in the chart.
     */
    public Color highRangeColor = DEFAULT_HIGH_RANGE_COLOR;

    /**
     * Font that is used to draw the legend for the chart's X and Y axis.
     */
    public Font legendFont = DEFAULT_LEGEND_FONT;

    /**
     * Color that is used to draw the legend text for the chart's X and Y axis.
     */
    public Color legendTextColor = DEFAULT_LEGEND_TEXT_COLOR;

    /**
     * Width of lines in the chart.
     */
    public int lineWidth = DEFAULT_LINE_WIDTH;

    /**
     * Retrieves the color of the low data point horizontal line.
     */
    public Color lowLineColor = DEFAULT_LOW_COLOR;

    /**
     * Low range in the chart.
     */
    public double lowRange = Double.NaN;

    /**
     * High range color in the chart.
     */
    public Color lowRangeColor = DEFAULT_LOW_RANGE_COLOR;

    /**
     * Color of the peak data point horizontal line.
     */
    public Color peakLineColor = DEFAULT_PEAK_COLOR;

    /**
     * Sets a fixed size for the label width on the right vertical axis.
     */
    public int rightLabelWidth = -1;

    /**
     * Calculates and shows an Average line in the chart.
     */
    public boolean showAverage = false;

    /**
     * Shows a Baseline in the chart.
     */
    public boolean showBaseline = false;

    /**
     * Shows labels on the X axis bottom side.
     */
    public boolean showBottomLabels = true;

    /**
     * Shows a top legend.
     */
    public boolean showBottomLegend = true;

    /**
     * Shows events plotted on a chart.
     */
    public boolean showEvents = true;

    /**
     * Show full labels on the bottom x axis.
     */
    public boolean showFullLabels = false;

    /**
     * Shows a shaded high range in the chart.
     */
    public boolean showHighRange = false;

    /**
     * Shows labels on the Y axis left side.
     */
    public boolean showLeftLabels = true;

    /**
     * Shows a Left legend.
     */
    public boolean showLeftLegend = true;

    /**
     * Calculates and shows a Low line in the chart.
     */
    public boolean showLow = false;

    /**
     * Shows a shaded low range in the chart.
     */
    public boolean showLowRange = false;

    /**
     * Calculates and shows a peak line in the chart.
     */
    public boolean showPeak = false;

    /**
     * Shows labels on the Y axis right side.
     */
    public boolean showRightLabels = true;

    /**
     * Shows a Right legend.
     */
    public boolean showRightLegend = false;

    /**
     * Shows labels on the X axis top side.
     */
    public boolean showTopLabels = false;

    /**
     * Shows a top legend.
     */
    public boolean showTopLegend = false;

    /**
     * Shows vertical crossing lines at each data point on the y axis.
     */
    public boolean showUnitLines = false;

    /**
     * Shows horizontal crossing lines at each data point on the x axis.
     */
    public boolean showValueLines = false;

    /**
     * Shows values.
     */
    public boolean showValues = true;

    /**
     * The width of the white space between tick marks and the label text.
     */
    public int textWhitespace = DEFAULT_TEXT_WHITESPACE;

    /**
     * The height, in pixels, of the tick mark lines that extend
     * outside the chart's border. The tick marks for data points that don't
     * have a label are drawn at half this height.
     */
    public int tickMarkHeight = DEFAULT_TICK_MARK_HEIGHT;

    /**
     * Number of pixels to indent the value axis.
     */
    public int valueIndent = DEFAULT_VALUE_INDENT;

    /**
     * Number of value lines that the chart draws at data points along
     * the chart's value axis. For vertical chart's this is the Y axis. For
     * horizontal charts this is the chart's X axis. This number includes the
     * chart's top and bottom border.
     */
    public int valueLines = DEFAULT_VALUE_LINES;

    /**
     * Color of the border lines, horizontal lines and tick marks
     */
    public Color xLineColor = DEFAULT_X_LINE_COLOR;

    /**
     * Amount to offset the chart on the X axis in pixels.
     */
    public int xOffset = 0;

    /**
     * Amount to offset the chart on the Y axis in pixels.
     */
    public int yOffset = 0;

    ////////////////////////////////////////////////
    // Constructors

    /**
     * Constructs a Chart class with a default width of 755 pixels and a
     * default height of 300 pixels.
     */
    protected Chart() {
        this(1);
    }

    /**
     * Constructs a Chart class with a default width of 755 pixels and a
     * default height of 300 pixels.
     * 
     * @param charts
     *      The number of charts to display.
     */
    protected Chart(int charts) {
        this(Chart.DEFAULT_WIDTH, Chart.DEFAULT_HEIGHT, charts);
    }

    /**
     * Constructs a Chart class with a specified width and height.
     *
     * @param width
     *      The width of the chart in pixels.
     * @param height
     *      The height of the chart in pixels.
     */
    protected Chart(int width, int height) {
        this(width, height, 1);
    }

    /**
     * Constructs a Chart class with a specified width and height.
     *
     * @param width
     *      The width of the chart in pixels.
     * @param height
     *      The height of the chart in pixels.
     * @param charts
     *      The number of charts to display.
     */
    protected Chart(int width, int height, int charts) {
        super(width, height);
        this.shadowWidth = 0;
        this.textColor = DEFAULT_TEXT_COLOR;

        if (charts <= 0)
            charts = 1;
        this.setNumberDataSets(charts);

        this.initFonts();
    }

    ////////////////////////////////////////////////
    // Methods

    protected void draw(Graphics2D g) {
        super.draw(g);
        this.draw(new ChartGraphics(this, g));
    }

    protected Rectangle draw(ChartGraphics g) {
        /////////////////////////////////////////////////////////////
        // Draw the chart outline and fills the interior

        // Fill chart background
        Rectangle rect = this.getInteriorRectangle(g);

        if (this.m_bNoData == false) {
            // Fill chart interior
            g.graphics.setColor(this.m_clrFrame);
            g.graphics.drawRect(rect.x, rect.y, rect.width, rect.height);

            g.graphics.setColor(this.chartColor);
            g.graphics.fillRect(rect.x + this.lineWidth, rect.y + this.lineWidth, rect.width - (this.lineWidth),
                    rect.height - (this.lineWidth));
        } else {
            FontMetrics metrics = g.graphics.getFontMetrics(this.legendFont);

            g.graphics.setColor(this.m_clrLegendText);
            g.graphics.setFont(DEFAULT_LEGEND_PLAIN);
            g.graphics.drawString(this.m_strNoData, (this.width / 2) - (metrics.stringWidth(this.m_strNoData) / 2),
                    this.yOffset + (this.height / 2) + (metrics.getAscent() / 2));
        }

        return rect;
    }

    protected void calc(Graphics2D g) {
        FormattedNumber[] fmtValueLabels = UnitsFormat.formatSame(m_adRangeMarks, m_fmtType, m_fmtScale);

        Rectangle rect = new Rectangle(this.xOffset, this.yOffset, this.width, this.height);

        // Calculate the X axis    
        rect.x += this.leftBorder;

        if (this.showHighRange == true || this.showLowRange == true)
            rect.x += this.lineWidth;

        int xVertLabels = (this.showLeftLegend == true)
                ? rect.x + m_metricsLegend.charWidth('V') + (this.textWhitespace * 2)
                : 0;
        int cxLabels = (this.rightLabelWidth < 0) ? this.getYLabelWidth(g) : this.rightLabelWidth;

        if (this.showLeftLabels == true) {
            x2VertLabels = xVertLabels + cxLabels;
            xVertMarks = x2VertLabels + this.textWhitespace;
            rect.x = xVertMarks + this.tickMarkHeight;
        }

        rect.width = rect.width - rect.x - this.rightBorder;

        if (this.showRightLabels == true) {
            rect.width -= this.tickMarkHeight;
            rect.width = rect.width - cxLabels;
        } else {
            // We need to bring the right frame in a little if there's no right
            // label, but there are top or bottom labels.
            String[] labels = this.getXLabels();
            if (labels != null && labels.length > 1)
                rect.width -= (this.m_metricsLabel.stringWidth(labels[labels.length - 1]) / 2);
        }

        // Adjust the interior of the rectangle    
        Rectangle rectAdj = this.adjustRectangle(g, rect);

        x2VertMarks = rect.x + rect.width + (this.lineWidth * 2);
        if (this.showRightLabels == true)
            x2VertMarks += this.tickMarkHeight;

        xRLabel = x2VertMarks + this.textWhitespace;

        int yVertLegend = rect.y + (this.height / 2)
                - (m_metricsLegend.getAscent() * this.m_strValueLegend.length() / 2);

        // Calculate the Y axis
        rect.y = rect.y + this.topBorder;

        if (this.showTopLegend == true) {
            rect.y += m_metricsLegend.getAscent();
            yTopLegend = rect.y;
            rect.y += this.textWhitespace;
        }

        if (this.showTopLabels == true)
            rect.y += (this.getXLabelHeight() + this.tickMarkHeight);

        if (this.showTopLegend == false && this.showTopLabels == false)
            rect.y += (m_metricsLabel.getAscent() / 2);

        yBottomLegend = this.yOffset + (rect.height - this.bottomBorder);

        int yHorzMarks;
        if (this.showBottomLegend == false && this.showBottomLabels == false) {
            yHorzLabels = yBottomLegend - (m_metricsLabel.getAscent() / 2);
            yHorzMarks = yHorzLabels;
        } else {
            yHorzLabels = yBottomLegend - ((this.showBottomLegend == true) ? this.getXLabelHeight() : 0);
            yHorzMarks = yHorzLabels
                    - ((this.showBottomLabels == true) ? (this.m_metricsLabel.getAscent() + this.tickMarkHeight)
                            : 0);
        }

        int y2Rect = yHorzMarks - this.lineWidth;
        rect.height = y2Rect - rect.y;

        int xHorzLegend = (this.width / 2) - (m_metricsLegend.stringWidth(this.getUnitLegend()) / 2);
        int xHorzMarks = rect.x + this.valueIndent;
    }

    /**
     * Give the child class an opportunity to change the size of the interior
     * rectangle. This is done to make the tick marks fit symetrically in the
     * chart rectangle.
     */
    protected Rectangle adjustRectangle(Graphics2D g, Rectangle rect) {
        return rect;
    }

    /**
     * Calculates the high, low and average values of the chart data set.
     */
    protected void calcRanges() {
        int cActualVals = 0;
        int index;
        double unit;
        double topRange;

        // Calculate Top and Bottom Range
        double dVal;

        // ///////////////////////////////////////////////////////////////
        // Iterator through the DataSets to calculate the avg, low & peak
        Iterator iterDataSet = this.m_collDataPointColl.iterator();
        while (iterDataSet.hasNext() == true) {
            // Each DataSet has a collection of data points.
            Iterator iterDataPt = ((DataPointCollection) iterDataSet.next()).iterator();

            while (iterDataPt.hasNext() == true) {
                IDisplayDataPoint datapt = (IDisplayDataPoint) iterDataPt.next();

                // Skip NaN
                if (Double.isNaN(datapt.getValue()) == true)
                    continue;

                if (checkHighLow()) {
                    if (datapt instanceof IHighLowDataPoint) {
                        IHighLowDataPoint hlPt = (IHighLowDataPoint) datapt;
                        if (hlPt.getLowValue() != Double.NaN)
                            this.m_dLowValue = Math.min(this.m_dLowValue, hlPt.getLowValue());

                        if (hlPt.getHighValue() != Double.NaN)
                            this.m_dPeakValue = Math.max(this.m_dPeakValue, hlPt.getHighValue());
                    }
                }

                double[] vals = datapt instanceof IStackedDataPoint ? ((IStackedDataPoint) datapt).getValues()
                        : new double[] { datapt.getValue() };

                for (int i = 0; i < vals.length; i++) {
                    dVal = vals[i];
                    // Accumulate a total and number of points that make up the
                    // total                    
                    this.m_dAvgValue += dVal;
                    cActualVals++;

                    // Set the low
                    this.m_dLowValue = Math.min(this.m_dLowValue, dVal);

                    // Set the high
                    this.m_dPeakValue = Math.max(this.m_dPeakValue, dVal);
                }
            }
        }

        // Set the NoData flag and exit if we don't have any
        this.m_bNoData = (cActualVals == 0);
        if (this.m_bNoData == true) {
            this.m_adRangeMarks = new double[0];
            return;
        }

        // Caclulate the average
        this.m_dAvgValue /= cActualVals;

        ///////////////////////////////////////////////////////////////////
        // Calculate the value axis units (X for horiz, Y for vertical)

        if (this.ceiling == this.floor) {
            double range = (this.m_dPeakValue - this.m_dLowValue);

            if (range != 0) {
                // Buffer the top and bottom by 10%
                double buffer = range * .1;
                double topbuf = buffer / 2;
                double botbuf = (this.m_dLowValue - topbuf < 0) ? m_dLowValue : topbuf;

                range += (topbuf + botbuf);
                unit = range / (this.valueLines - 1);

                double tmp = range / unit;

                this.m_floor = this.m_dLowValue - botbuf;
                topRange = this.m_dPeakValue + topbuf;
            } else {
                // If the peak value and low value are the same create a range
                // that puts the charted value 1/2 up the chart
                this.m_floor = 0;

                if (this.m_dPeakValue == 0) {
                    topRange = (this.valueLines - 1);
                    if (this.m_fmtType == UnitsConstants.UNIT_DURATION) {
                        topRange *= 1000;
                    }
                } else {
                    topRange = this.m_dPeakValue * 2;
                }

                unit = (topRange - this.m_floor) / (this.valueLines - 1);
            }
        } else {
            // We just accept the floor and ceiling if they are preset by
            // whoever instantiated the chart
            this.m_floor = this.floor;
            unit = (this.ceiling - this.floor) / (this.valueLines - 1);
        }

        ////////////////////////////////////////////////////////////////////    
        // Calculate Cross Line Values

        this.m_adRangeMarks = new double[this.valueLines];

        for (int i = 0; i < this.valueLines; i++)
            this.m_adRangeMarks[i] = this.m_floor + (i * unit);
    }

    protected int calcVariableHeight() {
        return this.height;
    }

    protected int calcVariableWidth() {
        return this.width;
    }

    /**
     * Calculates the label width of the horizontal axis of the chart.
     *
     * @param graph
     *      The java.awt.Graphics context to draw into.
     *
     * @return
     *      The width of the widest label on the X (horizontal) axis.
     */
    protected int getXLabelWidth() {
        int iWidth;
        int iMaxWidth = 0;

        Iterator iter = this.getDataPoints().iterator();

        while (iter.hasNext()) {
            iWidth = m_metricsLabel.stringWidth(((IDisplayDataPoint) iter.next()).getLabel());

            if (iWidth > iMaxWidth)
                iMaxWidth = iWidth;
        }

        return iMaxWidth;
    }

    protected int getXLegendHeight() {
        int height = m_metricsLegend.getAscent() + (this.textWhitespace * 2);
        int result = 0;

        if (this.showTopLegend == true)
            result += height;

        if (this.showBottomLegend == true)
            result += height;

        return result;
    }

    /**
     * Calculates the label width of the vertical axis of the chart.
     *
     * @param graph
     *      The ChartGraphics context to draw into.
     *
     * @return
     *      The width of the widest label on the Y (vertical) axis.
     */
    protected abstract int getYLabelWidth(Graphics2D g);

    protected abstract String[] getXLabels();

    protected int getXLabelHeight() {
        int result = 0;

        if (this.showTopLabels == true || this.showBottomLabels == true) {
            String[] labels = this.getXLabels();
            int labelHeight = 0;

            if (labels != null && labels.length > 0) {
                labelHeight = this.tickMarkHeight
                        + ((labels != null) ? ChartGraphics.getStringHeight(labels[0], this.m_metricsLabel)
                                : this.m_metricsLabel.getAscent());
            } else {
                labelHeight = this.tickMarkHeight + m_metricsLabel.getAscent();
            }

            if (this.showTopLabels == true)
                result += labelHeight;

            if (this.showBottomLabels == true)
                result += labelHeight;
        }

        return result;
    }

    protected abstract Rectangle getInteriorRectangle(ChartGraphics g);

    protected int getExteriorHeight() {
        int cyLabel = this.getXLabelHeight();
        int cyLegend = this.getXLegendHeight();

        int cyBuf = (m_metricsLabel.getAscent() / 2);

        // Provide a little extra space if there is no bottom label or legend
        if (this.showBottomLabels == false && this.showBottomLegend == false)
            cyBuf += (m_metricsLabel.getAscent() / 2);

        return (this.topBorder + cyLegend + cyLabel + cyBuf + this.bottomBorder);
    }

    protected Collection initData(Collection coll) {
        return coll;
    }

    protected void initFonts() {
        // Initialize FontMetrics
        Image img = new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_BINARY);
        Graphics2D g = (Graphics2D) img.getGraphics();
        m_metricsLabel = g.getFontMetrics(this.font);
        m_metricsLegend = g.getFontMetrics(this.legendFont);
        g.dispose();
    }

    protected void preInit() {
        // Create the ChartGraphics
        Collection coll = this.initData(this.m_collDataPointColl);
        if (coll instanceof ArrayList)
            this.m_collDataPointColl = (ArrayList) coll;
        else
            throw new ClassCastException("initData() must return a collection of type ArrayList.");

        // Set the variable width and height
        if (this.height == Chart.VARIABLE_HEIGHT)
            this.height = this.calcVariableHeight();
        if (this.width == Chart.VARIABLE_WIDTH)
            this.width = this.calcVariableWidth();
    }

    protected void postInit(Graphics2D g) {
        // Calculate the base part of the image
        this.calcRanges();
        this.calc(g);
    }

    /**
     * Retrieves the value and unit coordinate for a data point in the chart.
     *
     * @param valuePixels
     *      An int that specifies the size of the value axis in pixels.
     * @param unitPixels
     *      An int that specifies the size of the unit axis in pixels.
     * @param datapoint
     *      An int that specifies the zero-based index of the datum to
     *      calculate coordinates for.
     * @param collection
     *      The DataPointCollection the datapoint is located in.
     *
     * @return
     *      A java.awt.Point object that contains the X and Y coordinate.
     *
     * @see java.awt.Rectangle
     */
    protected Point getDataPoint(int valuePixels, int unitPixels, int datapoint, DataPointCollection coll) {
        int cDataPts = coll.size();
        double dVal = ((IDisplayDataPoint) coll.get(datapoint)).getValue();
        return this.getDisplayPoint(valuePixels, unitPixels, cDataPts, dVal, datapoint);
    }

    /**
     * Retrieves the value and unit coordinate for a value on the x and y axis
     * in the chart.
     *
     * @param valuePixels
     *      An int that specifies the size of the value axis in pixels.
     * @param unitPixels
     *      An int that specifies the size of the unit axis in pixels.
     * @param unitPoints
     *      Number of ticks on the unit axis.
     * @param value
     *      A double that specifies the value to get coordinates for.
     * @param unitIndex
     *      Ticks on the unit axis to get coordinates for.
     *
     * @return
     *      A java.awt.Point object that contains the X and Y coordinate.
     */
    protected Point getDisplayPoint(int valuePixels, int unitPixels, int unitPoints, double value, int unitIndex) {
        int iSpread = unitPixels - (this.valueIndent * 2);
        iSpread = (unitPoints > 1) ? (iSpread / (unitPoints - 1)) : iSpread;

        if (Double.isNaN(value) == true)
            return null;

        if (this.ceiling != this.floor) {
            if (value < this.floor) {
                // This is a problem, set value to the floor
                log.error("Data point value (" + value + ") lower than floor (" + this.floor + ")");
                value = this.floor;
            } else if (value > this.ceiling) {
                // Could be availability, where ceiling is 100% and unknown is 2
                if (ceiling == 1 && value == 2)
                    return null;

                // This is a problem, set value to the ceiling
                log.error("Data point value (" + value + ") higher than ceiling (" + this.ceiling + ")");
                value = this.ceiling;
            }
        }

        // X = unitPixels, Y = valuePixels
        int x = this.valueIndent + (iSpread * unitIndex);
        int y = valuePixels - (int) Math.round((value - this.m_floor) * this.scale(valuePixels));

        if (x == 0)
            x++;
        else if (x == (unitPixels - this.lineWidth))
            x--;

        if (y == 0)
            y++;
        else if (y == valuePixels)
            y--;

        return new Point(x, y);
    }

    protected String getUnitLabel(IDisplayDataPoint data) {
        return data.getLabel();
    }

    protected boolean hasData() {
        return (this.m_bNoData == false);
    }

    /**
     * Calculates the scale of the graph data points. This is the number of
     * pixels per data point.
     *
     * @param height
     *      The height of the rectangle to calculate the vertical scale for.
     *
     * @return
     *      A floating point value that specifies the scale multiplier of the vertical axis.
     */
    protected double scale(int height) {
        double result = (height / (this.m_adRangeMarks[this.m_adRangeMarks.length - 1] - this.m_adRangeMarks[0]));
        return result;
    }

    protected Class getDataCollectionClass() {
        return DataPointCollection.class;
    }

    protected boolean checkHighLow() {
        return false;
    }

    //    protected IndexColorModel getIndexColorModel() {
    //        return (IndexColorModel)(new BufferedImage(
    //                   1, 1, BufferedImage.TYPE_BYTE_INDEXED)).getColorModel();
    //    }

    ////////////////////////////////////////////////
    // Properties

    /**
     * Retrieves the average value of the chart's data set.
     *
     * @return
     *      A floating point value that is the average value of the chart's
     *      data set.
     */
    public double getAverageValue() {
        return this.m_dAvgValue;
    }

    public String getNoDataString() {
        return this.m_strNoData;
    }

    public void setNoDataString(String s) {
        this.m_strNoData = (s == null) ? EMPTY_STRING : s;
    }

    /**
     * Retrieves the data set of the chart.
     *
     * @return
     *      A net.hyperic.chart.DataPointCollection object that contains the
     *      current data set points.
     *
     * @see net.hyperic.chart.DataPointCollection
     */
    public DataPointCollection getDataPoints() {
        return this.getDataPoints(0);
    }

    public EventPointCollection getEventPoints() {
        return this.getEventPoints(0);
    }

    public DataPointCollection getDataPoints(int index) {
        return (DataPointCollection) this.m_collDataPointColl.get(index);
    }

    public EventPointCollection getEventPoints(int index) {
        return (EventPointCollection) this.m_collEvtPointColl.get(index);
    }

    public int getDataSetCount() {
        return this.m_collDataPointColl.size();
    }

    public Iterator getDataSetIterator() {
        return this.m_collDataPointColl.iterator();
    }

    public Iterator getEventSetIterator() {
        return this.m_collEvtPointColl.iterator();
    }

    public void setNumberDataSets(int number) {
        int delta = number - m_collDataPointColl.size();

        if (delta > 0) {
            try {
                for (int i = 0; i < delta; i++) {
                    m_collDataPointColl.add(this.getDataCollectionClass().newInstance());
                    m_collEvtPointColl.add(new EventPointCollection());
                }
            } catch (Exception e) {
                System.out.println(e);
            }
        } else if (delta < 0) {
            for (int i = delta; i < 0; i++) {
                m_collDataPointColl.remove(m_collDataPointColl.size() - 1);
                m_collEvtPointColl.remove(m_collEvtPointColl.size() - 1);
            }
        }

        // Make sure we allways have at least one empty collection
        if (this.getDataSetCount() == 0) {
            try {
                m_collDataPointColl.add(this.getDataCollectionClass().newInstance());
            } catch (Exception e) {
                System.out.println(e);
            }
        }
    }

    public void setFormat(int type, int scale) {
        this.m_fmtType = type;
        this.m_fmtScale = scale;
    }

    public Rectangle getExteriorRectangle() {
        return new Rectangle(this.xOffset, this.yOffset, this.width, this.height);
    }

    /**
     * Retrieves the low value of the chart's data set.
     *
     * @return
     *      A floating point value that is the low value of the chart's
     *      data set.
     */
    public double getLowValue() {
        return this.m_dLowValue;
    }

    /**
     * Retrieves the peak value of the chart's data set.
     *
     * @return A floating point value that is the peak value of the chart's
     * data set.
     */
    public double getPeakValue() {
        return this.m_dPeakValue;
    }

    /**
     * Get the chart's title.
     * 
     * @return A string containing the chart's title.
     */
    public String getTitle() {
        return this.m_strTitle;
    }

    /**
     * Set the chart's title.
     * 
     * @param title A string containing the chart's title.
     */
    public void setTitle(String title) {
        this.m_strTitle = ((title == null) ? Chart.EMPTY_STRING : title);
    }

    /**
     * Retrieves the legend for the chart's Unit axis. The default legend is
     * "TIME".
     *
     * @return
     *      A java.lang.String object that contains the chart's Unit axis
     *      lagend.
     */
    public String getUnitLegend() {
        return this.m_strUnitLegend;
    }

    /**
     * Sets the legend for the chart's Unit axis.
     *
     * @param label
     *      A java.lang.String object that contains the chart's Unit axis
     *      legend.
     *
     * @exception IllegalArgumentException
     *      If the legend parameter is null.
     */
    public void setUnitLegend(String legend) {
        this.m_strUnitLegend = ((legend == null) ? Chart.EMPTY_STRING : legend);
    }

    /**
     * Retrieves the legend for the chart's Value axis. The default legend is
     * "VALUE".
     *
     * @return
     *      A java.lang.String object that contains the chart's Value axis
     *      legend.
     */
    public String getValueLegend() {
        return this.m_strValueLegend;
    }

    /**
     * Sets the legend for the chart's Value axis.
     *
     * @param label
     *      A java.lang.String object that contains the chart's Value axis
     *      legend.
     *
     * @exception IllegalArgumentException
     *      If the legend parameter is null.
     */
    public void setValueLegend(String legend) {
        this.m_strValueLegend = ((legend == null) ? Chart.EMPTY_STRING : legend);
    }

    public void setAbsTimeLabels(boolean useAbsTimeLabels, long interval) {
        m_useAbsTimeLabels = useAbsTimeLabels;
        m_smartLabelMaker = new SmartLabelMaker(interval);
    }
}