Package org.jfree.chart.plot

Source Code of org.jfree.chart.plot.RadarPlot

// Process Dashboard - Data Automation Tool for high-maturity processes
// Copyright (C) 2003 Software Process Dashboard Initiative
//
// 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 2
// of the License, or (at your option) any later version.
//
// 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.
//
// The author(s) may be contacted at:
// OO-ALC/TISHD
// Attn: PSP Dashboard Group
// 6137 Wardleigh Road
// Hill AFB, UT 84056-5843
//
// E-Mail POC:  processdash-devel@lists.sourceforge.net


/*
* Radar chart plotter, designed for drawing quality profiles
*/

package org.jfree.chart.plot;


import java.awt.Graphics2D;
import java.awt.Font;
import java.awt.Paint;
import java.awt.Color;
import java.awt.Stroke;
import java.awt.BasicStroke;
import java.awt.Insets;
import java.awt.Image;
import java.awt.Shape;
import java.awt.Composite;
import java.awt.AlphaComposite;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.Arc2D;
import java.awt.geom.Line2D;
import java.awt.geom.GeneralPath;
import java.awt.font.FontRenderContext;
import java.awt.font.LineMetrics;
import java.text.DecimalFormat;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.Iterator;

import org.jfree.chart.plot.PlotRenderingInfo;
import org.jfree.chart.event.PlotChangeEvent;
import org.jfree.chart.plot.PlotState;
import org.jfree.data.PieDataset;

/**
* A plot that displays data in the form of a radar chart, using data
* from any class that implements the CategoryDataSource interface.
* <P>
* Notes:
* (1) negative values in the dataset are ignored;
* (2) vertical axis and horizontal axis are set to null;
* (3) there are utility methods for creating a CategoryDataSource from a
* CategoryDataset;
* @see Plot
* @see CategoryDataSource */
public class RadarPlot extends Plot {

    /** The default interior gap percent (currently 20%). */
    public static final double DEFAULT_INTERIOR_GAP = 0.20;

    /** The maximum interior gap (currently 40%). */
    public static final double MAX_INTERIOR_GAP = 0.40;

    /** The default radius percent (currently 100%). */
    public static final double DEFAULT_RADIUS = 1.00;

    /** The maximum radius (currently 100%). */
    public static final double MAX_RADIUS = 1.00;

    /** The default axis label font. */
    public static final Font DEFAULT_AXIS_LABEL_FONT =
        new Font("SansSerif", Font.PLAIN, 10);

    /** The default axis label paint. */
    public static final Paint DEFAULT_AXIS_LABEL_PAINT = Color.black;

    /** The default axis label gap (currently 10%). */
    public static final double DEFAULT_AXIS_LABEL_GAP = 0.10;

    /** The maximum interior gap (currently 30%). */
    public static final double MAX_AXIS_LABEL_GAP = 0.30;

    /** The default stroke for the series line */
    public static final Stroke DEFAULT_LINE_STROKE = new BasicStroke(3.0f);

    /** A magic color object used to designate adaptive coloring, based
     *  on the computed quality index */
    public static final Paint ADAPTIVE_COLORING = new Color(0);

    /** The dataset for the radar chart. */
    private PieDataset dataset;

    /** The amount of space left around the outside of the radar
        chart, expressed as a percentage. */
    protected double interiorGap;

//    /** Flag determining whether to draw an ellipse or a perfect circle. */
//    protected boolean circular;

    /** The radius as a percentage of the available drawing area. */
    protected double radius;

    /** The font used to display the axis labels. */
    protected Font axisLabelFont;

    /** The color used to draw the axis labels. */
    protected Paint axisLabelPaint;

    /** The gap between the labels and the radar axes, as a
        percentage of the radius. */
    protected double axisLabelGap;

    /** Whether or not axis labels should be drawn */
    protected boolean showAxisLabels;

    /** The color used to paint the axis lines (i.e. spokes) */
    protected Paint axisPaint;

    /** The stroke used to paint the axis lines (i.e. spokes) */
    protected Stroke axisStroke;

    /** The color used to paint the grid lines */
    protected Paint gridLinePaint;

