com.google.code.pentahoflashcharts.charts.AbstractChartFactory.java Source code

Java tutorial

Introduction

Here is the source code for com.google.code.pentahoflashcharts.charts.AbstractChartFactory.java

Source

/*
 * This program is free software; you can redistribute it and/or modify it under the 
 * terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software 
 * Foundation.
 *
 * You should have received a copy of the GNU Lesser General Public License along with this 
 * program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html 
 * or from the Free Software Foundation, Inc., 
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * 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 Lesser General Public License for more details.
 *
 * Copyright 2009 Pentaho Corporation.  All rights reserved.
 */
package com.google.code.pentahoflashcharts.charts;

import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import jofc2.model.Chart;
import jofc2.model.Text;
import jofc2.model.axis.Axis;
import jofc2.model.axis.XAxis;
import jofc2.model.axis.YAxis;
import jofc2.model.elements.Element;

import org.apache.commons.logging.Log;
import org.dom4j.Node;
import org.pentaho.commons.connection.IPentahoDataTypes;
import org.pentaho.commons.connection.IPentahoResultSet;
import org.pentaho.commons.connection.PentahoDataTransmuter;
import org.pentaho.platform.engine.core.system.PentahoSystem;
import org.pentaho.platform.engine.services.runtime.TemplateUtil;

import com.google.code.pentahoflashcharts.Messages;

public abstract class AbstractChartFactory implements IChartFactory {

    public static class MinMax {
        int min;
        int max;

        public MinMax(int min, int max) {
            this.min = min;
            this.max = max;
        }
    }

    // general chart related elements
    public static final String COLOR_NODE_LOC = "color"; //$NON-NLS-1$

    public static final String TITLE_NODE_LOC = "title"; //$NON-NLS-1$
    public static final String TITLE_FONT_NODE_LOC = "title-font"; //$NON-NLS-1$
    public static final String DATASET_TYPE_NODE_LOC = "dataset-type"; //$NON-NLS-1$
    public static final String COLOR_PALETTE_NODE_LOC = "color-palette"; //$NON-NLS-1$  
    public static final String PLOT_BACKGROUND_NODE_LOC = "plot-background"; //$NON-NLS-1$
    public static final String PLOT_BACKGROUND_COLOR_XPATH = "@type"; //attribute of plot-background  //$NON-NLS-1$
    public static final String CHART_BACKGROUND_NODE_LOC = "chart-background"; //$NON-NLS-1$
    public static final String CHART_BACKGROUND_COLOR_XPATH = "@type"; //attribute of chart-background  //$NON-NLS-1$
    public static final String URL_TEMPLATE_NODE_LOC = "url-template"; //$NON-NLS-1$
    public static final String USE_BASE_URL_NODE_LOC = "use-base-url"; //$NON-NLS-1$
    public static final String TOOLTIP_NODE_LOC = "tooltip"; //$NON-NLS-1$
    public static final String ORIENTATION_NODE_LOC = "orientation"; //$NON-NLS-1$
    public static final String ALPHA_NODE_LOC = "alpha"; //$NON-NLS-1$

    // font related elements 
    public static final String FONT_FAMILY_NODE_LOC = "font-family"; //$NON-NLS-1$
    public static final String FONT_SIZE_NODE_LOC = "size"; //$NON-NLS-1$
    public static final String FONT_BOLD_NODE_LOC = "is-bold"; //$NON-NLS-1$
    public static final String FONT_ITALIC_NODE_LOC = "is-italic"; //$NON-NLS-1$

    // domain axis related elements
    public static final String DOMAIN_STROKE_NODE_LOC = "domain-stroke"; //$NON-NLS-1$
    public static final String DOMAIN_GRID_COLOR_NODE_LOC = "domain-grid-color"; //$NON-NLS-1$
    public static final String DOMAIN_COLOR_NODE_LOC = "domain-color"; //$NON-NLS-1$
    public static final String DOMAIN_STEPS_NODE_LOC = "domain-steps"; //$NON-NLS-1$
    public static final String DOMAIN_TITLE_NODE_LOC = "domain-title"; //$NON-NLS-1$
    public static final String DOMAIN_TITLE_FONT_NODE_LOC = "domain-title-font"; //$NON-NLS-1$
    public static final String DOMAIN_MAXIMUM_NODE_LOC = "domain-maximum"; //$NON-NLS-1$
    public static final String DOMAIN_MINIMUM_NODE_LOC = "domain-minimum"; //$NON-NLS-1$

