guiDrought_Model.java [src/java/m/cfa/drought] Revision:   Date:
package m.cfa.drought;

import WaterData.WaterData;
import WaterData.WaterDataInterface;
import m.cfa.CircleDrawer;
import m.cfa.DoubleArray;
import m.cfa.DoubleMath;
import m.cfa.Graphing;
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.ParseException;
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: 9-April-2019
* @author Tyler Wible
* @since 10-July-2012
*/
public class guiDrought_Model {
    //Inputs
    String directory = "E:/Projects/TylerWible_repos/NetBeans/data/CFA/Drought";
    String database = "USGS";//"CDWR";//"STORET";//"CDSN";//"UserData";//
    String orgId  = "n/a";//"n/a";//"21COL001";//"CITYFTCO_WQX";//"n/a";//
    String stationId = "06752000";//"CLAGRECO";//"000028";//"1EFF";//"n/a";//
    String stationName = "CACHE LA POUDRE RIV AT MO OF CN, NR FT COLLINS, CO";//"Cache La Poudre Near Greeley";//"BIG THOMPSON R NEAR MOUTH";//"n/a";//"n/a";//
    String startDate = "";//"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;
    boolean mergeDatasets = false;//true;//
    String mergeMethod = "user";//"public";//"max";//"average";//"min";//
    String userData = "";//"Date\tFlow\n1997-04-29\t8.3\n1998-05-09\t60.2\n1999-05-29\t20.1";
    
    //Outputs
    String len = "-1";
    String start = "?";
    String end = "?";
    String dataSource = "?";
    
