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