Added a visualization of the contents of clusters + mousewheel support
authorveltr
Fri, 25 May 2012 16:32:31 +0200
changeset 68 4def147b1604
parent 67 5d1ac260d3ee
child 69 7bb5f89ad242
Added a visualization of the contents of clusters + mousewheel support
timeline/css/timeline.css
timeline/img/cluster-overlay.png
timeline/img/tooltip-down.png
timeline/js/timeline.js
timeline/lib/jquery.mousewheel.min.js
timeline/timeline.html
--- a/timeline/css/timeline.css	Thu May 24 19:02:39 2012 +0200
+++ b/timeline/css/timeline.css	Fri May 25 16:32:31 2012 +0200
@@ -1,5 +1,5 @@
 .Tl-Main {
-    border: 1px solid #ccc;
+    overflow: hidden; border: 1px solid #ccc;
 }
 
 .Tl-TopBar {
@@ -72,7 +72,7 @@
 }
 
 .Tl-MainPart {
-    position: absolute; right: 0; top: 0; bottom: 0; border-left: 1px solid #ccc; right: 0; top: 0; bottom: 0; overflow: hidden;
+    position: absolute; right: 0; top: 0; bottom: 0; border-left: 1px solid #ccc; right: 0; top: 0; bottom: 0;
 }
 
 .Tl-UniversLabels {
@@ -104,7 +104,15 @@
 }
 
 .Tl-Occurrence, .Tl-Cluster {
-    position: absolute; width: 19px; height: 22px; background: url(../img/sprites.png); margin-left: -9px; margin-top: -1px; cursor: pointer;
+    width: 19px; height: 22px; background: url(../img/sprites.png); cursor: pointer;
+}
+
+.Tl-OccOnGrid, .Tl-Cluster {
+    position: absolute; margin-left: -9px; margin-top: -1px;
+}
+
+.Tl-OccInCluster {
+    float: left;
 }
 
 .Tl-Occnarrative {
@@ -132,6 +140,10 @@
     padding: 1px 2px; font-size: 9px; color: #ffffff; border-radius: 5px;
 }
 
+.Tl-ClusterOverlay {
+    position: absolute; margin-top: -20px; height: 30px; background: url(../img/cluster-overlay.png) center;
+}
+
 .Tl-Overlay-Container {
     position: absolute; top: 0;
 }
@@ -141,13 +153,37 @@
 }
 
 .Tl-Overlay {
-    position: absolute; left: -105px; bottom: 0; width: 210px;
+    position: absolute; left: -105px; width: 210px;
+}
+
+.Tl-Overlay-Up .Tl-Overlay {
+     bottom: 0;
+}
+
+.Tl-Overlay-Down .Tl-Overlay {
+     top: 22px;
 }
 
 .Tl-Overlay-Main {
-    width: 190px; padding: 10px 10px 0; background: url(../img/tooltip.png) top;
+    width: 190px;
+}
+
+.Tl-Overlay-Up .Tl-Overlay-Main {
+    background: url(../img/tooltip.png) top; padding: 10px 10px 0;
+}
+
+.Tl-Overlay-Down .Tl-Overlay-Main {
+    background: url(../img/tooltip-down.png) bottom; padding: 0 10px 10px;
 }
 