    // range axis related elements
    public static final String RANGE_STROKE_NODE_LOC = "range-stroke"; //$NON-NLS-1$
    public static final String RANGE_GRID_COLOR_NODE_LOC = "range-grid-color"; //$NON-NLS-1$
    public static final String RANGE_COLOR_NODE_LOC = "range-color"; //$NON-NLS-1$

    public static final String RANGE_STEPS_NODE_LOC = "range-steps"; //$NON-NLS-1$

    public static final String RANGE_TITLE_NODE_LOC = "range-title"; //$NON-NLS-1$
    public static final String RANGE_TITLE_FONT_NODE_LOC = "range-title-font"; //$NON-NLS-1$
    public static final String RANGE_MAXIMUM_NODE_LOC = "range-maximum"; //$NON-NLS-1$
    public static final String RANGE_MINIMUM_NODE_LOC = "range-minimum"; //$NON-NLS-1$

    // color types

    public static final String COLOR_TYPE = "color"; //$NON-NLS-1$

    public static final String CSS_FONT_STYLES = "font-family: {fontfamily}; font-size: {fontsize}px; " + //$NON-NLS-1$
            "font-weight: {fontweight}; font-style: {fontstyle};"; //$NON-NLS-1$

    // Orientation Type Values (ORIENTATION_NODE_LOC)
    public static final String HORIZONTAL_ORIENTATION = "horizontal"; //$NON-NLS-1$
    public static final String VERTICAL_ORIENTATION = "vertical"; //$NON-NLS-1$

    // Dataset Type Values
    public static final String CATEGORY_TYPE = "CategoryDataset"; //$NON-NLS-1$
    public static final String XY_TYPE = "XYSeriesCollection"; //$NON-NLS-1$
    public static final String XYZ_TYPE = "XYZSeriesCollection"; //$NON-NLS-1$

    // defaults
    public static final String DATASET_TYPE_DEFAULT = "CategoryDataset"; //$NON-NLS-1$
    public static final String CSS_FONT_FAMILY_DEFAULT = "Arial"; //$NON-NLS-1$
    public static final String CSS_FONT_SIZE_DEFAULT = "14"; //$NON-NLS-1$
    public static final String CSS_FONT_WEIGHT_DEFAULT = "normal"; //$NON-NLS-1$
    public static final String CSS_FONT_STYLE_DEFAULT = "normal"; //$NON-NLS-1$

    public static final String AXIS_GRID_COLOR_DEFAULT = "#aaaaaa"; //$NON-NLS-1$
    public static final String AXIS_COLOR_DEFAULT = "#000000"; //$NON-NLS-1$

    public static final String ORIENTATION_DEFAULT = "vertical"; //$NON-NLS-1$

    @SuppressWarnings("nls")
    public static final String[] COLORS_DEFAULT = { "#006666", "#0066CC", "#009999", "#336699", "#339966",
            "#3399FF", "#663366", "#666666", "#666699", "#669999", "#6699CC", "#66CCCC", "#993300", "#999933",
            "#999966", "#999999", "#9999CC", "#9999FF", "#99CC33", "#99CCCC", "#99CCFF", "#CC6600", "#CC9933",
            "#CCCC33", "#CCCC66", "#CCCC99", "#CCCCCC", "#FF9900", "#FFCC00", "#FFCC66" };

    protected ArrayList<String> colors = new ArrayList<String>();
    protected Chart chart = new Chart();
    protected ArrayList<Element> elements = new ArrayList<Element>();
    protected Node chartNode;
    private Log log;