    /** The stroke used to paint the grid lines */
    protected Stroke gridLineStroke;

    /** The color to use to draw the data polygon */
    protected Paint plotLinePaint;

    /** The stroke used to draw the data polygon */
    protected Stroke plotLineStroke;

    /**
     * Constructs a new radar chart, using default attributes as required.
     */
    public RadarPlot() {
        this(null);
    }

    public RadarPlot(PieDataset dataset) {
        super();
        this.dataset = dataset;
        initialise();
    }

    private void initialise() {
        this.interiorGap = DEFAULT_INTERIOR_GAP;
//        this.circular = true;
        this.radius = DEFAULT_RADIUS;
        this.showAxisLabels = true;
        this.axisLabelFont = DEFAULT_AXIS_LABEL_FONT;
        this.axisLabelPaint = DEFAULT_AXIS_LABEL_PAINT;
        this.axisLabelGap = DEFAULT_AXIS_LABEL_GAP;
//        this.itemLabelGenerator = null;
//        this.urlGenerator = null;

        this.plotLinePaint = ADAPTIVE_COLORING;
        this.axisPaint = Color.black;
        this.axisStroke = DEFAULT_OUTLINE_STROKE;
        this.gridLinePaint = Color.lightGray;
        this.gridLineStroke = DEFAULT_OUTLINE_STROKE;
        this.plotLineStroke = DEFAULT_LINE_STROKE;
        setForegroundAlpha(0.5f);
        setInsets(new Insets(0, 5, 5, 5));
    }

    /**
     * Returns the interior gap, measured as a percentage of the
     * available drawing space.
     *
     * @return The gap percentage.  */
    public double getInteriorGap() {
        return this.interiorGap;
    }

    /**
     * Sets the interior gap.
     *
     * @param percent The gap.
     */
    public void setInteriorGap(double percent) {

        // check arguments...
        if ((percent < 0.0) || (percent > MAX_INTERIOR_GAP)) {
            throw new IllegalArgumentException(
                "RadarPlot.setInteriorGap(double): percentage "+
                "outside valid range.");
        }

        // make the change...
        if (this.interiorGap != percent) {
            this.interiorGap = percent;
            notifyListeners(new PlotChangeEvent(this));
        }

    }

//    /**
//     * Returns a flag indicating whether the pie chart is circular, or
//     * stretched into an elliptical shape.
//     *
//     * @return a flag indicating whether the pie chart is circular.
//     */
//    public boolean isCircular() {
//        return circular;
//    }
//
//    /**
//     * A flag indicating whether the pie chart is circular, or stretched
//     * into an elliptical shape.
//     *
//     * @param flag  the new value.
//     */
//    public void setCircular(boolean flag) {
//
//        // no argument checking required...
//        // make the change...
//        if (circular != flag) {
//            circular = flag;
//            notifyListeners(new PlotChangeEvent(this));
//        }
//
//    }

    /**
     * Returns the radius (a percentage of the available space).
     *
     * @return The radius percentage.
     */
    public double getRadius() {
        return this.radius;
    }

    /**
     * Sets the radius.
     *
     * @param percent  the new value.
     */
    public void setRadius(double percent) {

        // check arguments...
        if ((percent <= 0.0) || (percent > MAX_RADIUS)) {
            throw new IllegalArgumentException(
                "RadarPlot.setRadius(double): percentage "+
                "outside valid range.");
        }

        // make the change (if necessary)...
        if (this.radius != percent) {
            this.radius = percent;
            notifyListeners(new PlotChangeEvent(this));
        }

    }

    /**
     * Returns the axis label font.
     * @return The axis label font.
     */
    public Font getAxisLabelFont() {
        return this.axisLabelFont;
    }

    /**
     * Sets the axis label font.
     * <P>
     * Notifies registered listeners that the plot has been changed.
     * @param font The new axis label font.
     */
    public void setAxisLabelFont(Font font) {

        // check arguments...
        if (font==null) {
            throw new IllegalArgumentException
                ("RadarPlot.setAxisLabelFont(...): "
                 +"null font not allowed.");
        }

        // make the change...
        if (!this.axisLabelFont.equals(font)) {
            this.axisLabelFont = font;
            notifyListeners(new PlotChangeEvent(this));
        }

    }

