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     * YIntervalRenderer.java
029     * ----------------------
030     * (C) Copyright 2002-2008, by Object Refinery Limited.
031     *
032     * Original Author:  David Gilbert (for Object Refinery Limited);
033     * Contributor(s):   -;
034     *
035     * Changes
036     * -------
037     * 05-Nov-2002 : Version 1 (DG);
038     * 25-Mar-2003 : Implemented Serializable (DG);
039     * 01-May-2003 : Modified drawItem() method signature (DG);
040     * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG);
041     * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
042     * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState (DG);
043     * 27-Sep-2004 : Access double values from dataset (DG);
044     * 11-Nov-2004 : Now uses ShapeUtilities to translate shapes (DG);
045     * 11-Apr-2008 : New override for findRangeBounds() (DG);
046     * 26-May-2008 : Added item label support (DG);
047     *
048     */
049    
050    package org.jfree.chart.renderer.xy;
051    
052    import java.awt.Font;
053    import java.awt.Graphics2D;
054    import java.awt.Paint;
055    import java.awt.Shape;
056    import java.awt.Stroke;
057    import java.awt.geom.Line2D;
058    import java.awt.geom.Point2D;
059    import java.awt.geom.Rectangle2D;
060    import java.io.Serializable;
061    
062    import org.jfree.chart.axis.ValueAxis;
063    import org.jfree.chart.entity.EntityCollection;
064    import org.jfree.chart.event.RendererChangeEvent;
065    import org.jfree.chart.labels.ItemLabelPosition;
066    import org.jfree.chart.labels.XYItemLabelGenerator;
067    import org.jfree.chart.plot.CrosshairState;
068    import org.jfree.chart.plot.PlotOrientation;
069    import org.jfree.chart.plot.PlotRenderingInfo;
070    import org.jfree.chart.plot.XYPlot;
071    import org.jfree.data.Range;
072    import org.jfree.data.general.DatasetUtilities;
073    import org.jfree.data.xy.IntervalXYDataset;
074    import org.jfree.data.xy.XYDataset;
075    import org.jfree.text.TextUtilities;
076    import org.jfree.ui.RectangleEdge;
077    import org.jfree.util.ObjectUtilities;
078    import org.jfree.util.PublicCloneable;
079    import org.jfree.util.ShapeUtilities;
080    
081    /**
082     * A renderer that draws a line connecting the start and end Y values for an
083     * {@link XYPlot}.
084     */
085    public class YIntervalRenderer extends AbstractXYItemRenderer
086            implements XYItemRenderer, Cloneable, PublicCloneable, Serializable {
087    
088        /** For serialization. */
089        private static final long serialVersionUID = -2951586537224143260L;
090    
091        /**
092         * An additional item label generator.  If this is non-null, the item
093         * label generated will be displayed near the lower y-value at the
094         * position given by getNegativeItemLabelPosition().
095         *
096         * @since 1.0.10
097         */
098        private XYItemLabelGenerator additionalItemLabelGenerator;
099    
100        /**
101         * The default constructor.
102         */
103        public YIntervalRenderer() {
104            super();
105            this.additionalItemLabelGenerator = null;
106        }
107    
108        /**
109         * Returns the generator for the item labels that appear near the lower
110         * y-value.
111         *
112         * @return The generator (possibly <code>null</code>).
113         *
114         * @see #setAdditionalItemLabelGenerator(XYItemLabelGenerator)
115         *
116         * @since 1.0.10
117         */
118        public XYItemLabelGenerator getAdditionalItemLabelGenerator() {
119            return this.additionalItemLabelGenerator;
120        }
121    
122        /**
123         * Sets the generator for the item labels that appear near the lower
124         * y-value and sends a {@link RendererChangeEvent} to all registered
125         * listeners.  If this is set to <code>null</code>, no item labels will be
126         * drawn.
127         *
128         * @param generator  the generator (<code>null</code> permitted).
129         *
130         * @see #getAdditionalItemLabelGenerator()
131         *
132         * @since 1.0.10
133         */
134        public void setAdditionalItemLabelGenerator(
135                XYItemLabelGenerator generator) {
136            this.additionalItemLabelGenerator = generator;
137            fireChangeEvent();
138        }
139    
140        /**
141         * Returns the range of values the renderer requires to display all the
142         * items from the specified dataset.
143         *
144         * @param dataset  the dataset (<code>null</code> permitted).
145         *
146         * @return The range (<code>null</code> if the dataset is <code>null</code>
147         *         or empty).
148         */
149        public Range findRangeBounds(XYDataset dataset) {
150            if (dataset != null) {
151                return DatasetUtilities.findRangeBounds(dataset, true);
152            }
153            else {
154                return null;
155            }
156        }
157    
158        /**
159         * Draws the visual representation of a single data item.
160         *
161         * @param g2  the graphics device.
162         * @param state  the renderer state.
163         * @param dataArea  the area within which the plot is being drawn.
164         * @param info  collects information about the drawing.
165         * @param plot  the plot (can be used to obtain standard color
166         *              information etc).
167         * @param domainAxis  the domain axis.
168         * @param rangeAxis  the range axis.
169         * @param dataset  the dataset.
170         * @param series  the series index (zero-based).
171         * @param item  the item index (zero-based).
172         * @param crosshairState  crosshair information for the plot
173         *                        (<code>null</code> permitted).
174         * @param pass  the pass index (ignored here).
175         */
176        public void drawItem(Graphics2D g2,
177                             XYItemRendererState state,
178                             Rectangle2D dataArea,
179                             PlotRenderingInfo info,
180                             XYPlot plot,
181                             ValueAxis domainAxis,
182                             ValueAxis rangeAxis,
183                             XYDataset dataset,
184                             int series,
185                             int item,
186                             CrosshairState crosshairState,
187                             int pass) {
188    
189            // setup for collecting optional entity info...
190            EntityCollection entities = null;
191            if (info != null) {
192                entities = info.getOwner().getEntityCollection();
193            }
194    
195            IntervalXYDataset intervalDataset = (IntervalXYDataset) dataset;
196    
197            double x = intervalDataset.getXValue(series, item);
198            double yLow   = intervalDataset.getStartYValue(series, item);
199            double yHigh  = intervalDataset.getEndYValue(series, item);
200    
201            RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
202            RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
203    
204            double xx = domainAxis.valueToJava2D(x, dataArea, xAxisLocation);
205            double yyLow = rangeAxis.valueToJava2D(yLow, dataArea, yAxisLocation);
206            double yyHigh = rangeAxis.valueToJava2D(yHigh, dataArea, yAxisLocation);
207    
208            Paint p = getItemPaint(series, item);
209            Stroke s = getItemStroke(series, item);
210    
211            Line2D line = null;
212            Shape shape = getItemShape(series, item);
213            Shape top = null;
214            Shape bottom = null;
215            PlotOrientation orientation = plot.getOrientation();
216            if (orientation == PlotOrientation.HORIZONTAL) {
217                line = new Line2D.Double(yyLow, xx, yyHigh, xx);
218                top = ShapeUtilities.createTranslatedShape(shape, yyHigh, xx);
219                bottom = ShapeUtilities.createTranslatedShape(shape, yyLow, xx);
220            }
221            else if (orientation == PlotOrientation.VERTICAL) {
222                line = new Line2D.Double(xx, yyLow, xx, yyHigh);
223                top = ShapeUtilities.createTranslatedShape(shape, xx, yyHigh);
224                bottom = ShapeUtilities.createTranslatedShape(shape, xx, yyLow);
225            }
226            g2.setPaint(p);
227            g2.setStroke(s);
228            g2.draw(line);
229    
230            g2.fill(top);
231            g2.fill(bottom);
232    
233            // for item labels, we have a special case because there is the
234            // possibility to draw (a) the regular item label near to just the
235            // upper y-value, or (b) the regular item label near the upper y-value
236            // PLUS an additional item label near the lower y-value.
237            if (isItemLabelVisible(series, item)) {
238                drawItemLabel(g2, orientation, dataset, series, item, xx, yyHigh,
239                        false);
240                drawAdditionalItemLabel(g2, orientation, dataset, series, item,
241                        xx, yyLow);
242            }
243    
244            // add an entity for the item...
245            if (entities != null) {
246                addEntity(entities, line.getBounds(), dataset, series, item, 0.0,
247                        0.0);
248            }
249    
250        }
251    
252        /**
253         * Draws an item label.
254         *
255         * @param g2  the graphics device.
256         * @param orientation  the orientation.
257         * @param dataset  the dataset.
258         * @param series  the series index (zero-based).
259         * @param item  the item index (zero-based).
260         * @param x  the x coordinate (in Java2D space).
261         * @param y  the y coordinate (in Java2D space).
262         * @param negative  indicates a negative value (which affects the item
263         *                  label position).
264         */
265        private void drawAdditionalItemLabel(Graphics2D g2,
266                PlotOrientation orientation, XYDataset dataset, int series,
267                int item, double x, double y) {
268    
269            if (this.additionalItemLabelGenerator == null) {
270                return;
271            }
272    
273            Font labelFont = getItemLabelFont(series, item);
274            Paint paint = getItemLabelPaint(series, item);
275            g2.setFont(labelFont);
276            g2.setPaint(paint);
277            String label = this.additionalItemLabelGenerator.generateLabel(dataset,
278                    series, item);
279    
280            ItemLabelPosition position = getNegativeItemLabelPosition(series, item);
281            Point2D anchorPoint = calculateLabelAnchorPoint(
282                    position.getItemLabelAnchor(), x, y, orientation);
283            TextUtilities.drawRotatedString(label, g2,
284                    (float) anchorPoint.getX(), (float) anchorPoint.getY(),
285                    position.getTextAnchor(), position.getAngle(),
286                    position.getRotationAnchor());
287        }
288    
289        /**
290         * Tests this renderer for equality with an arbitrary object.
291         *
292         * @param obj  the object (<code>null</code> permitted).
293         *
294         * @return A boolean.
295         */
296        public boolean equals(Object obj) {
297            if (obj == this) {
298                return true;
299            }
300            if (!(obj instanceof YIntervalRenderer)) {
301                return false;
302            }
303            YIntervalRenderer that = (YIntervalRenderer) obj;
304            if (!ObjectUtilities.equal(this.additionalItemLabelGenerator,
305                    that.additionalItemLabelGenerator)) {
306                return false;
307            }
308            return super.equals(obj);
309        }
310    
311        /**
312         * Returns a clone of the renderer.
313         *
314         * @return A clone.
315         *
316         * @throws CloneNotSupportedException  if the renderer cannot be cloned.
317         */
318        public Object clone() throws CloneNotSupportedException {
319            return super.clone();
320        }
321    
322    }