    //Gets
    public File getResult(){ return new File(directory, "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 getOptimizedModel_ARgraph(){ return "optimize_drought_graph1.jpg"; }
    public String[] getOptimizedModel_ARMAgraphs(){
        String[] graphs = {"optimize_drought_graph3.jpg", "optimize_drought_graph4.jpg"};
        return graphs;
    }
    public File getTimeseriesOutput(){ return new File(directory, "drought1_timeseries.out"); }//for use with JSHighCharts
    public File getColumnChartOutput(){ return new File(directory, "drought2_column.outt"); }//for use with JSHighCharts
    public File getFittedGraphOutput(){ return new File(directory, "drought3_fitted.out"); }//for use with JSHighCharts
    public File getProjectedGraphOutput(){ return new File(directory, "drought4_projected.out"); }//for use with JSHighCharts
    public File getRecurrenceGraphOutput(){ return new File(directory, "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 setDirectory(String directory_str){ directory = directory_str; }
    public void setDatabase(String database_str){ database = database_str; }
    public void setOrganizationID(String orgId_str){ orgId = orgId_str; }
    public void setStationId(String stationId_str){ stationId = stationId_str; }
    public void setStationName(String stationName_str){ stationName = stationName_str; }
    public void setStartDate(String startDate_str){ startDate = startDate_str; }
    public void setEndDate(String endDate_str){ endDate = endDate_str; }
    public void setLambdaString(String lambdaString_str){ lambdaString = lambdaString_str; }
    public void setAction(String action_str){ action = action_str; }
    public void setPhiValues(String phiValues_str){ phiValues = phiValues_str; }
    public void setThetaValues(String thetaValues_str){ thetaValues = thetaValues_str; }
    public void setDroughtLimit(double droughtLimit_dbl){ droughtLimit = droughtLimit_dbl; }
    public void setMergeDatasets(boolean mergeDatasets_TF){ mergeDatasets = mergeDatasets_TF; }
    public void setMergeMethod(String mergeMethod_str){ mergeMethod = mergeMethod_str; }
    public void setUserData(String userData_str){ userData = userData_str; }
    /**
     * 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 
     * @throws java.text.ParseException 
     */
    public String[] generalDroughtAnalysis(String[][] sortedData) throws IOException, ParseException{
        //Calculate total annual flows
        double[][] annualData = sumAnnualFlows(sortedData);

        //Get the flow values and calculate the long-term (total) average of them
        droughtLimit = DoubleMath.meanArithmetic(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
     * @throws ParseException 
     */
    public String[] generalDroughtAnalysis(String[][] sortedData,
                                           double droughtLimit) throws IOException, ParseException{
//	//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);
        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.meanArithmetic(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(directory, 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(directory, "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){
        double[] lambda = {0.0, 0.5, 0.75, 1.0, 2.0, 3.0, 4.0};
        ArrayList<Double> droughtLambda = new ArrayList<>();
        ArrayList<Double> droughtRecurrence = new ArrayList<>();
        ArrayList<Double> droughtLength = new ArrayList<>();
        //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<>();
                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.isEmpty()){
                    droughtLambda.add(lambda[i]);//lambda value
                    droughtRecurrence.add(DoubleMath.meanArithmetic(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<>();
        ArrayList<Double> uniqueFlows = new ArrayList<>();
        ArrayList<Integer> uniqueDays = new ArrayList<>();
        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)
     * @throws IOException
     * @throws ParseException 
     */
    private void graphTimeseries(double[][] sortedData, double droughtLimit) throws IOException, ParseException{
        //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.writeTimeSeries(directory, graphData, "Yearly", getTimeseriesOutput().getName(), false);

        //Graph data
        XYPlot plotTime = new XYPlot();
        plotTime = Graphing.addTimeseriesData(plotTime, series, true, Color.blue, false, false, false, true, 0);
        plotTime = Graphing.addTimeseriesData(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
        plotTime = 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 = directory + 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)
     * @throws IOException 
     */
    private void graphNegativeBarChart(double[][] sortedData, double droughtLimit) throws IOException{
        //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.writeXYseries(directory, 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
        plot = 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 = directory + 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
     * @throws IOException 
     */
    private void graphFittedData(double[][] originalData, 
                                 double[][] fittedData, 
                                 String methodType) throws IOException{
        //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.writeXYseries(directory, 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
        plot = 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 = directory + 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
     * @throws IOException
     * @throws ParseException 
     */
    private void graphPredictedData(double[][] originalData, 
                                    double[][] predictedData, 
                                    String methodType) throws IOException, ParseException{
        //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.writeTimeSeries(directory, 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
        plot = 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 = directory + 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
     * @throws IOException 
     */
    private void graphReturnPeriod(double[][] originalData, 
                                   double[][] predictedData) throws IOException{
        //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<ArrayList<Double>> graphDataY = new ArrayList<>();
        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<>();
            ArrayList<Double> temp_originalY = new ArrayList<>();
            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<>();
            ArrayList<Double> temp_predictedY = new ArrayList<>();
            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(directory, 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
        plot = 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 = directory + 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){
        //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 path = directory + File.separator + getOptimizedModel_ARgraph();
            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){
        //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<>();
        ArrayList<Double> pValues = new ArrayList<>();
        ArrayList<Double> qValues = new ArrayList<>();
        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<>();
            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
        plot = 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 path = directory + File.separator + getOptimizedModel_ARMAgraphs()[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 = directory + 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);
    }
    /**
     * Main run method for drought model
     * @param storetResourceFile  python driver.py resource file for storet data extraction
     * @throws IOException
     * @throws InterruptedException
     * @throws Exception 
     */
    public void run() throws IOException, InterruptedException, Exception {
        //If no date input, make it the maximum of available data
        if(startDate == null || startDate.equalsIgnoreCase("")){
            startDate = "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
        WaterDataInterface waterLib = WaterData.getNewWaterDataInterface(database, userData);
        String[][] sortableData = waterLib.extractFlowData_formatted(directory, orgId, stationId, startDate, endDate);
        dataSource = waterLib.getDataSourceCitation();
        
        //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){
            WaterDataInterface waterLibUser = WaterData.getNewWaterDataInterface("UserData", userData);
            sortableData_user = waterLibUser.extractFlowData_formatted(directory, orgId, stationId, startDate, endDate);
        }
        
        //Sort the Data by date to remove duplicate date entries
        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<>();
            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);			
        }
        len = String.valueOf(sortedData_combined.length);
        start = String.valueOf(sortedData_combined[0][0]);
        end = String.valueOf(sortedData_combined[sortedData_combined.length - 1][0]);
        
        writeSummary(droughtInfo);
    }
    public static void main(String[] args) throws IOException, InterruptedException, Exception {
        //Run Model
        guiDrought_Model drought_Model = new guiDrought_Model();
        drought_Model.run();
    }
}