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 }