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 }