    // data related members
    public String[] rowHeaders;
    public String[] columnHeaders;
    public IPentahoResultSet data;
    public boolean hasRowHeaders = false;
    public boolean hasColumnHeaders = false;

    protected String datasetType;

    // general chart members
    protected String orientation;
    protected String baseURLTemplate;
    protected String tooltipText;
    protected Float alpha;

    protected abstract void createElements();

    protected void setupStyles() {
        Node temp = chartNode.selectSingleNode(TOOLTIP_NODE_LOC);
        if (getValue(temp) != null) {
            tooltipText = getValue(temp);
        }

        temp = chartNode.selectSingleNode(ALPHA_NODE_LOC);
        if (getValue(temp) != null) {
            alpha = Float.parseFloat(getValue(temp));
        }
    }

    public void setChartNode(Node chartNode) {
        this.chartNode = chartNode;
    }

    public void setLog(Log log) {
        this.log = log;
    }

    public void validateData() {
        // make sure data is of a category dataset.
        if (getRowCount() < 1) {
            throw new RuntimeException(Messages.getErrorString("AbstractChartFactory.ERROR_0001_ROW_COUNT")); //$NON-NLS-1$
        }
        if (getColumnCount() < 1) {
            throw new RuntimeException(Messages.getErrorString("AbstractChartFactory.ERROR_0002_COLUMN_COUNT")); //$NON-NLS-1$
        }

        // check first row and first column, making sure the values are numbers
        for (int c = 0; c < getColumnCount(); c++) {
            Object data = getValueAt(0, c);
            if (!(data instanceof Number)) {
                throw new RuntimeException(Messages.getErrorString("AbstractChartFactory.ERROR_0003_INVALID_TYPE")); //$NON-NLS-1$
            }
        }

        // check first row
        for (int r = 1; r < getRowCount(); r++) {
            Object data = getValueAt(r, 0);
            if (!(data instanceof Number)) {
                throw new RuntimeException(Messages.getErrorString("AbstractChartFactory.ERROR_0003_INVALID_TYPE")); //$NON-NLS-1$
            }
        }
    }

    public String convertToJson() {

        // first, determine the dataset type

        Node temp = chartNode.selectSingleNode(DATASET_TYPE_NODE_LOC);
        if (getValue(temp) != null) {
            datasetType = getValue(temp);
        } else {
            // Default is CategoricalDataset
            datasetType = DATASET_TYPE_DEFAULT;
        }

        validateData();

        // These things apply to pretty much all charts
        setupColors();
        setupStyles();
        setupDomain();
        setupOnclick();

        // Build the elements (usually Chart Type specific)
        createElements();

        // Setup a few additional things after creating elements
        setupTitles();
        setupRange();

        chart.addElements(elements);
        return chart.toString();
    }

    //
    // Setup Methods
    //

    protected void setupTitles() {
        // in the Pentaho chart, range-title equals yLengend title
        Node rangeTitle = chartNode.selectSingleNode(RANGE_TITLE_NODE_LOC);
        Node rangeTitleFont = chartNode.selectSingleNode(RANGE_TITLE_FONT_NODE_LOC);
        Node title = chartNode.selectSingleNode(TITLE_NODE_LOC);

        // in the Pentaho chart, domain-title equals xLengend title
        Node domainTitle = chartNode.selectSingleNode(DOMAIN_TITLE_NODE_LOC);
        Node domainTitleFont = chartNode.selectSingleNode(DOMAIN_TITLE_FONT_NODE_LOC);
        Node titleFont = chartNode.selectSingleNode(TITLE_FONT_NODE_LOC);

        if (getValue(title) != null) {
            Text titleText = new Text();
            titleText.setText(getValue(title));
            titleText.setStyle(buildCSSStringFromNode(titleFont));
            chart.setTitle(titleText);
        }

        Text domainText = new Text();
        if (getValue(domainTitle) != null) {
            domainText.setText(getValue(domainTitle));
        } else {
            // TODO figure out what to do if the header isn't CategoryDataset
            domainText.setText(columnHeaders[0]);
        }
        domainText.setStyle(buildCSSStringFromNode(domainTitleFont));

        Text rangeText = new Text();
        if (getValue(rangeTitle) != null) {
            rangeText.setText(getValue(rangeTitle));
            rangeText.setStyle(buildCSSStringFromNode(rangeTitleFont));
            chart.setYLegend(rangeText);
        }

        // TODO: need to support YRightLegend, exposed as y2_legend in open flash charts

        chart.setXLegend(domainText);
    }

