content: ///**
  //* Copyright 2004-2006 DFKI GmbH.
  //* All Rights Reserved.  Use is subject to license terms.
  //*
  //* This file is part of MARY TTS.
  //*
  //* MARY TTS is free software: you can redistribute it and/or modify
  //* it under the terms of the GNU Lesser General Public License as published by
  //* the Free Software Foundation, version 3 of the License.
  //*
  //* This program is distributed in the hope that it will be useful,
  //* but WITHOUT ANY WARRANTY; without even the implied warranty of
  //* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  //* GNU Lesser General Public License for more details.
  //*
  //* You should have received a copy of the GNU Lesser General Public License
  //* along with this program.  If not, see <http://www.gnu.org/licenses/>.
  //*
  //*/
  //package marytts.signalproc.display;
  //
  ////import java.awt.BorderLayout;
  ////import java.awt.Color;
  ////import java.awt.Dimension;
  ////import java.awt.Font;
  ////import java.awt.Graphics;
  ////import java.awt.Graphics2D;
  ////import java.awt.Rectangle;
  ////import java.awt.event.ActionEvent;
  ////import java.awt.event.ActionListener;
  ////import java.awt.event.MouseEvent;
  ////import java.awt.event.MouseListener;
  ////import java.awt.image.BufferedImage;
  //import java.util.ArrayList;
  //import java.util.List;
  //
  ////import javax.swing.Box;
  ////import javax.swing.BoxLayout;
  ////import javax.swing.JButton;
  ////import javax.swing.JFrame;
  ////import javax.swing.JPanel;
  ////import javax.swing.JScrollPane;
  ////import javax.swing.ScrollPaneConstants;
  //
  //
  ///**
  //* @author Marc Schr&ouml;der
  //*
  //*/
  //public class FunctionGraph
  //{
  //    public static final int DEFAULT_WIDTH=640;
  //    public static final int DEFAULT_HEIGHT=480;
  //    public static final int DRAW_LINE = 1;
  //    public static final int DRAW_DOTS = 2;
  //    public static final int DRAW_LINEWITHDOTS = 3;
  //    public static final int DRAW_HISTOGRAM = 4;
  //    public static final int DOT_FULLCIRCLE = 1;
  //    public static final int DOT_FULLSQUARE = 2;
  //    public static final int DOT_FULLDIAMOND = 3;
  //    public static final int DOT_EMPTYCIRCLE = 11;
  //    public static final int DOT_EMPTYSQUARE = 12;
  //    public static final int DOT_EMPTYDIAMOND = 13;
  //
  //    protected int paddingLeft = 40;
  //    protected int paddingRight = 10;
  //    protected int paddingTop = 10;
  //    protected int paddingBottom = 40;
  //    protected double x0;
  //    protected double xStep;
  //    protected List<double[]> dataseries = new ArrayList<double[]>();
  //    protected double ymin;
  //    protected double ymax;
  //    protected boolean showXAxis = true;
  //    protected boolean showYAxis = true;
  //  //  protected BufferedImage graphImage = null;
  //   // protected Color backgroundColor = Color.WHITE;
  //   // protected Color axisColor = Color.BLACK;
  //  //  protected List<Color> graphColor = new ArrayList<Color>();
  //   // protected Color histogramBorderColor = Color.BLACK;
  //    protected List<Integer> graphStyle = new ArrayList<Integer>();
  //    protected List<Integer> dotStyle = new ArrayList<Integer>();
  //    protected int dotSize = 6;
  //    protected int histogramWidth = 10;
  //    protected boolean autoYMinMax = true; // automatically determine ymin and ymax
  //
  //    // data to be used for drawing cursor et al on the GlassPane:
  //    // x and y coordinates, in data space
  //    protected DoublePoint positionCursor = new DoublePoint();
  //    protected DoublePoint rangeCursor = new DoublePoint();
  //    protected List cursorListeners = new ArrayList();
  //
  //    /**
  //     * Display a 2d graph showing y(x), with labelled scales.
  //     * This constructor is for subclasses only, which may need
  //     * to perform some operations before calling initialise().
  //     */
  //    protected FunctionGraph()
  //    {
  //        super();
  //    }
  //
  //    /**
  //     * Display a 2d graph showing y(x), with labelled scales.
  //     */
  //    public FunctionGraph(double x0, double xStep,  double[] y) {
  //        this(DEFAULT_WIDTH, DEFAULT_HEIGHT, x0, xStep, y);
  //    }
  //
  //    /**
  //     * Display a 2d graph showing y(x), with labelled scales.
  //     */
  //    public FunctionGraph(int width, int height,
  //                   double x0, double xStep,  double[] y) {
  //        super();
  //        initialise(width, height, x0, xStep, y);
  //   }
  //
  //   public void initialise(int width, int height,
  //                    double newX0, double newXStep,  double[] data)
  //   {
  ////       setPreferredSize(new Dimension(width, height));
  ////       setOpaque(true);
  ////       this.addMouseListener(new MouseListener() {
  ////           public void mouseClicked(MouseEvent e) {
  ////               //System.err.println("Mouse clicked");
  ////               if (e.getButton() == MouseEvent.BUTTON1) { // left mouse button
  ////                   // set position cursor; if we are to the right of rangeCursor,
  ////                   // delete rangeCursor.
  ////                   positionCursor.x = imageX2X(e.getX()-paddingLeft);
  ////                   positionCursor.y = imageY2Y(getHeight()-paddingBottom-e.getY());
  ////                   if (!Double.isNaN(rangeCursor.x) && positionCursor.x > rangeCursor.x) {
  ////                       rangeCursor.x = Double.NaN;
  ////                   }
  ////               } else if (e.getButton() == MouseEvent.BUTTON3) { // right mouse button
  ////                   // set range cursor, but only if we are to the right of positionCursor
  ////                   rangeCursor.x = imageX2X(e.getX()-paddingLeft);
  ////                   rangeCursor.y = imageY2Y(getHeight()-paddingBottom-e.getY());
  ////                   if (positionCursor.x > rangeCursor.x) {
  ////                       rangeCursor.x = Double.NaN;
  ////                   }
  ////               }
  ////               FunctionGraph.this.notifyCursorListeners();
  ////               FunctionGraph.this.requestFocusInWindow();
  ////           }
  ////           public void mousePressed(MouseEvent e) {}
  ////           public void mouseReleased(MouseEvent e) {}
  ////           public void mouseEntered(MouseEvent e) {}
  ////           public void mouseExited(MouseEvent e) {}
  ////       });
  ////       updateData(newX0, newXStep, data);
  ////       // set styles for primary data series:
  ////       graphColor.add(Color.BLUE);
  ////       graphStyle.add(DRAW_LINE);
  ////       dotStyle.add(DOT_FULLCIRCLE);
  //   }
  //
  //   /*
  //    * Replace the previous data with the given new data.
  //    * Any secondary data series added using {{@link #addDataSeries(double[], Color, int, int)} are removed.
  //    * @param newX0 x position of first data point
  //    * @param newXStep distance between data points on X axis
  //    * @param data all data points
  //    */
  //   public void updateData(double newX0, double newXStep, double[] data)
  //   {
  ////       if (newXStep <= 0) {
  ////           throw new IllegalArgumentException("newXStep must be >0");
  ////       }
  ////       if (data == null || data.length == 0) {
  ////           throw new IllegalArgumentException("No data");
  ////       }
  ////       this.x0 = newX0;
  ////        this.xStep = newXStep;
  ////        double[] series = new double[data.length];
  ////        System.arraycopy(data, 0, series, 0, data.length);
  ////        // Do not allow old secondary data sets with a new primary one:
  ////        while (dataseries.size() > 0) {
  ////            dataseries.remove(0);
  ////        }
  ////        // Also remove the styles of the secondary data sets:
  ////        while (graphColor.size() > 1) {
  ////            graphColor.remove(1);
  ////        }
  ////        while (graphStyle.size() > 1) {
  ////            graphStyle.remove(1);
  ////        }
  ////        while (dotStyle.size() > 1) {
  ////            dotStyle.remove(1);
  ////        }
  ////        this.dataseries.add(0, series);
  ////        if (autoYMinMax) {
  ////            ymin = Double.NaN;
  ////            ymax = Double.NaN;
  ////            for (int i=0; i<data.length; i++) {
  ////                if (Double.isNaN(data[i])) // missing value -- skip
  ////                    continue;
  ////                if (Double.isNaN(ymin)) {
  ////                    assert Double.isNaN(ymax);
  ////                    ymin = data[i];
  ////                    ymax = data[i];
  ////                    continue;
  ////                }
  ////                if (data[i] < ymin) ymin = data[i];
  ////                else if (data[i] > ymax) ymax = data[i];
  ////            }
  ////            // If the x axis is painted in the middle (ymin << 0),
  ////            // we need much less paddingBottom:
  ////            if (ymin < 0) {
  ////                paddingBottom = paddingTop;
  ////            }
  ////        }
  ////
  ////        // And invalidate any previous graph image:
  ////        graphImage = null;
  //    }
  //
  ////   public void setPrimaryDataSeriesStyle(Color newGraphColor, int newGraphStyle, int newDotStyle)
  ////   {
  ////       graphColor.remove(0);
  ////       graphColor.add(0, newGraphColor);
  ////       graphStyle.remove(0);
  ////       graphStyle.add(0, newGraphStyle);
  ////       dotStyle.remove(0);
  ////       dotStyle.add(0, newDotStyle);
  ////   }
  //
  //   /**
  //    * Manually set the min and max values for the y axis.
  //    * @param theYMin
  //    * @param theYMax
  //    */
  //   public void setYMinMax(double theYMin, double theYMax)
  //   {
  //       autoYMinMax = false;
  //       ymin = theYMin;
  //       ymax = theYMax;
  //       // If the x axis is painted in the middle (ymin << 0),
  //       // we need much less paddingBottom:
  //       if (ymin < 0) {
  //           paddingBottom = paddingTop;
  //       }
  //   }
  //
  //   /*
  //    * Add a secondary data series to this graph.
  //    * @param data the function data, which must be of same length as the original data. {@link #updateData(double, double, double[])}
  //    * @param newGraphColor a colour
  //    * @param newGraphStyle the style for painting this data series. One of {@link #DRAW_LINE}, {@link #DRAW_DOTS}, {@value #DRAW_LINEWITHDOTS}, {@link #DRAW_HISTOGRAM}.
  //    * @param newDotStyle the shape of any dots to use (meaningful only with newGraphStyle == {@link #DRAW_DOTS} or {@link #DRAW_LINEWITHDOTS}).
  //    * One of {@link #DOT_EMPTYCIRCLE}, {@link #DOT_EMPTYDIAMOND}, {@link #DOT_EMPTYSQUARE}, {@link #DOT_FULLCIRCLE}, {@link #DOT_FULLDIAMOND}, {@link #DOT_FULLSQUARE}.
  //    * For other graph styles, this is ignored, and it is recommended to set it to -1 for clarity.
  //    */
  ////   public void addDataSeries(double[] data, Color newGraphColor, int newGraphStyle, int newDotStyle)
  ////   {
  ////       if (data == null) throw new NullPointerException("Cannot add null data");
  ////       if (dataseries.get(0).length != data.length)
  ////           throw new IllegalArgumentException("Can only add data of the exact same length as the original data series; len(orig)="
  ////                   +dataseries.get(0).length+", len(data)="+data.length);
  ////        double[] series = new double[data.length];
  ////        System.arraycopy(data, 0, series, 0, data.length);
  ////        dataseries.add(series);
  ////        graphColor.add(newGraphColor);
  ////        graphStyle.add(newGraphStyle);
  ////        dotStyle.add(newDotStyle);
  ////        if (autoYMinMax) {
  ////            for (int i=0; i<data.length; i++) {
  ////                if (Double.isNaN(data[i])) // missing value -- skip
  ////                    continue;
  ////                if (Double.isNaN(ymin)) {
  ////                    assert Double.isNaN(ymax);
  ////                    ymin = data[i];
  ////                    ymax = data[i];
  ////                    continue;
  ////                }
  ////                if (data[i] < ymin) ymin = data[i];
  ////                else if (data[i] > ymax) ymax = data[i];
  ////            }
  ////            // If the x axis is painted in the middle (ymin << 0),
  ////            // we need much less paddingBottom:
  ////            if (ymin < 0) {
  ////                paddingBottom = paddingTop;
  ////            }
  ////        }
  ////
  ////        // And invalidate any previous graph image:
  ////        graphImage = null;
  ////    }
  //
  //
  ////    public double getZoomX()
  ////    {
  ////        double[] data = dataseries.get(0);
  ////        double zoom = ((double)getPreferredSize().width-paddingLeft-paddingRight) / data.length;
  ////        //System.err.println("Current Zoom: " + zoom + "(pref. size: " + getPreferredSize().width + "x" + getPreferredSize().height + ")");
  ////        return zoom;
  ////    }
  //
  //    /*
  //     * Set the zoom of the X
  //     * @param factor the zoom factor for X; 1 means that each data point corresponds to one pixel;
  //     * 0.5 means that 2 data points are mapped onto one pixel; etc.
  //     */
  ////    public void setZoomX(double factor)
  ////    {
  ////        //System.err.println("New zoom factor requested: " + factor);
  ////        // Old visible rectangle:
  ////        Rectangle r = getVisibleRect();
  ////        int oldWidth = getPreferredSize().width;
  ////        double[] data = dataseries.get(0);
  ////        int newWidth = (int)(data.length*factor)+paddingLeft+paddingRight;
  ////        if (isVisible()) {
  ////            setVisible(false);
  ////            setPreferredSize(new Dimension(newWidth, getPreferredSize().height));
  ////            // Only scroll to center of what was previous visible if not at left end:
  ////            if (r.x != 0) {
  ////                Rectangle newVisibleRect = new Rectangle((r.x+r.width/2-paddingLeft)*newWidth/oldWidth-r.width/2+paddingLeft, r.y, r.width, r.height);
  ////                scrollRectToVisible(newVisibleRect);
  ////            }
  ////            setVisible(true);
  ////        } else {
  ////            setPreferredSize(new Dimension(newWidth, getPreferredSize().height));
  ////            createGraphImage();
  ////        }
  ////        //System.err.print("updated ");
  ////        getZoomX();
  ////    }
  //
  ////    protected void createGraphImage()
  ////    {
  ////        graphImage = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
  ////        if (graphImage == null) {
  ////            throw new NullPointerException("Cannot create image for drawing graph");
  ////        }
  ////        Graphics2D g = (Graphics2D) graphImage.createGraphics();
  ////        double width = getWidth();
  ////        double height = getHeight();
  ////
  ////        int image_fromX = 0;
  ////        int image_toX = (int) width;
  ////
  ////        g.setBackground(backgroundColor);
  ////        g.clearRect(0, 0, (int) width, (int) height);
  ////        g.setFont(new Font("Courier", 0, 10));
  ////        // Now reduce the drawing area:
  ////        int startX = paddingLeft;
  ////        int startY = (int)height - paddingBottom;
  ////        width -= paddingLeft + paddingRight;
  ////        height -= paddingTop + paddingBottom;
  ////        // Make sure we are not trying to draw the function outside its area:
  ////        if (image_fromX < startX) image_fromX = startX;
  ////        if (image_toX > startX + width) image_toX = (int) (startX + width);
  ////
  ////
  ////        int image_y_origin;
  ////        if (getYRange() == 0) image_y_origin = startY;
  ////        else image_y_origin = startY - (int) ((-ymin/getYRange()) * height);
  ////        int image_x_origin = startX + (int) ((-x0/getXRange()) * width);
  ////
  ////        // Draw the function itself:
  ////        if (getYRange() > 0) {
  ////            for (int s=0; s<dataseries.size(); s++) {
  ////                drawData(g, image_fromX-startX, image_toX-startX, startX, image_y_origin, startY, (int) height,
  ////                        dataseries.get(s), graphColor.get(s), graphStyle.get(s), dotStyle.get(s));
  ////            }
  ////        }
  ////
  ////        // Draw the x axis, if requested:
  ////        if (showXAxis) {
  ////            if (startY >= image_y_origin && image_y_origin >= startY-height) {
  ////                drawXAxis(g, width, startX, startY, image_y_origin);
  ////            } else { // draw x axis at the bottom, even if that is not 0:
  ////                drawXAxis(g, width, startX, startY, startY);
  ////            }
  ////        }
  ////
  ////        // Draw the y axis, if requested:
  ////        if (showYAxis) {
  ////            if (image_fromX <= image_x_origin && image_x_origin <= image_toX) {
  ////                drawYAxis(g, height, startX, startY, image_x_origin);
  ////            } else { // draw y axis at the left, even if that is not 0:
  ////                drawYAxis(g, height, startX, startY, startX);
  ////            }
  ////        }
  ////    }
  ////
  ////    /*
  ////     * While painting the graph, draw the actual function data.
  ////     * @param g the graphics2d object to paint in
  ////     * @param image_fromX first visible X coordinate of the Graph display area (= after subtracting space reserved for Y axis)
  ////     * @param image_toX last visible X coordinate of the Graph display area (= after subtracting space reserved for Y axis)
  ////     * @param image_refX X coordinate of the origin, in the display area
  ////     * @param image_refY Y coordinate of the origin, in the display area
  ////     * @param xScaleFactor conversion factor between data space and image space, image_x = xScaleFactor * data_x
  ////     * @param yScaleFactor conversion factor between data space and image space, image_y = yScaleFactor * data_y
  ////     * @param startY the start position on the Y axis (= the lower bound of the drawing area)
  ////     * @param image_height the height of the drawable region for the y values
  ////     */
  ////    protected void drawData(Graphics2D g,
  ////        int image_fromX, int image_toX,
  ////        int image_refX, int image_refY,
  ////        int startY, int image_height,
  ////        double[] data, Color currentGraphColor, int currentGraphStyle, int currentDotStyle)
  ////    {
  ////        int index_fromX = imageX2indexX(image_fromX);
  ////        if (index_fromX < 0) index_fromX = 0;
  ////        int index_toX   = imageX2indexX(image_toX);
  ////        if (index_toX < data.length) index_toX += 20;
  ////        if (index_toX > data.length) index_toX = data.length;
  ////        //System.err.println("Drawing values " + index_fromX + " to " + index_toX + " of " + y.length);
  ////        double xo = 0.0;
  ////        double yo = 0.0;
  ////        double xp = 0.0;
  ////        double yp = 0.0;
  ////        g.setColor(currentGraphColor);
  ////        for (int i = index_fromX; i < index_toX; i++) {
  ////            if (!Double.isNaN(data[i])) {
  ////                xp = indexX2imageX(i);
  ////                yp = y2imageY(data[i]);
  ////                //System.err.println("Point "+i+": ("+(image_refX+(int)xp)+","+(image_refY-(int)yp)+")");
  ////                if (currentGraphStyle == DRAW_LINE || currentGraphStyle == DRAW_LINEWITHDOTS) {
  ////                    g.drawLine(image_refX+(int)xo, image_refY-(int)yo, image_refX+(int)xp, image_refY-(int)yp);
  ////                }
  ////                if (currentGraphStyle == DRAW_DOTS || currentGraphStyle == DRAW_LINEWITHDOTS) {
  ////                    drawDot(g, image_refX+(int)xp, image_refY-(int)yp, currentDotStyle);
  ////                }
  ////                if (currentGraphStyle == DRAW_HISTOGRAM) {
  ////                    int topY = image_refY;
  ////                    if (yp>0) topY = image_refY-(int)yp;
  ////                    int histHeight = (int) Math.abs(yp);
  ////                    // cut to drawing area if x axis not at y==0:
  ////                    if (topY+histHeight>startY) {
  ////                        histHeight = startY-topY;
  ////                    }
  ////                    g.setColor(currentGraphColor);
  ////                    g.fillRect(image_refX+(int)xp-histogramWidth/2, topY, histogramWidth, histHeight);
  ////                    g.setColor(histogramBorderColor);
  ////                    g.drawRect(image_refX+(int)xp-histogramWidth/2, topY, histogramWidth, histHeight);
  ////                }
  ////                xo = xp;
  ////                yo = yp;
  ////            }
  ////        }
  ////    }
  //
  ////    protected void drawDot(Graphics2D g, int x, int y, int currentDotStyle)
  ////    {
  ////        switch(currentDotStyle) {
  ////            case DOT_FULLCIRCLE:
  ////                g.fillOval(x-dotSize/2, y-dotSize/2, dotSize, dotSize);
  ////                break;
  ////            case DOT_FULLSQUARE:
  ////                g.fillRect(x-dotSize/2, y-dotSize/2, dotSize, dotSize);
  ////                break;
  ////            case DOT_FULLDIAMOND:
  ////                g.fillPolygon(new int[]{x-dotSize/2,x, x+dotSize/2, x},
  ////                              new int[]{y, y-dotSize/2, y, y+dotSize/2}, 4);
  ////                break;
  ////            case DOT_EMPTYCIRCLE:
  ////                g.drawOval(x-dotSize/2, y-dotSize/2, dotSize, dotSize);
  ////                break;
  ////            case DOT_EMPTYSQUARE:
  ////                g.drawRect(x-dotSize/2, y-dotSize/2, dotSize, dotSize);
  ////                break;
  ////            case DOT_EMPTYDIAMOND:
  ////                g.drawPolygon(new int[]{x-dotSize/2,x, x+dotSize/2, x},
  ////                              new int[]{y, y-dotSize/2, y, y+dotSize/2}, 4);
  ////                break;
  ////            default:
  ////                break;
  ////        }
  ////    }
  //
  ////    protected void drawYAxis(Graphics2D g, double height, int startX, int startY, int image_x_origin) {
  ////        g.setColor(axisColor);
  ////        double yRange = getYRange();
  ////        g.drawLine(image_x_origin, startY, image_x_origin, startY-(int)height);
  ////        // Do not try to draw units if yRange is 0:
  ////        if (yRange == 0) return;
  ////        // Units on the y axis:
  ////        // major units with labels every 50-100 pixels
  ////        int unitOrder = (int)Math.floor(Math.log(yRange/5)/Math.log(10));
  ////        double unitDistance = Math.pow(10, unitOrder);
  ////        double image_unitDistance = unitDistance/yRange * height;
  ////        if (image_unitDistance < 20) {
  ////            unitDistance *= 5;
  ////        } else if (image_unitDistance < 50) {
  ////            unitDistance *= 2;
  ////        }
  ////        double unitStart = ymin;
  ////        double modulo = ymin%unitDistance;
  ////        if (modulo != 0) {
  ////            if (modulo > 0)
  ////                unitStart += unitDistance - modulo;
  ////            else // < 0
  ////                unitStart += Math.abs(modulo);
  ////        }
  ////        PrintfFormat labelFormat;
  ////        if (unitOrder > 0) {
  ////            labelFormat = new PrintfFormat("%.0f");
  ////        } else {
  ////            labelFormat = new PrintfFormat("%." + (-unitOrder) + "f");
  ////        }
  ////        boolean intLabels = ((int)unitDistance == (int)Math.ceil(unitDistance));
  ////        //System.err.println("y axis: yRange=" + yRange + ", unitDistance=" + unitDistance + ", unitStart=" + unitStart + ", ymin=" + ymin + ", ymin%unitDistance=" + (ymin%unitDistance));
  ////        for (double i=unitStart; i<ymax; i+= unitDistance) {
  ////            double yunit = (i-ymin)/yRange * height;
  ////            g.drawLine(image_x_origin+5, startY-(int)yunit, image_x_origin-5, startY-(int)yunit);
  ////            // labels to the left of y axis:
  ////            g.drawString(labelFormat.sprintf(i), image_x_origin-30, startY-(int)yunit+5);
  ////        }
  ////    }
  //
  ////    protected void drawXAxis(Graphics2D g, double width, int startX, int startY, int image_y_origin) {
  ////        g.setColor(axisColor);
  ////        double xRange = getXRange();
  ////        //System.err.println("Drawing X axis from " + startX + " to " + startX+(int)width + "; startY="+startY+", image_y_origin="+image_y_origin);
  ////        g.drawLine(startX, image_y_origin, startX+(int)width, image_y_origin);
  ////        // Units on the x axis:
  ////        // major units with labels every 50-100 pixels
  ////        int nUnits = (int) width / 50;
  ////        int unitOrder = (int) Math.floor(Math.log(xRange/nUnits)/Math.log(10));
  ////        double unitDistance = Math.pow(10, unitOrder);
  ////        double image_unitDistance = unitDistance/xRange * width;
  ////        if (image_unitDistance < 20) {
  ////            unitDistance *= 5;
  ////        } else if (image_unitDistance < 50) {
  ////            unitDistance *= 2;
  ////        }
  ////        double unitStart = x0;
  ////        double modulo = x0%unitDistance;
  ////        if (modulo != 0) {
  ////            if (modulo > 0)
  ////                unitStart += unitDistance - modulo;
  ////            else // < 0
  ////                unitStart += Math.abs(modulo);
  ////        }
  ////        PrintfFormat labelFormat;
  ////        if (unitOrder > 0) {
  ////            labelFormat = new PrintfFormat("%.0f");
  ////        } else {
  ////            labelFormat = new PrintfFormat("%." + (-unitOrder) + "f");
  ////        }
  ////        //System.err.println("x axis: xRange=" + xRange + ", unitDistance=" + unitDistance + ", unitStart=" + unitStart + ", x0=" + x0 + ", image_unitDistance=" + image_unitDistance);
  ////        for (double i=unitStart; i<x0+xRange; i+= unitDistance) {
  ////            double xunit = (i-x0)/xRange * width;
  ////            //System.err.println("Drawing unit at " + (startX+(int)xunit));
  ////            g.drawLine(startX+(int)xunit, image_y_origin+5, startX+(int)xunit, image_y_origin-5);
  ////            // labels below x axis:
  ////            g.drawString(labelFormat.sprintf(i), startX + (int)xunit-10, image_y_origin+20);
  ////        }
  ////    }
  //
  ////    public void paintComponent(Graphics gr) {
  ////        if (graphImage == null
  ////            || getWidth() != graphImage.getWidth()
  ////            || getHeight() != graphImage.getHeight()) {
  ////            createGraphImage();
  ////        }
  ////        Graphics2D g = (Graphics2D) gr;
  ////        g.drawImage(graphImage, null, null);
  ////    }
  //
  ////    protected int imageX2indexX(int imageX)
  ////    {
  ////        if (dataseries.isEmpty()) return 0;
  ////        double[] data = dataseries.get(0);
  ////        if (data == null) return 0;
  ////        double xScaleFactor = ((double) getWidth()-paddingLeft-paddingRight)/data.length;
  ////        return (int) (imageX / xScaleFactor);
  ////    }
  //
  ////    protected double imageX2X(int imageX)
  ////    {
  ////        double[] data = dataseries.get(0);
  ////        double xScaleFactor = ((double)getWidth()-paddingLeft-paddingRight)/(data.length*xStep);
  ////        return x0 + imageX / xScaleFactor;
  ////    }
  //
  ////    protected int indexX2imageX(int indexX)
  ////    {
  ////        if (dataseries.isEmpty()) return 0;
  ////        double[] data = dataseries.get(0);
  ////        if (data == null) return 0;
  ////        double xScaleFactor = ((double)getWidth()-paddingLeft-paddingRight)/data.length;
  ////        return (int) (indexX * xScaleFactor);
  ////    }
  //
  ////    protected int X2imageX(double x)
  ////    {
  ////        double[] data = dataseries.get(0);
  ////        double xScaleFactor = ((double)getWidth()-paddingLeft-paddingRight)/(data.length*xStep);
  ////        return (int) ((x - x0) * xScaleFactor);
  ////    }
  //
  //    protected int X2indexX(double x)
  //    {
  //        return (int) ((x - x0) / xStep);
  //    }
  //
  ////    protected double imageY2Y(int imageY)
  ////    {
  ////
  ////        double yScaleFactor = ((double) getHeight()-paddingTop-paddingBottom)/getYRange();
  ////        return imageY / yScaleFactor;
  ////    }
  //
  ////    protected int y2imageY(double y)
  ////    {
  ////
  ////        double yScaleFactor = ((double) getHeight()-paddingTop-paddingBottom)/getYRange();
  ////        return (int) (y * yScaleFactor);
  ////    }
  //
  //    protected double getYRange()
  //    {
  //        double yRange = ymax - ymin;
  //        if (Double.isNaN(yRange)) yRange = 0;
  //        return yRange;
  //    }
  //
  //    protected double getXRange()
  //    {
  //        double[] data = dataseries.get(0);
  //        double xRange = data.length * xStep;
  //        return xRange;
  //    }
  ////
  ////    public CursorDisplayer.CursorLine getPositionCursor()
  ////    {
  ////        if (Double.isNaN(positionCursor.x)) return null;
  ////        return new CursorDisplayer.CursorLine(this, paddingLeft+X2imageX(positionCursor.x),
  ////                paddingTop, getHeight()-paddingBottom);
  ////    }
  ////
  ////    public CursorDisplayer.CursorLine getRangeCursor()
  ////    {
  ////        if (Double.isNaN(rangeCursor.x)) return null;
  ////        int imageX = X2imageX(rangeCursor.x);
  ////        return new CursorDisplayer.CursorLine(this, paddingLeft+X2imageX(rangeCursor.x),
  ////                paddingTop, getHeight()-paddingBottom, Color.YELLOW);
  ////    }
  ////
  ////    public CursorDisplayer.Label getValueLabel()
  ////    {
  ////        if (Double.isNaN(positionCursor.x)) return null;
  ////        int imageX = X2imageX(positionCursor.x) + 10;
  ////        int imageY = paddingTop + 10;
  ////        return new CursorDisplayer.Label(this, getLabel(positionCursor.x, positionCursor.y),
  ////                imageX, imageY);
  ////    }
  ////
  ////    public void addCursorListener(CursorListener l)
  ////    {
  ////        cursorListeners.add(l);
  ////    }
  ////
  ////    public CursorListener[] getCursorListeners()
  ////    {
  ////        return (CursorListener[]) cursorListeners.toArray(new CursorListener[0]);
  ////    }
  //
  ////    public boolean removeCursorListener(CursorListener l)
  ////    {
  ////        return cursorListeners.remove(l);
  ////    }
  ////
  ////    protected void notifyCursorListeners()
  ////    {
  ////        for (Iterator it = cursorListeners.iterator(); it.hasNext(); ) {
  ////            CursorListener l = (CursorListener) it.next();
  ////            l.updateCursorPosition(new CursorEvent(this));
  ////        }
  ////    }
  ////
  ////    /**
  ////     * Used when keeping several FunctionGraphs' cursor positions in synchrony.
  ////     * Register each other as cursor listeners before the glass pane; whichever gets
  ////     * clicked causes the others to be updated. Make sure to add any peers _before_
  ////     * any displaying cursor listeners, to make sure all are in line before being
  ////     * displayed.
  ////     */
  ////    public void updateCursorPosition(CursorEvent e)
  ////    {
  ////        FunctionGraph source = e.getSource();
  ////        positionCursor.x = source.positionCursor.x;
  ////        rangeCursor.x = source.rangeCursor.x;
  ////    }
  //
  //
  ////    public JFrame showInJFrame(String title, boolean allowZoom, boolean exitOnClose)
  ////    {
  ////        return showInJFrame(title, DEFAULT_WIDTH, DEFAULT_HEIGHT + 50, allowZoom, true, exitOnClose);
  ////    }
  ////
  ////    public JFrame showInJFrame(String title, boolean allowZoom, boolean showControls, boolean exitOnClose)
  ////    {
  ////        return showInJFrame(title, DEFAULT_WIDTH, DEFAULT_HEIGHT + 50, allowZoom, showControls, exitOnClose);
  ////    }
  ////
  ////    public JFrame showInJFrame(String title, int width, int height, boolean allowZoom, boolean exitOnClose)
  ////    {
  ////        return showInJFrame(title, width, height, allowZoom, true, exitOnClose);
  ////    }
  //
  ////    public JFrame showInJFrame(String title, int width, int height, boolean allowZoom, boolean showControls, boolean exitOnClose)
  ////    {
  ////        final JFrame main = new JFrame(title);
  ////        int mainWidth = width;
  ////        JScrollPane scroll = new JScrollPane(this);
  ////        scroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
  ////        //JLayeredPane layers = new JLayeredPane();
  ////        //layers.add(scroll, new Integer(1));
  ////        //scroll.setBounds(0, 0, this.getPreferredSize().width, this.getPreferredSize().height);
  ////        //glass.setBounds(0, 0, this.getPreferredSize().width, this.getPreferredSize().height);
  ////        //layers.add(glass, new Integer(50));
  ////        main.getContentPane().add(scroll, BorderLayout.CENTER);
  ////        final CursorDisplayer glass = new CursorDisplayer();
  ////        main.setGlassPane(glass);
  ////        glass.setVisible(true);
  ////        glass.addCursorSource(this);
  ////        this.addCursorListener(glass);
  ////        if (allowZoom) {
  ////            JPanel zoomPanel = new JPanel();
  ////            zoomPanel.setLayout(new BoxLayout(zoomPanel, BoxLayout.Y_AXIS));
  ////            main.getContentPane().add(zoomPanel, BorderLayout.WEST);
  ////            zoomPanel.add(Box.createVerticalGlue());
  ////            JButton zoomIn = new JButton("Zoom In");
  ////            zoomIn.setAlignmentX(CENTER_ALIGNMENT);
  ////            zoomIn.addActionListener(new ActionListener() {
  ////                public void actionPerformed(ActionEvent evt) {
  ////                    setZoomX(getZoomX()*2);
  ////                    FunctionGraph.this.requestFocus();
  ////                }
  ////            });
  ////            zoomPanel.add(zoomIn);
  ////            JButton zoomOut = new JButton("Zoom Out");
  ////            zoomOut.setAlignmentX(CENTER_ALIGNMENT);
  ////            zoomOut.addActionListener(new ActionListener() {
  ////                public void actionPerformed(ActionEvent evt) {
  ////                    setZoomX(getZoomX()*0.5);
  ////                    FunctionGraph.this.requestFocus();
  ////                }
  ////            });
  ////            zoomPanel.add(zoomOut);
  ////            if (showControls) {
  ////                JPanel controls = getControls();
  ////                if (controls != null) {
  ////                    zoomPanel.add(Box.createVerticalGlue());
  ////                    controls.setAlignmentX(CENTER_ALIGNMENT);
  ////                    zoomPanel.add(controls);
  ////                }
  ////            }
  ////            mainWidth += zoomPanel.getPreferredSize().width + 30;
  ////            zoomPanel.add(Box.createVerticalGlue());
  ////        }
  ////        main.setSize(mainWidth, height);
  ////        if (exitOnClose) {
  ////            main.addWindowListener(new java.awt.event.WindowAdapter() {
  ////                public void windowClosing(java.awt.event.WindowEvent evt) {
  ////                    System.exit(0);
  ////                }
  ////            });
  ////        }
  ////        main.setVisible(true);
  ////        this.requestFocus();
  ////        return main;
  ////    }
  //
  ////    /**
  ////     * Subclasses may provide specific controls here.
  ////     * @return a JPanel filled with the controls, or null if none are to be provided.
  ////     */
  ////    protected JPanel getControls()
  ////    {
  ////        return null;
  ////    }
  //
  ////    protected String getLabel(double x, double y)
  ////    {
  ////        // be about one order of magnitude less precise than there are pixels
  ////        int pixelPrecisionX = 2;
  ////        if (graphImage != null) {
  ////            pixelPrecisionX = (int) (Math.log(graphImage.getWidth()/getXRange())/Math.log(10));
  ////        }
  ////        int precisionX = -(int)(Math.log(getXRange())/Math.log(10)) + pixelPrecisionX;
  ////        if (precisionX < 0) precisionX = 0;
  ////        // ignore imageY
  ////        int precisionY = -(int)(Math.log(getYRange())/Math.log(10)) + 2;
  ////        if (precisionY < 0) precisionY = 0;
  ////        int indexX = X2indexX(x);
  ////        double[] data = dataseries.get(0);
  ////        return "f(" + new PrintfFormat("%."+precisionX+"f").sprintf(x)
  ////            + ")=" + new PrintfFormat("%."+precisionY+"f").sprintf(data[indexX]);
  ////
  ////    }
  //
  //    public class DoublePoint
  //    {
  //        public DoublePoint()
  //        {
  //            this(Double.NaN, Double.NaN);
  //        }
  //        public DoublePoint(double x, double y)
  //        {
  //            this.x = x;
  //            this.y = y;
  //        }
  //        double x;
  //        double y;
  //    }
  //}
  //
  //
  