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