001    /* ===========================================================
002     * JFreeChart : a free chart library for the Java(tm) platform
003     * ===========================================================
004     *
005     * (C) Copyright 2000-2006, 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     * HighLowRenderer.java
029     * --------------------
030     * (C) Copyright 2001-2006, by Object Refinery Limited.
031     *
032     * Original Author:  David Gilbert (for Object Refinery Limited);
033     * Contributor(s):   Richard Atkinson;
034     *                   Christian W. Zuckschwerdt;
035     *
036     * $Id: HighLowRenderer.java,v 1.5.2.3 2006/07/06 10:03:34 mungady Exp $
037     *
038     * Changes
039     * -------
040     * 13-Dec-2001 : Version 1 (DG);
041     * 23-Jan-2002 : Added DrawInfo parameter to drawItem() method (DG);
042     * 28-Mar-2002 : Added a property change listener mechanism so that renderers 
043     *               no longer need to be immutable (DG);
044     * 09-Apr-2002 : Removed translatedRangeZero from the drawItem() method, and 
045     *               changed the return type of the drawItem method to void, 
046     *               reflecting a change in the XYItemRenderer interface.  Added 
047     *               tooltip code to drawItem() method (DG);
048     * 05-Aug-2002 : Small modification to drawItem method to support URLs for 
049     *               HTML image maps (RA);
050     * 25-Mar-2003 : Implemented Serializable (DG);
051     * 01-May-2003 : Modified drawItem() method signature (DG);
052     * 30-Jul-2003 : Modified entity constructor (CZ);
053     * 31-Jul-2003 : Deprecated constructor (DG);
054     * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG);
055     * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
056     * 29-Jan-2004 : Fixed bug (882392) when rendering with 
057     *               PlotOrientation.HORIZONTAL (DG);
058     * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState.  Renamed 
059     *               XYToolTipGenerator --> XYItemLabelGenerator (DG);
060     * 15-Jul-2004 : Switched getX() with getXValue() and getY() with 
061     *               getYValue() (DG);
062     * 01-Nov-2005 : Added optional openTickPaint and closeTickPaint settings (DG);
063     * ------------- JFREECHART 1.0.0 ---------------------------------------------
064     * 06-Jul-2006 : Replace dataset methods getX() --> getXValue() (DG);
065     * 
066     */
067    
068    package org.jfree.chart.renderer.xy;
069    
070    import java.awt.Graphics2D;
071    import java.awt.Paint;
072    import java.awt.Shape;
073    import java.awt.Stroke;
074    import java.awt.geom.Line2D;
075    import java.awt.geom.Rectangle2D;
076    import java.io.IOException;
077    import java.io.ObjectInputStream;
078    import java.io.ObjectOutputStream;
079    import java.io.Serializable;
080    
081    import org.jfree.chart.axis.ValueAxis;
082    import org.jfree.chart.entity.EntityCollection;
083    import org.jfree.chart.entity.XYItemEntity;
084    import org.jfree.chart.event.RendererChangeEvent;
085    import org.jfree.chart.labels.XYToolTipGenerator;
086    import org.jfree.chart.plot.CrosshairState;
087    import org.jfree.chart.plot.PlotOrientation;
088    import org.jfree.chart.plot.PlotRenderingInfo;
089    import org.jfree.chart.plot.XYPlot;
090    import org.jfree.data.xy.OHLCDataset;
091    import org.jfree.data.xy.XYDataset;
092    import org.jfree.io.SerialUtilities;
093    import org.jfree.ui.RectangleEdge;
094    import org.jfree.util.PaintUtilities;
095    import org.jfree.util.PublicCloneable;
096    
097    /**
098     * A renderer that draws high/low/open/close markers on an {@link XYPlot} 
099     * (requires a {@link OHLCDataset}).  This renderer does not include code to 
100     * calculate the crosshair point for the plot.
101     */
102    public class HighLowRenderer extends AbstractXYItemRenderer
103                                 implements XYItemRenderer,
104                                            Cloneable,
105                                            PublicCloneable,
106                                            Serializable {
107        
108        /** For serialization. */
109        private static final long serialVersionUID = -8135673815876552516L;
110        
111        /** A flag that controls whether the open ticks are drawn. */
112        private boolean drawOpenTicks;
113    
114        /** A flag that controls whether the close ticks are drawn. */
115        private boolean drawCloseTicks;
116        
117        /** 
118         * The paint used for the open ticks (if <code>null</code>, the series
119         * paint is used instead).
120         */
121        private transient Paint openTickPaint;
122        
123        /** 
124         * The paint used for the close ticks (if <code>null</code>, the series
125         * paint is used instead).
126         */
127        private transient Paint closeTickPaint;
128    
129        /**
130         * The default constructor.
131         */
132        public HighLowRenderer() {
133            super();
134            this.drawOpenTicks = true;
135            this.drawCloseTicks = true;
136        }
137    
138        /**
139         * Returns the flag that controls whether open ticks are drawn.
140         * 
141         * @return A boolean.
142         */
143        public boolean getDrawOpenTicks() {
144            return this.drawOpenTicks;
145        }
146        
147        /**
148         * Sets the flag that controls whether open ticks are drawn, and sends a 
149         * {@link RendererChangeEvent} to all registered listeners.
150         * 
151         * @param draw  the flag.
152         */
153        public void setDrawOpenTicks(boolean draw) {
154            this.drawOpenTicks = draw;
155            notifyListeners(new RendererChangeEvent(this));
156        }
157        
158        /**
159         * Returns the flag that controls whether close ticks are drawn.
160         * 
161         * @return A boolean.
162         */
163        public boolean getDrawCloseTicks() {
164            return this.drawCloseTicks;
165        }
166        
167        /**
168         * Sets the flag that controls whether close ticks are drawn, and sends a 
169         * {@link RendererChangeEvent} to all registered listeners.
170         * 
171         * @param draw  the flag.
172         */
173        public void setDrawCloseTicks(boolean draw) {
174            this.drawCloseTicks = draw;
175            notifyListeners(new RendererChangeEvent(this));
176        }
177        
178        /**
179         * Returns the paint used to draw the ticks for the open values.
180         * 
181         * @return The paint used to draw the ticks for the open values (possibly 
182         *         <code>null</code>).
183         */
184        public Paint getOpenTickPaint() {
185            return this.openTickPaint;
186        }
187        
188        /**
189         * Sets the paint used to draw the ticks for the open values and sends a 
190         * {@link RendererChangeEvent} to all registered listeners.  If you set
191         * this to <code>null</code> (the default), the series paint is used 
192         * instead.
193         * 
194         * @param paint  the paint (<code>null</code> permitted).
195         */
196        public void setOpenTickPaint(Paint paint) {
197            this.openTickPaint = paint;
198            notifyListeners(new RendererChangeEvent(this));
199        }
200        
201        /**
202         * Returns the paint used to draw the ticks for the close values.
203         * 
204         * @return The paint used to draw the ticks for the close values (possibly 
205         *         <code>null</code>).
206         */
207        public Paint getCloseTickPaint() {
208            return this.closeTickPaint;
209        }
210        
211        /**
212         * Sets the paint used to draw the ticks for the close values and sends a 
213         * {@link RendererChangeEvent} to all registered listeners.  If you set
214         * this to <code>null</code> (the default), the series paint is used 
215         * instead.
216         * 
217         * @param paint  the paint (<code>null</code> permitted).
218         */
219        public void setCloseTickPaint(Paint paint) {
220            this.closeTickPaint = paint;
221            notifyListeners(new RendererChangeEvent(this));
222        }
223        
224        /**
225         * Draws the visual representation of a single data item.
226         *
227         * @param g2  the graphics device.
228         * @param state  the renderer state.
229         * @param dataArea  the area within which the plot is being drawn.
230         * @param info  collects information about the drawing.
231         * @param plot  the plot (can be used to obtain standard color 
232         *              information etc).
233         * @param domainAxis  the domain axis.
234         * @param rangeAxis  the range axis.
235         * @param dataset  the dataset.
236         * @param series  the series index (zero-based).
237         * @param item  the item index (zero-based).
238         * @param crosshairState  crosshair information for the plot 
239         *                        (<code>null</code> permitted).
240         * @param pass  the pass index.
241         */
242        public void drawItem(Graphics2D g2,
243                             XYItemRendererState state,
244                             Rectangle2D dataArea,
245                             PlotRenderingInfo info,
246                             XYPlot plot,
247                             ValueAxis domainAxis,
248                             ValueAxis rangeAxis,
249                             XYDataset dataset,
250                             int series,
251                             int item,
252                             CrosshairState crosshairState,
253                             int pass) {
254    
255            double x = dataset.getXValue(series, item);
256            if (!domainAxis.getRange().contains(x)) {
257                return;    // the x value is not within the axis range
258            }
259            double xx = domainAxis.valueToJava2D(x, dataArea, 
260                    plot.getDomainAxisEdge());
261            
262            // setup for collecting optional entity info...
263            Shape entityArea = null;
264            EntityCollection entities = null;
265            if (info != null) {
266                entities = info.getOwner().getEntityCollection();
267            }
268    
269            PlotOrientation orientation = plot.getOrientation();
270            RectangleEdge location = plot.getRangeAxisEdge();
271    
272            Paint itemPaint = getItemPaint(series, item);
273            Stroke itemStroke = getItemStroke(series, item);
274            g2.setPaint(itemPaint);
275            g2.setStroke(itemStroke);
276            
277            if (dataset instanceof OHLCDataset) {
278                OHLCDataset hld = (OHLCDataset) dataset;
279                
280                double yHigh = hld.getHighValue(series, item);
281                double yLow = hld.getLowValue(series, item);
282                if (!Double.isNaN(yHigh) && !Double.isNaN(yLow)) {
283                    double yyHigh = rangeAxis.valueToJava2D(yHigh, dataArea, 
284                            location);
285                    double yyLow = rangeAxis.valueToJava2D(yLow, dataArea, 
286                            location);
287                    if (orientation == PlotOrientation.HORIZONTAL) {
288                        g2.draw(new Line2D.Double(yyLow, xx, yyHigh, xx));
289                        entityArea = new Rectangle2D.Double(Math.min(yyLow, yyHigh),
290                                xx - 1.0, Math.abs(yyHigh - yyLow), 2.0);
291                    }
292                    else if (orientation == PlotOrientation.VERTICAL) {
293                        g2.draw(new Line2D.Double(xx, yyLow, xx, yyHigh));   
294                        entityArea = new Rectangle2D.Double(xx - 1.0, 
295                                Math.min(yyLow, yyHigh), 2.0,  
296                                Math.abs(yyHigh - yyLow));
297                    }
298                }
299                
300                double delta = 2.0;
301                if (domainAxis.isInverted()) {
302                    delta = -delta;
303                }
304                if (getDrawOpenTicks()) {
305                    double yOpen = hld.getOpenValue(series, item);
306                    if (!Double.isNaN(yOpen)) {
307                        double yyOpen = rangeAxis.valueToJava2D(yOpen, dataArea, 
308                                location);
309                        if (this.openTickPaint != null) {
310                            g2.setPaint(this.openTickPaint);
311                        }
312                        else {
313                            g2.setPaint(itemPaint);
314                        }
315                        if (orientation == PlotOrientation.HORIZONTAL) {
316                            g2.draw(new Line2D.Double(yyOpen, xx + delta, yyOpen, 
317                                    xx));   
318                        }
319                        else if (orientation == PlotOrientation.VERTICAL) {
320                            g2.draw(new Line2D.Double(xx - delta, yyOpen, xx, 
321                                    yyOpen));   
322                        }
323                    }
324                }
325                
326                if (getDrawCloseTicks()) {
327                    double yClose = hld.getCloseValue(series, item);
328                    if (!Double.isNaN(yClose)) {
329                        double yyClose = rangeAxis.valueToJava2D(
330                            yClose, dataArea, location);
331                        if (this.closeTickPaint != null) {
332                            g2.setPaint(this.closeTickPaint);
333                        }
334                        else {
335                            g2.setPaint(itemPaint);
336                        }
337                        if (orientation == PlotOrientation.HORIZONTAL) {
338                            g2.draw(new Line2D.Double(yyClose, xx, yyClose, 
339                                    xx - delta));   
340                        }
341                        else if (orientation == PlotOrientation.VERTICAL) {
342                            g2.draw(new Line2D.Double(xx, yyClose, xx + delta, 
343                                    yyClose));   
344                        }
345                    }
346                }
347      
348            }
349            else {
350                // not a HighLowDataset, so just draw a line connecting this point 
351                // with the previous point...
352                if (item > 0) {
353                    double x0 = dataset.getXValue(series, item - 1);
354                    double y0 = dataset.getYValue(series, item - 1);
355                    double y = dataset.getYValue(series, item);
356                    if (Double.isNaN(x0) || Double.isNaN(y0) || Double.isNaN(y)) {
357                        return;
358                    }
359                    double xx0 = domainAxis.valueToJava2D(x0, dataArea, 
360                            plot.getDomainAxisEdge());
361                    double yy0 = rangeAxis.valueToJava2D(y0, dataArea, location);
362                    double yy = rangeAxis.valueToJava2D(y, dataArea, location);
363                    if (orientation == PlotOrientation.HORIZONTAL) {
364                        g2.draw(new Line2D.Double(yy0, xx0, yy, xx));
365                    }
366                    else if (orientation == PlotOrientation.VERTICAL) {
367                        g2.draw(new Line2D.Double(xx0, yy0, xx, yy));
368                    }
369                }
370            }
371            
372            // add an entity for the item...
373            if (entities != null) {
374                String tip = null;
375                XYToolTipGenerator generator = getToolTipGenerator(series, item);
376                if (generator != null) {
377                    tip = generator.generateToolTip(dataset, series, item);
378                }
379                String url = null;
380                if (getURLGenerator() != null) {
381                    url = getURLGenerator().generateURL(dataset, series, item);
382                }
383                XYItemEntity entity = new XYItemEntity(entityArea, dataset, 
384                        series, item, tip, url);
385                entities.add(entity);
386            }
387    
388        }
389        
390        /**
391         * Returns a clone of the renderer.
392         * 
393         * @return A clone.
394         * 
395         * @throws CloneNotSupportedException  if the renderer cannot be cloned.
396         */
397        public Object clone() throws CloneNotSupportedException {
398            return super.clone();
399        }
400        
401        /**
402         * Tests this renderer for equality with an arbitrary object.
403         * 
404         * @param obj  the object (<code>null</code> permitted).
405         * 
406         * @return A boolean.
407         */
408        public boolean equals(Object obj) {
409            if (this == obj) {
410                return true;
411            }
412            if (!(obj instanceof HighLowRenderer)) {
413                return false;
414            }
415            HighLowRenderer that = (HighLowRenderer) obj;
416            if (this.drawOpenTicks != that.drawOpenTicks) {
417                return false;
418            }
419            if (this.drawCloseTicks != that.drawCloseTicks) {
420                return false;
421            }
422            if (!PaintUtilities.equal(this.openTickPaint, that.openTickPaint)) {
423                return false;
424            }
425            if (!PaintUtilities.equal(this.closeTickPaint, that.closeTickPaint)) {
426                return false;
427            }
428            if (!super.equals(obj)) {
429                return false;
430            }
431            return true;
432        }
433        
434        /**
435         * Provides serialization support.
436         *
437         * @param stream  the input stream.
438         *
439         * @throws IOException  if there is an I/O error.
440         * @throws ClassNotFoundException  if there is a classpath problem.
441         */
442        private void readObject(ObjectInputStream stream) 
443                throws IOException, ClassNotFoundException {
444            stream.defaultReadObject();
445            this.openTickPaint = SerialUtilities.readPaint(stream);
446            this.closeTickPaint = SerialUtilities.readPaint(stream);
447        }
448        
449        /**
450         * Provides serialization support.
451         *
452         * @param stream  the output stream.
453         *
454         * @throws IOException  if there is an I/O error.
455         */
456        private void writeObject(ObjectOutputStream stream) throws IOException {
457            stream.defaultWriteObject();
458            SerialUtilities.writePaint(this.openTickPaint, stream);
459            SerialUtilities.writePaint(this.closeTickPaint, stream);
460        }
461    
462    }