    /**
     * Returns the axis label paint.
     * @return The axis label paint.
     */
    public Paint getAxisLabelPaint() {
        return this.axisLabelPaint;
    }

    /**
     * Sets the axis label paint.
     * <P>
     * Notifies registered listeners that the plot has been changed.
     * @param paint The new axis label paint.
     */
    public void setAxisLabelPaint(Paint paint) {

        // check arguments...
        if (paint==null) {
            throw new IllegalArgumentException
                ("RadarPlot.setAxisLabelPaint(...): "
                 +"null paint not allowed.");
        }

        // make the change...
        if (!this.axisLabelPaint.equals(paint)) {
            this.axisLabelPaint = paint;
            notifyListeners(new PlotChangeEvent(this));
        }

    }

    /**
     * Returns the plot line paint.
     * @return The plot line paint.
     */
    public Paint getPlotLinePaint() {
        return this.plotLinePaint;
    }

    /**
     * Sets the plot line paint.
     * <P>
     * Notifies registered listeners that the plot has been changed.
     * @param paint The new plot line paint.
     */
    public void setPlotLinePaint(Paint paint) {

        // check arguments...
        if (paint==null) {
            throw new IllegalArgumentException
                ("RadarPlot.setPlotPaint(...): "
                 +"null paint not allowed.");
        }

        // make the change...
        if (!this.plotLinePaint.equals(paint)) {
            this.plotLinePaint = paint;
            notifyListeners(new PlotChangeEvent(this));
        }

    }

    /**
     * Returns the axis label gap, measures as a percentage of the radius.
     * @return The axis label gap, measures as a percentage of the radius.
     */
    public double getAxisLabelGap() {
        return this.axisLabelGap;
    }

    /**
     * Sets the axis label gap percent.
     */
    public void setAxisLabelGap(double percent) {

        // check arguments...
        if ((percent<0.0) || (percent>MAX_AXIS_LABEL_GAP)) {
            throw new IllegalArgumentException
                ("RadarPlot.setAxisLabelGap(double): "
                 +"percentage outside valid range.");
        }

        // make the change...
        if (this.axisLabelGap!=percent) {
            this.axisLabelGap = percent;
            notifyListeners(new PlotChangeEvent(this));
        }

    }

    /**
     * Returns the show axis labels flag.
     *
     * @return the show axis label flag.
     */
    public boolean getShowAxisLabels () {
        return (this.showAxisLabels);
    }

    /**
     * Sets the show axis labels flag.
     * <P>
     * Notifies registered listeners that the plot has been changed.
     *
     * @param flag  the new show axis labels flag.
     */
    public void setShowAxisLabels(boolean flag) {
        if (this.showAxisLabels != flag) {
            this.showAxisLabels = flag;
            notifyListeners(new PlotChangeEvent(this));
        }
    }

    /**
     * Returns the dataset for the plot, cast as a CategoryDataSource.
     * <P>
     * Provided for convenience.
     * @return The dataset for the plot, cast as a CategoryDataSource.
     */
    public PieDataset getPieDataset() {
        return dataset;
    }

    /**
     * Returns a collection of the section keys (or categories) in the dataset.
     *
     * @return the categories.
     */
    public Collection getKeys() {
        if (dataset != null)
            return Collections.unmodifiableCollection(dataset.getKeys());
        else
            return null;
    }

