src/js/widgets/sparklineWidget.js
branchpopcorn-port
changeset 834 573c7ca752e0
parent 820 7968346b9689
child 835 a8af9da7c622
equal deleted inserted replaced
833:33a5d5882be4 834:573c7ca752e0
     2 IriSP.SparklineWidget = function(Popcorn, config, Serializer) {
     2 IriSP.SparklineWidget = function(Popcorn, config, Serializer) {
     3   IriSP.Widget.call(this, Popcorn, config, Serializer);
     3   IriSP.Widget.call(this, Popcorn, config, Serializer);
     4 
     4 
     5   this._oldAnnotation = null;
     5   this._oldAnnotation = null;
     6   this._results = [];
     6   this._results = [];
       
     7   
       
     8   this.lineColor = this._config.lineColor || "#7492b4";
       
     9   this.fillColor = this._config.fillColor || "#aeaeb8";
       
    10   this.lineWidth = this._config.lineWidth || 2;
       
    11   this.slices = this._config.slices || Math.floor(this.width/20);
       
    12   if (!this.width) {
       
    13       this.width = this.selector.width();
       
    14   }
       
    15   if (!this.height) {
       
    16       this.height = 40;
       
    17   }
       
    18   this.selector.css("height", this.height + "px");
       
    19   if (this._config.background) {
       
    20       this.selector.css("background", this._config.background);
       
    21   }
     7 };
    22 };
     8 
    23 
     9 
    24 
    10 IriSP.SparklineWidget.prototype = new IriSP.Widget();
    25 IriSP.SparklineWidget.prototype = new IriSP.Widget();
    11 
    26 
    13 
    28 
    14 };
    29 };
    15 
    30 
    16 /** draw the sparkline using jquery sparkline */
    31 /** draw the sparkline using jquery sparkline */
    17 IriSP.SparklineWidget.prototype.draw = function() {
    32 IriSP.SparklineWidget.prototype.draw = function() {
    18   var templ = Mustache.to_html(IriSP.SparklineWidget_template, {width: this.width, height: this.height});
    33     this.duration = this._serializer.getDuration();
    19   /** this widget uses three divs -
    34     this.paper = new Raphael(this.selector[0], this.width, this.height);
    20     the first is the sparkline, which is generated by jquery sparkline,
       
    21     the second is an overlay div to display the progression in the video,
       
    22     and the third is a div to react to clicks
       
    23   */
       
    24   
    35   
    25   var views = this._serializer._data.views;
    36   var views = this._serializer._data.views;
    26   var stat_view;
    37   var stat_view;
    27   if (!IriSP.null_or_undefined(views)) {
    38   if (!IriSP.null_or_undefined(views)) {
    28     
    39     for (var i = 0; i < views.length; i++) {
    29     var i;
       
    30     for (i = 0; i < views.length; i++) {
       
    31       var view = views[i];
    40       var view = views[i];
    32       if (view.id === "stat") {
    41       if (view.id === "stat") {
    33           stat_view = view;
    42           stat_view = view;
    34           break;
    43           break;
    35       }
    44       }
    36     }
    45     }
    37   }
    46   }
    38   
    47   
       
    48     var _ = IriSP.underscore;
    39   // If we've found the correct view, feed the directly the data from the view
    49   // If we've found the correct view, feed the directly the data from the view
    40   // to jquery sparkline. Otherwise, compute it ourselves.
    50   // to jquery sparkline. Otherwise, compute it ourselves.
    41   if (!IriSP.null_or_undefined(stat_view)) {
    51     if (!IriSP.null_or_undefined(stat_view)) {
    42     console.log("sparklinewidget : using stats embedded in the json");
    52         console.log("sparklinewidget : using stats embedded in the json");
    43     var results = stat_view.meta.stat.split(",");      
    53         var _results = stat_view.meta.stat.split(",");      
    44   } else {
    54     } else {
    45     console.log("sparklinewidget : computing stats ourselves");
    55         var _annotations = this._serializer._data.annotations,
    46     var num_columns = (this.selector.width()) / IriSP.widgetsDefaults["SparklineWidget"].column_width;
    56             _sliceDuration = Math.floor( this.duration / this.slices),
    47     var duration = this._serializer.getDuration();
    57             _results = _(_.range(this.slices)).map(function(_i) {
    48     var time_step = duration / num_columns; /* the time interval between two columns */
    58                 return _(_annotations).filter(function(_a){
    49     var results = [];
    59                     return (_a.begin <= (1 + _i) * _sliceDuration) && (_a.end >= _i * _sliceDuration)
    50     var i = 0; /* the index in the loop */  
    60                 }).length;
    51 
    61             });
    52     /* this algorithm makes one assumption : that the array is sorted 
       
    53        (it's done for us by the JSONSerializer). We go through the array 
       
    54        and count how many comments fall within a peculiar time piece.
       
    55        As i is preserved between each iteration, it's O(n).
       
    56     */
       
    57     
       
    58     for(var j = 0; j < num_columns && i < this._serializer._data.annotations.length; j++) {    
       
    59       var count = 0;
       
    60       var annotation_begin = +(this._serializer._data.annotations[i].begin);
       
    61       
       
    62       while(annotation_begin >= j * time_step && annotation_begin <= (j + 1) * time_step ) {
       
    63         count++;
       
    64         i++;
       
    65         if (i >= this._serializer._data.annotations.length)
       
    66           break;
       
    67           
       
    68         annotation_begin = +(this._serializer._data.annotations[i].begin);
       
    69         
       
    70       }
       
    71       
       
    72       results.push(count);
       
    73     }
    62     }
    74   }
    63     var _max = Math.max(1, _(_results).max()),
       
    64         _h = this.height,
       
    65         _scale = (_h - this.lineWidth) / _max,
       
    66         _width = this.width / this.slices,
       
    67         _y = _(_results).map(function(_v) {
       
    68             return _h - (_scale * _v);
       
    69         }),
       
    70         _d = _(_y).reduce(function(_memo, _v, _k) {
       
    71                return _memo + ( _k
       
    72                    ? 'C' + (_k * _width) + ' ' + _y[_k - 1] + ' ' + (_k * _width) + ' ' + _v + ' ' + ((_k + .5) * _width) + ' ' + _v
       
    73                    : 'M0 ' + _v + 'L' + (.5*_width) + ' ' + _v )
       
    74             },'') + 'L' + this.width + ' ' + _y[_y.length - 1],
       
    75         _d2 = _d + 'L' + this.width + ' ' + this.height + 'L0 ' + this.height;
       
    76     this.paper.path(_d2).attr({
       
    77         "stroke" : "none",
       
    78         "fill" : this.fillColor
       
    79     });
       
    80          
       
    81     this.paper.path(_d).attr({
       
    82         "fill" : "none",
       
    83         "stroke" : this.lineColor,
       
    84         "stroke-width" : this.lineWidth
       
    85     });
    75   
    86   
       
    87     this.rectangleProgress = this.paper.rect(0,0,0,this.height)
       
    88         .attr({
       
    89             "stroke" : "none",
       
    90             "fill" : "#808080",
       
    91             "opacity" : .3
       
    92         });
       
    93     this.ligneProgress = this.paper.path("M0 0L0 "+this.height).attr({"stroke":"#ff00ff", "line-width" : 2});
    76   // save the results in an array so that we can re-use them when a new annotation
    94   // save the results in an array so that we can re-use them when a new annotation
    77   // is added.
    95   // is added.
    78   this._results = results;
    96   this._results = _results;
    79   
    97   
    80   this.selector.append(templ);
       
    81   this.selector.find(".Ldt-sparkLine").css("background", "#c7c8cc");
       
    82   this.selector.find(".Ldt-sparkLine").sparkline(results, {lineColor: "#7492b4", fillColor: "#aeaeb8",
       
    83                                                            spotColor: "#b70056",
       
    84                                                            width: this.width, height: this.height});
       
    85   this._Popcorn.listen("timeupdate", IriSP.wrap(this, this.timeUpdateHandler));
    98   this._Popcorn.listen("timeupdate", IriSP.wrap(this, this.timeUpdateHandler));
    86   this._Popcorn.listen("IriSP.createAnnotationWidget.addedAnnotation", IriSP.wrap(this, this.handleNewAnnotation));
    99 //  this._Popcorn.listen("IriSP.createAnnotationWidget.addedAnnotation", IriSP.wrap(this, this.handleNewAnnotation));
    87   
   100   
    88   IriSP.jQuery(".Ldt-sparkLineClickOverlay").click(IriSP.wrap(this, this.clickHandler));  
   101   this.selector.click(IriSP.wrap(this, this.clickHandler));  
    89 };
   102 };
    90 
   103 
    91 /** react to a timeupdate event */
   104 /** react to a timeupdate event */
    92 IriSP.SparklineWidget.prototype.timeUpdateHandler = function() {
   105 IriSP.SparklineWidget.prototype.timeUpdateHandler = function() {
    93   var currentTime = this._Popcorn.currentTime();  
   106     var _currentTime = this._Popcorn.currentTime(),
    94   var duration = this._serializer.getDuration() / 1000;
   107         _x = (1000 * _currentTime / this.duration) * this.width;
    95   var proportion = ((currentTime / duration) * 100).toFixed(4);
   108     this.rectangleProgress.attr({
    96   
   109         "width" : _x
    97   IriSP.jQuery(".Ldt-sparkLinePositionMarker").css("width", proportion + "%");                                    
   110     });
       
   111     this.ligneProgress.attr({
       
   112         "path" : "M" + _x + " 0L" + _x + " " + this.height
       
   113     });
       
   114                                   
    98 }
   115 }
    99 
   116 
   100 /** handle clicks on the widget */
   117 /** handle clicks on the widget */
   101 IriSP.SparklineWidget.prototype.clickHandler = function(event) {
   118 IriSP.SparklineWidget.prototype.clickHandler = function(event) {
   102   /* this piece of code is a little bit convoluted - here's how it works :
   119   var relX = event.pageX - this.selector.offset().left;
   103      we want to handle clicks on the progress bar and convert those to seeks in the media.
   120   var newTime = ((relX / this.width) * this.duration/1000).toFixed(2);
   104      However, jquery only gives us a global position, and we want a number of pixels relative
       
   105      to our container div, so we get the parent position, and compute an offset to this position,
       
   106      and finally compute the progress ratio in the media.
       
   107      Finally we multiply this ratio with the duration to get the correct time
       
   108   */
       
   109 
       
   110   var parentOffset = this.selector.offset();
       
   111   var width = this.selector.width();
       
   112   var relX = event.pageX - parentOffset.left;
       
   113 
       
   114   var duration = this._serializer.getDuration() / 1000;
       
   115   var newTime = ((relX / width) * duration).toFixed(2);
       
   116     
   121     
   117   this._Popcorn.trigger("IriSP.SparklineWidget.clicked", newTime);
   122   this._Popcorn.trigger("IriSP.SparklineWidget.clicked", newTime);
   118   this._Popcorn.currentTime(newTime);                                 
   123   this._Popcorn.currentTime(newTime);
   119 };
   124 };
   120 
   125 
   121 /** react when a new annotation is added */
   126 /** react when a new annotation is added */
   122 IriSP.SparklineWidget.prototype.handleNewAnnotation = function(annotation) {
   127 IriSP.SparklineWidget.prototype.handleNewAnnotation = function(annotation) {
   123   var num_columns = this._results.length;
   128 //  var num_columns = this._results.length;
   124   var duration = this._serializer.getDuration();
   129 //  var duration = this._serializer.getDuration();
   125   var time_step = Math.round(duration / num_columns); /* the time interval between two columns */
   130 //  var time_step = Math.round(duration / num_columns); /* the time interval between two columns */
   126   var begin = +annotation.begin;
   131 //  var begin = +annotation.begin;
   127   var end = +annotation.end;
   132 //  var end = +annotation.end;
   128   
   133 //  
   129   /* increment all the values between the beginning and the end of the annotation */
   134 //  /* increment all the values between the beginning and the end of the annotation */
   130   var index_begin = Math.floor(begin / time_step);
   135 //  var index_begin = Math.floor(begin / time_step);
   131   var index_end = Math.floor(end / time_step);
   136 //  var index_end = Math.floor(end / time_step);
   132   
   137 //  
   133   for (var i = index_begin; i < Math.min(index_end, this._results.length); i++) {
   138 //  for (var i = index_begin; i < Math.min(index_end, this._results.length); i++) {
   134     this._results[i]++;
   139 //    this._results[i]++;
   135   }
   140 //  }
   136   
   141 //  
   137   this.selector.find(".Ldt-sparkLine").sparkline(this._results, {lineColor: "#7492b4", fillColor: "#aeaeb8",
   142 //  this.selector.find(".Ldt-sparkLine").sparkline(this._results, {lineColor: "#7492b4", fillColor: "#aeaeb8",
   138                                                            spotColor: "#b70056",
   143 //                                                           spotColor: "#b70056",
   139                                                            width: this.width, height: this.height});
   144 //                                                           width: this.width, height: this.height});
   140 };
   145 };