Skip to content
This repository has been archived by the owner on Jul 29, 2019. It is now read-only.

Vertical focus #3504

Merged
merged 5 commits into from
Sep 30, 2017
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions examples/timeline/interaction/setSelection.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,17 @@ <h1>Set selection</h1>
</p>
<div id="visualization"></div>

<br/>
<p>Focusing on items will also cause the timeline to scroll vertically. If focusing on multiple items only the first item will be scrolled to.</p>
<button id="prevFocus">Select Previous</button>
<button id="nextFocus">Select Next</button>

<p>
Select item(s): <input type="text" id="selectionVertical" value="g1_5, g2_3"><input type="button" id="selectVertical" value="Select"><br>
</p>

<div id="vertical-visualization"></div>

<script>
// create a dataset with items
// we specify the type of the fields `start` and `end` here to be strings
Expand Down Expand Up @@ -61,6 +72,94 @@ <h1>Set selection</h1>
});
timeline.setSelection(ids, {focus: focus.checked});
};

function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min)) + min; //The maximum is exclusive and the minimum is inclusive
};

// Vertical scroll example
var groups = [];
var items = [];
var groupItems = {};

for (var g = 0; g < 10; g++) {
groups.push({
id: g,
content: "Group " + g
});

groupItems[g] = [];

for (var i = 0; i < 30; i++) {
items.push({
id: "g" + g + "_" + i,
content: "g" + g + "_" + i,
group: g,
start: "2014-" + (g + 1) + "-" + getRandomInt(1, 20)
});

groupItems[g].push(items[items.length - 1]);
}
}

var container2 = document.getElementById('vertical-visualization');
var options = {
editable: false,
stack: true,
height: 300,
verticalScroll: true,
groupOrder: 'id'
};

var timeline2 = new vis.Timeline(container2, items, groups, options);

var groupIndex = 0;
var itemIndex = 0;

var moveToItem = function(direction) {
itemIndex += direction;
groupIndex += direction;

if (groupIndex < 0) {
groupIndex = groups.length - 1;
} else if (groupIndex >= groups.length) {
groupIndex = 0;
}

var items = groupItems[groupIndex];

if (itemIndex < 0) {
itemIndex = items.length - 1;
} else if (itemIndex >= items.length) {
itemIndex = 0;
}

var id = items[itemIndex].id;

timeline2.setSelection(id, {focus: true});
}

var nextFocus = document.getElementById('nextFocus');
var prevFocus = document.getElementById('prevFocus');
var selectionVertical = document.getElementById('selectionVertical');
var selectVertical = document.getElementById('selectVertical');

selectVertical.onclick = function () {
var ids = selectionVertical.value.split(',').map(function (value) {
return value.trim();
});
timeline2.setSelection(ids, {focus: focus.checked});
};

nextFocus.onclick = function() {
moveToItem(1);
};

prevFocus.onclick = function() {
moveToItem(-1);
};
</script>
</body>
</html>
6 changes: 4 additions & 2 deletions lib/timeline/Range.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {};
}
Expand Down Expand Up @@ -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);
}

Expand Down
101 changes: 100 additions & 1 deletion lib/timeline/Timeline.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
};

Expand Down Expand Up @@ -448,6 +503,50 @@ function getEnd(item) {
return util.convert(end, 'Date').valueOf();
}

/**
* @param {vis.Timeline} timeline
* @param {vis.Item} item
* @return {{shouldScroll: bool, scrollOffset: number, itemTop: number}}
*/
function getItemVerticalScroll(timeline, item) {
var leftHeight = timeline.props.leftContainer.height;
var contentHeight = timeline.props.left.height;

var group = item.parent;
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.
Expand Down