guiTimeseries_Model.java [src/java/cfa] Revision: 1b69320646d4eeb4b67833c13ef5e0bb175788f9  Date: Mon Sep 30 15:04:28 MDT 2013
package cfa;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Stroke;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.DateFormat;
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.axis.DateAxis;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.data.time.Day;
import org.jfree.data.time.Month;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection;
import org.jfree.data.time.Year;
import org.jfree.data.xy.XYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;

/**
* Last Updated: 26-August-2013
* @author Tyler Wible
* @since 24-June-2011
*/
public class guiTimeseries_Model {
    //Inputs
    String mainFolder = "C:/Projects/TylerWible/CodeDirectories/NetBeans/CSIP/data/CFA";
    String organizationName = "USGS";//"Colorado Dept. of Public Health & Environment";//
    String stationID = "06764880";//"000028";//
    String stationName = "South Platte River at Roscoe, Nebr.";//"BIG THOMPSON R NEAR MOUTH";//
    String wqTest = "flow";//"00600        Total nitrogen, water, unfiltered, milligrams per liter, mg/L";
    String beginDate = "";
    String endDate = "";
    String timeStep = "Daily";//"Yearly";//"Monthly";//
    String method = "Max";//"Min";//"Average";//"Total";//
    String userData = "";//"Date\tFlow\n1999-04-29\t8.3\n1999-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 units = "?";
    double max = -1;
    double min = -1;
    double upperQuartile = -1;
    double lowerQuartile = -1;
    double median = -1;
    double mean = -1;
    double standardDeviation = -1;

    //Gets
    public File getParagraph() {
        return new File(mainFolder, "timeseries_summary.txt");
    }
    public String getGraph() {
        return "timeseries_graph.jpg";
    }
    public String getBoxplot() {
        return "timeseries_boxplot.jpg";
    }
    public String getLen(){
        return len;
    }
    public String getStart(){
        return start;
    }
    public String getEnd(){
        return end;
    }
    public String getUnits(){
        return units;
    }
    public String getMax(){
        return String.valueOf(max);
    }
    public String getMin(){
        return String.valueOf(min);
    }
    public String getUpperQuartile(){
        return String.valueOf(upperQuartile);
    }
    public String getLowerQuartile(){
        return String.valueOf(lowerQuartile);
    }
    public String getMedian(){
        return String.valueOf(median);
    }
    public String getMean(){
        return String.valueOf(mean);
    }
    public String getStandardDeviation(){
        return String.valueOf(standardDeviation);
    }
    