-.Tl-Overlay-Tip {
+.Tl-Overlay-Up .Tl-Overlay-Tip-Bottom  {
     width: 210px; height: 20px; background: url(../img/tooltip.png) bottom;
 }
+
+.Tl-Overlay-Down .Tl-Overlay-Tip-Top {
+    width: 210px; height: 20px; background: url(../img/tooltip-down.png) top;
+}
+
+.Tl-Overlay-Up .Tl-Overlay-Tip-Top, .Tl-Overlay-Down .Tl-Overlay-Tip-Bottom {
+    display: none;
+}
Binary file timeline/img/cluster-overlay.png has changed
Binary file timeline/img/tooltip-down.png has changed
--- a/timeline/js/timeline.js	Thu May 24 19:02:39 2012 +0200
+++ b/timeline/js/timeline.js	Fri May 25 16:32:31 2012 +0200
@@ -115,7 +115,7 @@
     sync_now: true,
     url_occurrences: '',
     occurrences: [],
-    cluster_spacing: 9
+    cluster_spacing: 10
 }
 
 for (var _i = 0; _i < Tlns.Defaults.Timeline.timescales.length; _i++) {
@@ -129,11 +129,11 @@
     + '<div class="Tl-TopBar-Button Tl-Border-Right"><div class="Tl-TopBar-SyncButton"></div></div><div class="Tl-TopBar-Button Tl-Border-Right"><div class="Tl-TopBar-NextButton"></div></div><div class="Tl-TopBar-Spacer Tl-Border-Right"></div>'
     + '<div class="Tl-TopBar-Timescales">{{#timescales}}<div class="Tl-TopBar-Button Tl-TopBar-TextBtn Tl-Border-Right" data-level="{{level}}">{{label}}</div>{{/timescales}}</div></div>'
     + '<div class="Tl-BottomPart"><ul class="Tl-UniversLabels"></ul><div class="Tl-MainPart"><div class="Tl-Layer Tl-Grid"></div><canvas class="Tl-Layer Tl-Canvas"></canvas><div class="Tl-Layer Tl-Occurrences"></div></div>'
-    + '<div class="Tl-Overlay-Container"><div class="Tl-Overlay-Box"><div class="Tl-Overlay"><div class="Tl-Overlay-Main"></div><div class="Tl-Overlay-Tip"></div></div></div></div></div>';
+    + '<div class="Tl-Overlay-Container"><div class="Tl-Overlay-Box"><div class="Tl-Overlay"><div class="Tl-Overlay-Tip-Top"></div><div class="Tl-Overlay-Main"></div><div class="Tl-Overlay-Tip-Bottom"></div></div></div></div></div>';
 
 Tlns.Templates.Univers = '<span class="Tl-UniversText">{{title}}</span>';
 
-Tlns.Templates.Occurrence = '{{#clusters}}<div class="Tl-Cluster Tl-Occ{{type}}" style="left: {{x}}px; top: {{y}}px;" cluster-contents="{{#occurrences}}{{type}}|{{id}},{{/occurrences}}"><div class="Tl-ClusterCount">{{occurrences.length}}</div></div>{{/clusters}}{{#occurrences}}<div class="Tl-Occurrence Tl-Occ{{type}}{{#editing}} Tl-Editing{{/editing}}" occurrence-type="{{type}}" occurrence-id="{{id}}" style="left: {{x}}px; top: {{univers.y}}px;"></div>{{/occurrences}}';
+Tlns.Templates.Occurrence = '{{#clusters}}<div class="Tl-Cluster Tl-Occ{{type}}" style="left: {{x}}px; top: {{y}}px;" cluster-contents="{{contents}}"><div class="Tl-ClusterCount">{{occurrences.length}}</div></div>{{/clusters}}{{#occurrences}}<div class="Tl-Occurrence Tl-OccOnGrid Tl-Occ{{type}}{{#editing}} Tl-Editing{{/editing}}" occurrence-type="{{type}}" occurrence-id="{{id}}" style="left: {{x}}px; top: {{y}}px;"></div>{{/occurrences}}{{#open_cluster}}<div class="Tl-ClusterOverlay" style="left: {{x}}px; top: {{y}}px;">{{#occurrences}}<div class="Tl-Occurrence Tl-OccInCluster Tl-Occ{{type}}{{#editing}} Tl-Editing{{/editing}}" occurrence-type="{{type}}" occurrence-id="{{id}}"></div>{{/occurrences}}</div>{{/open_cluster}}';
 
 /* Classes */
 
@@ -167,9 +167,14 @@
         bottom: _o.top + this.$.find('.Tl-MainPart').outerHeight(),
     };
     
+    var _this = this;
+    
+    this.throttledDrawGrid = _.throttle(function() {
+        _this.drawGrid();
+    }, 150);
+    
     this.setLevel(this.level);
     
-    var _this = this;
     this.$.find('.Tl-TopBar-Timescales>div').click(function() {
         _this.setLevel($(this).attr("data-level"));
     });
@@ -202,15 +207,25 @@
         return false;
     });
     
