001 package org.hackystat.projectbrowser.page.projectportfolio.detailspanel.chart; 002 003 import java.io.Serializable; 004 import java.util.ArrayList; 005 import java.util.Arrays; 006 import java.util.Collections; 007 import java.util.List; 008 import java.util.logging.Logger; 009 import org.apache.wicket.PageParameters; 010 import org.hackystat.projectbrowser.googlechart.ChartType; 011 import org.hackystat.projectbrowser.googlechart.GoogleChart; 012 import org.hackystat.projectbrowser.page.projectportfolio.detailspanel. 013 PortfolioMeasureConfiguration; 014 import org.hackystat.projectbrowser.page.projectportfolio.detailspanel.ProjectPortfolioDataModel; 015 import org.hackystat.telemetry.service.resource.chart.jaxb.TelemetryPoint; 016 import org.hackystat.telemetry.service.resource.chart.jaxb.TelemetryStream; 017 import org.hackystat.telemetry.service.resource.chart.jaxb.YAxis; 018 019 /** 020 * A mini bar chart. 021 * Use GoogleChart to generate the chart. 022 * @author Shaoxuan Zhang 023 * 024 */ 025 public class MiniBarChart implements Serializable { 026 027 /** Support serialization. */ 028 private static final long serialVersionUID = 1213447209533926430L; 029 030 /** The stream of this chart. */ 031 protected List<Double> streamData; 032 /** The list of original streams. */ 033 protected List<List<Double>> streams = new ArrayList<List<Double>>(); 034 /** The granularity of the data. */ 035 protected String granularity = "Day"; 036 /** The index of the last valid value. */ 037 private int lastValidIndex = -1; 038 /** The latest value of the stream. */ 039 private double latestValue; 040 /** The width of a bar. */ 041 public static final int BAR_WIDTH = 5; 042 /** The max width of the chart. */ 043 public static final int MAX_WIDTH = 50; 044 /** The height of the chart. */ 045 public static final int CHART_HEIGHT = 15; 046 047 /** The PageParameters to construct the chart link to telemetry page. */ 048 private PageParameters telemetryPageParameters; 049 /** The PageParameters to construct the value link to dpd page. */ 050 //private PageParameters dpdPageParameters; 051 052 /** The configuration of this chart. */ 053 private final PortfolioMeasureConfiguration configuration; 054 055 056 /** 057 * @param streams The stream of this chart. 058 * @param configuration The configuration of this chart. 059 * @param granularity The granularity of the data. 060 */ 061 public MiniBarChart(List<TelemetryStream> streams, PortfolioMeasureConfiguration configuration, 062 String granularity) { 063 if (streams != null) { 064 for (TelemetryStream stream : streams) { 065 this.streams.add(getStreamData(stream)); 066 } 067 if (streams.size() > 1) { 068 this.streamData = getStreamData(mergeStream(streams, configuration.getMerge())); 069 } 070 else { 071 this.streamData = getStreamData(streams.get(0)); 072 } 073 for (int i = streamData.size() - 1; i >= 0; --i) { 074 this.latestValue = streamData.get(i); 075 if (latestValue >= 0) { 076 this.lastValidIndex = i; 077 break; 078 } 079 } 080 } 081 this.configuration = configuration; 082 } 083 084 /** 085 * Merge the streams according to the merge parameter. 086 * @param telemetryStreams the input streams. 087 * @param merge the method to merge. 088 * @return a TelemetryStream. 089 */ 090 private TelemetryStream mergeStream(List<TelemetryStream> telemetryStreams, String merge) { 091 //construct the target instance with the first stream. 092 TelemetryStream telemetryStream = new TelemetryStream(); 093 telemetryStream.setYAxis(telemetryStreams.get(0).getYAxis()); 094 telemetryStream.setName(telemetryStreams.get(0).getName()); 095 //check y axis, has to be all the same. Otherwise will give out empty stream. 096 boolean allMatch = true; 097 for (TelemetryStream stream : telemetryStreams) { 098 if (!streamsEqual(stream.getYAxis(), telemetryStream.getYAxis())) { 099 allMatch = false; 100 Logger logger = ProjectPortfolioDataModel.getLogger(); 101 logger.severe("YAxis: " + stream.getYAxis().getName() + " in stream: " + stream.getName() 102 + " is not match to YAxis: " + telemetryStream.getYAxis().getName() + " in stream :" 103 + telemetryStream.getName()); 104 } 105 } 106 if (allMatch) { 107 //combine streams' data. 108 List<TelemetryPoint> points = new ArrayList<TelemetryPoint>(); 109 points.addAll(telemetryStreams.get(0).getTelemetryPoint()); 110 for (int i = 0; i < points.size(); i++) { 111 List<Double> doubleValues = new ArrayList<Double>(); 112 //get all valid values in the same point. 113 for (int j = 0; j < telemetryStreams.size(); ++j) { 114 String stringValue = telemetryStreams.get(j).getTelemetryPoint().get(i).getValue(); 115 if (stringValue != null) { 116 doubleValues.add(Double.valueOf(stringValue)); 117 } 118 } 119 120 points.get(i).setValue(null); 121 //if no valid data, the value of this point will be null 122 if (!doubleValues.isEmpty()) { 123 if ("sum".equals(merge)) { 124 Double value = 0.0; 125 for (Double v : doubleValues) { 126 value += v; 127 } 128 points.get(i).setValue(value.toString()); 129 } 130 else if ("avg".equals(merge)) { 131 Double value = 0.0; 132 for (Double v : doubleValues) { 133 value += v; 134 } 135 value /= telemetryStreams.size(); 136 points.get(i).setValue(value.toString()); 137 } 138 else if ("min".equals(merge)) { 139 points.get(i).setValue(Collections.min(doubleValues).toString()); 140 } 141 else if ("max".equals(merge)) { 142 points.get(i).setValue(Collections.max(doubleValues).toString()); 143 } 144 } 145 } 146 telemetryStream.getTelemetryPoint().addAll(points); 147 } 148 return telemetryStream; 149 } 150 151 /** 152 * Compare the two given YAxis objects. 153 * @param axis1 the first YAxis. 154 * @param axis2 the second YAxis. 155 * @return true if they are equal. 156 */ 157 private boolean streamsEqual(YAxis axis1, YAxis axis2) { 158 return eqauls(axis1.getName(), axis2.getName()) && eqauls(axis1.getUnits(), axis2.getUnits()) && 159 eqauls(axis1.getNumberType(), axis2.getNumberType()) && 160 eqauls(axis1.getLowerBound(), axis2.getLowerBound()) && 161 eqauls(axis1.getUpperBound(), axis2.getUpperBound()); 162 } 163 164 /** 165 * Compare the two given objects. If both objects are null, they are considered equal. 166 * @param o1 the first object 167 * @param o2 the second object 168 * @return true if the two objects are equal, otherwise false. 169 */ 170 private boolean eqauls(Object o1, Object o2) { 171 if (o1 == null && o2 == null) { 172 return true; 173 } 174 if (o1 == null || o2 == null) { 175 return false; 176 } 177 return o1.equals(o2); 178 } 179 180 /** 181 * Return a List of Double from the given telemetry stream. 182 * @param stream a TelemetryStream 183 * @return the list of data of this stream 184 */ 185 public static final List<Double> getStreamData(TelemetryStream stream) { 186 List<Double> streamData = new ArrayList<Double>(); 187 for (TelemetryPoint point : stream.getTelemetryPoint()) { 188 if (point.getValue() == null) { 189 streamData.add(-1.0); 190 } 191 else { 192 Double value = Double.valueOf(point.getValue()); 193 if (value.isNaN()) { 194 value = -2.0; 195 } 196 streamData.add(value); 197 } 198 } 199 return streamData; 200 } 201 202 /** 203 * @return a String URL that represents this chart. 204 */ 205 public String getImageUrl() { 206 double max = Collections.max(streamData); 207 //return empty string when the chart is empty. 208 if (max < 0) { 209 return ""; 210 } 211 if (max < 1) { 212 max = 1; 213 } 214 GoogleChart googleChart; 215 int chartSize = streamData.size(); 216 int width = chartSize * BAR_WIDTH; 217 width = (width > MAX_WIDTH) ? MAX_WIDTH : width; 218 googleChart = new GoogleChart(ChartType.VERTICAL_SERIES_BAR, width, CHART_HEIGHT); 219 googleChart.setBarChartSize((width - streamData.size()) / streamData.size(), 1, 0); 220 googleChart.getChartData().add(streamData); 221 googleChart.getChartDataScale(). 222 add(Arrays.asList(new Double[]{ 0.0, max})); 223 googleChart.getColors().add(this.getChartColor()); 224 googleChart.setBackgroundColor(this.configuration.getDataModel().getBackgroundColor()); 225 return googleChart.getUrl(); 226 } 227 228 229 /** 230 * @return the color 231 */ 232 public String getChartColor() { 233 return configuration.getChartColor(this); 234 } 235 236 237 /** 238 * @return the latestValue 239 */ 240 public double getLatestValue() { 241 return latestValue; 242 } 243 244 /** 245 * Return the color for the latest value. 246 * @return the color 247 */ 248 public String getValueColor() { 249 return configuration.getValueColor(this.getLatestValue()); 250 } 251 252 /** 253 * @param telemetryPageParameters the telemetryPageParameters to set 254 */ 255 public void setTelemetryPageParameters(PageParameters telemetryPageParameters) { 256 this.telemetryPageParameters = telemetryPageParameters; 257 } 258 259 /** 260 * @return the telemetryPageParameters 261 */ 262 public PageParameters getTelemetryPageParameters() { 263 return telemetryPageParameters; 264 } 265 266 /** 267 * @return the configuration 268 */ 269 public PortfolioMeasureConfiguration getConfiguration() { 270 return configuration; 271 } 272 273 /** 274 * @return the lastValidIndex 275 */ 276 public int getLastValidIndex() { 277 return lastValidIndex; 278 } 279 280 }