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     * XYAreaRenderer.java
029     * -------------------
030     * (C) Copyright 2002-2005, by Hari and Contributors.
031     *
032     * Original Author:  Hari (ourhari@hotmail.com);
033     * Contributor(s):   David Gilbert (for Object Refinery Limited);
034     *                   Richard Atkinson;
035     *                   Christian W. Zuckschwerdt;
036     *
037     * $Id: XYAreaRenderer.java,v 1.12.2.4 2005/12/02 11:59:43 mungady Exp $
038     *
039     * Changes:
040     * --------
041     * 03-Apr-2002 : Version 1, contributed by Hari.  This class is based on the 
042     *               StandardXYItemRenderer class (DG);
043     * 09-Apr-2002 : Removed the translated zero from the drawItem method - 
044     *               overridden the initialise() method to calculate it (DG);
045     * 30-May-2002 : Added tool tip generator to constructor to match super 
046     *               class (DG);
047     * 25-Jun-2002 : Removed unnecessary local variable (DG);
048     * 05-Aug-2002 : Small modification to drawItem method to support URLs for HTML
049     *               image maps (RA);
050     * 01-Oct-2002 : Fixed errors reported by Checkstyle (DG);
051     * 07-Nov-2002 : Renamed AreaXYItemRenderer --> XYAreaRenderer (DG);
052     * 25-Mar-2003 : Implemented Serializable (DG);
053     * 01-May-2003 : Modified drawItem() method signature (DG);
054     * 27-Jul-2003 : Made line and polygon properties protected rather than 
055     *               private (RA);
056     * 30-Jul-2003 : Modified entity constructor (CZ);
057     * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG);
058     * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
059     * 07-Oct-2003 : Added renderer state (DG);
060     * 08-Dec-2003 : Modified hotspot for chart entity (DG);
061     * 10-Feb-2004 : Changed the drawItem() method to make cut-and-paste overriding
062     *               easier.  Also moved state class into this class (DG);
063     * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState.  Renamed 
064     *               XYToolTipGenerator --> XYItemLabelGenerator (DG);
065     * 15-Jul-2004 : Switched getX() with getXValue() and getY() with 
066     *               getYValue() (DG);
067     * 11-Nov-2004 : Now uses ShapeUtilities to translate shapes (DG);
068     * 19-Jan-2005 : Now accesses primitives only from dataset (DG);
069     * 21-Mar-2005 : Override getLegendItem() and equals() methods (DG);
070     * 20-Apr-2005 : Use generators for legend tooltips and URLs (DG);
071     * 
072     */
073    
074    package org.jfree.chart.renderer.xy;
075    
076    import java.awt.Graphics2D;
077    import java.awt.Paint;
078    import java.awt.Polygon;
079    import java.awt.Shape;
080    import java.awt.Stroke;
081    import java.awt.geom.GeneralPath;
082    import java.awt.geom.Line2D;
083    import java.awt.geom.Rectangle2D;
084    import java.io.IOException;
085    import java.io.ObjectInputStream;
086    import java.io.ObjectOutputStream;
087    import java.io.Serializable;
088    
089    import org.jfree.chart.LegendItem;
090    import org.jfree.chart.axis.ValueAxis;
091    import org.jfree.chart.entity.EntityCollection;
092    import org.jfree.chart.entity.XYItemEntity;
093    import org.jfree.chart.event.RendererChangeEvent;
094    import org.jfree.chart.labels.XYSeriesLabelGenerator;
095    import org.jfree.chart.labels.XYToolTipGenerator;
096    import org.jfree.chart.plot.CrosshairState;
097    import org.jfree.chart.plot.PlotOrientation;
098    import org.jfree.chart.plot.PlotRenderingInfo;
099    import org.jfree.chart.plot.XYPlot;
100    import org.jfree.chart.urls.XYURLGenerator;
101    import org.jfree.data.xy.XYDataset;
102    import org.jfree.io.SerialUtilities;
103    import org.jfree.util.PublicCloneable;
104    import org.jfree.util.ShapeUtilities;
105    
106    /**
107     * Area item renderer for an {@link XYPlot}.  This class can draw (a) shapes at
108     * each point, or (b) lines between points, or (c) both shapes and lines, 
109     * or (d) filled areas, or (e) filled areas and shapes.
110     */
111    public class XYAreaRenderer extends AbstractXYItemRenderer 
112                                implements XYItemRenderer, 
113                                           Cloneable,
114                                           PublicCloneable,
115                                           Serializable {
116    
117        /** For serialization. */
118        private static final long serialVersionUID = -4481971353973876747L;
119        
120        /**
121         * A state object used by this renderer.
122         */
123        static class XYAreaRendererState extends XYItemRendererState {
124            
125            /** Working storage for the area under one series. */
126            public Polygon area;
127            
128            /** Working line that can be recycled. */
129            public Line2D line;
130            
131            /**
132             * Creates a new state.
133             * 
134             * @param info  the plot rendering info.
135             */
136            public XYAreaRendererState(PlotRenderingInfo info) {
137                super(info);
138                this.area = new Polygon();
139                this.line = new Line2D.Double();
140            }
141            
142        }
143        
144        /** Useful constant for specifying the type of rendering (shapes only). */
145        public static final int SHAPES = 1;
146    
147        /** Useful constant for specifying the type of rendering (lines only). */
148        public static final int LINES = 2;
149    
150        /** 
151         * Useful constant for specifying the type of rendering (shapes and lines).
152         */
153        public static final int SHAPES_AND_LINES = 3;
154    
155        /** Useful constant for specifying the type of rendering (area only). */
156        public static final int AREA = 4;
157    
158        /** 
159         * Useful constant for specifying the type of rendering (area and shapes). 
160         */
161        public static final int AREA_AND_SHAPES = 5;
162    
163        /** A flag indicating whether or not shapes are drawn at each XY point. */
164        private boolean plotShapes;
165    
166        /** A flag indicating whether or not lines are drawn between XY points. */
167        private boolean plotLines;
168    
169        /** A flag indicating whether or not Area are drawn at each XY point. */
170        private boolean plotArea;
171    
172        /** A flag that controls whether or not the outline is shown. */
173        private boolean showOutline;
174    
175        /** 
176         * The shape used to represent an area in each legend item (this should 
177         * never be <code>null</code>). 
178         */
179        private transient Shape legendArea;
180    
181        /**
182         * Constructs a new renderer.
183         */
184        public XYAreaRenderer() {
185            this(AREA);
186        }
187    
188        /**
189         * Constructs a new renderer.
190         *
191         * @param type  the type of the renderer.
192         */
193        public XYAreaRenderer(int type) {
194            this(type, null, null);
195        }
196    
197        /**
198         * Constructs a new renderer.
199         * <p>
200         * To specify the type of renderer, use one of the constants: SHAPES, LINES,
201         * SHAPES_AND_LINES, AREA or AREA_AND_SHAPES.
202         *
203         * @param type  the type of renderer.
204         * @param toolTipGenerator  the tool tip generator to use 
205         *                          (<code>null</code> permitted).
206         * @param urlGenerator  the URL generator (<code>null</code> permitted).
207         */
208        public XYAreaRenderer(int type, XYToolTipGenerator toolTipGenerator, 
209                              XYURLGenerator urlGenerator) {
210    
211            super();
212            setBaseToolTipGenerator(toolTipGenerator);
213            setURLGenerator(urlGenerator);
214    
215            if (type == SHAPES) {
216                this.plotShapes = true;
217            }
218            if (type == LINES) {
219                this.plotLines = true;
220            }
221            if (type == SHAPES_AND_LINES) {
222                this.plotShapes = true;
223                this.plotLines = true;
224            }
225            if (type == AREA) {
226                this.plotArea = true;
227            }
228            if (type == AREA_AND_SHAPES) {
229                this.plotArea = true;
230                this.plotShapes = true;
231            }
232            this.showOutline = false;
233            GeneralPath area = new GeneralPath();
234            area.moveTo(0.0f, -4.0f);
235            area.lineTo(3.0f, -2.0f);
236            area.lineTo(4.0f, 4.0f);
237            area.lineTo(-4.0f, 4.0f);
238            area.lineTo(-3.0f, -2.0f);
239            area.closePath();
240            this.legendArea = area;
241    
242        }
243    
244        /**
245         * Returns a flag that controls whether or not outlines of the areas are 
246         * drawn.
247         *
248         * @return The flag.
249         */
250        public boolean isOutline() {
251            return this.showOutline;
252        }
253    
254        /**
255         * Sets a flag that controls whether or not outlines of the areas are drawn.
256         *
257         * @param show  the flag.
258         */
259        public void setOutline(boolean show) {
260            this.showOutline = show;
261        }
262    
263        /**
264         * Returns true if shapes are being plotted by the renderer.
265         *
266         * @return <code>true</code> if shapes are being plotted by the renderer.
267         */
268        public boolean getPlotShapes() {
269            return this.plotShapes;
270        }
271    
272        /**
273         * Returns true if lines are being plotted by the renderer.
274         *
275         * @return <code>true</code> if lines are being plotted by the renderer.
276         */
277        public boolean getPlotLines() {
278            return this.plotLines;
279        }
280    
281        /**
282         * Returns true if Area is being plotted by the renderer.
283         *
284         * @return <code>true</code> if Area is being plotted by the renderer.
285         */
286        public boolean getPlotArea() {
287            return this.plotArea;
288        }
289    
290        /**
291         * Returns the shape used to represent an area in the legend.
292         * 
293         * @return The legend area (never <code>null</code>).
294         */
295        public Shape getLegendArea() {
296            return this.legendArea;   
297        }
298        
299        /**
300         * Sets the shape used as an area in each legend item and sends a 
301         * {@link RendererChangeEvent} to all registered listeners.
302         * 
303         * @param area  the area (<code>null</code> not permitted).
304         */
305        public void setLegendArea(Shape area) {
306            if (area == null) {
307                throw new IllegalArgumentException("Null 'area' argument.");   
308            }
309            this.legendArea = area;
310            notifyListeners(new RendererChangeEvent(this));
311        }
312    
313        /**
314         * Initialises the renderer and returns a state object that should be 
315         * passed to all subsequent calls to the drawItem() method.
316         *
317         * @param g2  the graphics device.
318         * @param dataArea  the area inside the axes.
319         * @param plot  the plot.
320         * @param data  the data.
321         * @param info  an optional info collection object to return data back to 
322         *              the caller.
323         *
324         * @return A state object for use by the renderer.
325         */
326        public XYItemRendererState initialise(Graphics2D g2,
327                                              Rectangle2D dataArea,
328                                              XYPlot plot,
329                                              XYDataset data,
330                                              PlotRenderingInfo info) {
331            XYAreaRendererState state = new XYAreaRendererState(info);
332            return state;
333        }
334    
335        /**
336         * Returns a default legend item for the specified series.  Subclasses 
337         * should override this method to generate customised items.
338         *
339         * @param datasetIndex  the dataset index (zero-based).
340         * @param series  the series index (zero-based).
341         *
342         * @return A legend item for the series.
343         */
344        public LegendItem getLegendItem(int datasetIndex, int series) {
345            LegendItem result = null;
346            XYPlot xyplot = getPlot();
347            if (xyplot != null) {
348                XYDataset dataset = xyplot.getDataset(datasetIndex);
349                if (dataset != null) {
350                    XYSeriesLabelGenerator lg = getLegendItemLabelGenerator();
351                    String label = lg.generateLabel(dataset, series);
352                    String description = label;
353                    String toolTipText = null;
354                    if (getLegendItemToolTipGenerator() != null) {
355                        toolTipText = getLegendItemToolTipGenerator().generateLabel(
356                            dataset, series
357                        );
358                    }
359                    String urlText = null;
360                    if (getLegendItemURLGenerator() != null) {
361                        urlText = getLegendItemURLGenerator().generateLabel(
362                            dataset, series
363                        );
364                    }
365                    Paint paint = getSeriesPaint(series);
366                    result = new LegendItem(label, description, toolTipText, 
367                            urlText, this.legendArea, paint);
368                }
369            }
370            return result;
371        }
372    
373        /**
374         * Draws the visual representation of a single data item.
375         *
376         * @param g2  the graphics device.
377         * @param state  the renderer state.
378         * @param dataArea  the area within which the data is being drawn.
379         * @param info  collects information about the drawing.
380         * @param plot  the plot (can be used to obtain standard color information 
381         *              etc).
382         * @param domainAxis  the domain axis.
383         * @param rangeAxis  the range axis.
384         * @param dataset  the dataset.
385         * @param series  the series index (zero-based).
386         * @param item  the item index (zero-based).
387         * @param crosshairState  crosshair information for the plot 
388         *                        (<code>null</code> permitted).
389         * @param pass  the pass index.
390         */
391        public void drawItem(Graphics2D g2,
392                             XYItemRendererState state,
393                             Rectangle2D dataArea,
394                             PlotRenderingInfo info,
395                             XYPlot plot,
396                             ValueAxis domainAxis,
397                             ValueAxis rangeAxis,
398                             XYDataset dataset,
399                             int series,
400                             int item,
401                             CrosshairState crosshairState,
402                             int pass) {
403            
404            if (!getItemVisible(series, item)) {
405                return;   
406            }
407            XYAreaRendererState areaState = (XYAreaRendererState) state;
408            
409            // get the data point...
410            double x1 = dataset.getXValue(series, item);
411            double y1 = dataset.getYValue(series, item);
412            if (Double.isNaN(y1)) {
413                y1 = 0.0;
414            }
415            double transX1 = domainAxis.valueToJava2D(
416                x1, dataArea, plot.getDomainAxisEdge()
417            );
418            double transY1 = rangeAxis.valueToJava2D(
419                y1, dataArea, plot.getRangeAxisEdge()
420            );
421            
422            // get the previous point and the next point so we can calculate a 
423            // "hot spot" for the area (used by the chart entity)...
424            int itemCount = dataset.getItemCount(series);
425            double x0 = dataset.getXValue(series, Math.max(item - 1, 0));
426            double y0 = dataset.getYValue(series, Math.max(item - 1, 0));
427            if (Double.isNaN(y0)) {
428                y0 = 0.0;
429            }
430            double transX0 = domainAxis.valueToJava2D(
431                x0, dataArea, plot.getDomainAxisEdge()
432            );
433            double transY0 = rangeAxis.valueToJava2D(
434                y0, dataArea, plot.getRangeAxisEdge()
435            );
436            
437            double x2 = dataset.getXValue(
438                series, Math.min(item + 1, itemCount - 1)
439            );
440            double y2 = dataset.getYValue(
441                series, Math.min(item + 1, itemCount - 1)
442            );
443            if (Double.isNaN(y2)) {
444                y2 = 0.0;
445            }
446            double transX2 = domainAxis.valueToJava2D(
447                x2, dataArea, plot.getDomainAxisEdge()
448            );
449            double transY2 = rangeAxis.valueToJava2D(
450                y2, dataArea, plot.getRangeAxisEdge()
451            );
452            
453            double transZero = rangeAxis.valueToJava2D(
454                0.0, dataArea, plot.getRangeAxisEdge()
455            );
456            Polygon hotspot = null;
457            if (plot.getOrientation() == PlotOrientation.HORIZONTAL) {
458                hotspot = new Polygon();
459                hotspot.addPoint(
460                    (int) transZero, (int) ((transX0 + transX1) / 2.0)
461                );
462                hotspot.addPoint(
463                    (int) ((transY0 + transY1) / 2.0), 
464                    (int) ((transX0 + transX1) / 2.0)
465                );
466                hotspot.addPoint((int) transY1, (int) transX1);
467                hotspot.addPoint(
468                    (int) ((transY1 + transY2) / 2.0), 
469                    (int) ((transX1 + transX2) / 2.0)
470                );
471                hotspot.addPoint(
472                    (int) transZero, (int) ((transX1 + transX2) / 2.0)
473                );
474            }
475            else {  // vertical orientation
476                hotspot = new Polygon();
477                hotspot.addPoint(
478                    (int) ((transX0 + transX1) / 2.0), (int) transZero
479                );
480                hotspot.addPoint(
481                    (int) ((transX0 + transX1) / 2.0), 
482                    (int) ((transY0 + transY1) / 2.0)
483                );
484                hotspot.addPoint((int) transX1, (int) transY1);
485                hotspot.addPoint(
486                    (int) ((transX1 + transX2) / 2.0), 
487                    (int) ((transY1 + transY2) / 2.0)
488                );
489                hotspot.addPoint(
490                    (int) ((transX1 + transX2) / 2.0), (int) transZero
491                );
492            }
493            
494            if (item == 0) {  // create a new area polygon for the series
495                areaState.area = new Polygon();
496                // the first point is (x, 0)
497                double zero = rangeAxis.valueToJava2D(
498                    0.0, dataArea, plot.getRangeAxisEdge()
499                );
500                if (plot.getOrientation() == PlotOrientation.VERTICAL) {
501                    areaState.area.addPoint((int) transX1, (int) zero);
502                }
503                else if (plot.getOrientation() == PlotOrientation.HORIZONTAL) {
504                    areaState.area.addPoint((int) zero, (int) transX1);
505                }
506            }
507    
508            // Add each point to Area (x, y)
509            if (plot.getOrientation() == PlotOrientation.VERTICAL) {
510                areaState.area.addPoint((int) transX1, (int) transY1);
511            }
512            else if (plot.getOrientation() == PlotOrientation.HORIZONTAL) {
513                areaState.area.addPoint((int) transY1, (int) transX1);
514            }
515            
516            PlotOrientation orientation = plot.getOrientation();
517            Paint paint = getItemPaint(series, item);
518            Stroke stroke = getItemStroke(series, item);
519            g2.setPaint(paint);
520            g2.setStroke(stroke);
521            
522            Shape shape = null;
523            if (getPlotShapes()) {
524                shape = getItemShape(series, item);
525                if (orientation == PlotOrientation.VERTICAL) {
526                    shape = ShapeUtilities.createTranslatedShape(
527                        shape, transX1, transY1
528                    );
529                }
530                else if (orientation == PlotOrientation.HORIZONTAL) {
531                    shape = ShapeUtilities.createTranslatedShape(
532                        shape, transY1, transX1
533                    );
534                }
535                g2.draw(shape);
536            }
537    
538            if (getPlotLines()) {
539                if (item > 0) {
540                    if (plot.getOrientation() == PlotOrientation.VERTICAL) {
541                        areaState.line.setLine(transX0, transY0, transX1, transY1);
542                    }
543                    else if (plot.getOrientation() == PlotOrientation.HORIZONTAL) {
544                        areaState.line.setLine(transY0, transX0, transY1, transX1);
545                    }
546                    g2.draw(areaState.line);
547                }
548            }
549    
550            // Check if the item is the last item for the series.
551            // and number of items > 0.  We can't draw an area for a single point.
552            if (getPlotArea() && item > 0 && item == (itemCount - 1)) {
553    
554                if (orientation == PlotOrientation.VERTICAL) {
555                    // Add the last point (x,0)
556                    areaState.area.addPoint((int) transX1, (int) transZero);
557                }
558                else if (orientation == PlotOrientation.HORIZONTAL) {
559                    // Add the last point (x,0)
560                    areaState.area.addPoint((int) transZero, (int) transX1);
561                }
562    
563                g2.fill(areaState.area);
564    
565                // draw an outline around the Area.
566                if (isOutline()) {
567                    g2.setStroke(getItemOutlineStroke(series, item));
568                    g2.setPaint(getItemOutlinePaint(series, item));
569                    g2.draw(areaState.area);
570                }
571            }
572    
573            updateCrosshairValues(
574                crosshairState, x1, y1, transX1, transY1, orientation
575            );
576            
577            // collect entity and tool tip information...
578            if (state.getInfo() != null) {
579                EntityCollection entities = state.getEntityCollection();
580                if (entities != null && hotspot != null) {
581                    String tip = null;
582                    XYToolTipGenerator generator 
583                        = getToolTipGenerator(series, item);
584                    if (generator != null) {
585                        tip = generator.generateToolTip(dataset, series, item);
586                    }
587                    String url = null;
588                    if (getURLGenerator() != null) {
589                        url = getURLGenerator().generateURL(dataset, series, item);
590                    }
591                    XYItemEntity entity = new XYItemEntity(
592                        hotspot, dataset, series, item, tip, url
593                    );
594                    entities.add(entity);
595                }
596            }
597    
598        }
599    
600        /**
601         * Returns a clone of the renderer.
602         * 
603         * @return A clone.
604         * 
605         * @throws CloneNotSupportedException  if the renderer cannot be cloned.
606         */
607        public Object clone() throws CloneNotSupportedException {
608            return super.clone();
609        }
610        
611        /**
612         * Tests this renderer for equality with an arbitrary object.
613         * 
614         * @param obj  the object (<code>null</code> permitted).
615         * 
616         * @return A boolean.
617         */
618        public boolean equals(Object obj) {
619            if (obj == this) {
620                return true;   
621            }
622            if (!(obj instanceof XYAreaRenderer)) {
623                return false;   
624            }
625            XYAreaRenderer that = (XYAreaRenderer) obj;
626            if (this.plotArea != that.plotArea) {
627                return false;   
628            }
629            if (this.plotLines != that.plotLines) {
630                return false;   
631            }
632            if (this.plotShapes != that.plotShapes) {
633                return false;   
634            }
635            if (this.showOutline != that.showOutline) {
636                return false;   
637            }
638            if (!ShapeUtilities.equal(this.legendArea, that.legendArea)) {
639                return false;   
640            }
641            return true;
642        }
643        
644        /**
645         * Provides serialization support.
646         *
647         * @param stream  the input stream.
648         *
649         * @throws IOException  if there is an I/O error.
650         * @throws ClassNotFoundException  if there is a classpath problem.
651         */
652        private void readObject(ObjectInputStream stream) 
653                throws IOException, ClassNotFoundException {
654            stream.defaultReadObject();
655            this.legendArea = SerialUtilities.readShape(stream);
656        }
657        
658        /**
659         * Provides serialization support.
660         *
661         * @param stream  the output stream.
662         *
663         * @throws IOException  if there is an I/O error.
664         */
665        private void writeObject(ObjectOutputStream stream) throws IOException {
666            stream.defaultWriteObject();
667            SerialUtilities.writeShape(this.legendArea, stream);
668        }
669    }
670