    /**
     * Draws the plot on a Java 2D graphics device (such as the screen
     * or a printer).
     * @param g2 The graphics device.
     * @param plotArea The area within which the plot should be drawn.
     */
    public void draw(Graphics2D g2, Rectangle2D plotArea, PlotState state,
                     PlotRenderingInfo info) {
        // adjust for insets...
        Insets insets = getInsets();
        if (insets!=null) {
            plotArea.setRect(plotArea.getX()+insets.left,
                             plotArea.getY()+insets.top,
                             plotArea.getWidth()-insets.left-insets.right,
                             plotArea.getHeight()-insets.top-insets.bottom);
        }

        if (info != null) {
            info.setPlotArea(plotArea);
            info.setDataArea(plotArea);
        }

        drawBackground(g2, plotArea);
        drawOutline(g2, plotArea);

        Shape savedClip = g2.getClip();
        g2.clip(plotArea);

        Composite originalComposite = g2.getComposite();
        g2.setComposite(AlphaComposite.getInstance
                        (AlphaComposite.SRC_OVER, getForegroundAlpha()));

        if (this.dataset != null) {
            drawRadar(g2, plotArea, info, 0, this.dataset);
        } else {
            drawNoDataMessage(g2, plotArea);
        }

        g2.clip(savedClip);
        g2.setComposite(originalComposite);

        drawOutline(g2, plotArea);

    }



    protected void drawRadar(Graphics2D g2, Rectangle2D plotArea,
                             PlotRenderingInfo info, int pieIndex,
                             PieDataset data) {

        // adjust the plot area by the interior spacing value
        double gapHorizontal = plotArea.getWidth() * this.interiorGap;
        double gapVertical = plotArea.getHeight() * this.interiorGap;
        double radarX = plotArea.getX() + gapHorizontal / 2;
        double radarY = plotArea.getY() + gapVertical / 2;
        double radarW = plotArea.getWidth() - gapHorizontal;
        double radarH = plotArea.getHeight() - gapVertical;

        // make the radar area a square if the radar chart is to be circular...
        // NOTE that non-circular radar charts are not currently supported.
        if (true) { //circular) {
            double min = Math.min(radarW, radarH) / 2;
            radarX = (radarX + radarX + radarW) / 2 - min;
            radarY = (radarY + radarY + radarH) / 2 - min;
            radarW = 2 * min;
            radarH = 2 * min;
        }

        double radius = radarW / 2;
        double centerX = radarX + radarW / 2;
        double centerY = radarY + radarH / 2;

        Rectangle2D radarArea = new Rectangle2D.Double
            (radarX, radarY, radarW, radarH);

        // plot the data (unless the dataset is null)...
        if ((data != null) && (data.getKeys().size() > 0)) {

            // get a list of categories...
            List keys = data.getKeys();
            int numAxes = keys.size();

            // draw each of the axes on the radar chart, and register
            // the shape of the radar line.

            double multiplier = 1.0;
            GeneralPath lineShape =
                new GeneralPath(GeneralPath.WIND_NON_ZERO, numAxes+1);
            GeneralPath gridShape =
                new GeneralPath(GeneralPath.WIND_NON_ZERO, numAxes+1);

            int axisNumber = -1;
            Iterator iterator = keys.iterator();
            while (iterator.hasNext()) {
                Comparable currentKey = (Comparable) iterator.next();
                axisNumber++;
                Number dataValue = data.getValue(currentKey);

                double value =
                    (dataValue != null ? dataValue.doubleValue() : 0);
                if (value > 1 || Double.isNaN(value) ||
                    Double.isInfinite(value)) value = 1.0;
                if (value < 0) value = 0.0;
                multiplier *= value;

                double angle = 2 * Math.PI * axisNumber / numAxes;
                double deltaX = Math.sin(angle) * radius;
                double deltaY = - Math.cos(angle) * radius;

                // draw the spoke
                g2.setPaint(axisPaint);
                g2.setStroke(axisStroke);
                Line2D line = new Line2D.Double
                    (centerX, centerY, centerX + deltaX, centerY + deltaY);
                g2.draw(line);

                // register the grid line and the shape line
                if (axisNumber == 0) {
                    gridShape.moveTo((float)deltaX, (float)deltaY);
                    lineShape.moveTo((float)(deltaX*value),
                                     (float)(deltaY*value));
                } else {
                    gridShape.lineTo((float)deltaX, (float)deltaY);
                    lineShape.lineTo((float)(deltaX*value),
                                     (float)(deltaY*value));
                }

                if (showAxisLabels) {
                    // draw the label
                    double labelX = centerX + deltaX*(1+axisLabelGap);
                    double labelY = centerY + deltaY*(1+axisLabelGap);
                    String label = currentKey.toString();
                    drawLabel(g2, radarArea, label, axisNumber, labelX,
                              labelY);
                }

            }
            gridShape.closePath();
            lineShape.closePath();

            // draw five gray concentric gridlines
            g2.translate(centerX, centerY);
            g2.setPaint(gridLinePaint);
            g2.setStroke(gridLineStroke);
            for (int i = 5;   i > 0;   i--) {
                Shape scaledGrid = gridShape.createTransformedShape
                    (AffineTransform.getScaleInstance(i / 5.0, i/5.0));
                g2.draw(scaledGrid);
            }

            // get the color for the plot shape.
            Paint dataPaint = plotLinePaint;
            if (dataPaint == ADAPTIVE_COLORING) {
                //multiplier = Math.exp(Math.log(multiplier) * 2 / numAxes);
                dataPaint = getMultiplierColor((float)multiplier);
            }

            // compute a slightly transparent version of the plot color for
            // the fill.
            Paint dataFill = null;
            if (dataPaint instanceof Color &&
                getForegroundAlpha() != 1.0)
                dataFill = new Color(((Color)dataPaint).getRed() / 255f,
                                     ((Color)dataPaint).getGreen() / 255f,
                                     ((Color)dataPaint).getBlue() / 255f,
                                     getForegroundAlpha());
            else
                dataFill = dataPaint;

            // draw the plot shape.  First fill with a parially
            // transparent color, then stroke with the opaque color.
            g2.setPaint(dataFill);
            g2.fill(lineShape);
            g2.setPaint(dataPaint);
            g2.setStroke(plotLineStroke);
            g2.draw(lineShape);

            // cleanup the graphics context.
            g2.translate(-centerX, -centerY);
        }
    }