    /**
     * Setup colors for the series and also background
     */
    protected void setupColors() {

        Node temp = chartNode.selectSingleNode(COLOR_PALETTE_NODE_LOC);
        if (temp != null) {
            Object[] colorNodes = temp.selectNodes(COLOR_NODE_LOC).toArray();
            for (int j = 0; j < colorNodes.length; j++) {
                colors.add(getValue((Node) colorNodes[j]));
            }
        } else {
            for (int i = 0; i < COLORS_DEFAULT.length; i++) {
                colors.add(COLORS_DEFAULT[i]);
            }
        }

        // Use either chart-background or plot-background (chart takes precendence)
        temp = chartNode.selectSingleNode(PLOT_BACKGROUND_NODE_LOC);
        if (getValue(temp) != null) {
            String type = temp.valueOf(PLOT_BACKGROUND_COLOR_XPATH);
            if (type != null && COLOR_TYPE.equals(type)) {
                chart.setBackgroundColour(getValue(temp));
                chart.setInnerBackgroundColour(getValue(temp));
            }
        }
        temp = chartNode.selectSingleNode(CHART_BACKGROUND_NODE_LOC);
        if (getValue(temp) != null) {
            String type = temp.valueOf(CHART_BACKGROUND_COLOR_XPATH);
            if (type != null && COLOR_TYPE.equals(type))
                chart.setBackgroundColour(getValue(temp));
        }
    }

