edu.umn.ecology.populus.plot.BasicPlotInfo.java Source code

Java tutorial

Introduction

Here is the source code for edu.umn.ecology.populus.plot.BasicPlotInfo.java

Source

/*******************************************************************************
 * Copyright (c) 2015 Regents of the University of Minnesota.
 *
 * This software is released under GNU General Public License 2.0
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html
 *******************************************************************************/
package edu.umn.ecology.populus.plot;

import com.klg.jclass.chart.*;

import edu.umn.ecology.populus.math.*;
import edu.umn.ecology.populus.plot.plotshapes.*;
import edu.umn.ecology.populus.constants.ColorScheme;
import edu.umn.ecology.populus.fileio.Logging;

import java.awt.*;
import java.awt.geom.Ellipse2D;
import java.io.*;
import java.util.*;

import org.jfree.chart.ChartTheme;
import org.jfree.chart.JFreeChart;
import org.jfree.data.xy.AbstractXYDataset;
import org.jfree.data.xy.IntervalXYDataset;
import org.jfree.ui.TextAnchor;
import org.jfree.chart.annotations.XYTextAnnotation;
import org.jfree.chart.axis.LogarithmicAxis;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.NumberTickUnit;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.StandardXYBarPainter;
import org.jfree.chart.renderer.xy.XYBarRenderer;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;

import Jama.EigenvalueDecomposition;

/**
  * this class is meant to hold all the data necessary for graphing a data set. the vector <code>lines</code>
  * is meant to hold all the stylization information like line color, line style (e.g. dashed), symbol color, etc.
  * this class also hold information like on whether the chart should be a log-plot or not. one of the more
  * important parts of this class is the caption information and the viewing bounds information.
  * as far as the bounds go, JCChart actually does a pretty good job of coming up with default bounds, so it is
  * usually best to let it just handle the bounds. however some plots, like frequency plots, have bounds that should
  * be enforced.
  */
