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