    //Sets
    public void setMainFolder(String mainFolder) {
        this.mainFolder = mainFolder;
    }
    public void setOrganizationName(String organizationName) {
        this.organizationName = organizationName;
    }
    public void setBeginDate(String beginDate) {
        this.beginDate = beginDate;
    }
    public void setEndDate(String endDate) {
        this.endDate = endDate;
    }
    public void setStationName(String stationName) {
        this.stationName = stationName;
    }
    public void setStationID(String stationID) {
        this.stationID = stationID;
    }
    public void setWQtest(String wqTest) {
        this.wqTest = wqTest;
    }
    public void setTimeStep(String timeStep) {
        this.timeStep = timeStep;
    }
    public void setMethod(String method) {
        this.method = method;
    }
    public void setUserData(String userData) {
        this.userData = userData;
    }
    public void setMergeDatasets(boolean mergeDatasets) {
        this.mergeDatasets = mergeDatasets;
    }
    public void setMergeMethod(String mergeMethod) {
        this.mergeMethod = mergeMethod;
    }
    /**
     * Computes the daily/monthly/yearly max/min/average/total of the daily dataset provided
     * @param dailyData  a string array with column1 = dates (yyyy-mm-dd format), column2 = value
     * @param timeStep  the desired timestep "daily", "monthly", or "yearly"
     * @param method  the desired method "max", "min", "average", or "total"
     * @return a new string[][] with column 1 = dates (yyyy-mm-dd format for "daily" timeStep,
     * yyyy-mm format for "monthly" timeStep, and yyyy format for "yearly" timeStep) and column2 = values
     */
    public String[][] computeFlowMethod(String[][] dailyData, String timeStep, String method){
        String[][] newData = null;
        if(timeStep.equalsIgnoreCase("daily")){
            return dailyData;
        }else if(timeStep.equalsIgnoreCase("monthly")){
            //Compute the method on the unique monthYears
            newData = computeMethod(dailyData, method, 7);

        }else if(timeStep.equalsIgnoreCase("yearly")){
            //Compute the method on the unique monthYears
            newData = computeMethod(dailyData, method, 4);
        }

        return newData;
    }
    /**
     * Computes the method on the dailyData provided substring-ing the dates from 0-dateLimit to 
     * get a unique set of time periods on which to perform the method
     * @param dailyData  a string array with column1 = dates (yyyy-mm-dd format), column2 = value
     * @param method  the desired method "max", "min", "average", or "total"
     * @param dateLimit  an integer for the limit of the date substring (typically 4 or 7 for the 
     * format yyyy-mm-dd resulting in yyyy and yyyy-mm respectively)
     * @return a new string[][] with column 1 = dates (yyyy-mm-dd format for "daily" timeStep,
     * yyyy-mm format for "monthly" timeStep, and yyyy format for "yearly" timeStep) and column2 = values
     */
    public String[][] computeMethod(String[][] dailyData, String method, int dateLimit){
        DoubleMath doubleMath = new DoubleMath();

        //Find the unique set of months/years for which the method is desired for
        ArrayList<String> uniqueTimeStep = new ArrayList<String>();
        String previousMonthYear = dailyData[0][0].substring(0,dateLimit);
        uniqueTimeStep.add(previousMonthYear);
        for(int i=1; i<dailyData.length; i++){
            //Check if current monthYear is the same or different from the previous
            String currentMonthYear = dailyData[i][0].substring(0,dateLimit);
            if(!previousMonthYear.equalsIgnoreCase(currentMonthYear)){
                uniqueTimeStep.add(currentMonthYear);
                previousMonthYear = currentMonthYear;
            }
        }

        //Loop through daily data, pull out each uniqueTimeStep's dataset and perform the method on that dataset
        String[][] newData = new String[uniqueTimeStep.size()][2];
        ArrayList<Double> currentMonthData = new ArrayList<Double>();
        int ctr=0;		
        for(int i=0; i<dailyData.length; i++){
            if(uniqueTimeStep.get(ctr).equals(dailyData[i][0].substring(0, dateLimit))){
                //If current data = current month, add it to the dataset
                currentMonthData.add(Double.parseDouble(dailyData[i][1]));

            }else{
                //If current data != current month, calculate method on the current month's dataset, save the result and reset the dataset
                newData[ctr][0] = uniqueTimeStep.get(ctr);
                if(method.equalsIgnoreCase("max")){
                        newData[ctr][1] = String.valueOf(doubleMath.max(currentMonthData));
                }else if(method.equalsIgnoreCase("average")){
                        newData[ctr][1] = String.valueOf(doubleMath.Average(currentMonthData));
                }else if(method.equalsIgnoreCase("min")){
                        newData[ctr][1] = String.valueOf(doubleMath.min(currentMonthData));
                }else if(method.equalsIgnoreCase("total")){
                        newData[ctr][1] = String.valueOf(doubleMath.sum(currentMonthData));
                }

                //Reset the dataset and add the current data to it as the new month's data
                currentMonthData.clear();
                currentMonthData.add(Double.parseDouble(dailyData[i][1]));
                ctr++;
            }

            //If on the last point calculate the method on the current dataset
            if(i == dailyData.length-1){
                newData[ctr][0] = uniqueTimeStep.get(ctr);
                if(method.equalsIgnoreCase("max")){
                    newData[ctr][1] = String.valueOf(doubleMath.max(currentMonthData));
                }else if(method.equalsIgnoreCase("average")){
                    newData[ctr][1] = String.valueOf(doubleMath.Average(currentMonthData));
                }else if(method.equalsIgnoreCase("min")){
                    newData[ctr][1] = String.valueOf(doubleMath.min(currentMonthData));
                }else if(method.equalsIgnoreCase("total")){
                    newData[ctr][1] = String.valueOf(doubleMath.sum(currentMonthData));
                }
            }
        }

        return newData;
    }
    /**
     * Main statistics function calls other functions to calculate each statistic value then returns a list of statistics values
     * @param dataList  data on which statistical values are desired
     * @return  list of statistical values (min##max##upperQuartile##lowerQuartile##median##mean##standardDeviation)
     */
    public void CalculateStatistics(String[][] sortedData) {
        DoubleMath doubleMath = new DoubleMath();

        //get data
        double[] dataList = new double[sortedData.length];
        for(int i=0; i<sortedData.length; i++){
            dataList[i] = Double.parseDouble(sortedData[i][1]);
        }

        //Call Max function
        max = Math.round(doubleMath.Min_Max(dataList, true)*1000);
        max = max/1000;

        //Call Min function
        min =  Math.round(doubleMath.Min_Max(dataList, false)*1000);
        min = min/1000;

        //Call Upper Quartile function
        upperQuartile = Math.round(doubleMath.Percentile_function(dataList,0.75)*1000);
        upperQuartile = upperQuartile/1000;

        //Call Lower Quartile function
        lowerQuartile = Math.round(doubleMath.Percentile_function(dataList,0.25)*1000);
        lowerQuartile = lowerQuartile/1000;

        //Call Median function
        median = Math.round(doubleMath.Median(dataList)*1000);
        median = median/1000;

        //Call Mean function
        mean = Math.round(doubleMath.Average(dataList)*1000);
        mean = mean/1000;

        //Call standard deviation
        standardDeviation = Math.round(doubleMath.StandardDeviationSample(dataList)*1000);
        standardDeviation = standardDeviation/1000;
    }
    /**
     * Graph the timeseries and user 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 if timeStep = "daily", yyyy-mm if 
     * timeStep = "monthly", yyyy if timeStep = "yearly") column 2 = value
     * @param sortedData_user  the String[][] containing sorted user data for the 
     * timeseries (column 1 = dates (yyyy-mm-dd if timeStep = "daily", yyyy-mm 
     * if timeStep = "monthly", yyyy if timeStep = "yearly") column 2 = value
     * @param color  the color of the merged dataset (Java.Color)
     * @param color2  the color of the user dataset (Java.Color)
     * @param showLine  a boolean, if true lines will be shown on the graph, if false only shapes for the data
     * @param yAxisTitle  the String label for the y axis of the graph
     */
    public void createTimeseriesGraph(String[][] sortedData,
                                      String[][] sortedData_user,
                                      Color color,
                                      Color color2,
                                      boolean showLine,
                                      String yAxisTitle){
        DurationCurve durationCurve = new DurationCurve();

        //Create TimeSeries graph of merged data
        TimeSeries series = new TimeSeries(stationID + " Public Data");
        for(int i=0; i < sortedData.length; i++) {
            double value = Double.parseDouble(sortedData[i][1]);
            String tmpStr = sortedData[i][0];

            if(timeStep.equalsIgnoreCase("daily")){
                double d = Double.parseDouble(tmpStr.substring(8));
                double m = Double.parseDouble(tmpStr.substring(5,7));
                double y = Double.parseDouble(tmpStr.substring(0,4));
                int day =  (int)d;
                int month = (int)m;
                int year = (int)y;
                Day date = new Day(day,month,year);//day,month,year
                series.add(date, value);
            }else if(timeStep.equalsIgnoreCase("monthly")){
                double m = Double.parseDouble(tmpStr.substring(5,7));
                double y = Double.parseDouble(tmpStr.substring(0,4));
                int month = (int)m;
                int year = (int)y;
                Month date = new Month(month,year);//month,year
                series.add(date, value);
            }else if(timeStep.equalsIgnoreCase("yearly")){
                double y = Double.parseDouble(tmpStr.substring(0,4));
                int year = (int)y;
                Year date = new Year(year);//year
                series.add(date, value);
            }
        }
        //Create TimeSeries graph of user data
        TimeSeries series2 = new TimeSeries(stationID + " User Data");
        for(int i=0; i < sortedData_user.length; i++) {
            double value = Double.parseDouble(sortedData_user[i][1]);
            String tmpStr = sortedData_user[i][0];

            if(timeStep.equalsIgnoreCase("daily")){
                double d = Double.parseDouble(tmpStr.substring(8));
                double m = Double.parseDouble(tmpStr.substring(5,7));
                double y = Double.parseDouble(tmpStr.substring(0,4));
                int day =  (int)d;
                int month = (int)m;
                int year = (int)y;
                Day date = new Day(day,month,year);//day,month,year
                series2.add(date, value);
            }else if(timeStep.equalsIgnoreCase("monthly")){
                double m = Double.parseDouble(tmpStr.substring(5,7));
                double y = Double.parseDouble(tmpStr.substring(0,4));
                int month = (int)m;
                int year = (int)y;
                Month date = new Month(month,year);//month,year
                series2.add(date, value);
            }else if(timeStep.equalsIgnoreCase("yearly")){
                double y = Double.parseDouble(tmpStr.substring(0,4));
                int year = (int)y;
                Year date = new Year(year);//year
                series2.add(date, value);
            }
        }
		
        //Create renderer, and axis for timeseries graph
        XYPlot plotTime = new XYPlot();
        TimeSeriesCollection dataset = new TimeSeriesCollection(series);
        TimeSeriesCollection dataset2 = new TimeSeriesCollection(series2);
        XYItemRenderer rendererTime = new XYLineAndShapeRenderer(showLine, !showLine);
        XYItemRenderer rendererTime2 = new XYLineAndShapeRenderer(showLine, !showLine);
        rendererTime.setSeriesPaint(0, color);
        rendererTime2.setSeriesPaint(0, color2);
        if(!showLine){
            rendererTime.setSeriesShape(0, new Ellipse2D.Double(-2.0, 2.0, 4.0, 4.0));
            rendererTime2.setSeriesShape(0, new Ellipse2D.Double(-2.0, 2.0, 4.0, 4.0));
        }
        
        
        //Create X Axis
        DateAxis domainTime = new DateAxis("Date");
        domainTime.setLowerMargin(0.05);
        domainTime.setUpperMargin(0.05);
        plotTime.setDomainAxis(0, domainTime);

        //Create Y Axis
        ValueAxis rangeTime = new NumberAxis(yAxisTitle);
        plotTime.setRangeAxis(0, rangeTime);

        // Set the timeseries line data, renderer, and axis into plot
        plotTime.setDataset(1, dataset);
        plotTime.setDataset(0, dataset2);
        plotTime.setRenderer(1, rendererTime);
        plotTime.setRenderer(0, rendererTime2);

        //Map the line to the first Domain and first Range
        plotTime.mapDatasetToDomainAxis(0, 0);
        plotTime.mapDatasetToRangeAxis(0, 0);
        plotTime.setForegroundAlpha(0.5f);  

        //Set extra plot preferences
        plotTime.setOutlinePaint(Color.black);
        plotTime.setDomainGridlinePaint(Color.black);
        plotTime.setRangeGridlinePaint(Color.black);
        plotTime.setRangeMinorGridlinesVisible(true);
        plotTime.setRangeMinorGridlinePaint(Color.gray);
        setAxisFonts(plotTime);

        //Create the chart with the plot and a legend
        JFreeChart chart = new JFreeChart("TimeSeries for Station: " + stationID + "; " + stationName, durationCurve.titleFont, plotTime, true);

        //Save resulting graph for use later
        try{
            String path = mainFolder + File.separator + getGraph();
            ChartUtilities.saveChartAsJPEG(new File(path), chart, 1280, 800);
            System.out.println("JFreeChart created properly at: " + path);

        }catch(IOException e){
            System.err.println("A problem occurred while trying to creating the chart.");
        }
    }
    /**
     * 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 if timeStep = "daily", 
     * yyyy-mm if timeStep = "monthly", yyyy if timeStep = "yearly") column 2 = value
     * @param showLine  a boolean, if true lines will be shown on the graph, if false only shapes for the data
     * @param lineColor  the color of the line (Java.Color)
     * @param yAxisTitle  the String label for the y axis of the graph
     */
    public void createTimeseriesGraph(String[][] sortedData,
	                                  Color lineColor,
	                                  boolean showLine,
	                                  String yAxisTitle){
        DurationCurve durationCurve = new DurationCurve();

        //Create TimeSeries graph
        TimeSeries series = new TimeSeries(stationID + " Data Time Series");
        for(int i=0; i < sortedData.length; i++) {
            double value = Double.parseDouble(sortedData[i][1]);
            String tmpStr = sortedData[i][0];

            if(timeStep.equalsIgnoreCase("daily")){
                double d = Double.parseDouble(tmpStr.substring(8));
                double m = Double.parseDouble(tmpStr.substring(5,7));
                double y = Double.parseDouble(tmpStr.substring(0,4));
                int day =  (int)d;
                int month = (int)m;
                int year = (int)y;
                Day date = new Day(day,month,year);//day,month,year
                series.add(date, value);
            }else if(timeStep.equalsIgnoreCase("monthly")){
                double m = Double.parseDouble(tmpStr.substring(5,7));
                double y = Double.parseDouble(tmpStr.substring(0,4));
                int month = (int)m;
                int year = (int)y;
                Month date = new Month(month,year);//month,year
                series.add(date, value);
            }else if(timeStep.equalsIgnoreCase("yearly")){
                double y = Double.parseDouble(tmpStr.substring(0,4));
                int year = (int)y;
                Year date = new Year(year);//year
                series.add(date, value);
            }
        }
		
        //Create renderer, and axis for timeseries graph
        XYPlot plotTime = new XYPlot();
        TimeSeriesCollection dataset = new TimeSeriesCollection(series);
        XYItemRenderer rendererTime = new XYLineAndShapeRenderer(showLine, !showLine);
        rendererTime.setSeriesPaint(0, lineColor);
        if(!showLine){
            rendererTime.setSeriesShape(0, new Ellipse2D.Double(-2.0, 2.0, 4.0, 4.0));
        }
        
        //Create X Axis
        DateAxis domainTime = new DateAxis("Date");
        domainTime.setLowerMargin(0.05);
        domainTime.setUpperMargin(0.05);
        plotTime.setDomainAxis(0, domainTime);

        //Create Y Axis
        ValueAxis rangeTime = new NumberAxis(yAxisTitle);
        plotTime.setRangeAxis(0, rangeTime);

        // Set the timeseries line data, renderer, and axis into plot
        plotTime.setDataset(0, dataset);
        plotTime.setRenderer(0, rendererTime);

        //Map the line to the first Domain and first Range
        plotTime.mapDatasetToDomainAxis(0, 0);
        plotTime.mapDatasetToRangeAxis(0, 0);
        plotTime.setForegroundAlpha(0.5f);  

        //Set extra plot preferences
        plotTime.setOutlinePaint(Color.black);
        plotTime.setDomainGridlinePaint(Color.black);
        plotTime.setRangeGridlinePaint(Color.black);
        plotTime.setRangeMinorGridlinesVisible(true);
        plotTime.setRangeMinorGridlinePaint(Color.gray);
        setAxisFonts(plotTime);

        //Create the chart with the plot and a legend
        JFreeChart chart = new JFreeChart("TimeSeries for Station: " + stationID + "; " + stationName, durationCurve.titleFont, plotTime, false);

        //Save resulting graph for use later
        try{
            String path = mainFolder + File.separator + getGraph();
            ChartUtilities.saveChartAsJPEG(new File(path), chart, 1280, 800);
            System.out.println("JFreeChart created properly at: " + path);

        }catch(IOException e){
            System.err.println("A problem occurred while trying to creating the chart.");
        }
    }
    /**
     * Creates a boxplot of the timeseries data to be displayed next to a summary of the statistics of the timeseries data
     * @param yAxisTitle  a String of the title for the Y axis of the boxplot
     */
    public void createTimeseriesBoxplot(String[][] data, String yAxisTitle){    	
    	//Create boxplot of the timeseries data
    	DurationCurve durationCurve = new DurationCurve();
        XYPlot plot = new XYPlot();

        //Create X Axis
        ValueAxis xAxis = new NumberAxis("");
        xAxis.setRange(0, 10);
        xAxis.setLabelFont(durationCurve.masterFont);
        xAxis.setTickLabelFont(durationCurve.masterFont);
        xAxis.setTickLabelsVisible(false);
        plot.setDomainAxis(0, xAxis);
        
    	ValueAxis yAxis = new NumberAxis(yAxisTitle);
        yAxis.setLabelFont(durationCurve.masterFont);
        yAxis.setTickLabelFont(durationCurve.masterFont);
        plot.setRangeAxis(0, yAxis);

    	//Calculate and add Median to dataset
        XYSeries median_series = new XYSeries("Median");
        median_series.add(5, median);

        //Create median Line
        XYDataset median_scatter = new XYSeriesCollection(median_series);
        XYItemRenderer renderer_median = new XYLineAndShapeRenderer(false, true);
        renderer_median.setSeriesShape(0, new Rectangle2D.Double(-4.0, 0.0, 8.0, 1));//new Ellipse2D.Double(-4, -4, 8, 8));
        renderer_median.setSeriesPaint(0, Color.red);
        renderer_median.setSeriesVisibleInLegend(0, false);
        plot.setDataset(0, median_scatter);
        plot.setRenderer(0, renderer_median);


        //Create quartile Box shapes for the box plot
        //Create XYSeries for the box shape
        XYSeries shapeSeries = new XYSeries("Shape");
        shapeSeries.add(5, lowerQuartile);
        shapeSeries.add(5, upperQuartile);

        //Create the quartile rectangle shape
        XYDataset shapeDataset = new XYSeriesCollection(shapeSeries);
        XYItemRenderer renderer_shape = new XYLineAndShapeRenderer(true, false);
        Stroke thickness = new BasicStroke(10, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);
        renderer_shape.setSeriesStroke(0, thickness);
        renderer_shape.setSeriesPaint(0, Color.blue);
        renderer_shape.setSeriesVisibleInLegend(0, false);
        plot.setDataset(1, shapeDataset);
        plot.setRenderer(1, renderer_shape);

        
        //Creates 1.5 * Interquartile Range (IQR) lines
        //Create XYSeries for the min-max lines
        double IQR = upperQuartile - lowerQuartile;
        double lowerLimit = lowerQuartile - 1.5*IQR;
        double upperLimit = upperQuartile + 1.5*IQR;
        if(lowerLimit < min){
            lowerLimit = min;
        }
        if(upperLimit > max){
            upperLimit = max;
        }
        XYSeries lineSeries = new XYSeries("Line");
        lineSeries.add(5, lowerLimit);
        lineSeries.add(5, upperLimit);

        //Create the 1.5*IQR lines
        XYDataset lineDataset = new XYSeriesCollection(lineSeries);
        XYItemRenderer lineRenderer = new XYLineAndShapeRenderer(true, true);
        Stroke thickness2 = new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);
        lineRenderer.setSeriesStroke(0, thickness2);
        lineRenderer.setSeriesShape(0, new Rectangle2D.Double(-10.0, 0.0, 20.0, 1));
        lineRenderer.setSeriesPaint(0, Color.black);
        lineRenderer.setSeriesVisibleInLegend(0, false);
        plot.setDataset(2, lineDataset);
        plot.setRenderer(2, lineRenderer);
        
