Browse Source

Implemented animated range change for functions `fit`, `focus`, `setSelection`, and `setWindow`.

v3_develop
jos 10 years ago
parent
commit
1d70335b1a
6 changed files with 144 additions and 29 deletions
  1. +2
    -0
      HISTORY.md
  2. +19
    -7
      docs/timeline.html
  3. +20
    -6
      lib/timeline/Core.js
  4. +66
    -10
      lib/timeline/Range.js
  5. +20
    -6
      lib/timeline/Timeline.js
  6. +17
    -0
      lib/util.js

+ 2
- 0
HISTORY.md View File

@ -15,6 +15,8 @@ http://visjs.org
on screen.
- Implemented an option `focus` for `setSelection(ids, options)`, to immediately
focus selected nodes.
- Implemented animated range change for functions `fit`, `focus`, `setSelection`,
and `setWindow`.
### Network

+ 19
- 7
docs/timeline.html View File

@ -737,16 +737,23 @@ timeline.clear({options: true}); // clear options only
</tr>
<tr>
<td>fit()</td>
<td>fit([options])</td>
<td>none</td>
<td>Adjust the visible window such that it fits all items. See also function <code>focus(id)</code>.
Available options:
<ul>
<li><code>animate: boolean | number</code><br>If true (default), the range is animated smoothly to the new window. If a number, the number is taken as duration for the animation. Default duration is 500 ms.</li>
</ul>
</td>
</tr>
<tr>
<td>focus(id | ids)</td>
<td>focus(id | ids [, options])</td>
<td>none</td>
<td>Adjust the visible window such that the selected item (or multiple items) are centered on screen. See also function <code>fit()</code>.
<td>Adjust the visible window such that the selected item (or multiple items) are centered on screen. See also function <code>fit()</code>. Available options:
<ul>
<li><code>animate: boolean | number</code><br>If true (default), the range is animated smoothly to the new window. If a number, the number is taken as duration for the animation. Default duration is 500 ms.</li>
</ul>
</td>
</tr>
@ -829,19 +836,24 @@ timeline.clear({options: true}); // clear options only
</tr>
<tr>
<td>setSelection([ids [, options]])</td>
<td>setSelection(id | ids [, options])</td>
<td>none</td>
<td>Select one or multiple items by their id. The currently selected items will be unselected. To unselect all selected items, call `setSelection([])`. Available options:
<ul>
<li><code>focus: boolean</code>. If true, focus will be set to the selected item(s)</li>
<li><code>focus: boolean</code><br>If true, focus will be set to the selected item(s)</li>
<li><code>animate: boolean | number</code><br>If true (default), the range is animated smoothly to the new window. If a number, the number is taken as duration for the animation. Default duration is 500 ms. Only applicable when option focus is true.</li>
</ul>
</td>
</tr>
<tr>
<td>setWindow(start, end)</td>
<td>setWindow(start, end [, options])</td>
<td>none</td>
<td>Set the current visible window. The parameters <code>start</code> and <code>end</code> can be a <code>Date</code>, <code>Number</code>, or <code>String</code>. If the parameter value of <code>start</code> or <code>end</code> is null, the parameter will be left unchanged.</td>
<td>Set the current visible window. The parameters <code>start</code> and <code>end</code> can be a <code>Date</code>, <code>Number</code>, or <code>String</code>. If the parameter value of <code>start</code> or <code>end</code> is null, the parameter will be left unchanged. Available options:
<ul>
<li><code>animate: boolean | number</code><br>If true (default), the range is animated smoothly to the new window. If a number, the number is taken as duration for the animation. Default duration is 500 ms.</li>
</ul>
</td>
</tr>
</table>

+ 20
- 6
lib/timeline/Core.js View File

