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 }; |