Graphing.java [src/java/cfa] Revision: 1376ab11a5e41a0ad6bcbc162c031abe5e27beb3  Date: Fri Jul 18 10:31:53 MDT 2014
package cfa;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Stroke;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import org.jfree.chart.axis.CategoryAxis;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.axis.LogarithmicAxis;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.IntervalMarker;
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;
import org.jfree.ui.Layer;
import org.jfree.ui.RectangleAnchor;
import org.jfree.ui.TextAnchor;

/**
* Last Updated: 18-June-2014
* @author Tyler Wible
* @since 3-February-2014
*/
public class Graphing {
    private String fontFamily = "SansSerif";
    Font titleFont = new Font(fontFamily, Font.BOLD, 30);
    Font masterFont = new Font(fontFamily, Font.PLAIN, 22);
    Font DClabelFont = new Font(fontFamily, Font.ITALIC, 20);
    Font floodLabelFont = new Font(fontFamily, Font.PLAIN, 18);
    //Calibri
    //Georgia
    //SansSerif
    //Serif
    //Tahoma
    //Verdana
    /**
     * Sub-graphing function to add graph x-interval marker/label
     * @param plot  the graph on which to add the interval
     * @param Label_title  the desired label title.
     * @param lowerlimit  the lower limit of the x-interval marker.
     * @param upperlimit  the upper limit of the x-interval marker.
     * @return An Interval Marker.
     */
    public XYPlot addIntervalLabel (XYPlot plot, String Label_title, double lowerlimit, double upperlimit){
        //Create a new interval marker and set its properties
        final IntervalMarker newLabel = new IntervalMarker(lowerlimit,upperlimit);
        newLabel.setLabel(Label_title);
        newLabel.setLabelFont(DClabelFont);
        newLabel.setLabelAnchor(RectangleAnchor.BOTTOM);
        newLabel.setLabelTextAnchor(TextAnchor.BASELINE_CENTER);
        newLabel.setPaint(plot.getBackgroundPaint());//new Color(250, 250, 250, 100));//new Color(222, 222, 255, 130));
        newLabel.setOutlinePaint(Color.black);

        //Add the interval to the provided plot
        plot.addDomainMarker(newLabel, Layer.BACKGROUND);
        
        return plot;
    }
    /**
     * Sub-function to create lines/rectangle to mimic a box plot
     * @param plot  the plot in which the objects are being graphed
     * @param x_coord  x coordinate of the correct boxplot
     * @param data  water quality data within the current flow interval
     * @param series_int  which element in the plot series is being created
     * @param existingOutliers  a variable indicating if there are already 
     * existing outliers (true) which are already labeled in the legend, 
     * therefore don't label these outliers in the legend
     * @param existingExtremeOutliers  a variable indicating if there are already 
     * existing extreme outliers (true) which are already labeled in the legend, 
     * therefore don't label these outliers in the legend
     * @return the original plot with new object in it.
     */
    public Object[] boxplot_shapes(XYPlot plot, 
                                   double x_coord, 
    		                   ArrayList<Double> data, 
    		                   int series_int, 
    		                   boolean existingOutliers, 
    		                   boolean existingExtremeOutliers){
        DoubleMath doubleMath = new DoubleMath();

        //Calculate and add Median to dataset
        XYSeries median_series = new XYSeries("Medians");
        double mid_median = doubleMath.Percentile_function(data,0.50);
        median_series.add(x_coord,mid_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, 0.5));//new Ellipse2D.Double(-4, -4, 8, 8));
        renderer_median.setSeriesPaint(0, Color.red);
        renderer_median.setSeriesVisibleInLegend(0, false);
        plot.setDataset(series_int, median_scatter);
        plot.setRenderer(series_int, renderer_median);
        series_int++;
                
        //Create quartile Box shapes for the box plot
        //Create XYSeries for the box shape
        XYSeries shapeSeries = new XYSeries("Shape");
        double lowerQuartile = doubleMath.Percentile_function(data,0.25);
        double upperQuartile = doubleMath.Percentile_function(data,0.75);
        shapeSeries.add(x_coord, lowerQuartile);
        shapeSeries.add(x_coord, upperQuartile);

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

