Package net.lenkaspace.creeper.report

Source Code of net.lenkaspace.creeper.report.CRTimeSeriesReport

package net.lenkaspace.creeper.report;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.Ellipse2D;
import java.util.ArrayList;
import java.util.List;

import net.lenkaspace.creeper.helpers.CRFileIOHelper;
import net.lenkaspace.creeper.model.CRSettings;

import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.NumberTickUnit;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.data.Range;
import org.jfree.data.xy.XYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;

/**
* Creates graphical time series reports, usually of a single run / trial.
*
* For better performance, maintain an instance that opens and closes with (updated) data rather than
* creating a new popup every time.
*
* @author      Lenka Pitonakova contact@lenkaspace.net
* @version     2.0                                     
*/
public class CRTimeSeriesReport extends CRBaseReport {
 
  public static enum FUNCTION {
    AVERAGE,
    MEDIAN
  }
 
  protected String xAxisTitle;
  protected String yAxisTitle;
 
  protected List<XYSeries> dataSeriesArray;
  protected double[] totalDataSeriesValuesOfSecond;
  protected String[] variableNames;
  protected Range yAxisDisplayRange;
  protected boolean shouldDisplayLegend;
  protected int[] numOfValuesAddedInCurrentSecond;
  protected boolean isScatterPlot;
  protected boolean shouldOuputVariableNames;
 
  protected JFreeChart jFreeChart;
  protected ChartPanel chartPanel;
 
  protected Color [] colors = {Color.blue, Color.green, Color.red, Color.gray, Color.magenta, Color.black, Color.orange, Color.pink};
  protected Shape[] shapes = {new Ellipse2D.Double(-1, -1.0, 2.0, 2.0)};
  protected Stroke[] strokes = {new BasicStroke(1)};

  /**
   * Constructor
   * @param title_ String title of the report
   * @param variableNames_ String array of variable names to identify different time series lines
   */
  public CRTimeSeriesReport(String title_, String[] variableNames_) {
    super(title_, new Dimension(1000,500), true);
    xAxisTitle = "Time";
    yAxisTitle = "Value";
    variableNames = variableNames_;
    shouldDisplayLegend = true;
    isScatterPlot = false;
    shouldOuputVariableNames = false;
  }
 
  /**
   * Constructor
   * @param title_ String title of the report
   * @param variableNames_ String array of variable names to identify different time series lines
   * @param size_ Dimension size of the report window
   */
  public CRTimeSeriesReport(String title_, String[] variableNames_, Dimension size_) {
    super(title_, size_, true);
    xAxisTitle = "Time";
    yAxisTitle = "Value";
    variableNames = variableNames_;
    shouldDisplayLegend = true;
    isScatterPlot = false;
    shouldOuputVariableNames = false;
  }
 
 
  //==================================== SIMULATION EVENTS ====================================
 
  /**
   * Called by CRController each time a run starts.
   * Use this to reset self for a brand new simulation and call onNewTrial of children models.
   * @param runNumber_ int new run number
   */
  public void onRunStart(int runNumber_) {
   
  }
 
  /**
   * Called by CRController each time a trial starts.
   * Set all variables to 0 and create new data series.
   * @param trialNumber_ int new trial number
   * @param runNumber_ int current run number
   */
  public void onTrialStart(int trialNumber_, int runNumber_) {
    dataSeriesArray = new ArrayList <XYSeries>();
    totalDataSeriesValuesOfSecond = new double[variableNames.length];
    numOfValuesAddedInCurrentSecond = new int[variableNames.length];
    //-- create elements of the dataSeriesArray
    for (String seriesName : variableNames) {
      XYSeries series = new XYSeries(seriesName);
      series.setDescription(seriesName);
      dataSeriesArray.add(series);
    }
  }
 
  /**
   * Save self to an image if allowed
   * @param trialNumber_ int ending trial number
   * @param runNumber_ int current run number
   */
  public void onTrialEnd(int trialNumber_, int runNumber_) {
    CRSettings settings = CRSettings.getSingleton();
    if (settings.getShouldPrintGraphicReports()) {
      createSelf(reportController.getCurrentFilePath() + "_" + this.getFileName(), false);
    }
    if (CRSettings.getSingleton().getShouldPrintTextReports()) {
          createSelfAsText(reportController.getCurrentFilePath() + "_" + this.getFileName());
        }
  }
 
