org.pentaho.chart.plugin.jfreechart.chart.dial.JFreeDialChartGenerator.java Source code

Java tutorial

Introduction

Here is the source code for org.pentaho.chart.plugin.jfreechart.chart.dial.JFreeDialChartGenerator.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 (c) 2002-2017 Hitachi Vantara..  All rights reserved.
*/

package org.pentaho.chart.plugin.jfreechart.chart.dial;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.GradientPaint;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Paint;
import java.awt.Point;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.Arc2D;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.ImageObserver;

import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.PlotRenderingInfo;
import org.jfree.chart.plot.PlotState;
import org.jfree.chart.plot.dial.DialBackground;
import org.jfree.chart.plot.dial.DialCap;
import org.jfree.chart.plot.dial.DialLayerChangeEvent;
import org.jfree.chart.plot.dial.DialPlot;
import org.jfree.chart.plot.dial.DialPointer;
import org.jfree.chart.plot.dial.DialScale;
import org.jfree.chart.plot.dial.DialTextAnnotation;
import org.jfree.chart.plot.dial.DialValueIndicator;
import org.jfree.chart.plot.dial.StandardDialFrame;
import org.jfree.chart.plot.dial.StandardDialRange;
import org.jfree.chart.plot.dial.StandardDialScale;
import org.jfree.data.general.ValueDataset;
import org.jfree.text.TextUtilities;
import org.jfree.ui.GradientPaintTransformType;
import org.jfree.ui.StandardGradientPaintTransformer;
import org.jfree.ui.TextAnchor;
import org.jfree.util.PaintUtilities;
import org.pentaho.chart.ChartDocumentContext;
import org.pentaho.chart.core.ChartDocument;
import org.pentaho.chart.core.ChartElement;
import org.pentaho.chart.css.keys.ChartStyleKeys;
import org.pentaho.chart.data.ChartTableModel;
import org.pentaho.chart.plugin.jfreechart.chart.JFreeChartGenerator;
import org.pentaho.chart.plugin.jfreechart.utils.ColorFactory;
import org.pentaho.chart.plugin.jfreechart.utils.JFreeChartUtils;
import org.pentaho.chart.plugin.jfreechart.utils.StrokeFactory;
import org.pentaho.reporting.libraries.css.keys.border.BorderStyleKeys;
import org.pentaho.reporting.libraries.css.keys.box.BoxStyleKeys;
import org.pentaho.reporting.libraries.css.values.CSSValue;
import org.pentaho.reporting.libraries.css.values.CSSValuePair;

public class JFreeDialChartGenerator extends JFreeChartGenerator {

    private static final String DIALVALUEINDICATOR = "dialvalueindicator"; //$NON-NLS-1$

    private static final String DIALRANGES = "dialranges"; //$NON-NLS-1$

    private static final String COUNT = "count"; //$NON-NLS-1$

    private static final String MINORTICK = "minortick"; //$NON-NLS-1$

    private static final String INCREMENT = "increment"; //$NON-NLS-1$

    private static final String MAJORTICK = "majortick"; //$NON-NLS-1$

    private static final String TICKLABEL = "ticklabel"; //$NON-NLS-1$

    private static final String EXTENT = "extent"; //$NON-NLS-1$

    private static final String STARTANGLE = "startangle"; //$NON-NLS-1$

    private static final String SCALE = "scale"; //$NON-NLS-1$

    private static final String DIALCAP = "dialcap"; //$NON-NLS-1$

    private static final String ANNOTATION = "annotation"; //$NON-NLS-1$

    private static final String DIALPOINTER = "dialpointer"; //$NON-NLS-1$

    private static final String UPPERBOUND = "upperbound"; //$NON-NLS-1$

    private static final String LOWERBOUND = "lowerbound"; //$NON-NLS-1$

    private static final String DIALRANGE = "dialrange"; //$NON-NLS-1$

