guiDrought_Model.java [src/java/cfa] Revision: 00f80058bb3bdd01b1d15173d0673c872e1697c1  Date: Wed Nov 19 14:38:31 MST 2014
package cfa;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.annotations.XYAnnotation;
import org.jfree.chart.annotations.XYDrawableAnnotation;
import org.jfree.chart.annotations.XYPointerAnnotation;
import org.jfree.chart.annotations.XYTextAnnotation;
import org.jfree.chart.annotations.XYTitleAnnotation;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.axis.DateTickMarkPosition;
import org.jfree.chart.axis.LogarithmicAxis;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.NumberTickUnit;
import org.jfree.chart.axis.TickUnits;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.block.BlockBorder;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.StackedXYBarRenderer;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.chart.title.LegendTitle;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeTableXYDataset;
import org.jfree.data.time.Year;
import org.jfree.data.xy.XYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import org.jfree.ui.RectangleAnchor;
import org.jfree.ui.RectangleEdge;
import org.jfree.ui.TextAnchor;

/**
* Last Updated: 15-September-2014
* @author Tyler Wible
* @since 10-July-2012
*/
public class guiDrought_Model {
    //Inputs
    String mainFolder 	= "C:/Projects/TylerWible/CodeDirectories/NetBeans/CSIP/data/CFA/Drought";
    String database = "USGS";//"CDWR";//"STORET";//"UserData";//
    String organizationName = "USGS";//"Co. Division of Water Resources";//"Colorado Dept. of Public Health & Environment";//
    String stationID = "06752000";//"CLAGRECO";//"000028";//
    String stationName = "CACHE LA POUDRE RIV AT MO OF CN, NR FT COLLINS, CO";//"Cache La Poudre Near Greeley";//"BIG THOMPSON R NEAR MOUTH";//
    String beginDate 	= "";//"1900-01-01";//
    String endDate 	= "";//"2002-01-01";//
    String lambdaString	= "optimize";//"1";//
    String action	= "all";//"optimizeModel";//"optimizeParameters";//"useParameters";//
    String phiValues	= "1";
    String thetaValues	= "";
    double droughtLimit	= -1;
    String userData = "";//"Date\tFlow\n1997-04-29\t8.3\n1998-05-09\t60.2\n1999-05-29\t20.1";
    boolean mergeDatasets = false;//true;//
    String mergeMethod = "user";//"public";//"max";//"average";//"min";//
    
    //Outputs
    String len = "-1";
    String start = "?";
    String end = "?";
    String dataSource = "?";
    
