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    }