    public Axis setupDomain() {
        String[] labels = null;
        Number domainMin = null;
        Number domainMax = null;
        Integer stepforchart = null;

        if (CATEGORY_TYPE.equals(datasetType)) {
            int rowCount = getRowCount();
            labels = new String[rowCount];
            for (int j = 0; j < rowCount; j++) {
                labels[j] = getRowHeader(j);
            }
        } else if (XYZ_TYPE.equals(datasetType) || XY_TYPE.equals(datasetType)) {
            domainMin = ((Number) getValueAt(0, 0)).intValue();
            domainMax = domainMin;
            // Iterate over rows
            for (int r = 1; r < getRowCount(); r++) {
                if (domainMin.intValue() > ((Number) getValueAt(r, 0)).intValue()) {
                    domainMin = ((Number) getValueAt(r, 0)).intValue();
                }
                if (domainMax.intValue() < ((Number) getValueAt(r, 0)).intValue()) {
                    domainMax = ((Number) getValueAt(r, 0)).intValue();
                }
            }

            int steps = 9;
            int diff = domainMax.intValue() - domainMin.intValue();

            Node temp = chartNode.selectSingleNode(DOMAIN_STEPS_NODE_LOC);
            if (getValue(temp) != null) {
                steps = new Integer(getValue(temp)).intValue();
            }

            int chunksize = diff / steps;

            if (chunksize > 0) {
                stepforchart = new Integer(chunksize);
            }

            // If actual min is positive, don't go below ZERO
            if (domainMin.intValue() > 0 && domainMin.intValue() - chunksize < 0) {
                domainMin = 0;
            } else {
                domainMin = domainMin.intValue() - chunksize;
            }
            domainMax = domainMin.intValue() + (chunksize * (steps + 2));

            temp = chartNode.selectSingleNode(DOMAIN_MINIMUM_NODE_LOC);
            if (getValue(temp) != null) {
                domainMin = new Integer(getValue(temp)).intValue();
            }

            temp = chartNode.selectSingleNode(DOMAIN_MAXIMUM_NODE_LOC);
            if (getValue(temp) != null) {
                domainMax = new Integer(getValue(temp)).intValue();
            }
        }

        String domainColor = AXIS_COLOR_DEFAULT;
        String domainGridColor = AXIS_GRID_COLOR_DEFAULT;
        int domainStroke = 1;

        Node temp = chartNode.selectSingleNode(DOMAIN_COLOR_NODE_LOC);
        if (getValue(temp) != null) {
            domainColor = getValue(temp);
        }

        temp = chartNode.selectSingleNode(DOMAIN_GRID_COLOR_NODE_LOC);
        if (getValue(temp) != null) {
            domainGridColor = getValue(temp);
        }

        temp = chartNode.selectSingleNode(DOMAIN_STROKE_NODE_LOC);
        if (getValue(temp) != null) {
            domainStroke = Integer.parseInt(getValue(temp));
        }

        // Orientation
        temp = chartNode.selectSingleNode(ORIENTATION_NODE_LOC);
        if (getValue(temp) != null) {
            orientation = getValue(temp);
        } else {
            orientation = ORIENTATION_DEFAULT;
        }

        if (HORIZONTAL_ORIENTATION.equals(orientation)) {
            YAxis yaxis = new YAxis();
            if (labels != null) {

                // BISERVER-3075: must reverse the category labels on hbar
                // charts due to bug in OFC2. 
                String[] reversedLabels = new String[labels.length];
                int reversedLabelCount = 0;
                for (int i = reversedLabels.length - 1; i >= 0; i--) {
                    reversedLabels[reversedLabelCount++] = labels[i];
                }

                yaxis.setLabels(reversedLabels);
            }
            yaxis.setStroke(domainStroke);
            yaxis.setColour(domainColor);
            yaxis.setGridColour(domainGridColor);

            if (domainMin != null && domainMax != null) {
                yaxis.setRange(domainMin.intValue(), domainMax.intValue(), stepforchart);
            }

            chart.setYAxis(yaxis);
            return yaxis;
        } else {
            XAxis xaxis = new XAxis();
            if (labels != null) {
                xaxis.addLabels(labels);
            }
            xaxis.setStroke(domainStroke);
            xaxis.setColour(domainColor);
            xaxis.setGridColour(domainGridColor);
            if (domainMin != null && domainMax != null) {
                xaxis.setRange(domainMin.intValue(), domainMax.intValue(), stepforchart);
            }

            chart.setXAxis(xaxis);
            return xaxis;
        }
    }

    public MinMax getRangeMinMax() {
        int rangeMin = 0;
        int rangeMax = 100;
        if (XYZ_TYPE.equals(datasetType) || XY_TYPE.equals(datasetType)) {
            rangeMin = ((Number) getValueAt(0, 1)).intValue();
            rangeMax = rangeMin;
            // Iterate over 2nd row
            for (int r = 0; r < getRowCount(); r++) {
                if (rangeMin > ((Number) getValueAt(r, 1)).intValue())
                    rangeMin = ((Number) getValueAt(r, 1)).intValue();
                if (rangeMax < ((Number) getValueAt(r, 1)).intValue())
                    rangeMax = ((Number) getValueAt(r, 1)).intValue();
            }
        } else {
            rangeMin = ((Number) getValueAt(0, 0)).intValue();
            rangeMax = rangeMin;
            // Iterate over columns 1+
            for (int c = 0; c < getColumnCount(); c++) {
                for (int r = 0; r < getRowCount(); r++) {
                    if (rangeMin > ((Number) getValueAt(r, c)).intValue())
                        rangeMin = ((Number) getValueAt(r, c)).intValue();
                    if (rangeMax < ((Number) getValueAt(r, c)).intValue())
                        rangeMax = ((Number) getValueAt(r, c)).intValue();
                }
            }
        }
        return new MinMax(rangeMin, rangeMax);
    }

