001    /*
002     * $Id: JXGraph.java,v 1.3 2006/03/17 22:10:33 gfx Exp $
003     *
004     * Copyright 2006 Sun Microsystems, Inc., 4150 Network Circle,
005     * Santa Clara, California 95054, U.S.A. All rights reserved.
006     *
007     * This library is free software; you can redistribute it and/or
008     * modify it under the terms of the GNU Lesser General Public
009     * License as published by the Free Software Foundation; either
010     * version 2.1 of the License, or (at your option) any later version.
011     * 
012     * This library is distributed in the hope that it will be useful,
013     * but WITHOUT ANY WARRANTY; without even the implied warranty of
014     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015     * Lesser General Public License for more details.
016     * 
017     * You should have received a copy of the GNU Lesser General Public
018     * License along with this library; if not, write to the Free Software
019     * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
020     */
021    
022    package org.jdesktop.swingx;
023    
024    import java.awt.BasicStroke;
025    import java.awt.Color;
026    import java.awt.Cursor;
027    import java.awt.Dimension;
028    import java.awt.FontMetrics;
029    import java.awt.Graphics;
030    import java.awt.Graphics2D;
031    import java.awt.Point;
032    import java.awt.Rectangle;
033    import java.awt.RenderingHints;
034    import java.awt.Stroke;
035    import java.awt.event.MouseAdapter;
036    import java.awt.event.MouseEvent;
037    import java.awt.event.MouseMotionAdapter;
038    import java.awt.event.MouseWheelEvent;
039    import java.awt.event.MouseWheelListener;
040    import java.awt.geom.GeneralPath;
041    import java.awt.geom.Point2D;
042    import java.awt.geom.Rectangle2D;
043    import java.beans.PropertyChangeEvent;
044    import java.beans.PropertyChangeListener;
045    import java.beans.PropertyChangeSupport;
046    import java.text.DecimalFormat;
047    import java.text.NumberFormat;
048    import java.util.LinkedList;
049    import java.util.List;
050    
051    // TODO: keyboard navigation
052    // TODO: honor clip rect with text painting
053    // TODO: let client change zoom multiplier
054    // TODO: improve text drawing when origin is not on a multiple of majorX/majorY
055    // TODO: programmatically zoom in and out (or expose ZOOM_MULTIPLIER)
056    
057    /**
058     * <h2>Presentation</h2>
059     *
060     * <p><code>JXGraph</code> provides a component which can display one or more
061     * plots on top of a graduated background (or grid.)</p>
062     * 
063     * <h2>User input</h2>
064     * 
065     * <p>To help analyzing the plots, this component allows the user to pan the
066     * view by left-clicking and dragging the mouse around. Using the mouse wheel,
067     * the user is also able to zoom in and out. Clicking the middle button resets
068     * the view to its original position.</p>
069     *
070     * <p>All user input can be disabled by calling
071     * {@link #setInputEnabled(boolean)} and passing false. This does not prevent
072     * subclasses to register their own event listeners, such as mouse or key
073     * listeners.</p>
074     *
075     * <h2>Initializing the component and setting the view</h2>
076     *
077     * <p>Whenever a new instance of this component is created, the grid boundaries,
078     * or view, must be defined. The view is comprised of several elements whose
079     * descriptions are the following:</p>
080     *
081     * <ul>
082     *   <li><i>minX</i>: Minimum value initially displayed by the component on the
083     *   X axis (horizontally.)</li>
084     *   <li><i>minY</i>: Minimum value initially displayed by the component on the
085     *   Y axis (vertycally.)</li>
086     *   <li><i>maxX</i>: Maximum value initially displayed by the component on the
087     *   X axis (horizontally.)</li>
088     *   <li><i>maxY</i>: Maximum value initially displayed by the component on the
089     *   Y axis (vertycally.)</li>
090     *   <li><i>originX</i>: Origin on the X axis of the vertical axis.</li>
091     *   <li><i>originY</i>: Origin on the Y axis of the horizontal axis.</li>
092     *   <li><i>majorX</i>: Distance between two major vertical lines of the
093     *   grid.</li>
094     *   <li><i>majorY</i>: Distance between two major horizontal lines of the
095     *   grid.</li>
096     *   <li><i>minCountX</i>: Number of minor vertical lines between two major
097     *   vertical lines in the grid.</li>
098     *   <li><i>minCountY</i>: Number of minor horizontal lines between two major
099     *   horizontal lines in the grid.</li>
100     * </ul>
101     *
102     * <h3>View and origin</h3>
103     *
104     * <p>The default constructor defines a view bounds by <code>-1.0</code> and
105     * <code>+1.0</code> on both axis, and centered on an origin at
106     * <code>(0, 0)</code>.</p>
107     *
108     * <p>To simplify the API, the origin can be read and written with a
109     * <code>Point2D</code> instance (see {@link #getOrigin()} and
110     * {@link #setOrigin(Point2D)}.)</p>
111     *
112     * <p>Likewise, the view can be read and written with a
113     * <code>Rectangle2D</code> instance (see {@link #getView()} and
114     * {@link #setView(Rectangle2D)}.) In this case, you need not to define the
115     * maximum boundaries of the view. Instead, you need to set the origin of the
116     * rectangle as the minimum boundaries. The width and the height of the
117     * rectangle define the distance between the minimum and maximum boundaries. For
118     * instance, to set the view to minX=-1.0, maxX=1.0, minY=-1.0 and maxY=1.0 you
119     * can use the following rectangle:</p>
120     *
121     * <pre>new Rectangle2D.Double(-1.0d, -1.0d, 2.0d, 2.0d);</pre>
122     *
123     * <p>You can check the boundaries by calling <code>Rectangle2D.getMaxX()</code>
124     * and <code>Rectangle2D.getMaxY()</code> once your rectangle has been
125     * created.</p>
126     *
127     * <p>Alternatively, you can set the view and the origin at the same time by
128     * calling the method {@link #setViewAndOrigin(Rectangle2D)}. Calling this
129     * method will set the origin so as to center it in the view defined by the
130     * rectangle.</p>
131     *
132     * <h3>Grid lines</h3>
133     *
134     * <p>By default, the component defines a spacing of 0.2 unites between two
135     * major grid lines. It also defines 4 minor grid lines between two major
136     * grid lines. The spacing between major grid lines and the number of minor
137     * grid lines can be accessed through the getters {@link #getMajorX()},
138     * {@link #getMajorY()}, {@link #getMinorCountX()} and
139     * {@link #getMinorCountY()}.</p>
140     *
141     * <p>You can change the number of grid lines at runtime by calling the setters
142     * {@link #setMajorX(double)}, {@link #setMajorY(double)},
143     * {@link #setMinorCountX(int)} and {@link #setMinorCountY(int)}.</p>
144     *
145     * <h3>Appearance</h3>
146     *
147     * <p>Although it provides sensible defaults, this component lets you change
148     * its appearance in several ways. It is possible to modify the colors of the
149     * graph by calling the setters {@link #setAxisColor(Color)},
150     * {@link #setMajorGridColor(Color)} and {@link #setMinorGridColor(Color)}.</p>
151     *
152     * <p>You can also enable or disable given parts of the resulting graph by
153     * calling the following setters:</p>
154     *
155     * <ul>
156     *   <li>{@link #setAxisPainted(boolean)}: Defines whether the main axis (see
157     *   {@link #getOrigin()}) is painted.</li>
158     *   <li>{@link #setBackgroundPainted(boolean)}: Defines whether the background
159     *   is painted (see {@link #setBackground(Color)}.)</li>
160     *   <li>{@link #setGridPainted(boolean)}: Defines whether the grid is
161     *   painted.</li>
162     *   <li>{@link #setTextPainted(boolean)}: Defines whether the axis labels are
163     *   painted.</li>
164     * </ul>
165     *
166     * <h3>Usage example</h3>
167     *
168     * <p>The following code snippet creates a new graph centered on
169     * <code>(0, 0)</code>, bound to the view <code>[-1.0 1.0 -1.0 1.0</code>, with
170     * a major grid line every 0.5 units and a minor grid lines count of 5:</p>
171     *
172     * <pre>
173     * Point2D origin = new Point2D.Double(0.0d, 0.0d);
174     * Rectangle2D view = new Rectangle2D.Double(-1.0d, 1.0d, 2.0d, 2.0d);
175     * JXGraph graph = new JXGraph(origin, view, 0.5d, 5, 0.5d, 5);
176     * </pre>
177     *
178     * <h2>Plots</h2>
179     *
180     * <h3>Definition</h3>
181     *
182     * <p>A plot is defined by a mathematical transformation that, given a value on
183     * the graph's X axis, returns a value on the Y axis. The component draws the
184     * result by plotting a spot of color at the coordinates defined by
185     * <code>(X, f(X))</code> where <code>f()</code> is the aforementionned
186     * mathematical transformation. Given the following transformation:</p>
187     *
188     * <pre>
189     * f(X) = X * 2.0
190     * </pre>
191     *
192     * <p>For <code>X=1.0</code>, the component will show a spot of color at the
193     * coordinates <code>(1.0, 2.0)</code>.</p>
194     *
195     * <h3>Creating a new plot</h3>
196     *
197     * <p>Every plot drawn by the component must be a subclass of
198     * {@link JXGraph.Plot}. This abstract public class defines a single method to
199     * be implemented by its children:</p>
200     *
201     * <pre>
202     * public double compute(double value)
203     * </pre>
204     *
205     * <p>The previous example can be defined by a concrete
206     * <code>JXGraph.Plot</code> as follow:</p>
207     *
208     * <pre>
209     * class TwiceTheValuePlot extends JXGraph.Plot {
210     *     public double compute(double value) {
211     *         return value * 2.0d;
212     *     }
213     * }
214     * </pre>
215     *
216     * <p>Most of the time though, a plot requires supplementary parameters. For
217     * instance, let's define the X axis of your graph as the mass of an object. To
218     * compute the weight of the object given its mass, you need to use the
219     * acceleration of gravity (<code>w=m.g</code> where <code>g</code> is the
220     * acceleration.) To let the user modify this last parameter, to compute his
221     * weight at the surface of the moon for instance, you need to add a parameter
222     * to your plot.</p>
223     *
224     * <p>While <code>JXGraph.Plot</code> does not give you an API for such a
225     * purpose, it does define an event dispatching API (see
226     * {@link JXGraph.Plot#firePropertyChange(String, double, double)}.) Whenever a
227     * plot is added to the graph, the component registers itself as a property
228     * listener of the plot. If you take care of firing events whenever the user
229     * changes a parameter of your plot, the graph will automatically update its
230     * display. While not mandatory, it is highly recommended to leverage this
231     * API.</p>
232     *
233     * <h3>Adding and removing plots to and from the graph</h3>
234     *
235     * <p>To add a plot to the graph, simply call the method
236     * {@link #addPlots(Color, JXGraph.Plot...)}. You can use it to add one or more
237     * plots at the same time and associate them with a color. This color is used
238     * when drawing the plots:</p>
239     *
240     * <pre>
241     * JXGraph.Plot plot = new TwiceTheValuePlot();
242     * graph.addPlots(Color.BLUE, plot);
243     * </pre>
244     *
245     * <p>These two lines will display our previously defined plot in blue on
246     * screen. Removing one or several plots is as simple as calling the method
247     * {@link #removePlots(JXGraph.Plot...)}. You can also remove all plots at once
248     * with {@link #removeAllPlots()}.</p>
249     *
250     * <h2>Painting more information</h2>
251     *
252     * <h3>How to draw on the graph</h3>
253     *
254     * <p>If you need to add more information on the graph you need to extend
255     * its class and override the method {@link #paintExtra(Graphics2D)}. This
256     * method has a default empty implementation and is called after everything
257     * has been drawn. Its sole parameter is a reference to the component's drawing
258     * surface, as configured by {@link #setupGraphics(Graphics2D)}. By default, the
259     * setup method activates antialising but it can be overriden to change the
260     * drawing surface. (Translation, rotation, new rendering hints, etc.)</p>
261     *
262     * <h3>Getting the right coordinates</h3>
263     *
264     * <p>To properly draw on the graph you will need to perform a translation
265     * between the graph's coordinates and the screen's coordinates. The component
266     * defines 4 methods to assist you in this task:</p>
267     *
268     * <ul>
269     *   <li>{@link #xPixelToPosition(double)}: Converts a pixel coordinate on the
270     *   X axis into a world coordinate.</li>
271     *   <li>{@link #xPositionToPixel(double)}: Converts a world coordinate on the
272     *   X axis into a pixel coordinate.</li>
273     *   <li>{@link #yPixelToPosition(double)}: Converts a pixel coordinate on the
274     *   Y axis into a world coordinate.</li>
275     *   <li>{@link #yPositionToPixel(double)}: Converts a world coordinate on the
276     *   Y axis into a pixel coordinate.</li>
277     * </ul>
278     *
279     * <p>If you have defined a graph view centered on the origin
280     * <code>(0, 0)</code>, the origin of the graph will be at the exact center of
281     * the screen. That means the world coordinates <code>(0, 0)</code> are
282     * equivalent to the pixel coordinates <code>(width / 2, height / 2)</code>.
283     * Thus, calling <code>xPositionToPixel(0.0d)</code> would give you the same
284     * value as the expression <code>getWidth() / 2.0d</code>.</p>
285     *
286     * <p>Converting from world coordinates to pixel coordinates is mostly used to
287     * draw the result of a mathematical transformation. Converting from pixel
288     * coordinates to world coordinates is mostly used to get the position in the
289     * world of a mouse event.</p>
290     *
291     * @see JXGraph.Plot
292     * @author Romain Guy <romain.guy@mac.com>
293     */
294    public class JXGraph extends JXPanel implements PropertyChangeListener {
295        // stroke widths used to draw the main axis and the grid
296        // the main axis is slightly thicker
297        private static final float STROKE_AXIS = 1.2f;
298        private static final float STROKE_GRID = 1.0f;
299        
300        // defines by how much the view is shrinked or expanded everytime the
301        // user zooms in or out
302        private static final float ZOOM_MULTIPLIER = 1.1f;
303    
304        // default color of the graph (does not include plots colors)
305        private Color majorGridColor = Color.GRAY.brighter();
306        private Color minorGridColor = new Color(220, 220, 220);
307        private Color axisColor = Color.BLACK;
308        
309        // the list of plots currently known and displayed by the graph
310        private List<DrawablePlot> plots;
311    
312        // view boundaries as defined by the user
313        private double minX;
314        private double maxX;
315        private double minY;
316        private double maxY;
317        
318        // the default view is set when the view is manually changed by the client
319        // it is used to reset the view in resetView()
320        private Rectangle2D defaultView;
321    
322        // coordinates of the major axis
323        private double originX;
324        private double originY;
325    
326        // definition of the grid
327        // various default values are used when the view is reset
328        private double majorX;
329        private double defaultMajorX;
330        private int minorCountX;
331        private double majorY;
332        private double defaultMajorY;
333        private int minorCountY;
334        
335        // enables painting layers
336        private boolean textPainted = true;
337        private boolean gridPainted = true;
338        private boolean axisPainted = true;
339        private boolean backPainted = true;
340        
341        // used by the PanHandler to move the view
342        private Point dragStart;
343        
344        // mainFormatter is used for numbers > 0.01 and < 100
345        // secondFormatter uses scientific notation
346        private NumberFormat mainFormatter;
347        private NumberFormat secondFormatter;
348    
349        // input handlers
350        private boolean inputEnabled = true;
351        private ZoomHandler zoomHandler;
352        private PanMotionHandler panMotionHandler;
353        private PanHandler panHandler;
354        private ResetHandler resetHandler;
355    
356        /**
357         * <p>Creates a new graph display. The following properties are
358         * automatically set:</p>
359         * <ul>
360         *   <li><i>view</i>: -1.0 to +1.0 on both axis</li>
361         *   <li><i>origin</i>: At <code>(0, 0)</code></li>
362         *   <li><i>grid</i>: Spacing of 0.2 between major lines; minor lines
363         *   count is 4</li>
364         * </ul>
365         */
366        public JXGraph() {
367            this(0.0, 0.0, -1.0, 1.0, -1.0, 1.0, 0.2, 4, 0.2, 4);
368        }
369    
370        /**
371         * <p>Creates a new graph display with the specified view. The following
372         * properties are automatically set:</p>
373         * <ul>
374         *   <li><i>origin</i>: Center of the specified view</code></li>
375         *   <li><i>grid</i>: Spacing of 0.2 between major lines; minor lines
376         *   count is 4</li>
377         * </ul>
378         * 
379         * @param view the rectangle defining the view boundaries
380         */
381        public JXGraph(Rectangle2D view) {
382            this(new Point2D.Double(view.getCenterX(), view.getCenterY()),
383                view, 0.2, 4, 0.2, 4);
384        }
385        
386        /**
387         * <p>Creates a new graph display with the specified view and grid lines.
388         * The origin is set at the center of the view.</p>
389         * 
390         * @param view        the rectangle defining the view boundaries
391         * @param majorX      the spacing between two major grid lines on the X axis
392         * @param minorCountX the number of minor grid lines betweek two major
393         *                    grid lines on the X axis
394         * @param majorY      the spacing between two major grid lines on the Y axis
395         * @param minorCountY the number of minor grid lines betweek two major
396         *                    grid lines on the Y axis
397         * @throws IllegalArgumentException if minX >= maxX or minY >= maxY or
398         *                                  minorCountX < 0 or minorCountY < 0 or
399         *                                  majorX <= 0.0 or majorY <= 0.0
400         */
401        public JXGraph(Rectangle2D view,
402                       double majorX, int minorCountX,
403                       double majorY, int minorCountY) {
404            this(new Point2D.Double(view.getCenterX(), view.getCenterY()),
405                view, majorX, minorCountX, majorY, minorCountY);
406        }
407        
408        /**
409         * <p>Creates a new graph display with the specified view and origin.
410         * The following properties are automatically set:</p>
411         * <ul>
412         *   <li><i>grid</i>: Spacing of 0.2 between major lines; minor lines
413         *   count is 4</li>
414         * </ul>
415         * 
416         * @param origin the coordinates of the main axis origin
417         * @param view the rectangle defining the view boundaries
418         */
419        public JXGraph(Point2D origin, Rectangle2D view) {
420            this(origin, view, 0.2, 4, 0.2, 4);
421        }
422        
423        /**
424         * <p>Creates a new graph display with the specified view, origin and grid
425         * lines.</p>
426         * 
427         * @param origin      the coordinates of the main axis origin
428         * @param view        the rectangle defining the view boundaries
429         * @param majorX      the spacing between two major grid lines on the X axis
430         * @param minorCountX the number of minor grid lines betweek two major
431         *                    grid lines on the X axis
432         * @param majorY      the spacing between two major grid lines on the Y axis
433         * @param minorCountY the number of minor grid lines betweek two major
434         *                    grid lines on the Y axis
435         * @throws IllegalArgumentException if minX >= maxX or minY >= maxY or
436         *                                  minorCountX < 0 or minorCountY < 0 or
437         *                                  majorX <= 0.0 or majorY <= 0.0
438         */
439        public JXGraph(Point2D origin, Rectangle2D view,
440                       double majorX, int minorCountX,
441                       double majorY, int minorCountY) {
442            this(origin.getX(), origin.getY(),
443                view.getMinX(), view.getMaxX(), view.getMinY(), view.getMaxY(),
444                majorX, minorCountX, majorY, minorCountY);
445        }
446        
447        /**
448         * <p>Creates a new graph display with the specified view, origin and grid
449         * lines.</p>
450         * 
451         * @param originX     the coordinate of the major X axis
452         * @param originY     the coordinate of the major Y axis
453         * @param minX        the minimum coordinate on the X axis for the view
454         * @param maxX        the maximum coordinate on the X axis for the view
455         * @param minY        the minimum coordinate on the Y axis for the view
456         * @param maxY        the maximum coordinate on the Y axis for the view
457         * @param majorX      the spacing between two major grid lines on the X axis
458         * @param minorCountX the number of minor grid lines betweek two major
459         *                    grid lines on the X axis
460         * @param majorY      the spacing between two major grid lines on the Y axis
461         * @param minorCountY the number of minor grid lines betweek two major
462         *                    grid lines on the Y axis
463         * @throws IllegalArgumentException if minX >= maxX or minY >= maxY or
464         *                                  minorCountX < 0 or minorCountY < 0 or
465         *                                  majorX <= 0.0 or majorY <= 0.0
466         */
467        public JXGraph(double originX, double originY,
468                       double minX,    double maxX,
469                       double minY,    double maxY,
470                       double majorX,  int minorCountX,
471                       double majorY,  int minorCountY) {
472            if (minX >= maxX) {
473                throw new IllegalArgumentException("minX must be < to maxX");
474            }
475            
476            if (minY >= maxY) {
477                throw new IllegalArgumentException("minY must be < to maxY");
478            }
479            
480            if (minorCountX < 0) {
481                throw new IllegalArgumentException("minorCountX must be >= 0");
482            }
483            
484            if (minorCountY < 0) {
485                throw new IllegalArgumentException("minorCountY must be >= 0");
486            }
487            
488            if (majorX <= 0.0) {
489                throw new IllegalArgumentException("majorX must be > 0.0");
490            }
491            
492            if (majorY <= 0.0) {
493                throw new IllegalArgumentException("majorY must be > 0.0");
494            }
495            
496            this.originX = originX;
497            this.originY = originY;
498    
499            this.minX = minX;
500            this.maxX = maxX;
501            this.minY = minY;
502            this.maxY = maxY;
503            
504            this.defaultView = new Rectangle2D.Double(minX, minY,
505                maxX - minX, maxY - minY);
506            
507            this.setMajorX(this.defaultMajorX = majorX);
508            this.setMinorCountX(minorCountX);
509            this.setMajorY(this.defaultMajorY = majorY);
510            this.setMinorCountY(minorCountY);
511            
512            this.plots = new LinkedList<DrawablePlot>();
513            
514            this.mainFormatter = NumberFormat.getInstance();
515            this.mainFormatter.setMaximumFractionDigits(2);
516            
517            this.secondFormatter = new DecimalFormat("0.##E0");
518            
519            resetHandler = new ResetHandler();
520            addMouseListener(resetHandler);
521            panHandler = new PanHandler();
522            addMouseListener(panHandler);
523            panMotionHandler = new PanMotionHandler();
524            addMouseMotionListener(panMotionHandler);
525            zoomHandler = new ZoomHandler();
526            addMouseWheelListener(zoomHandler);
527            
528            setBackground(Color.WHITE);
529            setForeground(Color.BLACK);
530        }
531    
532        /**
533         * {@inheritDoc}
534         */
535        @Override
536       public boolean isOpaque() {
537            if (!isBackgroundPainted()) {
538                return false;
539            }
540            return super.isOpaque();
541        }
542        
543        /**
544         * {@inheritDoc}
545         * @see #setInputEnabled(boolean)
546         */
547        @Override
548        public void setEnabled(boolean enabled) {
549            super.setEnabled(enabled);
550            setInputEnabled(enabled);
551        }
552    
553        /**
554         * <p>Enables or disables user input on the component. When user input is
555         * enabled, panning, zooming and view resetting. Disabling input will
556         * prevent the user from modifying the currently displayed view.<p>
557         * <p>Calling {@link #setEnabled(boolean)} disables the component in the
558         * Swing hierarchy and invokes this method.</p>
559         *
560         * @param enabled true if user input must be enabled, false otherwise
561         * @see #setEnabled(boolean)
562         * @see #isInputEnabled()
563         */
564        public void setInputEnabled(boolean enabled) {
565            if (inputEnabled != enabled) {
566                this.inputEnabled = inputEnabled;
567    
568                if (enabled) {
569                    addMouseListener(resetHandler);
570                    addMouseListener(panHandler);
571                    addMouseMotionListener(panMotionHandler);
572                    addMouseWheelListener(zoomHandler);
573                } else {
574                    removeMouseListener(resetHandler);
575                    removeMouseListener(panHandler);
576                    removeMouseMotionListener(panMotionHandler);
577                    removeMouseWheelListener(zoomHandler);
578                }
579            }
580        }
581        
582        /**
583         * <p>Defines whether or not user input is accepted and managed by this
584         * component. The component is always created with user input enabled.</p>
585         *
586         * @return true if user input is enabled, false otherwise
587         * @see #setInputEnabled(boolean)
588         */
589        public boolean isInputEnabled() {
590            return inputEnabled;
591        }
592        
593        /**
594         * <p>Defines whether or not axis labels are painted by this component.
595         * The component is always created with text painting enabled.</p>
596         *
597         * @return true if axis labels are painted, false otherwise
598         * @see #setTextPainted(boolean)
599         * @see #getForeground()
600         */
601        public boolean isTextPainted() {
602            return textPainted;
603        }
604    
605        /**
606         * <p>Enables or disables the painting of axis labels depending on the
607         * value of the parameter. Text painting is enabled by default.</p>
608         * 
609         * @param textPainted if true, axis labels are painted
610         * @see #isTextPainted()
611         * @see #setForeground(Color)
612         */
613        public void setTextPainted(boolean textPainted) {
614            this.textPainted = textPainted;
615        }
616    
617        /**
618         * <p>Defines whether or not grids lines are painted by this component.
619         * The component is always created with grid lines painting enabled.</p>
620         *
621         * @return true if grid lines are painted, false otherwise
622         * @see #setGridPainted(boolean)
623         * @see #getMajorGridColor()
624         * @see #getMinorGridColor()
625         */
626        public boolean isGridPainted() {
627            return gridPainted;
628        }
629    
630        /**
631         * <p>Enables or disables the painting of grid lines depending on the
632         * value of the parameter. Grid painting is enabled by default.</p>
633         * 
634         * @param gridPainted if true, axis labels are painted
635         * @see #isGridPainted()
636         * @see #setMajorGridColor(Color)
637         * @see #setMinorGridColor(Color)
638         */
639        public void setGridPainted(boolean gridPainted) {
640            this.gridPainted = gridPainted;
641        }
642    
643        /**
644         * <p>Defines whether or not the graph main axis is painted by this
645         * component. The component is always created with main axis painting
646         * enabled.</p>
647         *
648         * @return true if main axis is painted, false otherwise
649         * @see #setTextPainted(boolean)
650         * @see #getAxisColor()
651         */
652        public boolean isAxisPainted() {
653            return axisPainted;
654        }
655    
656        /**
657         * <p>Enables or disables the painting of main axis depending on the
658         * value of the parameter. Axis painting is enabled by default.</p>
659         * 
660         * @param axisPainted if true, axis labels are painted
661         * @see #isAxisPainted()
662         * @see #setAxisColor(Color)
663         */
664        public void setAxisPainted(boolean axisPainted) {
665            this.axisPainted = axisPainted;
666        }
667    
668        /**
669         * <p>Defines whether or not the background painted by this component.
670         * The component is always created with background painting enabled.
671         * When background painting is disabled, background painting is deferred
672         * to the parent class.</p>
673         *
674         * @return true if background is painted, false otherwise
675         * @see #setBackgroundPainted(boolean)
676         * @see #getBackground()
677         */
678        public boolean isBackgroundPainted() {
679            return backPainted;
680        }
681    
682        /**
683         * <p>Enables or disables the painting of background depending on the
684         * value of the parameter. Background painting is enabled by default.</p>
685         * 
686         * @param backPainted if true, axis labels are painted
687         * @see #isBackgroundPainted()
688         * @see #setBackground(Color)
689         */
690        public void setBackgroundPainted(boolean backPainted) {
691            this.backPainted = backPainted;
692        }
693        
694        /**
695         * <p>Gets the major grid lines color of this component.</p>
696         *
697         * @return this component's major grid lines color
698         * @see #setMajorGridColor(Color)
699         * @see #setGridPainted(boolean)
700         */
701        public Color getMajorGridColor() {
702            return majorGridColor;
703        }
704    
705        /**
706         * <p>Sets the color of major grid lines on this component. The color
707         * can be translucent.</p>
708         *
709         * @param majorGridColor the color to become this component's major grid
710         *                       lines color
711         * @throws IllegalArgumentException if the specified color is null
712         * @see #getMajorGridColor()
713         * @see #isGridPainted()
714         */
715        public void setMajorGridColor(Color majorGridColor) {
716            if (majorGridColor == null) {
717                throw new IllegalArgumentException("Color cannot be null.");
718            }
719            
720            this.majorGridColor = majorGridColor;
721        }
722    
723        /**
724         * <p>Gets the minor grid lines color of this component.</p>
725         *
726         * @return this component's minor grid lines color
727         * @see #setMinorGridColor(Color)
728         * @see #setGridPainted(boolean)
729         */
730        public Color getMinorGridColor() {
731            return minorGridColor;
732        }
733    
734        /**
735         * <p>Sets the color of minor grid lines on this component. The color
736         * can be translucent.</p>
737         *
738         * @param minorGridColor the color to become this component's minor grid
739         *                       lines color
740         * @throws IllegalArgumentException if the specified color is null
741         * @see #getMinorGridColor()
742         * @see #isGridPainted()
743         */
744        public void setMinorGridColor(Color minorGridColor) {
745            if (minorGridColor == null) {
746                throw new IllegalArgumentException("Color cannot be null.");
747            }
748            this.minorGridColor = minorGridColor;
749        }
750    
751        /**
752         * <p>Gets the main axis color of this component.</p>
753         *
754         * @return this component's main axis color
755         * @see #setAxisColor(Color)
756         * @see #setGridPainted(boolean)
757         */
758        public Color getAxisColor() {
759            return axisColor;
760        }
761    
762        /**
763         * <p>Sets the color of main axis on this component. The color
764         * can be translucent.</p>
765         *
766         * @param axisColor the color to become this component's main axis color
767         * @throws IllegalArgumentException if the specified color is null
768         * @see #getAxisColor()
769         * @see #isAxisPainted()
770         */
771        public void setAxisColor(Color axisColor) {
772            if (axisColor == null) {
773                throw new IllegalArgumentException("Color cannot be null.");
774            }
775            this.axisColor = axisColor;
776        }
777        
778        /**
779         * <p>Gets the distance, in graph units, between two major grid lines on
780         * the X axis.</p>
781         *
782         * @return the spacing between two major grid lines on the X axis
783         * @see #setMajorX(double)
784         * @see #getMajorY()
785         * @see #setMajorY(double)
786         * @see #getMinorCountX()
787         * @see #setMinorCountX(int)
788         */
789        public double getMajorX() {
790            return majorX;
791        }
792    
793        /**
794         * <p>Sets the distance, in graph units, between two major grid lines on
795         * the X axis.</p>
796         *
797         * @param majorX the requested spacing between two major grid lines on the
798         *               X axis
799         * @throws IllegalArgumentException if majorX is <= 0.0d
800         * @see #getMajorX()
801         * @see #getMajorY()
802         * @see #setMajorY(double)
803         * @see #getMinorCountX()
804         * @see #setMinorCountX(int)
805         */
806        public void setMajorX(double majorX) {
807            if (majorX <= 0.0) {
808                throw new IllegalArgumentException("majorX must be > 0.0");
809            }
810            
811            this.majorX = majorX;
812            this.defaultMajorX = majorX;
813            repaint();
814        }
815        
816        /**
817         * <p>Gets the number of minor grid lines between two major grid lines
818         * on the X axis.</p>
819         *
820         * @return the number of minor grid lines between two major grid lines
821         * @see #setMinorCountX(int)
822         * @see #getMinorCountY()
823         * @see #setMinorCountY(int)
824         * @see #getMajorX()
825         * @see #setMajorX(double)
826         */
827        public int getMinorCountX() {
828            return minorCountX;
829        }
830    
831        /**
832         * <p>Sets the number of minor grid lines between two major grid lines on
833         * the X axis.</p>
834         *
835         * @param minorCountX the number of minor grid lines between two major grid
836         *                    lines on the X axis
837         * @throws IllegalArgumentException if minorCountX is < 0
838         * @see #getMinorCountX()
839         * @see #getMinorCountY()
840         * @see #setMinorCountY(int)
841         * @see #getMajorX()
842         * @see #setMajorX(double)
843         */
844        public void setMinorCountX(int minorCountX) {
845            if (minorCountX < 0) {
846                throw new IllegalArgumentException("minorCountX must be >= 0");
847            }
848    
849            this.minorCountX = minorCountX;
850            repaint();
851        }
852    
853        /**
854         * <p>Gets the distance, in graph units, between two major grid lines on
855         * the Y axis.</p>
856         *
857         * @return the spacing between two major grid lines on the Y axis
858         * @see #setMajorY(double)
859         * @see #getMajorX()
860         * @see #setMajorX(double)
861         * @see #getMinorCountY()
862         * @see #setMinorCountY(int)
863         */
864        public double getMajorY() {
865            return majorY;
866        }
867    
868        /**
869         * <p>Sets the distance, in graph units, between two major grid lines on
870         * the Y axis.</p>
871         *
872         * @param majorY the requested spacing between two major grid lines on the
873         *               Y axis
874         * @throws IllegalArgumentException if majorY is <= 0.0d
875         * @see #getMajorY()
876         * @see #getMajorX()
877         * @see #setMajorX(double)
878         * @see #getMinorCountY()
879         * @see #setMinorCountY(int)
880         */
881        public void setMajorY(double majorY) {
882            if (majorY <= 0.0) {
883                throw new IllegalArgumentException("majorY must be > 0.0");
884            }
885    
886            this.majorY = majorY;
887            this.defaultMajorY = majorY;
888            repaint();
889        }
890    
891        /**
892         * <p>Gets the number of minor grid lines between two major grid lines
893         * on the Y axis.</p>
894         *
895         * @return the number of minor grid lines between two major grid lines
896         * @see #setMinorCountY(int)
897         * @see #getMinorCountX()
898         * @see #setMinorCountX(int)
899         * @see #getMajorY()
900         * @see #setMajorY(double)
901         */
902        public int getMinorCountY() {
903            return minorCountY;
904        }
905    
906        /**
907         * <p>Sets the number of minor grid lines between two major grid lines on
908         * the Y axis.</p>
909         *
910         * @param minorCountY the number of minor grid lines between two major grid
911         *                    lines on the Y axis
912         * @throws IllegalArgumentException if minorCountY is < 0
913         * @see #getMinorCountY()
914         * @see #getMinorCountX()
915         * @see #setMinorCountX(int)
916         * @see #getMajorY()
917         * @see #setMajorY(double)
918         */
919        public void setMinorCountY(int minorCountY) {
920            if (minorCountY < 0) {
921                throw new IllegalArgumentException("minorCountY must be >= 0");
922            }
923    
924            this.minorCountY = minorCountY;
925            repaint();
926        }
927    
928        /**
929         * <p>Sets the view and the origin of the graph at the same time. The view
930         * minimum boundaries are defined by the location of the rectangle passed
931         * as parameter. The width and height of the rectangle define the distance
932         * between the minimum and maximum boundaries:</p>
933         *
934         * <ul>
935         *   <li><i>minX</i>: bounds.getX()</li>
936         *   <li><i>minY</i>: bounds.getY()</li>
937         *   <li><i>maxY</i>: bounds.getMaxX() (minX + bounds.getWidth())</li>
938         *   <li><i>maxX</i>: bounds.getMaxY() (minY + bounds.getHeight())</li>
939         * </ul>
940         *
941         * <p>The origin is located at the center of the view. Its coordinates are
942         * defined by calling bounds.getCenterX() and bounds.getCenterY().</p>
943         *
944         * @param bounds the rectangle defining the graph's view and its origin
945         * @see #getView()
946         * @see #setView(Rectangle2D)
947         * @see #getOrigin()
948         * @see #setOrigin(Point2D)
949         */
950        public void setViewAndOrigin(Rectangle2D bounds) {
951            setView(bounds);
952            setOrigin(new Point2D.Double(bounds.getCenterX(), bounds.getCenterY()));
953        }
954        
955        /**
956         * <p>Sets the view of the graph. The view minimum boundaries are defined by
957         * the location of the rectangle passed as parameter. The width and height
958         * of the rectangle define the distance between the minimum and maximum
959         * boundaries:</p>
960         *
961         * <ul>
962         *   <li><i>minX</i>: bounds.getX()</li>
963         *   <li><i>minY</i>: bounds.getY()</li>
964         *   <li><i>maxY</i>: bounds.getMaxX() (minX + bounds.getWidth())</li>
965         *   <li><i>maxX</i>: bounds.getMaxY() (minY + bounds.getHeight())</li>
966         * </ul>
967         *
968         * <p>If the specificed view is null, nothing happens.</p>
969         *
970         * <p>Calling this method leaves the origin intact.</p>
971         *
972         * @param bounds the rectangle defining the graph's view and its origin
973         * @see #getView()
974         * @see #setViewAndOrigin(Rectangle2D)
975         */
976        public void setView(Rectangle2D bounds) {
977            if (bounds == null) {
978                return;
979            }
980    
981            defaultView = new Rectangle2D.Double(bounds.getX(), bounds.getY(),
982                bounds.getWidth(), bounds.getHeight());
983                    
984            minX = defaultView.getMinX();
985            maxX = defaultView.getMaxX();
986            minY = defaultView.getMinY();
987            maxY = defaultView.getMaxY();
988            
989            majorX = defaultMajorX;
990            majorY = defaultMajorY;
991            
992            repaint();
993        }
994    
995        /**
996         * <p>Gets the view of the graph. The returned rectangle defines the bounds
997         * of the view as follows:</p>
998         *
999         * <ul>
1000         *   <li><i>minX</i>: bounds.getX()</li>
1001         *   <li><i>minY</i>: bounds.getY()</li>
1002         *   <li><i>maxY</i>: bounds.getMaxX() (minX + bounds.getWidth())</li>
1003         *   <li><i>maxX</i>: bounds.getMaxY() (minY + bounds.getHeight())</li>
1004         * </ul>
1005         *
1006         * @return the rectangle corresponding to the current view of the graph
1007         * @see #setView(Rectangle2D)
1008         * @see #setViewAndOrigin(Rectangle2D)
1009         */
1010        public Rectangle2D getView() {
1011            return new Rectangle2D.Double(minX, minY, maxX - minX, maxY - minY);
1012        }
1013        
1014        /**
1015         * <p>Resets the view to the default view if it has been changed by the user
1016         * by panning and zooming. The default view is defined by the view last
1017         * specified in a constructor call or a call to the methods
1018         * {@link #setView(Rectangle2D)} and
1019         * {@link #setViewAndOrigin(Rectangle2D)}.</p>
1020         *
1021         * @see #setView(Rectangle2D)
1022         * @see #setViewAndOrigin(Rectangle2D)
1023         */
1024        public void resetView() {
1025            setView(defaultView);
1026        }
1027    
1028        /**
1029         * <p>Sets the origin of the graph. The coordinates of the origin are
1030         * defined by the coordinates of the point passed as parameter.</p>
1031         *
1032         * <p>If the specificed view is null, nothing happens.</p>
1033         *
1034         * <p>Calling this method leaves the view intact.</p>
1035         *
1036         * @param origin the coordinates of the new origin
1037         * @see #getOrigin()
1038         * @see #setViewAndOrigin(Rectangle2D)
1039         */
1040        public void setOrigin(Point2D origin) {
1041            if (origin == null) {
1042                return;
1043            }
1044    
1045            originX = origin.getX();
1046            originY = origin.getY();
1047    
1048            repaint();
1049        }
1050    
1051        /**
1052         * <p>Gets the origin coordinates of the graph. The coordinates are
1053         * represented as an instance of <code>Point2D</code> and stored in
1054         * <code>double</code> format.</p>
1055    
1056         * @return the origin coordinates in double format
1057         * @see #setOrigin(Point2D)
1058         * @see #setViewAndOrigin(Rectangle2D)
1059         */
1060        public Point2D getOrigin() {
1061            return new Point2D.Double(originX, originY);
1062        }
1063    
1064        /**
1065         * <p>Adds one or more plots to the graph. These plots are associated to
1066         * a color used to draw them.</p>
1067         * 
1068         * <p>If plotList is null or empty, nothing happens.</p>
1069         *
1070         * <p>This method is not thread safe and should be called only from the
1071         * EDT.</p>
1072         *
1073         * @param color    the color to be usd to draw the plots
1074         * @param plotList the list of plots to add to the graph
1075         * @throws IllegalArgumentException if color is null
1076         * @see #removePlots(JXGraph.Plot...)
1077         * @see #removeAllPlots()
1078         */
1079        public void addPlots(Color color, Plot... plotList) {
1080            if (color == null) {
1081                throw new IllegalArgumentException("Plots color cannot be null.");
1082            }
1083            
1084            if (plotList == null) {
1085                return;
1086            }
1087    
1088            for (Plot plot : plotList) {
1089                if (plot != null && !plots.contains(plot)) {
1090                    plot.addPropertyChangeListener(this);
1091                    plots.add(new DrawablePlot(plot, color));
1092                }
1093            }
1094            repaint();
1095        }
1096    
1097        /**
1098         * <p>Removes the specified plots from the graph. Plots to be removed
1099         * are identified by identity. This means you cannot remove a plot by
1100         * passing a clone or another instance of the same subclass of
1101         * {@link JXGraph.Plot}.</p>
1102         *
1103         * <p>If plotList is null or empty, nothing happens.</p>
1104         *
1105         * <p>This method is not thread safe and should be called only from the
1106         * EDT.</p>
1107         *
1108         * @param plotList the list of plots to be removed from the graph
1109         * @see #removeAllPlots()
1110         * @see #addPlots(Color, JXGraph.Plot...)
1111         */
1112        public void removePlots(Plot... plotList) {
1113            if (plotList == null) {
1114                return;
1115            }
1116    
1117            for (Plot plot : plotList) {
1118                if (plot != null) {
1119                    DrawablePlot toRemove = null;
1120                    for (DrawablePlot drawable: plots) {
1121                        if (drawable.getEquation() == plot) {
1122                            toRemove = drawable;
1123                            break;
1124                        }
1125                    }
1126    
1127                    if (toRemove != null) {
1128                        plot.removePropertyChangeListener(this);
1129                        plots.remove(toRemove);
1130                    }
1131                }
1132            }
1133            repaint();
1134        }
1135    
1136        /**
1137         * <p>Removes all the plots currently associated with this graph.</p>
1138         *
1139         * <p>This method is not thread safe and should be called only from the
1140         * EDT.</p>
1141         *
1142         * @see #removePlots(JXGraph.Plot...)
1143         * @see #addPlots(Color, JXGraph.Plot...)
1144         */
1145        public void removeAllPlots() {
1146            plots.clear();
1147            repaint();
1148        }
1149    
1150        /**
1151         * {@inheritDoc}
1152         */
1153        @Override
1154        public Dimension getPreferredSize() {
1155            return new Dimension(400, 400);
1156        }
1157    
1158        /**
1159         * {@inheritDoc}
1160         */
1161        public void propertyChange(PropertyChangeEvent evt) {
1162            repaint();
1163        }
1164        
1165        /**
1166         * <p>Converts a position, in graph units, from the Y axis into a pixel
1167         * coordinate. For instance, if you defined the origin so it appears at the
1168         * exact center of the view, calling
1169         * <code>yPositionToPixel(getOriginY())</code> will return a value
1170         * approximately equal to <code>getHeight() / 2.0</code>.</p>
1171         *
1172         * @param position the Y position to be converted into pixels
1173         * @return the coordinate in pixels of the specified graph Y position
1174         * @see #xPositionToPixel(double)
1175         * @see #yPixelToPosition(double)
1176         */
1177        protected double yPositionToPixel(double position) {
1178            double height = (double) getHeight();
1179            return height - ((position - minY) * height / (maxY - minY));
1180        }
1181    
1182        /**
1183         * <p>Converts a position, in graph units, from the X axis into a pixel
1184         * coordinate. For instance, if you defined the origin so it appears at the
1185         * exact center of the view, calling
1186         * <code>xPositionToPixel(getOriginX())</code> will return a value
1187         * approximately equal to <code>getWidth() / 2.0</code>.</p>
1188         *
1189         * @param position the X position to be converted into pixels
1190         * @return the coordinate in pixels of the specified graph X position
1191         * @see #yPositionToPixel(double)
1192         * @see #xPixelToPosition(double)
1193         */
1194        protected double xPositionToPixel(double position) {
1195            return (position - minX) * (double) getWidth() / (maxX - minX);
1196        }
1197        
1198        /**
1199         * <p>Converts a pixel coordinate from the X axis into a graph position, in
1200         * graph units. For instance, if you defined the origin so it appears at the
1201         * exact center of the view, calling
1202         * <code>xPixelToPosition(getWidth() / 2.0)</code> will return a value
1203         * approximately equal to <code>getOriginX()</code>.</p>
1204         *
1205         * @param pixel the X pixel coordinate to be converted into a graph position
1206         * @return the graph X position of the specified pixel coordinate
1207         * @see #yPixelToPosition(double)
1208         * @see #xPositionToPixel(double)
1209         */
1210        protected double xPixelToPosition(double pixel) {
1211            double axisV = xPositionToPixel(originX);
1212            return (pixel - axisV) * (maxX - minX) / (double) getWidth();
1213        }
1214        
1215        /**
1216         * <p>Converts a pixel coordinate from the Y axis into a graph position, in
1217         * graph units. For instance, if you defined the origin so it appears at the
1218         * exact center of the view, calling
1219         * <code>yPixelToPosition(getHeight() / 2.0)</code> will return a value
1220         * approximately equal to <code>getOriginY()</code>.</p>
1221         *
1222         * @param pixel the Y pixel coordinate to be converted into a graph position
1223         * @return the graph Y position of the specified pixel coordinate
1224         * @see #xPixelToPosition(double)
1225         * @see #yPositionToPixel(double)
1226         */
1227        protected double yPixelToPosition(double pixel) {
1228            double axisH = yPositionToPixel(originY);
1229            return (getHeight() - pixel - axisH) * (maxY - minY) / (double) getHeight();
1230        }
1231    
1232        /**
1233         * {@inheritDoc}
1234         */
1235        @Override
1236        protected void paintComponent(Graphics g) {
1237            if (!isVisible()) {
1238                return;
1239            }
1240            
1241            Graphics2D g2 = (Graphics2D) g;
1242            setupGraphics(g2);
1243    
1244            paintBackground(g2);
1245            drawGrid(g2);
1246            drawAxis(g2);
1247            drawPlots(g2);
1248            drawLabels(g2);
1249            
1250            paintExtra(g2);
1251        }
1252        
1253        /**
1254         * <p>This painting method is meant to be overriden by subclasses of
1255         * <code>JXGraph</code>. This method is called after all the painting
1256         * is done. By overriding this method, a subclass can display extra
1257         * information on top of the graph.</p>
1258         * <p>The graphics surface passed as parameter is configured by
1259         * {@link #setupGraphics(Graphics2D)}.</p>
1260         *
1261         * @param g2 the graphics surface on which the graph is drawn
1262         * @see #setupGraphics(Graphics2D)
1263         * @see #xPixelToPosition(double)
1264         * @see #yPixelToPosition(double)
1265         * @see #xPositionToPixel(double)
1266         * @see #yPositionToPixel(double)
1267         */
1268        protected void paintExtra(Graphics2D g2) {
1269        }
1270    
1271        // Draw all the registered plots with the appropriate color.
1272        private void drawPlots(Graphics2D g2) {
1273            for (DrawablePlot drawable: plots) {
1274                g2.setColor(drawable.getColor());
1275                drawPlot(g2, drawable.getEquation());
1276            }
1277        }
1278    
1279        // Draw a single plot as a GeneralPath made of straight lines.
1280        private void drawPlot(Graphics2D g2, Plot equation) {
1281            float x = 0.0f;
1282            float y = (float) yPositionToPixel(equation.compute(xPixelToPosition(0.0)));
1283            
1284            GeneralPath path = new GeneralPath();
1285            path.moveTo(x, y);
1286            
1287            float width = (float) getWidth();
1288            for (x = 0.0f; x < width; x += 1.0f) {
1289                double position = xPixelToPosition(x);
1290                y = (float) yPositionToPixel(equation.compute(position));
1291                path.lineTo(x, y);
1292            }
1293            
1294            g2.draw(path);
1295        }
1296    
1297        // Draws the grid. First draw the vertical lines, then the horizontal lines.
1298        private void drawGrid(Graphics2D g2) {
1299            Stroke stroke = g2.getStroke();
1300    
1301            if (isGridPainted()) {
1302                drawVerticalGrid(g2);
1303                drawHorizontalGrid(g2);
1304            }
1305    
1306            g2.setStroke(stroke);
1307        }
1308        
1309        // Draw all labels. First draws labels on the horizontal axis, then labels
1310        // on the vertical axis. If the axis is set not to be painted, this
1311        // method draws the origin as a straight cross.
1312        private void drawLabels(Graphics2D g2) {
1313            if (isTextPainted()) {
1314                double axisH = yPositionToPixel(originY);
1315                double axisV = xPositionToPixel(originX);
1316    
1317                if (!isAxisPainted()) {
1318                    Stroke stroke = g2.getStroke();
1319                    g2.setStroke(new BasicStroke(STROKE_AXIS));
1320                    g2.setColor(getAxisColor());
1321                    g2.drawLine((int) axisV - 3, (int) axisH,
1322                        (int) axisV + 3, (int) axisH);
1323                    g2.drawLine((int) axisV, (int) axisH - 3,
1324                        (int) axisV, (int) axisH + 3);
1325                    g2.setStroke(stroke);
1326                }
1327    
1328                g2.setColor(getForeground());
1329                FontMetrics metrics = g2.getFontMetrics();
1330                g2.drawString(format(originX) + "; " +
1331                    format(originY), (int) axisV + 5,
1332                    (int) axisH + metrics.getHeight());
1333            
1334                drawHorizontalAxisLabels(g2);
1335                drawVerticalAxisLabels(g2);
1336            }
1337        }
1338        
1339        // Draws labels on the vertical axis. First draws labels below the origin,
1340        // then draw labels on top of the origin.
1341        private void drawVerticalAxisLabels(Graphics2D g2) {
1342            double axisV = xPositionToPixel(originX);
1343    
1344            double startY = Math.floor((minY - originY) / majorY) * majorY;
1345            for (double y = startY; y < maxY + majorY; y += majorY) {
1346                if (((y - majorY / 2.0) < originY) &&
1347                    ((y + majorY / 2.0) > originY)) {
1348                    continue;
1349                }
1350                
1351                int position = (int) yPositionToPixel(y);
1352                g2.drawString(format(y), (int) axisV + 5, position);
1353            }
1354        }
1355        
1356        // Draws the horizontal lines of the grid. Draws both minor and major
1357        // grid lines.
1358        private void drawHorizontalGrid(Graphics2D g2) {
1359            double minorSpacing = majorY / getMinorCountY();
1360            double axisV = xPositionToPixel(originX);
1361            
1362            Stroke gridStroke = new BasicStroke(STROKE_GRID);
1363            Stroke axisStroke = new BasicStroke(STROKE_AXIS);
1364            
1365            Rectangle clip = g2.getClipBounds();
1366            
1367            int position = 0;
1368            
1369            if (!isAxisPainted()) {
1370                position = (int) xPositionToPixel(originX);
1371                if (position >= clip.x && position <= clip.x + clip.width) {
1372                    g2.setColor(getMajorGridColor());
1373                    g2.drawLine(position, clip.y, position, clip.y + clip.height);
1374                }
1375            }
1376    
1377            double startY = Math.floor((minY - originY) / majorY) * majorY;
1378            for (double y = startY; y < maxY + majorY; y += majorY) {
1379                g2.setStroke(gridStroke);
1380                g2.setColor(getMinorGridColor());
1381                for (int i = 0; i < getMinorCountY(); i++) {
1382                    position = (int) yPositionToPixel(y - i * minorSpacing);
1383                    if (position >= clip.y && position <= clip.y + clip.height) {
1384                        g2.drawLine(clip.x, position, clip.x + clip.width, position);    
1385                    }
1386                }
1387    
1388                position = (int) yPositionToPixel(y);
1389                if (position >= clip.y && position <= clip.y + clip.height) {
1390                    g2.setColor(getMajorGridColor());
1391                    g2.drawLine(clip.x, position, clip.x + clip.width, position);
1392    
1393                    g2.setStroke(axisStroke);
1394                    g2.setColor(getAxisColor());
1395                    g2.drawLine((int) axisV - 3, position, (int) axisV + 3, position);
1396                }
1397            }
1398        }
1399    
1400        // Draws labels on the horizontal axis. First draws labels on the right of
1401        // the origin, then on the left.
1402        private void drawHorizontalAxisLabels(Graphics2D g2) {
1403            double axisH = yPositionToPixel(originY);
1404            FontMetrics metrics = g2.getFontMetrics();
1405            
1406            double startX = Math.floor((minX - originX) / majorX) * majorX;
1407            for (double x = startX; x < maxX + majorX; x += majorX) {
1408                if (((x - majorX / 2.0) < originX) &&
1409                    ((x + majorX / 2.0) > originX)) {
1410                    continue;
1411                }
1412                
1413                int position = (int) xPositionToPixel(x);
1414                g2.drawString(format(x), position,
1415                    (int) axisH + metrics.getHeight());
1416            }
1417        }
1418        
1419        // Draws the vertical lines of the grid. Draws both minor and major
1420        // grid lines.
1421        private void drawVerticalGrid(Graphics2D g2) {
1422            double minorSpacing = majorX / getMinorCountX();
1423            double axisH = yPositionToPixel(originY);
1424            
1425            Stroke gridStroke = new BasicStroke(STROKE_GRID);
1426            Stroke axisStroke = new BasicStroke(STROKE_AXIS);
1427            
1428            Rectangle clip = g2.getClipBounds();
1429            
1430            int position = 0;
1431            if (!isAxisPainted()) {
1432                position = (int) yPositionToPixel(originY);
1433                if (position >= clip.y && position <= clip.y + clip.height) {
1434                    g2.setColor(getMajorGridColor());
1435                    g2.drawLine(clip.x, position, clip.x + clip.width, position);
1436                }
1437            }
1438            
1439            double startX = Math.floor((minX - originX) / majorX) * majorX;
1440            for (double x = startX; x < maxX + majorX; x += majorX) {
1441                g2.setStroke(gridStroke);
1442                g2.setColor(getMinorGridColor());
1443                for (int i = 0; i < getMinorCountX(); i++) {
1444                    position = (int) xPositionToPixel(x - i * minorSpacing);
1445                    if (position >= clip.x && position <= clip.x + clip.width) {
1446                        g2.drawLine(position, clip.y, position, clip.y + clip.height);
1447                    }
1448                }
1449                
1450                position = (int) xPositionToPixel(x);
1451                if (position >= clip.x && position <= clip.x + clip.width) {
1452                    g2.setColor(getMajorGridColor());
1453                    g2.drawLine(position, clip.y, position, clip.y + clip.height);
1454    
1455                    g2.setStroke(axisStroke);
1456                    g2.setColor(getAxisColor());
1457                    g2.drawLine(position, (int) axisH - 3, position, (int) axisH + 3);
1458                }
1459            }
1460        }
1461    
1462        // Drase the main axis.
1463        private void drawAxis(Graphics2D g2) {
1464            if (!isAxisPainted()) {
1465                return;
1466            }
1467            
1468            double axisH = yPositionToPixel(originY);
1469            double axisV = xPositionToPixel(originX);
1470            
1471            Rectangle clip = g2.getClipBounds();
1472            
1473            g2.setColor(getAxisColor());
1474            Stroke stroke = g2.getStroke();
1475            g2.setStroke(new BasicStroke(STROKE_AXIS));
1476    
1477            if (axisH >= clip.y && axisH <= clip.y + clip.height) {
1478                g2.drawLine(clip.x, (int) axisH, clip.x + clip.width, (int) axisH);
1479            }
1480            if (axisV >= clip.x && axisV <= clip.x + clip.width) {
1481                g2.drawLine((int) axisV, clip.y, (int) axisV, clip.y + clip.height);
1482            }
1483    
1484            g2.setStroke(stroke);
1485        }
1486    
1487        /**
1488         * <p>This method is called by the component prior to any drawing operation
1489         * to configure the drawing surface. The default implementation enables
1490         * antialiasing on the graphics.</p>
1491         * <p>This method can be overriden by subclasses to modify the drawig
1492         * surface before any painting happens.</p>
1493         *
1494         * @param g2 the graphics surface to set up
1495         * @see #paintExtra(Graphics2D)
1496         * @see #paintBackground(Graphics2D)
1497         */
1498        protected void setupGraphics(Graphics2D g2) {
1499            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
1500                                RenderingHints.VALUE_ANTIALIAS_ON);
1501        }
1502    
1503        /**
1504         * <p>This method is called by the component whenever it needs to paint
1505         * its background. The default implementation fills the background with
1506         * a solid color as defined by {@link #getBackground()}. Background painting
1507         * does not happen when {@link #isBackgroundPainted()} returns false.</p>
1508         * <p>It is recommended to subclasses to honor the contract defined by
1509         * {@link #isBackgroundPainted()} and {@link #setBackgroundPainted(boolean)}.
1510         *
1511         * @param g2 the graphics surface on which the background must be drawn
1512         * @see #setupGraphics(Graphics2D)
1513         * @see #paintExtra(Graphics2D)
1514         * @see #isBackgroundPainted()
1515         * @see #setBackgroundPainted(boolean)
1516         */
1517        protected void paintBackground(Graphics2D g2) {
1518            if (isBackgroundPainted()) {
1519                g2.setColor(getBackground());
1520                g2.fill(g2.getClipBounds());
1521            }
1522        }
1523        
1524        // Format a number with the appropriate number formatter. Numbers >= 0.01
1525        // and < 100 are formatted with a regular, 2-digits, numbers formatter.
1526        // Other numbers use a scientific notation given by a DecimalFormat instance
1527        private String format(double number) {
1528            boolean farAway = (number != 0.0d && Math.abs(number) < 0.01d) ||
1529                Math.abs(number) > 99.0d;
1530            return (farAway ? secondFormatter : mainFormatter).format(number);
1531        }
1532    
1533        /**
1534         * <p>A plot represents a mathematical transformation used by
1535         * {@link JXGraph}. When a plot belongs to a graph, the graph component
1536         * asks for the transformation of a value along the X axis. The resulting
1537         * value defines the Y coordinates at which the graph must draw a spot of
1538         * color.</p>
1539         *
1540         * <p>Here is a sample implemention of this class that draws a straight line
1541         * once added to a graph (it follows the well-known equation y=a.x+b):</p>
1542         *
1543         * <pre>
1544         * class LinePlot extends JXGraph.Plot {
1545         *     public double compute(double value) {
1546         *         return 2.0 * value + 1.0;
1547         *     }
1548         * }
1549         * </pre>
1550         *
1551         * <p>More information about plots usage can be found in {@link JXGraph} in
1552         * the section entitled <i>Plots</i>.</p>
1553         *
1554         * @see JXGraph
1555         * @see JXGraph#addPlots(Color, JXGraph.Plot...)
1556         */
1557        public abstract static class Plot {
1558            /**
1559             * <p>List of property change listeners listening to this plot's
1560             * instance. When this plot is added to an instance of
1561             * <code>JXGraph</code>, the <code>JXGraph</code> automatically becomes
1562             * a new property change listener of this plot.</p>
1563             */
1564            protected PropertyChangeSupport support;
1565    
1566            /**
1567             * <p>Creates a new, parameter-less plot.</p>
1568             */
1569            protected Plot() {
1570                this.support = new PropertyChangeSupport(this);
1571            }
1572            
1573            /**
1574             * <p>This method must return the result of a mathematical
1575             * transformation of its sole parameter.</p>
1576             * 
1577             * @param value a value along the X axis of the graph currently
1578             *              drawing this plot
1579             * @return the result of the mathematical transformation of value
1580             */
1581            public abstract double compute(double value);
1582    
1583            /**
1584             * <p>Registers a new property change listener with the current plot.</p>
1585             *
1586             * @param listener the new property change listener to be notified of
1587             *                 property changes
1588             */
1589            public void addPropertyChangeListener(PropertyChangeListener listener) {
1590                support.addPropertyChangeListener(listener);
1591            }
1592    
1593            /**
1594             * <p>Removes a new property change listener from the current plot.</p>
1595             *
1596             * @param listener the  property change listener to be removed
1597             */
1598            public void removePropertyChangeListener(PropertyChangeListener listener) {
1599                support.removePropertyChangeListener(listener);
1600            }
1601    
1602            /**
1603             * <p>Notifies property change listeners that a property has changed.
1604             * This method must be used to notify the graph that a parameter of the
1605             * mathematical transformation of this plot has changed. Failing to do
1606             * so, a plot will be refreshed when its internal parameters change.</p>
1607             *
1608             * @param propertyName the name of the property known to have changed
1609             * @param oldValue     the value of the property prior to the change
1610             * @param newValue     the value of the property after the change
1611             */
1612            protected void firePropertyChange(String propertyName,
1613                                              double oldValue, double newValue) {
1614                PropertyChangeEvent changeEvent =
1615                    new PropertyChangeEvent(this, propertyName, oldValue, newValue);
1616                for (PropertyChangeListener listener : support.getPropertyChangeListeners()) {
1617                    listener.propertyChange(changeEvent);
1618                }
1619            }
1620        }
1621    
1622        // Encapsulates a plot and its color. Avoids the use of a full-blown Map.
1623        private class DrawablePlot {
1624            private final Plot equation;
1625            private final Color color;
1626    
1627            private DrawablePlot(Plot equation, Color color) {
1628                this.equation = equation;
1629                this.color = color;
1630            }
1631            
1632            private Plot getEquation() {
1633                return equation;
1634            }
1635            
1636            private Color getColor() {
1637                return color;
1638            }
1639        }
1640        
1641        // Shrinks or expand the view depending on the mouse wheel direction.
1642        // When the wheel moves down, the view is expanded. Otherwise it is shrunk.
1643        private class ZoomHandler implements MouseWheelListener {
1644            public void mouseWheelMoved(MouseWheelEvent e) {
1645                double distanceX = maxX - minX;
1646                double distanceY = maxY - minY;
1647                
1648                double cursorX = minX + distanceX / 2.0;
1649                double cursorY = minY + distanceY / 2.0;
1650                
1651                int rotation = e.getWheelRotation();
1652                if (rotation < 0) {
1653                    distanceX /= ZOOM_MULTIPLIER;
1654                    distanceY /= ZOOM_MULTIPLIER;
1655    
1656                    majorX /= ZOOM_MULTIPLIER;
1657                    majorY /= ZOOM_MULTIPLIER;
1658                } else {
1659                    distanceX *= ZOOM_MULTIPLIER;
1660                    distanceY *= ZOOM_MULTIPLIER;
1661    
1662                    majorX *= ZOOM_MULTIPLIER;
1663                    majorY *= ZOOM_MULTIPLIER;
1664                }
1665                
1666                minX = cursorX - distanceX / 2.0;
1667                maxX = cursorX + distanceX / 2.0;
1668                minY = cursorY - distanceY / 2.0;
1669                maxY = cursorY + distanceY / 2.0;
1670                
1671                repaint();
1672            }
1673        }
1674        
1675        // Listens for a click on the middle button of the mouse and resets the view
1676        private class ResetHandler extends MouseAdapter {
1677            @Override
1678            public void mousePressed(MouseEvent e) {
1679                if (e.getButton() != MouseEvent.BUTTON2) {
1680                    return;
1681                }
1682                
1683                resetView();
1684            }
1685        }
1686        
1687        // Starts and ends drag gestures with mouse left button.
1688        private class PanHandler extends MouseAdapter {
1689            @Override
1690            public void mousePressed(MouseEvent e) {
1691                if (e.getButton() != MouseEvent.BUTTON1) {
1692                    return;
1693                }
1694                
1695                dragStart = e.getPoint();
1696                setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
1697            }
1698            
1699            @Override
1700            public void mouseReleased(MouseEvent e) {
1701                if (e.getButton() != MouseEvent.BUTTON1) {
1702                    return;
1703                }
1704    
1705                setCursor(Cursor.getDefaultCursor());
1706            }
1707        }
1708    
1709        // Handles drag gesture with the left mouse button and relocates the view
1710        // accordingly.
1711        private class PanMotionHandler extends MouseMotionAdapter {
1712            @Override
1713            public void mouseDragged(MouseEvent e) {
1714                Point dragEnd = e.getPoint();
1715    
1716                double distance = xPixelToPosition(dragEnd.getX()) -
1717                                  xPixelToPosition(dragStart.getX());
1718                minX = minX - distance;
1719                maxX = maxX - distance;
1720    
1721                distance = yPixelToPosition(dragEnd.getY()) -
1722                           yPixelToPosition(dragStart.getY());
1723                minY = minY - distance;
1724                maxY = maxY - distance;
1725                
1726                repaint();
1727                dragStart = dragEnd;
1728            }
1729        }
1730    }