    protected JFreeChart doCreateChart(final ChartDocumentContext chartDocContext, final ChartTableModel data) {
        final ChartDocument chartDocument = chartDocContext.getChartDocument();

        // ~ plot ======================================================================================================== 

        DialPlot dialPlot = new SquareDialPlot();

        // ~ data ========================================================================================================

        // ~ params begin
        final ValueDataset dataset = (ValueDataset) datasetGeneratorFactory.createDataset(chartDocContext, data);
        // ~ params end

        dialPlot.setDataset(dataset);

        // ~ frame type: either circular or arc ==========================================================================

        setDialFrame(chartDocument, dialPlot);

        // ~ annotation ==================================================================================================

        setDialTextAnnotation(chartDocument, dialPlot);

        // ~ value indicator: prints value of dial as text ===============================================================

        setDialValueIndicator(chartDocument, dialPlot);

        // ~ ticks =======================================================================================================

        setDialScale(chartDocument, dialPlot);

        // ~ cap over pointer ============================================================================================

        setDialCap(chartDocument, dialPlot);

        // ~ ranges ======================================================================================================

        setDialRange(chartDocument, dialPlot);

        // ~ background ==================================================================================================

        setDialBackground(chartDocument, dialPlot);

        // ~ pointer: either pin or pointer ==============================================================================

        setDialPointer(chartDocument, dialPlot);

        JFreeChart chart = new JFreeChart(getTitle(chartDocument), dialPlot);
        return chart;

    }

    protected void setDialFrame(ChartDocument chartDocument, DialPlot dialPlot) {
        // ~ params begin
        Color frameForegroundPaint = Color.black;
        Color frameInnerForegroundPaint = Color.black;
        Color frameBackgroundPaint = Color.white;
        Stroke frameStroke = new BasicStroke(2.0f);
        // ~ params end

        final StrokeFactory strokeFacObj = StrokeFactory.getInstance();

        ChartElement plotElement = getUniqueElement(chartDocument, ChartElement.TAG_NAME_PLOT);

        final BasicStroke borderStyleStroke = strokeFacObj.getBorderStroke(plotElement);
        if (borderStyleStroke != null) {
            frameStroke = borderStyleStroke;
        }

        final Color outerBorderColor = ColorFactory.getInstance().getColor(plotElement,
                BorderStyleKeys.BORDER_TOP_COLOR);
        if (outerBorderColor != null) {
            frameForegroundPaint = outerBorderColor;
        }

        final Color innerBorderColorTmp = ColorFactory.getInstance().getColor(plotElement,
                BorderStyleKeys.BORDER_BOTTOM_COLOR);
        if (innerBorderColorTmp != null) {
            frameInnerForegroundPaint = innerBorderColorTmp;
        }

        final Color borderBackgroundColorTmp = ColorFactory.getInstance().getColor(plotElement);
        if (borderBackgroundColorTmp != null) {
            frameBackgroundPaint = borderBackgroundColorTmp;
        }

        final DoubleLineDialFrame dialFrame = new DoubleLineDialFrame();
        dialFrame.setForegroundPaint(frameForegroundPaint);
        dialFrame.setInnerForegroundPaint(frameInnerForegroundPaint);
        dialFrame.setStroke(frameStroke);
        dialFrame.setBackgroundPaint(frameBackgroundPaint);

        dialPlot.setDialFrame(dialFrame);
    }

    protected void setDialBackground(ChartDocument chartDocument, DialPlot dialPlot) {
        ChartElement plotElement = getUniqueElement(chartDocument, ChartElement.TAG_NAME_PLOT);

        CSSValuePair cssValue = (CSSValuePair) plotElement.getLayoutStyle().getValue(ChartStyleKeys.GRADIENT_COLOR);
        Color beginColor = JFreeChartUtils.getColorFromCSSValue(cssValue.getFirstValue());
        Color endColor = JFreeChartUtils.getColorFromCSSValue(cssValue.getSecondValue());

        GradientPaint gradientpaint = new GradientPaint(new Point(), beginColor, new Point(), endColor);
        DialBackground dialbackground = new DialBackground(gradientpaint); // specify Color here for no gradient
        dialbackground.setGradientPaintTransformer(
                new StandardGradientPaintTransformer(GradientPaintTransformType.VERTICAL));
        dialPlot.setBackground(dialbackground);
    }

    protected void setDialRange(ChartDocument chartDocument, DialPlot dialPlot) {

        final double rangeInnerRadius = 0.4D;
        ChartElement[] rangeElements = getElements(chartDocument, DIALRANGE);

        for (int i = 0; i < rangeElements.length; i++) {

            double lowerBound = Double.parseDouble(rangeElements[i].getAttribute(LOWERBOUND).toString());
            double upperBound = Double.parseDouble(rangeElements[i].getAttribute(UPPERBOUND).toString());

            final Color rangeColorTmp = ColorFactory.getInstance().getColor(rangeElements[i]);
            Color rangeColor = Color.BLACK;
            if (rangeColorTmp != null) {
                rangeColor = rangeColorTmp;
            }

            SingleLineDialRange standarddialrange = new SingleLineDialRange(lowerBound, upperBound, rangeColor);
            standarddialrange.setInnerRadius(rangeInnerRadius);
            dialPlot.addLayer(standarddialrange);
        }
    }

