001    package org.hackystat.projectbrowser.page.projectportfolio.detailspanel.chart;
002    
003    import java.io.Serializable;
004    import java.util.List;
005    import org.apache.wicket.markup.html.panel.Panel;
006    import org.hackystat.projectbrowser.page.projectportfolio.jaxb.Measures.Measure;
007    import org.hackystat.projectbrowser.page.projectportfolio.jaxb.Measures.Measure.DeviationParameters;
008    
009    /**
010     * Classify the trend according to its deviation. Higher deviation is worse.
011     * Expectation value is not considered when coloring trend.
012     * Value is colored according to its deviation to the expectation value.
013     * 
014     * @author Shaoxuan
015     *
016     */
017    public class StreamDeviationClassifier implements Serializable, StreamClassifier {
018    
019      /** Support serialization. */
020      private static final long serialVersionUID = 2104163143505110271L;
021      /** Name of this classifier. */
022      public static final String name = "Deviation";
023    
024      /** The deviation within which the trend is considered healthy. */
025      private double moderateDeviation;
026      /** The deviation beyond which the trend is considered unhealthy. */
027      private double unacceptableDeviation;
028      /** The expectation value. */
029      private double expectationValue;
030      /** If the condition scale with granularity. */
031      private boolean scaleWithGranularity;
032      /**
033       * 
034       * @param moderateDeviation The deviation within which the trend is considered healthy.
035       * @param unacceptableDeviation The deviation beyond which the trend is considered unhealthy.
036       * @param expectationValue The expectation value.
037       * @param scaleWithGranularity If the condition will scale with granularity.
038       */
039      public StreamDeviationClassifier(double moderateDeviation, double unacceptableDeviation, 
040          double expectationValue, boolean scaleWithGranularity) {
041        this.moderateDeviation = moderateDeviation;
042        this.unacceptableDeviation = unacceptableDeviation;
043        this.expectationValue = expectationValue;
044        this.scaleWithGranularity = scaleWithGranularity;
045      }
046      
047      /**
048       * {@inheritDoc}
049       */
050      @Override
051      public Panel getConfigurationPanel(String id) {
052        return new StreamDeviationClassifierConfigurationPanel(id, this);
053      }
054    
055      /**
056       * {@inheritDoc}
057       */
058      @Override
059      public String getName() {
060        return name;
061      }
062    
063      /**
064       * Parse the given MiniBarChart and produce a PortfolioCategory result.
065       * Result is determined by the comparison of the standard deviation of the chart to the 
066       * moderateDeviation and unacceptableDeviation. 
067       * Less than moderateDeviation will be GOOD.
068       * Larger than moderateDeviation and less than unacceptableDeviation will be AVERAGE.
069       * Otherwise will be POOR.
070       * @param chart the input chart
071       * @return PortfolioCategory that indicates the health category of the chart: POOR, AVERAGE, GOOD.
072       *         NA will be returned if chart is empty.
073       */
074      @Override
075      public PortfolioCategory getStreamCategory(MiniBarChart chart) {
076        double moderateDeviation = this.moderateDeviation;
077        double unacceptableDeviation = this.unacceptableDeviation;
078        if ("Week".equals(chart.granularity)) {
079          moderateDeviation *= 7;
080          unacceptableDeviation *= 7;
081        }
082        else if ("Month".equals(chart.granularity)) {
083          moderateDeviation *= 30;
084          unacceptableDeviation *= 30;
085        }
086        List<Double> streamData = chart.streamData;
087        double sum = 0;
088        double sumSqt = 0;
089        for (int i = 0; i < streamData.size(); i++) {
090          if (streamData.get(i).isNaN() || streamData.get(i) < 0) {
091            //remove null points.
092            streamData.remove(i);
093            i--;
094          }
095          else {
096            double d = streamData.get(i);
097            sum += d;
098            sumSqt += d * d;
099          }
100        }
101        if (streamData.isEmpty()) {
102          return PortfolioCategory.NA;
103        }
104        // compute standard deviation
105        int size = streamData.size();
106        double deviation = Math.sqrt((sumSqt - (sum * sum) / size) / size);
107        if (deviation < moderateDeviation) {
108          return PortfolioCategory.GOOD;
109        }
110        else if (deviation < unacceptableDeviation) {
111          return PortfolioCategory.AVERAGE;
112        }
113        else {
114          return PortfolioCategory.POOR;
115        }
116      }
117    
118      /**
119       * Return the category of the given value.
120       * If value is lower than {@link moderateDeviation}, GOOD will be returned.
121       * If value is lower than {@link unacceptableDeviation}, AVERAGE will be returned.
122       * Otherwise, will return POOR.
123       * @param value the given value.
124       * @return a {@link PortfolioCategory} result
125       */
126      @Override
127      public PortfolioCategory getValueCategory(double value) {
128        if (value < 0) {
129          return PortfolioCategory.NA;
130        }
131        double deviation = Math.abs(value - this.expectationValue);
132        if (deviation <= this.moderateDeviation) {
133          return PortfolioCategory.GOOD;
134        }
135        else if (deviation <= this.unacceptableDeviation) {
136          return PortfolioCategory.AVERAGE;
137        }
138        else {
139          return PortfolioCategory.POOR;
140        }
141      }
142    
143      /**
144       * {@inheritDoc}
145       */
146      @Override
147      public void saveSetting(Measure measure) {
148        DeviationParameters param = new DeviationParameters();
149        param.setUnacceptableDeviation(this.unacceptableDeviation);
150        param.setModerateDeviation(this.moderateDeviation);
151        param.setExpectationValue(this.expectationValue);
152        param.setScaleWithGranularity(this.scaleWithGranularity);
153        measure.setDeviationParameters(param);
154      }
155    
156    }