    //Gets
    public File getResult() {
        return new File(mainFolder, "drought_summary.txt");
    }
    public String getTimeseriesGraph(){
        return "drought1_timeseries.jpg";
    }
    public String getColumnChart(){
        return "drought2_column.jpg";
    }
    public String getFittedDataGraph(){
        return "drought3_fitted.jpg";
    }
    public String getProjectedDataGraph(){
        return "drought4_projected.jpg";
    }
    public String getDroughtRecurrenceGraph(){
        return "drought5_recurrence.jpg";
    }
    public String[] get_optimizeModel_output() {
        if(thetaValues.equalsIgnoreCase("")){
            //The drought model used is AR(p) so only 1 graph
            String[] graphs = new String[1];
            graphs[0] = "optimize_drought_graph1.jpg";
            return graphs;
        }else{
            //The drought model used is ARMA(p,q) so only 2 graph
            String[] graphs = new String[2];
            graphs[0] = "optimize_drought_graph3.jpg";
            graphs[1] = "optimize_drought_graph4.jpg";
            return graphs;
        }
    }
    public File getTimeseriesOutput(){
        //This output file is for use with JSHighCharts
        return new File(mainFolder, "drought1_timeseries.out");
    }
    public File getColumnChartOutput(){
        //This output file is for use with JSHighCharts
        return new File(mainFolder, "drought2_column.outt");
        //Note that this file has a different extension as a flag for eRAMS to parse the first column as strings not numbers
    }
    public File getFittedGraphOutput(){
        //This output file is for use with JSHighCharts
        return new File(mainFolder, "drought3_fitted.out");
    }
    public File getProjectedGraphOutput(){
        //This output file is for use with JSHighCharts
        return new File(mainFolder, "drought4_projected.out");
    }
    public File getRecurrenceGraphOutput(){
        //This output file is for use with JSHighCharts
        return new File(mainFolder, "drought5_recurrence.out");
    }
    public String getLen() {
        return len;
    }
    public String getStart() {
        return start;
    }
    public String getEnd() {
        return end;
    }
    public String getDataSource(){
        return dataSource;
    }
    public String getDroughtLimit() {
        return String.valueOf(droughtLimit);
    }
    //These 'gets' are to help decide which return function to perform
    public String getAction() {
        return action;
    }
    public String getThetaValues() {
        return thetaValues;
    }
    
    
    //Sets
    public void setMainFolder(String mainFolder) {
        this.mainFolder = mainFolder;
    }
    public void setDatabase(String database) {
        this.database = database;
    }
    public void setOrganizationName(String organizationName) {
        this.organizationName = organizationName;
    }
    public void setStationID(String stationID) {
        this.stationID = stationID;
    }
    public void setStationName(String stationName) {
        this.stationName = stationName;
    }
    public void setBeginDate(String beginDate) {
        this.beginDate = beginDate;
    }
    public void setEndDate(String endDate) {
        this.endDate = endDate;
    }
    public void setLambdaString(String lambdaString) {
        this.lambdaString = lambdaString;
    }
    public void setAction(String action) {
        this.action = action;
    }
    public void setPhiValues(String phiValues) {
        this.phiValues = phiValues;
    }
    public void setThetaValues(String thetaValues) {
        this.thetaValues = thetaValues;
    }
    public void setDroughtLimit(double droughtLimit) {
        this.droughtLimit = droughtLimit;
    }
    public void setUserData(String userData) {
        this.userData = userData;
    }
    public void setMergeDatasets(boolean mergeDatasets) {
        this.mergeDatasets = mergeDatasets;
    }
    public void setMergeMethod(String mergeMethod) {
        this.mergeMethod = mergeMethod;
    }
    /**
     * Performs a drought analysis on the provided sortedData (daily average stream flow value) and calculates the long-term 
     * average water supply to be taken as the drought threshold.  First it calculates total annual river flows based on the 
     * provided daily stream flow averages, then calculates the long-term average of these annual flow values, then calculates 
     * the properties of the droughts (deficit, duration, intensity) compared against the drought threshold.  Finally graphs a 
     * timeseries of the annual flow data against the water demand.
     * @param sortedData  a string[][] containing daily average stream flow values in the following format: column1 = dates (yyyy-mm-dd format), 
     * column2 = daily average stream flow values (cfs)
     * @return  a String[] containing a tab-delimited summary of the droughts (water supply < water demand)
     * @throws IOException 
     */
    public String[] generalDroughtAnalysis(String[][] sortedData) throws IOException{
            DoubleMath doubleMath = new DoubleMath();
            DoubleArray doubleArray = new DoubleArray();
            //Calculate total annual flows
            double[][] annualData = sumAnnualFlows(sortedData);


            //Get the flow values and calculate the long-term (total) average of them
            droughtLimit = doubleMath.Average(doubleArray.getColumn(annualData, 1));


            //Call built in function to perform the drough analysis and graph the resulting data
            String[] droughtInfo = generalDroughtAnalysis(sortedData, droughtLimit);


            return droughtInfo;
    }
    /**
     * Performs a drought analysis on the provided sortedData (daily average stream flow value) and the provided long-term 
     * average water demand specified by the user.  First it calculated total annual river flows based on the provided daily 
     * stream flow averages, then calculates the properties of the droughts (deficit, duration, intensity) compared against 
     * the water demand.  Finally graphs a timeseries of the annual flow data against the water demand.
     * @param sortedData  a string[][] containing daily average stream flow values in the following format: column1 = dates (yyyy-mm-dd format), 
     * column2 = daily average stream flow values (cfs)
     * @param droughtLimit  a double representing the annual drought limit (water demand) on the watershed as specified by the user (in units of acre-ft/year)
     * @return  a String[] containing a tab-delimited summary of the droughts (water supply < water demand)
     * @throws IOException 
     */
    @SuppressWarnings("unchecked")
    public String[] generalDroughtAnalysis(String[][] sortedData,
                                           double droughtLimit) throws IOException{
//	//Test AR(p) model on sample data
//	double[][] annualData = sumAnnualFlows(sortedData);
//	Object[] returnArray = autoRegression.AR(p, annualData);
//	annualData = (double[][]) returnArray[0];
//	double[][] generatedData = (double[][]) returnArray[1];
//	autoRegression.graphAR1Double(annualData, generatedData);
        DoubleMath doubleMath = new DoubleMath();
        DoubleArray doubleArray = new DoubleArray();
        AutoRegression autoRegression = new AutoRegression();

        //Calculate total annual flows
        double[][] annualData = sumAnnualFlows(sortedData);


        //Convert the data to only the stoicastic portion to modeled using AR(1) or ARMA(1,1)
        double average = doubleMath.Average(doubleArray.getColumn(annualData, 1));
        double stDev = doubleMath.StandardDeviationSample(doubleArray.getColumn(annualData, 1));
        double[][] stocaisticData = autoRegression.StoicasticConversion(annualData, average, stDev, true);


        //Perform a transformation on the stoicastic data to change its distribution to a normal distribution
        double lambda = 1.0;
        if(lambdaString.equalsIgnoreCase("optimize")){
                lambda = autoRegression.BoxCox(stocaisticData);			
        }else{
                lambda = Double.parseDouble(lambdaString);
        }
        double[][] transformedData = autoRegression.BoxCox(lambda, stocaisticData, true);
//	double[][] transformedData = autoRegression.SalasExampleTransformation(stocaisticData, true);



        //Run the user specified auto-regressive model on the data
        double[][] fittedData = null;
        double[][] predictedData = null;
        String methodType = "";
        Object[] returnArray = autoRegression.AutoregressiveModel(mainFolder, action, phiValues, thetaValues, transformedData);
        if(action.equalsIgnoreCase("optimizeParameters")){
            //Find the parameters of the regression only and report these back
            String[] stringArray = (String[]) returnArray;


            //Perform regression with data and graph the resulting data
            Object[] dataArray = autoRegression.AutoregressiveModel(mainFolder, "useParameters", stringArray[0], stringArray[1], transformedData);

            //Keep the data from the regression and graph it
            methodType = (String) dataArray[0];
            transformedData = (double[][]) dataArray[1];
            fittedData = (double[][]) dataArray[2];
            predictedData = (double[][]) dataArray[3];

        }else if(action.equalsIgnoreCase("optimizeModel")){
            if(thetaValues.equalsIgnoreCase("")){
                //Calculate optimal AR(p) model and return the optimal phi values
                ArrayList<Double> aic = (ArrayList<Double>) returnArray[0];
                ArrayList<Double> bic = (ArrayList<Double>) returnArray[1];
                String phi = (String) returnArray[2];
                String theta = (String) returnArray[3];

                //Graph the AIC/BIC curve for the user
                graphAR_AIC_BIC(aic, bic);

                String[] optimalValues = {phi + "$$", theta};
                return optimalValues;

            }else{
                //Calculate optimal ARMA(p,q) model and return the optimal phi/theta values
                double[][] results = (double[][]) returnArray[0];
                String phi = (String) returnArray[1];
                String theta = (String) returnArray[2];

                //Graph the AIC/BIC curve for the user
                graphARMA_AIC_BIC("AIC", results, 3);
                graphARMA_AIC_BIC("BIC" , results, 4);

                String[] optimalValues = {phi + "$$", theta};
                return optimalValues;
            }

        }else{
            //Keep the data from the regression and graph it
            methodType = (String) returnArray[0];
            transformedData = (double[][]) returnArray[1];
            fittedData = (double[][]) returnArray[2];
            predictedData = (double[][]) returnArray[3];
        }

        //Un-transform the stoicastic historic and stoicastic generated data so as to graph real stream flow values
        transformedData = autoRegression.BoxCox(lambda, transformedData, false);
        fittedData = autoRegression.BoxCox(lambda, fittedData, false);
        predictedData = autoRegression.BoxCox(lambda, predictedData, false);
//	transformedData = autoRegression.SalasExampleTransformation(transformedData, false);
//	fittedData = autoRegression.SalasExampleTransformation(fittedData, false);
//	predictedData = autoRegression.SalasExampleTransformation(predictedData, false);


        //Convert back from the stoicastic component of the data to the entire original data
        double[][] originalData = autoRegression.StoicasticConversion(transformedData, average, stDev, false);
        fittedData = autoRegression.StoicasticConversion(fittedData, average, stDev, false);
        predictedData = autoRegression.StoicasticConversion(predictedData, average, stDev, false);


        //Calculate the droughts, their accumulated deficits, durations, and intensities
        DroughtProperties historicDroughtProps = new DroughtProperties(annualData, droughtLimit);
        DroughtProperties predictedDroughtProps = new DroughtProperties(predictedData, droughtLimit);



        //Determine the return periods for the droughts on record
        double[][] historicDroughts = recurrenceInterval(historicDroughtProps, droughtLimit, annualData);
        double[][] predictedDroughts = recurrenceInterval(predictedDroughtProps, droughtLimit, predictedData);


        //Graph the drought data
        graphTimeseries(annualData, droughtLimit);                  //graph1
        graphNegativeBarChart(annualData, droughtLimit);            //graph2
        graphFittedData(originalData, fittedData, methodType);	//graph3
        graphPredictedData(originalData, predictedData, methodType);//graph4
        graphReturnPeriod(historicDroughts, predictedDroughts);	//graph5




        //Populate the return array with the information gained from the drought analysis
        String[] droughtInfo = new String[historicDroughtProps.numberOfDroughts() + 1];
        droughtInfo[0] = "Deficit [acre-ft]\tLambda\tDuration [years]\tIntensity\tFinal Year";
        for(int i=0; i<historicDroughtProps.numberOfDroughts(); i++){
            droughtInfo[i+1] = String.valueOf(historicDroughtProps.getDeficit(i)) + "\t" + 
                            String.valueOf(historicDroughtProps.getDeficit(i)/droughtLimit) + "\t" + 
                            String.valueOf(historicDroughtProps.getDuration(i)) + "\t" + 
                            String.valueOf(historicDroughtProps.getIntensity(i)) + "\t" + 
                            String.valueOf(historicDroughtProps.getEndYear(i));
        }

        if(action.equalsIgnoreCase("optimizeParameters")){
            //Return only the parameters of the regression
            String[] stringArray = (String[]) returnArray;
            stringArray[0] = stringArray[0] + "$$";
            return stringArray;
        }else if(action.equalsIgnoreCase("updateParameters")){
            //Return only the parameters of the regression
            String[] stringArray = {phiValues + "$$", thetaValues};
            return stringArray;
        }

        return droughtInfo;
    }
    /**
     * Calculates the average recurrence interval for each drought on record and the lambda coefficient of each drought (deficit = lambda * droughtLimit)
     * @param currentDroughtProps  the DroughtProperties class containing the droughts to be analyzed
     * @param droughtLimit  the value of the water demand in acre-ft to calculate lambda from (lambda = deficit/water demand)
     * @param flowData  the double[][] array containing the annual flow data with column1 = years, column2 = flow values in acre-ft
     * @return  a double[][] array containing: column1 = lambda sets of eleven lambda values {0. 0.5, 0.75, 1.0, 2.0}, column2 = average recurrence interval (return period), column3 = length of drought (from 1 to 11
     */
    private double[][] recurrenceInterval(DroughtProperties currentDroughtProps, double droughtLimit, double[][] flowData){
            DoubleMath doubleMath = new DoubleMath();

            double[] lambda = {0.0, 0.5, 0.75, 1.0, 2.0, 3.0, 4.0};
            ArrayList<Double> droughtLambda = new ArrayList<Double>();
            ArrayList<Double> droughtRecurrence = new ArrayList<Double>();
            ArrayList<Double> droughtLength = new ArrayList<Double>();
            //Loop through lambda values
            for(int i=0; i<lambda.length; i++){
                    //Loop drought year length
                    for(int j=1; j<11; j++){
                            ArrayList<Double> recurrence = new ArrayList<Double>();
                            int endYear = (int) flowData[0][0];

                            //Loop through droughts
                            for(int k=0; k<currentDroughtProps.numberOfDroughts(); k++){
                                    //Compare the properties of the current drought to the provided
                                    if(currentDroughtProps.getDeficit(k) > lambda[i]*droughtLimit && currentDroughtProps.getDuration(k) == j){
                                            //Measure recurrence from start of drought to start of drought (Salas, 2005 pg 385)
                                            recurrence.add((double) (currentDroughtProps.getEndYear(k) - endYear));
                                            endYear = currentDroughtProps.getEndYear(k);
                                    }
                            }

                            //There were observed droughts larger than this one, calculate the average recurrence interval
                            if(recurrence.size() != 0){
                                    droughtLambda.add(lambda[i]);//lambda value
                                    droughtRecurrence.add(doubleMath.Average(recurrence));//average recurrence = return period
                                    droughtLength.add((double) j);//length of drought
                            }
                    }
            }

            //Return the drought properties in a single double[][]
            double[][] newDroughts = new double[droughtLambda.size()][3];
            for(int i=0; i<droughtLambda.size(); i++){
                    newDroughts[i][0] = droughtLambda.get(i);//lambda value
                    newDroughts[i][1] = droughtRecurrence.get(i);//average recurrence = return period
                    newDroughts[i][2] = droughtLength.get(i);//length of drought
            }


            return newDroughts;
    }
    /**
     * Calculated the annual flow values given the average daily data provided.  This sums the daily cfs values and 
     * converts it to acre-ft/day adding each day's worth of flow to the annual total
     * @param sortedData  a String[][] containing the flow data in the following format: column1 = dates (yyyy-mm-dd format), 
     * column2 = average daily streamflow values (cubic feet per second, cfs)
     * @return
     */
    private double[][] sumAnnualFlows(String[][] sortedData){
            //Check for size issues and return early
            if(sortedData.length<=1){
                    return new double[0][2];
            }


            //Get a unique list of years along with their accumulated flow values
            ArrayList<String> uniqueYears = new ArrayList<String>();
            ArrayList<Double> uniqueFlows = new ArrayList<Double>();
            ArrayList<Integer> uniqueDays = new ArrayList<Integer>();
            uniqueYears.add(sortedData[0][0].substring(0,4));
            uniqueFlows.add((Double.parseDouble(sortedData[0][1])*24*3600)/43560);
            uniqueDays.add(1);
            int ctr = 0;

            for(int i=1; i<sortedData.length; i++){
                    String previousYear = sortedData[i-1][0].substring(0,4);
                    String currentYear = sortedData[i][0].substring(0,4);

                    if(previousYear.equalsIgnoreCase(currentYear)){
                            //If the current year matches the previous year then add its flow value to the current total and continue
                            double currentFlowTotal = uniqueFlows.remove(ctr);
                            int currentNumberOfDays = uniqueDays.remove(ctr);

                            //convert daily cfs into acre-ft contributed by one day
                            //acre-ft =  cfs * 1day * 24hr/1day * 3600sec/1hr * 1acre-ft/43560 ft^3
                            double currentFlow = (Double.parseDouble(sortedData[i][1])*24*3600)/43560;
                            uniqueFlows.add(ctr, currentFlowTotal + currentFlow);
                            uniqueDays.add(ctr, currentNumberOfDays + 1);
                    }else{
                            //If the current year does not match the previous year, then we are in a new year so add the year to the 
                            //uniqueYear list and reset the annual flow total 
                            uniqueYears.add(currentYear);
                            uniqueDays.add(1);

                            //convert daily cfs into acre-ft for one day
                            //acre-ft =  cfs * 1day * 24hr/1day * 3600sec/1hr * 1acre-ft/43560 ft^3
                            double currentFlow = (Double.parseDouble(sortedData[i][1])*24*3600)/43560;
                            uniqueFlows.add(currentFlow);
                            ctr++;
                    }
            }

            System.out.println("Total Years: " + uniqueYears.size() + "\tTotal Flows: " + uniqueFlows.size());


//	//Compute average flow/day for each year in acre-ft/day and then annualize this value for a total flow per year value
//	//This average allows some years to have minimal data but still be used in the drought analysis
//	for(int i=0; i<uniqueYears.size(); i++){
//		int totalDays = uniqueDays.remove(i);
//		if(totalDays < 365){
//			//If less than a full year's worth of flows has been added, annualize each year's flow based on an average of the available data
//			double totalFlow = uniqueFlows.remove(i);
//			String year = uniqueYears.get(i);
//			System.out.println(year + "\tOld flow: " + totalFlow + "acre-ft/day\t" + totalDays + " days");
//			
//			double averageFlowDay = totalFlow/totalDays;
//			double annualFlow = averageFlowDay*365;
//			
//			uniqueFlows.add(i, annualFlow);
//			uniqueDays.add(i, 365);
//			System.out.println(year + "\tNew flow: " + annualFlow + "acre-ft/day\t365 days");
//			
//		}else{
//			//If it is a full year of data, then don't change anything
//			uniqueDays.add(i, totalDays);
//		}
//	}



            double[][] uniqueData = new double[uniqueYears.size()][2];
            for(int i=0; i<uniqueYears.size(); i++){
                    uniqueData[i][0] = Double.parseDouble(uniqueYears.get(i));//years
                    uniqueData[i][1] = uniqueFlows.get(i);//annual flow value
            }

            return uniqueData;
    }
    /**
     * Graph the timeseries data and save the resulting graph to the specified location
     * @param sortedData  the String[][] containing sorted data for the timeseries (column 1 = dates (yyyy-mm-dd) column 2 = values)
     * @param droughtLimit  the calculated drought limit (if supply < drought limit then that year is a drought)
     */
    private void graphTimeseries(double[][] sortedData,
                                double droughtLimit) throws IOException{
        Graphing graphing = new Graphing();

        //Round the long term average to fit the data better
        double tempRounding = droughtLimit*1000;
        tempRounding = Math.round(tempRounding)/1000;

        //Create TimeSeries graph
        TimeSeries series = new TimeSeries("Annual Flow Rate");
        TimeSeries averageSeries = new TimeSeries("Drought Limit: " + String.valueOf(tempRounding) + " [acre-ft]");
        String[][] graphData = new String[sortedData.length][3];
        for(int i=0; i < sortedData.length; i++) {
            int year_int = (int) sortedData[i][0];
            if(year_int >= 1900){//artificial limit for JFreeCharts' "Day" class
                Year year = new Year(year_int);
                series.add(year, sortedData[i][1]);
                averageSeries.add(year,droughtLimit);
            }
            graphData[i][0] = String.valueOf(year_int);
            graphData[i][1] = String.valueOf(sortedData[i][1]);
            graphData[i][2] = String.valueOf(droughtLimit);
        }
        
        //Save results for graphing with JSHighcharts
        DoubleArray doubleArray = new DoubleArray();
        doubleArray.writeTimeSeries(mainFolder, graphData, "Yearly", getTimeseriesOutput().getName(), false);

        //Graph data
        XYPlot plotTime = new XYPlot();
        plotTime = graphing.graphTimeData(plotTime, series, true, Color.blue, false, false, false, true, 0);
        plotTime = graphing.graphTimeData(plotTime, averageSeries, true, Color.black, false, false, true, true, 1);

        //Create Y axis
        ValueAxis rangeTime = new NumberAxis("Annual Flow Rate [acre-ft]");
        plotTime.setRangeAxis(0, rangeTime);

        //Create X Axes
        DateAxis domainTime = new DateAxis("Year");
        domainTime.setLowerMargin(0.05);
        domainTime.setUpperMargin(0.05);
        plotTime.setDomainAxis(0, domainTime);
        
        //Set extra plot preferences
        graphing.setTimeAxisPreferences(plotTime);

        //Create the chart with the plot and a legend
        String graphTitle = "Annual Time Series for " + database + " Station: " + stationID + "; " + stationName;
        JFreeChart chart = new JFreeChart(graphTitle, graphing.titleFont, plotTime, true);

        //Set legend Font
        LegendTitle legendTitle = chart.getLegend();
        legendTitle.setItemFont(graphing.masterFont);

        //Save resulting graph for use later
        try{
            String path = mainFolder + File.separator + getTimeseriesGraph();
            ChartUtilities.saveChartAsJPEG(new File(path), chart, 1280, 800);
            System.out.println("Graph located at:\t" + path);
        }catch(IOException e){
            System.err.println("A problem occurred while trying to creating the chart.");
        }
    }
    /**
     * Graph the bar chart of the timeseries data where the values graphed ar those in sortedData and save the resulting graph to the specified location
     * @param sortedData  the String[][] containing sorted data for the timeseries (column 1 = dates (yyyy-mm-dd) column 2 = values)
     * @param droughtLimit  the calculated drought limit (if supply < drought limit then that year is a drought)
     */
    private void graphNegativeBarChart(double[][] sortedData, 
                                       double droughtLimit) throws IOException{
        Graphing graphing = new Graphing();

        //Round the long term average to fit the data better
        double tempRounding = droughtLimit*1000;
        tempRounding = Math.round(tempRounding)/1000;

        //Create TimeSeries graph
        TimeTableXYDataset dataset = new TimeTableXYDataset();
        boolean deficitFirst = false;
        String[][] graphData = new String[sortedData.length][2];
        for(int i=0; i < sortedData.length; i++) {
            Year year = new Year((int) sortedData[i][0]);

            if(sortedData[i][1] < droughtLimit){
                dataset.add(year, sortedData[i][1] - droughtLimit, "Deficit");
                if(i == 0){
                    //If the first year is a deficit mark it as true to get the colors for the graph right
                    deficitFirst = true;
                }
            }else{
                dataset.add(year, sortedData[i][1] - droughtLimit, "Surplus");
            }
            graphData[i][0] = String.valueOf((int) sortedData[i][0]);
            graphData[i][1] = String.valueOf(sortedData[i][1] - droughtLimit);
        }
        
        //Save results for graphing with JSHighcharts
        DoubleArray doubleArray = new DoubleArray();
        doubleArray.writeXYseries(mainFolder, graphData, getColumnChartOutput().getName());

        //Define axis and properties
        NumberAxis numberaxis = new NumberAxis("Annual Deficit/Surplus of Drought Limit (" + tempRounding + " acre-ft)");
        DateAxis dateaxis = new DateAxis("Year");
        dateaxis.setTickMarkPosition(DateTickMarkPosition.MIDDLE);
        dateaxis.setLowerMargin(0.01D);
        dateaxis.setUpperMargin(0.01D);


        //Define renderer properties for stacked graph
        StackedXYBarRenderer stackedxybarrenderer = new StackedXYBarRenderer(0);
        stackedxybarrenderer.setDrawBarOutline(false);
        stackedxybarrenderer.setShadowVisible(false);
        if(deficitFirst){
            stackedxybarrenderer.setSeriesPaint(0, Color.red);
            stackedxybarrenderer.setSeriesPaint(1, Color.blue);
        }else{
            stackedxybarrenderer.setSeriesPaint(0, Color.blue);
            stackedxybarrenderer.setSeriesPaint(1, Color.red);
        }

        //Graph the dataset using the renderer and create a chart
        XYPlot plot = new XYPlot(dataset, dateaxis, numberaxis, stackedxybarrenderer);

        //Set extra plot preferences
        graphing.setTimeAxisPreferences(plot);

        //Create the chart with the plot
        String graphTitle = "Annual Drought/Surplus for " + database + " Station: " + stationID + "; " + stationName;
        JFreeChart chart = new JFreeChart(graphTitle, graphing.titleFont, plot, false);

        //Save resulting graph for use later
        try{
            String path = mainFolder + File.separator + getColumnChart();
            ChartUtilities.saveChartAsJPEG(new File(path), chart, 1280, 800);
            System.out.println("Graph located at:\t" + path);
        }catch(IOException e){
            System.err.println("A problem occurred while trying to creating the chart.");
        }
    }
    /**
     * Graph the orignalData against the generatedData on a timeseries graph.  Also graphs a scatter plot of the errors between the originalData and generatedData
     * @param originalData  double array of the original data.  Column1 = years, column2 = annual flow values
     * @param fittedData  double array of the fitted data during the same time period as the original data.  Column1 = years, column2 = annual flow values
     * @param methodType  the name of the method used to generate and project the data to be used in the legend of the graph
     */
    private void graphFittedData(double[][] originalData, 
                                 double[][] fittedData, 
                                 String methodType) throws IOException{
        Graphing graphing = new Graphing();

        //Convert graph x = original data, y = fitted data
        XYSeries series1 = new XYSeries("Regression of Data");
        String[][] graphData = new String[originalData.length][4];
        double max = 0;
        for(int i=0; i < originalData.length; i++) {
            series1.add(originalData[i][1], fittedData[i][1]);
            graphData[i][0] = "-1";
            graphData[i][1] = "-1";
            graphData[i][2] = String.valueOf(originalData[i][1]);
            graphData[i][3] = String.valueOf(fittedData[i][1]);

            if(Double.compare(originalData[i][1], max) > 0){
                max = originalData[i][1];
            }

            if(Double.compare(fittedData[i][1], max) > 0){
                max = fittedData[i][1];
            }
        }
        
        //Save info to graph 1-to-1 line of perfect simulation
        max = 1.02*max;
        if(graphData.length > 1){
            graphData[0][0] = "0";//x
            graphData[0][1] = "0";//y
            
            graphData[1][0] = String.valueOf(max);//x
            graphData[1][1] = String.valueOf(max);//y
        }
        
        //Save results for graphing with JSHighcharts
        DoubleArray doubleArray = new DoubleArray();
        doubleArray.writeXYseries(mainFolder, graphData, getFittedGraphOutput().getName());

        //Add first series to graph
        XYPlot plot = new XYPlot();
        
        XYDataset dataset1 = new XYSeriesCollection(series1);
        XYItemRenderer currentRenderer = new XYLineAndShapeRenderer(false, true);
        currentRenderer.setSeriesPaint(0, Color.black);
        plot.setDataset(0, dataset1);
        plot.setRenderer(0, currentRenderer);

        //Add line of slope 1 to graph
        XYSeries series2 = new XYSeries("One to one line, Don't show in legend");
        series2.add(0,0);
        series2.add(max, max);
        XYDataset dataset2 = new XYSeriesCollection(series2);
        XYItemRenderer currentRenderer2 = new XYLineAndShapeRenderer(true, false);
        currentRenderer2.setSeriesPaint(0, Color.lightGray);
        plot.setDataset(1, dataset2);
        plot.setRenderer(1, currentRenderer2);

        //Create the X Axis
        ValueAxis xAxis = new NumberAxis("Original Data [acre-ft/yr]");
        xAxis.setRange(0, max);
        xAxis.setVerticalTickLabels(true);
        plot.setDomainAxis(0, xAxis);

        //Create the Y Axis
        ValueAxis yAxis = new NumberAxis(methodType + " Data [acre-ft/yr]");
        yAxis.setRange(0, max);
        plot.setRangeAxis(0, yAxis);

        //Set extra plot preferences
        graphing.setAxisPreferences(plot);


        //Create the chart with the plot
        String graphTitle = "Data Correlation for " + database + " Station: " + stationID + "; " + stationName;
        JFreeChart chart = new JFreeChart(graphTitle, graphing.titleFont, plot, false);

        try{
            String path = mainFolder + File.separator + getFittedDataGraph();
            ChartUtilities.saveChartAsJPEG(new File(path), chart, 1280, 800);
            System.out.println("Graph located at:\t" + path);
        }catch(IOException e){
            System.err.println("A problem occurred while trying to creating the chart.");
        }
    }
    /**
     * Graph the orignalData against the future predictedData on a timeseries graph.
     * @param originalData  double array of the original data.  Column1 = years, column2 = annual flow values
     * @param predictedData  double array of the future predicted data.  Column1 = years, column2 = annual flow values
     * @param methodType  the name of the method used to generate and project the data to be used in the legend of the graph
     */
    private void graphPredictedData(double[][] originalData, 
                                    double[][] predictedData, 
                                    String methodType) throws IOException{
        Graphing graphing = new Graphing();

        //Convert series into Dates
        XYSeries series1 = new XYSeries("Original Data");
        XYSeries series2 = new XYSeries(methodType + " warm up period");
        XYSeries series3 = new XYSeries(methodType + " Predicted Data");
        String[][] graphData = new String[originalData.length][3];
        for(int i=0; i < originalData.length; i++) {
            series1.add(originalData[i][0], originalData[i][1]);
            
            //Save results for graphing with JSHighcharts
            graphData[i][0] = String.valueOf(originalData[i][0]);
            graphData[i][1] = String.valueOf(originalData[i][1]);
            graphData[i][2] = String.valueOf(predictedData[i][1]);
        }
        for(int i=0; i < predictedData.length; i++) {
            if(i<100){
                //Keep the warm up series
                series2.add(predictedData[i][0], predictedData[i][1]);
            }else{
                //Keep the final dataseries
                series3.add(predictedData[i][0], predictedData[i][1]);
            }
        }
        
        //Save results for graphing with JSHighcharts
        DoubleArray doubleArray = new DoubleArray();
        doubleArray.writeTimeSeries(mainFolder, graphData, "Yearly", getProjectedGraphOutput().getName(), false);

        //Add first series to graph
        XYPlot plot = new XYPlot();
        XYDataset dataset1 = new XYSeriesCollection(series1);
        XYItemRenderer currentRenderer = new XYLineAndShapeRenderer(true, false);
        currentRenderer.setSeriesPaint(0, Color.black);
        plot.setDataset(0, dataset1);
        plot.setRenderer(0, currentRenderer);

        //Add second series to graph
        XYDataset dataset2 = new XYSeriesCollection(series2);
        XYItemRenderer currentRenderer2 = new XYLineAndShapeRenderer(true, false);
        currentRenderer2.setSeriesPaint(0, Color.gray);
        plot.setDataset(1, dataset2);
        plot.setRenderer(1, currentRenderer2);

        //Add third series to graph
        XYDataset dataset3 = new XYSeriesCollection(series3);
        XYItemRenderer currentRenderer3 = new XYLineAndShapeRenderer(true, false);
        currentRenderer3.setSeriesPaint(0, Color.red);
        plot.setDataset(2, dataset3);
        plot.setRenderer(2, currentRenderer3);

        //Create the X Axis
        NumberAxis xAxis = new NumberAxis("Year");
        xAxis.setRange(originalData[0][0], predictedData[predictedData.length - 1][0]/35);
        DecimalFormat formatter = new DecimalFormat("#0");
        xAxis.setNumberFormatOverride(formatter);
        plot.setDomainAxis(0, xAxis);

        //Create the Y Axis
        ValueAxis yAxis = new NumberAxis("Annual Discharge [acre-ft/yr]");
        plot.setRangeAxis(0, yAxis);

        //Set extra plot preferences
        graphing.setAxisPreferences(plot);

        //Create the chart with the plot
        String graphTitle = "Projected Data for " + database + " Station: " + stationID + "; " + stationName;
        JFreeChart chart = new JFreeChart(graphTitle, graphing.titleFont, plot, true);

        //Set legend Font
        LegendTitle legendTitle = chart.getLegend();
        legendTitle.setItemFont(graphing.masterFont);

        try{
            String path = mainFolder + File.separator + getProjectedDataGraph();
            ChartUtilities.saveChartAsJPEG(new File(path), chart, 1280, 800);
            System.out.println("Graph located at:\t" + path);
        }catch(IOException e){
            System.err.println("A problem occurred while trying to creating the chart.");
        }
    }
    /**
     * Graphs the drought length verses the return period for each lambda (drought deficit = lambda * droughtLimit) value on a scatter plot
     * @param originalData  double array of the original data.  Column1 = years, column2 = annual flow values
     * @param predictedData  double array of the future predicted data.  Column1 = years, column2 = annual flow values
     */
    private void graphReturnPeriod(double[][] originalData, 
                                   double[][] predictedData) throws IOException{
        DoubleMath doubleMath = new DoubleMath();
        DoubleArray doubleArray = new DoubleArray();
        Graphing graphing = new Graphing();

        //Create xy scatter plot
        XYPlot plot = new XYPlot();

        //Add fist series to graph
        double[] lambda = {0.0};//, 0.5, 0.75, 1.0};
        Color[] colorMatrix2 = {Color.black, Color.darkGray, Color.gray, Color.lightGray};
        ArrayList<ArrayList<Double>> graphDataX = new ArrayList<ArrayList<Double>>();
        ArrayList<ArrayList<Double>> graphDataY = new ArrayList<ArrayList<Double>>();
        int ctr = 0;
        for(int i=0; i<lambda.length; i++){
            //Create and populate the lambda series
            XYSeries series2 = new XYSeries("Original Data: Lambda = " + lambda[i]);
            ArrayList<Double> temp_originalX = new ArrayList<Double>();
            ArrayList<Double> temp_originalY = new ArrayList<Double>();
            for(int j=0; j < originalData.length; j++){
                if(Double.compare(originalData[j][0], lambda[i]) == 0){
                    series2.add(originalData[j][2], originalData[j][1]);
                    temp_originalX.add(originalData[j][2]);
                    temp_originalY.add(originalData[j][1]);
                }
            }
            graphDataX.add(temp_originalX);
            graphDataY.add(temp_originalY);
            //Add the new series to the dataset
            XYDataset dataset2 = new XYSeriesCollection(series2);
            XYItemRenderer currentRenderer2 = new XYLineAndShapeRenderer(true, true);
            currentRenderer2.setSeriesPaint(0, colorMatrix2[i]);
            plot.setDataset(ctr, dataset2);
            plot.setRenderer(ctr, currentRenderer2);
            ctr++;
        }


        //Add second series to graph
        double[] lambda2 = {0.0, 0.5, 0.75, 1.0, 2.0, 3.0, 4.0};
        Color[] colorMatrix1 = {new Color(105, 0, 255), Color.blue, Color.cyan, Color.green, new Color(255, 135, 0), new Color(255, 0, 50), new Color(255, 0, 255, 100)};
        //Colors in the color matrix above are: purple, blue, cyan, green, gold, red, pink
        for(int i=0; i<lambda2.length; i++){
            //Create and populate the lambda series
            XYSeries series2 = new XYSeries("Simmulated Data: Lambda = " + lambda2[i]);
            ArrayList<Double> temp_predictedX = new ArrayList<Double>();
            ArrayList<Double> temp_predictedY = new ArrayList<Double>();
            for(int j=0; j < predictedData.length; j++){
                if(Double.compare(predictedData[j][0], lambda2[i]) == 0){
                    series2.add(predictedData[j][2], predictedData[j][1]);
                    temp_predictedX.add(predictedData[j][2]);
                    temp_predictedY.add(predictedData[j][1]);
                }
            }
            graphDataX.add(temp_predictedX);
            graphDataY.add(temp_predictedY);
            //Add the new series to the dataset
            XYDataset dataset2 = new XYSeriesCollection(series2);
            XYItemRenderer currentRenderer2 = new XYLineAndShapeRenderer(true, true);
            currentRenderer2.setSeriesPaint(0, colorMatrix1[i]);
            plot.setDataset(ctr, dataset2);
            plot.setRenderer(ctr, currentRenderer2);
            ctr++;
        }
        
        //Save results for graphing with JSHighcharts
        int maxSize = 0;
        for(int i=0; i<graphDataX.size(); i++){
            int size = graphDataX.get(i).size();
            if(size > maxSize){
                maxSize = size;
            }
        }
        String[][] graphData = new String[maxSize][16];//16 = 8 * 2 columns for both x and y points
        ctr = 0;
        for(int j=0; j<8; j++){
            ArrayList<Double> currentX = graphDataX.get(j);
            ArrayList<Double> currentY = graphDataY.get(j);
            for(int i=0; i<maxSize; i++){
                try{
                    graphData[i][ctr] = String.valueOf(currentX.get(i));
                    graphData[i][ctr + 1] = String.valueOf(currentY.get(i));
                }catch(IndexOutOfBoundsException e){
                    graphData[i][ctr] = "-1";
                    graphData[i][ctr + 1] = "-1";
                }
            }
            ctr++;//Increase by 1 to move from x column to y column
            ctr++;//Increase by 1 to move from y column to new x column
        }
        doubleArray.writeXYseries(mainFolder, graphData, getRecurrenceGraphOutput().getName());
        

        //Create the X Axis
        ValueAxis xAxis = new NumberAxis("Drought Length [years]");
        xAxis.setRange(0, 11);
        TickUnits unit1 = new TickUnits();
        unit1.add(new NumberTickUnit(1, NumberFormat.getNumberInstance()));
        xAxis.setStandardTickUnits(unit1);
        plot.setDomainAxis(0, xAxis);

        //Create the Y Axis
        LogarithmicAxis yAxis = new LogarithmicAxis("Recurrence Interval [years]");
        yAxis.setAllowNegativesFlag(true);
        try{
            double maxValue = Math.log10(doubleMath.max(doubleArray.getColumn(predictedData, 1)));
            if(Double.compare(maxValue, Double.NEGATIVE_INFINITY) != 0 &&
                    Double.compare(maxValue, Double.POSITIVE_INFINITY) != 0 ){
                maxValue = Math.ceil(maxValue);
                yAxis.setRange(1, Math.pow(10, maxValue));
            }
        }catch(ArrayIndexOutOfBoundsException e){
            System.err.print("No predicted data lambda values\n");
        }
        plot.setRangeAxis(0, yAxis);

        //Add equation explaining the term lambda
        XYTextAnnotation equation = new XYTextAnnotation(" Lambda = Drought Deficit / Drought Limit  ", 9, yAxis.getUpperBound()*0.5);
        equation.setFont(graphing.masterFont);
        equation.setBackgroundPaint(Color.white);
        equation.setOutlinePaint(Color.black);
        equation.setOutlineVisible(true);
        plot.addAnnotation(equation);

        //Set extra plot preferences
        graphing.setAxisPreferences(plot);

        //Create the chart with the plot
        String graphTitle = "Drought Recurrence Intervals for " + database + " Station: " + stationID + "; " + stationName;
        JFreeChart chart = new JFreeChart(graphTitle, graphing.titleFont, plot, true);

        //Set legend Font
        LegendTitle legendTitle = chart.getLegend();
        legendTitle.setItemFont(graphing.masterFont);

        try{
            String path = mainFolder + File.separator + getDroughtRecurrenceGraph();
            ChartUtilities.saveChartAsJPEG(new File(path), chart, 1280, 800);
            System.out.println("Graph located at:\t" + path);
        }catch(IOException e){
            System.err.println("A problem occurred while trying to creating the chart.");
        }
    }
    /**
     * Graph the AR(p) AIC and BIC verses the p order of the AR(p) model
     * @param aic  an array list of aic values corresponding to p=1, 2, etc.
     * @param bic  an array list of bic values corresponding to p=1, 2, etc.
     */
    private void graphAR_AIC_BIC(ArrayList<Double> aic, ArrayList<Double> bic){
        DoubleMath doubleMath = new DoubleMath();
        Graphing graphing = new Graphing();

        //Graph AIC and BIC data vs. the p parameter of the AR(p) model
        XYSeries series1 = new XYSeries("AIC");
        XYSeries series2 = new XYSeries("BIC");
        for(int i=0; i < aic.size(); i++) {
            series1.add(i+1, aic.get(i));
            series2.add(i+1, bic.get(i));
        }
        XYPlot plot = new XYPlot();

        //Add first series to graph
        XYDataset dataset1 = new XYSeriesCollection(series1);
        XYItemRenderer currentRenderer = new XYLineAndShapeRenderer(true, true);
        currentRenderer.setSeriesPaint(0, Color.black);
        plot.setDataset(0, dataset1);
        plot.setRenderer(0, currentRenderer);

        //Add second series to graph
        XYDataset dataset2 = new XYSeriesCollection(series2);
        XYItemRenderer currentRenderer2 = new XYLineAndShapeRenderer(true, true);
        currentRenderer2.setSeriesPaint(0, Color.cyan);
        plot.setDataset(1, dataset2);
        plot.setRenderer(1, currentRenderer2);

        //Create the X Axis
        ValueAxis xAxis = new NumberAxis("Number of Model Parameters");
        TickUnits unit1 = new TickUnits();
        unit1.add(new NumberTickUnit(1, NumberFormat.getNumberInstance()));
        xAxis.setStandardTickUnits(unit1);
        xAxis.setRange(0.01, aic.size() + 0.99);
        plot.setDomainAxis(0, xAxis);

        //Create the Y Axis
        ValueAxis yAxis = new NumberAxis("AIC/BIC Value");
        double max = doubleMath.max(aic);
        if(max < doubleMath.max(bic)){
                max = doubleMath.max(bic);
        }
        double min = doubleMath.min(aic);
        if(min > doubleMath.min(bic)){
                min = doubleMath.min(bic);
        }
        yAxis.setRange(0.98*min, 1.02*max);
        plot.setRangeAxis(0, yAxis);

        //Create the legend inside of the graph
        LegendTitle lt = new LegendTitle(plot);
        lt.setItemFont(new Font("Dialog", Font.PLAIN, 12));
        lt.setBackgroundPaint(new Color(255, 255, 255, 255));
        lt.setFrame(new BlockBorder(Color.black));
        lt.setPosition(RectangleEdge.RIGHT);
        lt.setPosition(RectangleEdge.LEFT);
        XYTitleAnnotation ta = new XYTitleAnnotation(1, 1, lt, RectangleAnchor.TOP_RIGHT);
        plot.addAnnotation(ta);

        //Set extra plot preferences
        graphing.setAxisPreferences(plot);

        //Create the chart with the plot
        JFreeChart chart = new JFreeChart("AR(p) Parameter Optimization", graphing.titleFont, plot, false);

        try{
            String[] graphNames = get_optimizeModel_output();
            String path = mainFolder + File.separator + graphNames[0];
            ChartUtilities.saveChartAsJPEG(new File(path), chart, 1280, 800);
            System.out.println("Graph located at:\t" + path);
        }catch(IOException e){
            System.err.println("A problem occurred while trying to creating the chart.");
        }
    }
    /**
     * Graph the ARMA(p,q) AIC or BIC values verses the total number of ARMA model parameters (p+q)
     * @param method  the name of the method either AIC or BIC
     * @param results  a double[][] containing the points to be plotted
     * @param graphNumber  the index of the graph either 3 (AIC) or 4 (BIC)
     */
    private void graphARMA_AIC_BIC(String method, double[][] results, int graphNumber){
        DoubleMath doubleMath = new DoubleMath();
        DoubleArray doubleArray = new DoubleArray();
        Graphing graphing = new Graphing();

        //Add each point of the aic/bic to the graph
        XYPlot plot = new XYPlot();
        XYSeries series1 = new XYSeries(method + " Points");
        for(int i=0; i < results.length; i++){
            series1.add(results[i][0] + results[i][1], results[i][graphNumber - 1]);
        }

        //Determine what the optimal values of AIC/BIC are for each p+q combination
        XYSeries series2 = new XYSeries(method + " Optimal Front");
        ArrayList<Double> aicBIC = new ArrayList<Double>();
        ArrayList<Double> pValues = new ArrayList<Double>();
        ArrayList<Double> qValues = new ArrayList<Double>();
        for(int i=2; i<=8; i++){
            //For each "value"=p+q find all of the p+q combinations which add to this 
            //"value" and retrieve their corresponding AIC/BIC value
            ArrayList<Double> reArrangedData = new ArrayList<Double>();
            for(int j=0; j<results.length; j++){
                if(results[j][0] + results[j][1] == i){
                    reArrangedData.add(results[j][graphNumber - 1]);
                }
            }

            double currentMinimum = doubleMath.min(reArrangedData);
            series2.add(i, currentMinimum);
            for(int j=0; j<results.length; j++){
                if(Double.compare(currentMinimum,results[j][graphNumber - 1]) == 0){
                    aicBIC.add(results[j][graphNumber - 1]);
                    pValues.add(results[j][0]);
                    qValues.add(results[j][1]);
                }
            }
        }

        //Add first series to graph
        XYDataset dataset1 = new XYSeriesCollection(series1);
        XYItemRenderer currentRenderer = new XYLineAndShapeRenderer(false, true);
        currentRenderer.setSeriesPaint(0, Color.black);
        plot.setDataset(0, dataset1);
        plot.setRenderer(0, currentRenderer);

        //Add second series to graph
        XYDataset dataset2 = new XYSeriesCollection(series2);
        XYItemRenderer currentRenderer2 = new XYLineAndShapeRenderer(true, false);
        currentRenderer2.setSeriesStroke(0, 
            new BasicStroke(
                1.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND,
                1.0f, new float[] {6.0f, 6.0f}, 0.0f
            ));
        currentRenderer2.setSeriesPaint(0, Color.blue);
        plot.setDataset(1, dataset2);
        plot.setRenderer(1, currentRenderer2);

        //Add highlight to best AIC/BIC value (lowest)
        XYSeries series3 = new XYSeries("Error");
        double minAICbic = doubleMath.min(aicBIC);
        String p = "", q = "";
        for(int i=0; i<aicBIC.size(); i++){
            if(Double.compare(aicBIC.get(i), minAICbic) == 0){
                p = String.valueOf(pValues.get(i));
                q = String.valueOf(qValues.get(i));
                series3.add(pValues.get(i) + qValues.get(i), minAICbic);
                break;
            }
        }

        //Create label for optimal ARMA model
        p = p.substring(0,p.indexOf("."));
        q = q.substring(0,q.indexOf("."));
        XYDataset dataset3 = new XYSeriesCollection(series3);
        XYItemRenderer currentRenderer3 = new XYLineAndShapeRenderer(false, true);
        currentRenderer3.setSeriesPaint(0, Color.black);
        currentRenderer3.setSeriesVisibleInLegend(0, false);

                //Add an outline around the point
                CircleDrawer cd = new CircleDrawer(Color.black, new BasicStroke(1.0f), null);
                double x =  series3.getX(0).doubleValue();
                double y = series3.getY(0).doubleValue();
                XYAnnotation bestBid = new XYDrawableAnnotation(x, y, 11, 11, cd);
                plot.addAnnotation(bestBid);

                //Add an arrow pointing to the point with the year of that flood
                final XYPointerAnnotation pointer = new XYPointerAnnotation("Optimal ARMA Model: p= " + p + " q= " + q, x, y, Math.PI - 0.3);
                pointer.setBaseRadius(30.0);
                pointer.setTipRadius(10.0);
                pointer.setFont(graphing.masterFont);
                pointer.setPaint(Color.black);
                pointer.setBackgroundPaint(Color.white);
                pointer.setOutlinePaint(Color.black);
                pointer.setTextAnchor(TextAnchor.CENTER_RIGHT);
                plot.addAnnotation(pointer);

        //Put the line data, renderer, and axis into plot
        plot.setDataset(3, dataset3);
        plot.setRenderer(3, currentRenderer3);

        //Create the X Axis
        ValueAxis xAxis = new NumberAxis("Number of Model Parameters");
        TickUnits unit1 = new TickUnits();
        unit1.add(new NumberTickUnit(1, NumberFormat.getNumberInstance()));
        xAxis.setStandardTickUnits(unit1);
        xAxis.setRange(0.01, 8.99);
        plot.setDomainAxis(0, xAxis);

        //Create the Y Axis
        ValueAxis yAxis = new NumberAxis(method + " Value");
        double max = doubleMath.max(doubleArray.getColumn(results, graphNumber - 1));
        double min = doubleMath.min(doubleArray.getColumn(results, graphNumber - 1));
        yAxis.setRange(0.8*min, 1.02*max);
        plot.setRangeAxis(0, yAxis);

        //Set extra plot preferences
        graphing.setAxisPreferences(plot);

        //Create the chart with the plot
        JFreeChart chart = new JFreeChart("ARMA(p, q) " + method + " Parameter Optimization", graphing.titleFont, plot, false);

        try{
            String[] graphNames = get_optimizeModel_output();
            String path = mainFolder + File.separator + graphNames[graphNumber-3];
            ChartUtilities.saveChartAsJPEG(new File(path), chart, 1280, 800);
            System.out.println("Graph located at:\t" + path);
        }catch(IOException e){
            System.err.println("A problem occurred while trying to creating the chart.");
        }
    }
    /**
     * Writes out the dynamically created summary table to be displayed to the user along with the flood graph
     * @param dynamicSummary  string[] array to be written as each line of the text file
     * @throws IOException
     */
    public void writeSummary(String[] dynamicSummary) throws IOException{
        //Intialize the file writer
        String path = mainFolder + File.separator + getResult().getName();
        FileWriter writer =  new FileWriter(path, false);
        PrintWriter print_line = new PrintWriter(writer);

        //Write the summary to the file
        for(int i=0; i < dynamicSummary.length; i++) {
            print_line.printf("%s" + "\r\n", 	dynamicSummary[i]);
        }
        print_line.close();
        writer.close();
        System.out.println("Text File located at:\t" + path);
    }
    /**
     * Writes out the error message, if any, for finding the file and then exits the program
     * @param error  string array to be written as each line of an error message
     * @throws IOException
     */
    public void writeError(ArrayList<String> error) throws IOException{
        //Output data to text file
        String errorContents = error.get(0);
        for(int i=1; i<error.size(); i++){
            errorContents = errorContents + "\n" + error.get(i);
        }
        throw new IOException("Error encountered. Please see the following message for details: \n" + errorContents);
    }
    public void run() throws IOException, InterruptedException, Exception {
        //If no date input, make it the maximum of available data
        if(beginDate == null || beginDate.equalsIgnoreCase("")){
            beginDate = "1850-01-01";
        }
        if(endDate == null || endDate.equalsIgnoreCase("")){
            // Pull current date for upper limit of data search
            DateFormat desiredDateFormat = new SimpleDateFormat("yyyy-MM-dd");
            Date currentDate = new Date();
            endDate = desiredDateFormat.format(currentDate);
        }

        //Check if any flow data exists
        Data data = new Data();
        String[][] sortableData = data.extractFlowData(mainFolder, database, organizationName, stationID, beginDate, endDate, userData);
        
        //If the user wants the datasets (public and user) merged then retrieve the second dataset (user)
        String[][] sortableData_user = new String[0][0];
        if(mergeDatasets){
            User_Data user_Data = new User_Data();
            sortableData_user = user_Data.readUserFile(userData, "flow", beginDate, endDate);
        }
        
        //Sort the Data by date to remove duplicate date entries
        DoubleArray doubleArray = new DoubleArray();
        String[][] sortedData = doubleArray.removeDuplicateDates(sortableData);
        String[][] sortedData_user = doubleArray.removeDuplicateDates(sortableData_user);
        
        //Merge the two datasets (if user data is empty nothing will be merged)
        String[][] sortedData_combined = doubleArray.mergeData(sortedData, sortedData_user, mergeMethod);
        if(sortedData_combined.length == 0){
            ArrayList<String> errorMessage = new ArrayList<String>();
            if(sortedData.length == 0){
                errorMessage.add("There is no available flow data in the " + database + " database for station '" + stationID + "' and the specified date range.");
                if(database.equalsIgnoreCase("CDWR")){
                    errorMessage.add("The CDWR database is sensitive to the begin date used, try specifying a later begin date");
                }
            }
            if(sortedData_user.length == 0){
                errorMessage.add("There is no available uploaded data for station '" + stationID + "' and the specified date range");
            }
            writeError(errorMessage);
        }
        
        //Perform drought analysis
        String[] droughtInfo = null;

        if(Double.compare(droughtLimit, 0) < 0){
            //If no drought limit is provided (aka -1 is provided) then calculate the drought limit = average(annual flows)
            droughtInfo = generalDroughtAnalysis(sortedData_combined);
        }else{
            //If a drought limit is provided (aka greater than or equal to zero) then use this as the drought limit
            droughtInfo = generalDroughtAnalysis(sortedData_combined, droughtLimit);			
        }

        //Get today's date for the source reference
        Date currentDate = new Date();
        SimpleDateFormat sourceDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
        String today = sourceDateFormat.format(currentDate);
        if(database.equalsIgnoreCase("USGS")){
            this.dataSource = "Stream flow data retrieved from the U.S. Geological Survey, National Water Information System: Web Interface. http://waterdata.usgs.gov/nwis, accessed: " + today;
        }else if(database.equalsIgnoreCase("STORET")){
            this.dataSource = "Stream flow data retrieved from the U.S. Environmental Protection Agency, STORET. http://www.epa.gov/storet/index.html accessed: " + today;
        }else if(database.equalsIgnoreCase("CDWR")){
            this.dataSource = "Stream flow data retrieved from the Colorado Division of Water Resources, CDWR. http://www.dwr.state.co.us accessed: " + today;
        }
        this.len = String.valueOf(sortedData_combined.length);
        this.start = String.valueOf(sortedData_combined[0][0]);
        this.end = String.valueOf(sortedData_combined[sortedData_combined.length - 1][0]);
        
        writeSummary(droughtInfo);
    }
    public static void main(String[] args) throws IOException, InterruptedException, Exception {
        guiDrought_Model drought_Model = new guiDrought_Model();
        
        //Set Inputs
//        assert args.length > 0;
//        drought_Model.setMainFolder(args[0]);   //The output location of the graph
//        drought_Model.setFileName(args[1]);     //The name of the output graph and summary text file
//        drought_Model.setOrganizationName(args[2]); //Supervising organization of the station (only used for STORET stations)
//        drought_Model.setStationID(args[3]);    //The station ID used to retrieve the station's flow data
//        drought_Model.setStationName(args[4]);  //The station Name for the current station, used to title the graph
//        drought_Model.setBeginDate(args[5]);    //Begin date of analysis
//        drought_Model.setEndDate(args[6]);      //End date of analysis
//        drought_Model.setLambdaString(args[7]); //the value of lambda for the Box-Cox transformation or the word "optimize" to optimize this value
//        drought_Model.setAction(args[8]);       //The action to be taken for the drought analysis, either "all" for a full model, "optimizeParameters" to return only the phi and theta values for the AR/ARMA model fit, or useParameters to use the provided phi and theta parameters
//        drought_Model.setPhiValues(args[9]);    //the number of phi values desired for the AR/ARMA model or a tab-delimited string of the previously calculated phi values
//        drought_Model.setThetaValues(args[10]); //the number of theta values desired for the AR/ARMA model or a tab-delimited string of the previously calculated theta values (blank is acceptable and results in an AR(p) model instead of an ARMA(p,q) model)
//        drought_Model.setDroughtLimit(Double.parseDouble(args[11]));    //If not <0 then this represents the user specified drought limit so use it instead of calculating the long term average as the drought limit
        
        //Run Model
        drought_Model.run();
    }
}