net.sourceforge.processdash.ui.web.CGIChartBase.java Source code

Java tutorial

Introduction

Here is the source code for net.sourceforge.processdash.ui.web.CGIChartBase.java

Source

// Copyright (C) 2001-2016 Tuma Solutions, LLC
// Process Dashboard - Data Automation Tool for high-maturity processes
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 3
// of the License, or (at your option) any later version.
//
// Additional permissions also apply; see the README-license.txt
// file in the project root directory for more information.
//
// 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, see <http://www.gnu.org/licenses/>.
//
// The author(s) may be contacted at:
//     processdash@tuma-solutions.com
//     processdash-devel@lists.sourceforge.net

package net.sourceforge.processdash.ui.web;

import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.GradientPaint;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.Map;

import javax.imageio.ImageIO;

import org.jfree.chart.ChartRenderingInfo;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.Axis;
import org.jfree.chart.axis.CategoryAxis;
import org.jfree.chart.axis.CategoryLabelPositions;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.entity.ChartEntity;
import org.jfree.chart.entity.EntityCollection;
import org.jfree.chart.entity.StandardEntityCollection;
import org.jfree.chart.imagemap.ImageMapUtilities;
import org.jfree.chart.imagemap.StandardToolTipTagFragmentGenerator;
import org.jfree.chart.imagemap.StandardURLTagFragmentGenerator;
import org.jfree.chart.imagemap.ToolTipTagFragmentGenerator;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.title.LegendTitle;
import org.jfree.chart.title.TextTitle;
import org.jfree.ui.RectangleInsets;

import net.sourceforge.processdash.Settings;
import net.sourceforge.processdash.data.util.ResultSet;
import net.sourceforge.processdash.i18n.Resources;
import net.sourceforge.processdash.i18n.Translator;
import net.sourceforge.processdash.util.HTMLUtils;
import net.sourceforge.processdash.util.StringUtils;

public abstract class CGIChartBase extends net.sourceforge.processdash.ui.web.TinyCGIBase {

    protected ResultSet data = null;
    protected boolean chromeless = false;

    private static Color INVISIBLE = new Color(1f, 1f, 1f, 0f);

    private static final String HTML_PARAM = "html";

    private static final Resources resources = Resources.getDashBundle("Analysis");

    protected boolean isHtmlMode() {
        return parameters.containsKey(HTML_PARAM);
    }

    /** Write the CGI header. */
    @Override
    protected void writeHeader() {
        if (isHtmlMode())
            writeHtmlHeader();
        else
            writeImageHeader();
    }

    protected void writeHtmlHeader() {
        super.writeHeader();
    }

    protected void writeImageHeader() {
        out.print("Content-type: image/png\r\n\r\n");
        out.flush();
    }

    /** create the data upon which this chart is based. */
    protected void buildData() throws IOException {
        retrieveParamsFromServlet("dqf");
        massageParameters();
        String prefix = getPrefix();
        data = ResultSet.get(getDataRepository(), parameters, prefix, getPSPProperties());
        if (parameters.get("transpose") != null)
            data = data.transpose();
    }

