/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package CPS.Core.CropPlanStats;
import CPS.Data.CPSCalculations;
import CPS.Data.CPSComplexPlantingFilter;
import CPS.Data.CPSDateValidator;
import CPS.Data.CPSPlanting;
import CPS.Data.CPSPlantingComparator;
import CPS.Module.CPSDataModelConstants;
import CPS.Module.CPSDisplayableDataUserModule;
import CPS.Module.CPSGlobalSettings;
import CPS.UI.Swing.CPSCardPanel;
import ca.odell.glazedlists.BasicEventList;
import ca.odell.glazedlists.FilterList;
import ca.odell.glazedlists.SortedList;
import ca.odell.glazedlists.matchers.Matchers;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSeparator;
import javax.swing.SwingConstants;
import net.miginfocom.swing.MigLayout;
/**
*
* @author crcarter
*/
public class CropPlanStats extends CPSDisplayableDataUserModule implements ActionListener {
JPanel jplContents, jplBedsCharts, jplFlatsChart, jplReq, jplFlats;
JButton updateButton;
JLabel labelInfo;
private BasicEventList<CPSPlanting> dataList;
private SortedList<CPSPlanting> dataSorted;
private FilterList<CPSPlanting> dataFiltered;
private Date dateFirstPlanting, dateLastHarvest;
public CropPlanStats() {
setModuleName("Stats & Charts");
setModuleType("Core");
setModuleVersion( CPSGlobalSettings.getVersion() );
}
//****************************************************************************//
// Called when the user pressed "Update"
//****************************************************************************//
private void doIt() {
updateButton.setEnabled(false);
jplContents.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
Calendar cal = Calendar.getInstance();
//****************************************************************************//
// Create the lists and setup the sort/filter/function chain
//****************************************************************************//
dataList = new BasicEventList<CPSPlanting>();
dataFiltered = new FilterList<CPSPlanting>( dataList );
dataSorted = new SortedList<CPSPlanting>( dataFiltered, null );
CPSCalculations.SumBedsRowftFlats statSums = new CPSCalculations.SumBedsRowftFlats( dataFiltered );
//****************************************************************************//
// Fill up the lists
//****************************************************************************//
dataList.addAll( getDataSource().getCropPlan( getMediator().getCropPlan() ) );
List<String> fieldNames = getDataSource().getFieldNameList( getMediator().getCropPlan() );
List<String> flatSizes = getDataSource().getFlatSizeList( getMediator().getCropPlan() );
List<String> requirements = getDataSource().getRequirementsForPlan( getMediator().getCropPlan() );
// make sure we include blank field names
if ( ! fieldNames.contains("") )
fieldNames.add("");
// and include "blank" flat size as "total flats"
if ( ! flatSizes.contains("") )
flatSizes.add("");
// and include "blank" requirements as "total beds"
if ( ! requirements.contains("") )
requirements.add("");
//****************************************************************************//
// Find the first and last weeks to worry about
//****************************************************************************//
// Find the first planting
dataSorted.setComparator( new CPSPlantingComparator( CPSDataModelConstants.PROP_DATE_PLANT ));
String labelString = "First planting is " + dataSorted.get(0).getCropName() +
" on " + dataSorted.get(0).getDateToPlantString();
dateFirstPlanting = dataSorted.get(0).getDateToPlant();
// ... and the last
// this sorts the list based on harvest date + num of weeks to yield
dataSorted.setComparator( new Comparator<CPSPlanting>() {
public int compare( CPSPlanting o1, CPSPlanting o2) {
return o1.getDateHarvestEnd().compareTo(o2.getDateHarvestEnd());
}
}
);
dateLastHarvest = dataSorted.get( dataSorted.size()-1 ).getDateHarvestEnd();
labelString += "<br>Last harvest is " + dataSorted.get( dataSorted.size()-1 ).getCropName() +
" on " + CPSDateValidator.format( dateLastHarvest );
Calendar tempCal = Calendar.getInstance();
tempCal.setTime( dateLastHarvest );
cal.setTime( dateFirstPlanting );
int numWeeks = 0;
while ( cal.getTime().before( tempCal.getTime() ) ) {
cal.add( Calendar.WEEK_OF_YEAR, 1 );
numWeeks++;
}
//****************************************************************************//
// Create the filters depending on how it was planted
//****************************************************************************//
CPSInTheFieldMatcherEditor inFieldMatcher = new CPSInTheFieldMatcherEditor();
CPSInTheGreenhouseMatcher inGHMatcher = new CPSInTheGreenhouseMatcher();
//****************************************************************************//
// Create our data structures
//****************************************************************************//
// eg. 128 => 78.25, 72 => 105.5, 606 => ...
Map<String, Float> flatssTotalMap = new HashMap<String, Float>();
// eg 128 => flats => 34.25
// flatsdate => 4/12
Map<String, WeeklyMaxStruct> flatsMaxMap = new HashMap<String, WeeklyMaxStruct>();
for (String f : flatSizes)
flatsMaxMap.put( f, new WeeklyMaxStruct () );
// eg. black-plastic => beds => 68.5, ... rowlen => 1243.11
Map<String, WeeklyMaxStruct> reqsTotalMap = new HashMap<String, WeeklyMaxStruct>();
for (String r : requirements )
reqsTotalMap.put( r, new WeeklyMaxStruct () );
// eg black-plastic => beds => 34.25
// bedsDate => 6/12
// rowLen => 102.5
// rowLenDate => 7/2
Map<String, WeeklyMaxStruct> reqsMaxMap = new HashMap<String, WeeklyMaxStruct>();
for (String r : requirements )
reqsMaxMap.put( r, new WeeklyMaxStruct () );
// weekNum => field name (or "flats" or "all") => usage#
Map<Integer, Map<String, Double>> bedFlatUsageMap = new HashMap<Integer, Map<String, Double>>();
//****************************************************************************//
// get total bed/flat count for requirements and flats
//****************************************************************************//
for ( String r : requirements ) {
inFieldMatcher.setRequirement( r );
dataFiltered.setMatcher(inFieldMatcher);
reqsTotalMap.get( r ).beds = statSums.beds;
reqsTotalMap.get( r ).rowLen = statSums.rowUnitLengthes;
}
for ( String f : flatSizes) {
inGHMatcher.setFlatSize( f );
dataFiltered.setMatcher( inGHMatcher );
flatssTotalMap.put( f, statSums.flats );
}
// unfilter the list
dataFiltered.setMatcher( Matchers.trueMatcher() );
inFieldMatcher.setRequirement( null );
inGHMatcher.setFlatSize( null );
//**********************************************************************//
// Loop over the weeks
//**********************************************************************//
cal.setTime( dateFirstPlanting );
int weekNum = cal.get( Calendar.WEEK_OF_YEAR );
while ( cal.getTime().before( dateLastHarvest ) ) {
// init this weeks' usage Map
bedFlatUsageMap.put( weekNum, new HashMap<String, Double> () );
//*************************************//
// Check total trays in GH this week
//*************************************//
inGHMatcher.setInTheGreenhouseOn( cal.getTime() );
inGHMatcher.setFlatSize( null );
dataFiltered.setMatcher( inGHMatcher );
bedFlatUsageMap.get( weekNum )
.put( "flats",
1.0 * CPSCalculations.roundQuarter( statSums.flats ));
//*************************************//
// Check to see if this week is the maximum tray usage (by size)
//*************************************//
for ( String f : flatSizes ) {
inGHMatcher.setFlatSize( f );
dataFiltered.setMatcher( inGHMatcher );
if ( statSums.flats > flatsMaxMap.get( f ).flats ) {
flatsMaxMap.get( f ).flats = statSums.flats;
flatsMaxMap.get( f ).flatsDate = cal.getTime();
}
}
//*************************************//
// Check total beds in use this week
//*************************************//
inFieldMatcher.setInTheFieldOn( cal.getTime() );
inFieldMatcher.setFieldName( null );
inFieldMatcher.setRequirement( null );
dataFiltered.setMatcher( inFieldMatcher );
bedFlatUsageMap.get( weekNum )
.put( "all",
1.0 * CPSCalculations.roundQuarter( statSums.beds ));
//*************************************//
// Check to see about max usage of requirements
//*************************************//
for ( String r : requirements ) {
inFieldMatcher.setRequirement( r );
dataFiltered.setMatcher( inFieldMatcher );
if ( statSums.beds > reqsMaxMap.get(r).beds ) {
reqsMaxMap.get(r).beds = statSums.beds;
reqsMaxMap.get(r).bedsDate = cal.getTime();
}
if ( statSums.rowUnitLengthes > reqsMaxMap.get( r).rowLen ) {
reqsMaxMap.get(r).rowLen = statSums.rowUnitLengthes;
reqsMaxMap.get(r).rowLenDate = cal.getTime();
}
}
//*************************************//
// now ignore requirements and just look at field names for bed usage
//*************************************//
inFieldMatcher.setRequirement( null );
for ( String f : fieldNames ) {
inFieldMatcher.setFieldName( f );
dataFiltered.setMatcher( inFieldMatcher );
bedFlatUsageMap.get( weekNum )
.put( f,
1.0 * CPSCalculations.roundQuarter( statSums.beds ));
}
// now bump the week for the calendar and the weekNum
cal.add( Calendar.WEEK_OF_YEAR, 1 );
weekNum++;
}
//****************************************************************************//
// Done collecting data, now put to together into the charts
//****************************************************************************//
List<String> bedChartTitles = new ArrayList<String>();
List<JPanel> bedCharts = new ArrayList<JPanel>();
JPanel ghChart = null;
List<String> chartsToMake = new ArrayList<String>( fieldNames );
chartsToMake.add( "all" );
chartsToMake.add( "flats" );
//*************************************//
// Loop over fields to build charts for bed usage (and flats)
//*************************************//
for ( String f : chartsToMake ) {
List<String> chartLabels = new ArrayList<String>();
List<Double> chartValues = new ArrayList<Double>();
cal.setTime( dateFirstPlanting );
weekNum = cal.get( Calendar.WEEK_OF_YEAR );
while ( cal.getTime().before( dateLastHarvest ) ) {
chartLabels.add( CPSDateValidator.format( cal.getTime(),
CPSDateValidator.DATE_FORMAT_SHORT));
chartValues.add( bedFlatUsageMap.get(weekNum).get(f) );
// now bump the week for the calendar and the weekNum
cal.add( Calendar.WEEK_OF_YEAR, 1 );
weekNum++;
}
//*************************************//
// Catch special case where we're working with flats
//*************************************//
if ( f.equals( "flats") ) {
ghChart = new ChartPanel( chartValues, chartLabels, "Trays in the Greenhouse" );
}
else {
// and special cases for specific field names
if ( f.equals("all") )
bedChartTitles.add( "Total beds in use" );
else if ( f.equals("") )
bedChartTitles.add( "Beds in use in field" );
else
bedChartTitles.add( "Beds in use: " + f );
bedCharts.add( new ChartPanel( chartValues, chartLabels, "" ) );
}
}
//****************************************************************************//
// Now build all of the displayable components
//****************************************************************************//
JLabel tempLabel;
//*************************************//
// Charts
//*************************************//
labelInfo = new JLabel( "<html>" + labelString + "</html>" );
jplBedsCharts = new CPSCardPanel( bedChartTitles, bedCharts, bedChartTitles.indexOf( "Total beds in use" ) );
jplFlatsChart = ghChart;
//*************************************//
// Total and Max Usage of Beds/Requirements
//*************************************//
jplReq = new JPanel( new MigLayout( "",
"[align right, 15%:][align center, 2%:][align right, 15%:]" ));
jplReq.add( new JLabel( "Requirement" ), "align center" );
jplReq.add( new JLabel( "<html><center>" + "Total<br>Beds" + "</center></html>" ), "skip 1" );
jplReq.add( new JLabel( "<html><center>" + "Max<br>Beds" + "</center></html>" ) );
if ( CPSGlobalSettings.useMetric() ) {
jplReq.add( new JLabel( "<html><center>" + "Total<br>Row Meters" + "</center></html>" ) );
jplReq.add( new JLabel( "<html><center>" + "Max<br>Row Meters" + "</center></html>" ), "wrap" );
} else {
jplReq.add( new JLabel( "<html><center>" + "Total<br>RowFt" + "</center></html>" ) );
jplReq.add( new JLabel( "<html><center>" + "Max<br>RowFt" + "</center></html>" ), "wrap" );
}
jplReq.add( new JSeparator(), "growx, span, wrap" );
boolean firstRow = true;
for ( String req : requirements ) {
if ( req.equals("") )
continue;
jplReq.add( new JLabel( req ) );
if ( firstRow ) {
jplReq.add( new JSeparator( SwingConstants.VERTICAL ), "growy, spany" );
firstRow = false;
}
jplReq.add( new JLabel( "" + CPSCalculations.roundQuarter( reqsTotalMap.get(req).beds ) ));
tempLabel = new JLabel( "" + CPSCalculations.roundQuarter( reqsMaxMap.get(req).beds ) );
tempLabel.setToolTipText( "on " + CPSDateValidator.format( reqsMaxMap.get(req).bedsDate,
CPSDateValidator.DATE_FORMAT_SHORT ));
jplReq.add( tempLabel );
jplReq.add( new JLabel( "" + reqsTotalMap.get(req).rowLen ));
tempLabel = new JLabel( "" + reqsMaxMap.get(req).rowLen );
tempLabel.setToolTipText( "on " + CPSDateValidator.format( reqsMaxMap.get(req).rowLenDate,
CPSDateValidator.DATE_FORMAT_SHORT ));
jplReq.add( tempLabel, "wrap" );
}
tempLabel = new JLabel("Total:");
tempLabel.setToolTipText( "for all beds in this plan, regardless of 'requirements' needed" );
jplReq.add( tempLabel );
// still have to add the separator if we're on the first row
if ( firstRow )
jplReq.add( new JSeparator( SwingConstants.VERTICAL ), "growy, spany" );
jplReq.add( new JLabel( "" + CPSCalculations.roundQuarter( reqsTotalMap.get("").beds ) ));
tempLabel = new JLabel( "" + CPSCalculations.roundQuarter( reqsMaxMap.get("").beds ) );
tempLabel.setToolTipText( "on " + CPSDateValidator.format( reqsMaxMap.get("").bedsDate,
CPSDateValidator.DATE_FORMAT_SHORT ));
jplReq.add( tempLabel );
jplReq.add( new JLabel( "" + reqsTotalMap.get("").rowLen ));
tempLabel = new JLabel( "" + reqsMaxMap.get("").rowLen );
tempLabel.setToolTipText( "on " + CPSDateValidator.format( reqsMaxMap.get("").rowLenDate,
CPSDateValidator.DATE_FORMAT_SHORT ));
jplReq.add( tempLabel, "wrap" );
//*************************************//
// Total/Max usage of flats
//*************************************//
jplFlats = new JPanel( new MigLayout( "",
"[align right, 15%:][align center, 2%:][align right, 15%:]" ));
jplFlats.add( new JLabel( "Flat Size" ), "align center" );
jplFlats.add( new JLabel( "<html><center>" + "Total<br>Flats" + "</center></html>" ), "skip 1" );
jplFlats.add( new JLabel( "<html><center>" + "Max<br>Flats" + "</center></html>" ), "wrap" );
jplFlats.add( new JSeparator(), "growx, span, wrap" );
firstRow = true;
for ( String flat : flatSizes ) {
if ( flat.equals( "" ) )
continue;
jplFlats.add( new JLabel( flat ) );
if ( firstRow ) {
jplFlats.add( new JSeparator( SwingConstants.VERTICAL ), "growy, spany" );
firstRow = false;
}
jplFlats.add( new JLabel( flatssTotalMap.get(flat).toString() ));
tempLabel = new JLabel( "" + CPSCalculations.roundQuarter( flatsMaxMap.get(flat).flats ));
tempLabel.setToolTipText( "on " + CPSDateValidator.format( flatsMaxMap.get(flat).flatsDate,
CPSDateValidator.DATE_FORMAT_SHORT ));
jplFlats.add( tempLabel, "wrap" );
}
tempLabel = new JLabel("Total:");
tempLabel.setToolTipText( "for all flats in this plan, regardless of size" );
jplFlats.add( tempLabel );
if ( firstRow )
jplFlats.add( new JSeparator( SwingConstants.VERTICAL ), "growy, spany" );
jplFlats.add( new JLabel( flatssTotalMap.get("").toString() ));
tempLabel = new JLabel( "" + CPSCalculations.roundQuarter( flatsMaxMap.get("").flats ));
tempLabel.setToolTipText( "on " + CPSDateValidator.format( flatsMaxMap.get("").flatsDate,
CPSDateValidator.DATE_FORMAT_SHORT ));
jplFlats.add( tempLabel, "wrap" );
//*************************************//
// all done
//*************************************//
rebuildContentPanel();
updateButton.setEnabled(true);
jplContents.setCursor(Cursor.getDefaultCursor());
}
@Override
public void dataUpdated() {}
@Override
public int init() { return 0; }
@Override
protected int saveState() { return 0; }
@Override
public int shutdown() { return 0; }
protected void rebuildContentPanel() {
if ( jplContents == null )
jplContents = new JPanel( new MigLayout( "insets 2px, gap 0px!",
"[50%][50%]",
"[][60%][]" ));
// "[][grow, fill][]" ));
jplContents.removeAll();
if ( labelInfo == null )
labelInfo = new JLabel( "Nothing to display; try pressing \"Update\"" );
jplContents.add( labelInfo, "align left" );
if ( updateButton == null ) {
updateButton = new JButton("Update");
updateButton.addActionListener(this);
}
jplContents.add( updateButton, "align right, wrap" );
if ( jplBedsCharts != null ) {
jplContents.add( jplBedsCharts, "grow" );
jplContents.add( jplFlatsChart, "grow, wrap" );
}
if ( jplReq != null ) {
jplContents.add( jplReq, "align left, grow" );
jplContents.add( jplFlats, "align left, grow, wrap" );
}
}
public JPanel display() {
if ( jplContents == null )
rebuildContentPanel();
return jplContents;
}
public Dimension getSize() {
return new Dimension(100, 100);
}
public void actionPerformed(ActionEvent e) {
if ( e.getActionCommand().equalsIgnoreCase( updateButton.getActionCommand() ) )
doIt();
}
//****************************************************************************//
// Internal Classes
//****************************************************************************//
//****************************************************************************//
// Filters
//****************************************************************************//
/**
* Given a date (through the {@link setPlantingInTheFieldOn( Date d )} method)
* this will match any planting that has been planted before the date and whose
* "harvest window" (defined as Harvest Date + Num Weeks of Harvest) has not
* passed.
*/
class CPSInTheFieldMatcherEditor extends CPSComplexPlantingFilter {
private Date dateInTheField;
private String fieldName;
private String requirement = null;
@Override
public boolean matches(CPSPlanting p) {
boolean m = true;
if ( dateInTheField != null ) {
if ( p.isDirectSeeded() )
m &= dateInTheField.after( bumpDateByDay( p.getDateToPlant(), -1 )) &&
dateInTheField.before( p.getDateHarvestEnd() );
else
m &= dateInTheField.after( bumpDateByDay( p.getDateToTP(), -1 )) &&
dateInTheField.before( p.getDateHarvestEnd() );
}
// if fieldName is null, then match any field name
// if it's blank, then match blank
// otherwise match that it "starts with"
if ( fieldName != null ) {
if ( fieldName.equals( "") )
m &= p.getLocation().equals( "" );
else
m &= p.getLocation().startsWith(fieldName);
}
if ( requirement != null && ! requirement.equals("") )
m &= p.getOtherRequirements().contains( requirement );
return m;
}
public void setInTheFieldOn(Date d) {
dateInTheField = d;
fireChanged(this);
}
public void setFieldName(String s) {
fieldName = s;
fireChanged(this);
}
public void setRequirement( String f ) {
requirement = f;
fireChanged(this);
}
}
/**
* Given a date (through the {@link setSeededInTheGreenhouseOn( Date d )} method)
* this will match any planting that has been planted before the date and whose
* transplant date has not passed.
* It will also match on a flat size, if given via {@link setFlatSize( String f )}.
* Set either to null stop attempting to match that property. (ie match any
* value of that property)
*/
class CPSInTheGreenhouseMatcher extends CPSComplexPlantingFilter {
private Date seededInTheGreenhouse = null;
private String flatSize = null;
public CPSInTheGreenhouseMatcher() {
setAsTransplatedFilter();
}
@Override
public boolean matches(CPSPlanting p) {
boolean m = true;
if ( seededInTheGreenhouse != null )
m &= seededInTheGreenhouse.after( bumpDateByDay( p.getDateToPlant(), -1 )) &&
seededInTheGreenhouse.before( p.getDateToTP() );
if ( flatSize != null && ! flatSize.equals("") )
m &= p.getFlatSize().equals(flatSize);
return m;
}
public void setInTheGreenhouseOn(Date d) {
this.seededInTheGreenhouse = d;
fireChanged(this);
}
public void setFlatSize( String f ) {
flatSize = f;
fireChanged(this);
}
}
//****************************************************************************//
// Basic struct to hold max values for each week
//****************************************************************************//
private final class WeeklyMaxStruct {
public float beds = 0;
public float rowLen = 0;
public float flats = 0;
public Date bedsDate = null;
public Date rowLenDate = null;
public Date flatsDate = null;
}
}