    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
    006     *
    007     * Project Info:  http://www.jfree.org/jfreechart/index.html
    008     *
    009     * This library is free software; you can redistribute it and/or modify it
    010     * under the terms of the GNU Lesser General Public License as published by
    011     * the Free Software Foundation; either version 2.1 of the License, or
    012     * (at your option) any later version.
    013     *
    014     * This library is distributed in the hope that it will be useful, but
    015     * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
    016     * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
    017     * License for more details.
    018     *
    019     * You should have received a copy of the GNU Lesser General Public
    020     * License along with this library; if not, write to the Free Software
    021     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
    022     * USA.
    023     *
    024     * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
    025     * in the United States and other countries.]
    026     *
    027     * --------------------
    028     * MultiplePiePlot.java
    029     * --------------------
    030     * (C) Copyright 2004, 2005, by Object Refinery Limited.
    031     *
    032     * Original Author:  David Gilbert (for Object Refinery Limited);
    033     * Contributor(s):   -;
    034     *
    035     * $Id: MultiplePiePlot.java,v 1.12.2.4 2005/11/28 12:06:35 mungady Exp $
    036     *
    037     * Changes (from 21-Jun-2001)
    038     * --------------------------
    039     * 29-Jan-2004 : Version 1 (DG);
    040     * 31-Mar-2004 : Added setPieIndex() call during drawing (DG);
    041     * 20-Apr-2005 : Small change for update to LegendItem constructors (DG);
    042     * 05-May-2005 : Updated draw() method parameters (DG);
    043     * 16-Jun-2005 : Added get/setDataset() and equals() methods (DG);
    044     *
    045     */
    046
    047    package org.jfree.chart.plot;
    048
    049    import java.awt.Font;
    050    import java.awt.Graphics2D;
    051    import java.awt.Paint;
    052    import java.awt.Rectangle;
    053    import java.awt.Stroke;
    054    import java.awt.geom.Point2D;
    055    import java.awt.geom.Rectangle2D;
    056    import java.io.Serializable;
    057    import java.util.Iterator;
    058    import java.util.List;
    059
    060    import org.jfree.chart.ChartRenderingInfo;
    061    import org.jfree.chart.JFreeChart;
    062    import org.jfree.chart.LegendItem;
    063    import org.jfree.chart.LegendItemCollection;
    064    import org.jfree.chart.event.PlotChangeEvent;
    065    import org.jfree.chart.title.TextTitle;
    066    import org.jfree.data.category.CategoryDataset;
    067    import org.jfree.data.category.CategoryToPieDataset;
    068    import org.jfree.data.general.DatasetChangeEvent;
    069    import org.jfree.data.general.DatasetUtilities;
    070    import org.jfree.data.general.PieDataset;
    071    import org.jfree.ui.RectangleEdge;
    072    import org.jfree.ui.RectangleInsets;
    073    import org.jfree.util.ObjectUtilities;
    074    import org.jfree.util.TableOrder;
    075
    076    /**
    077     * A plot that displays multiple pie plots using data from a
    078     * {@link CategoryDataset}.
    079     */
    080    public class MultiplePiePlot extends Plot implements Cloneable, Serializable {
    081
    082        /** For serialization. */
    083        private static final long serialVersionUID = -355377800470807389L;
    084
    085        /** The chart object that draws the individual pie charts. */
    086        private JFreeChart pieChart;
    087
    088        /** The dataset. */
    089        private CategoryDataset dataset;
    090
    091        /** The data extract order (by row or by column). */
    092        private TableOrder dataExtractOrder;
    093
    094        /** The pie section limit percentage. */
    095        private double limit = 0.0;
    096
    097        /**
    098         * Creates a new plot with no data.
    099         */
    100        public MultiplePiePlot() {
    101            this(null);
    102        }
    103
    104        /**
    105         * Creates a new plot.
    106         *
    107         * @param dataset  the dataset (<code>null</code> permitted).
    108         */
    109        public MultiplePiePlot(CategoryDataset dataset) {
    110            super();
    111            this.dataset = dataset;
    112            PiePlot piePlot = new PiePlot(null);
    113            this.pieChart = new JFreeChart(piePlot);
    114            this.pieChart.removeLegend();
    115            this.dataExtractOrder = TableOrder.BY_COLUMN;
    116            this.pieChart.setBackgroundPaint(null);
    117            TextTitle seriesTitle = new TextTitle(
    118                "Series Title", new Font("SansSerif", Font.BOLD, 12)
    119            );
    120            seriesTitle.setPosition(RectangleEdge.BOTTOM);
    121            this.pieChart.setTitle(seriesTitle);
    122        }
    123
    124        /**
    125         * Returns the dataset used by the plot.
    126         *
    127         * @return The dataset (possibly <code>null</code>).
    128         */
    129        public CategoryDataset getDataset() {
    130            return this.dataset;
    131        }
    132
    133        /**
    134         * Sets the dataset used by the plot and sends a {@link PlotChangeEvent}
    135         * to all registered listeners.
    136         *
    137         * @param dataset  the dataset (<code>null</code> permitted).
    138         */
    139        public void setDataset(CategoryDataset dataset) {
    140            // if there is an existing dataset, remove the plot from the list of
    141            // change listeners...
    142            if (this.dataset != null) {
    143                this.dataset.removeChangeListener(this);
    144            }
    145
    146            // set the new dataset, and register the chart as a change listener...
    147            this.dataset = dataset;
    148            if (dataset != null) {
    149                setDatasetGroup(dataset.getGroup());
    150                dataset.addChangeListener(this);
    151            }
    152
    153            // send a dataset change event to self to trigger plot change event
    154            datasetChanged(new DatasetChangeEvent(this, dataset));
    155        }
    156
    157        /**
    158         * Returns the pie chart that is used to draw the individual pie plots.
    159         *
    160         * @return The pie chart.
    161         */
    162        public JFreeChart getPieChart() {
    163            return this.pieChart;
    164        }
    165
    166        /**
    167         * Sets the chart that is used to draw the individual pie plots.
    168         *
    169         * @param pieChart  the pie chart.
    170         */
    171        public void setPieChart(JFreeChart pieChart) {
    172            this.pieChart = pieChart;
    173            notifyListeners(new PlotChangeEvent(this));
    174        }
    175
    176        /**
    177         * Returns the data extract order (by row or by column).
    178         *
    179         * @return The data extract order (never <code>null</code>).
    180         */
    181        public TableOrder getDataExtractOrder() {
    182            return this.dataExtractOrder;
    183        }
    184
    185        /**
    186         * Sets the data extract order (by row or by column) and sends a
    187         * {@link PlotChangeEvent} to all registered listeners.
    188         *
    189         * @param order  the order (<code>null</code> not permitted).
    190         */
    191        public void setDataExtractOrder(TableOrder order) {
    192            if (order == null) {
    193                throw new IllegalArgumentException("Null 'order' argument");
    194            }
    195            this.dataExtractOrder = order;
    196            notifyListeners(new PlotChangeEvent(this));
    197        }
    198
    199        /**
    200         * Returns the limit (as a percentage) below which small pie sections are
    201         * aggregated.
    202         *
    203         * @return The limit percentage.
    204         */
    205        public double getLimit() {
    206            return this.limit;
    207        }
    208
    209        /**
    210         * Sets the limit below which pie sections are aggregated.
    211         * Set this to 0.0 if you don't want any aggregation to occur.
    212         *
    213         * @param limit  the limit percent.
    214         */
    215        public void setLimit(double limit) {
    216            this.limit = limit;
    217            notifyListeners(new PlotChangeEvent(this));
    218        }
    219
    220        /**
    221         * Returns a short string describing the type of plot.
    222         *
    223         * @return The plot type.
    224         */
    225        public String getPlotType() {
    226            return "Multiple Pie Plot";
    227             // TODO: need to fetch this from localised resources
    228        }
    229
    230        /**
    231         * Draws the plot on a Java 2D graphics device (such as the screen or a
    232         * printer).
    233         *
    234         * @param g2  the graphics device.
    235         * @param area  the area within which the plot should be drawn.
    236         * @param anchor  the anchor point (<code>null</code> permitted).
    237         * @param parentState  the state from the parent plot, if there is one.
    238         * @param info  collects info about the drawing.
    239         */
    240        public void draw(Graphics2D g2,
    241                         Rectangle2D area,
    242                         Point2D anchor,
    243                         PlotState parentState,
    244                         PlotRenderingInfo info) {
    245
    246
    247            // adjust the drawing area for the plot insets (if any)...
    248            RectangleInsets insets = getInsets();
    249            insets.trim(area);
    250            drawBackground(g2, area);
    251            drawOutline(g2, area);
    252
    253            // check that there is some data to display...
    254            if (DatasetUtilities.isEmptyOrNull(this.dataset)) {
    255                drawNoDataMessage(g2, area);
    256                return;
    257            }
    258
    259            int pieCount = 0;
    260            if (this.dataExtractOrder == TableOrder.BY_ROW) {
    261                pieCount = this.dataset.getRowCount();
    262            }
    263            else {
    264                pieCount = this.dataset.getColumnCount();
    265            }
    266
    267            // the columns variable is always >= rows
    268            int displayCols = (int) Math.ceil(Math.sqrt(pieCount));
    269            int displayRows
    270                = (int) Math.ceil((double) pieCount / (double) displayCols);
    271
    272            // swap rows and columns to match plotArea shape
    273            if (displayCols > displayRows && area.getWidth() < area.getHeight()) {
    274                int temp = displayCols;
    275                displayCols = displayRows;
    276                displayRows = temp;
    277            }
    278
    279            // int fontHeight
    280            //     = g2.getFontMetrics(this.plotLabelFont).getHeight() * 2;
    281            int x = (int) area.getX();
    282            int y = (int) area.getY();
    283            int width = ((int) area.getWidth()) / displayCols;
    284            int height = ((int) area.getHeight()) / displayRows;
    285            int row = 0;
    286            int column = 0;
    287            int diff = (displayRows * displayCols) - pieCount;
    288            int xoffset = 0;
    289            Rectangle rect = new Rectangle();
    290
    291            for (int pieIndex = 0; pieIndex < pieCount; pieIndex++) {
    292                rect.setBounds(
    293                    x + xoffset + (width * column), y + (height * row),
    294                    width, height
    295                );
    296
    297                String title = null;
    298                if (this.dataExtractOrder == TableOrder.BY_ROW) {
    299                    title = this.dataset.getRowKey(pieIndex).toString();
    300                }
    301                else {
    302                    title = this.dataset.getColumnKey(pieIndex).toString();
    303                }
    304                this.pieChart.setTitle(title);
    305
    306                PieDataset piedataset = null;
    307                PieDataset dd = new CategoryToPieDataset(
    308                    this.dataset, this.dataExtractOrder, pieIndex
    309                );
    310                if (this.limit > 0.0) {
    311                    piedataset = DatasetUtilities.createConsolidatedPieDataset(
    312                        dd, "Other", this.limit
    313                    );
    314                }
    315                else {
    316                    piedataset = dd;
    317                }
    318                PiePlot piePlot = (PiePlot) this.pieChart.getPlot();
    319                piePlot.setDataset(piedataset);
    320                piePlot.setPieIndex(pieIndex);
    321                ChartRenderingInfo subinfo = null;
    322                if (info != null) {
    323                    subinfo = new ChartRenderingInfo();
    324                }
    325                this.pieChart.draw(g2, rect, subinfo);
    326                if (info != null) {
    327                    info.getOwner().getEntityCollection().addAll(
    328                        subinfo.getEntityCollection()
    329                    );
    330                    info.addSubplotInfo(subinfo.getPlotInfo());
    331                }
    332
    333                ++column;
    334                if (column == displayCols) {
    335                    column = 0;
    336                    ++row;
    337
    338                    if (row == displayRows - 1 && diff != 0) {
    339                        xoffset = (diff * width) / 2;
    340                    }
    341                }
    342            }
    343
    344        }
    345
    346        /**
    347         * Returns a collection of legend items for the pie chart.
    348         *
    349         * @return The legend items.
    350         */
    351        public LegendItemCollection getLegendItems() {
    352
    353            LegendItemCollection result = new LegendItemCollection();
    354
    355            if (this.dataset != null) {
    356                List keys = null;
    357
    358                if (this.dataExtractOrder == TableOrder.BY_ROW) {
    359                    keys = this.dataset.getColumnKeys();
    360                }
    361                else if (this.dataExtractOrder == TableOrder.BY_COLUMN) {
    362                    keys = this.dataset.getRowKeys();
    363                }
    364
    365                if (keys != null) {
    366                    int section = 0;
    367                    Iterator iterator = keys.iterator();
    368                    while (iterator.hasNext()) {
    369                        String label = iterator.next().toString();
    370                        String description = label;
    371                        PiePlot plot = (PiePlot) this.pieChart.getPlot();
    372                        Paint paint = plot.getSectionPaint(section);
    373                        Paint outlinePaint = plot.getSectionOutlinePaint(section);
    374                        Stroke outlineStroke
    375                            = plot.getSectionOutlineStroke(section);
    376                        LegendItem item = new LegendItem(label, description,
    377                                null, null, Plot.DEFAULT_LEGEND_ITEM_CIRCLE,
    378                                paint, outlineStroke, outlinePaint);
    379
    380                        result.add(item);
    381                        section++;
    382                    }
    383                }
    384            }
    385            return result;
    386        }
    387
    388        /**
    389         * Tests this plot for equality with an arbitrary object.  Note that the
    390         * plot's dataset is not considered in the equality test.
    391         *
    392         * @param obj  the object (<code>null</code> permitted).
    393         */
    394        public boolean equals(Object obj) {
    395            if (obj == this) {
    396                return true;
    397            }
    398            if (!(obj instanceof MultiplePiePlot)) {
    399                return false;
    400            }
    401            MultiplePiePlot that = (MultiplePiePlot) obj;
    402            if (this.dataExtractOrder != that.dataExtractOrder) {
    403                return false;
    404            }
    405            if (this.limit != that.limit) {
    406                return false;
    407            }
    408            if (!ObjectUtilities.equal(this.pieChart, that.pieChart)) {
    409                return false;
    410            }
    411            if (!super.equals(obj)) {
    412                return false;
    413            }
    414            return true;
    415        }
    416
    417    }