    /** Generate CGI chart output. */
    @Override
    protected void writeContents() throws IOException {
        buildData(); // get the data for display

        chromeless = (parameters.get("chromeless") != null);
        JFreeChart chart = createChart();

        int width = getIntSetting("width");
        int height = getIntSetting("height");
        Color initGradColor = getColorSetting("initGradColor");
        Color finalGradColor = getColorSetting("finalGradColor");
        chart.setBackgroundPaint(new GradientPaint(0, 0, initGradColor, width, height, finalGradColor));
        if (parameters.get("hideOutline") != null)
            chart.getPlot().setOutlinePaint(INVISIBLE);

        String title = getSetting("title");
        if (chromeless || title == null || title.length() == 0)
            chart.setTitle((TextTitle) null);
        else {
            chart.setTitle(Translator.translate(title));
            String titleFontSize = getSetting("titleFontSize");
            if (titleFontSize != null)
                try {
                    float fontSize = Float.parseFloat(titleFontSize);
                    TextTitle t = chart.getTitle();
                    t.setFont(t.getFont().deriveFont(fontSize));
                } catch (Exception tfe) {
                }
        }

        if (chromeless || parameters.get("hideLegend") != null)
            chart.removeLegend();
        else {
            LegendTitle l = chart.getLegend();
            String legendFontSize = getSetting("legendFontSize");
            if (l != null && legendFontSize != null)
                try {
                    float fontSize = Float.parseFloat(legendFontSize);
                    l.setItemFont(l.getItemFont().deriveFont(fontSize));
                } catch (Exception lfe) {
                }
        }

        chart.getPlot().setNoDataMessage(resources.getString("No_Data_Message"));

        Axis xAxis = getHorizontalAxis(chart);
        if (xAxis != null) {
            if (parameters.get("hideTickLabels") != null || parameters.get("hideXTickLabels") != null) {
                xAxis.setTickLabelsVisible(false);
            } else if (parameters.get("tickLabelFontSize") != null
                    || parameters.get("xTickLabelFontSize") != null) {
                String tfs = getParameter("xTickLabelFontSize");
                if (tfs == null)
                    tfs = getParameter("tickLabelFontSize");
                float fontSize = Float.parseFloat(tfs);
                xAxis.setTickLabelFont(xAxis.getTickLabelFont().deriveFont(fontSize));
            }
        }

        Axis yAxis = getVerticalAxis(chart);
        if (yAxis != null) {
            if (parameters.get("hideTickLabels") != null || parameters.get("hideYTickLabels") != null) {
                yAxis.setTickLabelsVisible(false);
            } else if (parameters.get("tickLabelFontSize") != null
                    || parameters.get("yTickLabelFontSize") != null) {
                String tfs = getParameter("yTickLabelFontSize");
                if (tfs == null)
                    tfs = getParameter("tickLabelFontSize");
                float fontSize = Float.parseFloat(tfs);
                yAxis.setTickLabelFont(yAxis.getTickLabelFont().deriveFont(fontSize));
            }
        }

        String axisFontSize = getSetting("axisLabelFontSize");
        if (axisFontSize != null)
            try {
                float fontSize = Float.parseFloat(axisFontSize);
                if (xAxis != null)
                    xAxis.setLabelFont(xAxis.getLabelFont().deriveFont(fontSize));
                if (yAxis != null)
                    yAxis.setLabelFont(yAxis.getLabelFont().deriveFont(fontSize));
            } catch (Exception afs) {
            }

        ChartRenderingInfo info = (isHtmlMode() ? new ChartRenderingInfo() : null);
        BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics2D g2 = img.createGraphics();
        if ("auto".equals(getSetting("titleFontSize")))
            maybeAdjustTitleFontSize(chart, g2, width);
        chart.draw(g2, new Rectangle2D.Double(0, 0, width, height), info);
        g2.dispose();

        String outputFormat = getSetting("outputFormat");
        OutputStream imgOut;
        if (isHtmlMode()) {
            imgOut = PngCache.getOutputStream();
        } else {
            imgOut = outStream;
        }
        ImageIO.write(img, outputFormat, imgOut);
        imgOut.flush();
        imgOut.close();
        if (isHtmlMode())
            writeImageHtml(width, height, imgOut.hashCode(), info);
    }