    public Axis setupRange() {
        int rangeMin = 0;
        int rangeMax = 100;
        int steps = 9;

        String rangeColor = AXIS_COLOR_DEFAULT;
        String rangeGridColor = AXIS_GRID_COLOR_DEFAULT;
        int rangeStroke = 1;

        MinMax rangeMinMax = getRangeMinMax();
        rangeMin = rangeMinMax.min;
        rangeMax = rangeMinMax.max;

        boolean minDefined = false;
        boolean maxDefined = false;

        Node temp = chartNode.selectSingleNode(RANGE_MINIMUM_NODE_LOC);
        if (getValue(temp) != null) {
            rangeMin = new Integer(getValue(temp)).intValue();
            minDefined = true;
        }

        temp = chartNode.selectSingleNode(RANGE_MAXIMUM_NODE_LOC);
        if (getValue(temp) != null) {
            rangeMax = new Integer(getValue(temp)).intValue();
            maxDefined = true;
        }

        temp = chartNode.selectSingleNode(RANGE_STEPS_NODE_LOC);
        if (getValue(temp) != null) {
            steps = new Integer(getValue(temp)).intValue();
        }

        temp = chartNode.selectSingleNode(RANGE_COLOR_NODE_LOC);
        if (getValue(temp) != null) {
            rangeColor = getValue(temp);
        }

        temp = chartNode.selectSingleNode(RANGE_GRID_COLOR_NODE_LOC);
        if (getValue(temp) != null) {
            rangeGridColor = getValue(temp);
        }

        temp = chartNode.selectSingleNode(RANGE_STROKE_NODE_LOC);
        if (getValue(temp) != null) {
            rangeStroke = Integer.parseInt(getValue(temp));
        }

        int diff = rangeMax - rangeMin;

        int chunksize = diff / steps;

        Integer stepforchart = null;
        if (chunksize > 0)
            stepforchart = new Integer(chunksize);

        // Readjust mins/maxs only if they weren't specified
        if (!minDefined) {
            // If actual min is positive, don't go below ZERO
            if (rangeMin >= 0 && rangeMin - chunksize < 0)
                rangeMin = 0;
            else
                rangeMin = rangeMin - chunksize;
        }
        if (!maxDefined) {
            rangeMax = rangeMin + (chunksize * (steps + 2));
        }

        if (HORIZONTAL_ORIENTATION.equals(orientation)) {
            XAxis xaxis = new XAxis();
            xaxis.setRange(rangeMin, rangeMax, stepforchart);
            xaxis.setStroke(rangeStroke);
            xaxis.setColour(rangeColor);
            xaxis.setGridColour(rangeGridColor);
            chart.setXAxis(xaxis);
            return xaxis;
        } else {
            YAxis yaxis = new YAxis();
            yaxis.setRange(rangeMin, rangeMax, stepforchart);
            yaxis.setStroke(rangeStroke);
            yaxis.setColour(rangeColor);
            yaxis.setGridColour(rangeGridColor);
            chart.setYAxis(yaxis);
            return yaxis;
        }
    }

    public void setupOnclick() {
        Node urlTemplateNode = chartNode.selectSingleNode(URL_TEMPLATE_NODE_LOC);
        Node useBaseURLNode = chartNode.selectSingleNode(USE_BASE_URL_NODE_LOC);

        baseURLTemplate = "";
        // default is "false" if unspecified
        if (getValue(useBaseURLNode) != null || "true".equals(getValue(useBaseURLNode))) {
            //baseURLTemplate += "http://localhost:8080/pentaho/";
            //baseURLTemplate += PentahoSystem.getSystemSetting("base-url", "http://");
            baseURLTemplate += PentahoSystem.getApplicationContext().getBaseUrl();
        }
        if (getValue(urlTemplateNode) != null) {
            baseURLTemplate += getValue(urlTemplateNode);

        }
    }

    //
    // Data Related Methods
    // 