public class BasicPlotInfo extends ParamInfo implements ChartDataModel, //getNumSeries, getYSeries, getXSeries
        JCChartListener //changeChart, paintChart
{
    private static final long serialVersionUID = -882044708806321402L;

    ResourceBundle res = ResourceBundle.getBundle("edu.umn.ecology.populus.plot.Res");

    public static final int k2D = 0;
    public static final int k3D = 1;
    public static final int kDeFinetti = 2;
    public static final int kSpecial = 3;
    public static final int kEigenSystem = 4;

    /**
     * it is assumed that a graph will be a 2D plot be default. if this needs to be changed however,
     * the different types available are listed above.
     */
    public int outputType = k2D;

    /* only stick these in if they will be used, the check is if this is null. if these are set, then the
       text output behaves a different way than otherwise.*/
    public String[] vsTimeChars = null;

    /*Symbol Styles*/
    public static final int NONE = JCSymbolStyle.NONE;
    //public static final int SQUARE    = JCSymbolStyle.BOX;
    public static final int DOTS = -1;
    public static final int ARROW = -2;
    public static final int FLETCHING = -3;

    /*these objects are for turning a plot line into an arrow*/
    protected Vector<PlotTerminus> plotTerminusList = new Vector<PlotTerminus>();

    /*Line Styles*/
    public static final int CONTINUOUS = JCLineStyle.SOLID; // 1
    public static final int DISCRETE = JCLineStyle.SHORT_DASH; // 3        5x 10o 
    public static final int DASHED = JCLineStyle.LONG_DASH; // 2       10x 10o
    public static final int LSL_DASH = JCLineStyle.LSL_DASH; // 4       10x 10o  5x 10o
    public static final int DASH_DOT = JCLineStyle.DASH_DOT; // 5       10x 10o  1x 10o
    public static final int NO_LINE = JCLineStyle.NONE; // 0

    /*Usage: data[lineNo][0 for x, 1 for y, 2 for z][point number]*/
    double[][][] data;

    /** 
     * lines give styling info about the same lines in data.
     * 
     * TODO - if we want to remove dependencies to JCChart, we will need to create a class that
     *  functions as JCChartStyle here, and contains:
     *      LineStyle  (e.g., CONTINUOUS)
     *      LineWidth
     *      LineColor
     *      SymbolShape
     *      SymbolSize
     *      SymbolColor
     *      
     */
    private Vector<JCChartStyle> lines = new Vector<JCChartStyle>();

    /*these are a collection of flags*/
    private boolean hasIso = false;
    private boolean isDiscrete = false;
    private boolean labelT = true;
    private boolean startGridded = false;
    private boolean zIsDiscrete = false;
    private boolean xIsDiscrete = false;
    public boolean isBarChart = false;
    public boolean isFrequencies = false;
    public boolean isLogPlot = false;
    private boolean isLive = false;
    private boolean axisReset = false;

    /*this is used in the aspg matrix output*/
    private Object special = null;

    private String[] xCaption, yCaption, mainCaption;
    private String zCaption; //Still need to make Free MultiLine Label
    /* List of InnerLabel objects for inside labels */
    private Vector<InnerLabel> innerLabels = new Vector<InnerLabel>();
    private double xMin, xMax;
    private double yMin, yMax;
    private double zMin, zMax;
    private double xAxisMin, xAxisMax;
    private double yAxisMin, yAxisMax;
    private double zAxisMin, zAxisMax;
    public boolean xMinSet = false;
    public boolean yMinSet = false;
    public boolean zMinSet = false;
    public boolean xMaxSet = false;
    public boolean yMaxSet = false;
    public boolean zMaxSet = false;

    /*these flags control whether or not to just let JCChart to determine the viewing bounds
    regardless of what xMin, xMax have been set to*/
    //public boolean hasXMax = false, hasYMax = false, hasZMax = false;

    private class JFCXYAdapter extends AbstractXYDataset implements IntervalXYDataset {
        private static final long serialVersionUID = -1226686444066513897L;
        private double barWidth = 0.1;

        public JFCXYAdapter() {
            updateBarWidth(0);
        }

        public void updateBarWidth(int series) {
            if (data[series][0].length > 1) {
                double dx = data[series][0][1] - data[series][0][0];
                this.barWidth = 3.0 * dx / 4.0;
            }
        }

        @Override
        public int getItemCount(int series) {
            //Logging.log("In getItemCount " + series);
            return data[series][0].length;
        }

        @Override
        public Number getX(int series, int item) {
            //Logging.log("In getX " + series + ", " + item + " = " + data[series][0][item]);
            return data[series][0][item];
        }

        @Override
        public Number getY(int series, int item) {
            //Logging.log("In getY " + series + ", " + item + " = " + data[series][1][item]);
            return data[series][1][item];
        }

        @Override
        public int getSeriesCount() {
            //Logging.log("In getSeriesCount " + data.length);
            return data.length;
        }

        @Override
        public String getSeriesKey(int arg0) {
            //TODO - I think this is the name of the series, will this implementation suffice?
            return "" + arg0;
        }

        @Override
        public Number getStartX(int series, int item) {
            return getXValue(series, item) - barWidth / 2.0;
        }

        @Override
        public double getStartXValue(int series, int item) {
            return getStartX(series, item).doubleValue();
        }

        @Override
        public Number getEndX(int series, int item) {
            return getXValue(series, item) + barWidth / 2.0;
        }

        @Override
        public double getEndXValue(int series, int item) {
            return getEndX(series, item).doubleValue();
        }

        @Override
        public Number getStartY(int series, int item) {
            return getY(series, item);
        }

        @Override
        public double getStartYValue(int series, int item) {
            return getY(series, item).doubleValue();
        }

        @Override
        public Number getEndY(int series, int item) {
            return getY(series, item);
        }

        @Override
        public double getEndYValue(int series, int item) {
            return getY(series, item).doubleValue();
        }
    }

    /***************
    * CONSTRUCTORS *
    ***************/
    public BasicPlotInfo() {
    }

    /**
     * this is used in ASPGParamInfo for the matrix style output
     * @param special
     */
    public BasicPlotInfo(Object special) {
        if (special instanceof EigenvalueDecomposition) {
            outputType = kEigenSystem;
        } else {
            outputType = kSpecial;
        }
        this.special = special;
    }

    /**
     * use this method if you want to set the data in one place, but will
     * add the captions later.
     */
    public BasicPlotInfo(double[][][] dataPoints) {
        setData(dataPoints);
    }

    /**
     * Constructor for 2D graph, giving data and captions.
     * This constructor is probably the most used of all of them.
     */
    public BasicPlotInfo(double[][][] dataPoints, String mC, String xC, String yC) {
        this.xCaption = new String[] { xC };
        this.yCaption = new String[] { yC };
        this.mainCaption = new String[] { mC };
        setData(dataPoints);
    }

    /**
     * if this constructor is used, then nothing else needs to happen to produce a 3D graph.
     */
    public BasicPlotInfo(double[][][] dataPoints, String mC, String xC, String yC, String zC) {
        this.xCaption = new String[] { xC };
        this.yCaption = new String[] { yC };
        this.zCaption = zC;
        this.mainCaption = new String[] { mC };
        outputType = k3D;
        setData(dataPoints);
    }

    private static float[] getDashArray(int type) {
        final float[][] ttable = { { 0.0f, 1.0f }, //NONE   0
                null, //SOLID  1
                { 10.0f, 10.0f }, //LONG   2
                { 5.0f, 10.0f }, //SHORT  3
                { 10.0f, 10.0f, 5.0f, 10.0f }, //LSL    4
                { 10.0f, 10.0f, 1.0f, 10.0f }, //DASH.  5
        };

        return ttable[type];
    }

    public ChartTheme getJFreeChartTheme() {
        class PopChartTheme implements ChartTheme {
            private BasicPlotInfo bpiRef;

            public PopChartTheme(BasicPlotInfo bpi) {
                this.bpiRef = bpi;
            }

            public void apply(JFreeChart chart) {
                JFCXYAdapter jfca = new JFCXYAdapter();
                XYPlot plot = chart.getXYPlot();
                plot.setDataset(jfca);

                if (isLogPlot) {
                    plot.setRangeAxis(new LogarithmicAxis(""));
                }
                if (isFrequencies) {
                    ValueAxis va = plot.getRangeAxis();
                    if (va instanceof NumberAxis) {
                        NumberAxis na = (NumberAxis) va;
                        na.setTickUnit(new NumberTickUnit(0.1));
                    } else {
                        Logging.log("Range Axis is not NumberAxis, why?", Logging.kWarn);
                    }
                }

                if (xMinSet)
                    plot.getDomainAxis().setLowerBound(xAxisMin);
                if (xMaxSet)
                    plot.getDomainAxis().setUpperBound(xAxisMax);
                if (yMinSet)
                    plot.getRangeAxis().setLowerBound(yAxisMin);
                if (yMaxSet)
                    plot.getRangeAxis().setUpperBound(yAxisMax);

                //TODO - just use this renderer
                plot.setRenderer(new ChartRendererWithOrientatedShapes(bpiRef));

                XYItemRenderer r = plot.getRenderer();
                //         AbstractXYItemRenderer r = plot.getRenderer();
                if (r instanceof XYLineAndShapeRenderer) {
                    XYLineAndShapeRenderer renderer = (XYLineAndShapeRenderer) r;
                    for (int i = 0; i < getNumSeries(); i++) {
                        //Set Line
                        renderer.setSeriesPaint(i, getLineColor(i));
                        renderer.setSeriesStroke(i, getLineStroke(i));

                        //Set Symbol
                        renderer.setSeriesFillPaint(i, getSymbolColor(i));
                        renderer.setSeriesOutlinePaint(i, getSymbolColor(i));

                        Shape shape = getSymbolShape(i);
                        if (shape != null) {
                            renderer.setSeriesShape(i, shape);
                            renderer.setSeriesShapesFilled(i, isSymbolOpaque(i));
                            renderer.setUseFillPaint(true);
                            renderer.setUseOutlinePaint(true);
                            renderer.setSeriesShapesVisible(i, true);
                        }
                    }
                } else if (r instanceof XYBarRenderer) {
                    XYBarRenderer barRenderer = (XYBarRenderer) r;
                    barRenderer.setBarPainter(new StandardXYBarPainter());
                } else {
                    Logging.log("Unknown renderer type: " + r.getClass(), Logging.kWarn);
                }

                //inner labels, used in AIDS: Therapy
                plot.clearAnnotations();
                Enumeration<InnerLabel> e = innerLabels.elements();
                while (e.hasMoreElements()) {
                    InnerLabel lab = (InnerLabel) e.nextElement();
                    Logging.log("Adding " + lab.caption + " at " + lab.x + ", " + lab.y);

                    XYTextAnnotation annotation = new XYTextAnnotation(lab.caption, lab.x, lab.y);
                    annotation.setTextAnchor(TextAnchor.BOTTOM_CENTER);
                    annotation.setOutlineVisible(true);
                    plot.addAnnotation(annotation);

                    //I actually think the annotation above is ugly.  We can use one of these instead in the future, maybe...
                    /*PointerAnnotation may look cool...
                     * That 2.0 is the angle, randomly picked
                     * XYPointerAnnotation annotation = new XYPointerAnnotation(lab.caption, lab.x, lab.y, 2.0); 
                     */

                    /*
                    ValueMarker marker = new ValueMarker(lab.x);
                    marker.setLabel(lab.caption);
                    marker.setLabelAnchor(RectangleAnchor.BOTTOM_LEFT);
                    plot.addDomainMarker(marker);
                     */

                }

                //This is set for GD: AMCM
                if (startGridded) {
                    plot.setDomainGridlinesVisible(true);
                    plot.setDomainGridlinePaint(Color.BLACK);
                    plot.setRangeGridlinesVisible(true);
                    plot.setRangeGridlinePaint(Color.BLACK);
                }

            }
        }
        return new PopChartTheme(this);
    }

    public void styleJFree(JFreeChart chart) {
        getJFreeChartTheme().apply(chart);
    }

    /************************
    * JCChart Communicators *
    ************************/
    /** This modifies the chart argument for the look and data wanted */
    public void styleJC(JCChart chart) {
        findBounds(); //compute xMin, xMax, yMin, yMax, zMin, zMax
        for (int i = 0; i < getNumSeries(); i++)
            chart.getDataView(0).setChartStyle(i, getChartStyle(i));

        chart.getDataView(0).setDataSource(this);
        JCAxis h = chart.getChartArea().getHorizActionAxis();
        setAxis(chart);
        if (this.isBarChart) {
            chart.getDataView(0).setChartType(JCChart.BAR);
            h.setAnnotationMethod(JCAxis.VALUE_LABELS);
            JCValueLabel[] labels = new JCValueLabel[data[0][0].length];
            for (int i = 0; i < labels.length; i++) {
                labels[i] = new JCValueLabel(data[0][0][i],
                        "" + NumberMath.roundSig(data[0][0][i], 4, NumberMath.NORMAL));
            }
            h.setValueLabels(labels);
        } else {
            chart.getDataView(0).setChartType(JCChart.PLOT);
            h.setAnnotationMethod(JCAxis.VALUE);
            h.setValueLabels(null);
        }

        chart.getChartLabelManager().removeAllChartLabels();
        Enumeration<InnerLabel> e = innerLabels.elements();
        while (e.hasMoreElements()) {
            InnerLabel lab = (InnerLabel) e.nextElement();
            JCChartLabel cl = new JCChartLabel(lab.caption);
            cl.setDataCoord(new JCDataCoord(lab.x, lab.y));
            cl.setAnchor(JCChartLabel.NORTH);
            cl.setAttachMethod(JCChartLabel.ATTACH_DATACOORD);
            cl.getComponent().setBorder(javax.swing.BorderFactory.createLineBorder(Color.black, 2));
            chart.getChartLabelManager().addChartLabel(cl);
        }

        if (startGridded) {
            JCAxis v = chart.getChartArea().getVertActionAxis();
            h.setGridSpacingIsDefault(true);
            v.setGridSpacingIsDefault(true);
            h.setGridVisible(true);
            v.setGridVisible(true);
        }

        chart.update();
        chart.addChartListener(this);
    }

    /**
     * this method is also called in BasicPlotCanvas so that it can fix the axes
     * when the graph is reset
     * @param chart
     */
    public void setAxis(JCChart chart) {
        axisReset = true;
        JCAxis v = chart.getChartArea().getVertActionAxis();
        JCAxis h = chart.getChartArea().getHorizActionAxis();

        if (xMinSet)
            h.setMin(xAxisMin);
        else
            h.setMinIsDefault(true);
        if (xMaxSet)
            h.setMax(xAxisMax);
        else
            h.setMaxIsDefault(true);
        if (yMinSet)
            v.setMin(yAxisMin);
        else
            v.setMinIsDefault(true);
        if (yMaxSet)
            v.setMax(yAxisMax);
        else
            v.setMaxIsDefault(true);

        if (isFrequencies) {
            v.setNumSpacing(0.1);
        }
        v.setLogarithmic(isLogPlot);
    }

    /**
     * this method will create more elements in the lines vector if the requested index doesn't exist.
     * the reason it does this is so that the graphing styles of lines can be set before the lines themselves
     * are added.
     * @param n
     * @return
     */
    private JCChartStyle getChartStyle(int n) {
        while (lines.size() <= n)
            lines.add(getDefaultStyle());
        return ((JCChartStyle) lines.elementAt(n));
    }

    private Stroke getLineStroke(int n) {
        JCChartStyle jccs = getChartStyle(n);
        return new BasicStroke((float) jccs.getLineWidth(), BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 10.0f,
                getDashArray(jccs.getLinePattern()), 0.0f);
    }

    private Color getLineColor(int n) {
        return getChartStyle(n).getLineColor();
    }

    /**
     * 
     * @param n Line index
     * @return shape of the symbol on the line.  May be null if no symbol.
     */
    private Shape getSymbolShape(int n) {
        JCSymbolStyle jcss = getChartStyle(n).getSymbolStyle();
        if (jcss.getCustomShape() != null) {
            //TODO -- this should ALWAYS be a PlotTerminus if it's customized.  We should force it in the compile.
            try {
                PlotTerminus pt = (PlotTerminus) jcss.getCustomShape();
                return pt.getShape();
            } catch (ClassCastException e) {
                Logging.log("Why is custom shape not PlotTerminus??", Logging.kWarn);
                Logging.log(e);
                return null;
            }
        }
        switch (jcss.getShape()) {
        case DOTS:
            double sz = 6.0; //TODO ??
            return new Ellipse2D.Double(sz / 2.0, sz / 2.0, sz, sz);
        default:
        case NONE:
            return null;
        }
    }

    private boolean isSymbolOpaque(int n) {
        JCSymbolStyle jcss = getChartStyle(n).getSymbolStyle();
        if (jcss.getCustomShape() != null) {
            //TODO -- this should ALWAYS be a PlotTerminus if it's customized.  We should force it in the compile.
            try {
                PlotTerminus pt = (PlotTerminus) jcss.getCustomShape();
                return pt.isOpaque();
            } catch (ClassCastException e) {
                Logging.log("Why is custom shape not PlotTerminus??", Logging.kWarn);
                Logging.log(e);
                return true;
            }
        }
        //default is true
        return true;
    }

    private Color getSymbolColor(int n) {
        return getChartStyle(n).getSymbolColor();
    }

    /***********************
    * Manipulation of data *
    ***********************/
    public double[][][] getData() {
        return data;
    }

    public void setData(double[][][] points) {
        data = null;
        for (int i = 0; i < points.length; i++)
            addData(points[i]);
    }

    /** Swaps chart data of two indices */
    public void swapData(int index1, int index2) {
        int nSeries = getNumSeries();
        if (index1 >= nSeries || index2 >= nSeries) {
            Logging.log("Invalid indices passed into swapData");
            return;
        }

        double[][] tempData = data[index1];
        data[index1] = data[index2];
        data[index2] = tempData;
    }

    /**Similar to swapData, but move one index to the front, and
     * shifts everything else back.
     */
    public void moveDataToFront(int index) {
        int nSeries = getNumSeries();
        if (index >= nSeries || index < 0) {
            Logging.log("Invalid index (" + index + "to moveDataToFront");
            return;
        }

        double[][] tempData = data[index];
        for (int i = index; i > 0; --i) {
            data[i] = data[i - 1];
        }
        data[0] = tempData;
    }

    /**Similar to swapData, but move one index to the back, and
     * shifts everything else forward.
     */
    public void moveDataToBack(int index) {
        int nSeries = getNumSeries();
        if (index >= nSeries || index < 0) {
            Logging.log("Invalid index (" + index + "to moveDataToFront");
            return;
        }

        double[][] tempData = data[index];
        for (int i = index; i < data.length - 1; ++i) {
            Logging.log("data[" + index + "] = data[" + (index + 1) + "]");
            data[i] = data[i + 1];
        }
        data[data.length - 1] = tempData;
        Logging.log("data[" + (data.length - 1) + "] = data[" + index + "]");
    }

    public int addData(double[][] newLine) {
        int nSeries = getNumSeries();
        double[][][] newData = new double[nSeries + 1][][];
        for (int i = 0; i < nSeries; i++)
            newData[i] = data[i];

        newData[newData.length - 1] = newLine;
        if (nSeries == lines.size())
            lines.add(getDefaultStyle());
        data = newData;
        return nSeries;
    }

    /**
     * to change what a line would look like by default, change the settings in this method
     * 
     * @return
     */
    private JCChartStyle getDefaultStyle() {
        JCChartStyle jccs = new JCChartStyle();
        jccs.getLineStyle().setPattern(CONTINUOUS);
        jccs.getSymbolStyle().setShape(NONE);
        jccs.setLineWidth(2);
        jccs.setSymbolSize(6);
        jccs.setLineColor(ColorScheme.colors[getNumSeries() % ColorScheme.colors.length]);
        jccs.setSymbolColor(ColorScheme.colors[getNumSeries() % ColorScheme.colors.length]);
        return jccs;
    }

    /**
     * Required for JCChartListener, called after a paint event.
     * 
     * One of the methods that make this class a JCChartListener. This method is called when the
     * chart is finally drawn so that the aspect of the plot window can be taken into account when
     * calculating the angle of the plot arrow and fletching.
     * 
     *  TODO - will we need to dynamically check for JFreeChart when resized, or will it automagically work out?
     * @param jc
     */
    public void paintChart(JCChart jc) {
        if (plotTerminusList.isEmpty())
            return;
        JCAxis h = jc.getChartArea().getHorizActionAxis();
        JCAxis v = jc.getChartArea().getVertActionAxis();
        double chartMod = (double) jc.getChartArea().getHeight() / (double) jc.getChartArea().getWidth();
        double dataMod = (v.getMax() - v.getMin()) / (h.getMax() - h.getMin());
        boolean needUpdate = updateDirectedSymbols(chartMod / dataMod);
        if (needUpdate)
            jc.update();
    }

    /**
     * This will tell the directed shapes (PlotTerminus) that they need to update their direction
     * 
     * @param ratio  (comp height/comp width) / (y value range / x value range)
     * @return true if values changed
     */
    public boolean updateDirectedSymbols(double ratio) {
        boolean needUpdate = false;
        Enumeration<PlotTerminus> e = plotTerminusList.elements();
        while (e.hasMoreElements()) {
            PlotTerminus term = e.nextElement();
            needUpdate |= term.updateAdjustment(ratio);
        }
        return needUpdate;
    }

    public void updateDirectedSymbolsJFC(ChartRendererWithOrientatedShapes r, double ratio) {
        boolean needUpdate = updateDirectedSymbols(ratio);
        if (needUpdate) {
            for (int i = 0; i < getNumSeries(); i++) {
                r.setSeriesShape(i, getSymbolShape(i));
            }
        }
    }

    /**
     * Required for JCChartListener
     * 
     * This method is called when one of the axes is changed, e.g. when a zoom is performed. if the graph
     * is a frequency graph, then we want to force the y-axis to have a spacing of 0.1, but if zoomed in, then
     * the 0.1 spacing isn't any good anymore, and we should let the axis choose its own spacing.
     * @param jce
     */
    public void changeChart(JCChartEvent jce) {
        JCAxis a = jce.getModifiedAxis();
        if (!a.isVertical())
            return;

        if (!axisReset) {
            a.setNumSpacingIsDefault(true);
        }
        axisReset = false;
    }

    public int getNumSeries() {
        if (data == null)
            return 0;
        return data.length;
    }

    public double[][] getPoints(int n) {
        return data[n];
    }

    public double[] getXSeries(int p0) {
        return this.data[p0][0];
    }

    public double[] getYSeries(int p0) {
        return this.data[p0][1];
    }

    /************************
    *  Style Manipulations  *
    ************************/
    public void setLineWidth(int whichLine, int width) {
        getChartStyle(whichLine).setLineWidth(width);
    }

    public void setLineColor(int whichLine, Color newColor) {
        getChartStyle(whichLine).setLineColor(newColor);
    }

    public void setLineStyle(int style) {
        for (int i = 0; i < this.getNumSeries(); i++) {
            getChartStyle(i).getLineStyle().setPattern(style);
        }
    }

    public void setLineStyle(int whichLine, int style) {
        getChartStyle(whichLine).getLineStyle().setPattern(style);
    }

    public void setDiscrete(int whichLine) {
        setLineStyle(whichLine, BasicPlotInfo.DASHED);
        setLineColor(whichLine, Color.black);
        setSymbolStyle(whichLine, BasicPlotInfo.DOTS);
    }

    public void setSymbolColor(int whichLine, Color newColor) {
        getChartStyle(whichLine).setSymbolColor(newColor);
    }

    public void setSymbolSize(int whichLine, int size) {
        getChartStyle(whichLine).setSymbolSize(size);
    }

    /** Like setSymbolStyle, but for the beginning or ending of a line
     * todo:  Ability to remove...
     */
    public void setTerminus(int whichLine, double[][] newLine, PlotTerminus term) {
        int terminusLine = getData().length;
        addData(newLine);
        getChartStyle(terminusLine).getSymbolStyle().setCustomShape(term.getJCShape());
        Color c = ColorScheme.colors[whichLine % ColorScheme.colors.length];
        setColor(c, terminusLine);
        plotTerminusList.add(term);
    }

    public void setSymbolStyle(int whichLine, int style) {
        if (style >= 0) {
            if (style > 0) {
                edu.umn.ecology.populus.fileio.Logging.log("Why set symbolstyle to " + style, Logging.kWarn);
            }
            getChartStyle(whichLine).getSymbolStyle().setShape(style);
        } else {
            switch (style) {
            case DOTS:
                getChartStyle(whichLine).getSymbolStyle().setCustomShape(new CircleTerminus(false).getJCShape());
                break;
            case ARROW:
            case FLETCHING:
                edu.umn.ecology.populus.fileio.Logging.log("Should use setTerminus", Logging.kWarn);
                break;
            }
        }
    }

    public void setSymbolStyle(int style) {
        for (int i = 0; i < getNumSeries(); i++) {
            setSymbolStyle(i, style);
        }
    }

    public void setSymbolSize(int size) {
        for (int i = 0; i < getNumSeries(); i++) {
            setSymbolSize(i, size);
        }
    }

    public void setColors(java.awt.Color[] newColors) {
        int numLines = getNumSeries();
        for (int i = 0; i < numLines; i++) {
            setColor(newColors[i % newColors.length], i);
        }
    }

    public void setColor(java.awt.Color newColor, int line) {
        //newColor = Color.black;
        //getChartStyle(line).getSymbolStyle().setColor(newColor); //Lars - redundant???
        getChartStyle(line).setSymbolColor(newColor);
        getChartStyle(line).setLineColor(newColor);
    }

    /******************************
    * Viewing Bound Manipulations *
    ******************************/
    public double getXMin() {
        return xAxisMin;
    }

    public double getXMax() {
        return xAxisMax;
    }

    public double getYMin() {
        return yAxisMin;
    }

    public double getYMax() {
        return yAxisMax;
    }

    public double getZMin() {
        return zAxisMin;
    }

    public double getZMax() {
        return zAxisMax;
    }

    /** Get min Y value (not view window).  MUST call findbounds() first */
    public double getMinYVal() {
        return yMin;
    }

    /** Get max Y value (not view window).  MUST call findbounds() first */
    public double getMaxYVal() {
        return yMax;
    }

    /** Get min X value (not view window).  MUST call findbounds() first */
    public double getMinXVal() {
        return xMin;
    }

    /** Get max X value (not view window).  MUST call findbounds() first */
    public double getMaxXVal() {
        return xMax;
    }

    public void setXMin(double newXMin) {
        xAxisMin = newXMin;
        xMinSet = true;
    }

    public void setXMax(double newXMax) {
        xAxisMax = newXMax;
        xMaxSet = true;
    }

    public void setYMin(double newYMin) {
        yAxisMin = newYMin;
        yMinSet = true;
    }

    public void setYMax(double newYMax) {
        yAxisMax = newYMax;
        yMaxSet = true;
    }

    public void setZMin(double newZMin) {
        zAxisMin = newZMin;
        zMinSet = true;
    }

    public void setZMax(double newZMax) {
        zAxisMax = newZMax;
        zMaxSet = true;
    }

    public void setWindow(double xmin, double xmax, double ymin, double ymax) {
        setXMin(xmin);
        setXMax(xmax);
        setYMin(ymin);
        setYMax(ymax);
    }

    /**
     * don't arbitrarily delete this method, the x and y max values are used to help
     * figure out plot arrow and fletching angles.
     */
    public void findBounds() {
        double currentX, currentY, currentZ;
        currentX = data[0][0][0];
        currentY = data[0][1][0];
        xMin = currentX;
        xMax = currentX;
        yMin = currentY;
        yMax = currentY;
        for (int i = 0; i < data.length; i++) {
            for (int j = 0; j < data[i][0].length; j++) {
                currentX = data[i][0][j];
                currentY = data[i][1][j];
                xMin = Math.min(xMin, currentX);
                xMax = Math.max(xMax, currentX);
                yMin = Math.min(yMin, currentY);
                yMax = Math.max(yMax, currentY);
            }
        }
        if (data[0].length == 3) {
            currentZ = data[0][2][0];
            zMin = currentZ;
            zMax = currentZ;
            for (int i = 0; i < data.length; i++) {
                for (int j = 0; j < data[i][2].length; j++) {
                    currentZ = data[i][2][j];
                    zMin = Math.min(zMin, currentZ);
                    zMax = Math.max(zMax, currentZ);
                }
            }
        }
    }

    /***********
    * Captions *
    ***********/
    public void setCaptions(String mainCaption, String xCaption, String yCaption, String zCaption) {
        this.mainCaption = new String[] { mainCaption };
        this.xCaption = new String[] { xCaption };
        this.yCaption = new String[] { yCaption };
        this.zCaption = zCaption;
    }

    public void setMainCaption(String cap) {
        mainCaption = new String[] { cap };
    }

    public void setXCaption(String newXCaption) {
        xCaption = new String[] { newXCaption };
    }

    public void setYCaption(String newYCaption) {
        yCaption = new String[] { newYCaption };
    }

    public void setZCaption(String newZCaption) {
        zCaption = newZCaption;
    }

    public void setMainCaptions(String[] cap) {
        mainCaption = cap;
    }

    public void setXCaptions(String[] newXCaption) {
        xCaption = newXCaption;
    }

    public void setYCaptions(String[] newYCaption) {
        yCaption = newYCaption;
    }

    public String getMainCaption() {
        return mainCaption[0];
    }

    public String getXCaption() {
        return xCaption[0];
    }

    public String getYCaption() {
        return yCaption[0];
    }

    public String getZCaption() {
        return zCaption;
    }

    public String[] getMainCaptions() {
        return mainCaption;
    }

    public String[] getXCaptions() {
        return xCaption;
    }

    public String[] getYCaptions() {
        return yCaption;
    }

    public void addInnerCaption(String caption, double x, double y) {
        InnerLabel label = new InnerLabel(caption, x, y);
        innerLabels.add(label);
    }

    public void clearInnerCaptions() {
        innerLabels.clear();
    }

    /***************************
    * Graph type manipulations *
    ***************************/
    public int getGraphType() {
        return outputType;
    }

    public void setGraphType(int newValue) {
        outputType = newValue;
    }

    public Object getSpecial() {
        return special;
    }

    public void setIsFrequencies(boolean isFreq) {
        this.isFrequencies = isFreq;
        if (isFreq) {
            setYMax(1.0);
            setYMin(0.0);
        }
    }

    /********
    * Flags *
    ********/
    public void set3DIsDiscrete(boolean z, boolean x) {
        this.zIsDiscrete = z;
        this.xIsDiscrete = x;
    }

    public boolean isZDiscrete() {
        return zIsDiscrete;
    }

    public boolean isXDiscrete() {
        return xIsDiscrete;
    }

    public void setDefaultAxis(boolean yes) {
        xMinSet = false;
        xMaxSet = false;
        yMinSet = false;
        yMaxSet = false;
        zMinSet = false;
        zMaxSet = false;
    }

    public boolean isHasIsoclines() {
        return getGraphType() == k3D && hasIso;
    }

    public void setHasIsoclines(boolean b) {
        this.hasIso = b;
    }

    public boolean isLabelsT() {
        return labelT;
    }

    public void setLabelsT(boolean b) {
        labelT = b;
    }

    public boolean isDiscrete() {
        return isDiscrete;
    }

    public void setIsLive(boolean b) {
        isLive = b;
    }

    public boolean isLive() {
        return (getGraphType() == k3D || getGraphType() == kDeFinetti) && isLive;
    }

    public boolean isStartGridded() {
        return startGridded;
    }

    public void setStartGridded(boolean b) {
        startGridded = b;
    }

    /**
     * this is the look that i've chosen for a discrete graph. the only thing that i've left out of this
     * method is the setting of the symbol color because i will assume that would be set elsewhere if it
     * needs to be set.
     * @param b
     */
    public void setIsDiscrete(boolean b) {
        isDiscrete = b;
        if (b) {
            setLineStyle(BasicPlotInfo.DASHED);
            for (int j = 0; j < getNumSeries(); j++) {
                setLineColor(j, Color.black); //TODO = why set to black?
                setSymbolStyle(j, BasicPlotInfo.DOTS);
            }
        } else {
            setSymbolStyle(BasicPlotInfo.NONE);
            setLineStyle(BasicPlotInfo.CONTINUOUS);
        }
    }

    /***************************************
    * Serialization and data storage stuff *
    ***************************************/
    public void dump(PrintWriter pw) {
        java.util.Enumeration<JCChartStyle> e;
        int inc = 1;
        pw.println(res.getString("Output_of") + mainCaption);
        pw.println(xCaption + res.getString("vs_") + yCaption);
        e = lines.elements();
        while (e.hasMoreElements()) {
            pw.println(res.getString("Line_") + inc++);
        }
    }

}

class InnerLabel {
    String caption;
    double x, y;

    InnerLabel(String c, double x, double y) {
        this.caption = c;
        this.x = x;
        this.y = y;
    }
}