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     * XYAreaRenderer2.java
029     * --------------------
030     * (C) Copyright 2004, 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: XYAreaRenderer2.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 
049     *               HTML 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 
062     *               overriding easier.  Also moved state class into this 
063     *               class (DG);
064     * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState.  Renamed 
065     *               XYToolTipGenerator --> XYItemLabelGenerator (DG);
066     * 15-Jul-2004 : Switched getX() with getXValue() and getY() with 
067     *               getYValue() (DG);
068     * 11-Nov-2004 : Now uses ShapeUtilities to translate shapes (DG);
069     * 19-Jan-2005 : Now accesses only primitives from the dataset (DG);
070     * 21-Mar-2005 : Override getLegendItem() (DG);
071     * 20-Apr-2005 : Use generators for legend tooltips and URLs (DG);
072     * 
073     */
074    
075    package org.jfree.chart.renderer.xy;
076    
077    import java.awt.Graphics2D;
078    import java.awt.Paint;
079    import java.awt.Polygon;
080    import java.awt.Shape;
081    import java.awt.Stroke;
082    import java.awt.geom.GeneralPath;
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    
105    /**
106     * Area item renderer for an {@link XYPlot}.  
107     */
108    public class XYAreaRenderer2 extends AbstractXYItemRenderer 
109                                 implements XYItemRenderer, 
110                                            Cloneable,
111                                            PublicCloneable,
112                                            Serializable {
113    
114        /** For serialization. */
115        private static final long serialVersionUID = -7378069681579984133L;
116    
117        /** A flag indicating whether or not lines are drawn between XY points. */
118        private boolean plotLines;
119    
120        /** A flag that controls whether or not the outline is shown. */
121        private boolean showOutline;
122    
123        /** 
124         * The shape used to represent an area in each legend item (this should 
125         * never be <code>null</code>). 
126         */
127        private transient Shape legendArea;
128    
129        /**
130         * Constructs a new renderer.
131         */
132        public XYAreaRenderer2() {
133            this(null, null);
134        }
135    
136        /**
137         * Constructs a new renderer.
138         * <p>
139         * To specify the type of renderer, use one of the constants: SHAPES, LINES,
140         * SHAPES_AND_LINES, AREA or AREA_AND_SHAPES.
141         *
142         * @param labelGenerator  the tool tip generator to use.  <code>null</code> 
143         *                        is none.
144         * @param urlGenerator  the URL generator (null permitted).
145         */
146        public XYAreaRenderer2(XYToolTipGenerator labelGenerator, 
147                               XYURLGenerator urlGenerator) {
148            super();
149            this.plotLines = false;
150            this.showOutline = false;
151            setBaseToolTipGenerator(labelGenerator);
152            setURLGenerator(urlGenerator);
153            GeneralPath area = new GeneralPath();
154            area.moveTo(0.0f, -4.0f);
155            area.lineTo(3.0f, -2.0f);
156            area.lineTo(4.0f, 4.0f);
157            area.lineTo(-4.0f, 4.0f);
158            area.lineTo(-3.0f, -2.0f);
159            area.closePath();
160            this.legendArea = area;
161        }
162    
163        /**
164         * Returns a flag that controls whether or not outlines of the areas are 
165         * drawn.
166         *
167         * @return The flag.
168         */
169        public boolean isOutline() {
170            return this.showOutline;
171        }
172    
173        /**
174         * Sets a flag that controls whether or not outlines of the areas are drawn.
175         *
176         * @param show  the flag.
177         */
178        public void setOutline(boolean show) {
179            this.showOutline = show;
180        }
181    
182        /**
183         * Returns true if lines are being plotted by the renderer.
184         *
185         * @return <code>true</code> if lines are being plotted by the renderer.
186         */
187        public boolean getPlotLines() {
188            return this.plotLines;
189        }
190    
191        /**
192         * Returns the shape used to represent an area in the legend.
193         * 
194         * @return The legend area (never <code>null</code>).
195         */
196        public Shape getLegendArea() {
197            return this.legendArea;   
198        }
199        
200        /**
201         * Sets the shape used as an area in each legend item and sends a 
202         * {@link RendererChangeEvent} to all registered listeners.
203         * 
204         * @param area  the area (<code>null</code> not permitted).
205         */
206        public void setLegendArea(Shape area) {
207            if (area == null) {
208                throw new IllegalArgumentException("Null 'area' argument.");   
209            }
210            this.legendArea = area;
211            notifyListeners(new RendererChangeEvent(this));
212        }
213    
214        /**
215         * Returns a default legend item for the specified series.  Subclasses 
216         * should override this method to generate customised items.
217         *
218         * @param datasetIndex  the dataset index (zero-based).
219         * @param series  the series index (zero-based).
220         *
221         * @return A legend item for the series.
222         */
223        public LegendItem getLegendItem(int datasetIndex, int series) {
224            LegendItem result = null;
225            XYPlot xyplot = getPlot();
226            if (xyplot != null) {
227                XYDataset dataset = xyplot.getDataset(datasetIndex);
228                if (dataset != null) {
229                    XYSeriesLabelGenerator lg = getLegendItemLabelGenerator();
230                    String label = lg.generateLabel(dataset, series);
231                    String description = label;
232                    String toolTipText = null;
233                    if (getLegendItemToolTipGenerator() != null) {
234                        toolTipText = getLegendItemToolTipGenerator().generateLabel(
235                            dataset, series
236                        );
237                    }
238                    String urlText = null;
239                    if (getLegendItemURLGenerator() != null) {
240                        urlText = getLegendItemURLGenerator().generateLabel(
241                            dataset, series
242                        );
243                    }
244                    Paint paint = getSeriesPaint(series);
245                    result = new LegendItem(label, description, toolTipText, 
246                            urlText, this.legendArea, paint);
247                }
248            }
249            return result;
250        }
251        
252        /**
253         * Draws the visual representation of a single data item.
254         *
255         * @param g2  the graphics device.
256         * @param state  the renderer state.
257         * @param dataArea  the area within which the data is being drawn.
258         * @param info  collects information about the drawing.
259         * @param plot  the plot (can be used to obtain standard color 
260         *              information etc).
261         * @param domainAxis  the domain axis.
262         * @param rangeAxis  the range axis.
263         * @param dataset  the dataset.
264         * @param series  the series index (zero-based).
265         * @param item  the item index (zero-based).
266         * @param crosshairState  crosshair information for the plot 
267         *                        (<code>null</code> permitted).
268         * @param pass  the pass index.
269         */
270        public void drawItem(Graphics2D g2,
271                             XYItemRendererState state,
272                             Rectangle2D dataArea,
273                             PlotRenderingInfo info,
274                             XYPlot plot,
275                             ValueAxis domainAxis,
276                             ValueAxis rangeAxis,
277                             XYDataset dataset,
278                             int series,
279                             int item,
280                             CrosshairState crosshairState,
281                             int pass) {
282            
283            if (!getItemVisible(series, item)) {
284                return;   
285            }
286            // get the data point...
287            double x1 = dataset.getXValue(series, item);
288            double y1 = dataset.getYValue(series, item);
289            if (Double.isNaN(y1)) {
290                y1 = 0.0;
291            }
292            
293            double transX1 = domainAxis.valueToJava2D(
294                x1, dataArea, plot.getDomainAxisEdge()
295            );
296            double transY1 = rangeAxis.valueToJava2D(
297                y1, dataArea, plot.getRangeAxisEdge()
298            );
299            
300            // get the previous point and the next point so we can calculate a 
301            // "hot spot" for the area (used by the chart entity)...
302            double x0 = dataset.getXValue(series, Math.max(item - 1, 0));
303            double y0 = dataset.getYValue(series, Math.max(item - 1, 0));
304            if (Double.isNaN(y0)) {
305                y0 = 0.0;
306            }
307            double transX0 = domainAxis.valueToJava2D(
308                x0, dataArea, plot.getDomainAxisEdge()
309            );
310            double transY0 = rangeAxis.valueToJava2D(
311                y0, dataArea, plot.getRangeAxisEdge()
312            );
313            
314            int itemCount = dataset.getItemCount(series);
315            double x2 = dataset.getXValue(
316                series, Math.min(item + 1, itemCount - 1)
317            );
318            double y2 = dataset.getYValue(
319                series, Math.min(item + 1, itemCount - 1)
320            );
321            if (Double.isNaN(y2)) {
322                y2 = 0.0;
323            }
324            double transX2 = domainAxis.valueToJava2D(
325                x2, dataArea, plot.getDomainAxisEdge()
326            );
327            double transY2 = rangeAxis.valueToJava2D(
328                y2, dataArea, plot.getRangeAxisEdge()
329            );
330            
331            double transZero = rangeAxis.valueToJava2D(
332                0.0, dataArea, plot.getRangeAxisEdge()
333            );
334            Polygon hotspot = null;
335            if (plot.getOrientation() == PlotOrientation.HORIZONTAL) {
336                hotspot = new Polygon();
337                hotspot.addPoint(
338                    (int) transZero, (int) ((transX0 + transX1) / 2.0)
339                );
340                hotspot.addPoint(
341                    (int) ((transY0 + transY1) / 2.0), 
342                    (int) ((transX0 + transX1) / 2.0)
343                );
344                hotspot.addPoint((int) transY1, (int) transX1);
345                hotspot.addPoint(
346                    (int) ((transY1 + transY2) / 2.0), 
347                    (int) ((transX1 + transX2) / 2.0)
348                );
349                hotspot.addPoint(
350                    (int) transZero, (int) ((transX1 + transX2) / 2.0)
351                );
352            }
353            else {  // vertical orientation
354                hotspot = new Polygon();
355                hotspot.addPoint(
356                    (int) ((transX0 + transX1) / 2.0), (int) transZero
357                );
358                hotspot.addPoint(
359                    (int) ((transX0 + transX1) / 2.0), 
360                    (int) ((transY0 + transY1) / 2.0)
361                );
362                hotspot.addPoint((int) transX1, (int) transY1);
363                hotspot.addPoint(
364                    (int) ((transX1 + transX2) / 2.0), 
365                    (int) ((transY1 + transY2) / 2.0)
366                );
367                hotspot.addPoint(
368                    (int) ((transX1 + transX2) / 2.0), (int) transZero
369                );
370            }
371                    
372            PlotOrientation orientation = plot.getOrientation();
373            Paint paint = getItemPaint(series, item);
374            Stroke stroke = getItemStroke(series, item);
375            g2.setPaint(paint);
376            g2.setStroke(stroke);
377    
378            if (getPlotLines()) {
379                if (item > 0) {
380                    if (plot.getOrientation() == PlotOrientation.VERTICAL) {
381                        state.workingLine.setLine(
382                            transX0, transY0, transX1, transY1
383                        );
384                    }
385                    else if (plot.getOrientation() == PlotOrientation.HORIZONTAL) {
386                        state.workingLine.setLine(
387                            transY0, transX0, transY1, transX1
388                        );
389                    }
390                    g2.draw(state.workingLine);
391                }
392            }
393    
394            // Check if the item is the last item for the series.
395            // and number of items > 0.  We can't draw an area for a single point.
396            g2.fill(hotspot);
397    
398            // draw an outline around the Area.
399            if (isOutline()) {
400                g2.setStroke(getSeriesOutlineStroke(series));
401                g2.setPaint(getSeriesOutlinePaint(series));
402                g2.draw(hotspot);
403            }
404            updateCrosshairValues(
405                crosshairState, x1, y1, transX1, transY1, orientation
406            );
407            
408            // collect entity and tool tip information...
409            if (state.getInfo() != null) {
410                EntityCollection entities = state.getEntityCollection();
411                if (entities != null && hotspot != null) {
412                    String tip = null;
413                    XYToolTipGenerator generator = getToolTipGenerator(
414                        series, item
415                    );
416                    if (generator != null) {
417                        tip = generator.generateToolTip(dataset, series, item);
418                    }
419                    String url = null;
420                    if (getURLGenerator() != null) {
421                        url = getURLGenerator().generateURL(dataset, series, item);
422                    }
423                    XYItemEntity entity = new XYItemEntity(
424                        hotspot, dataset, series, item, tip, url
425                    );
426                    entities.add(entity);
427                }
428            }
429    
430        }
431    
432        /**
433         * Returns a clone of the renderer.
434         * 
435         * @return A clone.
436         * 
437         * @throws CloneNotSupportedException  if the renderer cannot be cloned.
438         */
439        public Object clone() throws CloneNotSupportedException {
440            return super.clone();
441        }
442        
443        /**
444         * Provides serialization support.
445         *
446         * @param stream  the input stream.
447         *
448         * @throws IOException  if there is an I/O error.
449         * @throws ClassNotFoundException  if there is a classpath problem.
450         */
451        private void readObject(ObjectInputStream stream) 
452                throws IOException, ClassNotFoundException {
453            stream.defaultReadObject();
454            this.legendArea = SerialUtilities.readShape(stream);
455        }
456        
457        /**
458         * Provides serialization support.
459         *
460         * @param stream  the output stream.
461         *
462         * @throws IOException  if there is an I/O error.
463         */
464        private void writeObject(ObjectOutputStream stream) throws IOException {
465            stream.defaultWriteObject();
466            SerialUtilities.writeShape(this.legendArea, stream);
467        }
468    
469    }
470