    public void setData(IPentahoResultSet data) {
        hasRowHeaders = data.getMetaData().getRowHeaders() != null;
        hasColumnHeaders = data.getMetaData().getColumnHeaders() != null;
        if (!hasRowHeaders || !hasColumnHeaders) {
            // this populates the data's row header and col headers if not already populated
            data = PentahoDataTransmuter.transmute(data, false);
        }
        try {
            rowHeaders = PentahoDataTransmuter.getCollapsedHeaders(IPentahoDataTypes.AXIS_ROW, data, '|');
            columnHeaders = PentahoDataTransmuter.getCollapsedHeaders(IPentahoDataTypes.AXIS_COLUMN, data, '|');
        } catch (Exception e) {
            // should really NEVER get here
            if (log != null) {
                log.error(null, e);
            }
        }

        this.data = data;
    }

    protected int getColumnCount() {
        if (!hasRowHeaders) {
            return data.getColumnCount() - 1;
        } else {
            return data.getColumnCount();
        }
    }

    protected int getRowCount() {
        if (!hasColumnHeaders) {
            return data.getRowCount() - 1;
        } else {
            return data.getRowCount();
        }
    }

    protected String getRowHeader(int r) {
        if (!hasColumnHeaders) {
            r = r + 1;
        }
        return rowHeaders[r];
    }

    protected String getColumnHeader(int c) {
        if (!hasRowHeaders) {
            c = c + 1;
        }
        return columnHeaders[c];
    }

    protected Object getValueAt(int r, int c) {
        if (!hasRowHeaders) {
            c = c + 1;
        }
        if (!hasColumnHeaders) {
            r = r + 1;
        }
        return data.getValueAt(r, c);
    }

    //
    // Utility Methods
    //

    public static String buildCSSStringFromNode(Node n) {
        String fontFamily = getNodeValue(n, FONT_FAMILY_NODE_LOC);
        String fontSize = getNodeValue(n, FONT_SIZE_NODE_LOC);
        String fontWeight = null;
        if ("true".equals(getNodeValue(n, FONT_BOLD_NODE_LOC))) { //$NON-NLS-1$
            fontWeight = "bold"; //$NON-NLS-1$
        }
        String fontStyle = null;
        if ("true".equals(getNodeValue(n, FONT_ITALIC_NODE_LOC))) { //$NON-NLS-1$
            fontStyle = "italic"; //$NON-NLS-1$
        }
        return buildCSSString(fontFamily, fontSize, fontWeight, fontStyle);
    }

    public static String buildCSSString(String fontfamily, String fontsize, String fontweight, String fontstyle) {
        Properties props = new Properties();
        props.put("fontfamily", fontfamily != null ? fontfamily : CSS_FONT_FAMILY_DEFAULT); //$NON-NLS-1$
        props.put("fontsize", fontsize != null ? fontsize : CSS_FONT_SIZE_DEFAULT); //$NON-NLS-1$
        props.put("fontweight", fontweight != null ? fontweight : CSS_FONT_WEIGHT_DEFAULT); //$NON-NLS-1$
        props.put("fontstyle", fontstyle != null ? fontstyle : CSS_FONT_STYLE_DEFAULT); //$NON-NLS-1$

        return TemplateUtil.applyTemplate(CSS_FONT_STYLES, props, null);
    }

    protected String getColor(int i) {
        return colors.get(i % colors.size());
    }

    public static String getNodeValue(Node parent, String node) {
        if (parent == null) {
            return null;
        }
        Node textNode = parent.selectSingleNode(node);
        return getValue(textNode);
    }

    public static String getValue(Node n) {
        if (n != null && n.getText() != null && n.getText().length() > 0) {
            return n.getText().trim();
        } else {
            return null;
        }
    }

    protected String buildURLTemplate(String seriesName, String categoryName) {

        if (baseURLTemplate != null) {
            String seriesReplacementString = "";
            if (seriesName != null) {
                seriesReplacementString = seriesName;
            }
            String categoryReplacementString = "";
            if (categoryName != null) {
                categoryReplacementString = categoryName;
            }

            return this.baseURLTemplate.replace("{series}", seriesReplacementString).replace("{category}",
                    categoryReplacementString);
        }
        return null;
    }
}