    protected void setDialPointer(ChartDocument chartDocument, DialPlot dialPlot) {
        // ~ params begin
        double pointerRadius = 0.9; // length of pointer
        double pointerWidthRadius = 0.05; // width of base of pointer
        Color pointerFillPaint = Color.gray;
        Color pointerOutlinePaint = Color.black;
        Stroke pointerOutlineStroke = new BasicStroke(2.0f);
        // ~ params end

        VariableStrokePointer pointer = new VariableStrokePointer();

        ChartElement pointerElement = getUniqueElement(chartDocument, DIALPOINTER);

        final Color pointerColorTmp = ColorFactory.getInstance().getColor(pointerElement);
        if (pointerColorTmp != null) {
            pointerFillPaint = pointerColorTmp;
        }

        final Color pointerBorderColorTmp = ColorFactory.getInstance().getColor(pointerElement,
                BorderStyleKeys.BORDER_TOP_COLOR);
        if (pointerBorderColorTmp != null) {
            pointerOutlinePaint = pointerBorderColorTmp;
        }

        double pointerWidthRadiusTmp = parseDouble(pointerElement.getLayoutStyle().getValue(BoxStyleKeys.WIDTH))
                / 100;
        if (pointerWidthRadiusTmp != 0) {
            pointerWidthRadius = pointerWidthRadiusTmp;
        }

        double pointerRadiusTmp = parseDouble(pointerElement.getLayoutStyle().getValue(BoxStyleKeys.HEIGHT)) / 100;
        if (pointerRadiusTmp != 0) {
            pointerRadius = pointerRadiusTmp;
        }

        final BasicStroke borderStyleStroke = StrokeFactory.getInstance().getBorderStroke(pointerElement);
        if (borderStyleStroke != null) {
            pointerOutlineStroke = borderStyleStroke;
        }

        pointer.setRadius(pointerRadius);
        pointer.setOutlineStroke(pointerOutlineStroke);
        pointer.setWidthRadius(pointerWidthRadius);
        pointer.setFillPaint(pointerFillPaint);
        pointer.setOutlinePaint(pointerOutlinePaint);
        // DialPointer pointer = new DialPointer.Pin();
        dialPlot.addPointer(pointer);
    }

    protected void setDialTextAnnotation(ChartDocument chartDocument, DialPlot dialPlot) {
        // ~ params begin
        Font textAnnotationFont = new Font("Dialog", Font.BOLD, 14); //$NON-NLS-1$
        Color textAnnotationPaint = Color.black;
        final double textAnnotationRadius = 0.69999999999999996D; // hard-coded for now
        // ~ params end

        ChartElement annotationElement = getUniqueElement(chartDocument, ANNOTATION);
        if (annotationElement != null && annotationElement.getText() != null) {
            String annotation = annotationElement.getText();

            Color annotationColorTmp = ColorFactory.getInstance().getColor(annotationElement);
            if (annotationColorTmp != null) {
                textAnnotationPaint = annotationColorTmp;
            }

            Font annotationFontTmp = JFreeChartUtils.getFont(annotationElement);
            if (annotationFontTmp != null) {
                textAnnotationFont = annotationFontTmp;
            }

            DialTextAnnotation dialtextannotation = new DialTextAnnotation(annotation);
            dialtextannotation.setFont(textAnnotationFont);
            dialtextannotation.setPaint(textAnnotationPaint);
            dialtextannotation.setRadius(textAnnotationRadius);
            dialPlot.addLayer(dialtextannotation);
        }

    }

    protected void setDialCap(ChartDocument chartDocument, DialPlot dialPlot) {
        // ~ params begin
        double capRadius = 0.05; // expressed as percentage of dial's framing rectangle 
        Color capFillPaint = Color.white;
        Color capOutlinePaint = Color.black;
        Stroke capOutlineStroke = new BasicStroke(2.0f);
        // ~ params end

        final StrokeFactory strokeFacObj = StrokeFactory.getInstance();

        ChartElement dialCapElement = getUniqueElement(chartDocument, DIALCAP);

        final BasicStroke borderStyleStroke = strokeFacObj.getBorderStroke(dialCapElement);
        if (borderStyleStroke != null) {
            capOutlineStroke = borderStyleStroke;
        }

        final Color borderColor = ColorFactory.getInstance().getColor(dialCapElement,
                BorderStyleKeys.BORDER_TOP_COLOR);
        if (borderColor != null) {
            capOutlinePaint = borderColor;
        }

        final Color capColor = ColorFactory.getInstance().getColor(dialCapElement);
        if (capColor != null) {
            capFillPaint = capColor;
        }

        capRadius = parseDouble(dialCapElement.getLayoutStyle().getValue(BoxStyleKeys.WIDTH)) / 100;

        DialCap dialCap = new DialCap();
        dialCap.setRadius(capRadius);
        dialCap.setFillPaint(capFillPaint);
        dialCap.setOutlinePaint(capOutlinePaint);
        dialCap.setOutlineStroke(capOutlineStroke);
        dialPlot.setCap(dialCap);
    }

