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     * ColorBar.java
029     * -------------
030     * (C) Copyright 2002-2004, by David M. O'Donnell and Contributors.
031     *
032     * Original Author:  David M. O'Donnell;
033     * Contributor(s):   David Gilbert (for Object Refinery Limited);
034     *
035     * $Id: ColorBar.java,v 1.6.2.2 2005/11/24 16:11:48 mungady Exp $
036     *
037     * Changes
038     * -------
039     * 26-Nov-2002 : Version 1 contributed by David M. O'Donnell (DG);
040     * 14-Jan-2003 : Changed autoRangeMinimumSize from Number --> double (DG);
041     * 17-Jan-2003 : Moved plot classes to separate package (DG);
042     * 20-Jan-2003 : Removed unnecessary constructors (DG);
043     * 26-Mar-2003 : Implemented Serializable (DG);
044     * 09-Jul-2003 : Changed ColorBar from extending axis classes to enclosing 
045     *               them (DG);
046     * 05-Aug-2003 : Applied changes in bug report 780298 (DG);
047     * 14-Aug-2003 : Implemented Cloneable (DG);
048     * 08-Sep-2003 : Changed ValueAxis API (DG);
049     * 21-Jan-2004 : Update for renamed method in ValueAxis (DG);
050     *
051     */
052    
053    package org.jfree.chart.axis;
054    
055    import java.awt.BasicStroke;
056    import java.awt.Graphics2D;
057    import java.awt.Paint;
058    import java.awt.RenderingHints;
059    import java.awt.Stroke;
060    import java.awt.geom.Line2D;
061    import java.awt.geom.Rectangle2D;
062    import java.io.Serializable;
063    
064    import org.jfree.chart.plot.ColorPalette;
065    import org.jfree.chart.plot.ContourPlot;
066    import org.jfree.chart.plot.Plot;
067    import org.jfree.chart.plot.RainbowPalette;
068    import org.jfree.ui.RectangleEdge;
069    
070    /**
071     * A color bar.
072     *
073     * @author David M. O'Donnell
074     */
075    public class ColorBar implements Cloneable, Serializable {
076    
077        /** For serialization. */
078        private static final long serialVersionUID = -2101776212647268103L;
079        
080        /** The default color bar thickness. */
081        public static final int DEFAULT_COLORBAR_THICKNESS = 0;
082    
083        /** The default color bar thickness percentage. */
084        public static final double DEFAULT_COLORBAR_THICKNESS_PERCENT = 0.10;
085    
086        /** The default outer gap. */
087        public static final int DEFAULT_OUTERGAP = 2;
088    
089        /** The axis. */
090        private ValueAxis axis;
091        
092        /** The color bar thickness. */
093        private int colorBarThickness = DEFAULT_COLORBAR_THICKNESS;
094    
095        /** 
096         * The color bar thickness as a percentage of the height of the data area. 
097         */
098        private double colorBarThicknessPercent 
099            = DEFAULT_COLORBAR_THICKNESS_PERCENT;
100    
101        /** The color palette. */
102        private ColorPalette colorPalette = null;
103    
104        /** The color bar length. */
105        private int colorBarLength = 0; // default make height of plotArea
106    
107        /** The amount of blank space around the colorbar. */
108        private int outerGap;
109    
110        /**
111         * Constructs a horizontal colorbar axis, using default values where 
112         * necessary.
113         *
114         * @param label  the axis label.
115         */
116        public ColorBar(String label) {
117       
118            NumberAxis a = new NumberAxis(label);
119            a.setAutoRangeIncludesZero(false);
120            this.axis = a;
121            this.axis.setLowerMargin(0.0);
122            this.axis.setUpperMargin(0.0);
123    
124            this.colorPalette = new RainbowPalette();
125            this.colorBarThickness = DEFAULT_COLORBAR_THICKNESS;
126            this.colorBarThicknessPercent = DEFAULT_COLORBAR_THICKNESS_PERCENT;
127            this.outerGap = DEFAULT_OUTERGAP;
128            this.colorPalette.setMinZ(this.axis.getRange().getLowerBound());
129            this.colorPalette.setMaxZ(this.axis.getRange().getUpperBound());
130    
131        }
132    
133        /**
134         * Configures the color bar.
135         * 
136         * @param plot  the plot.
137         */
138        public void configure(ContourPlot plot) {
139            double minZ = plot.getDataset().getMinZValue();
140            double maxZ = plot.getDataset().getMaxZValue();
141            setMinimumValue(minZ);
142            setMaximumValue(maxZ);
143        }
144        
145        /**
146         * Returns the axis.
147         * 
148         * @return The axis.
149         */
150        public ValueAxis getAxis() {
151            return this.axis;
152        }
153        
154        /**
155         * Sets the axis.
156         * 
157         * @param axis  the axis.
158         */
159        public void setAxis(ValueAxis axis) {
160            this.axis = axis;
161        }
162        
163        /**
164         * Rescales the axis to ensure that all data are visible.
165         */
166        public void autoAdjustRange() {
167            this.axis.autoAdjustRange();
168            this.colorPalette.setMinZ(this.axis.getLowerBound());
169            this.colorPalette.setMaxZ(this.axis.getUpperBound());
170        }
171    
172        /**
173         * Draws the plot on a Java 2D graphics device (such as the screen or a 
174         * printer).
175         *
176         * @param g2  the graphics device.
177         * @param cursor  the cursor.
178         * @param plotArea  the area within which the chart should be drawn.
179         * @param dataArea  the area within which the plot should be drawn (a
180         *                  subset of the drawArea).
181         * @param reservedArea  the reserved area.
182         * @param edge  the color bar location.
183         * 
184         * @return The new cursor location.
185         */
186        public double draw(Graphics2D g2, double cursor,
187                           Rectangle2D plotArea, Rectangle2D dataArea, 
188                           Rectangle2D reservedArea, RectangleEdge edge) {
189    
190    
191            Rectangle2D colorBarArea = null;
192            
193            double thickness = calculateBarThickness(dataArea, edge);
194            if (this.colorBarThickness > 0) {
195                thickness = this.colorBarThickness;  // allow fixed thickness
196            }
197    
198            double length = 0.0;
199            if (RectangleEdge.isLeftOrRight(edge)) {
200                length = dataArea.getHeight();
201            }
202            else {
203                length = dataArea.getWidth();
204            }
205            
206            if (this.colorBarLength > 0) {
207                length = this.colorBarLength;
208            }
209    
210            if (edge == RectangleEdge.BOTTOM) {
211                colorBarArea = new Rectangle2D.Double(
212                    dataArea.getX(), plotArea.getMaxY() + this.outerGap,
213                    length, thickness
214                );
215            }
216            else if (edge == RectangleEdge.TOP) {
217                colorBarArea = new Rectangle2D.Double(
218                    dataArea.getX(), reservedArea.getMinY() + this.outerGap,
219                    length, thickness
220                );
221            }
222            else if (edge == RectangleEdge.LEFT) {
223                colorBarArea = new Rectangle2D.Double(
224                    plotArea.getX() - thickness - this.outerGap ,
225                    dataArea.getMinY(), thickness, length
226                );            
227            }
228            else if (edge == RectangleEdge.RIGHT) {
229                colorBarArea = new Rectangle2D.Double(
230                    plotArea.getMaxX() + this.outerGap, dataArea.getMinY(),
231                    thickness, length
232                );            
233            }
234            
235            // update, but dont draw tick marks (needed for stepped colors)
236            this.axis.refreshTicks(
237                g2, new AxisState(), colorBarArea, edge
238            );
239    
240            drawColorBar(g2, colorBarArea, edge);
241    
242            AxisState state = null;
243            if (edge == RectangleEdge.TOP) {
244                cursor = colorBarArea.getMinY();
245                state = this.axis.draw(
246                    g2, cursor, reservedArea, colorBarArea, RectangleEdge.TOP, null
247                );
248            } 
249            else if (edge == RectangleEdge.BOTTOM) {
250                cursor = colorBarArea.getMaxY();
251                state = this.axis.draw(
252                    g2, cursor, reservedArea, colorBarArea, RectangleEdge.BOTTOM, 
253                    null
254                );
255            } 
256            else if (edge == RectangleEdge.LEFT) {
257                cursor = colorBarArea.getMinX();
258                state = this.axis.draw(
259                    g2, cursor, reservedArea, colorBarArea, RectangleEdge.LEFT, null
260                );
261            } 
262            else if (edge == RectangleEdge.RIGHT) {
263                cursor = colorBarArea.getMaxX();
264                state = this.axis.draw(
265                    g2, cursor, reservedArea, colorBarArea, RectangleEdge.RIGHT, 
266                    null
267                );
268            }
269            return state.getCursor();
270            
271        }
272    
273        /**
274         * Draws the plot on a Java 2D graphics device (such as the screen or a 
275         * printer).
276         *
277         * @param g2  the graphics device.
278         * @param colorBarArea  the area within which the axis should be drawn.
279         * @param edge  the location.
280         */
281        public void drawColorBar(Graphics2D g2, Rectangle2D colorBarArea, 
282                                 RectangleEdge edge) {
283    
284            Object antiAlias = g2.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
285            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
286                                RenderingHints.VALUE_ANTIALIAS_OFF);
287    
288            // setTickValues was missing from ColorPalette v. 0.96
289            //colorPalette.setTickValues(this.axis.getTicks());
290    
291            Stroke strokeSaved = g2.getStroke();
292            g2.setStroke(new BasicStroke(1.0f));
293    
294            if (RectangleEdge.isTopOrBottom(edge)) {
295                double y1 = colorBarArea.getY();
296                double y2 = colorBarArea.getMaxY();
297                double xx = colorBarArea.getX();
298                Line2D line = new Line2D.Double();
299                while (xx <= colorBarArea.getMaxX()) {
300                    double value = this.axis.java2DToValue(xx, colorBarArea, edge);
301                    line.setLine(xx, y1, xx, y2);
302                    g2.setPaint(getPaint(value));
303                    g2.draw(line);
304                    xx += 1;
305                }
306            }
307            else {
308                double y1 = colorBarArea.getX();
309                double y2 = colorBarArea.getMaxX();
310                double xx = colorBarArea.getY();
311                Line2D line = new Line2D.Double();
312                while (xx <= colorBarArea.getMaxY()) {
313                    double value = this.axis.java2DToValue(xx, colorBarArea, edge);
314                    line.setLine(y1, xx, y2, xx);
315                    g2.setPaint(getPaint(value));
316                    g2.draw(line);
317                    xx += 1;
318                }            
319            }
320    
321            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, antiAlias);
322            g2.setStroke(strokeSaved);
323    
324        }
325    
326        /**
327         * Returns the color palette.
328         *
329         * @return The color palette.
330         */
331        public ColorPalette getColorPalette() {
332            return this.colorPalette;
333        }
334    
335        /**
336         * Returns the Paint associated with a value.
337         *
338         * @param value  the value.
339         *
340         * @return The paint.
341         */
342        public Paint getPaint(double value) {
343            return this.colorPalette.getPaint(value);
344        }
345    
346        /**
347         * Sets the color palette.
348         *
349         * @param palette  the new palette.
350         */
351        public void setColorPalette(ColorPalette palette) {
352            this.colorPalette = palette;
353        }
354    
355        /**
356         * Sets the maximum value.
357         *
358         * @param value  the maximum value.
359         */
360        public void setMaximumValue(double value) {
361            this.colorPalette.setMaxZ(value);
362            this.axis.setUpperBound(value);
363        }
364    
365        /**
366         * Sets the minimum value.
367         *
368         * @param value  the minimum value.
369         */
370        public void setMinimumValue(double value) {
371            this.colorPalette.setMinZ(value);
372            this.axis.setLowerBound(value);
373        }
374    
375        /**
376         * Reserves the space required to draw the color bar.
377         *
378         * @param g2  the graphics device.
379         * @param plot  the plot that the axis belongs to.
380         * @param plotArea  the area within which the plot should be drawn.
381         * @param dataArea  the data area.
382         * @param edge  the axis location.
383         * @param space  the space already reserved.
384         *
385         * @return The space required to draw the axis in the specified plot area.
386         */
387        public AxisSpace reserveSpace(Graphics2D g2, Plot plot, 
388                                      Rectangle2D plotArea,
389                                      Rectangle2D dataArea, RectangleEdge edge, 
390                                      AxisSpace space) {
391    
392            AxisSpace result = this.axis.reserveSpace(
393                g2, plot, plotArea, edge, space
394            );
395            double thickness = calculateBarThickness(dataArea, edge);
396            result.add(thickness + 2 * this.outerGap, edge);
397            return result;
398    
399        }
400        
401        /**
402         * Calculates the bar thickness.
403         * 
404         * @param plotArea  the plot area.
405         * @param edge  the location.
406         * 
407         * @return The thickness.
408         */
409        private double calculateBarThickness(Rectangle2D plotArea, 
410                                             RectangleEdge edge) {
411            double result = 0.0;
412            if (RectangleEdge.isLeftOrRight(edge)) {
413                result = plotArea.getWidth() * this.colorBarThicknessPercent;
414            }
415            else {
416                result = plotArea.getHeight() * this.colorBarThicknessPercent;
417            }
418            return result;  
419        }
420    
421        /**
422         * Returns a clone of the object.
423         * 
424         * @return A clone.
425         * 
426         * @throws CloneNotSupportedException if some component of the color bar 
427         *         does not support cloning.
428         */
429        public Object clone() throws CloneNotSupportedException {
430        
431            ColorBar clone = (ColorBar) super.clone();
432            clone.axis = (ValueAxis) this.axis.clone();
433            return clone;
434                
435        }
436        
437        /**
438         * Tests this object for equality with another.
439         * 
440         * @param obj  the object to test against.
441         * 
442         * @return A boolean.
443         */
444        public boolean equals(Object obj) {
445    
446            if (obj == this) {
447                return true;
448            }
449            if (!(obj instanceof ColorBar)) {
450                return false;   
451            }
452            ColorBar that = (ColorBar) obj;
453            if (!this.axis.equals(that.axis)) {
454                return false;
455            }
456            if (this.colorBarThickness != that.colorBarThickness) {
457                return false;
458            }
459            if (this.colorBarThicknessPercent != that.colorBarThicknessPercent) {
460                return false;
461            }
462            if (!this.colorPalette.equals(that.colorPalette)) {
463                return false;
464            }
465            if (this.colorBarLength != that.colorBarLength) {
466                return false;
467            }
468            if (this.outerGap != that.outerGap) {
469                return false;
470            }
471            return true;
472            
473        }
474        
475        /**
476         * Returns a hash code for this object.
477         * 
478         * @return A hash code.
479         */
480        public int hashCode() {
481            return this.axis.hashCode();
482        }
483        
484    }