        //Calculate and create Outliers (# < lowerQuartile - 1.5*IQR or # > upperQuartile + 1.5*IQR)
        //Calculate and create Extreme Outliers (# < lowerQuartile - 3*IQR or # > upperQuartile + 3*IQR)
        XYSeries outliers = new XYSeries("Outliers");
        XYSeries extremeOutliers = new XYSeries("Extreme Outliers");
        for(int i=0; i<data.length; i++){
            double value = Double.parseDouble(data[i][1]);
            //Lower outliers
            if(value < (lowerQuartile - 1.5*IQR) && value > (lowerQuartile - 3*IQR)){
                outliers.add(5, value);
            }
            //Upper outliers
            if(value > (upperQuartile + 1.5*IQR) && value < (lowerQuartile + 3*IQR)){
                outliers.add(5, value);
            }

            //Extreme Lower outliers
            if(value < (lowerQuartile - 3*IQR)){
                extremeOutliers.add(5, value);
            }
            //Extreme Upper outliers
            if(value > (lowerQuartile + 3*IQR)){
                extremeOutliers.add(5, value);
            }
        }

        //Create outlier scatter
        XYDataset outlier_scatter = new XYSeriesCollection(outliers);
        XYItemRenderer renderer_outlier = new XYLineAndShapeRenderer(false, true);
        renderer_outlier.setSeriesShape(0, new Ellipse2D.Double(-2.0, 2.0, 4.0, 4.0));
        renderer_outlier.setSeriesPaint(0, Color.DARK_GRAY);
        if(outliers.isEmpty()){
            renderer_outlier.setSeriesVisibleInLegend(0, false);        	
        }
        plot.setDataset(3, outlier_scatter);
        plot.setRenderer(3, renderer_outlier);
        