    /** Calculate an appropriate color for the quality chart.
     * if the multiplier is 0, use red; if it is 1, use green;
     * use yellow in between, and fade proportionately.
     */
    private Paint getMultiplierColor(float value) {
        if (value > 0.4)
            return Color.green;
        else if (value > 0.2)
            // at 0.4, red component should be 0.0; at 0.2, it should be 1.0
            return new Color(2 - 5*value, 1, 0, 1);
        else
            // at 0.0, green component should be 0.0; at 0.2, it should be 1.0
            return new Color(1, 5*value, 0, 1);
    }


    /**
     * Draws the label for one radar axis.
     *
     * @param g2 The graphics device.
     * @param chartArea The area for the radar chart.
     * @param data The data for the plot.
     * @param axis The axis (zero-based index).
     * @param startAngle The starting angle.
     */
    protected void drawLabel(Graphics2D g2, Rectangle2D chartArea,
                             String label, int axis,
                             double labelX, double labelY) {

        // handle label drawing...
        FontRenderContext frc = g2.getFontRenderContext();
        Rectangle2D labelBounds =
            this.axisLabelFont.getStringBounds(label, frc);
        LineMetrics lm = this.axisLabelFont.getLineMetrics(label, frc);
        double ascent = lm.getAscent();

        if (labelX == chartArea.getCenterX())
            labelX -= labelBounds.getWidth() / 2;
        else if (labelX < chartArea.getCenterX())
            labelX -= labelBounds.getWidth();
        if (labelY > chartArea.getCenterY())
                labelY += ascent;

        g2.setPaint(this.axisLabelPaint);
        g2.setFont(this.axisLabelFont);
        g2.drawString(label, (float)labelX, (float)labelY);
    }


    /**
     * Returns a short string describing the type of plot.
     */
    public String getPlotType() {
        return "Radar Chart";
    }


    /**
     * A zoom method that does nothing.
     * <p>
     * Plots are required to support the zoom operation.  In the case
     * of a radar chart, it doesn't make sense to zoom in or out, so
     * the method is empty.
     *
     * @param percent The zoom percentage.
     */
    public void zoom(double percent) {
    }

}
TOP

Related Classes of org.jfree.chart.plot.RadarPlot

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.