001    package org.hackystat.projectbrowser.page.trajectory.datapanel;
002    
003    import java.io.Serializable;
004    import java.text.DecimalFormat;
005    import java.text.NumberFormat;
006    import java.text.SimpleDateFormat;
007    import java.util.ArrayList;
008    import java.util.Collections;
009    import java.util.Date;
010    import java.util.HashMap;
011    import java.util.List;
012    import java.util.Locale;
013    import java.util.Map;
014    import java.util.logging.Level;
015    import java.util.logging.Logger;
016    
017    import org.apache.wicket.Application;
018    import org.apache.wicket.model.IModel;
019    import org.hackystat.projectbrowser.ProjectBrowserApplication;
020    import org.hackystat.projectbrowser.ProjectBrowserSession;
021    import org.hackystat.projectbrowser.googlechart.ChartType;
022    import org.hackystat.projectbrowser.googlechart.GoogleChart;
023    import org.hackystat.projectbrowser.page.ProjectBrowserBasePage;
024    import org.hackystat.projectbrowser.page.loadingprocesspanel.Processable;
025    import org.hackystat.projectbrowser.page.trajectory.ProjectRecord;
026    import org.hackystat.projectbrowser.page.trajectory.dtw.DTWAlignment;
027    import org.hackystat.projectbrowser.page.trajectory.dtw.DTWException;
028    import org.hackystat.projectbrowser.page.trajectory.dtw.DTWFactory;
029    import org.hackystat.projectbrowser.page.trajectory.dtw.EuclideanDistance;
030    import org.hackystat.projectbrowser.page.trajectory.dtw.step.SymmetricStepFunction;
031    import org.hackystat.sensorbase.resource.projects.jaxb.Project;
032    import org.hackystat.telemetry.service.client.TelemetryClient;
033    import org.hackystat.telemetry.service.client.TelemetryClientException;
034    import org.hackystat.telemetry.service.resource.chart.jaxb.TelemetryPoint;
035    import org.hackystat.telemetry.service.resource.chart.jaxb.TelemetryStream;
036    import org.hackystat.utilities.logger.HackystatLogger;
037    import org.hackystat.utilities.tstamp.Tstamp;
038    
039    /**
040     * Data model to hold state of the telemetry chart.
041     * 
042     * @author Shaoxuan Zhang, Pavel Senin
043     */
044    public class TrajectoryChartDataModel implements Serializable, Processable {
045      /** Support serialization. */
046      private static final long serialVersionUID = 1L;
047      /** The granularity of the chart. Either Day, Week, or Month. */
048      private String granularity = "Day";
049    
050      /** The project this user has selected. */
051      private Map<Project, String> projectCharts = new HashMap<Project, String>();
052    
053      /** The projects this user has selected. */
054      private ProjectRecord selectedProject1 = new ProjectRecord();
055      // /** The start date this user has selected. */
056      // private long project1StartDate = 0;
057      // /** The end date this user has selected. */
058      // private long project1EndDate = 0;
059      /** The total length of the timeseries within the project1. */
060      private int selectedProject1StreamLength;
061    
062      /** The projects this user has selected. */
063      private ProjectRecord selectedProject2 = new ProjectRecord();
064      // /** The start date this user has selected. */
065      // private long project2StartDate = 0;
066      // /** The end date this user has selected. */
067      // private long project2EndDate = 0;
068      /** The total length of the timeseries within the project2. */
069      private int selectedProject2StreamLength;
070    
071      /** The analysis this user has selected. */
072      private String telemetryName = null;
073      /** The parameters for this telemetry chart. */
074      private List<String> parameters = new ArrayList<String>();
075      /** Store the data retrieved from telemetry service. */
076      private Map<Project, List<SelectableTrajectoryStream>> projectStreamData = 
077        new HashMap<Project, List<SelectableTrajectoryStream>>();
078      /** Chart with selected project streams. */
079      private String selectedChart = null;
080      /** the width of this chart. */
081      private int width = 800;
082      /** the height of this chart. */
083      private int height = 300;
084      /** state of data loading process. */
085      private volatile boolean inProcess = false;
086      /** result of data loading. */
087      private volatile boolean complete = false;
088      /** message to display when data loading is in process. */
089      private String processingMessage = "";
090      /** host of the telemetry host. */
091      private String telemetryHost;
092      /** email of the user. */
093      private String email;
094      /** password of the user. */
095      private String password;
096      private int maxStreamLength;
097    
098      /** Chart with selected project streams. */
099      private String normalizedTSChart = null;
100    
101      /** Chart with selected project streams. */
102      private String dtwChart = null;
103    
104      private static final String CR = "\n";
105      private static final String IDNT = "                ";
106      private static final String IDNT2 = "                  ";
107      private static final String MARK = "[DEBUG] ";
108    
109      private String dtwStatistics = "dtw Placeholder";
110      private String trajectoryDataWarningMessage = "";
111    
112      /**
113       * Set all the parameters for the current chart.
114       * 
115       * @param selectedProject1 The trajectory project1.
116       * @param selectedProject2 The trajectory project2.
117       * @param telemetryName The selected telemetry stream name.
118       * @param granularity The seleceted granularity.
119       * @param parameters Additional telemetry parameters.
120       * 
121       */
122      public void setModel(ProjectRecord selectedProject1, ProjectRecord selectedProject2,
123          String telemetryName, String granularity, List<IModel> parameters) {
124    
125        // okay, going to get some updates here
126        this.inProcess = true;
127        this.complete = false;
128    
129        // but log first what we got from that form
130        getLogger().log(
131            Level.FINER,
132            MARK + "TrajectoryChartModelParameters: setModel()" + CR + IDNT + "selectedProject1: "
133                + selectedProject1.getProject().getName() + CR + IDNT + IDNT2 + "startDate: "
134                + selectedProject1.getStartDate() + CR + IDNT + IDNT2 + "endDate: "
135                + selectedProject1.getEndDate() + CR + IDNT + IDNT2 + "indent: "
136                + selectedProject1.getIndent() + CR + IDNT + "selectedProject2: "
137                + selectedProject2.getProject().getName() + CR + IDNT + IDNT2 + "startDate: "
138                + selectedProject2.getStartDate() + CR + IDNT + IDNT2 + "endDate: "
139                + selectedProject2.getEndDate() + CR + IDNT + IDNT2 + "indent: "
140                + selectedProject2.getIndent() + CR + IDNT + "telemetry: " + telemetryName + CR + IDNT
141                + "granularity: " + granularity + CR + IDNT + "parameters: " + CR
142                + parametersToLog(parameters));
143    
144        // clear the process message and get sensorbase config from session
145        this.processingMessage = "";
146        this.telemetryHost = ((ProjectBrowserApplication) ProjectBrowserSession.get().getApplication())
147            .getTelemetryHost();
148        this.email = ProjectBrowserSession.get().getEmail();
149        this.password = ProjectBrowserSession.get().getPassword();
150    
151        // reset and set parameters
152        this.selectedProject1 = selectedProject1;
153        this.selectedProject2 = selectedProject2;
154        this.granularity = granularity;
155        this.telemetryName = telemetryName;
156        this.projectCharts.clear();
157        this.projectStreamData.clear();
158        this.parameters.clear();
159        for (IModel model : parameters) {
160          if (model.getObject() != null) {
161            this.parameters.add(model.getObject().toString());
162          }
163        }
164        this.selectedChart = null;
165        this.normalizedTSChart = null;
166        this.dtwChart = null;
167      }
168    
169      /**
170       * Load data from Hackystat service.
171       */
172      public void loadData() {
173        Logger logger = HackystatLogger.getLogger("org.hackystat.projectbrowser", "projectbrowser");
174        this.inProcess = true;
175        this.complete = false;
176        logger.log(Level.FINER, MARK + "TrajectoryChartDataModel: loadData() INPROCESS: "
177            + isInProcess() + ", COMPLETE: " + isComplete());
178        this.processingMessage = "Retrieving telemetry chart <" + getTelemetryName()
179            + "> from Hackystat Telemetry service.\n";
180        try {
181          TelemetryClient client = new TelemetryClient(this.telemetryHost, this.email, this.password);
182    
183          // Loading data for the project #1
184          //
185          List<SelectableTrajectoryStream> streamList = new ArrayList<SelectableTrajectoryStream>();
186          Project project = selectedProject1.getProject();
187    
188          this.processingMessage += "Retrieving data for project: " + project.getName() + ".\n";
189          logger.log(Level.FINER, MARK + "Retrieving chart <" + getTelemetryName()
190              + "> data from the telemetry: " + CR + IDNT + "project: "
191              + selectedProject1.getProject().getName() + CR + IDNT + "start: "
192              + Tstamp.makeTimestamp(selectedProject1.getStartDate().getTime()) + CR + IDNT + "end: "
193              + Tstamp.makeTimestamp(selectedProject1.getEndDate().getTime()) + CR + IDNT
194              + "parameters: " + this.getParameterAsString());
195    
196          List<TelemetryStream> streams = client.getChart(this.getTelemetryName(), project.getOwner(),
197              project.getName(), granularity,
198              Tstamp.makeTimestamp(selectedProject1.getStartDate().getTime()),
199              Tstamp.makeTimestamp(selectedProject1.getEndDate().getTime()),
200              this.getParameterAsString()).getTelemetryStream();
201    
202          for (TelemetryStream stream : streams) {
203            streamList.add(new SelectableTrajectoryStream(stream, selectedProject1.getIndent()));
204          }
205          this.projectStreamData.put(project, streamList);
206    
207          if (streams.isEmpty()) {
208            selectedProject1StreamLength = -1;
209          }
210          else {
211            selectedProject1StreamLength = streams.get(0).getTelemetryPoint().size();
212            selectedProject1.setStreamColor(GoogleChart.getNextJetColor());
213          }
214          logger.log(Level.FINER, MARK + "Finished retrieving chart <" + getTelemetryName()
215              + "> for project: " + project.getName() + " streams retrieved: " + streams.size()
216              + " length: " + selectedProject1StreamLength);
217    
218          // Loading data for the project #2
219          //
220          streamList = new ArrayList<SelectableTrajectoryStream>();
221          project = selectedProject2.getProject();
222    
223          this.processingMessage += "Retrieving data for project: " + project.getName() + ".\n";
224          logger.log(Level.FINER, MARK + "Retrieving chart <" + getTelemetryName()
225              + "> data from the telemetry: " + CR + IDNT + "project: "
226              + selectedProject2.getProject().getName() + CR + IDNT + "start: "
227              + Tstamp.makeTimestamp(selectedProject2.getStartDate().getTime()) + CR + IDNT + "end: "
228              + Tstamp.makeTimestamp(selectedProject2.getEndDate().getTime()) + CR + IDNT
229              + "parameters: " + this.getParameterAsString());
230    
231          streams = client.getChart(this.getTelemetryName(), project.getOwner(), project.getName(),
232              granularity, Tstamp.makeTimestamp(selectedProject2.getStartDate().getTime()),
233              Tstamp.makeTimestamp(selectedProject2.getEndDate().getTime()),
234              this.getParameterAsString()).getTelemetryStream();
235    
236          for (TelemetryStream stream : streams) {
237            streamList.add(new SelectableTrajectoryStream(stream));
238          }
239          this.projectStreamData.put(project, streamList);
240    
241          if (streams.isEmpty()) {
242            selectedProject2StreamLength = -1;
243          }
244          else {
245            selectedProject2StreamLength = streams.get(0).getTelemetryPoint().size();
246            selectedProject2.setStreamColor(GoogleChart.getNextJetColor());
247          }
248          logger.log(Level.FINER, MARK + "Finished retrieving chart <" + getTelemetryName()
249              + "> for project: " + project.getName() + " streams retrieved: " + streams.size()
250              + " length: " + selectedProject2StreamLength);
251    
252          // save the maximal stream length
253          //
254          maxStreamLength = Math.max(selectedProject1StreamLength + this.selectedProject1.getIndent(),
255              selectedProject2StreamLength + this.selectedProject2.getIndent());
256          logger.log(Level.FINER, MARK + "the longest stream is " + maxStreamLength + " points.");
257          
258          if (selectedProject1StreamLength != selectedProject2StreamLength) {
259            this.trajectoryDataWarningMessage = "You've selected streams of different length, "
260                + "the open-endede DTW is not implemented yet.";
261          }
262    
263        }
264        catch (TelemetryClientException e) {
265          String errorMessage = "Errors when retrieving " + getTelemetryName() + " telemetry data: "
266              + e.getMessage() + ". Please try again.";
267          this.processingMessage += errorMessage + "\n";
268    
269          logger.warning(errorMessage);
270          this.complete = false;
271          this.inProcess = false;
272          return;
273        }
274    
275        this.inProcess = false;
276        this.complete = true;
277        this.processingMessage += "All done.\n";
278        logger.log(Level.FINER, "[DEBUG] TrajectoryChartDataModel: loadData() INPROCESS: "
279            + isInProcess() + ", COMPLETE: " + isComplete());
280    
281      }
282    
283      /**
284       * Cancel the data loading process.
285       */
286      public void cancelDataLoading() {
287        this.processingMessage += "Process Cancelled.\n";
288        this.inProcess = false;
289      }
290    
291      /**
292       * Returns the start date in yyyy-MM-dd format.
293       * 
294       * @return The date as a simple string.
295       */
296      public String getProject1StartDateString() {
297        SimpleDateFormat format = new SimpleDateFormat(ProjectBrowserBasePage.DATA_FORMAT,
298            Locale.ENGLISH);
299        return format.format(new Date(this.selectedProject1.getStartDate().getTime()));
300      }
301    
302      /**
303       * Returns the end date in yyyy-MM-dd format.
304       * 
305       * @return The date as a simple string.
306       */
307      public String getProject1EndDateString() {
308        SimpleDateFormat format = new SimpleDateFormat(ProjectBrowserBasePage.DATA_FORMAT,
309            Locale.ENGLISH);
310        return format.format(new Date(this.selectedProject1.getEndDate().getTime()));
311      }
312    
313      /**
314       * Returns the start date in yyyy-MM-dd format.
315       * 
316       * @return The date as a simple string.
317       */
318      public String getProject2StartDateString() {
319        SimpleDateFormat format = new SimpleDateFormat(ProjectBrowserBasePage.DATA_FORMAT,
320            Locale.ENGLISH);
321        return format.format(new Date(this.selectedProject2.getStartDate().getTime()));
322      }
323    
324      /**
325       * Returns the end date in yyyy-MM-dd format.
326       * 
327       * @return The date as a simple string.
328       */
329      public String getProject2EndDateString() {
330        SimpleDateFormat format = new SimpleDateFormat(ProjectBrowserBasePage.DATA_FORMAT,
331            Locale.ENGLISH);
332        return format.format(new Date(this.selectedProject2.getEndDate().getTime()));
333      }
334    
335      /**
336       * @return the telemetryName
337       */
338      public String getTelemetryName() {
339        return telemetryName;
340      }
341    
342      /**
343       * Returns true if this model does not contain any data.
344       * 
345       * @return True if no data.
346       */
347      public boolean isEmpty() {
348        return (null == this.selectedProject1) && (null == this.selectedProject2);
349      }
350    
351      /**
352       * Return the list of TelemetryStream associated with the given project.
353       * 
354       * @param project the given project.
355       * @return the list of TelemetryStream.
356       */
357      public List<SelectableTrajectoryStream> getTrajectoryStream(Project project) {
358        List<SelectableTrajectoryStream> streamList = this.projectStreamData.get(project);
359        if (streamList == null) {
360          streamList = new ArrayList<SelectableTrajectoryStream>();
361        }
362        return streamList;
363      }
364    
365      /**
366       * @return the selectedChart
367       */
368      public String getSelectedChart() {
369        getLogger()
370            .log(Level.FINER, MARK + "TrajectoryChartDataModel: getting selected TelemetryChart");
371        return selectedChart;
372      }
373    
374      /**
375       * @return true if the chart is empty.
376       */
377      public boolean isChartEmpty() {
378        return selectedChart == null || "".equals(selectedChart);
379      }
380    
381      /**
382       * @return the selectedChart
383       */
384      public String getNormalizedTSChart() {
385        getLogger().log(Level.FINER, MARK + "TrajectoryChartDataModel, getting Normalized TS chart");
386        return normalizedTSChart;
387      }
388    
389      /**
390       * @return true if the chart is empty.
391       */
392      public boolean isNormalizedTSChartEmpty() {
393        return normalizedTSChart == null || "".equals(normalizedTSChart);
394      }
395    
396      /**
397       * @return the selectedChart
398       */
399      public String getDTWChart() {
400        getLogger().log(Level.FINER, MARK + "TrajectoryChartDataModel, getting DTW chart");
401        return dtwChart;
402      }
403    
404      /**
405       * @return true if the chart is empty.
406       */
407      public boolean isDTWChartEmpty() {
408        return dtwChart == null || "".equals(dtwChart);
409      }
410    
411      /**
412       * update the selectedChart.
413       * 
414       * @return true if the chart is successfully updated.
415       */
416      public boolean updateSelectedChart() {
417        getLogger().log(Level.FINER, MARK + "TelemetryChart: updating");
418        // Since we have only two projects here let's plot them
419        //
420        List<SelectableTrajectoryStream> streams = new ArrayList<SelectableTrajectoryStream>();
421        boolean dashed = false;
422        ArrayList<ProjectRecord> rec = new ArrayList<ProjectRecord>();
423        rec.add(selectedProject1);
424        rec.add(selectedProject2);
425        for (ProjectRecord projectRec : rec) {
426          Project project = projectRec.getProject();
427          getLogger()
428              .log(Level.FINER, MARK + "TelemetryChart: processing project " + project.getName());
429          List<SelectableTrajectoryStream> streamList = this.getTrajectoryStream(project);
430          getLogger().log(Level.FINER,
431              MARK + "TelemetryChart: found " + streamList.size() + " stream(s)."); // NOPMD
432          String projectMarker = null;
433          double thickness = 2;
434          double lineLength = 1;
435          double blankLength = 0;
436          boolean isLineStylePicked = false;
437          for (SelectableTrajectoryStream stream : streamList) {
438            getLogger().log(
439                Level.FINER,
440                MARK + "TelemetryChart: processing stream " + stream.getTelemetryStream().getName()
441                    + ", selected: " + stream.isSelected() + ", length: "
442                    + stream.getStreamData().size());
443            if (stream.isSelected()) {
444              if (projectMarker == null) {
445                projectMarker = GoogleChart.getNextMarker();
446              }
447              if (!isLineStylePicked) {
448                isLineStylePicked = true;
449                if (dashed) {
450                  lineLength = 6;
451                  blankLength = 3;
452                }
453                dashed = !dashed;
454              }
455              stream.setMarker(projectMarker);
456              stream.setThickness(thickness);
457              stream.setLineLength(lineLength);
458              stream.setBlankLength(blankLength);
459              stream.setIndent(projectRec.getIndent());
460              streams.add(stream);
461              stream.setColor(projectRec.getStreamColor());
462            }
463            else {
464              stream.setColor("");
465              stream.setMarker("");
466            }
467          }
468        }
469        if (streams.isEmpty()) {
470          selectedChart = "";
471          return false;
472        }
473        selectedChart = this.getTelemetryChartURL(streams);
474        getLogger().log(Level.FINER, MARK + "TelemetryChart URL: " + selectedChart);
475        return true;
476      }
477    
478      /**
479       * Extract the time series from the selectedChart, normalize those and chart them.
480       * 
481       * @return true if the chart is successfully updated.
482       */
483      public boolean updateNormalizedTSChart() {
484        getLogger().log(Level.FINER, MARK + "Normalized TS Chart: updating.");
485        boolean dashed = false;
486        // Since we have only two projects here let's plot them
487        //
488        // create the list of streams we are about to plot
489        List<SelectableTrajectoryStream> streams = new ArrayList<SelectableTrajectoryStream>();
490        //
491        // make sure we do know which projects we are working on
492        ArrayList<ProjectRecord> rec = new ArrayList<ProjectRecord>();
493        rec.add(selectedProject1);
494        rec.add(selectedProject2);
495        //
496        // now, iterate over this projects and make chart happen
497        for (ProjectRecord projectRec : rec) {
498          Project project = projectRec.getProject();
499          getLogger().log(Level.FINER,
500              MARK + "Normalized TS Chart: processing project " + project.getName());
501          List<SelectableTrajectoryStream> streamList = this.getTrajectoryStream(project);
502          getLogger().log(Level.FINER,
503              MARK + "Normalized TS Chart: found " + streamList.size() + " stream(s).");
504          String projectMarker = null;
505          double thickness = 2;
506          double lineLength = 1;
507          double blankLength = 0;
508          boolean isLineStylePicked = false;
509          for (SelectableTrajectoryStream stream : streamList) {
510            getLogger().log(
511                Level.FINER,
512                MARK + "Normalized TS Chart: processing stream "
513                    + stream.getTelemetryStream().getName() + ", selected: " + stream.isSelected()
514                    + ", length: " + stream.getStreamData().size());
515            if (stream.isSelected()) {
516              if (projectMarker == null) {
517                projectMarker = GoogleChart.getNextMarker();
518              }
519              if (!isLineStylePicked) {
520                isLineStylePicked = true;
521                if (dashed) {
522                  lineLength = 6;
523                  blankLength = 3;
524                }
525                dashed = !dashed;
526              }
527              stream.setMarker(projectMarker);
528              stream.setThickness(thickness);
529              stream.setLineLength(lineLength);
530              stream.setBlankLength(blankLength);
531              stream.setIndent(projectRec.getIndent());
532              streams.add(stream);
533            }
534            else {
535              stream.setColor("");
536              stream.setMarker("");
537            }
538          }
539        }
540        if (streams.isEmpty()) {
541          normalizedTSChart = "";
542          return false;
543        }
544        normalizedTSChart = this.getNormalizedTSChartURL(streams);
545        getLogger().log(Level.FINER, MARK + "Normalized TS Chart URL: " + normalizedTSChart);
546        return true;
547      }
548    
549      /**
550       * Extract the time series from the selectedChart, normalize those and chart them.
551       * 
552       * @return true if the chart is successfully updated.
553       */
554      public boolean updateDTWChart() {
555        getLogger().log(Level.FINER, MARK + "DTW Chart: updating.");
556        boolean dashed = false;
557        // Since we have only two projects here let's plot them
558        //
559        // create the list of streams we are about to plot
560        List<SelectableTrajectoryStream> streams = new ArrayList<SelectableTrajectoryStream>();
561        //
562        // make sure we do know which projects we are working on
563        ArrayList<ProjectRecord> rec = new ArrayList<ProjectRecord>();
564        rec.add(selectedProject1);
565        rec.add(selectedProject2);
566        //
567        // now, iterate over this projects and make chart happen
568        for (ProjectRecord projectRec : rec) {
569          Project project = projectRec.getProject();
570          getLogger().log(Level.FINER, MARK + "DTW Chart: processing project " + project.getName());
571          List<SelectableTrajectoryStream> streamList = this.getTrajectoryStream(project);
572          getLogger().log(Level.FINER, MARK + "DTW Chart: found " + streamList.size() + " stream(s).");
573          String projectMarker = null;
574          double thickness = 2;
575          double lineLength = 1;
576          double blankLength = 0;
577          boolean isLineStylePicked = false;
578          for (SelectableTrajectoryStream stream : streamList) {
579            getLogger().log(
580                Level.FINER,
581                MARK + "DTW Chart: processing stream " + stream.getTelemetryStream().getName()
582                    + ", selected: " + stream.isSelected() + ", length: "
583                    + stream.getStreamData().size());
584            if (stream.isSelected()) {
585              if (projectMarker == null) {
586                projectMarker = GoogleChart.getNextMarker();
587              }
588              if (!isLineStylePicked) {
589                isLineStylePicked = true;
590                if (dashed) {
591                  lineLength = 6;
592                  blankLength = 3;
593                }
594                dashed = !dashed;
595              }
596              stream.setMarker(projectMarker);
597              stream.setThickness(thickness);
598              stream.setLineLength(lineLength);
599              stream.setBlankLength(blankLength);
600              stream.setIndent(projectRec.getIndent());
601              streams.add(stream);
602            }
603            else {
604              stream.setColor("");
605              stream.setMarker("");
606            }
607          }
608        }
609        if (streams.isEmpty()) {
610          dtwChart = "";
611          return false;
612        }
613        dtwChart = this.getDTWChartURL(streams);
614        getLogger().log(Level.FINER, MARK + "DTW chart URL:" + dtwChart);
615        return true;
616      }
617    
618      /**
619       * Get the DTW statistics.
620       * 
621       * @return DTW statistics.
622       */
623      public String getDTWStatistics() {
624        return this.dtwStatistics;
625      }
626    
627      /**
628       * Return the google chart url that present all streams within the given list.
629       * 
630       * @param streams the given stream list.
631       * @return the URL string of the chart.
632       */
633      public String getTelemetryChartURL(List<SelectableTrajectoryStream> streams) {
634        getLogger().log(Level.FINER,
635            MARK + "TelemetryChart: getting url for " + streams.size() + " stream(s).");
636        GoogleChart googleChart = new GoogleChart(ChartType.LINE, this.width, this.height);
637        TrajectoryStreamYAxis streamAxis = null;
638    
639        // make up the Y axis for all streams
640        for (SelectableTrajectoryStream stream : streams) {
641          if (stream.isEmpty()) {
642            continue;
643          }
644          getLogger().log(
645              Level.FINER,
646              MARK + "TelemetryChart: processing stream " + stream.getTelemetryStream().getName()
647                  + ", length " + stream.getTelemetryStream().getTelemetryPoint().size());
648          double streamMax = stream.getMaximum();
649          double streamMin = stream.getMinimum();
650          String streamUnitName = stream.getUnitName();
651          if (streamAxis == null) {
652            streamAxis = newYAxis(streamUnitName, streamMax, streamMin);
653          }
654          else {
655            double axisMax = streamAxis.getMaximum();
656            double axisMin = streamAxis.getMinimum();
657            streamAxis.setMaximum((axisMax > streamMax) ? axisMax : streamMax);
658            streamAxis.setMinimum((axisMin < streamMin) ? axisMin : streamMin);
659          }
660          // stream.setColor(GoogleChart.getNextJetColor());
661        }
662        // add the y axis and extend the range if it contains only one horizontal line.
663        String axisType = "r";
664        if (googleChart.isYAxisEmpty()) {
665          axisType = "y";
666        }
667        List<Double> rangeList = new ArrayList<Double>();
668        // extend the range of the axis if it contains only one vertical straight line.
669        if (streamAxis.getMaximum() - streamAxis.getMinimum() < 0.1) {
670          streamAxis.setMaximum(streamAxis.getMaximum() + 1.0);
671          streamAxis.setMinimum(streamAxis.getMinimum() - 1.0);
672        }
673        rangeList.add(streamAxis.getMinimum());
674        rangeList.add(streamAxis.getMaximum());
675        // add the axis to the chart.
676        googleChart.addAxisRange(axisType, rangeList, streamAxis.getColor());
677    
678        // add streams to the chart.
679        for (int i = 0; i < streams.size(); ++i) {
680          SelectableTrajectoryStream stream = streams.get(i);
681          if (!stream.isEmpty()) {
682            this.addStreamToChart(stream, streamAxis.getMinimum(), streamAxis.getMaximum(),
683                maxStreamLength, googleChart);
684          }
685        }
686        // add the x axis, X axis is a list of markers
687        if (!streams.isEmpty()) {
688          googleChart.addAxisLabel("x", getMarkersList(), "");
689        }
690        return googleChart.getUrl();
691      }
692    
693      /**
694       * Return the google chart url that present normalized streams.
695       * 
696       * @param streams the given stream list.
697       * @return the URL string of the chart.
698       */
699      public String getNormalizedTSChartURL(List<SelectableTrajectoryStream> streams) {
700        getLogger().log(Level.FINER,
701            MARK + "Normalized TS: getting url for " + streams.size() + " stream(s).");
702        GoogleChart googleChart = new GoogleChart(ChartType.LINE, this.width, this.height);
703        TrajectoryStreamYAxis streamAxis = null;
704    
705        // okay here we are expecting to see two streams in the input array - get them normalized.
706        double streamMin = Double.MAX_VALUE;
707        double streamMax = Double.MIN_VALUE;
708        for (SelectableTrajectoryStream stream : streams) {
709          if (!stream.isEmpty()) {
710            List<Double> normalizedStreamData = stream.getNormalizedStreamData();
711            if (streamMin > Collections.min(normalizedStreamData)) {
712              streamMin = Collections.min(normalizedStreamData);
713            }
714            if (streamMax < Collections.max(normalizedStreamData)) {
715              streamMax = Collections.max(normalizedStreamData);
716            }
717          }
718        }
719        getLogger().log(Level.FINER,
720            MARK + "Normalized TS: min: " + streamMin + ", maximum: " + streamMax);
721    
722        List<List<Double>> normalizedStreams = new ArrayList<List<Double>>();
723    
724        // make up the Y axis for all streams
725        for (SelectableTrajectoryStream stream : streams) {
726          if (!stream.isEmpty()) {
727            String streamUnitName = stream.getUnitName();
728            streamAxis = newYAxis(streamUnitName, streamMax, streamMin);
729            //stream.setColor(GoogleChart.getNextJetColor());
730          }
731        }
732    
733        // add the y axis and extend the range if it contains only one horizontal line.
734        String axisType = "r";
735        if (googleChart.isYAxisEmpty()) {
736          axisType = "y";
737        }
738    
739        List<Double> rangeList = new ArrayList<Double>();
740        rangeList.add(streamAxis.getMinimum());
741        rangeList.add(streamAxis.getMaximum());
742    
743        // add the axis to the chart.
744        googleChart.addAxisRange(axisType, rangeList, streamAxis.getColor());
745    
746        // add streams to the chart.
747        for (int i = 0; i < streams.size(); ++i) {
748          SelectableTrajectoryStream stream = streams.get(i);
749          if (!stream.isEmpty()) {
750            List<Double> s = this.addStreamToNormalizedChart(stream, streamAxis.getMinimum(),
751                streamAxis.getMaximum(), maxStreamLength, googleChart);
752            normalizedStreams.add(s);
753          }
754        }
755        // add the x axis, X axis is a list of markers
756        if (!streams.isEmpty()) {
757          googleChart.addAxisLabel("x", getMarkersList(), "");
758          try {
759            NumberFormat decFormatter = new DecimalFormat("00.00");
760            googleChart.setTitle("Normalized streamData|Euclidean distance="
761                + decFormatter.format(this.getEuclideanDistance(normalizedStreams.get(0),
762                    normalizedStreams.get(1))));
763          }
764          catch (DTWException e) {
765            e.printStackTrace();
766          }
767        }
768        return googleChart.getUrl();
769      }
770    
771      /**
772       * Calculates the Euclidean distance.
773       * 
774       * @param s1 time series 1.
775       * @param s2 time series 2.
776       * @return the Euclidean distance.
777       * @throws DTWException if error occures.
778       */
779      private Double getEuclideanDistance(List<Double> s1, List<Double> s2) throws DTWException {
780        double[][] stream1 = new double[s1.size()][2];
781        int i = 0;
782        for (Double d : s1) {
783          stream1[i][0] = Integer.valueOf(i).doubleValue();
784          stream1[i][1] = d.doubleValue();
785          i++;
786        }
787        double[][] stream2 = new double[s2.size()][2];
788        i = 0;
789        for (Double d : s2) {
790          stream2[i][0] = Integer.valueOf(i).doubleValue();
791          stream2[i][1] = d.doubleValue();
792          i++;
793        }
794        return EuclideanDistance.getSeriesDistnace(stream1, stream2);
795      }
796    
797      /**
798       * Return the google chart url that present normalized streams.
799       * 
800       * @param streams the given stream list.
801       * @return the URL string of the chart.
802       */
803      public String getDTWChartURL(List<SelectableTrajectoryStream> streams) {
804        getLogger().log(Level.FINER,
805            MARK + "DTW Chart: getting url for " + streams.size() + " stream(s).");
806        GoogleChart googleChart = new GoogleChart(ChartType.LINE, this.width, this.height);
807        TrajectoryStreamYAxis streamAxis = null;
808    
809        // okay here we are expecting to see two streams in the input array - get them normalized.
810        double streamMin = Double.MAX_VALUE;
811        double streamMax = Double.MIN_VALUE;
812        for (SelectableTrajectoryStream stream : streams) {
813          if (!stream.isEmpty()) {
814            List<Double> normalizedStreamData = stream.getNormalizedStreamData();
815            if (streamMin > Collections.min(normalizedStreamData)) {
816              streamMin = Collections.min(normalizedStreamData);
817            }
818            if (streamMax < Collections.max(normalizedStreamData)) {
819              streamMax = Collections.max(normalizedStreamData);
820            }
821          }
822        }
823    
824        getLogger().log(Level.FINER,
825            MARK + "Normalized TS: min: " + streamMin + ", maximum: " + streamMax);
826    
827        // make up the Y axis for all streams
828        for (SelectableTrajectoryStream stream : streams) {
829          if (stream.isEmpty()) {
830            continue;
831          }
832          getLogger().log(
833              Level.FINER,
834              MARK + "DTW Chart: processing stream " + stream.getTelemetryStream().getName()
835                  + ", length " + stream.getTelemetryStream().getTelemetryPoint().size());
836          String streamUnitName = stream.getUnitName();
837          streamAxis = newYAxis(streamUnitName, streamMax, streamMin);
838          //stream.setColor(GoogleChart.getNextJetColor());
839        }
840    
841        // add the y axis and extend the range if it contains only one horizontal line.
842        String axisType = "r";
843        if (googleChart.isYAxisEmpty()) {
844          axisType = "y";
845        }
846    
847        List<Double> rangeList = new ArrayList<Double>();
848        rangeList.add(streamAxis.getMinimum());
849        rangeList.add(streamAxis.getMaximum());
850    
851        // add the axis to the chart.
852        googleChart.addAxisRange(axisType, rangeList, streamAxis.getColor());
853    
854        // run DTW in here
855        //
856        // extract time series as the list of double values
857        List<List<Double>> rawSeries = this.getRawSeriesForDTW(streams);
858        List<List<Double>> dtwSeries = this.getDTWSeries(rawSeries);
859    
860        // add streams to the chart.
861        // add streams to the chart.
862        for (int i = 0; i < streams.size(); ++i) {
863          SelectableTrajectoryStream stream = streams.get(i);
864          if (!stream.isEmpty()) {
865            this.addStreamToDTWChart(stream, dtwSeries.get(i), streamAxis.getMinimum(), streamAxis
866                .getMaximum(), maxStreamLength, googleChart);
867          }
868        }
869    
870        // add the x axis, X axis is a list of markers
871        if (!streams.isEmpty()) {
872          googleChart.addAxisLabel("x", getMarkersList(), "");
873          try {
874            NumberFormat decFormatter = new DecimalFormat("00.00");
875            googleChart.setTitle("Normalized streamData|Euclidean distance="
876                + decFormatter.format(this.getEuclideanDistance(dtwSeries.get(0), dtwSeries.get(1))));
877          }
878          catch (DTWException e) {
879            e.printStackTrace();
880          }
881        }
882        return googleChart.getUrl();
883      }
884    
885      /**
886       * Performs DTW alignment.
887       * 
888       * @param rawSeries the series to align.
889       * @return aligned series.
890       */
891      private List<List<Double>> getDTWSeries(List<List<Double>> rawSeries) {
892    
893        List<List<Double>> res = new ArrayList<List<Double>>();
894    
895        List<Double> query = rawSeries.get(0);
896        double[][] queryD = new double[query.size()][1];
897        int i = 0;
898        for (Double n : query) {
899          queryD[i][0] = n.doubleValue();
900          i++;
901        }
902    
903        List<Double> template = rawSeries.get(1);
904        double[][] templateD = new double[template.size()][1];
905        i = 0;
906        for (Double n : template) {
907          templateD[i][0] = n.doubleValue();
908          i++;
909        }
910    
911        try {
912          DTWAlignment r = DTWFactory.doDTW(queryD, templateD, SymmetricStepFunction.STEP_PATTERN_P0);
913          // getLogger().log(Level.FINER, MARK + "DTW Record: " + CR + r.toString());
914    
915          double[] rawWarpingQuery = r.getWarpingQuery();
916          List<Double> warpingQuery = new ArrayList<Double>();
917          for (int k = 0; k < rawWarpingQuery.length; k++) {
918            warpingQuery.add(Double.valueOf(rawWarpingQuery[k]));
919          }
920    
921          res.add(warpingQuery);
922          res.add(template);
923          return res;
924        }
925        catch (DTWException e) {
926          // TODO Auto-generated catch block
927          e.printStackTrace();
928        }
929        return null;
930      }
931    
932      /**
933       * Prepares series for the alignment - extracts data from streams.
934       * 
935       * @param streams input streams
936       * @return extracted data.
937       */
938      private List<List<Double>> getRawSeriesForDTW(List<SelectableTrajectoryStream> streams) {
939        getLogger().log(Level.FINER, MARK + "DTW Chart: getting raw series.");
940        List<List<Double>> res = new ArrayList<List<Double>>();
941        // add streams to the chart.
942        for (int i = 0; i < streams.size(); ++i) {
943          SelectableTrajectoryStream stream = streams.get(i);
944          if (!stream.isEmpty()) {
945            res.add(stream.getNormalizedStreamData());
946          }
947        }
948        if (!res.isEmpty()) {
949          StringBuffer sb = new StringBuffer(1000);
950          NumberFormat decFormatter = new DecimalFormat("####0.00");
951          for (List<Double> l : res) {
952            for (Double d : l) {
953              sb.append(decFormatter.format(d));
954              sb.append(", ");
955            }
956            sb.append('|');
957          }
958          getLogger().log(Level.FINER, MARK + "DTW Chart: RAW series: " + sb.toString());
959          return res;
960        }
961        return null;
962      }
963    
964      /**
965       * Add the given stream to the google chart. And return the maximum value of the stream.
966       * 
967       * @param stream the given stream.
968       * @param maximum the maximum value of the range this stream will be associated to.
969       * @param minimum the minimum value of the range this stream will be associated to.
970       * @param maxStreamLength the plotted stream length on X axis.
971       * @param googleChart the google chart.
972       */
973      public void addStreamToChart(SelectableTrajectoryStream stream, double minimum, double maximum,
974          Integer maxStreamLength, GoogleChart googleChart) {
975        // add the chart only if the stream is not empty.
976        if (!stream.isEmpty()) {
977          // add stream data
978          googleChart.getChartData().add(stream.getStreamData(maxStreamLength));
979          // prepare the range data.
980          List<Double> range = getRangeList(minimum, maximum);
981          // add range
982          googleChart.getChartDataScale().add(range);
983          // add stream color
984          googleChart.addColor(stream.getColor());
985          // add line style;
986          googleChart.addLineStyle(stream.getThickness(), stream.getLineLength(), stream
987              .getBlankLength());
988          /* googleChart.addAxisRange(axisType, range, randomColor); */
989          // add markers
990          /* stream.setMarker(GoogleChart.getRandomMarker()); */
991          googleChart.getChartMarker().add(stream.getMarker());
992          googleChart.getMarkerColors().add(stream.getColor());
993        }
994      }
995    
996      /**
997       * Add the given stream to the google chart and return the normalized data.
998       * 
999       * @param stream the given stream.
1000       * @param maximum the maximum value of the range this stream will be associated to.
1001       * @param minimum the minimum value of the range this stream will be associated to.
1002       * @param maxStreamLength the plotted stream length on X axis.
1003       * @param googleChart the google chart.
1004       * 
1005       * @return normalized data extracted from the stream.
1006       */
1007      public List<Double> addStreamToNormalizedChart(SelectableTrajectoryStream stream,
1008          double minimum, double maximum, Integer maxStreamLength, GoogleChart googleChart) {
1009        // add the chart only if the stream is not empty.
1010        if (!stream.isEmpty()) {
1011          // add stream data
1012          List<Double> res = stream.getNormalizedStreamData();
1013          getLogger().log(Level.FINER, "TrajectoryChart: adding series: " + res);
1014          googleChart.getChartData().add(res);
1015          // prepare the range data.
1016          List<Double> range = getRangeList(minimum, maximum);
1017          // add range
1018          googleChart.getChartDataScale().add(range);
1019          // add stream color
1020          googleChart.addColor(stream.getColor());
1021          // add line style;
1022          googleChart.addLineStyle(stream.getThickness(), stream.getLineLength(), stream
1023              .getBlankLength());
1024          /* googleChart.addAxisRange(axisType, range, randomColor); */
1025          // add markers
1026          /* stream.setMarker(GoogleChart.getRandomMarker()); */
1027          googleChart.getChartMarker().add(stream.getMarker());
1028          googleChart.getMarkerColors().add(stream.getColor());
1029          return res;
1030        }
1031        return new ArrayList<Double>();
1032      }
1033    
1034      /**
1035       * Add the given stream to the google chart. And return the maximum value of the stream.
1036       * 
1037       * @param stream the given stream.
1038       * @param list the chart data.
1039       * @param maximum the maximum value of the range this stream will be associated to.
1040       * @param minimum the minimum value of the range this stream will be associated to.
1041       * @param maxStreamLength the plotted stream length on X axis.
1042       * @param googleChart the google chart.
1043       */
1044      public void addStreamToDTWChart(SelectableTrajectoryStream stream, List<Double> list,
1045          double minimum, double maximum, Integer maxStreamLength, GoogleChart googleChart) {
1046        // add the chart only if the stream is not empty.
1047        if (!stream.isEmpty()) {
1048          // add stream data
1049          googleChart.getChartData().add(list);
1050          // prepare the range data.
1051          List<Double> range = getRangeList(minimum, maximum);
1052          // add range
1053          googleChart.getChartDataScale().add(range);
1054          // add stream color
1055          googleChart.addColor(stream.getColor());
1056          // add line style;
1057          googleChart.addLineStyle(stream.getThickness(), stream.getLineLength(), stream
1058              .getBlankLength());
1059          /* googleChart.addAxisRange(axisType, range, randomColor); */
1060          // add markers
1061          /* stream.setMarker(GoogleChart.getRandomMarker()); */
1062          googleChart.getChartMarker().add(stream.getMarker());
1063          googleChart.getMarkerColors().add(stream.getColor());
1064        }
1065      }
1066    
1067      /**
1068       * create a new TelemetryStreamYAxis instance with given parameters.
1069       * 
1070       * @param streamUnitName the unit name
1071       * @param streamMax the maximum value
1072       * @param streamMin the minimum value
1073       * @return the new object
1074       */
1075      private TrajectoryStreamYAxis newYAxis(String streamUnitName, double streamMax, 
1076          double streamMin) {
1077        TrajectoryStreamYAxis axis = new TrajectoryStreamYAxis(streamUnitName);
1078        axis.setMaximum(streamMax);
1079        axis.setMinimum(streamMin);
1080        axis.setColor("00007F");
1081        return axis;
1082      }
1083    
1084      /**
1085       * return a list that contains the range minimum and maximum. The minimum value and maximum value
1086       * will be normalized accordingly.
1087       * 
1088       * @param min the minimum value
1089       * @param max the maximum value
1090       * @return the list
1091       */
1092      private List<Double> getRangeList(double min, double max) {
1093        double minimum = min;
1094        double maximum = max;
1095        List<Double> rangeList = new ArrayList<Double>();
1096        // if (minimum == maximum) {
1097        // minimum -= 0.5;
1098        // minimum = (minimum < 0) ? 0 : minimum;
1099        // maximum += 0.5;
1100        // }
1101        // minimum = Math.floor(minimum);
1102        // maximum = Math.ceil(maximum);
1103        rangeList.add(minimum);
1104        rangeList.add(maximum);
1105        return rangeList;
1106      }
1107    
1108      /**
1109       * Return the date list within the list of points.
1110       * 
1111       * @param points the point list.
1112       * @return the date list.
1113       */
1114      public List<String> getDateList(List<TelemetryPoint> points) {
1115        List<String> dates = new ArrayList<String>();
1116        SimpleDateFormat dateFormat = new SimpleDateFormat("MM-dd", Locale.US);
1117        for (TelemetryPoint point : points) {
1118          dates.add(dateFormat.format(point.getTime().toGregorianCalendar().getTime()));
1119        }
1120        return dates;
1121      }
1122    
1123      /**
1124       * Return the date list inside this model.
1125       * 
1126       * @return the date list.
1127       */
1128      public List<String> getMarkersList() {
1129        // check if both streams are empty
1130        if (isEmpty()) {
1131          return new ArrayList<String>();
1132        }
1133        List<String> markersList = new ArrayList<String>();
1134        for (int i = 0; i < maxStreamLength; i++) {
1135          markersList.add(String.valueOf(i));
1136        }
1137        return markersList;
1138      }
1139    
1140      /**
1141       * @return the selectedProjects
1142       */
1143      public List<ProjectRecord> getSelectedProjects() {
1144        List<ProjectRecord> list = new ArrayList<ProjectRecord>();
1145        list.add(selectedProject1);
1146        list.add(selectedProject2);
1147        return list;
1148      }
1149    
1150      /**
1151       * Return the chart for the given project
1152       * 
1153       * @param project the given project.
1154       * @return the chart link.
1155       */
1156      /*
1157       * public String getProjectChart(Project project) { String chartLink =
1158       * this.projectCharts.get(project); if (chartLink == null) { chartLink =
1159       * this.getChartUrl(project); this.projectCharts.put(project, chartLink); } return chartLink; }
1160       */
1161    
1162      /**
1163       * Return the comma-separated list of parameters in String.
1164       * 
1165       * @return the parameters as String
1166       */
1167      public String getParameterAsString() {
1168        StringBuffer stringBuffer = new StringBuffer();
1169        for (String model : this.parameters) {
1170          stringBuffer.append(model);
1171          stringBuffer.append(',');
1172        }
1173        String param = stringBuffer.toString();
1174        if (param.length() >= 1) {
1175          param = param.substring(0, param.length() - 1);
1176        }
1177        return param;
1178      }
1179    
1180      /**
1181       * @return the overallChart
1182       */
1183      /*
1184       * public String getOverallChart() { if (overallChart == null) { List<TelemetryStream> streams =
1185       * new ArrayList<TelemetryStream>(); List<String> projectNames = new ArrayList<String>(); for
1186       * (Project project : this.selectedProjects) { List<SelectableTelemetryStream> streamList =
1187       * this.getTelemetryStream(project); for (int i = 0; i < streamList.size(); ++i) {
1188       * streams.add(streamList.get(i).getTelemetryStream()); projectNames.add(project.getName()); } }
1189       * overallChart = this.getChartUrl(projectNames, streams); } return overallChart; }
1190       */
1191      /**
1192       * @param width the width to set
1193       */
1194      public void setWidth(int width) {
1195        this.width = width;
1196      }
1197    
1198      /**
1199       * @return the width
1200       */
1201      public int getWidth() {
1202        return width;
1203      }
1204    
1205      /**
1206       * @param height the height to set
1207       */
1208      public void setHeight(int height) {
1209        this.height = height;
1210      }
1211    
1212      /**
1213       * @return the height
1214       */
1215      public int getHeight() {
1216        return height;
1217      }
1218    
1219      /**
1220       * Return statistics for the stream1.
1221       * 
1222       * @return the statistics for the stream1.
1223       */
1224      public String getStream1Statistics() {
1225        return this.selectedProject1.toLabelMessage();
1226      }
1227    
1228      /**
1229       * Return statistics for the stream2.
1230       * 
1231       * @return the statistics for the stream2.
1232       */
1233      public String getStream2Statistics() {
1234        return this.selectedProject2.toLabelMessage();
1235      }
1236    
1237      /**
1238       * Change all selected flags of streams to the given flag.
1239       * 
1240       * @param flag the boolean flag.
1241       */
1242      public void changeSelectionForAll(boolean flag) {
1243        for (List<SelectableTrajectoryStream> streamList : this.projectStreamData.values()) {
1244          for (SelectableTrajectoryStream stream : streamList) {
1245            stream.setSelected(flag);
1246          }
1247        }
1248      }
1249    
1250      /**
1251       * @return the inProcess
1252       */
1253      public boolean isInProcess() {
1254        return inProcess;
1255      }
1256    
1257      /**
1258       * @return the isComplete
1259       */
1260      public boolean isComplete() {
1261        return complete;
1262      }
1263    
1264      /**
1265       * @return the message
1266       */
1267      public String getProcessingMessage() {
1268        return processingMessage;
1269      }
1270    
1271      /**
1272       * @return the logger that associated to this web application.
1273       */
1274      private Logger getLogger() {
1275        return ((ProjectBrowserApplication) Application.get()).getLogger();
1276      }
1277    
1278      /**
1279       * Helps to format parameters list.
1280       * 
1281       * @param parameters The parameters list.
1282       * @return formatted parameters log message.
1283       */
1284      private String parametersToLog(List<IModel> parameters) {
1285        StringBuffer sb = new StringBuffer(500);
1286        for (IModel im : parameters) {
1287          sb.append(IDNT + IDNT2 + im.getClass() + ": " + im.getObject() + CR);
1288        }
1289        return sb.toString();
1290      }
1291      
1292      /**
1293       * Set the paramErrorMessage.
1294       * 
1295       * @param message the message.
1296       */
1297      public void setWarningMessage(String message) {
1298        this.trajectoryDataWarningMessage = message;
1299      }
1300      
1301      /**
1302       * @return the paramErrorMessage
1303       */
1304      public String getWarningMessage() {
1305        String temp = this.trajectoryDataWarningMessage;
1306        this.trajectoryDataWarningMessage = "";
1307        return temp;
1308      }
1309    
1310      
1311    }