/*
 * @(#)FunctionsPlotter.java  2.6.0  9 September 2007
 *
 * Copyright 2007
 * College of Computer and Information Science
 * Northeastern University
 * Boston, MA  02115
 *
 * The Java Power Tools software may be used for educational
 * purposes as long as this copyright notice is retained intact
 * at the top of all source files.
 *
 * To discuss possible commercial use of this software, 
 * contact Richard Rasala at Northeastern University, 
 * College of Computer and Information Science,
 * 617-373-2462 or rasala@ccs.neu.edu.
 *
 * The Java Power Tools software has been designed and built
 * in collaboration with Viera Proulx and Jeff Raab.
 *
 * Should this software be modified, the words "Modified from 
 * Original" must be included as a comment below this notice.
 *
 * All publication rights are retained.  This software or its 
 * documentation may not be published in any media either
 * in whole or in part without explicit permission.
 *
 * This software was created with support from Northeastern 
 * University and from NSF grant DUE-9950829.
 */

package edu.neu.ccs.gui;

import edu.neu.ccs.*;
import edu.neu.ccs.parser.*;

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import javax.swing.*;
import javax.swing.border.*;
import java.text.ParseException;
import java.text.DecimalFormat;

/**
 * <p>Class <code>FunctionsPlotter</code> is an end-user tool for
 * plotting functions of one parameter.  The tool provides two panes:</p>
 * 
 * <ul>
 *   <li>A plot pane in which the user can list the functions to be
 *       plotted, choose the range of x, optionally choose the range
 *       of y, and optionally choose the x or y grid spacing.</li>
 *   <li>A function definition pane to name and define functions to
 *       be plotted.  These functions are user functions that add to
 *       the functions already built into the <code>JPTParser</code>.
 *       This function definition pane is implemented using a
 *       <code>SimpleFunctionBuilder</code> object.</li>
 * </ul>
 * 
 * <p>Functions are plotted in different colors and a
 * <code>MultiColorView</code> object shows which colors are assigned
 * to which functions.  Using this view, the user can change the
 * default color settings.</p>
 * 
 * <p>As the mouse moves over the plot window, the world coordinates
 * of its position are displayed in text views below.  This allows a
 * user to obtain approximate values for points of interest in the
 * plot.</p>
 * 
 * <p>If the mouse is pressed in the plot window, then the world
 * coordinates of its position will be displayed as an (x,y) pair.
 * Normally, the midpoint of the baseline will be the point that was
 * pressed but if that would cause the (x,y) pair to display outside
 * of the plot pane then the position will be slightly adjusted.</p>
 * 
 * <p>For more fine-grained access to the plot data, a window that
 * shows all plot data points can be displayed.</p>
 * 
 * <p>In 2.6.0, the only public methods are the constructor that
 * creates the panel and the static main method that launches such
 * a panel in a frame.  All internal data and methods are protected,
 * however, so a developer may tweak settings in a derived class.</p>
 * 
 * <p>In future releases, we may expose functionality but that is a
 * question that requires further investigation.</p>
 * 
 * @author  Richard Rasala
 * @version 2.6.0
 * @since   2.6.0
 */
