Browse Source

Merge remote-tracking branch 'origin/develop' into develop

v3_develop
Alex de Mulder 10 years ago
parent
commit
311f1c84db
9 changed files with 697 additions and 497 deletions
  1. +7
    -0
      HISTORY.md
  2. +479
    -464
      docs/graph2d.html
  3. +35
    -8
      docs/timeline.html
  4. +48
    -6
      lib/timeline/Core.js
  5. +66
    -10
      lib/timeline/Range.js
  6. +20
    -6
      lib/timeline/Timeline.js
  7. +23
    -1
      lib/timeline/component/CurrentTime.js
  8. +2
    -2
      lib/timeline/component/CustomTime.js
  9. +17
    -0
      lib/util.js

+ 7
- 0
HISTORY.md View File

@ -15,6 +15,12 @@ http://visjs.org
on screen. on screen.
- Implemented an option `focus` for `setSelection(ids, options)`, to immediately - Implemented an option `focus` for `setSelection(ids, options)`, to immediately
focus selected nodes. focus selected nodes.
- Implemented animated range change for functions `fit`, `focus`, `setSelection`,
and `setWindow`.
- Implemented functions `setCurrentTime(date)` and `getCurrentTime()`.
- Fixed the `change` event sometimes being fired twice on IE10.
- Fixed canceling moving an item to another group did not move the item
back to the original group.
### Network ### Network
@ -32,6 +38,7 @@ http://visjs.org
- Added 'customRange' for the Y axis and an example showing how it works. - Added 'customRange' for the Y axis and an example showing how it works.
- Added localization support. - Added localization support.
- Implemented option `clickToUse`. - Implemented option `clickToUse`.
- Implemented functions `setCurrentTime(date)` and `getCurrentTime()`.
- Fixed bugs. - Fixed bugs.
- Added groups.visibility functionality and an example showing how it works. - Added groups.visibility functionality and an example showing how it works.

+ 479
- 464
docs/graph2d.html
File diff suppressed because it is too large
View File


+ 35
- 8
docs/timeline.html View File

@ -737,16 +737,30 @@ timeline.clear({options: true}); // clear options only
</tr> </tr>
<tr> <tr>
<td>fit()</td>
<td>fit([options])</td>
<td>none</td> <td>none</td>
<td>Adjust the visible window such that it fits all items. See also function <code>focus(id)</code>. <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> </td>
</tr> </tr>
<tr> <tr>
<td>focus(id | ids)</td>
<td>focus(id | ids [, options])</td>
<td>none</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>
<tr>
<td>getCurrentTime()</td>
<td>Date</td>
<td>Get the current time. Only applicable when option <code>showCurrentTime</code> is true.
</td> </td>
</tr> </tr>
@ -757,10 +771,18 @@ timeline.clear({options: true}); // clear options only
</td> </td>
</tr> </tr>
<tr>
<td>setCurrentTime(time)</td>
<td>none</td>
<td>Set a current time. This can be used for example to ensure that a client's time is synchronized with a shared server time.
<code>time</code> can be a Date object, numeric timestamp, or ISO date string.
Only applicable when option <code>showCurrentTime</code> is true.</td>
</tr>
<tr> <tr>
<td>setCustomTime(time)</td> <td>setCustomTime(time)</td>
<td>none</td> <td>none</td>
<td>Adjust the custom time bar. Only applicable when the option <code>showCustomTime</code> is true. <code>time</code> is a Date object.
<td>Adjust the custom time bar. Only applicable when the option <code>showCustomTime</code> is true. <code>time</code> can be a Date object, numeric timestamp, or ISO date string.
</td> </td>
</tr> </tr>
@ -829,19 +851,24 @@ timeline.clear({options: true}); // clear options only
</tr> </tr>
<tr> <tr>
<td>setSelection([ids [, options]])</td>
<td>setSelection(id | ids [, options])</td>
<td>none</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: <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> <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> </ul>
</td> </td>
</tr> </tr>
<tr> <tr>
<td>setWindow(start, end)</td>
<td>setWindow(start, end [, options])</td>
<td>none</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> </tr>
</table> </table>

