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