  /**
   * Called from CRController before onUpdateLoopStart() of all world objects is called.
   * Set the value for current time to 0
   * @param timeUnit_ int current time unit
   */
  public void onUpdateLoopStart(int timeCounter_, int timeUnit_) {
    super.onUpdateLoopStart(timeCounter_, timeUnit_);
    //-- add 0 at the beginning, so that there is at least some value for each second.
    if (timeCounter_ == 0) {
      for (int variableIndex=0; variableIndex < dataSeriesArray.size(); variableIndex++) {
        addValue(0,variableIndex);
        //-- reset the number of values added this second to 0
        numOfValuesAddedInCurrentSecond[variableIndex] = 0;
      }
    }
   
  }
 
 
  /**
   * Called from CRController after onUpdateLoopEnd() of all world objects is called.
   * Calculate average over the passed second
   * @param timeUnit_ int current time unit
   */
  public void onUpdateLoopEnd(int timeCounter_, int timeUnit_) {
    super.onUpdateLoopEnd(timeCounter_, timeUnit_);
    for (int variableIndex=0; variableIndex < dataSeriesArray.size(); variableIndex++) {     
      //-- only add value if at the end of a new second
      if (timeCounter_ == CRSettings.getSingleton().getTimeUnitInterval()-1) {
        //-- add an average of previous second, average over the number of values there can be for each second
        double registeredValue =  totalDataSeriesValuesOfSecond[variableIndex];
        if (numOfValuesAddedInCurrentSecond[variableIndex] > 1) {
          registeredValue = totalDataSeriesValuesOfSecond[variableIndex]/(double)numOfValuesAddedInCurrentSecond[variableIndex];
        }
        dataSeriesArray.get(variableIndex).add(timeUnit_, registeredValue);
        //-- reset total value:
        totalDataSeriesValuesOfSecond[variableIndex] = 0.0;
      }
    }
  }
 
 
 
 
  //==================================== DATA COLLECTION =================================
  /**
   * Register new value set for the current time step.
   * The value set size should be the same as size of set names the report was initialised with.
   * @param values_ double[] array of values
   */
  public void addValueSet(double[] values_) {
    for (int i=0; i<variableNames.length; i++) {
      addValue(values_[i], variableNames[i]);
    }
  }
 
 
  /**
   * Register a new value for a specific variable name from the overal value set
   * @param value_ double new value
   * @param variableName_ String variable name from the variable name set this object was initialised with
   */
  public void addValue(double value_, String variableName_) {
    int index = getVariableNameIndex(variableName_);
    addValue(value_, index);
  }
 
  /**
   * Register a new value for a 0th index of the overal value set
   * @param value_ double new value
   */
  public void addValue(double value_) {
    addValue(value_, 0);
  }
 
  /**
   * Register a new value for a specific index of the overal value set
   * @param value_ double new value
   * @param variableIndex_ int 0 <= index < size of set names
   */
  public void addValue(double value_, int variableIndex_) {
   
    if (variableIndex_ >= 0 && variableIndex_ < variableNames.length) {
      //-- add to temporary value:
      totalDataSeriesValuesOfSecond[variableIndex_] += value_;
      numOfValuesAddedInCurrentSecond[variableIndex_]++;
     
    }
  }
 
 
  //==================================== REPORT CREATION =================================
 