+    this.$.find('.Tl-MainPart').mousewheel(function(_event, _delta) {
+        var _newLevel = Math.max(0,Math.min(_this.timescales.length-1, (_delta < 0 ? -1 : 1) + parseInt(_this.level)));
+        if (_newLevel != _this.level) {
+            _this.hideTooltip();
+            var _deltaX = _event.pageX - _this.dragging_bounds.left,
+                _tAtMouse = _this.timeFromMouse(_event.pageX),
+                _newScale = _this.main_width / (_this.timescales[_newLevel].span),
+                _newStart = _tAtMouse - _deltaX / _newScale;
+            _this.central_time = _newStart + _this.timescales[_newLevel].span / 2;
+            _this.setLevel(_newLevel);
+        }
+        return false;
+    });
+    
     this.$.find('.Tl-Overlay-Box').mouseover(function(_event) {
         $(this).show();
     }).mouseout(function(_event) {
         $(this).hide();
-    })
-    
-    this.throttledDrawGrid = _.throttle(function() {
-        _this.drawGrid();
-    }, 150);
+    });
     
     /* Loading Univers */
     $.getJSON(this.url_univers, function(_data) {
@@ -245,26 +260,34 @@
     this.dragging_type = undefined;
 }
 
+Tlns.Classes.Timeline.prototype.timeFromX = function(_x) {
+    return Math.max(this.start_time,Math.min(this.end_time, this.start_time + _x / this.current_scale));
+}
+
+Tlns.Classes.Timeline.prototype.timeFromMouse = function(_pageX) {
+    return this.timeFromX(_pageX - this.dragging_bounds.left);
+}
+
 Tlns.Classes.Timeline.prototype.onMouseMove = function(_event) {
     if (this.mouse_down) {
         this.is_dragging = true;
-        this.$.find('.Tl-Overlay-Box').hide();
+        this.hideTooltip();
         if (_event.pageX > this.dragging_bounds.left
             && _event.pageX < this.dragging_bounds.right
             && _event.pageY > this.dragging_bounds.top
             && _event.pageY < this.dragging_bounds.bottom
         ) {
-            var _timeDelta = Math.floor(( this.start_pos.x - _event.pageX ) / this.current_scale);
             switch (this.dragging_type) {
                 case "occurrence":
-                    this.editing_occurrence.date = this.time_at_start - _timeDelta;
-                    var _u = Math.max(0,Math.min(this.univers.length, Math.floor(this.u_at_start - (this.start_pos.y - _event.pageY) / this.univers_height)));
+                    var _d = this.timeFromMouse(_event.pageX);
+                    this.editing_occurrence.date = _d;
+                    var _u = Math.max(0,Math.min(this.univers.length, Math.floor((_event.pageY - this.dragging_bounds.top) / this.univers_height)));
                     this.editing_occurrence.univers = this.univers[_u];
                     this.editing_occurrence.univers_id = this.univers[_u].id;
                     this.throttledDrawGrid();
                 break;
                 case "timeline":
-                    this.setTime(this.time_at_start + _timeDelta);
+                    this.setTime(this.time_at_start + Math.floor(( this.start_pos.x - _event.pageX ) / this.current_scale));
                 break;
             }
         } else {
@@ -305,7 +328,7 @@
             }
         });
         this.level = _level;
-        this.drawGrid();
+        this.throttledDrawGrid();
     }
 }
 