public class FunctionsPlotter
    extends BasePane
{
    
    /** The small gap for inner table panels. */
    protected int SMALLGAP = getGap();
    
    /** The large gap for outer table panels. */
    protected int LARGEGAP = 2 * SMALLGAP;
    
    /** The extra wide horizontal gap for a data table. */
    protected int EXTRAGAP = 20;
    
    /**
     * The empty border thickness used to create space
     * for titled borders. */
    protected int BORDER = 4;
    
    
    /** The width of fields for numeric values. */
    protected int VALUEWIDTH = getSmallFieldWidth();
    
    /** The width of the field for function names. */
    protected int NAMESWIDTH = getMediumFieldWidth();
    
    
    /** The size of the main plot square. */
    protected int PLOTSIZE = 480;
    
    /** The amount of inset on all sides of the plot square. */
    protected int PLOTINSET = 10;
    
    /** The size of the full plot square with insets. */
    protected int WINDOWSIZE = PLOTSIZE + 2 * PLOTINSET;
    
    
    /**
     * <p>The horizontal gap between data pixels in the plot.</p>
     * 
     * <p>In 2.6.0, this gap is set to 1, so plots are dense.</p>
     */
    protected int PIXELGAP = 1;
    
    
    /** The maximum width of a data table. */
    protected int MAXWIDTH = 800;
    
    
    // Plot controls
    
    /** The text field view for function names. */
    protected TextFieldView functionNames =
        new TextFieldView(fieldFont, NAMESWIDTH);
    
    /** The text field view for the minimum x value. */
    protected TextFieldView xMinimum =
        new TextFieldView(fieldFont, VALUEWIDTH);
    
    /** The text field view for the maximum x value. */
    protected TextFieldView xMaximum =
        new TextFieldView(fieldFont, VALUEWIDTH);
    
    /** The text field view for the x grid spacing value. */
    protected TextFieldView xSpacing =
        new TextFieldView(fieldFont, VALUEWIDTH);
    
    /** The text field view for the minimum y value. */
    protected TextFieldView yMinimum =
        new TextFieldView(fieldFont, VALUEWIDTH);
    
    /** The text field view for the maximum y value. */
    protected TextFieldView yMaximum =
        new TextFieldView(fieldFont, VALUEWIDTH);
    
    /** The text field view for the y grid spacing value. */
    protected TextFieldView ySpacing =
        new TextFieldView(fieldFont, VALUEWIDTH);
    
    
    /** The label for the function names text field view. */
    protected Annotation functionNamesLabel =
        new Annotation("Names of Functions to Plot", labelFont);
    
    /** The label for the minimum x text field view. */
    protected Annotation xMinimumLabel =
        new Annotation("Minimum x", labelFont);
    
    /** The label for the maximum x text field view. */
    protected Annotation xMaximumLabel =
        new Annotation("Maximum x", labelFont);
    
    /** The label for the x grid spacing text field view. */
    protected Annotation xSpacingLabel =
        new Annotation("x Grid Spacing", labelFont);
    
    /** The label for the minimum y text field view. */
    protected Annotation yMinimumLabel =
        new Annotation("Minimum y", labelFont);
    
    /** The label for the maximum y text field view. */
    protected Annotation yMaximumLabel =
        new Annotation("Maximum y", labelFont);
    
    /** The label for the y grid spacing text field view. */
    protected Annotation ySpacingLabel =
        new Annotation("y Grid Spacing", labelFont);
    
    /**
     * The action for the y scaling check box which enables
     * or disables the corresponding text field views.
     */
    protected SimpleAction yScalingAction =
        new SimpleAction() {
            public void perform() { yScalingMethod(); }
    };
    
    /** The y scaling check box. */
    protected BooleanView yScalingCheckBox =
        new BooleanView
            ("Compute y Scaling Automatically", yScalingAction, true);
    
    
    /**
     * The action for the x grid spacing check box which enables
     * or disables the corresponding text field view.
     */
    protected SimpleAction xSpacingAction =
        new SimpleAction() {
            public void perform() { xSpacingMethod(); }
    };
    
    /** The x grid spacing check box. */
    protected BooleanView xSpacingCheckBox =
        new BooleanView
            ("Compute x Grid Spacing Automatically", xSpacingAction, true);
    
    
    /**
     * The action for the y grid spacing check box which enables
     * or disables the corresponding text field view.
     */
    protected SimpleAction ySpacingAction =
        new SimpleAction() {
            public void perform() { ySpacingMethod(); }
    };
    
    /** The y grid spacing check box. */
    protected BooleanView ySpacingCheckBox =
        new BooleanView
            ("Compute y Grid Spacing Automatically", ySpacingAction, true);
    
    
    /**
     * The ingredients for the table panel for
     * the minimum and maximum x.
     */ 
    protected Object[][] xStuff =
        { { xMinimumLabel, xMinimum },
          { xMaximumLabel, xMaximum }
        };
    
    /**
     * The table panel for
     * the minimum and maximum x.
     */ 
    protected TablePanel xPanel =
        new TablePanel(xStuff, SMALLGAP, SMALLGAP, CENTER);
    
    
    /**
     * The ingredients for the table panel for
     * the minimum and maximum y.
     */ 
    protected Object[][] yStuff =
        { { yMinimumLabel, yMinimum },
          { yMaximumLabel, yMaximum }
        };
    
    /**
     * The table panel for
     * the minimum and maximum y.
     */ 
    protected TablePanel yPanel =
        new TablePanel(yStuff, SMALLGAP, SMALLGAP, CENTER);
    
    
    /**
     * The ingredients for the table panel for
     * the x grid spacing.
     */ 
    protected Object[] xSpacingStuff =
        { xSpacingLabel, xSpacing };
    
    /**
     * The table panel for
     * the x grid spacing.
     */ 
    protected HTable xSpacingPanel =
        new HTable(xSpacingStuff, SMALLGAP, SMALLGAP, CENTER);
    
    /**
     * The ingredients for the table panel for
     * the y grid spacing.
     */ 
    protected Object[] ySpacingStuff =
        { ySpacingLabel, ySpacing };
    
    /**
     * The table panel for
     * the y grid spacing.
     */ 
    protected HTable ySpacingPanel =
        new HTable(ySpacingStuff, SMALLGAP, SMALLGAP, CENTER);
    
    
    /**
     * The ingredients for the table panel for
     * all plot parameters combined.
     */ 
    protected Object[] parameterStuff = {
        functionNamesLabel,
        functionNames,
        xPanel,
        yScalingCheckBox,
        yPanel,
        xSpacingCheckBox,
        xSpacingPanel,
        ySpacingCheckBox,
        ySpacingPanel
    };
    
    /**
     * The table panel for
     * all plot parameters combined.
     */ 
    protected VTable parameterPanel =
        new VTable(parameterStuff, SMALLGAP, SMALLGAP, WEST);
    
    
    /**
     * The default colors for the <code>MultiColorView</code>
     * that determines the plot colors for functions.
     */
    protected Color[] colors =
        { Colors.red,
          Colors.blue,
          Colors.lime,
          Colors.orange,
          Colors.skyblue,
          Colors.tan
         };
    
    /**
     * The labels for the <code>MultiColorView</code>
     * that determines the plot colors for functions.
     */
    protected String[] labels =
        { "Function 1",
          "Function 2",
          "Function 3",
          "Function 4",
          "Function 5",
          "Function 6"
        };
    
    /**
     * The <code>MultiColorView</code>
     * that determines the plot colors for functions.
     */
    protected MultiColorView multiColorView =
        new MultiColorView(colors, labels);
    
    
    /**
     * <p>The action for the button
     * &ldquo;Plot Functions&rdquo;.</p>
     */
    protected SimpleAction plotAction =
        new SimpleAction("Plot Functions") {
            public void perform() { plotMethod(); }
    };
    
    /**
     * <p>The button
     * &ldquo;Plot Functions&rdquo;.</p>
     */
    protected JButton plotButton =
        new JButton(plotAction);
    
    
    /**
     * <p>The action for the button
     * &ldquo;Show Plot Data Table&rdquo;.</p>
     */
    protected SimpleAction dataAction =
        new SimpleAction("Show Plot Data Table") {
            public void perform() { showDataTable(); }
    };
    
    
    /**
     * <p>The button
     * &ldquo;Show Plot Data Table&rdquo;.</p>
     */
    protected JButton dataButton =
        new JButton(dataAction);
    
    
    /**
     * <p>The action for the button
     * &ldquo;Clear Coordinates in Plot Pane&rdquo;.</p>
     */
    protected SimpleAction clearCoordinatesAction =
        new SimpleAction("Clear Coordinates in Plot Pane") {
            public void perform() { clearCoordinates(); }
    };
    
    
    /**
     * <p>The button
     * &ldquo;Show Plot Data Table&rdquo;.</p>
     */
    protected JButton clearCoordinatesButton =
        new JButton(clearCoordinatesAction);
    
    
    
    /**
     * The ingredients for the controls table panel
     * that constitutes the left side of the GUI.
     */ 
    protected Object[] controlStuff =
        { parameterPanel, multiColorView, plotButton, dataButton };
    
    /**
     * The controls table panel
     * that constitutes the left side of the GUI.
     */ 
    protected VTable controlPanel =
        new VTable(controlStuff, LARGEGAP, LARGEGAP, CENTER);
    
    
    /** 
     * The <code>BufferedPanel</code> for the function plot
     * window.
     */
    protected BufferedPanel window =
        new BufferedPanel(WINDOWSIZE, WINDOWSIZE);
    
    
    /**
     * <p>The action for mouse motion in the plot window that
     * converts pixel coordinates to world coordinates and
     * then displays these coordinates in the text views
     * below the function plot window.</p>
     * 
     * <p>This mouse action will be performed whether or not
     * the mouse button is pressed.</p>
     * 
     * <p>This mouse action does nothing if there is no plot
     * in the function plot pane.</p>
     */
    protected MouseAction motionAction =
        new MouseAction() {
            public void mouseActionPerformed
                (MouseEvent event)
            {
                motionMethod(event);
            }
    };
    
    
    /**
     * <p>The action for mouse pressed in the plot window that
     * converts pixel coordinates to world coordinates and
     * then displays these coordinates as a text paintable
     * with string (x,y) at the postion of the mouse press.</p>
     * 
     * <p>This mouse action does nothing if there is no plot
     * in the function plot pane.</p>
     */
    protected MouseAction pressedAction =
        new MouseAction() {
            public void mouseActionPerformed
                (MouseEvent event)
            {
                pressedMethod(event);
            }
    };
    
    
    /**
     * The action for mouse exit from the function plot
     * window that clears the coordinate text views.
     */
    protected MouseAction exitedAction =
        new MouseAction() {
            public void mouseActionPerformed
                (MouseEvent event)
            {
                clearMouseXY();
            }
    };
    
    
    /**
     * The text field view for the world x-coordinate
     * corresponding to the mouse position.
     */
    protected TextFieldView xMouse =
        new TextFieldView(fieldFont, VALUEWIDTH);
    
    /**
     * The text field view for the world y-coordinate
     * corresponding to the mouse position.
     */
    protected TextFieldView yMouse =
        new TextFieldView(fieldFont, VALUEWIDTH);
    
    /**
     * The label for the text field view for the world x-coordinate
     * corresponding to the mouse position.
     */
    protected Annotation xMouseLabel =
        new Annotation("x", labelFont);
    
    /**
     * The label for the text field view for the world y-coordinate
     * corresponding to the mouse position.
     */
    protected Annotation yMouseLabel =
        new Annotation("y", labelFont);
    
    
    /** The ingredients for the coordinates table panel. */ 
    protected Object[] coordinatesStuff =
        { xMouseLabel, xMouse, yMouseLabel, yMouse };
    
    /** The coordinates table panel. */ 
    protected HTable coordinatesPanel =
        new HTable(coordinatesStuff, SMALLGAP, SMALLGAP, CENTER);
    
    
    /** The ingredients for the plot window plus coordinates table panel. */
    protected Object[] graphicsStuff =
        { window, coordinatesPanel, clearCoordinatesButton };
    
    /** The plot window plus coordinates table panel. */
    protected VTable graphicsPanel =
        new VTable(graphicsStuff, LARGEGAP, LARGEGAP, CENTER);
    
    
    
    /** The ingredients for the entire plot pane table panel. */
    protected Object[] plotPaneStuff =
        { controlPanel, graphicsPanel };
    
    /** The entire plot pane table panel. */
    protected HTable plotPanePanel =
        new HTable(plotPaneStuff, LARGEGAP, LARGEGAP, NORTH);
    
    
    /**
     * The <code>SimpleFunctionBuilder</code> for the
     * functions definition pane.
     */
    protected SimpleFunctionBuilder builder =
        new SimpleFunctionBuilder();
    
    
    /**
     * The <code>JTabbedPane</code> that combines
     * the plot pane and the functions definition pane.
     */
    protected JTabbedPane tabbedPane =
        new JTabbedPane();
    
    
    /**
     * The parser that will be used to evaluate functions
     * for the purpose of making plots.
     */
    protected BaseParser parser = ParserUtilities.getDefaultParser();
    
    
    /**
     * <p>The decimal format to be used for world x-coordinates
     * in the coordinates panel and in any data table.</p>
     * 
     * <p>This format is dynamically constructed based on the
     * actual data being plotted.</p>
     */
    protected DecimalFormat xFormat = null;
    
    /**
     * <p>The decimal format to be used for world y-coordinates
     * in the coordinates panel and in any data table.</p>
     * 
     * <p>This format is dynamically constructed based on the
     * actual data being plotted.</p>
     */
    protected DecimalFormat yFormat = null;
    
    
    /**
     * <p>Whether or not there is a plot in the plot window.</p>
     * 
     * <p>If this boolean is true, then there is data available
     * to display in a data table.</p>
     */
    protected boolean hasPlot = false;
    
    
    /** The <code>PlotTool</code> object for doing the plots. */
    protected PlotTool plottool = new PlotTool();
    
    
    /** The rectangle for the world coordinates. */
    protected Rectangle2D world = null;
    
    /** The rectangle for the image coordinates in the plot window. */
    protected Rectangle2D image =
        new XRect(0, 0, WINDOWSIZE, WINDOWSIZE);
    
    /** The stroke for function plots. */
    protected Stroke stroke = new BasicStroke(2);
    
    
    /**
     * The plot data that is computed to draw the function plots
     * and that may later be displayed in a data table.
     */
    protected Point2D[][] data = null;  // Plot data
    
    
    /**
     * <p>The string with the function names to be plotted.</p>
     * 
     * <p>Extracted from the GUI.</p>
     */
    protected String names = null;      // Function names
    
    
    /**
     * <p>The minimum x-coordinate for function plots.</p>
     * 
     * <p>Extracted from the GUI.</p>
     */
    protected double xmin = 0;
    
    /**
     * <p>The maximum x-coordinate for function plots.</p>
     * 
     * <p>Extracted from the GUI.</p>
     */
    protected double xmax = 0;
    
    
    /**
     * <p>The minimum y-coordinate for function plots.</p>
     * 
     * <p>Auto-computed or extracted from the GUI.</p>
     */
    protected double ymin = 0;
    
    /**
     * <p>The maximum y-coordinate for function plots.</p>
     * 
     * <p>Auto-computed or extracted from the GUI.</p>
     */
    protected double ymax = 0;
    
    
    /**
     * <p>The size of the x grid spacing for function plots.</p>
     * 
     * <p>Auto-computed or extracted from the GUI.</p>
     */
    protected double xgrid = 0;
    
    /**
     * <p>The size of the y grid spacing for function plots.</p>
     * 
     * <p>Auto-computed or extracted from the GUI.</p>
     */
    protected double ygrid = 0;
    
    
    /**
     * <p>The <code>FunctionsPlotter</code> constructor creates the tabbed pane
     * that contains the plot pane and the functions definition pane.</p>
     */
    public FunctionsPlotter() {
        initializeFunctionsPlotter();
        
        addObject(tabbedPane);
    }
    
    
    /**
     * This helper method handles all initialzations for the constructor
     * that cannot be done inline in the member data declarations.
     */
    protected void initializeFunctionsPlotter() {
        yScalingCheckBox.setFont(labelFont);
        yScalingMethod();
        
        xSpacingCheckBox.setFont(labelFont);
        xSpacingMethod();
        
        ySpacingCheckBox.setFont(labelFont);
        ySpacingMethod();
        
        parameterPanel.emptyBorder(BORDER);
        parameterPanel.titleBorder
            ("Function Plot Parameters", labelFont, Color.black);
        
        multiColorView.emptyBorder(BORDER);
        multiColorView.titleBorder
            ("Function Plot Colors", labelFont, Color.black);
        
        window.emptyBorder(BORDER);
        window.titleBorder
            ("Function Plot Pane", labelFont, Color.black);
        
        coordinatesPanel.emptyBorder(BORDER);
        coordinatesPanel.titleBorder
            ("Function Plot Coordinates", labelFont, Color.black);
        
        plotButton.setFont(buttonFont);
        plotButton.setBackground(Colors.yellow);
        
        dataButton.setFont(buttonFont);
        dataButton.setBackground(Colors.skyblue);
        
        clearCoordinatesButton.setFont(buttonFont);
        clearCoordinatesButton.setBackground(Colors.lime);
        
        tabbedPane.setFont(labelFont);
        tabbedPane.addTab("Function Plot", plotPanePanel);
        tabbedPane.addTab("Function Definition", builder);
        
        MouseActionAdapter adapter =
            window.getMouseActionAdapter();
        
        adapter.addMouseMovedAction(motionAction);
        adapter.addMouseDraggedAction(motionAction);
        adapter.addMousePressedAction(pressedAction);
        adapter.addMouseExitedAction(exitedAction);
    }
    
    
    /**
     * The method for the y scaling check box which enables
     * or disables the corresponding text field views.
     */
    protected void yScalingMethod() {
        boolean manualyscale = ! yScalingCheckBox.getBooleanValue();
        
        yMinimum.setEnabled(manualyscale);
        yMaximum.setEnabled(manualyscale);
    }
    
    
    /**
     * The method for the x grid spacing check box which enables
     * or disables the corresponding text field view.
     */
    protected void xSpacingMethod() {
        boolean manualxgrid = ! xSpacingCheckBox.getBooleanValue();
        
        xSpacing.setEnabled(manualxgrid);
    }
    
    
    /**
     * The method for the y grid spacing check box which enables
     * or disables the corresponding text field view.
     */
    protected void ySpacingMethod() {
        boolean manualygrid = ! ySpacingCheckBox.getBooleanValue();
        
        ySpacing.setEnabled(manualygrid);
    }
    
    
    /**
     * <p>The method for the button
     * &ldquo;Plot Functions&rdquo;.</p>
     * 
     * <p>Clears any previous plot and its data.</p>
     * 
     * <p>Extracts user input from the GUI including
     * function names, x,y bounds, and x,y grid spacing.
     * If an error is detected, shows a dialog and then
     * returns.</p>
     * 
     * <p>Creates the mathematical data for the plots.
     * This data is saved in case the user wishes to
     * view the data table.</p>
     * 
     * <p>Determines the bounds in world coordinates.</p>
     * 
     * <p>Determines the optimal fixed-point decimal
     * formats separately for x and y data values for
     * use with the coordinates panel and with a data
     * table.</p>
     * 
     * <p>Calls <code>plotData</code> to perform the plot.</p>
     */
    protected void plotMethod() {
        hasPlot = false;
        
        window.clearPanelAndSequence();
        clearMouseXY();
        
        names = functionNames.getViewState();
        
        boolean autoyscale = yScalingCheckBox.getBooleanValue();
        boolean autoxgrid  = xSpacingCheckBox.getBooleanValue();
        boolean autoygrid  = ySpacingCheckBox.getBooleanValue();
        
        // Read x and y limits and grid spacing from the GUI
        
        xmin = 0;
        xmax = 0;
        
        ymin = 0;
        ymax = 0;
        
        xgrid = 0;
        ygrid = 0;
        
        try {
            xmin = xMinimum.requestDouble();
            xmax = xMaximum.requestDouble();
            
            if (xmin == xmax) {
                PlotLimitError('x');
                return;
            }
            
            if (! autoyscale) {
                ymin = yMinimum.requestDouble();
                ymax = yMaximum.requestDouble();
                
                if (ymin == ymax) {
                    PlotLimitError('y');
                    return;
                }
            }
            
            if (! autoxgrid) {
                xgrid = xSpacing.requestDouble();
                
                if (xgrid <= 0) {
                    GridSpacingError('x');
                    return;
                }
            }
            
            if (! autoygrid) {
                ygrid = ySpacing.requestDouble();
                
                if (ygrid <= 0) {
                    GridSpacingError('y');
                    return;
                }
            }
        }
        catch (CancelledException cancelled) {
            return;
        }
        
        // Read functions and make the function plot data
        
        data = null;
        
        int steps = PLOTSIZE / PIXELGAP;
        
        try {
            data = parser.makeTable(names, xmin, xmax, steps);
        }
        catch (ParseException exception) {
            FunctionError(exception.getMessage());
            return;
        }
        
        if ((data == null) || (data.length == 0)) {
            FunctionError("No function names");
            return;
        }
        
        // Find world bounds
        
        if (autoyscale) {
            world = PlotTool.makeBoundsRectangle2D(data);
            
            ymin = world.getMinY();
            ymax = world.getMaxY();
            
            if (ymin == ymax) {
                PlotLimitError('y');
                return;
            }
        }
        else {
            XRect rect = new XRect();
            rect.setX1Y1X2Y2(xmin, ymin, xmax, ymax);
            
            world = rect;
        }
        
        setDecimalFormats();
        
        plotData();
    }
    
    
    /**
     * <p>The helper method for the button
     * &ldquo;Plot Functions&rdquo;.</p>
     * 
     * <p>Utilizes the tools in class <code>PlotTool</code>.</p>
     * 
     * <p>Determines the transform from world to image coordinates.</p>
     * 
     * <p>Plots grids based either on user settings or by automatic
     * means.</p>
     * 
     * <p>Plots axes if visible in the plot region.</p>
     * 
     * <p>Plots the function data using the color choices selected
     * in the GUI to distinguish various functions.</p>
     */
    protected void plotData()
    {
        if (data == null)
            return;
        
        Graphics2D g = window.getBufferGraphics();
        
        plottool.setWorldBounds(world);
        plottool.setImageBounds(image);
        plottool.setInset(PLOTINSET);
        
        Point2D grid =
            plottool.autoSpacing(PlotTool.MINIMUM_GRID_PIXELS);
        
        if (xgrid <= 0)
            xgrid = grid.getX();
        
        if (ygrid <= 0)
            ygrid = grid.getY();
        
        Color gridcolor = PlotTool.DEFAULT_GRID_COLOR;
        
        plottool.plotGridLines(g, gridcolor, xgrid, ygrid);
        
        plottool.autoAxes(g);
        
        Color[] plotcolors =
            multiColorView.getColors();
        
        plottool.plotData(g, data, plotcolors, stroke);
        
        window.repaint();
        
        hasPlot = true;
    }
    
    
    /**
     * <p>The method for the button
     * &ldquo;Show Plot Data Table&rdquo;.</p>
     * 
     * <p>Calls <code>makeDataTable</code> to place the plot
     * data for the functions
     * into a <code>PaintableComponent</code>.</p>
     * 
     * <p>Inserts the <code>PaintableComponent</code>
     * into a <code>JPTScrollPane</code>.</p>
     * 
     * <p>Inserts the <code>JPTScrollPane</code>
     * into a <code>GeneralDialog</code>
     * which is then displayed.</p>
     */
    protected void showDataTable() {
        if (! hasPlot)
            return;
        
        PaintableComponent dataTable = makeDataTable();
        
        Dimension dimension = dataTable.getPreferredSize();
        
        int w = dimension.width;
        
        if (w > MAXWIDTH)
            w = MAXWIDTH;
        
        int h = 600;
        
        int hsbPolicy = JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED;
        int vsbPolicy = JScrollPane.VERTICAL_SCROLLBAR_ALWAYS;
        
        JPTScrollPane scrollpane =
            new JPTScrollPane(dataTable, vsbPolicy, hsbPolicy);
        
        scrollpane.boundViewportPreferredSize(w, h);
        scrollpane.OKDialog("Plot Data");
    }
    
    
    /**
     * <p>The helper method for the button
     * &ldquo;Show Plot Data Table&rdquo;.</p>
     * 
     * <p>Column 0 has the x-coordinate of a plot point.
     * The remaining columns have the y-coordinates of
     * the function values at the common x-coordinate
     * position.</p>
     * 
     * <p>The top and bottom rows have column labels with
     * &ldquo;x&rdquo; in column 0 and the function names
     * in the remaining columns.</p>
     * 
     * <p>Technically, the data table is constructed with
     * a huge <code>PaintableSequence</code> that contains
     * one <code>TextPaintable</code> for each item in the
     * table.  This <code>PaintableSequence</code> is then
     * placed in a <code>PaintableComponent</code> which
     * is the component that is returned.  The perforance
     * for this design is much, much faster than alternate
     * approaches that we tested.</p>
     */
    protected PaintableComponent makeDataTable() {
        if (! hasPlot)
            return null;
        
        // initial setup
        
        String[] list = Strings.splitCommaList(names);
        
        int fcns = list.length;
        
        int cols = fcns + 1;
        
        int steps = PLOTSIZE / PIXELGAP;
        
        int rows = steps + 3;
        
        int row;
        int col;
        
        // create the data strings for the data table
        
        String[][] string = new String[rows][cols];
        
        row = 0;
        
        string[row][0] = "x";
        
        for (int fcn = 0; fcn < fcns; fcn++) {
            col = fcn + 1;
            string[row][col] = list[fcn];
        }
        
        for (int step = 0; step <= steps; step++) {
            row = step + 1;
            
            string[row][0] =
                formatDouble(data[0][step].getX(), xFormat);
            
            for (int fcn = 0; fcn < fcns; fcn++) {
                col = fcn + 1;
                string[row][col] =
                    formatDouble(data[fcn][step].getY(), yFormat);
            }
        }
        
        row = steps + 2;
        
        string[row][0] = "x";
        
        for (int fcn = 0; fcn < fcns; fcn++) {
            col = fcn + 1;
            string[row][col] = list[fcn];
        }
        
        // find the maximum string length in each column
        
        int[] maxlength = new int[cols];
        
        for (col = 0; col < cols; col++) {
            for (row = 0; row <= (steps + 2); row++) {
                int length = string[row][col].length();
                
                if (length > maxlength[col])
                    maxlength[col] = length;
            }
        }
        
        // preliminary text paintable measurements
        
        TextPaintable paintable;
        
        XRect bounds;
        
        Font font = fieldFont;
        
        int[] textwidth = new int[cols];
        
        int textheight = 0;
        
        for (col = 0; col < cols; col++) {
            String test = "";
            
            for (int i = 0; i < maxlength[col]; i++)
                test += "0";
            
            paintable =
                new TextPaintable(test, font);
            
            bounds = paintable.getLooseBounds();
            
            textwidth[col] = 1 + (int) bounds.getWidth();
            
            if (col == 0)
                textheight = 1 + (int) bounds.getHeight();
        }
        
        // compute the right side position of each column
        
        int[] rightside = new int[cols];
        
        rightside[0] = textwidth[0] + SMALLGAP;
        
        for (col = 1; col < cols; col++) {
            rightside[col] =
                rightside[col-1] + textwidth[col] + EXTRAGAP;
        }
        
        // compute the middle positon of each column
        
        int[] middle = new int[cols];
        
        for (col = 0; col < cols; col++) {
            middle[col] = rightside[col] - textwidth[col]/2;
        }
        
        // construct the array of text paintables
        
        int size = rows * cols;
        int index = 0;
        
        Paintable[] paintables = new Paintable[size];
        
        TextAnchor.Locator MID = TextAnchor.CENTER_ASCENTLINE;
        
        TextAnchor.Locator RHS = TextAnchor.RIGHT_ASCENTLINE;
        
        TextAnchor.Locator locator;
        
        int anchorX;
        int anchorY;
        
        int[] position;
        
        for (row = 0; row < rows; row++) {
            anchorY = SMALLGAP + row * textheight;
            
            if ((row == 0) || (row == (steps + 2))) {
                locator  = MID;
                position = middle;
            }
            else {
                locator  = RHS;
                position = rightside;
            }
            
            for (col = 0; col < cols; col++) {
                anchorX = position[col];
                
                paintables[index++] =
                    new TextPaintable
                        (string[row][col], font, locator, anchorX, anchorY);
            }
        }
        
        // construct the paintable sequence
        
        PaintableSequence sequence = new PaintableSequence(paintables);
        
        int w = rightside[cols - 1] + SMALLGAP;
        int h = rows * textheight + 2 * SMALLGAP;
        
        bounds = new XRect(0, 0, w, h);
        
        sequence.setDefaultBounds2D(bounds);
        
        // construct and return the paintable component
        // created from the paintable sequence
        
        return new PaintableComponent(sequence);
    }
    
    
    /**
     * Set the x and y decimal format strings at the optimum
     * number of decimal places needed to distinguish pixel
     * positions translated to world coordinates.
     */
    protected void setDecimalFormats()
    {
        double xpixel = Math.abs(xmax - xmin) / PLOTSIZE;
        double ypixel = Math.abs(ymax - ymin) / PLOTSIZE;
        
        xFormat = new DecimalFormat(makeFormatString(xpixel));
        yFormat = new DecimalFormat(makeFormatString(ypixel));
    }
    
    
    /**
     * Return a fixed point format string that is optimum
     * for distinguishing the given pixel thickness.
     * 
     * @param pixel the pixel thickness in world coordinates
     */
    protected String makeFormatString(double pixel) {
        pixel = Math.abs(pixel);
        
        if ((pixel == 0) || (pixel >= 1))
            return "0";
        
        String s = "0.";
        
        while (pixel < 1) {
            pixel *= 10;
            s += "0";
        }
        
        return s;
    }
    
    
    /**
     * <p>Return the formatted double using the given format string.</p>
     * 
     * <p><code>Nan</code> is returned as "Nan".</p>
     * 
     * <p><code>POSITIVE_INFINITY</code> is returned as "+Infinity".</p>
     * 
     * <p><code>NEGATIVE_INFINITY</code> is returned as "-Infinity".</p>
     * 
     * @param z the double to format
     * @param zFormat the format string
     */
    protected String formatDouble
        (double z, DecimalFormat zFormat)
    {
        if (Double.isNaN(z))
            return "NaN";
        else if (z == Double.POSITIVE_INFINITY)
            return "+Infinity";
        else if (z == Double.NEGATIVE_INFINITY)
            return "-Infinity";
        else
            return zFormat.format(z);
    }
    
    
    /**
     * Show a dialog box with an error for x or y plot limits.
     * 
     * @param c either 'x' or 'y'
     */
    protected void PlotLimitError(char c) {
        GeneralDialog.showOKDialog
            ("Minumum and maximum " + c + " must be distinct",
             "Plot Limit Error");
    }
    
    
    /**
     * Show a dialog box with an error for x or y grid spacing.
     * 
     * @param c either 'x' or 'y'
     */
    protected void GridSpacingError(char c) {
        GeneralDialog.showOKDialog
            (c + " Grid Spacing must be positive",
             "Grid Spacing Error");
    }
    
    
    /**
     * Show a dialog with an error regarding a plot function.
     * 
     * @param message the error message
     */
    protected void FunctionError(String message) {
        GeneralDialog.showOKDialog
            (message, "Function Error");
    }
    
    
    /**
     * <p>The method for mouse motion in the plot window that
     * converts pixel coordinates to world coordinates and
     * then displays these coordinates in the text views
     * below the function plot window.</p>
     * 
     * <p>This method displays the data using the x and y
     * format strings.</p>
     * 
     * <p>This method will be performed whether or not the
     * mouse button is pressed.</p>
     * 
     * <p>This method does nothing if there is no plot in
     * the function plot pane.</p>
     */
    protected void motionMethod(MouseEvent event) {
        if (hasPlot) {
            int x = event.getX();
            int y = event.getY();
            
            double xx = plottool.inverseXScale(x);
            double yy = plottool.inverseYScale(y);
            
            xMouse.setViewState(xFormat.format(xx));
            yMouse.setViewState(yFormat.format(yy));
        }
    }
    
    
    /**
     * <p>The method for mouse pressed in the plot window that
     * converts pixel coordinates to world coordinates and
     * then displays these coordinates as a text paintable
     * with string (x,y) at the postion of the mouse press.</p>
     * 
     * <p>This method displays the data using the x and y
     * format strings.</p>
     * 
     * <p>This method does nothing if there is no plot in
     * the function plot pane.</p>
     */
    protected void pressedMethod(MouseEvent event) {
        if (hasPlot) {
            int x = event.getX();
            int y = event.getY();
            
            double xx = plottool.inverseXScale(x);
            double yy = plottool.inverseYScale(y);
            
            String xs = xFormat.format(xx);
            String ys = yFormat.format(yy);
            String s = "(" + xs + "," + ys + ")";
            
            TextPaintable tp =
                new TextPaintable
                    (s, fieldFont, TextAnchor.CENTER_BASELINE, x, y);
            
            XRect bounds = tp.getBounds2D();
            
            double x1 = bounds.getMinX();
            double y1 = bounds.getMinY();
            
            double x2 = bounds.getMaxX();
            double y2 = bounds.getMaxY();
            
            if (x1 < 0) {
                tp.move(-x1, 0);
            }
            
            if (y1 < 0) {
                tp.move(0, -y1);
            }
            
            if (x2 > WINDOWSIZE) {
                tp.move(WINDOWSIZE - x2, 0);
            }
            
            if (y2 > WINDOWSIZE) {
                tp.move(0, WINDOWSIZE - y2);
            }
            
            window.appendPaintable(tp);
            
            window.repaint();
        }
    }
    
    
    /**
     * <p>The method for mouse exit from the function plot
     * window that clears the coordinate text views.</p>
     * 
     * <p>Since this method does not require a
     * <code>MouseEvent</code> parameter, it may also be
     * called directly when needed.</p>
     */
    protected void clearMouseXY() {
        xMouse.setViewState("");
        yMouse.setViewState("");
    }
    
    
    /**
     * Clear the coordinate labels in the plot window.
     */
    protected void clearCoordinates() {
        window.clearSequence();
        window.repaint();
    }
    
    
    /**
     * The main method that launches a <code>FunctionsPlotter</code>
     * in its own frame.
     * 
     * @param args ignored
     */
    public static void main(String[] args) {
        new FunctionsPlotter().frame("Functions Plotter");
    }
}