  /**
   * Create a window of the report. Optionally show it on screen as well
   * @param printToFileName_ String file name to print report into, without extension
   * @param show_ boolean if false, report is hidden immediately after printed
   */
  public void createSelf(String printToFileName_, boolean show_) {
    super.createSelf(printToFileName_, show_);
   
    //-- test if there is any data to show
    if (dataSeriesArray == null) {
      System.err.println("CRTimeSeries" + title + " has no data. Hint: Have you called onNewTrial to initialise?");
      return;
    }
    //-- test if there is at least 1 color:
    if (colors.length <= 0) {
      System.err.println("CRTimeSeries" + title + " no graph colors specified.");
      return;
    }
    //-- test if there is at least 1 shape:
    if (shapes.length <= 0) {
      System.err.println("CRTimeSeries" + title + " no graph line shapes specified.");
      return;
    }
    //-- test if there is at least 1 stroke:
    if (strokes.length <= 0) {
      System.err.println("CRTimeSeries" + title + " no graph line strokes specified.");
      return;
    }
   
    if (!CRSettings.getSingleton().getIsConsoleOnlyBuild()) {
      //-- prepare data
      XYDataset dataset = prepareXYSeriesForDisplay(dataSeriesArray)
     
      //-- create report object or update it
      if (jFreeChart == null) { 
        jFreeChart = createChart(title, xAxisTitle, yAxisTitle, dataset);
        jFreeChart.getXYPlot().setFixedLegendItems(null);
        jFreeChart.getXYPlot().getDomainAxis().setLabelFont(new Font("Tahoma", Font.BOLD, 12));
        ValueAxis yAxis = jFreeChart.getXYPlot().getRangeAxis();
        if (yAxis.getUpperBound() > 1) {
          yAxis.setAutoTickUnitSelection(false);
          NumberTickUnit rUnit = new NumberTickUnit((yAxis.getUpperBound() - yAxis.getLowerBound()) / 5.0);
          ((NumberAxis) yAxis).setTickUnit(rUnit);
        } else {
          yAxis.setAutoTickUnitSelection(false);
          NumberTickUnit rUnit = new NumberTickUnit( 0.1);
          ((NumberAxis) yAxis).setTickUnit(rUnit);
        }
        chartPanel = new ChartPanel(jFreeChart);
        chartPanel.setPreferredSize(new java.awt.Dimension(this.size.width, this.size.height-35));
        basePanel.add(chartPanel);
      } else {
        jFreeChart.getXYPlot().setDataset(dataset);
      }
   
      //-- set range if specified
      if (yAxisDisplayRange != null) {
        jFreeChart.getXYPlot().getRangeAxis().setRange(yAxisDisplayRange);
      }
         
      //-- format legend
      jFreeChart.getLegend().visible = shouldDisplayLegend;
     
        //-- set shape and paint of series
      XYLineAndShapeRenderer renderer = (XYLineAndShapeRenderer) jFreeChart.getXYPlot().getRenderer();
    //  XYLineAndShapeRenderer lineAndShapeRenderer = null;
          for (int i=1; i<dataset.getSeriesCount(); i++) {
            //-- make sure colors, shapes and paint strokes wrap around their arrays in case not enough specified.
            //   the first series is just dummy titled 'Legend' so don't count it (therefore use i-1) when calculating
            int colorId = ((i-1)%colors.length);
            int shapeId = ((i-1)%shapes.length);
            int strokeId = ((i-1)%strokes.length);
        renderer.setSeriesPaint(i,colors[colorId] );
          renderer.setSeriesShape(i, shapes[shapeId] );
          renderer.setSeriesStroke(i, strokes[strokeId]);
          if (isScatterPlot) {
            renderer.setSeriesLinesVisible(i,false);
          } else {
            renderer.setSeriesLinesVisible(i,true);
          }
         
      }
         
      //-- has to be visible for printing:
          baseFrame.setVisible(true);
          if (printToFileName_.length() > 0) {
            freezeDisplay(); //pause the program for a while, otherwise reports may come out incomplete
        CRFileIOHelper.componentToJpeg(chartPanel, printToFileName_);
       
        //-- hide afterwards:
        if (!show_) {
          baseFrame.setVisible(false);
        }
          }
    }
  }
 
  /**
   * Save a textual representation of data, where columns represent individual variables (tab-separated)
   * and rows represent different time steps.
   * @param printToFileName_ String path + file name
   */
  public void createSelfAsText(String printToFileName_) {
    String outputString = "";
    if (shouldOuputVariableNames) {
      outputString += "t\t";
      for (int variableIndex=0; variableIndex < variableNames.length; variableIndex++) {
        outputString += variableNames[variableIndex].replace(" ", "_");
        if (variableIndex < variableNames.length - 1) {
          outputString += "\t";
        }
      }
      outputString += "\n";
    }
    for (int t=0; t<dataSeriesArray.get(0).getItemCount(); t++) {
      //-- time
      outputString += dataSeriesArray.get(0).getX(t) "\t";
      //-- variables
      for (int variableIndex=0; variableIndex < dataSeriesArray.size(); variableIndex++) { 
        outputString += dataSeriesArray.get(variableIndex).getY(t);
        if (variableIndex < dataSeriesArray.size() - 1) {
          outputString += "\t";
        }
      }
      outputString += "\n";
    }
   
    CRFileIOHelper.stringToFile(outputString, printToFileName_);
  }
 

 
  /**
   * Create a chart based on a dataset.
   * @param title_ : name of the chart
   * @param xAxisTitle_ : label for x axis
   * @param yAxisTitle_ : label for y axis
   * @param dataset : data
   * @return chart
   */
  private JFreeChart createChart(String title_, String xAxisTitle_, String yAxisTitle_, final XYDataset dataset) {
       
        //-- create the chart...
        final JFreeChart chart = ChartFactory.createXYLineChart(
          title_,              // chart title
          xAxisTitle_,                // x axis label
          yAxisTitle_,                // y axis label
            dataset,                    // data
            PlotOrientation.VERTICAL,
            true,                       // include legend
            true,                       // tooltips
            false                       // urls
        );
       
        chart.setBackgroundPaint(Color.WHITE);
       
        //-- get a reference to the plot for further customisation
        final XYPlot plot = chart.getXYPlot();
        plot.setBackgroundPaint(Color.WHITE);
        plot.setDomainGridlinePaint(Color.gray);
        plot.setRangeGridlinePaint(Color.gray);
       
        final XYLineAndShapeRenderer renderer = new XYLineAndShapeRenderer();
        renderer.setSeriesLinesVisible(0, false);
        renderer.setSeriesShapesVisible(0, false);
        plot.setRenderer(renderer);

        //-- change the auto tick unit selection to integer units only
        final NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis();
        rangeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
               
        return chart;
    }
 