    private void writeImageHtml(int width, int height, int imgID, ChartRenderingInfo info) throws IOException {
        String tooltip = getParameter("tooltip");
        if (!StringUtils.hasValue(tooltip))
            tooltip = resources.getHTML("More_Detail_Here_Instruction");

        String href = getParameter("href");
        if (StringUtils.hasValue(href)) {
            // create a copy of the entity collection, and place an entity for
            // the entire chart at the beginning of the list.  This will
            // make it appear last in the image map (which is important,
            // because browsers process the image map areas in the order that
            // they appear; having the entire chart area listed first would
            // obscure all of the other image map areas).
            EntityCollection entities = new StandardEntityCollection();
            entities.add(new ChartEntity(info.getChartArea(), tooltip, href));
            if (info.getEntityCollection() != null)
                entities.addAll(info.getEntityCollection());

            // Next: most of our chart entities will not have URLs. URL values
            // don't inherit in the imagemap, so if we want the entire image
            // to have a single URL, we need to assign that URL to every
            // area in the chart.
            for (Iterator i = entities.iterator(); i.hasNext();) {
                ChartEntity ce = (ChartEntity) i.next();
                // check for no-op chart entity - these won't appear in the
                // image map anyway, so they don't need to be adjusted
                if (ce.getToolTipText() == null && ce.getURLText() == null)
                    continue;
                // for other entities, add a tooltip and a URL as needed.
                if (!StringUtils.hasValue(ce.getToolTipText()))
                    ce.setToolTipText(tooltip);
                if (!StringUtils.hasValue(ce.getURLText()))
                    ce.setURLText(href);
            }

            // install our modified version of the entity collection into
            // the chart info object, so it will be used when generating
            // the image map later.
            info.setEntityCollection(entities);
        }

        // write the image tag
        out.write("<img width=\"" + width + "\" height=\"" + height + "\" src=\"/reports/pngCache?id=" + imgID
                + "\" usemap=\"#IMG" + imgID + '"');

        // imagemaps have hyperlink borders by default, even if we don't
        // have a URL we're pointing to.  Turn that border off.
        if (!StringUtils.hasValue(href) || parameters.containsKey("noBorder"))
            out.write(" border=\"0\"");

        // Our client might want to add attributes to the image tag. Look
        // through the query parameters we received for arbitrary attributes
        // starting with the prefix "img", and copy them into the tag.
        for (Iterator i = parameters.entrySet().iterator(); i.hasNext();) {
            Map.Entry e = (Map.Entry) i.next();
            String attrName = (String) e.getKey();
            if (attrName.startsWith("img") && !attrName.endsWith("_ALL")) {
                out.write(" " + attrName.substring(3) + "=\"");
                out.write(HTMLUtils.escapeEntities(e.getValue().toString()));
                out.write('"');
            }
        }

        out.write(">");

        // finally, write the image map.  Note that we have to strip line
        // break characters from the resulting HTML, otherwise firefox seems
        // to decide that the <map> tag actually takes up space on the page
        String imageMapHtml = ImageMapUtilities.getImageMap("IMG" + imgID, info, getToolTipGenerator(tooltip),
                new StandardURLTagFragmentGenerator());
        for (int i = 0; i < imageMapHtml.length(); i++) {
            char c = imageMapHtml.charAt(i);
            if (c != '\r' && c != '\n')
                out.write(c);
        }
        out.flush();
    }

    protected ToolTipTagFragmentGenerator getToolTipGenerator(String defaultTooltip) {
        String tooltipType = getSetting("tooltips");
        if ("overlib".equalsIgnoreCase(tooltipType))
            return new CustomOverlibFragmentGenerator(defaultTooltip);
        else if ("plain".equalsIgnoreCase(tooltipType))
            return new StandardToolTipTagFragmentGenerator();
        else
            return null;
    }

    private static class CustomOverlibFragmentGenerator implements ToolTipTagFragmentGenerator {

        private String defaultTooltip;

        public CustomOverlibFragmentGenerator(String tooltip) {
            this.defaultTooltip = tooltip;
        }

        public String generateToolTipFragment(String toolTipText) {
            // In the dashboard, many tool tip generators are dual purpose
            // (generating tooltips both for rich client and HTML views). To
            // produce styled tips in the rich client, generators must wrap the
            // tool tip text in an <html> tag. If we see this pattern when
            // producing an HTML report, strip the <html> and <body> tags.
            if (toolTipText.startsWith("<html>"))
                toolTipText = toolTipText.substring(6);
            if (toolTipText.endsWith("</html>"))
                toolTipText = toolTipText.substring(0, toolTipText.length() - 7);
            if (toolTipText.startsWith("<body>"))
                toolTipText = toolTipText.substring(6);
            if (toolTipText.endsWith("</body>"))
                toolTipText = toolTipText.substring(0, toolTipText.length() - 7);

            // generate HTML attributes to display the tip via overLib.
            StringBuilder result = new StringBuilder();
            result.append(" onMouseOver=\"return overlib('");
            result.append(ImageMapUtilities.htmlEscape(ImageMapUtilities.javascriptEscape(toolTipText)));
            if (toolTipText.equals(defaultTooltip))
                result.append("', DELAY, 1000, FOLLOWMOUSE, FGCOLOR, '#FFFFCC");
            result.append("');\" onMouseOut=\"return nd();\"");
            return result.toString();
        }

    }