        //Create extreme outlier scatter
        XYDataset extremeOutlier_scatter = new XYSeriesCollection(extremeOutliers);
        XYItemRenderer renderer_ExtremeOutlier = new XYLineAndShapeRenderer(false, true);
        renderer_ExtremeOutlier.setSeriesShape(0, new Ellipse2D.Double(-2.0, 2.0, 4.0, 4.0));
        renderer_ExtremeOutlier.setSeriesPaint(0, Color.red);
        if(extremeOutliers.isEmpty()){
            renderer_ExtremeOutlier.setSeriesVisibleInLegend(0, false);        	
        }
        plot.setDataset(4, extremeOutlier_scatter);
        plot.setRenderer(4, renderer_ExtremeOutlier);

        //Put the line on the first Domain and first Range
        plot.mapDatasetToDomainAxis(0, 0);
        plot.mapDatasetToRangeAxis(0, 0);

        //Set extra plot preferences
        plot.setOutlinePaint(Color.black);
        plot.setDomainGridlinePaint(Color.white);
        plot.setRangeGridlinePaint(Color.black);
        
        
        //Create the chart with the plot and a legend
        JFreeChart chart = new JFreeChart("Boxplot of Timeseries Data", durationCurve.titleFont, plot, true);

        //Save resulting graph for use later
        try{
            String path = mainFolder + File.separator + getBoxplot();
            ChartUtilities.saveChartAsJPEG(new File(path), chart, 200, 400);
            System.out.println("JFreeChart created properly at: " + path);

        }catch(IOException e){
            System.err.println("A problem occurred while trying to creating the chart.");
        }
    }
    /**
     * This subfunction takes the provided plot and changes the fonts of the axis to a standardized new font
     * @param plot  the XYPlot containing the graph on which the fonts will be changed
     * @return the original plot with the modifications to the axis fonts
     */
    private XYPlot setAxisFonts(XYPlot plot){
        DurationCurve durationCurve = new DurationCurve();
        //Set Y axis fonts
        ValueAxis yAxis = plot.getRangeAxis();
        yAxis.setLabelFont(durationCurve.masterFont);
        yAxis.setTickLabelFont(durationCurve.masterFont);

        //Set X axis fonts
        DateAxis xAxis = (DateAxis) plot.getDomainAxis();
        xAxis.setLabelFont(durationCurve.masterFont);
        xAxis.setTickLabelFont(durationCurve.masterFont);	

        return plot;
    }
    /**
     * Writes out the dynamically created paragraph to be displayed to the user along with the LDC graph
     * @param dynamicParagraph  string array to be written as each line of the text file
     * @param partialpath  the partial folder path of the file to be written
     * @throws IOException
     */
    public void writeSummary(String[] dynamicParagraph, String partialpath) throws IOException{
        String path = partialpath + File.separator + "timeseries_summary.txt";
        FileWriter writer =  new FileWriter(path, false);
        PrintWriter print_line = new PrintWriter(writer);

        //Output data to text file
        for(int i = 0; i < dynamicParagraph.length; i++) {
            print_line.printf("%s" + "%n", dynamicParagraph[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(String[] error) throws IOException{
        //Output data to text file
        String errorContents = error[0];
        for(int i=1; i<error.length; i++){
            errorContents = errorContents + "\n" + error[i];
        }
        throw new IOException("Error encountered. Please see the following message for details: \n" + errorContents);
    }
    /**
     * Primary TimeSeries
     * It calls the subfunctions based on user selection/inputs.
     * Calls STORET or USGS database queries and their respective subfunctions
     * @throws IOException 
     * @throws InterruptedException 
     */
    public void run() throws IOException, InterruptedException {
        //Inputs
        //assert args.length > 0;
        //String mainFolder 		= args[0];
        //String fileName 		= args[1];
        //String organizationName = args[2];
        //String stationID 		= args[3];
        //String stationName		= args[4];
        //String wqTest 			= args[5];
        //String beginDate		= args[6];
        //String endDate	 		= args[7];
        //String timeStep			= args[8];
        //String method			= args[9];
        
        //If no date input, make it the maximum of available data
        if(beginDate == null || beginDate.equalsIgnoreCase("")){
            beginDate = "1900-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);
        }
        
        //Artificial limit due to the properties of the Jfreechart object "Date"
        if(beginDate.compareToIgnoreCase("1900-01-01") < 0){
            beginDate = "1900-01-01";
        }
        if(endDate.compareToIgnoreCase("1900-01-01") < 0){
            endDate = "1900-01-01";
        }
        
        //Initialize graph variables
        String[][] sortableData = new String[0][2];
        String yAxisTitle = "y axis";
        String graphUnits = "??";
        Color color = Color.black, color2 = Color.black;
        boolean showLine = true;
        USGS_Data usgs_Data = new USGS_Data();
        User_Data user_Data = new User_Data();
        STORET_Data storet_Data = new STORET_Data();
        DurationCurve durationCurve = new DurationCurve();
        DoubleArray doubleArray = new DoubleArray();
        
        if(mergeDatasets){
            //Combine the datasets for an analysis
             String[][] sortableData_user = user_Data.readUserFile(userData, beginDate, endDate);
             if(wqTest.equalsIgnoreCase("flow")){
                //Search for Flow Data
                if(organizationName.equalsIgnoreCase("USGS")){
                    //Search for USGS flow data
                    sortableData = usgs_Data.USGS_read_FDC(stationID, beginDate, endDate);
                    
                    //If there is minimal flow data try getting WQ-flow data
                    if(sortableData.length < 10){
                        //Retrieve WQ data from USGS website
                        String[][] WQData = usgs_Data.USGS_read_LDC(stationID);
                        //Extract USGS water quality code 00061 for dischage in cfs
                        String[][] WQFlow1 = usgs_Data.minimizeUSGSWQdata(WQData, "00061", beginDate, endDate);
                        //Extract USGS water quality code 30209 for discharge test in m^3/s (cms)
                        String[][] WQFlow2 = usgs_Data.minimizeUSGSWQdata(WQData, "30209", beginDate, endDate);

                        //Convert the m^3/s to ft^3/s
                        for(int i=0; i<WQFlow2.length; i++){
                            WQFlow2[i][1] = Double.toString((Double.parseDouble(WQFlow2[i][1])*(3.2808399*3.2808399*3.2808399)));
                        }

                        //combine the WQ flows (cfs and converted cms) into a single variable to be used with the Flowdata
                        String[][] WQDataflows = usgs_Data.mergeMinimizedWQdata(WQFlow1, WQFlow2);
                        //Combine flow data and WQ flow data into a variable of dates and flow values to be sorted
                        sortableData = usgs_Data.mergeMinimizedWQdata(sortableData, WQDataflows);
                    }
                    
                }else{			
                    //Search for STORET flow data
                    String zip_location = storet_Data.downloadSTORET(mainFolder, organizationName, stationID, "flow", beginDate, endDate);
                    //Unzip results file and extract all flow data
                    sortableData = storet_Data.Unzip_STORETDownloadFiles(zip_location, "flow", true);
                }
                graphUnits = "cfs";
                yAxisTitle = timeStep + " " + method + " Flow [" + graphUnits + "]";
                color = Color.blue;
                color2 = Color.DARK_GRAY;
                showLine = true;

            }else{
                //Search for WQ data
                if(organizationName.equalsIgnoreCase("USGS")){
                    //Search for USGS water quality data

                    //Pull the right portion of the wqTestcode
                    int endIndex = wqTest.lastIndexOf(", ");
                    if(endIndex == -1){
                        endIndex = wqTest.lastIndexOf("--");
                    }
                    String WQlabel = wqTest.substring(11,endIndex);//cut off the "98335      " part before the test name and the units after the name
                    WQlabel = WQlabel.split(",")[0];
                    wqTest = wqTest.substring(0,5);//pull just the 5 digit USGS WQ code

                    //Retrieve WQ data from USGS website
                    sortableData = usgs_Data.USGS_read_LDC(stationID);
                    sortableData = usgs_Data.minimizeUSGSWQdata(sortableData, wqTest, beginDate, endDate);

                    //Get Units and conversion for current WQ test
                    graphUnits = durationCurve.USGSwqUnits(wqTest);
                    yAxisTitle = timeStep + " " + method + " " + WQlabel + " [" + graphUnits + "]";

                }else{
                    //Search for STORET flow data
                    String zip_location = storet_Data.downloadSTORET(mainFolder, organizationName, stationID, wqTest, beginDate, endDate);

                    //Unzip results file and extract all flow data
                    sortableData = storet_Data.Unzip_STORETDownloadFiles(zip_location, wqTest, true);
                    graphUnits = "mg/L";
                    yAxisTitle = timeStep + " " + method + " " + wqTest + " [" + graphUnits + "]";
                }
                color = Color.magenta;
                color2 = Color.BLUE;
                showLine = false;
            }
             
             //Check if any data exists
            if(sortableData.length==0){
                String[] errorMessage = {"There is no available data for station '" + stationID + "' and the specified date range"};
                writeError(errorMessage);
            }
            if(sortableData_user.length==0){
                String[] errorMessage = {"There is no available uploaded data for station '" + stationID + "' and the specified date range"};
                writeError(errorMessage);
            }

            //Remove sort and remove duplicate days to aid in the graphing
            String[][] sortedData = durationCurve.removeDuplicateDates(sortableData);
            String[][] sortedData_user = durationCurve.removeDuplicateDates(sortableData_user);

            //Perform analysis method on data
            sortedData = computeFlowMethod(sortedData, timeStep, method);
            sortedData_user = computeFlowMethod(sortedData_user, timeStep, method);

            //Calculate stats of data
            String[][] combinedData = doubleArray.mergeData(sortedData, sortedData_user, mergeMethod);
            CalculateStatistics(combinedData);
            

            //Graph the timeseries data
            createTimeseriesGraph(combinedData, sortedData_user, color, color2, showLine, yAxisTitle);
            createTimeseriesBoxplot(combinedData, yAxisTitle);
            
            //Create dynamic summary paragraph
            this.start = sortedData[0][0];
            this.end = sortedData[sortedData.length - 1][0];
            this.len = String.valueOf(sortedData.length);
            this.units = graphUnits;
            String[] dynamicParagraph = durationCurve.dynamicParagraph("Time Series Graph Overview: ", organizationName);

            //Write dynamic Paragraph to text file
            writeSummary(dynamicParagraph, mainFolder);
             
             
             
        }else{
            //Run one analysis on one dataset (either USGS, UserData, or STORET)
            if(wqTest.equalsIgnoreCase("flow")){
                //Search for Flow Data
                if(organizationName.equalsIgnoreCase("USGS")){
                    //Search for USGS flow data
                    sortableData = usgs_Data.USGS_read_FDC(stationID, beginDate, endDate);
                    
                    //If there is minimal flow data try getting WQ-flow data
                    if(sortableData.length < 10){
                        //Retrieve WQ data from USGS website
                        String[][] WQData = usgs_Data.USGS_read_LDC(stationID);
                        //Extract USGS water quality code 00061 for dischage in cfs
                        String[][] WQFlow1 = usgs_Data.minimizeUSGSWQdata(WQData, "00061", beginDate, endDate);
                        //Extract USGS water quality code 30209 for discharge test in m^3/s (cms)
                        String[][] WQFlow2 = usgs_Data.minimizeUSGSWQdata(WQData, "30209", beginDate, endDate);

                        //Convert the m^3/s to ft^3/s
                        for(int i=0; i<WQFlow2.length; i++){
                            WQFlow2[i][1] = Double.toString((Double.parseDouble(WQFlow2[i][1])*(3.2808399*3.2808399*3.2808399)));
                        }

                        //combine the WQ flows (cfs and converted cms) into a single variable to be used with the Flowdata
                        String[][] WQDataflows = usgs_Data.mergeMinimizedWQdata(WQFlow1, WQFlow2);
                        //Combine flow data and WQ flow data into a variable of dates and flow values to be sorted
                        sortableData = usgs_Data.mergeMinimizedWQdata(sortableData, WQDataflows);
                    }
                    
                }else if(organizationName.equalsIgnoreCase("UserData")){
                    //Find the user uploaded data file and uses this for a timeseries graph
                    sortableData = user_Data.readUserFile(userData, beginDate, endDate);

                }else{			
                    //Search for STORET flow data
                    String zip_location = storet_Data.downloadSTORET(mainFolder, organizationName, stationID, "flow", beginDate, endDate);
                    //Unzip results file and extract all flow data
                    sortableData = storet_Data.Unzip_STORETDownloadFiles(zip_location, "flow", true);
                }	
                graphUnits = "cfs";
                yAxisTitle = timeStep + " " + method + " Flow [" + graphUnits + "]";
                color = Color.blue;
                showLine = true;

            }else{
                //Search for WQ data
                if(organizationName.equalsIgnoreCase("USGS")){
                    //Search for USGS water quality data

                    //Pull the right portion of the wqTestcode
                    int endIndex = wqTest.lastIndexOf(", ");
                    if(endIndex == -1){
                        endIndex = wqTest.lastIndexOf("--");
                    }
                    String WQlabel = wqTest.substring(11,endIndex);//cut off the "98335      " part before the test name and the units after the name
                    WQlabel = WQlabel.split(",")[0];
                    wqTest = wqTest.substring(0,5);//pull just the 5 digit USGS WQ code

                    //Retrieve WQ data from USGS website
                    sortableData = usgs_Data.USGS_read_LDC(stationID);
                    sortableData = usgs_Data.minimizeUSGSWQdata(sortableData, wqTest, beginDate, endDate);

                    //Get Units and conversion for current WQ test
                    graphUnits = durationCurve.USGSwqUnits(wqTest);
                    yAxisTitle = timeStep + " " + method + " " + WQlabel + " [" + graphUnits + "]";

                }else if(organizationName.equalsIgnoreCase("UserData")){
                    //Find the user uploaded data file and uses this for a timeseries graph
                    sortableData = user_Data.readUserFile(userData, beginDate, endDate);			

                    //Use the header to get the WQ test name
                    String[] headers = user_Data.getHeaders(userData);
                    graphUnits = durationCurve.USGSwqUnits(wqTest);//Because user uploaded file headers are wqTest
                    yAxisTitle = timeStep + " " + method + " " + headers[1] + " [" + graphUnits + "]"; 

                }else{
                    //Search for STORET flow data
                    String zip_location = storet_Data.downloadSTORET(mainFolder, organizationName, stationID, wqTest, beginDate, endDate);

                    //Unzip results file and extract all flow data
                    sortableData = storet_Data.Unzip_STORETDownloadFiles(zip_location, wqTest, true);
                    graphUnits = "mg/L";
                    yAxisTitle = timeStep + " " + method + " " + wqTest + "' [" + graphUnits + "]";
                }
                color = Color.magenta;
                showLine = false;
            }
            
            //Check if any data exists
            if(sortableData.length==0){
                String message = "There is no available data for station '" + stationID + "' and the specified date range";
                String[] errorMessage = {message};
                writeError(errorMessage);
            }

            //Remove sort and remove duplicate days to aid in the graphing
            String[][] sortedData = durationCurve.removeDuplicateDates(sortableData);

            //Perform analysis method on data
            sortedData = computeFlowMethod(sortedData, timeStep, method);

            //Calculate stats of data
            CalculateStatistics(sortedData);

            //Graph the timeseries data
            createTimeseriesGraph(sortedData, color, showLine, yAxisTitle);
            createTimeseriesBoxplot(sortedData, yAxisTitle);
            
            //Create dynamic summary paragraph
            this.start = sortedData[0][0];
            this.end = sortedData[sortedData.length - 1][0];
            this.len = String.valueOf(sortedData.length);
            this.units = graphUnits;
            String[] dynamicParagraph = durationCurve.dynamicParagraph("Time Series Graph Overview: ", organizationName);

            //Write dynamic Paragraph to text file
            writeSummary(dynamicParagraph, mainFolder);
        }
    }
    public static void main(String[] args) throws IOException, InterruptedException, Exception {
        guiTimeseries_Model timeseries_Model = new guiTimeseries_Model();
        
        //Run model
        timeseries_Model.run();
    }
}