    /**
     * Gets the numeric value inside the CSS value; ignores units.
     */
    protected double parseDouble(final CSSValue value) {
        String trimmedString = value.getCSSText().trim();
        for (int i = 0; i < trimmedString.length(); i++) {
            char c = trimmedString.charAt(i);
            if (!Character.isDigit(c)) {
                try {
                    double d = Double.parseDouble(trimmedString.substring(0, i));
                    return d;
                } catch (NumberFormatException e) {
                    return 0;
                }
            }
        }
        return 0;
    }

    protected void setDialScale(ChartDocument chartDocument, DialPlot dialPlot) {
        // ~ params begin
        double scaleLowerBound = -40D;
        double scaleUpperBound = 60D;
        double scaleStartAngle = -120D;
        double scaleExtent = -300D;
        double scaleMajorTickIncrement = 10D;
        int scaleMinorTickCount = 4;
        final double scaleTickRadius = 0.88D;
        final double scaleTickLabelOffset = 0.14999999999999999D;
        double scaleMajorTickLength = 0.04;
        Color scaleMajorTickPaint = Color.black;
        Stroke scaleMajorTickStroke = new BasicStroke(3.0f);
        double scaleMinorTickLength = 0.02;
        Color scaleMinorTickPaint = Color.black;
        Stroke scaleMinorTickStroke = new BasicStroke(1.0f);
        Font scaleTickLabelFont = new Font("Dialog", 0, 14); //$NON-NLS-1$
        Color scaleTickLabelPaint = Color.blue;
        // ~ params end

        ChartElement scaleElement = getUniqueElement(chartDocument, SCALE);

        scaleUpperBound = Double.parseDouble(scaleElement.getAttribute(UPPERBOUND).toString());
        scaleLowerBound = Double.parseDouble(scaleElement.getAttribute(LOWERBOUND).toString());
        scaleStartAngle = Double.parseDouble(scaleElement.getAttribute(STARTANGLE).toString());
        scaleExtent = Double.parseDouble(scaleElement.getAttribute(EXTENT).toString());

        ChartElement tickLabelElement = getUniqueElement(chartDocument, TICKLABEL);
        Color tickLabelColorTmp = ColorFactory.getInstance().getColor(tickLabelElement);
        if (tickLabelColorTmp != null) {
            scaleTickLabelPaint = tickLabelColorTmp;
        }

        Font tickLabelFontTmp = JFreeChartUtils.getFont(tickLabelElement);
        if (tickLabelFontTmp != null) {
            scaleTickLabelFont = tickLabelFontTmp;
        }

        ChartElement majorTickElement = getUniqueElement(chartDocument, MAJORTICK);
        scaleMajorTickIncrement = Double.parseDouble((String) majorTickElement.getAttribute(INCREMENT));

        float majorTickWidthTmp = (float) parseDouble(
                majorTickElement.getLayoutStyle().getValue(BoxStyleKeys.WIDTH));
        if (majorTickWidthTmp != 0) {
            scaleMajorTickStroke = new BasicStroke(majorTickWidthTmp);
        }

        double majorTickLengthTmp = parseDouble(majorTickElement.getLayoutStyle().getValue(BoxStyleKeys.HEIGHT))
                / 100;
        if (majorTickLengthTmp != 0) {
            scaleMajorTickLength = majorTickLengthTmp;
        }

        ChartElement minorTickElement = getUniqueElement(chartDocument, MINORTICK);
        scaleMinorTickCount = Integer.parseInt((String) minorTickElement.getAttribute(COUNT));

        float minorTickWidthTmp = (float) parseDouble(
                minorTickElement.getLayoutStyle().getValue(BoxStyleKeys.WIDTH));
        if (minorTickWidthTmp != 0) {
            scaleMinorTickStroke = new BasicStroke(minorTickWidthTmp);
        }

        double minorTickLengthTmp = parseDouble(minorTickElement.getLayoutStyle().getValue(BoxStyleKeys.HEIGHT))
                / 100;
        if (minorTickLengthTmp != 0) {
            scaleMinorTickLength = minorTickLengthTmp;
        }

        Color majorTickColorTmp = ColorFactory.getInstance().getColor(majorTickElement);
        if (majorTickColorTmp != null) {
            scaleMajorTickPaint = majorTickColorTmp;
        }

        Color minorTickColorTmp = ColorFactory.getInstance().getColor(minorTickElement);
        if (minorTickColorTmp != null) {
            scaleMinorTickPaint = minorTickColorTmp;
        }

        FixedStandardDialScale standardDialScale = new FixedStandardDialScale(scaleLowerBound, scaleUpperBound,
                scaleStartAngle, scaleExtent, scaleMajorTickIncrement, scaleMinorTickCount);
        standardDialScale.setTickRadius(scaleTickRadius);
        standardDialScale.setTickLabelOffset(scaleTickLabelOffset);
        standardDialScale.setTickLabelFont(scaleTickLabelFont);
        standardDialScale.setTickLabelPaint(scaleTickLabelPaint);
        standardDialScale.setMajorTickLength(scaleMajorTickLength);
        standardDialScale.setMajorTickPaint(scaleMajorTickPaint);
        standardDialScale.setMajorTickStroke(scaleMajorTickStroke);
        standardDialScale.setMinorTickLength(scaleMinorTickLength);
        standardDialScale.setMinorTickPaint(scaleMinorTickPaint);
        standardDialScale.setMinorTickStroke(scaleMinorTickStroke);
        dialPlot.addScale(0, standardDialScale);
    }