        //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 < doubleMath.Min_Max(data,false)){
            lowerLimit = doubleMath.Min_Max(data,false);
        }
        if(upperLimit > doubleMath.Min_Max(data,true)){
            upperLimit = doubleMath.Min_Max(data,true);
        }
        XYSeries lineSeries = new XYSeries("Line");
        lineSeries.add(x_coord, lowerLimit);
        lineSeries.add(x_coord, upperLimit);

        //Create the 1.5*IQR lines
        XYDataset lineDataset = new XYSeriesCollection(lineSeries);
        XYItemRenderer lineRenderer = new XYLineAndShapeRenderer(true, true);
        Stroke thickness2 = new BasicStroke(1);
        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(series_int, lineDataset);
        plot.setRenderer(series_int, lineRenderer);
        series_int++;
        
        //Create additional output for use with JHighCharts instead/in addition to JFreeCharts
        double[] boxplotData = {upperLimit,upperQuartile,mid_median,lowerQuartile,lowerLimit};
        ArrayList<Double> outliersJHighCharts = new ArrayList<Double>();// Get daily outliers
        
        //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.size(); i++){
            double value = data.get(i);
            //Lower outliers
            if(value < (lowerQuartile - 1.5*IQR) && value > (lowerQuartile - 3*IQR)){
                outliers.add(x_coord, value);
                outliersJHighCharts.add(value);
            }
            //Upper outliers
            if(value > (upperQuartile + 1.5*IQR) && value < (lowerQuartile + 3*IQR)){
                outliers.add(x_coord, value);
                outliersJHighCharts.add(value);
            }

            //Extreme Lower outliers
            if(value < (lowerQuartile - 3*IQR)){
                extremeOutliers.add(x_coord, value);
                outliersJHighCharts.add(value);
            }
            //Extreme Upper outliers
            if(value > (lowerQuartile + 3*IQR)){
                extremeOutliers.add(x_coord, value);
                outliersJHighCharts.add(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.darkGray);
        if(outliers.isEmpty() || existingOutliers == true){
            renderer_outlier.setSeriesVisibleInLegend(0, false);        	
        }else{
            existingOutliers = true;
        }
        plot.setDataset(series_int, outlier_scatter);
        plot.setRenderer(series_int, renderer_outlier);
        series_int++;

        //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() || existingExtremeOutliers == true){
            renderer_ExtremeOutlier.setSeriesVisibleInLegend(0, false);        	
        }else{
            existingExtremeOutliers = true;
        }
        plot.setDataset(series_int, extremeOutlier_scatter);
        plot.setRenderer(series_int, renderer_ExtremeOutlier);
        series_int++;
        
        Object[] returnArray = {plot, existingOutliers, existingExtremeOutliers, series_int, outliersJHighCharts, boxplotData};
        return returnArray;
    }
    /**
     * Graphs the provided "series" as an XY series with x as the first column y as the second column in the provided color
     * on the provided XYPlot, this series is not visible in the legend
     * @param plot  the XYPlot to add the "series" to
     * @param series  a double[][] array with series[all][0] =  x-values, series[all][1] = y-values
     * @param lineColor  the color of the line to be graphed
     * @param seriesIndex  the graph index of the series to be plotted
     * @return the provided XYPlot with the series added to it with the above properties
     */
    public XYPlot graphSeries(XYPlot plot, double[][] series, Color lineColor, int seriesIndex){
        XYSeries xySeries = new XYSeries("");
        for(int i=0; i<series.length; i++){
            double y = series[i][1];
            double x = series[i][0];
            xySeries.add(x, y);
        }

        //Create a graph with the line
        XYDataset xyDataset = new XYSeriesCollection(xySeries);
        XYItemRenderer renderer = new XYLineAndShapeRenderer(true, false);
        renderer.setSeriesPaint(0, lineColor);
        renderer.setSeriesVisibleInLegend(0, false);

        //Set the FDC line data, renderer, and axis into plot
        plot.setDataset(seriesIndex, xyDataset);
        plot.setRenderer(seriesIndex, renderer);

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

        return plot;
    }
    /**
     * Graphs the provided "series" as an XY series with x as the first column y as the second column in the provided color
     * on the provided XYPlot, this series is not visible in the legend
     * @param plot  the XYPlot to add the "series" to
     * @param series  a String[][] array with series[all][0] =  dates in yyyy-MM-dd format, series[all][1] = y-values
     * @param timeStep  a flag for what data is being graphed ("Daily", "Monthly", or "Yearly")
     * @param seriesName  the name of the series to appear in the legend (if showInLegend is true)
     * @param lineColor  the color of the line to be graphed
     * @param seriesIndex  the graph index of the series to be plotted
     * @param showInLegend  a flag to show this series in the legend (true) or not (false)
     * @param leapYearTF  a flag to check if the main dataset is a leap year or not so that if graphing data repeatedly for one year (see timeseries envelope graph) Feb. 29th does or doesnot get plotted
     * @return the provided XYPlot with the series added to it with the above properties
     */
    public XYPlot graphSeries(XYPlot plot,
                              String[][] series,
                              String timeStep,
                              String seriesName,
                              Color lineColor,
                              int seriesIndex,
                              boolean showInLegend,
                              boolean leapYearTF){
        TimeSeries timeSeries = new TimeSeries(seriesName);
        for(int i=0; i<series.length; i++){
            String tmpStr = series[i][0];
            double value = Double.parseDouble(series[i][1]);
            
            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;
                try{
                    Day date = new Day(day,month,year);//day,month,year
                    timeSeries.add(date, value);
                }catch(IllegalArgumentException e){
                    //If there is an error with the date and it is not due to trying 
                    //to plot Feb. 29th in a non-leap year then throw the error
                    if(!leapYearTF && month!=2 && day!=29){
                        throw new IllegalArgumentException (e);
                    }
                }
                
            }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
                timeSeries.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
                timeSeries.add(date, value);
            }
        }

        //Create a graph with the line
        TimeSeriesCollection currentDataset = new TimeSeriesCollection(timeSeries);
        XYItemRenderer renderer = new XYLineAndShapeRenderer(true, false);
        renderer.setSeriesPaint(0, lineColor);
        renderer.setSeriesVisibleInLegend(0, showInLegend);

        //Set the line data, renderer, and axis into plot
        plot.setDataset(seriesIndex, currentDataset);
        plot.setRenderer(seriesIndex, renderer);

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

        return plot;
    }
    /**
     * Graphs the TimeSeries on to the provided plot with the below properties for rendering, color, series index
     * @param plot  The XYPlot to graph the XYSeries on
     * @param currentLine  the XYSeries containing the x,y points of the current line series
     * @param lineTrue  a boolean, true if the series is to have lines connecting the points, otherwise no line
     * @param seriesColor  The desired color of the series (Java.Color)
     * @param dashedLine  if true then the XYSeries will have a dashed line instead of a solid one, if false it will be the normal solid line
     * @param shortDash  if true a dashed line like the major grid lines will be drawn, otherwise a long dashed line will be used
     * @param thickLine  if true the line will have a thicker stroke, if false it will be left to the defaults
     * @param visibleInLegend  if true then the XYSeries' name will be visible in the legend, if false this XYSeries' name will not be visible in the legend
     * @param graphIndex  the series index for the current line, this should be incrementally increased 
     * each time this function is call so that the new dataset does not replace the old one
     * @return the XYPlot with the added TimeSeries
     */
    public XYPlot graphTimeData(XYPlot plot, 
                                TimeSeries currentLine, 
                                boolean lineTrue, 
                                Color seriesColor, 
                                boolean dashedLine,
                                boolean shortDash,
                                boolean thickLine,
                                boolean visibleInLegend,
                                int graphIndex){
        //Create Line Data and renderer
        TimeSeriesCollection currentDataset = new TimeSeriesCollection(currentLine);

        //Check if this series should have a dashed line or solid line and change the renderer accordingly

        XYItemRenderer currentRenderer = new XYLineAndShapeRenderer(lineTrue, !lineTrue);
        currentRenderer.setSeriesPaint(0, seriesColor);
        currentRenderer.setSeriesVisibleInLegend(0, visibleInLegend);

        //Check if this series should have a dashed line, if so change the renderer
        if(dashedLine){
            if(shortDash){
                //Change the renderer's stroke to a short dashed line
                currentRenderer.setSeriesStroke(0, 
                    new BasicStroke(
                        1.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND,
                        1.0f, new float[] {1.0f, 2.0f}, 0.0f
                ));
            }else{
                //Change the renderer's stroke to a long dashed line
                currentRenderer.setSeriesStroke(0, 
                    new BasicStroke(
                        1.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND,
                        1.0f, new float[] {6.0f, 6.0f}, 0.0f
                    ));
            }
        }
        
        //Check if the series should have a thicker line
        if(thickLine){
            currentRenderer.setSeriesStroke(0, 
                new BasicStroke(
                    3.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND,
                    1.0f, new float[] {6.0f, 0.0f}, 0.0f
                ));
        }
        
        //Put the line data, renderer, and axis into plot
        plot.setDataset(graphIndex, currentDataset);
        plot.setRenderer(graphIndex, currentRenderer);

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

        return plot;
    }
    /**
     * 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
     */
    public XYPlot setTimeAxisPreferences(XYPlot plot){
        //Set Y axis fonts
        ValueAxis yAxis = plot.getRangeAxis();
        yAxis.setLabelFont(masterFont);
        yAxis.setTickLabelFont(masterFont);

        //Set X axis fonts
        DateAxis xAxis = (DateAxis) plot.getDomainAxis();
        xAxis.setLabelFont(masterFont);
        xAxis.setTickLabelFont(masterFont);	
        
        //Set extra plot preferences
        plot.setOutlinePaint(Color.black);
        plot.setDomainGridlinePaint(Color.black);
        plot.setRangeGridlinePaint(Color.black);
        plot.setRangeMinorGridlinesVisible(true);
        plot.setRangeMinorGridlinePaint(Color.gray);
        
        return plot;
    }
    /**
     * 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
     */
    public XYPlot setAxisPreferences(XYPlot plot){
        //Set Y axis fonts
        ValueAxis yAxis = plot.getRangeAxis();
        yAxis.setLabelFont(masterFont);
        yAxis.setTickLabelFont(masterFont);

        //Set X axis fonts
        ValueAxis xAxis = plot.getDomainAxis();
        xAxis.setLabelFont(masterFont);
        xAxis.setTickLabelFont(masterFont);	
        
        //Set extra plot preferences
        plot.setOutlinePaint(Color.black);
        plot.setDomainGridlinePaint(Color.black);
        plot.setRangeGridlinePaint(Color.black);
        plot.setRangeMinorGridlinesVisible(true);
        plot.setRangeMinorGridlinePaint(Color.gray);
        
        return plot;
    }
    /**
     * 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
     */
    public XYPlot setLogYaxisPreferences(XYPlot plot){
        //Set Y axis fonts
        LogarithmicAxis yAxis = (LogarithmicAxis) plot.getRangeAxis();
        yAxis.setLabelFont(masterFont);
        yAxis.setTickLabelFont(masterFont);

        //Set X axis fonts
        ValueAxis xAxis = (ValueAxis) plot.getDomainAxis();
        xAxis.setLabelFont(masterFont);
        xAxis.setTickLabelFont(masterFont);	
        
        //Set extra plot preferences
        plot.setOutlinePaint(Color.black);
        plot.setDomainGridlinePaint(Color.black);
        plot.setRangeGridlinePaint(Color.black);
        
        return plot;
    }
    /**
     * 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
     */
    public XYPlot setLogXaxisPreferences(XYPlot plot){
        //Set Y axis fonts
        ValueAxis xAxis = (ValueAxis) plot.getRangeAxis();
        xAxis.setLabelFont(masterFont);
        xAxis.setTickLabelFont(masterFont);

        //Set X axis fonts
        LogarithmicAxis yAxis = (LogarithmicAxis) plot.getDomainAxis();
        yAxis.setLabelFont(masterFont);
        yAxis.setTickLabelFont(masterFont);
        
        //Set extra plot preferences
        plot.setOutlinePaint(Color.black);
        plot.setDomainGridlinePaint(Color.black);
        plot.setRangeGridlinePaint(Color.black);
        
        return plot;
    }
    /**
     * This subfunction takes the provided plot and changes the fonts of the axis to a standardized new font
     * @param plot  the CategoryPlot containing the graph on which the fonts will be changed
     * @return the original plot with the modifications to the axis fonts
     */
    public CategoryPlot setCategoryAxisPreferences(CategoryPlot plot){
        //Set Y axis fonts
        ValueAxis yAxis = plot.getRangeAxis();
        yAxis.setLabelFont(masterFont);
        yAxis.setTickLabelFont(masterFont);

        //Set X axis fonts
        CategoryAxis xAxis = plot.getDomainAxis();
        xAxis.setLabelFont(masterFont);
        xAxis.setTickLabelFont(masterFont);	
        
        //Set extra plot preferences
        plot.setOutlinePaint(Color.black);
        plot.setDomainGridlinePaint(Color.black);
        plot.setDomainGridlinesVisible(true);
        plot.setRangeGridlinePaint(Color.black);
        plot.setRangeMinorGridlinesVisible(true);
        plot.setRangeMinorGridlinePaint(Color.gray);
        
        return plot;
    }
}