    protected void maybeAdjustTitleFontSize(JFreeChart chart, Graphics2D g, int width) {
        // if the chart has no title, do nothing.
        if (chart.getTitle() == null)
            return;
        String title = chart.getTitle().getText();
        if (title == null || title.length() == 0)
            return;

        // compute the width needed to draw the title.
        Font f = chart.getTitle().getFont();
        Font sf = getFontSizeToFit(g, f, title, (int) (width * 0.85));
        if (f != sf)
            chart.getTitle().setFont(sf);
    }

    protected Font getFontSizeToFit(Graphics2D g, Font baseFont, String text, int width) {
        FontMetrics m = g.getFontMetrics(baseFont);
        int currentWidth = m.stringWidth(text);
        Font result = baseFont;

        int maxIter = 100;
        while (currentWidth > width && maxIter-- > 0) {
            result = result.deriveFont(result.getSize2D() * 0.95f);
            m = g.getFontMetrics(result);
            currentWidth = m.stringWidth(text);
        }

        return result;
    }

    public static final String SETTING_PREFIX = "chart.";

    protected String getSetting(String name) {
        String result = getParameter(name);
        if (result == null)
            result = Settings.getVal(SETTING_PREFIX + name);
        return result;
    }

    protected int getIntSetting(String name) {
        try {
            return Integer.parseInt(getParameter(name));
        } catch (Exception e) {
        }
        try {
            return Integer.parseInt(Settings.getVal(SETTING_PREFIX + name));
        } catch (Exception e) {
        }
        return -1;
    }

    protected Color getColorSetting(String name) {
        String val = getParameter(name);
        if (val != null)
            try {
                if (val.startsWith("_"))
                    val = "#" + val.substring(1);
                return Color.decode(val);
            } catch (Exception e) {
            }
        return Color.decode(Settings.getVal(SETTING_PREFIX + name));
    }

    protected static float ALPHA = 1.0f;

    protected boolean get3DSetting() {
        String setting = getSetting("3d");

        if ("false".equalsIgnoreCase(setting))
            return false;

        if ("true".equalsIgnoreCase(setting)) {
            ALPHA = 1.0f;
            return true;
        }

        if (setting != null)
            try {
                ALPHA = Float.parseFloat(setting);
                return true;
            } catch (Exception e) {
            }

        ALPHA = 1.0f;
        return true;
    }

    protected void setupCategoryChart(JFreeChart chart) {

        if (!(chart.getPlot() instanceof CategoryPlot))
            return;
        CategoryPlot cp = chart.getCategoryPlot();

        CategoryAxis catAxis = cp.getDomainAxis();
        ValueAxis otherAxis = cp.getRangeAxis();

        if (!chromeless) {
            String catAxisLabel = data.getColName(0);
            if (catAxisLabel == null)
                catAxisLabel = Translator.translate("Project/Task");

            String otherAxisLabel = Translator.translate(getSetting("units"));
            if ((otherAxisLabel == null || otherAxisLabel.length() == 0) && data.numCols() == 1)
                otherAxisLabel = data.getColName(1);
            if (otherAxisLabel == null)
                otherAxisLabel = Translator.translate("Value");

            String catLabels = getParameter("categoryLabels");

            catAxis.setLabel(catAxisLabel);
            otherAxis.setLabel(otherAxisLabel);
            if ("vertical".equalsIgnoreCase(catLabels))
                catAxis.setCategoryLabelPositions(CategoryLabelPositions.UP_45);
            else if ("none".equalsIgnoreCase(catLabels))
                catAxis.setTickLabelsVisible(false);
        }

        if (data.numCols() == 1 && getParameter("noSkipLegend") == null) {
            chart.removeLegend();
            chart.getPlot().setInsets(new RectangleInsets(5, 2, 2, 5));
        }
    }

    /** Optionally massage the parameters before the ResultSet is generated */
    public void massageParameters() {
    }

    /** Create some specific type of chart */
    public abstract JFreeChart createChart();

    protected Axis getHorizontalAxis(JFreeChart chart) {
        return getAxis(chart, PlotOrientation.HORIZONTAL);
    }

    protected Axis getVerticalAxis(JFreeChart chart) {
        return getAxis(chart, PlotOrientation.VERTICAL);
    }

    protected Axis getAxis(JFreeChart chart, PlotOrientation dir) {
        try {
            CategoryPlot p = chart.getCategoryPlot();
            if (dir.equals(p.getOrientation()))
                return p.getRangeAxis();
            else
                return p.getDomainAxis();
        } catch (Exception e) {
            return null;
        }
    }
}