    /**
     * Very ugly but there is no stinking XPath support.
     */
    protected ChartElement[] getElements(ChartDocument doc, String name) {
        if (ChartElement.TAG_NAME_PLOT.equals(name)) {
            return new ChartElement[] { doc.getPlotElement() };
        } else if (DIALRANGE.equals(name)) {
            ChartElement plotElem = doc.getPlotElement();
            if (plotElem != null) {
                ChartElement[] dialRanges = plotElem.findChildrenByName(DIALRANGES);
                if (dialRanges.length > 0) {
                    return dialRanges[0].findChildrenByName(name);
                } else {
                    return null;
                }
            } else {
                return null;
            }
        } else if (TICKLABEL.equals(name) || MAJORTICK.equals(name) || MINORTICK.equals(name)) {
            return doc.getPlotElement().findChildrenByName(SCALE)[0].findChildrenByName(name);
        } else {
            ChartElement[] elems = doc.getPlotElement().findChildrenByName(name);
            return elems;
        }
    }

    public void setDialValueIndicator(ChartDocument chartDocument, DialPlot dialPlot) {

        // ~ params begin
        Font valueIndicatorFont = new Font("Dialog", Font.BOLD, 14); //$NON-NLS-1$
        Color valueIndicatorPaint = Color.black;
        Color valueIndicatorBackgroundPaint = Color.white;
        Stroke valueIndicatorOutlineStroke = new BasicStroke(1.0f);
        Color valueIndicatorOutlinePaint = Color.blue;
        // ~ params end

        ChartElement valIndicatorElement = getUniqueElement(chartDocument, DIALVALUEINDICATOR);
        Color valIndicatorColorTmp = ColorFactory.getInstance().getColor(valIndicatorElement);
        if (valIndicatorColorTmp != null) {
            valueIndicatorPaint = valIndicatorColorTmp;
        }

        Color valIndicatorBgColorTmp = ColorFactory.getInstance().getColor(valIndicatorElement,
                BorderStyleKeys.BACKGROUND_COLOR);
        if (valIndicatorBgColorTmp != null) {
            valueIndicatorBackgroundPaint = valIndicatorBgColorTmp;
        }

        final BasicStroke borderStyleStroke = StrokeFactory.getInstance().getBorderStroke(valIndicatorElement);
        if (borderStyleStroke != null) {
            valueIndicatorOutlineStroke = borderStyleStroke;
        }

        Color valIndicatorBorderColorTmp = ColorFactory.getInstance().getColor(valIndicatorElement,
                BorderStyleKeys.BORDER_TOP_COLOR);
        if (valIndicatorBorderColorTmp != null) {
            valueIndicatorOutlinePaint = valIndicatorBorderColorTmp;
        }

        Font valIndicatorFontTmp = JFreeChartUtils.getFont(valIndicatorElement);
        if (valIndicatorFontTmp != null) {
            valueIndicatorFont = valIndicatorFontTmp;
        }

        DialValueIndicator dialValueIndicator = new DialValueIndicator(0);

        // begin code to determine the size of the value indicator box 
        ChartElement scaleElement = getUniqueElement(chartDocument, SCALE);
        if (scaleElement != null) {

            double scaleUpperBound = Double.parseDouble(scaleElement.getAttribute(UPPERBOUND).toString());
            double scaleLowerBound = Double.parseDouble(scaleElement.getAttribute(LOWERBOUND).toString());

            if (Math.abs(scaleUpperBound) > Math.abs(scaleLowerBound)) {
                dialValueIndicator.setTemplateValue(scaleUpperBound);
            } else {
                dialValueIndicator.setTemplateValue(scaleLowerBound);
            }
        }
        // end code to determine the size of the value indicator box

        dialValueIndicator.setFont(valueIndicatorFont);
        dialValueIndicator.setPaint(valueIndicatorPaint);
        dialValueIndicator.setBackgroundPaint(valueIndicatorBackgroundPaint);
        dialValueIndicator.setOutlineStroke(valueIndicatorOutlineStroke);
        dialValueIndicator.setOutlinePaint(valueIndicatorOutlinePaint);
        dialPlot.addLayer(dialValueIndicator);
    }

