From 5509f3fa2347ba1de9aebd9c15c303c47da65216 Mon Sep 17 00:00:00 2001 From: Ian Oberst Date: Fri, 29 Sep 2017 17:10:37 -0700 Subject: [PATCH 1/5] - Added support for vertical scrolling while the timeline is focusing on an element, both for animated and non-animated calls of focus --- .../timeline/interaction/setSelection.html | 99 +++++++++++++++++ lib/timeline/Range.js | 6 +- lib/timeline/Timeline.js | 102 +++++++++++++++++- 3 files changed, 204 insertions(+), 3 deletions(-) diff --git a/examples/timeline/interaction/setSelection.html b/examples/timeline/interaction/setSelection.html index 362787977..54e8a92bc 100644 --- a/examples/timeline/interaction/setSelection.html +++ b/examples/timeline/interaction/setSelection.html @@ -26,6 +26,17 @@

Set selection

+
+

Focusing on items will also cause the timeline to scroll vertically. If focusing on multiple items only the first item will be scrolled to.

+ + + +

+Select item(s):
+

+ +
+ \ No newline at end of file diff --git a/lib/timeline/Range.js b/lib/timeline/Range.js index 96662559f..ef9a580c5 100644 --- a/lib/timeline/Range.js +++ b/lib/timeline/Range.js @@ -191,7 +191,7 @@ Range.prototype.stopRolling = function() { * */ -Range.prototype.setRange = function(start, end, options, callback) { +Range.prototype.setRange = function(start, end, options, callback, frameCallback) { if (!options) { options = {}; } @@ -238,7 +238,9 @@ Range.prototype.setRange = function(start, end, options, callback) { event: options.event }; - if (changed) { + if(frameCallback) { frameCallback(ease, changed, done); } + + if (changed) { me.body.emitter.emit('rangechange', params); } diff --git a/lib/timeline/Timeline.js b/lib/timeline/Timeline.js index c1089613e..0fc5a8349 100644 --- a/lib/timeline/Timeline.js +++ b/lib/timeline/Timeline.js @@ -392,13 +392,68 @@ Timeline.prototype.focus = function(id, options) { } }); + if (start !== null && end !== null) { + var me = this; + // Use the first item for the vertical focus + var item = this.itemSet.items[ids[0]]; + var startPos = this._getScrollTop() * -1; + var initialVerticalScroll = null; + + // Setup a handler for each frame of the vertical scroll + var verticalAnimationFrame = function(ease, willDraw, done) { + var verticalScroll = getItemVerticalScroll(me, item); + + if(!initialVerticalScroll) { + initialVerticalScroll = verticalScroll; + } + + if(initialVerticalScroll.itemTop == verticalScroll.itemTop && !initialVerticalScroll.shouldScroll) { + return; // We don't need to scroll, so do nothing + } + else if(initialVerticalScroll.itemTop != verticalScroll.itemTop && verticalScroll.shouldScroll) { + // The redraw shifted elements, so reset the animation to correct + initialVerticalScroll = verticalScroll; + startPos = me._getScrollTop() * -1; + } + + var from = startPos; + var to = initialVerticalScroll.scrollOffset; + var scrollTop = done ? to : (from + (to - from) * ease); + + me._setScrollTop(-scrollTop); + + if(!willDraw) { + me._redraw(); + } + }; + + // Perform one last check at the end to make sure the final vertical + // position is correct + var finalVerticalCallback = function() { + var finalVerticalScroll = getItemVerticalScroll(me, item); + + if(finalVerticalScroll.shouldScroll && finalVerticalScroll.itemTop != initialVerticalScroll.itemTop) { + me._setScrollTop(-finalVerticalScroll.scrollOffset); + me._redraw(); + } + }; + // calculate the new middle and interval for the window var middle = (start + end) / 2; var interval = Math.max((this.range.end - this.range.start), (end - start) * 1.1); var animation = (options && options.animation !== undefined) ? options.animation : true; - this.range.setRange(middle - interval / 2, middle + interval / 2, { animation: animation }); + + if(!animation) { + // We aren't animating so set a default so that the final callback forces the vertical location + initialVerticalScroll = {shouldScroll: false, scrollOffset: -1, itemTop: -1}; + } + + this.range.setRange(middle - interval / 2, middle + interval / 2, { animation: animation }, finalVerticalCallback, verticalAnimationFrame); + + // Let the redraw settle and finalize the position + setTimeout(finalVerticalCallback, 100); } }; @@ -448,6 +503,51 @@ function getEnd(item) { return util.convert(end, 'Date').valueOf(); } +/** + * + * @param {vis.Item} item + * @return {{shouldScroll: Boolean, scrollOffset: int, itemTop: int}} + */ +function getItemVerticalScroll(timeline, item) { + var leftHeight = timeline.props.leftContainer.height; + var contentHeight = timeline.props.left.height; + + var groupId = item.data.group; + var group = timeline.itemSet.groups[groupId] || { top: 0, height: 0 }; // Use a default if we don't have a group + var offset = group.top; + var shouldScroll = true; + var orientation = timeline.timeAxis.options.orientation.axis; + + var itemTop = function () { + if (orientation == "bottom") { + return group.height - item.top - item.height; + } + else { + return item.top; + } + }; + + var currentScrollHeight = timeline._getScrollTop() * -1; + var targetOffset = offset + itemTop(); + var height = item.height; + + if (targetOffset < currentScrollHeight) { + if (offset + leftHeight <= offset + itemTop() + height) { + offset += itemTop() - timeline.itemSet.options.margin.item.vertical; + } + } + else if (targetOffset + height > currentScrollHeight + leftHeight) { + offset += itemTop() + height - leftHeight + timeline.itemSet.options.margin.item.vertical; + } + else { + shouldScroll = false; + } + + offset = Math.min(offset, contentHeight - leftHeight); + + return { shouldScroll: shouldScroll, scrollOffset: offset, itemTop: targetOffset }; +} + /** * Determine the range of the items, taking into account their actual width * and a margin of 10 pixels on both sides. From 3e5feac15e35260b363d4911360e69d1bc392dae Mon Sep 17 00:00:00 2001 From: Ian Oberst Date: Fri, 29 Sep 2017 18:45:11 -0700 Subject: [PATCH 2/5] - Adjusted item offset calculations to use the item parent - Turned on animation for the focus in the example - Updated function documentation --- examples/timeline/interaction/setSelection.html | 2 +- lib/timeline/Timeline.js | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/examples/timeline/interaction/setSelection.html b/examples/timeline/interaction/setSelection.html index 54e8a92bc..d5c00352e 100644 --- a/examples/timeline/interaction/setSelection.html +++ b/examples/timeline/interaction/setSelection.html @@ -138,7 +138,7 @@

Set selection

var id = items[itemIndex].id; - timeline2.setSelection(id, {focus: true, animation: false}); + timeline2.setSelection(id, {focus: true}); } var nextFocus = document.getElementById('nextFocus'); diff --git a/lib/timeline/Timeline.js b/lib/timeline/Timeline.js index 0fc5a8349..cf5f51086 100644 --- a/lib/timeline/Timeline.js +++ b/lib/timeline/Timeline.js @@ -506,14 +506,13 @@ function getEnd(item) { /** * * @param {vis.Item} item - * @return {{shouldScroll: Boolean, scrollOffset: int, itemTop: int}} + * @return {{shouldScroll: bool, scrollOffset: number, itemTop: number}} */ function getItemVerticalScroll(timeline, item) { var leftHeight = timeline.props.leftContainer.height; var contentHeight = timeline.props.left.height; - - var groupId = item.data.group; - var group = timeline.itemSet.groups[groupId] || { top: 0, height: 0 }; // Use a default if we don't have a group + + var group = item.parent; var offset = group.top; var shouldScroll = true; var orientation = timeline.timeAxis.options.orientation.axis; From 2a7b4d6f200c6474aec129752c4cac8db6f5b74b Mon Sep 17 00:00:00 2001 From: Ian Oberst Date: Fri, 29 Sep 2017 18:49:11 -0700 Subject: [PATCH 3/5] - Fixing lint issues --- lib/timeline/Timeline.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/timeline/Timeline.js b/lib/timeline/Timeline.js index cf5f51086..b8e65ed9e 100644 --- a/lib/timeline/Timeline.js +++ b/lib/timeline/Timeline.js @@ -504,7 +504,7 @@ function getEnd(item) { } /** - * + * @param {vis.Timeline} timeline * @param {vis.Item} item * @return {{shouldScroll: bool, scrollOffset: number, itemTop: number}} */ From 8cff920750fa044008fbc802bd84829197b85a52 Mon Sep 17 00:00:00 2001 From: Ian Oberst Date: Sat, 30 Sep 2017 07:40:50 -0700 Subject: [PATCH 4/5] - Added documentation for the new 'frameCallback' parameter of 'setRange' - Fixed the documentation on 'setRange' for the 'callback' parameter - Fixed code not meeting style guidelines --- lib/timeline/Range.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/timeline/Range.js b/lib/timeline/Range.js index ef9a580c5..6de8c64a3 100644 --- a/lib/timeline/Range.js +++ b/lib/timeline/Range.js @@ -187,8 +187,12 @@ Range.prototype.stopRolling = function() { * function is 'easeInOutQuad'. * {boolean} [byUser=false] * {Event} event Mouse event - * {Function} a callback funtion to be executed at the end of this function - * + * @param {Function} callback a callback function to be executed at the end of this function + * @param {Function} frameCallback a callback function executed each frame of the range animation. + * The callback will be passed three parameters: + * {number} easeCoefficient an easing coefficent + * {boolean} willDraw If true the caller will redraw after the callback completes + * {boolean} done If true then animation is ending after the current frame */ Range.prototype.setRange = function(start, end, options, callback, frameCallback) { @@ -238,7 +242,7 @@ Range.prototype.setRange = function(start, end, options, callback, frameCallback event: options.event }; - if(frameCallback) { frameCallback(ease, changed, done); } + if (frameCallback) { frameCallback(ease, changed, done); } if (changed) { me.body.emitter.emit('rangechange', params); From 42346b115aa8b9608a02763b00f8539b1db19b1e Mon Sep 17 00:00:00 2001 From: Ian Oberst Date: Sat, 30 Sep 2017 10:52:45 -0700 Subject: [PATCH 5/5] - Updated the example for "setSelection" to be more clear about what the example buttons do. Focus the language to be more consistent with that fact that the demo uses "setSelection" --- examples/timeline/interaction/setSelection.html | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/examples/timeline/interaction/setSelection.html b/examples/timeline/interaction/setSelection.html index d5c00352e..45901bf19 100644 --- a/examples/timeline/interaction/setSelection.html +++ b/examples/timeline/interaction/setSelection.html @@ -27,10 +27,14 @@

Set selection


-

Focusing on items will also cause the timeline to scroll vertically. If focusing on multiple items only the first item will be scrolled to.

- - +

If the height of the timeline is limited some items may be vertically offscreen. This demo uses Timeline.setSelection(ids, {focus: true}) and demonstrates that focusing on an item will +cause the timeline to scroll vertically to the item that is being focused on. You can use the buttons below select a random item either above or below the currently selected item. +

+ + +
+

If focusing on multiple items only the first item will be scrolled to. Try entering several ids and hitting select.

Select item(s):

@@ -160,6 +164,11 @@

Set selection

prevFocus.onclick = function() { moveToItem(-1); }; + + // Set the initial focus + setTimeout(function() { + moveToItem(0); + }, 500); \ No newline at end of file