    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2007, 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     * MinMaxCategoryRenderer.java
    029     * ---------------------------
    030     * (C) Copyright 2002-2007, by Object Refinery Limited.
    031     *
    032     * Original Author:  Tomer Peretz;
    033     * Contributor(s):   David Gilbert (for Object Refinery Limited);
    034     *                   Christian W. Zuckschwerdt;
    035     *                   Nicolas Brodu (for Astrium and EADS Corporate Research
    036     *                   Center);
    037     *
    038     * $Id: MinMaxCategoryRenderer.java,v 1.6.2.7 2007/03/09 16:45:06 mungady Exp $
    039     *
    040     * Changes:
    041     * --------
    042     * 29-May-2002 : Version 1 (TP);
    043     * 02-Oct-2002 : Fixed errors reported by Checkstyle (DG);
    044     * 24-Oct-2002 : Amendments for changes in CategoryDataset interface and
    045     *               CategoryToolTipGenerator interface (DG);
    046     * 05-Nov-2002 : Base dataset is now TableDataset not CategoryDataset (DG);
    047     * 17-Jan-2003 : Moved plot classes to a separate package (DG);
    048     * 10-Apr-2003 : Changed CategoryDataset to KeyedValues2DDataset in drawItem()
    049     *               method (DG);
    050     * 30-Jul-2003 : Modified entity constructor (CZ);
    051     * 08-Sep-2003 : Implemented Serializable (NB);
    052     * 29-Oct-2003 : Added workaround for font alignment in PDF output (DG);
    053     * 05-Nov-2004 : Modified drawItem() signature (DG);
    054     * 17-Nov-2005 : Added change events and argument checks (DG);
    055     * ------------- JFREECHART 1.0.x ---------------------------------------------
    056     * 02-Feb-2007 : Removed author tags all over JFreeChart sources (DG);
    057     * 09-Mar-2007 : Fixed problem with horizontal rendering (DG);
    058     *
    059     */
    060
    061    package org.jfree.chart.renderer.category;
    062
    063    import java.awt.BasicStroke;
    064    import java.awt.Color;
    065    import java.awt.Component;
    066    import java.awt.Graphics;
    067    import java.awt.Graphics2D;
    068    import java.awt.Paint;
    069    import java.awt.Shape;
    070    import java.awt.Stroke;
    071    import java.awt.geom.AffineTransform;
    072    import java.awt.geom.Arc2D;
    073    import java.awt.geom.GeneralPath;
    074    import java.awt.geom.Line2D;
    075    import java.awt.geom.Rectangle2D;
    076    import java.io.IOException;
    077    import java.io.ObjectInputStream;
    078    import java.io.ObjectOutputStream;
    079
    080    import javax.swing.Icon;
    081
    082    import org.jfree.chart.axis.CategoryAxis;
    083    import org.jfree.chart.axis.ValueAxis;
    084    import org.jfree.chart.entity.CategoryItemEntity;
    085    import org.jfree.chart.entity.EntityCollection;
    086    import org.jfree.chart.event.RendererChangeEvent;
    087    import org.jfree.chart.labels.CategoryToolTipGenerator;
    088    import org.jfree.chart.plot.CategoryPlot;
    089    import org.jfree.chart.plot.PlotOrientation;
    090    import org.jfree.data.category.CategoryDataset;
    091    import org.jfree.io.SerialUtilities;
    092
    093    /**
    094     * Renderer for drawing min max plot. This renderer draws all the series under
    095     * the same category in the same x position using <code>objectIcon</code> and
    096     * a line from the maximum value to the minimum value.
    097     * <p>
    098     * For use with the {@link org.jfree.chart.plot.CategoryPlot} class.
    099     */
    100    public class MinMaxCategoryRenderer extends AbstractCategoryItemRenderer {
    101
    102        /** For serialization. */
    103        private static final long serialVersionUID = 2935615937671064911L;
    104
    105        /** A flag indicating whether or not lines are drawn between XY points. */
    106        private boolean plotLines = false;
    107
    108        /**
    109         * The paint of the line between the minimum value and the maximum value.
    110         */
    111        private transient Paint groupPaint = Color.black;
    112
    113        /**
    114         * The stroke of the line between the minimum value and the maximum value.
    115         */
    116        private transient Stroke groupStroke = new BasicStroke(1.0f);
    117
    118        /** The icon used to indicate the minimum value.*/
    119        private transient Icon minIcon = getIcon(new Arc2D.Double(-4, -4, 8, 8, 0,
    120                360, Arc2D.OPEN), null, Color.black);
    121
    122        /** The icon used to indicate the maximum value.*/
    123        private transient Icon maxIcon = getIcon(new Arc2D.Double(-4, -4, 8, 8, 0,
    124                360, Arc2D.OPEN), null, Color.black);
    125
    126        /** The icon used to indicate the values.*/
    127        private transient Icon objectIcon = getIcon(new Line2D.Double(-4, 0, 4, 0),
    128                false, true);
    129
    130        /** The last category. */
    131        private int lastCategory = -1;
    132
    133        /** The minimum. */
    134        private double min;
    135
    136        /** The maximum. */
    137        private double max;
    138
    139        /**
    140         * Default constructor.
    141         */
    142        public MinMaxCategoryRenderer() {
    143            super();
    144        }
    145
    146        /**
    147         * Gets whether or not lines are drawn between category points.
    148         *
    149         * @return boolean true if line will be drawn between sequenced categories,
    150         *         otherwise false.
    151         *
    152         * @see #setDrawLines(boolean)
    153         */
    154        public boolean isDrawLines() {
    155            return this.plotLines;
    156        }
    157
    158        /**
    159         * Sets the flag that controls whether or not lines are drawn to connect
    160         * the items within a series and sends a {@link RendererChangeEvent} to
    161         * all registered listeners.
    162         *
    163         * @param draw  the new value of the flag.
    164         *
    165         * @see #isDrawLines()
    166         */
    167        public void setDrawLines(boolean draw) {
    168            if (this.plotLines != draw) {
    169                this.plotLines = draw;
    170                this.notifyListeners(new RendererChangeEvent(this));
    171            }
    172
    173        }
    174
    175        /**
    176         * Returns the paint used to draw the line between the minimum and maximum
    177         * value items in each category.
    178         *
    179         * @return The paint (never <code>null</code>).
    180         *
    181         * @see #setGroupPaint(Paint)
    182         */
    183        public Paint getGroupPaint() {
    184            return this.groupPaint;
    185        }
    186
    187        /**
    188         * Sets the paint used to draw the line between the minimum and maximum
    189         * value items in each category and sends a {@link RendererChangeEvent} to
    190         * all registered listeners.
    191         *
    192         * @param paint  the paint (<code>null</code> not permitted).
    193         *
    194         * @see #getGroupPaint()
    195         */
    196        public void setGroupPaint(Paint paint) {
    197            if (paint == null) {
    198                throw new IllegalArgumentException("Null 'paint' argument.");
    199            }
    200            this.groupPaint = paint;
    201            notifyListeners(new RendererChangeEvent(this));
    202        }
    203
    204        /**
    205         * Returns the stroke used to draw the line between the minimum and maximum
    206         * value items in each category.
    207         *
    208         * @return The stroke (never <code>null</code>).
    209         *
    210         * @see #setGroupStroke(Stroke)
    211         */
    212        public Stroke getGroupStroke() {
    213            return this.groupStroke;
    214        }
    215
    216        /**
    217         * Sets the stroke of the line between the minimum value and the maximum
    218         * value.
    219         *
    220         * @param groupStroke The new stroke
    221         */
    222        public void setGroupStroke(Stroke groupStroke) {
    223            this.groupStroke = groupStroke;
    224        }
    225
    226        /**
    227         * Returns the icon drawn for each data item.
    228         *
    229         * @return The icon (never <code>null</code>).
    230         *
    231         * @see #setObjectIcon(Icon)
    232         */
    233        public Icon getObjectIcon() {
    234            return this.objectIcon;
    235        }
    236
    237        /**
    238         * Sets the icon drawn for each data item.
    239         *
    240         * @param icon  the icon.
    241         *
    242         * @see #getObjectIcon()
    243         */
    244        public void setObjectIcon(Icon icon) {
    245            if (icon == null) {
    246                throw new IllegalArgumentException("Null 'icon' argument.");
    247            }
    248            this.objectIcon = icon;
    249            notifyListeners(new RendererChangeEvent(this));
    250        }
    251
    252        /**
    253         * Returns the icon displayed for the maximum value data item within each
    254         * category.
    255         *
    256         * @return The icon (never <code>null</code>).
    257         *
    258         * @see #setMaxIcon(Icon)
    259         */
    260        public Icon getMaxIcon() {
    261            return this.maxIcon;
    262        }
    263
    264        /**
    265         * Sets the icon displayed for the maximum value data item within each
    266         * category and sends a {@link RendererChangeEvent} to all registered
    267         * listeners.
    268         *
    269         * @param icon  the icon (<code>null</code> not permitted).
    270         *
    271         * @see #getMaxIcon()
    272         */
    273        public void setMaxIcon(Icon icon) {
    274            if (icon == null) {
    275                throw new IllegalArgumentException("Null 'icon' argument.");
    276            }
    277            this.maxIcon = icon;
    278            notifyListeners(new RendererChangeEvent(this));
    279        }
    280
    281        /**
    282         * Returns the icon displayed for the minimum value data item within each
    283         * category.
    284         *
    285         * @return The icon (never <code>null</code>).
    286         *
    287         * @see #setMinIcon(Icon)
    288         */
    289        public Icon getMinIcon() {
    290            return this.minIcon;
    291        }
    292
    293        /**
    294         * Sets the icon displayed for the minimum value data item within each
    295         * category and sends a {@link RendererChangeEvent} to all registered
    296         * listeners.
    297         *
    298         * @param icon  the icon (<code>null</code> not permitted).
    299         *
    300         * @see #getMinIcon()
    301         */
    302        public void setMinIcon(Icon icon) {
    303            if (icon == null) {
    304                throw new IllegalArgumentException("Null 'icon' argument.");
    305            }
    306            this.minIcon = icon;
    307            notifyListeners(new RendererChangeEvent(this));
    308        }
    309
    310        /**
    311         * Draw a single data item.
    312         *
    313         * @param g2  the graphics device.
    314         * @param state  the renderer state.
    315         * @param dataArea  the area in which the data is drawn.
    316         * @param plot  the plot.
    317         * @param domainAxis  the domain axis.
    318         * @param rangeAxis  the range axis.
    319         * @param dataset  the dataset.
    320         * @param row  the row index (zero-based).
    321         * @param column  the column index (zero-based).
    322         * @param pass  the pass index.
    323         */
    324        public void drawItem(Graphics2D g2, CategoryItemRendererState state,
    325                Rectangle2D dataArea, CategoryPlot plot, CategoryAxis domainAxis,
    326                ValueAxis rangeAxis, CategoryDataset dataset, int row, int column,
    327                int pass) {
    328
    329            // first check the number we are plotting...
    330            Number value = dataset.getValue(row, column);
    331            if (value != null) {
    332                // current data point...
    333                double x1 = domainAxis.getCategoryMiddle(column, getColumnCount(),
    334                        dataArea, plot.getDomainAxisEdge());
    335                double y1 = rangeAxis.valueToJava2D(value.doubleValue(), dataArea,
    336                        plot.getRangeAxisEdge());
    337                g2.setPaint(getItemPaint(row, column));
    338                g2.setStroke(getItemStroke(row, column));
    339                Shape shape = null;
    340                shape = new Rectangle2D.Double(x1 - 4, y1 - 4, 8.0, 8.0);
    341
    342                PlotOrientation orient = plot.getOrientation();
    343                if (orient == PlotOrientation.VERTICAL) {
    344                    this.objectIcon.paintIcon(null, g2, (int) x1, (int) y1);
    345                }
    346                else {
    347                    this.objectIcon.paintIcon(null, g2, (int) y1, (int) x1);
    348                }
    349
    350                if (this.lastCategory == column) {
    351                    if (this.min > value.doubleValue()) {
    352                        this.min = value.doubleValue();
    353                    }
    354                    if (this.max < value.doubleValue()) {
    355                        this.max = value.doubleValue();
    356                    }
    357
    358                    // last series, so we are ready to draw the min and max
    359                    if (dataset.getRowCount() - 1 == row) {
    360                        g2.setPaint(this.groupPaint);
    361                        g2.setStroke(this.groupStroke);
    362                        double minY = rangeAxis.valueToJava2D(this.min, dataArea,
    363                                plot.getRangeAxisEdge());
    364                        double maxY = rangeAxis.valueToJava2D(this.max, dataArea,
    365                                plot.getRangeAxisEdge());
    366
    367                        if (orient == PlotOrientation.VERTICAL) {
    368                            g2.draw(new Line2D.Double(x1, minY, x1, maxY));
    369                            this.minIcon.paintIcon(null, g2, (int) x1, (int) minY);
    370                            this.maxIcon.paintIcon(null, g2, (int) x1, (int) maxY);
    371                        }
    372                        else {
    373                            g2.draw(new Line2D.Double(minY, x1, maxY, x1));
    374                            this.minIcon.paintIcon(null, g2, (int) minY, (int) x1);
    375                            this.maxIcon.paintIcon(null, g2, (int) maxY, (int) x1);
    376                        }
    377                    }
    378                }
    379                else {  // reset the min and max
    380                    this.lastCategory = column;
    381                    this.min = value.doubleValue();
    382                    this.max = value.doubleValue();
    383                }
    384
    385                // connect to the previous point
    386                if (this.plotLines) {
    387                    if (column != 0) {
    388                        Number previousValue = dataset.getValue(row, column - 1);
    389                        if (previousValue != null) {
    390                            // previous data point...
    391                            double previous = previousValue.doubleValue();
    392                            double x0 = domainAxis.getCategoryMiddle(column - 1,
    393                                    getColumnCount(), dataArea,
    394                                    plot.getDomainAxisEdge());
    395                            double y0 = rangeAxis.valueToJava2D(previous, dataArea,
    396                                    plot.getRangeAxisEdge());
    397                            g2.setPaint(getItemPaint(row, column));
    398                            g2.setStroke(getItemStroke(row, column));
    399                            Line2D line;
    400                            if (orient == PlotOrientation.VERTICAL) {
    401                                line = new Line2D.Double(x0, y0, x1, y1);
    402                            }
    403                            else {
    404                                line = new Line2D.Double(y0, x0, y1, x1);
    405                            }
    406                            g2.draw(line);
    407                        }
    408                    }
    409                }
    410
    411                // collect entity and tool tip information...
    412                if (state.getInfo() != null) {
    413                    EntityCollection entities = state.getEntityCollection();
    414                    if (entities != null && shape != null) {
    415                        String tip = null;
    416                        CategoryToolTipGenerator tipster = getToolTipGenerator(row,
    417                                column);
    418                        if (tipster != null) {
    419                            tip = tipster.generateToolTip(dataset, row, column);
    420                        }
    421                        CategoryItemEntity entity = new CategoryItemEntity(
    422                                shape, tip, null, dataset, row,
    423                                dataset.getColumnKey(column), column);
    424                        entities.add(entity);
    425                    }
    426                }
    427            }
    428        }
    429
    430        /**
    431         * Returns an icon.
    432         *
    433         * @param shape  the shape.
    434         * @param fillPaint  the fill paint.
    435         * @param outlinePaint  the outline paint.
    436         *
    437         * @return The icon.
    438         */
    439        private Icon getIcon(Shape shape, final Paint fillPaint,
    440                            final Paint outlinePaint) {
    441
    442          final int width = shape.getBounds().width;
    443          final int height = shape.getBounds().height;
    444          final GeneralPath path = new GeneralPath(shape);
    445          return new Icon() {
    446              public void paintIcon(Component c, Graphics g, int x, int y) {
    447                  Graphics2D g2 = (Graphics2D) g;
    448                  path.transform(AffineTransform.getTranslateInstance(x, y));
    449                  if (fillPaint != null) {
    450                      g2.setPaint(fillPaint);
    451                      g2.fill(path);
    452                  }
    453                  if (outlinePaint != null) {
    454                      g2.setPaint(outlinePaint);
    455                      g2.draw(path);
    456                  }
    457                  path.transform(AffineTransform.getTranslateInstance(-x, -y));
    458            }
    459
    460            public int getIconWidth() {
    461                return width;
    462            }
    463
    464            public int getIconHeight() {
    465                return height;
    466            }
    467
    468          };
    469        }
    470
    471        /**
    472         * Returns an icon.
    473         *
    474         * @param shape  the shape.
    475         * @param fill  the fill flag.
    476         * @param outline  the outline flag.
    477         *
    478         * @return The icon.
    479         */
    480        private Icon getIcon(Shape shape, final boolean fill,
    481                             final boolean outline) {
    482            final int width = shape.getBounds().width;
    483            final int height = shape.getBounds().height;
    484            final GeneralPath path = new GeneralPath(shape);
    485            return new Icon() {
    486                public void paintIcon(Component c, Graphics g, int x, int y) {
    487                    Graphics2D g2 = (Graphics2D) g;
    488                    path.transform(AffineTransform.getTranslateInstance(x, y));
    489                    if (fill) {
    490                        g2.fill(path);
    491                    }
    492                    if (outline) {
    493                        g2.draw(path);
    494                    }
    495                    path.transform(AffineTransform.getTranslateInstance(-x, -y));
    496                }
    497
    498                public int getIconWidth() {
    499                    return width;
    500                }
    501
    502                public int getIconHeight() {
    503                    return height;
    504                }
    505            };
    506        }
    507
    508        /**
    509         * Provides serialization support.
    510         *
    511         * @param stream  the output stream.
    512         *
    513         * @throws IOException  if there is an I/O error.
    514         */
    515        private void writeObject(ObjectOutputStream stream) throws IOException {
    516            stream.defaultWriteObject();
    517            SerialUtilities.writeStroke(this.groupStroke, stream);
    518            SerialUtilities.writePaint(this.groupPaint, stream);
    519        }
    520
    521        /**
    522         * Provides serialization support.
    523         *
    524         * @param stream  the input stream.
    525         *
    526         * @throws IOException  if there is an I/O error.
    527         * @throws ClassNotFoundException  if there is a classpath problem.
    528         */
    529        private void readObject(ObjectInputStream stream)
    530            throws IOException, ClassNotFoundException {
    531            stream.defaultReadObject();
    532            this.groupStroke = SerialUtilities.readStroke(stream);
    533            this.groupPaint = SerialUtilities.readPaint(stream);
    534
    535            this.minIcon = getIcon(new Arc2D.Double(-4, -4, 8, 8, 0, 360,
    536                    Arc2D.OPEN), null, Color.black);
    537            this.maxIcon = getIcon(new Arc2D.Double(-4, -4, 8, 8, 0, 360,
    538                    Arc2D.OPEN), null, Color.black);
    539            this.objectIcon = getIcon(new Line2D.Double(-4, 0, 4, 0), false, true);
    540        }
    541
    542    }