    /**
     * Very ugly but there is no stinking XPath support.
     */
    protected ChartElement getUniqueElement(ChartDocument doc, String name) {
        ChartElement[] chartElements = getElements(doc, name);
        return chartElements.length > 0 ? chartElements[0] : null;
    }

    /**
     * Resizes the dial plot so that the smallest side of the plot is the length of all sides--a square plot.
     */
    public static class SquareDialPlot extends DialPlot {

        @Override
        public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor, PlotState parentState,
                PlotRenderingInfo info) {
            Rectangle2D squareArea = new Rectangle2D.Double();
            double sideLength = Math.min(area.getWidth(), area.getHeight());

            double distToShiftToCenter = (area.getWidth() - sideLength) / 2;

            squareArea.setRect(area.getX() + distToShiftToCenter, area.getY(), sideLength, sideLength);
            super.draw(g2, squareArea, anchor, parentState, info);
        }

    }

    /**
     * Instead of the double line drawn by <code>StandardDialFrame</code>, this class draws a single line.
     */
    public static class SingleLineDialFrame extends StandardDialFrame {

        private static final long serialVersionUID = 1L;

        @Override
        public void draw(Graphics2D g2, DialPlot plot, Rectangle2D frame, Rectangle2D view) {

            Shape window = getWindow(frame);

            g2.setPaint(getBackgroundPaint());

            g2.setStroke(getStroke());
            g2.setPaint(getForegroundPaint());
            g2.draw(window);
        }

    }

    /**
     * Instead of the double line drawn by <code>StandardDialRange</code>, this class draws a single line.
     */
    public static class SingleLineDialRange extends StandardDialRange {

        private static final long serialVersionUID = 1L;

        public SingleLineDialRange(double lower, double upper, Paint paint) {
            super(lower, upper, paint);
        }

        @Override
        public void draw(Graphics2D g2, DialPlot plot, Rectangle2D frame, Rectangle2D view) {

            Rectangle2D arcRectInner = DialPlot.rectangleByRadius(frame, getInnerRadius(), getInnerRadius());

            DialScale scale = plot.getScale(getScaleIndex());
            if (scale == null) {
                throw new RuntimeException("No scale for scaleIndex = " + getScaleIndex()); //$NON-NLS-1$
            }
            double angleMin = scale.valueToAngle(getLowerBound());
            double angleMax = scale.valueToAngle(getUpperBound());

            Arc2D arcInner = new Arc2D.Double(arcRectInner, angleMin, angleMax - angleMin, Arc2D.OPEN);

            // make the stroke a percentage of the width of the frame
            double frameWidth = frame.getWidth();
            float strokeWidth = (float) frameWidth * 0.125f;

            g2.setPaint(getPaint());
            g2.setStroke(new BasicStroke(strokeWidth, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER));
            g2.draw(arcInner);
        }

    }

    /**
     * Same as <code>StandardDialFrame</code>, but allows the line colors to vary between the two lines.
     */
    public static class DoubleLineDialFrame extends StandardDialFrame {

        private static final long serialVersionUID = 1L;

        /**
         * The color used for the inner border around the window. This field is transient
         * because it requires special handling for serialization.
         */
        private transient Paint innerForegroundPaint;

        public DoubleLineDialFrame() {
            super();
            this.innerForegroundPaint = Color.black;
        }

        public Paint getInnerForegroundPaint() {
            return this.innerForegroundPaint;
        }

        public void setInnerForegroundPaint(Paint paint) {
            if (paint == null) {
                throw new IllegalArgumentException("Null 'paint' argument."); //$NON-NLS-1$
            }
            this.innerForegroundPaint = paint;
            notifyListeners(new DialLayerChangeEvent(this));
        }

        @Override
        public void draw(Graphics2D g2, DialPlot plot, Rectangle2D frame, Rectangle2D view) {
            Shape window = getWindow(frame);

            Rectangle2D f = DialPlot.rectangleByRadius(frame, getRadius() + 0.02, getRadius() + 0.02);
            Ellipse2D e = new Ellipse2D.Double(f.getX(), f.getY(), f.getWidth(), f.getHeight());

            Area area = new Area(e);
            Area area2 = new Area(window);
            area.subtract(area2);
            g2.setPaint(getBackgroundPaint());
            g2.fill(area);

            g2.setStroke(getStroke());
            g2.setPaint(getInnerForegroundPaint());
            g2.draw(window);
            g2.setPaint(getForegroundPaint());
            g2.draw(e);
        }

        @Override
        public boolean equals(Object obj) {
            if (obj == this) {
                return true;
            }
            if (!(obj instanceof DoubleLineDialFrame)) {
                return false;
            }
            DoubleLineDialFrame that = (DoubleLineDialFrame) obj;
            if (!PaintUtilities.equal(getBackgroundPaint(), that.getBackgroundPaint())) {
                return false;
            }
            if (!PaintUtilities.equal(getForegroundPaint(), that.getForegroundPaint())) {
                return false;
            }
            if (!PaintUtilities.equal(getInnerForegroundPaint(), that.getInnerForegroundPaint())) {
                return false;
            }
            if (getRadius() != that.getRadius()) {
                return false;
            }
            if (!this.getStroke().equals(that.getStroke())) {
                return false;
            }
            return super.equals(obj);
        }

    }

    public static class ImageDialBackground extends DialBackground {

        private static final long serialVersionUID = 1L;

        private Image image;

        public ImageDialBackground(Image image) {
            super();
            this.image = image;
        }

        @Override
        public void draw(Graphics2D g2, DialPlot plot, Rectangle2D frame, Rectangle2D view) {
            g2.drawImage(image, 20, 20, new ImageObserver() {

                public boolean imageUpdate(Image arg0, int arg1, int arg2, int arg3, int arg4, int arg5) {

                    return false;

                }

            });
        }

    }

    /**
     * Fixes a bug in JFreeChart where color for tick label is ignored. Bug ID: 2617557
     */
    public static class FixedStandardDialScale extends StandardDialScale {

        private static final long serialVersionUID = 1L;

        public FixedStandardDialScale(double lowerBound, double upperBound, double startAngle, double extent,
                double majorTickIncrement, int minorTickCount) {

            super(lowerBound, upperBound, startAngle, extent, majorTickIncrement, minorTickCount); // TODO Auto-generated constructor stub

        }

        @Override
        public void draw(Graphics2D g2, DialPlot plot, Rectangle2D frame, Rectangle2D view) {

            Rectangle2D arcRect = DialPlot.rectangleByRadius(frame, getTickRadius(), getTickRadius());
            Rectangle2D arcRectMajor = DialPlot.rectangleByRadius(frame, getTickRadius() - getMajorTickLength(),
                    getTickRadius() - getMajorTickLength());
            Rectangle2D arcRectMinor = arcRect;
            if (getMinorTickCount() > 0 && getMinorTickLength() > 0.0) {
                arcRectMinor = DialPlot.rectangleByRadius(frame, getTickRadius() - getMinorTickLength(),
                        getTickRadius() - getMinorTickLength());
            }
            Rectangle2D arcRectForLabels = DialPlot.rectangleByRadius(frame, getTickRadius() - getTickLabelOffset(),
                    getTickRadius() - getTickLabelOffset());

            boolean firstLabel = true;

            Arc2D arc = new Arc2D.Double();
            Line2D workingLine = new Line2D.Double();
            for (double v = getLowerBound(); v <= getUpperBound(); v += getMajorTickIncrement()) {
                arc.setArc(arcRect, getStartAngle(), valueToAngle(v) - getStartAngle(), Arc2D.OPEN);
                Point2D pt0 = arc.getEndPoint();
                arc.setArc(arcRectMajor, getStartAngle(), valueToAngle(v) - getStartAngle(), Arc2D.OPEN);
                Point2D pt1 = arc.getEndPoint();
                g2.setPaint(getMajorTickPaint());
                g2.setStroke(getMajorTickStroke());
                workingLine.setLine(pt0, pt1);
                g2.draw(workingLine);
                arc.setArc(arcRectForLabels, getStartAngle(), valueToAngle(v) - getStartAngle(), Arc2D.OPEN);
                Point2D pt2 = arc.getEndPoint();

                if (getTickLabelsVisible()) {
                    if (!firstLabel || getFirstTickLabelVisible()) {
                        g2.setFont(getTickLabelFont());
                        g2.setPaint(getTickLabelPaint());
                        TextUtilities.drawAlignedString(getTickLabelFormatter().format(v), g2, (float) pt2.getX(),
                                (float) pt2.getY(), TextAnchor.CENTER);
                    }
                }
                firstLabel = false;

                // now do the minor tick marks
                if (getMinorTickCount() > 0 && getMinorTickLength() > 0.0) {
                    double minorTickIncrement = getMajorTickIncrement() / (getMinorTickCount() + 1);
                    for (int i = 0; i < getMinorTickCount(); i++) {
                        double vv = v + ((i + 1) * minorTickIncrement);
                        if (vv >= getUpperBound()) {
                            break;
                        }
                        double angle = valueToAngle(vv);

                        arc.setArc(arcRect, getStartAngle(), angle - getStartAngle(), Arc2D.OPEN);
                        pt0 = arc.getEndPoint();
                        arc.setArc(arcRectMinor, getStartAngle(), angle - getStartAngle(), Arc2D.OPEN);
                        Point2D pt3 = arc.getEndPoint();
                        g2.setStroke(getMinorTickStroke());
                        g2.setPaint(getMinorTickPaint());
                        workingLine.setLine(pt0, pt3);
                        g2.draw(workingLine);
                    }
                }

            }
        }
    }

    /**
     * Enhances <code>DialPointer.Pointer</code> to allow any size stroke for the outline.
     */
    public static class VariableStrokePointer extends DialPointer.Pointer {

        private static final long serialVersionUID = 1L;

        private transient Stroke outlineStroke;

        public Stroke getOutlineStroke() {
            return this.outlineStroke;
        }

        public void setOutlineStroke(Stroke outlineStroke) {
            if (outlineStroke == null) {
                throw new IllegalArgumentException("Null 'stroke' argument."); //$NON-NLS-1$
            }
            this.outlineStroke = outlineStroke;
            notifyListeners(new DialLayerChangeEvent(this));
        }

        @Override
        public void draw(Graphics2D g2, DialPlot plot, Rectangle2D frame, Rectangle2D view) {
            g2.setPaint(Color.blue);
            g2.setStroke(this.outlineStroke);
            Rectangle2D lengthRect = DialPlot.rectangleByRadius(frame, getRadius(), getRadius());
            Rectangle2D widthRect = DialPlot.rectangleByRadius(frame, getWidthRadius(), getWidthRadius());
            double value = plot.getValue(getDatasetIndex());
            DialScale scale = plot.getScaleForDataset(getDatasetIndex());
            double angle = scale.valueToAngle(value);

            Arc2D arc1 = new Arc2D.Double(lengthRect, angle, 0, Arc2D.OPEN);
            Point2D pt1 = arc1.getEndPoint();
            Arc2D arc2 = new Arc2D.Double(widthRect, angle - 90.0, 180.0, Arc2D.OPEN);
            Point2D pt2 = arc2.getStartPoint();
            Point2D pt3 = arc2.getEndPoint();
            Arc2D arc3 = new Arc2D.Double(widthRect, angle - 180.0, 0.0, Arc2D.OPEN);
            Point2D pt4 = arc3.getStartPoint();

            GeneralPath gp = new GeneralPath();
            gp.moveTo((float) pt1.getX(), (float) pt1.getY());
            gp.lineTo((float) pt2.getX(), (float) pt2.getY());
            gp.lineTo((float) pt4.getX(), (float) pt4.getY());
            gp.lineTo((float) pt3.getX(), (float) pt3.getY());
            gp.closePath();
            g2.setPaint(getFillPaint());
            g2.fill(gp);

            g2.setPaint(getOutlinePaint());
            Line2D line = new Line2D.Double(frame.getCenterX(), frame.getCenterY(), pt1.getX(), pt1.getY());
            g2.draw(line);

            line.setLine(pt2, pt3);
            g2.draw(line);

            line.setLine(pt3, pt1);
            g2.draw(line);

            line.setLine(pt2, pt1);
            g2.draw(line);

            line.setLine(pt2, pt4);
            g2.draw(line);

            line.setLine(pt3, pt4);
            g2.draw(line);
        }

    }

}