  /**
   * Convert data for display in a line graph
   */
  private XYDataset prepareXYSeriesForDisplay(List<XYSeries> rawData) {
   
    if (rawData == null || rawData.size() == 0) {
      return null;
    } else {
      XYSeriesCollection dataset = new XYSeriesCollection();
      dataset.addSeries(new XYSeries("Legend: ")); //needs a series at the beginning, otherwise won't display...
     
      //-- go through the list of rawData (e.g. multiple variables the report should show:     
      for (int var=0; var < rawData.size(); var++) {
        dataset.addSeries(this.prepareDisplaySeries(rawData.get(var)));
      }
        return dataset;
    }
  }
 
  /**
   * Process raw data series to get rid of data cluttered in time
   */
  private XYSeries prepareDisplaySeries(XYSeries rawSeries) {
    XYSeries displaySeries = new XYSeries(rawSeries.getDescription());
    try {
      displaySeries = (XYSeries) rawSeries.clone();
    } catch (CloneNotSupportedException e) {
      e.printStackTrace();
    }
    return displaySeries;
  }
 
 
  //==================================== GETTERS / SETTERS =================================
 
  /**
   * Get mean of a data series identified by a variable name
   * @param variableName_ String variable name
   * @param timeRange_ Range time range
   * @return double variable mean
   */
  public double getVariableMean(String variableName_, Range timeRange_) {
    int index = getVariableNameIndex(variableName_);
    return getVariableMean(index, timeRange_);
  }
 
  /**
   * Get mean of a data series identified by a variable index.
   * @param variableIndex_ int variable index
   * @param timeRange_ Range time range
   * @return double variable mean
   */
  public double getVariableMean(int variableIndex_, Range timeRange_) {
    if (variableIndex_ < 0 || variableIndex_ >= dataSeriesArray.size()) {
      throw new IndexOutOfBoundsException();
    }
    XYSeries dataSeries = this.dataSeriesArray.get(variableIndex_);
   
    int start, end;
    if (timeRange_ != null) {
      start = Math.max(0, (int) timeRange_.getLowerBound());
      end = Math.min((int) dataSeries.getMaxX(), (int) timeRange_.getUpperBound());
    } else {
      start = 0;
      end = (int) dataSeries.getMaxX();
    }
   
    DescriptiveStatistics stats = new DescriptiveStatistics();
    for (int i = start; i<=end; i++) {
      stats.addValue(dataSeries.getY(i).doubleValue());
    }
    return stats.getMean();
   
  }
 
  /**
   * Get index of a variable name
   * @param variableName_ String variable name. Set to empty string to get the 1st index.
   * @return int index of the variable name
   */
  protected int getVariableNameIndex(String variableName_) {
    for (int i=0; i<variableNames.length; i++) {
      if (variableNames[i].equals(variableName_)) {
        return i;
      }
    }
    return -1;
  }
 
  public void setIsScatterPlot(boolean value_) { isScatterPlot = value_; }
  public void setColors(Color[] colors_) { colors = colors_; }
  public void setShapes(Shape[] shapes_) { shapes = shapes_; }
  public void setStrokes(Stroke[] strokes_) { strokes = strokes_; }
 
  public void setShouldDisplayLegend(boolean value_) { shouldDisplayLegend = value_; }
 
  public void setYAxisTitle(String value_) { yAxisTitle = value_; }
  public void setXAxisTitle(String value_) { xAxisTitle = value_; }
  public void setYAxisDisplayRange(Range range_) { if (range_ != null) { yAxisDisplayRange = new Range(range_.getLowerBound(), range_.getUpperBound()); }}
 
  /**
   * If set to true, variable names will be printed as the 1st row in the text ouput
   * @param value_ boolean
   */
  public void setShouldOutputVariableNames(boolean value_) { shouldOuputVariableNames = value_; }
  public boolean getShouldOutputVariableNames() { return shouldOuputVariableNames; }
 
}
TOP

Related Classes of net.lenkaspace.creeper.report.CRTimeSeriesReport

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.