@ -325,8 +325,14 @@ Core.prototype.clear = function(what) {
/**
* Set Core window such that it fits all items
* @param {Object} [options] Available options:
* `animate: boolean | number`
* If true (default), the range is animated
* smoothly to the new window.
* If a number, the number is taken as duration
* for the animation. Default duration is 500 ms.
*/
Core.prototype.fit = function() {
Core.prototype.fit = function(options) {
// apply the data range as range
var dataRange = this.getItemRange();
@ -348,7 +354,8 @@ Core.prototype.fit = function() {
return;
}
this.range.setRange(start, end);
var animate = (options && options.animate !== undefined) ? options.animate : true;
this.range.setRange(start, end, animate);
};
@ -363,15 +370,22 @@ Core.prototype.fit = function() {
* object with properties start and end.
*
* @param {Date | Number | String | Object} [start] Start date of visible window
* @param {Date | Number | String} [end] End date of visible window
* @param {Date | Number | String} [end] End date of visible window
* @param {Object} [options] Available options:
* `animate: boolean | number`
* If true (default), the range is animated
* smoothly to the new window.
* If a number, the number is taken as duration
* for the animation. Default duration is 500 ms.
*/
Core.prototype.setWindow = function(start, end) {
Core.prototype.setWindow = function(start, end, options) {
var animate = (options && options.animate !== undefined) ? options.animate : true;
if (arguments.length == 1) {
var range = arguments[0];
this.range.setRange(range.start, range.end);
this.range.setRange(range.start, range.end, animate);
}
else {
this.range.setRange(start, end);
this.range.setRange(start, end, animate);
}
};

+ 66
- 10
lib/timeline/Range.js View File

@ -35,6 +35,7 @@ function Range(body, options) {
this.props = {
touch: {}
};
this.animateTimer = null;
// drag listeners for dragging
this.body.emitter.on('dragstart', this._onDragStart.bind(this));
@ -76,7 +77,7 @@ Range.prototype = new Component();
Range.prototype.setOptions = function (options) {
if (options) {
// copy the options that we know
var fields = ['direction', 'min', 'max', 'zoomMin', 'zoomMax', 'moveable', 'zoomable'];
var fields = ['direction', 'min', 'max', 'zoomMin', 'zoomMax', 'moveable', 'zoomable', 'activate'];
util.selectiveExtend(fields, this.options, options);
if ('start' in options || 'end' in options) {
@ -101,16 +102,69 @@ function validateDirection (direction) {
* Set a new start and end range
* @param {Number} [start]
* @param {Number} [end]
* @param {boolean | number} [animate=false] If true, the range is animated
* smoothly to the new window.
* If animate is a number, the
* number is taken as duration
* Default duration is 500 ms.
*
*/
Range.prototype.setRange = function(start, end) {
var changed = this._applyRange(start, end);
if (changed) {
var params = {
start: new Date(this.start),
end: new Date(this.end)
};
this.body.emitter.emit('rangechange', params);
this.body.emitter.emit('rangechanged', params);
Range.prototype.setRange = function(start, end, animate) {
this._cancelAnimation();
if (animate) {
var me = this;
var initStart = this.start;
var initEnd = this.end;
var duration = typeof animate === 'number' ? animate : 500;
var initTime = new Date().valueOf();
var anyChanged = false;
function next() {
if (!me.props.touch.dragging) {
var now = new Date().valueOf();
var time = now - initTime;
var s = util.easeInOutQuad(time, initStart, start, duration);
var e = util.easeInOutQuad(time, initEnd, end, duration);
changed = me._applyRange(s, e);
anyChanged = anyChanged || changed;
if (changed) {
me.body.emitter.emit('rangechange', {start: new Date(s), end: new Date(e)});
}
if (time <= duration) {
// animate with as high as possible frame rate, leave 20 ms in between
// each to prevent the browser from blocking
me.animateTimer = setTimeout(next, 20);
}
else {
// done
if (anyChanged) {
me.body.emitter.emit('rangechanged', {start: new Date(me.start), end: new Date(me.end)});
}
}
}
}
return next();
}
else {
var changed = this._applyRange(start, end);
if (changed) {
var params = {start: new Date(this.start), end: new Date(this.end)};
this.body.emitter.emit('rangechange', params);
this.body.emitter.emit('rangechanged', params);
}
}
};
/**
* Stop an animation
* @private
*/
Range.prototype._cancelAnimation = function () {
if (this.animateTimer) {
clearTimeout(this.animateTimer);
this.animateTimer = null;
}
};
@ -284,6 +338,7 @@ Range.prototype._onDragStart = function(event) {
this.props.touch.start = this.start;
this.props.touch.end = this.end;
this.props.touch.dragging = true;
if (this.body.dom.root) {
this.body.dom.root.style.cursor = 'move';
@ -327,6 +382,7 @@ Range.prototype._onDragEnd = function (event) {
// when releasing the fingers in opposite order from the touch screen
if (!this.props.touch.allowDragging) return;
this.props.touch.dragging = false;
if (this.body.dom.root) {
this.body.dom.root.style.cursor = 'auto';
}

+ 20
- 6
lib/timeline/Timeline.js View File

@ -139,7 +139,7 @@ Timeline.prototype.setItems = function(items) {
var start = ('start' in this.options) ? util.convert(this.options.start, 'Date') : null;
var end = ('end' in this.options) ? util.convert(this.options.end, 'Date') : null;
this.setWindow(start, end);
this.setWindow(start, end, false);
}
};
@ -172,14 +172,20 @@ Timeline.prototype.setGroups = function(groups) {
* selected. If ids is an empty array, all items will be
* unselected.
* @param {Object} [options] Available options:
* `focus: boolean` If true, focus will be set
* to the selected item(s)
* `focus: boolean`
* If true, focus will be set to the selected item(s)
* `animate: boolean | number`
* If true (default), the range is animated
* smoothly to the new window.
* If a number, the number is taken as duration
* for the animation. Default duration is 500 ms.
* Only applicable when option focus is true.
*/
Timeline.prototype.setSelection = function(ids, options) {
this.itemSet && this.itemSet.setSelection(ids);
if (options && options.focus) {
this.focus(ids);
this.focus(ids, options);
}
};
@ -195,8 +201,15 @@ Timeline.prototype.getSelection = function() {
* Adjust the visible window such that the selected item (or multiple items)
* are centered on screen.
* @param {String | String[]} id An item id or array with item ids
* @param {Object} [options] Available options:
* `animate: boolean | number`
* If true (default), the range is animated
* smoothly to the new window.
* If a number, the number is taken as duration
* for the animation. Default duration is 500 ms.
* Only applicable when option focus is true
*/
Timeline.prototype.focus = function(id) {
Timeline.prototype.focus = function(id, options) {
if (!this.itemsData || id == undefined) return;
var ids = Array.isArray(id) ? id : [id];
@ -229,7 +242,8 @@ Timeline.prototype.focus = function(id) {
var middle = (start + end) / 2;
var interval = Math.max((this.range.end - this.range.start), (end - start) * 1.1);
this.range.setRange(middle - interval / 2, middle + interval / 2);
var animate = (options && options.animate !== undefined) ? options.animate : true;
this.range.setRange(middle - interval / 2, middle + interval / 2, animate);
};
/**

+ 17
- 0
lib/util.js View File

@ -1243,4 +1243,21 @@ exports.binarySearchGeneric = function(orderedItems, target, field, sidePreferen
}
}
return guess;
};
/**
* Quadratic ease-in-out
* http://gizma.com/easing/
* @param {number} t Current time
* @param {number} start Start value
* @param {number} end End value
* @param {number} duration Duration
* @returns {number} Value corresponding with current time
*/
exports.easeInOutQuad = function (t, start, end, duration) {
var change = end - start;
t /= duration/2;
if (t < 1) return change/2*t*t + start;
t--;
return -change/2 * (t*(t-2) - 1) + start;
};

Loading…
Cancel
Save