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.
008            StreamTrendParameters;
009    /**
010     * Classify stream trend into 4 classes: increasing, decreasing, stable and unstable(other).
011     * Monotonous increase line will be considered as increasing.
012     * Monotonous decrease line will be considered as decreasing.
013     * Exactly flat horizontal line will be considered as stable.
014     * Others will be considered as unstable(other).
015     * Note: a flat line with a little difference in the middle will be consider as unstable(other).
016     * @author Shaoxuan Zhang
017     *
018     */
019    public class StreamTrendClassifier implements Serializable, StreamClassifier {
020    
021      /** Support serialization. */
022      private static final long serialVersionUID = 5078158281421083678L;
023      /** Name of this classifier. */
024      public static final String name = "StreamTrend";
025      /** Acceptable error in percentage. */
026      private double e = 0.1;
027      /** The minimal error in absolute value. */
028      private static final double MINI_ERROR = 0.1;
029    
030      /** If higher value means better. */
031      private boolean higherBetter;
032      
033      /** The threshold of high value. */
034      private double higherThreshold;
035      /** The threshold of low value. */
036      private double lowerThreshold;
037      /** If the condition scale with granularity. */
038      private boolean scaleWithGranularity;
039      
040      /**
041       * @param lowerThreshold the {@link lowerThreshold} to set.
042       * @param higherThreshold the {@link higherThreshold} to set.
043       * @param higherBetter the {@link higherBetter} to set.
044       * @param scaleWithGranularity the scaleWithGranularity to set.
045       */
046      public StreamTrendClassifier(double lowerThreshold, double higherThreshold,
047          boolean higherBetter, boolean scaleWithGranularity) {
048        this.higherBetter = higherBetter;
049        this.higherThreshold = higherThreshold;
050        this.lowerThreshold = lowerThreshold;
051        this.scaleWithGranularity = scaleWithGranularity;
052      }
053      
054      /**
055       * Parse the given MiniBarChart and produce a PortfolioCategory result.
056       * @param chart the input chart
057       * @return PortfolioCategory enumeration. 
058       */
059      public PortfolioCategory getStreamCategory(MiniBarChart chart) {
060        List<Double> stream = chart.streamData;
061        for (int i = 0; i < stream.size(); i++) {
062          if (stream.get(i).isNaN() || stream.get(i) < 0) {
063            stream.remove(i);
064            i--;
065          }
066        }
067        int size = stream.size();
068        if (size == 1) {
069          return PortfolioCategory.GOOD;
070        }
071        // the first valid value
072        double firstValue = stream.get(0);
073        // the last valid value
074        double lastValue = stream.get(size - 1);
075        // the acceptable error
076        double error = (firstValue + lastValue) / 2 * e;
077        if (error < MINI_ERROR) {
078          error = MINI_ERROR;
079        }
080    
081        //if it is stable trend with acceptable vibration, it will be stable.
082        int increasePoint = 0;
083        int decreasePoint = 0;
084        for (int i = 1; i < size; ++i) {
085          if (!isEqual(stream.get(i), stream.get(i - 1), error)) {
086            if (stream.get(i) < stream.get(i - 1)) {
087              decreasePoint++;
088            }
089            else if (stream.get(i) > stream.get(i - 1)) {
090              increasePoint++;
091            }
092          }
093        }
094        if (isEqual(lastValue, firstValue, error) && decreasePoint == 0 && increasePoint == 0) {
095          return PortfolioCategory.GOOD;
096        }
097        if (lastValue > firstValue && decreasePoint == 0) {
098          return getHighCategory();
099        }
100        if (lastValue < firstValue && increasePoint == 0) {
101          return getLowCategory();
102        }
103        return PortfolioCategory.AVERAGE;
104      }
105      
106      /**
107       * Return the category of higher values.
108       * @return the PortfolioCategory.
109       */
110      private PortfolioCategory getHighCategory() {
111        if (this.higherBetter) {
112          return PortfolioCategory.GOOD;
113        }
114        return PortfolioCategory.POOR;
115      }
116    
117      /**
118       * Return the category of lower values.
119       * @return the PortfolioCategory.
120       */
121      private PortfolioCategory getLowCategory() {
122        if (this.higherBetter) {
123          return PortfolioCategory.POOR;
124        }
125        return PortfolioCategory.GOOD;
126      }
127      
128      /**
129       * Tests if the two given value are equal.
130       * @param a the first value.
131       * @param b the second value.
132       * @param error the acceptable error.
133       * @return true if their difference is within acceptable error, otherwise false.
134       */
135      private boolean isEqual(double a, double b, double error) {
136        if (Math.abs(a - b) > error) {
137          return false;
138        }
139        return true;
140      }
141    
142      /**
143       * Return the panel for users to configure this stream trend classifer.
144       * User can customize higher, lower thresholds and if higher is better.
145       * @param id The Wicket component id.
146       * @return a Panel
147       */
148      public Panel getConfigurationPanel(String id) {
149        return new StreamTrendClassifierConfigurationPanel(id, this);
150      }
151    
152      /**
153       * Return the category of the given value.
154       * If value is higher than {@link higherThreshold}, {@link getHighCategory()} will be returned.
155       * If value is higher than {@link lowerThreshold}, {@link getLowCategory()} will be returned.
156       * Otherwise, will return AVERAGE.
157       * @param value the given value.
158       * @return a {@link PortfolioCategory} result
159       */
160      public PortfolioCategory getValueCategory(double value) {
161        if (value < 0) {
162          return PortfolioCategory.NA;
163        }
164        if (value >= this.higherThreshold) {
165          return getHighCategory();
166        }
167        if (value < this.lowerThreshold) {
168          return getLowCategory();
169        }
170        return PortfolioCategory.AVERAGE;
171        
172      }
173    
174      /**
175       * @param higherBetter the higherBetter to set
176       */
177      public void setHigherBetter(boolean higherBetter) {
178        this.higherBetter = higherBetter;
179      }
180    
181      /**
182       * @return the higherBetter
183       */
184      public boolean isHigherBetter() {
185        return higherBetter;
186      }
187    
188      /**
189       * @param higherThreshold the higherThreshold to set
190       */
191      public void setHigherThreshold(double higherThreshold) {
192        this.higherThreshold = higherThreshold;
193      }
194    
195      /**
196       * @return the higherThreshold
197       */
198      public double getHigherThreshold() {
199        return higherThreshold;
200      }
201    
202      /**
203       * @param lowerThreshold the lowerThreshold to set
204       */
205      public void setLowerThreshold(double lowerThreshold) {
206        this.lowerThreshold = lowerThreshold;
207      }
208    
209      /**
210       * @return the lowerThreshold
211       */
212      public double getLowerThreshold() {
213        return lowerThreshold;
214      }
215    
216      /**
217       * @return the name of this classifier.
218       */
219      public String getName() {
220        return name;
221      }
222    
223      /**
224       * Save classifier's setting into the given {@link Measure} instance.
225       * @param measure the given {@link Measure} instance
226       */
227      public void saveSetting(Measure measure) {
228        StreamTrendParameters param = new StreamTrendParameters();
229        param.setHigherBetter(higherBetter);
230        param.setLowerThresold(lowerThreshold);
231        param.setHigherThresold(higherThreshold);
232        param.setScaleWithGranularity(scaleWithGranularity);
233        measure.setStreamTrendParameters(param);
234      }
235    }