+ 48
- 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 * 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 // apply the data range as range
var dataRange = this.getItemRange(); var dataRange = this.getItemRange();
@ -348,7 +354,8 @@ Core.prototype.fit = function() {
return; 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. * object with properties start and end.
* *
* @param {Date | Number | String | Object} [start] Start date of visible window * @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) { if (arguments.length == 1) {
var range = arguments[0]; var range = arguments[0];
this.range.setRange(range.start, range.end);
this.range.setRange(range.start, range.end, animate);
} }
else { else {
this.range.setRange(start, end);
this.range.setRange(start, end, animate);
} }
}; };
@ -536,6 +550,34 @@ Core.prototype.repaint = function () {
throw new Error('Function repaint is deprecated. Use redraw instead.'); throw new Error('Function repaint is deprecated. Use redraw instead.');
}; };
/**
* Set a current time. This can be used for example to ensure that a client's
* time is synchronized with a shared server time.
* Only applicable when option `showCurrentTime` is true.
* @param {Date | String | Number} time A Date, unix timestamp, or
* ISO date string.
*/
Core.prototype.setCurrentTime = function(time) {
if (!this.currentTime) {
throw new Error('Option showCurrentTime must be true');
}
this.currentTime.setCurrentTime(time);
};
/**
* Get the current time.
* Only applicable when option `showCurrentTime` is true.
* @return {Date} Returns the current time.
*/
Core.prototype.getCurrentTime = function() {
if (!this.currentTime) {
throw new Error('Option showCurrentTime must be true');
}
return this.currentTime.getCurrentTime();
};
/** /**
* Convert a position on screen (pixels) to a datetime * Convert a position on screen (pixels) to a datetime
* @param {int} x Position on the screen in pixels * @param {int} x Position on the screen in pixels

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

@ -35,6 +35,7 @@ function Range(body, options) {
this.props = { this.props = {
touch: {} touch: {}
}; };
this.animateTimer = null;
// drag listeners for dragging // drag listeners for dragging
this.body.emitter.on('dragstart', this._onDragStart.bind(this)); this.body.emitter.on('dragstart', this._onDragStart.bind(this));
@ -76,7 +77,7 @@ Range.prototype = new Component();
Range.prototype.setOptions = function (options) { Range.prototype.setOptions = function (options) {
if (options) { if (options) {
// copy the options that we know // 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); util.selectiveExtend(fields, this.options, options);
if ('start' in options || 'end' in options) { if ('start' in options || 'end' in options) {
@ -101,16 +102,69 @@ function validateDirection (direction) {
* Set a new start and end range * Set a new start and end range
* @param {Number} [start] * @param {Number} [start]
* @param {Number} [end] * @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.start = this.start;
this.props.touch.end = this.end; this.props.touch.end = this.end;
this.props.touch.dragging = true;
if (this.body.dom.root) { if (this.body.dom.root) {
this.body.dom.root.style.cursor = 'move'; 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 // when releasing the fingers in opposite order from the touch screen
if (!this.props.touch.allowDragging) return; if (!this.props.touch.allowDragging) return;
this.props.touch.dragging = false;
if (this.body.dom.root) { if (this.body.dom.root) {
this.body.dom.root.style.cursor = 'auto'; 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 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; 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 * selected. If ids is an empty array, all items will be
* unselected. * unselected.
* @param {Object} [options] Available options: * @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) { Timeline.prototype.setSelection = function(ids, options) {
this.itemSet && this.itemSet.setSelection(ids); this.itemSet && this.itemSet.setSelection(ids);
if (options && options.focus) { 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) * Adjust the visible window such that the selected item (or multiple items)
* are centered on screen. * are centered on screen.
* @param {String | String[]} id An item id or array with item ids * @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; if (!this.itemsData || id == undefined) return;
var ids = Array.isArray(id) ? id : [id]; var ids = Array.isArray(id) ? id : [id];
@ -229,7 +242,8 @@ Timeline.prototype.focus = function(id) {
var middle = (start + end) / 2; var middle = (start + end) / 2;
var interval = Math.max((this.range.end - this.range.start), (end - start) * 1.1); 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);
}; };
/** /**

+ 23
- 1
lib/timeline/component/CurrentTime.js View File

@ -22,6 +22,7 @@ function CurrentTime (body, options) {
locale: 'en' locale: 'en'
}; };
this.options = util.extend({}, this.defaultOptions); this.options = util.extend({}, this.defaultOptions);
this.offset = 0;
this._create(); this._create();
@ -83,7 +84,7 @@ CurrentTime.prototype.redraw = function() {
this.start(); this.start();
} }
var now = new Date();
var now = new Date(new Date().valueOf() + this.offset);
var x = this.body.util.toScreen(now); var x = this.body.util.toScreen(now);
var locale = this.options.locales[this.options.locale]; var locale = this.options.locales[this.options.locale];
@ -138,4 +139,25 @@ CurrentTime.prototype.stop = function() {
} }
}; };
/**
* Set a current time. This can be used for example to ensure that a client's
* time is synchronized with a shared server time.
* @param {Date | String | Number} time A Date, unix timestamp, or
* ISO date string.
*/
CurrentTime.prototype.setCurrentTime = function(time) {
var t = util.convert(time, 'Date').valueOf();
var now = new Date().valueOf();
this.offset = t - now;
this.redraw();
};
/**
* Get the current time.
* @return {Date} Returns the current time.
*/
CurrentTime.prototype.getCurrentTime = function() {
return new Date(new Date().valueOf() + this.offset);
};
module.exports = CurrentTime; module.exports = CurrentTime;

+ 2
- 2
lib/timeline/component/CustomTime.js View File

@ -125,10 +125,10 @@ CustomTime.prototype.redraw = function () {
/** /**
* Set custom time. * Set custom time.
* @param {Date} time
* @param {Date | number | string} time
*/ */
CustomTime.prototype.setCustomTime = function(time) { CustomTime.prototype.setCustomTime = function(time) {
this.customTime = new Date(time.valueOf());
this.customTime = util.convert(time, 'Date');
this.redraw(); this.redraw();
}; };

+ 17
- 0
lib/util.js View File

@ -1243,4 +1243,21 @@ exports.binarySearchGeneric = function(orderedItems, target, field, sidePreferen
} }
} }
return guess; 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