@@ -327,8 +350,11 @@
         _html = '';
     this.$.find('.Tl-TopBar-TimeSpan').html(Tlns.Utils.dateFormat(this.start_time, _timescale.start_date_format) + ' - ' + Tlns.Utils.dateFormat(this.end_time, _timescale.end_date_format));
     for (var _t = _roundstart; _t < this.end_time; _t += _timescale.grid_interval) {
-        _html += '<div class="Tl-Grid-Column" style="width:' + _grid_width + 'px; left: ' + this.current_scale * (_t - this.start_time) + 'px">'
-        + '<div class="Tl-Grid-Label">' + Tlns.Utils.dateFormat(_t, _timescale.grid_date_format) + '</div></div>';
+        var _x = this.current_scale * (_t - this.start_time);
+        if (_x > 0) {
+            _html += '<div class="Tl-Grid-Column" style="width:' + _grid_width + 'px; left: ' + _x + 'px">'
+            + '<div class="Tl-Grid-Label">' + Tlns.Utils.dateFormat(_t, _timescale.grid_date_format) + '</div></div>';
+        }
     }
 /*
  
@@ -385,8 +411,11 @@
     _occurrence.update(_type, _data);
 }
 
-Tlns.Classes.Timeline.prototype.showTooltip = function(_x, _y, _html) {
-    this.$.find('.Tl-Overlay-Box').show()
+Tlns.Classes.Timeline.prototype.showTooltip = function(_x, _y, _html, _isUp) {
+    this.$.find('.Tl-Overlay-Box')
+        .removeClass(_isUp ? 'Tl-Overlay-Down' : 'Tl-Overlay-Up')
+        .addClass(_isUp ? 'Tl-Overlay-Up' : 'Tl-Overlay-Down')
+        .show()
         .css({
             left: _x + "px",
             top: _y + "px"
@@ -405,6 +434,7 @@
     });
     _(_visible).each(function(_occ) {
         _occ.x = _this.current_scale * (_occ.date - _this.start_time);
+        _occ.y = _occ.univers.y;
         _occ.in_cluster = false;
     });
     
@@ -424,14 +454,15 @@
             }
         }
     }
-    var _clusters = [];
+    var _clusters = [],
+        _openCluster = false;
     for (var _i = 0; _i < _visible.length; _i++) {
         for (var _j = 0; _j < _i; _j++) {
             if (_visible[_j].univers_id == _visible[_i].univers_id && _visible[_j].x == _visible[_i].x) {
                 _visible[_j].in_cluster = true;
                 _visible[_i].in_cluster = true;
                 var _x = _visible[_j].x,
-                    _y = _visible[_j].univers.y;
+                    _y = _visible[_j].y;
                     _cluster = _(_clusters).find(function(_c) { return _c.x == _x && _c.y == _y });
                 if (typeof _cluster === "undefined") {
                     _cluster = { x: _x, y: _y, occurrences: [] };
@@ -440,18 +471,24 @@
                 if ("undefined" === typeof _(_cluster.occurrences).find(function(_o) {
                     return _o.type == _visible[_j].type && _o.id == _visible[_j].id;
                 })) {
-                    _cluster.occurrences.push({type: _visible[_j].type, id: _visible[_j].id});
+                    _cluster.occurrences.push(_visible[_j]);
                 }
                 if ("undefined" === typeof _(_cluster.occurrences).find(function(_o) {
                     return _o.type == _visible[_i].type && _o.id == _visible[_i].id;
                 })) {
-                    _cluster.occurrences.push({type: _visible[_i].type, id: _visible[_i].id});
+                    _cluster.occurrences.push(_visible[_i]);
                 }
             }
         }
     }
     _(_clusters).each(function(_cluster) {
         _cluster.type = _cluster.occurrences[0].type;
+        _cluster.contents = _cluster.occurrences.map(function(_o) {
+            return _o.type + ":" + _o.id;
+        }).join("|");
+        if (_cluster.contents == _this.open_cluster) {
+            _openCluster = _cluster;
+        }
         for (var _i = 1; _i < _cluster.occurrences.length; _i++) {
             if (_cluster.occurrences[_i].type !== _cluster.type) {
                 _cluster.type = "both";
@@ -460,6 +497,7 @@
         }
     });
     
+    
     var _links = [];
     
     _(_visible).each(function(_occurrence) {
@@ -470,9 +508,9 @@
             if (typeof _parent !== "undefined") {
                 _links.push({
                     from_x: _occurrence.x,
-                    from_y: _occurrence.univers.y + Math.floor(_this.univers_height / 2),
+                    from_y: _occurrence.y + Math.floor(_this.univers_height / 2),
                     to_x: _parent.x,
-                    to_y: _parent.univers.y + Math.floor(_this.univers_height / 2)
+                    to_y: _parent.y + Math.floor(_this.univers_height / 2)
                 });
             }
         });
@@ -487,27 +525,54 @@
         _ctx.stroke();
     })
     
+    console.log(_openCluster);
+    
     var _html = Mustache.to_html(Tlns.Templates.Occurrence, {
         occurrences:_(_visible).reject(function(_o) {return _o.in_cluster}),
-        clusters: _clusters
+        clusters: _clusters,
+        open_cluster: _openCluster
     });
     this.$.find('.Tl-Occurrences').html(_html);
+    
+    
+    if (_openCluster) {
+        var _w = this.$.find('.Tl-Occurrence').width(),
+            _ww = _w * _openCluster.occurrences.length;
+        this.$.find('.Tl-ClusterOverlay').css({
+            "margin-left": - Math.floor(_ww/2) + "px",
+            width: _ww
+        });
+        _(_openCluster.occurrences).each(function(_o, _i) {
+            _o.y = _o.y - 20;
+            _o.x = _o.x - (_ww / 2) + ((_i + .5) * _w);
+        });
+    }
+    
     this.$.find('.Tl-Occurrence').mousedown(function() {
         var _el = $(this);
         _this.editing_occurrence = _occurrence = _this.getOccurrence(_el.attr("occurrence-type"),_el.attr("occurrence-id"));
         if (typeof _this.editing_occurrence !== "undefined") {
             _this.dragging_type = "occurrence";
-            _this.time_at_start = _this.editing_occurrence.date;
-            _this.u_at_start = _this.editing_occurrence.univers.index;
             _this.editing_occurrence.editing = true;
         }
-    }).mouseover(function() {
+    }).mouseover(function(_event) {
         var _el = $(this),
             _occurrence = _this.getOccurrence(_el.attr("occurrence-type"),_el.attr("occurrence-id"));
-        _this.showTooltip(_occurrence.x, _occurrence.univers.y, _occurrence.title);
+        _this.showTooltip(_occurrence.x, _occurrence.y, _occurrence.title, (_event.pageY - _this.dragging_bounds.top) > (_this.main_height / 2) );
     }).mouseout(function() {
         _this.hideTooltip();
     });
+    this.$.find('.Tl-Cluster').click(function() {
+        var _el = $(this),
+            _contents = _el.attr("cluster-contents");
+        if (_this.open_cluster == _contents) {
+            _this.open_cluster = false;
+        } else {
+            _this.open_cluster = _contents;
+        }
+        console.log(_this.open_cluster);
+        _this.throttledDrawGrid();
+    })
 }
 
 Tlns.Classes.Timeline.prototype.getUnivers = function(_id) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/timeline/lib/jquery.mousewheel.min.js	Fri May 25 16:32:31 2012 +0200
@@ -0,0 +1,12 @@
+/*! Copyright (c) 2011 Brandon Aaron (http://brandonaaron.net)
+ * Licensed under the MIT License (LICENSE.txt).
+ *
+ * Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers.
+ * Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix.
+ * Thanks to: Seamus Leahy for adding deltaX and deltaY
+ *
+ * Version: 3.0.6
+ * 
+ * Requires: 1.2.2+
+ */
+(function(a){function d(b){var c=b||window.event,d=[].slice.call(arguments,1),e=0,f=!0,g=0,h=0;return b=a.event.fix(c),b.type="mousewheel",c.wheelDelta&&(e=c.wheelDelta/120),c.detail&&(e=-c.detail/3),h=e,c.axis!==undefined&&c.axis===c.HORIZONTAL_AXIS&&(h=0,g=-1*e),c.wheelDeltaY!==undefined&&(h=c.wheelDeltaY/120),c.wheelDeltaX!==undefined&&(g=-1*c.wheelDeltaX/120),d.unshift(b,e,g,h),(a.event.dispatch||a.event.handle).apply(this,d)}var b=["DOMMouseScroll","mousewheel"];if(a.event.fixHooks)for(var c=b.length;c;)a.event.fixHooks[b[--c]]=a.event.mouseHooks;a.event.special.mousewheel={setup:function(){if(this.addEventListener)for(var a=b.length;a;)this.addEventListener(b[--a],d,!1);else this.onmousewheel=d},teardown:function(){if(this.removeEventListener)for(var a=b.length;a;)this.removeEventListener(b[--a],d,!1);else this.onmousewheel=null}},a.fn.extend({mousewheel:function(a){return a?this.bind("mousewheel",a):this.trigger("mousewheel")},unmousewheel:function(a){return this.unbind("mousewheel",a)}})})(jQuery)
--- a/timeline/timeline.html	Thu May 24 19:02:39 2012 +0200
+++ b/timeline/timeline.html	Fri May 25 16:32:31 2012 +0200
@@ -8,6 +8,7 @@
         <meta name="author" content="Institut de Recherche et d'Innovation (IRI)" />
         <link rel="stylesheet" type="text/css" href="css/timeline.css" />
         <script type="text/javascript" src="lib/jquery-min.js"></script>
+        <script type="text/javascript" src="lib/jquery.mousewheel.min.js"></script>
         <script type="text/javascript" src="lib/underscore-min.js"></script>
         <script type="text/javascript" src="lib/raphael-min.js"></script>
         <script type="text/javascript" src="lib/mustache.js"></script>