001    /* ===========================================================
002     * JFreeChart : a free chart library for the Java(tm) platform
003     * ===========================================================
004     *
005     * (C) Copyright 2000-2008, 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     * XYShapeRenderer.java
029     * --------------------
030     * (C) Copyright 2008, by Andreas Haumer, xS+S and Contributors.
031     *
032     * Original Author:  Martin Hoeller (x Software + Systeme  xS+S - Andreas
033     *                       Haumer);
034     * Contributor(s):   David Gilbert (for Object Refinery Limited);
035     *
036     * Changes:
037     * --------
038     * 17-Sep-2008 : Version 1, based on a contribution from Martin Hoeller with
039     *               amendments by David Gilbert (DG);
040     *
041     */
042    
043    package org.jfree.chart.renderer.xy;
044    
045    import java.awt.BasicStroke;
046    import java.awt.Color;
047    import java.awt.Graphics2D;
048    import java.awt.Paint;
049    import java.awt.Shape;
050    import java.awt.Stroke;
051    import java.awt.geom.Ellipse2D;
052    import java.awt.geom.Line2D;
053    import java.awt.geom.Rectangle2D;
054    import java.io.IOException;
055    import java.io.ObjectInputStream;
056    import java.io.ObjectOutputStream;
057    import java.io.Serializable;
058    
059    import org.jfree.chart.axis.ValueAxis;
060    import org.jfree.chart.entity.EntityCollection;
061    import org.jfree.chart.event.RendererChangeEvent;
062    import org.jfree.chart.plot.CrosshairState;
063    import org.jfree.chart.plot.PlotOrientation;
064    import org.jfree.chart.plot.PlotRenderingInfo;
065    import org.jfree.chart.plot.XYPlot;
066    import org.jfree.chart.renderer.LookupPaintScale;
067    import org.jfree.chart.renderer.PaintScale;
068    import org.jfree.data.Range;
069    import org.jfree.data.general.DatasetUtilities;
070    import org.jfree.data.xy.XYDataset;
071    import org.jfree.data.xy.XYZDataset;
072    import org.jfree.io.SerialUtilities;
073    import org.jfree.util.PublicCloneable;
074    import org.jfree.util.ShapeUtilities;
075    
076    /**
077     * A renderer that draws shapes at (x, y) coordinates and, if the dataset
078     * is an instance of {@link XYZDataset}, fills the shapes with a paint that
079     * is based on the z-value (the paint is obtained from a lookup table).  The
080     * renderer also allows for optional guidelines, horizontal and vertical lines
081     * connecting the shape to the edges of the plot.
082     * <p>
083     * This renderer has similarities to, but also differences from, the
084     * {@link XYLineAndShapeRenderer}.
085     *
086     * @since 1.0.11
087     */
088    public class XYShapeRenderer extends AbstractXYItemRenderer
089            implements XYItemRenderer, Cloneable, Serializable {
090    
091        /** Auto generated serial version id. */
092        private static final long serialVersionUID = 8320552104211173221L;
093    
094        /** The paint scale. */
095        private PaintScale paintScale;
096    
097        /** A flag that controls whether or not the shape outlines are drawn. */
098        private boolean drawOutlines;
099    
100        /**
101         * A flag that controls whether or not the outline paint is used (if not,
102         * the regular paint is used).
103         */
104        private boolean useOutlinePaint;
105    
106        /**
107         * A flag that controls whether or not the fill paint is used (if not,
108         * the fill paint is used).
109         */
110        private boolean useFillPaint;
111    
112        /** Flag indicating if guide lines should be drawn for every item. */
113        private boolean guideLinesVisible;
114    
115        /** The paint used for drawing the guide lines. */
116        private transient Paint guideLinePaint;
117    
118        /** The stroke used for drawing the guide lines. */
119        private transient Stroke guideLineStroke;
120    
121        /**
122         * Creates a new <code>XYShapeRenderer</code> instance with default
123         * attributes.
124         */
125        public XYShapeRenderer() {
126            this.paintScale = new LookupPaintScale();
127            this.useFillPaint = false;
128            this.drawOutlines = false;
129            this.useOutlinePaint = true;
130            this.guideLinesVisible = false;
131            this.guideLinePaint = Color.darkGray;
132            this.guideLineStroke = new BasicStroke();
133            setBaseShape(new Ellipse2D.Double(-5.0, -5.0, 10.0, 10.0));
134            setAutoPopulateSeriesShape(false);
135        }
136    
137        /**
138         * Returns the paint scale used by the renderer.
139         *
140         * @return The paint scale (never <code>null</code>).
141         *
142         * @see #setPaintScale(PaintScale)
143         */
144        public PaintScale getPaintScale() {
145            return this.paintScale;
146        }
147    
148        /**
149         * Sets the paint scale used by the renderer and sends a
150         * {@link RendererChangeEvent} to all registered listeners.
151         *
152         * @param scale  the scale (<code>null</code> not permitted).
153         *
154         * @see #getPaintScale()
155         */
156        public void setPaintScale(PaintScale scale) {
157            if (scale == null) {
158                throw new IllegalArgumentException("Null 'scale' argument.");
159            }
160            this.paintScale = scale;
161            notifyListeners(new RendererChangeEvent(this));
162        }
163    
164        /**
165         * Returns <code>true</code> if outlines should be drawn for shapes, and
166         * <code>false</code> otherwise.
167         *
168         * @return A boolean.
169         *
170         * @see #setDrawOutlines(boolean)
171         */
172        public boolean getDrawOutlines() {
173            return this.drawOutlines;
174        }
175    
176        /**
177         * Sets the flag that controls whether outlines are drawn for
178         * shapes, and sends a {@link RendererChangeEvent} to all registered
179         * listeners.
180         * <P>
181         * In some cases, shapes look better if they do NOT have an outline, but
182         * this flag allows you to set your own preference.
183         *
184         * @param flag  the flag.
185         *
186         * @see #getDrawOutlines()
187         */
188        public void setDrawOutlines(boolean flag) {
189            this.drawOutlines = flag;
190            fireChangeEvent();
191        }
192    
193        /**
194         * Returns <code>true</code> if the renderer should use the fill paint
195         * setting to fill shapes, and <code>false</code> if it should just
196         * use the regular paint.
197         * <p>
198         * Refer to <code>XYLineAndShapeRendererDemo2.java</code> to see the
199         * effect of this flag.
200         *
201         * @return A boolean.
202         *
203         * @see #setUseFillPaint(boolean)
204         * @see #getUseOutlinePaint()
205         */
206        public boolean getUseFillPaint() {
207            return this.useFillPaint;
208        }
209    
210        /**
211         * Sets the flag that controls whether the fill paint is used to fill
212         * shapes, and sends a {@link RendererChangeEvent} to all
213         * registered listeners.
214         *
215         * @param flag  the flag.
216         *
217         * @see #getUseFillPaint()
218         */
219        public void setUseFillPaint(boolean flag) {
220            this.useFillPaint = flag;
221            fireChangeEvent();
222        }
223    
224        /**
225         * Returns the flag that controls whether the outline paint is used for
226         * shape outlines.  If not, the regular series paint is used.
227         *
228         * @return A boolean.
229         *
230         * @see #setUseOutlinePaint(boolean)
231         */
232        public boolean getUseOutlinePaint() {
233            return this.useOutlinePaint;
234        }
235    
236        /**
237         * Sets the flag that controls whether the outline paint is used for shape
238         * outlines, and sends a {@link RendererChangeEvent} to all registered
239         * listeners.
240         *
241         * @param use  the flag.
242         *
243         * @see #getUseOutlinePaint()
244         */
245        public void setUseOutlinePaint(boolean use) {
246            this.useOutlinePaint = use;
247            fireChangeEvent();
248        }
249    
250        /**
251         * Returns a flag that controls whether or not guide lines are drawn for
252         * each data item (the lines are horizontal and vertical "crosshairs"
253         * linking the data point to the axes).
254         *
255         * @return A boolean.
256         *
257         * @see #setGuideLinesVisible(boolean)
258         */
259        public boolean isGuideLinesVisible() {
260            return this.guideLinesVisible;
261        }
262    
263        /**
264         * Sets the flag that controls whether or not guide lines are drawn for
265         * each data item and sends a {@link RendererChangeEvent} to all registered
266         * listeners.
267         *
268         * @param visible  the new flag value.
269         *
270         * @see #isGuideLinesVisible()
271         */
272        public void setGuideLinesVisible(boolean visible) {
273            this.guideLinesVisible = visible;
274            fireChangeEvent();
275        }
276    
277        /**
278         * Returns the paint used to draw the guide lines.
279         *
280         * @return The paint (never <code>null</code>).
281         *
282         * @see #setGuideLinePaint(Paint)
283         */
284        public Paint getGuideLinePaint() {
285            return this.guideLinePaint;
286        }
287    
288        /**
289         * Sets the paint used to draw the guide lines and sends a
290         * {@link RendererChangeEvent} to all registered listeners.
291         *
292         * @param paint  the paint (<code>null</code> not permitted).
293         *
294         * @see #getGuideLinePaint()
295         */
296        public void setGuideLinePaint(Paint paint) {
297            if (paint == null) {
298                throw new IllegalArgumentException("Null 'paint' argument.");
299            }
300            this.guideLinePaint = paint;
301            fireChangeEvent();
302        }
303    
304        /**
305         * Returns the stroke used to draw the guide lines.
306         *
307         * @return The stroke.
308         *
309         * @see #setGuideLineStroke(Stroke)
310         */
311        public Stroke getGuideLineStroke() {
312            return this.guideLineStroke;
313        }
314    
315        /**
316         * Sets the stroke used to draw the guide lines and sends a
317         * {@link RendererChangeEvent} to all registered listeners.
318         *
319         * @param stroke  the stroke (<code>null</code> not permitted).
320         *
321         * @see #getGuideLineStroke()
322         */
323        public void setGuideLineStroke(Stroke stroke) {
324            if (stroke == null) {
325                throw new IllegalArgumentException("Null 'stroke' argument.");
326            }
327            this.guideLineStroke = stroke;
328            fireChangeEvent();
329        }
330    
331        /**
332         * Returns the lower and upper bounds (range) of the x-values in the
333         * specified dataset.
334         *
335         * @param dataset  the dataset (<code>null</code> permitted).
336         *
337         * @return The range (<code>null</code> if the dataset is <code>null</code>
338         *         or empty).
339         */
340        public Range findDomainBounds(XYDataset dataset) {
341            if (dataset != null) {
342                Range r = DatasetUtilities.findDomainBounds(dataset, false);
343                double offset = 0; // TODO getSeriesShape(n).getBounds().width / 2;
344                return new Range(r.getLowerBound() + offset,
345                                 r.getUpperBound() + offset);
346            }
347            else {
348                return null;
349            }
350        }
351    
352        /**
353         * Returns the range of values the renderer requires to display all the
354         * items from the specified dataset.
355         *
356         * @param dataset  the dataset (<code>null</code> permitted).
357         *
358         * @return The range (<code>null</code> if the dataset is <code>null</code>
359         *         or empty).
360         */
361        public Range findRangeBounds(XYDataset dataset) {
362            if (dataset != null) {
363                Range r = DatasetUtilities.findRangeBounds(dataset, false);
364                double offset = 0; // TODO getSeriesShape(n).getBounds().height / 2;
365                return new Range(r.getLowerBound() + offset, r.getUpperBound()
366                        + offset);
367            }
368            else {
369                return null;
370            }
371        }
372    
373        /**
374         * Returns the number of passes required by this renderer.
375         *
376         * @return <code>2</code>.
377         */
378        public int getPassCount() {
379            return 2;
380        }
381    
382        /**
383         * Draws the block representing the specified item.
384         *
385         * @param g2  the graphics device.
386         * @param state  the state.
387         * @param dataArea  the data area.
388         * @param info  the plot rendering info.
389         * @param plot  the plot.
390         * @param domainAxis  the x-axis.
391         * @param rangeAxis  the y-axis.
392         * @param dataset  the dataset.
393         * @param series  the series index.
394         * @param item  the item index.
395         * @param crosshairState  the crosshair state.
396         * @param pass  the pass index.
397         */
398        public void drawItem(Graphics2D g2, XYItemRendererState state,
399                Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot,
400                ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset,
401                int series, int item, CrosshairState crosshairState, int pass) {
402    
403            Shape hotspot = null;
404            EntityCollection entities = null;
405            if (info != null) {
406                entities = info.getOwner().getEntityCollection();
407            }
408    
409            double x = dataset.getXValue(series, item);
410            double y = dataset.getYValue(series, item);
411            if (Double.isNaN(x) || Double.isNaN(y)) {
412                // can't draw anything
413                return;
414            }
415    
416            double transX = domainAxis.valueToJava2D(x, dataArea,
417                    plot.getDomainAxisEdge());
418            double transY = rangeAxis.valueToJava2D(y, dataArea,
419                    plot.getRangeAxisEdge());
420    
421            PlotOrientation orientation = plot.getOrientation();
422    
423            // draw optional guide lines
424            if ((pass == 0) && this.guideLinesVisible) {
425                g2.setStroke(this.guideLineStroke);
426                g2.setPaint(this.guideLinePaint);
427                if (orientation == PlotOrientation.HORIZONTAL) {
428                    g2.draw(new Line2D.Double(transY, dataArea.getMinY(), transY,
429                            dataArea.getMaxY()));
430                    g2.draw(new Line2D.Double(dataArea.getMinX(), transX,
431                            dataArea.getMaxX(), transX));
432                }
433                else {
434                    g2.draw(new Line2D.Double(transX, dataArea.getMinY(), transX,
435                            dataArea.getMaxY()));
436                    g2.draw(new Line2D.Double(dataArea.getMinX(), transY,
437                            dataArea.getMaxX(), transY));
438                }
439            }
440            else if (pass == 1) {
441                Shape shape = getItemShape(series, item);
442                if (orientation == PlotOrientation.HORIZONTAL) {
443                    shape = ShapeUtilities.createTranslatedShape(shape, transY,
444                            transX);
445                }
446                else if (orientation == PlotOrientation.VERTICAL) {
447                    shape = ShapeUtilities.createTranslatedShape(shape, transX,
448                            transY);
449                }
450                hotspot = shape;
451                if (shape.intersects(dataArea)) {
452                    //if (getItemShapeFilled(series, item)) {
453                        g2.setPaint(getPaint(dataset, series, item));
454                        g2.fill(shape);
455                   //}
456                    if (this.drawOutlines) {
457                        if (getUseOutlinePaint()) {
458                            g2.setPaint(getItemOutlinePaint(series, item));
459                        }
460                        else {
461                            g2.setPaint(getItemPaint(series, item));
462                        }
463                        g2.setStroke(getItemOutlineStroke(series, item));
464                        g2.draw(shape);
465                    }
466                }
467    
468                // add an entity for the item...
469                if (entities != null) {
470                    addEntity(entities, hotspot, dataset, series, item, transX,
471                            transY);
472                }
473            }
474        }
475    
476        /**
477         * Get the paint for a given series and item from a dataset.
478         *
479         * @param dataset  the dataset..
480         * @param series  the series index.
481         * @param item  the item index.
482         *
483         * @return The paint.
484         */
485        protected Paint getPaint(XYDataset dataset, int series, int item) {
486            Paint p = null;
487            if (dataset instanceof XYZDataset) {
488                double z = ((XYZDataset) dataset).getZValue(series, item);
489                p = this.paintScale.getPaint(z);
490            }
491            else {
492                if (this.useFillPaint) {
493                    p = getItemFillPaint(series, item);
494                }
495                else {
496                    p = getItemPaint(series, item);
497                }
498            }
499            return p;
500        }
501    
502        /**
503         * Tests this instance for equality with an arbitrary object.  This method
504         * returns <code>true</code> if and only if:
505         * <ul>
506         * <li><code>obj</code> is an instance of <code>XYShapeRenderer</code> (not
507         *     <code>null</code>);</li>
508         * <li><code>obj</code> has the same field values as this
509         *     <code>XYShapeRenderer</code>;</li>
510         * </ul>
511         *
512         * @param obj  the object (<code>null</code> permitted).
513         *
514         * @return A boolean.
515         */
516        public boolean equals(Object obj) {
517            if (obj == this) {
518                return true;
519            }
520            if (!(obj instanceof XYShapeRenderer)) {
521                return false;
522            }
523            XYShapeRenderer that = (XYShapeRenderer) obj;
524            if ((this.paintScale == null && that.paintScale != null)
525                    || (!this.paintScale.equals(that.paintScale))) {
526                return false;
527            }
528            if (this.drawOutlines != that.drawOutlines) {
529                return false;
530            }
531            if (this.useOutlinePaint != that.useOutlinePaint) {
532                return false;
533            }
534            if (this.useFillPaint != that.useFillPaint) {
535                return false;
536            }
537            if (this.guideLinesVisible != that.guideLinesVisible) {
538                return false;
539            }
540            if ((this.guideLinePaint == null && that.guideLinePaint != null)
541                    || (!this.guideLinePaint.equals(that.guideLinePaint)))
542                return false;
543            if ((this.guideLineStroke == null && that.guideLineStroke != null)
544                    || (!this.guideLineStroke.equals(that.guideLineStroke)))
545                return false;
546    
547            return super.equals(obj);
548        }
549    
550        /**
551         * Returns a clone of this renderer.
552         *
553         * @return A clone of this renderer.
554         *
555         * @throws CloneNotSupportedException if there is a problem creating the
556         *     clone.
557         */
558        public Object clone() throws CloneNotSupportedException {
559            XYShapeRenderer clone = (XYShapeRenderer) super.clone();
560            if (this.paintScale instanceof PublicCloneable) {
561                PublicCloneable pc = (PublicCloneable) this.paintScale;
562                clone.paintScale = (PaintScale) pc.clone();
563            }
564            return clone;
565        }
566    
567        /**
568         * Provides serialization support.
569         *
570         * @param stream  the input stream.
571         *
572         * @throws IOException  if there is an I/O error.
573         * @throws ClassNotFoundException  if there is a classpath problem.
574         */
575        private void readObject(ObjectInputStream stream)
576                throws IOException, ClassNotFoundException {
577            stream.defaultReadObject();
578            this.guideLinePaint = SerialUtilities.readPaint(stream);
579            this.guideLineStroke = SerialUtilities.readStroke(stream);
580        }
581    
582        /**
583         * Provides serialization support.
584         *
585         * @param stream  the output stream.
586         *
587         * @throws IOException  if there is an I/O error.
588         */
589        private void writeObject(ObjectOutputStream stream) throws IOException {
590            stream.defaultWriteObject();
591            SerialUtilities.writePaint(this.guideLinePaint, stream);
592            SerialUtilities.writeStroke(this.guideLineStroke, stream);
593        }
594    
595    }