Browse Source

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

css_transitions
Eric Gillingham 11 years ago
parent
commit
172ad34aff
10 changed files with 384 additions and 77 deletions
  1. +2
    -0
      HISTORY.md
  2. +3
    -1
      Jakefile.js
  3. +11
    -1
      src/module/imports.js
  4. +52
    -45
      src/timeline/Range.js
  5. +1
    -2
      src/timeline/TimeStep.js
  6. +40
    -18
      src/timeline/Timeline.js
  7. +255
    -0
      src/timeline/component/CustomTime.js
  8. +7
    -6
      src/timeline/component/TimeAxis.js
  9. +6
    -0
      src/timeline/component/css/customtime.css
  10. +7
    -4
      test/timeline.html

+ 2
- 0
HISTORY.md View File

@ -5,6 +5,8 @@ http://visjs.org
- Implemented option `showCurrentTime`, displaying a red, vertical bar at - Implemented option `showCurrentTime`, displaying a red, vertical bar at
current time. Thanks fi0dor. current time. Thanks fi0dor.
- Fixed broken Timeline options `min` and `max`.
- Fixed not being able to load vis.js in node.js.
## 2013-09-20, version 0.2.0 ## 2013-09-20, version 0.2.0

+ 3
- 1
Jakefile.js View File

@ -35,7 +35,8 @@ task('build', {async: true}, function () {
'./src/timeline/component/css/itemset.css', './src/timeline/component/css/itemset.css',
'./src/timeline/component/css/item.css', './src/timeline/component/css/item.css',
'./src/timeline/component/css/timeaxis.css', './src/timeline/component/css/timeaxis.css',
'./src/timeline/component/css/currenttime.css'
'./src/timeline/component/css/currenttime.css',
'./src/timeline/component/css/customtime.css'
], ],
header: '/* vis.js stylesheet */', header: '/* vis.js stylesheet */',
separator: '\n' separator: '\n'
@ -64,6 +65,7 @@ task('build', {async: true}, function () {
'./src/timeline/component/RootPanel.js', './src/timeline/component/RootPanel.js',
'./src/timeline/component/TimeAxis.js', './src/timeline/component/TimeAxis.js',
'./src/timeline/component/CurrentTime.js', './src/timeline/component/CurrentTime.js',
'./src/timeline/component/CustomTime.js',
'./src/timeline/component/ItemSet.js', './src/timeline/component/ItemSet.js',
'./src/timeline/component/item/*.js', './src/timeline/component/item/*.js',
'./src/timeline/component/Group.js', './src/timeline/component/Group.js',

+ 11
- 1
src/module/imports.js View File

@ -5,4 +5,14 @@
// Try to load dependencies from the global window object. // Try to load dependencies from the global window object.
// If not available there, load via require. // If not available there, load via require.
var moment = (typeof window !== 'undefined') && window['moment'] || require('moment'); var moment = (typeof window !== 'undefined') && window['moment'] || require('moment');
var Hammer = (typeof window !== 'undefined') && window['Hammer'] || require('hammerjs');
var Hammer;
if (typeof window !== 'undefined') {
// load hammer.js only when running in a browser (where window is available)
Hammer = window['Hammer'] || require('hammerjs');
}
else {
Hammer = function () {
throw Error('hammer.js is only available in a browser, not in node.js.');
}
}

+ 52
- 45
src/timeline/Range.js View File

@ -8,16 +8,10 @@
*/ */
function Range(options) { function Range(options) {
this.id = util.randomUUID(); this.id = util.randomUUID();
this.start = 0; // Number
this.end = 0; // Number
// this.options = options || {}; // TODO: fix range options
this.options = {
min: null,
max: null,
zoomMin: null,
zoomMax: null
};
this.start = null; // Number
this.end = null; // Number
this.options = options || {};
this.listeners = []; this.listeners = [];
@ -27,8 +21,6 @@ function Range(options) {
/** /**
* Set options for the range controller * Set options for the range controller
* @param {Object} options Available options: * @param {Object} options Available options:
* {Number} start Set start value of the range
* {Number} end Set end value of the range
* {Number} min Minimum value for start * {Number} min Minimum value for start
* {Number} max Maximum value for end * {Number} max Maximum value for end
* {Number} zoomMin Set a minimum value for * {Number} zoomMin Set a minimum value for
@ -39,8 +31,9 @@ function Range(options) {
Range.prototype.setOptions = function (options) { Range.prototype.setOptions = function (options) {
util.extend(this.options, options); util.extend(this.options, options);
if (options.start != null || options.end != null) {
this.setRange(options.start, options.end);
// re-apply range with new limitations
if (this.start !== null && this.end !== null) {
this.setRange(this.start, this.end);
} }
}; };
@ -119,8 +112,8 @@ Range.prototype._trigger = function (event) {
/** /**
* Set a new start and end range * Set a new start and end range
* @param {Number} start
* @param {Number} end
* @param {Number} [start]
* @param {Number} [end]
*/ */
Range.prototype.setRange = function(start, end) { Range.prototype.setRange = function(start, end) {
var changed = this._applyRange(start, end); var changed = this._applyRange(start, end);
@ -134,21 +127,23 @@ Range.prototype.setRange = function(start, end) {
* Set a new start and end range. This method is the same as setRange, but * Set a new start and end range. This method is the same as setRange, but
* does not trigger a range change and range changed event, and it returns * does not trigger a range change and range changed event, and it returns
* true when the range is changed * true when the range is changed
* @param {Number} start
* @param {Number} end
* @param {Number} [start]
* @param {Number} [end]
* @return {Boolean} changed * @return {Boolean} changed
* @private * @private
*/ */
Range.prototype._applyRange = function(start, end) { Range.prototype._applyRange = function(start, end) {
var newStart = (start != null) ? util.convert(start, 'Number') : this.start;
var newEnd = (end != null) ? util.convert(end, 'Number') : this.end;
var diff;
var newStart = (start != null) ? util.convert(start, 'Number') : this.start,
newEnd = (end != null) ? util.convert(end, 'Number') : this.end,
max = (this.options.max != null) ? util.convert(this.options.max, 'Date').valueOf() : null,
min = (this.options.min != null) ? util.convert(this.options.min, 'Date').valueOf() : null,
diff;
// check for valid number // check for valid number
if (isNaN(newStart)) {
if (isNaN(newStart) || newStart === null) {
throw new Error('Invalid start "' + start + '"'); throw new Error('Invalid start "' + start + '"');
} }
if (isNaN(newEnd)) {
if (isNaN(newEnd) || newEnd === null) {
throw new Error('Invalid end "' + end + '"'); throw new Error('Invalid end "' + end + '"');
} }
@ -158,64 +153,76 @@ Range.prototype._applyRange = function(start, end) {
} }
// prevent start < min // prevent start < min
if (this.options.min != null) {
var min = this.options.min.valueOf();
if (min !== null) {
if (newStart < min) { if (newStart < min) {
diff = (min - newStart); diff = (min - newStart);
newStart += diff; newStart += diff;
newEnd += diff; newEnd += diff;
// prevent end > max
if (max != null) {
if (newEnd > max) {
newEnd = max;
}
}
} }
} }
// prevent end > max // prevent end > max
if (this.options.max != null) {
var max = this.options.max.valueOf();
if (max !== null) {
if (newEnd > max) { if (newEnd > max) {
diff = (newEnd - max); diff = (newEnd - max);
newStart -= diff; newStart -= diff;
newEnd -= diff; newEnd -= diff;
// prevent start < min
if (min != null) {
if (newStart < min) {
newStart = min;
}
}
} }
} }
// prevent (end-start) > zoomMin
if (this.options.zoomMin != null) {
var zoomMin = this.options.zoomMin.valueOf();
// prevent (end-start) < zoomMin
if (this.options.zoomMin !== null) {
var zoomMin = parseFloat(this.options.zoomMin);
if (zoomMin < 0) { if (zoomMin < 0) {
zoomMin = 0; zoomMin = 0;
} }
if ((newEnd - newStart) < zoomMin) { if ((newEnd - newStart) < zoomMin) {
if ((this.end - this.start) > zoomMin) {
if ((this.end - this.start) === zoomMin) {
// ignore this action, we are already zoomed to the minimum
newStart = this.start;
newEnd = this.end;
}
else {
// zoom to the minimum // zoom to the minimum
diff = (zoomMin - (newEnd - newStart)); diff = (zoomMin - (newEnd - newStart));
newStart -= diff / 2; newStart -= diff / 2;
newEnd += diff / 2; newEnd += diff / 2;
} }
else {
// ingore this action, we are already zoomed to the minimum
newStart = this.start;
newEnd = this.end;
}
} }
} }
// prevent (end-start) > zoomMin
if (this.options.zoomMax != null) {
var zoomMax = this.options.zoomMax.valueOf();
// prevent (end-start) > zoomMax
if (this.options.zoomMax !== null) {
var zoomMax = parseFloat(this.options.zoomMax);
if (zoomMax < 0) { if (zoomMax < 0) {
zoomMax = 0; zoomMax = 0;
} }
if ((newEnd - newStart) > zoomMax) { if ((newEnd - newStart) > zoomMax) {
if ((this.end - this.start) < zoomMax) {
if ((this.end - this.start) === zoomMax) {
// ignore this action, we are already zoomed to the maximum
newStart = this.start;
newEnd = this.end;
}
else {
// zoom to the maximum // zoom to the maximum
diff = ((newEnd - newStart) - zoomMax); diff = ((newEnd - newStart) - zoomMax);
newStart += diff / 2; newStart += diff / 2;
newEnd -= diff / 2; newEnd -= diff / 2;
} }
else {
// ingore this action, we are already zoomed to the maximum
newStart = this.start;
newEnd = this.end;
}
} }
} }

+ 1
- 2
src/timeline/TimeStep.js View File

@ -63,8 +63,7 @@ TimeStep.SCALE = {
*/ */
TimeStep.prototype.setRange = function(start, end, minimumStep) { TimeStep.prototype.setRange = function(start, end, minimumStep) {
if (!(start instanceof Date) || !(end instanceof Date)) { if (!(start instanceof Date) || !(end instanceof Date)) {
//throw "No legal start or end date in method setRange";
return;
throw "No legal start or end date in method setRange";
} }
this._start = (start != undefined) ? new Date(start.valueOf()) : new Date(); this._start = (start != undefined) ? new Date(start.valueOf()) : new Date();

+ 40
- 18
src/timeline/Timeline.js View File

@ -7,19 +7,21 @@
*/ */
function Timeline (container, items, options) { function Timeline (container, items, options) {
var me = this; var me = this;
this.options = util.extend({
var now = moment().hours(0).minutes(0).seconds(0).milliseconds(0);
this.options = {
orientation: 'bottom', orientation: 'bottom',
min: null, min: null,
max: null, max: null,
zoomMin: 10, // milliseconds
zoomMin: 10, // milliseconds
zoomMax: 1000 * 60 * 60 * 24 * 365 * 10000, // milliseconds zoomMax: 1000 * 60 * 60 * 24 * 365 * 10000, // milliseconds
// moveable: true, // TODO: option moveable // moveable: true, // TODO: option moveable
// zoomable: true, // TODO: option zoomable // zoomable: true, // TODO: option zoomable
showMinorLabels: true, showMinorLabels: true,
showMajorLabels: true, showMajorLabels: true,
showCurrentTime: false, showCurrentTime: false,
showCustomTime: false,
autoResize: false autoResize: false
}, options);
};
// controller // controller
this.controller = new Controller(); this.controller = new Controller();
@ -72,19 +74,13 @@ function Timeline (container, items, options) {
this.controller.add(this.labelPanel); this.controller.add(this.labelPanel);
// range // range
var now = moment().hours(0).minutes(0).seconds(0).milliseconds(0);
this.range = new Range({
start: now.clone().add('days', -3).valueOf(),
end: now.clone().add('days', 4).valueOf()
});
/* TODO: fix range options
var rangeOptions = Object.create(this.options); var rangeOptions = Object.create(this.options);
this.range = new Range(rangeOptions); this.range = new Range(rangeOptions);
this.range.setRange( this.range.setRange(
now.clone().add('days', -3).valueOf(), now.clone().add('days', -3).valueOf(),
now.clone().add('days', 4).valueOf() now.clone().add('days', 4).valueOf()
); );
*/
// TODO: reckon with options moveable and zoomable // TODO: reckon with options moveable and zoomable
this.range.subscribe(this.rootPanel, 'move', 'horizontal'); this.range.subscribe(this.rootPanel, 'move', 'horizontal');
this.range.subscribe(this.rootPanel, 'zoom', 'horizontal'); this.range.subscribe(this.rootPanel, 'zoom', 'horizontal');
@ -114,13 +110,22 @@ function Timeline (container, items, options) {
this.currenttime = new CurrentTime(this.timeaxis, [], rootOptions); this.currenttime = new CurrentTime(this.timeaxis, [], rootOptions);
this.controller.add(this.currenttime); this.controller.add(this.currenttime);
// custom time bar
this.customtime = new CustomTime(this.timeaxis, [], rootOptions);
this.controller.add(this.customtime);
// create itemset or groupset // create itemset or groupset
this.setGroups(null); this.setGroups(null);
this.itemsData = null; // DataSet this.itemsData = null; // DataSet
this.groupsData = null; // DataSet this.groupsData = null; // DataSet
// set data
// apply options
if (options) {
this.setOptions(options);
}
// set data (must be after options are applied)
if (items) { if (items) {
this.setItems(items); this.setItems(items);
} }
@ -131,16 +136,33 @@ function Timeline (container, items, options) {
* @param {Object} options TODO: describe the available options * @param {Object} options TODO: describe the available options
*/ */
Timeline.prototype.setOptions = function (options) { Timeline.prototype.setOptions = function (options) {
if (options) {
util.extend(this.options, options);
}
util.extend(this.options, options);
// TODO: apply range min,max
// force update of range
// options.start and options.end can be undefined
//this.range.setRange(options.start, options.end);
this.range.setRange();
this.controller.reflow(); this.controller.reflow();
this.controller.repaint(); this.controller.repaint();
}; };
/**
* Set a custom time bar
* @param {Date} time
*/
Timeline.prototype.setCustomTime = function (time) {
this.customtime._setCustomTime(time);
};
/**
* Retrieve the current custom time.
* @return {Date} customTime
*/
Timeline.prototype.getCustomTime = function() {
return new Date(this.customtime.customTime.valueOf());
};
/** /**
* Set items * Set items
* @param {vis.DataSet | Array | DataTable | null} items * @param {vis.DataSet | Array | DataTable | null} items
@ -174,7 +196,7 @@ Timeline.prototype.setItems = function(items) {
// apply the data range as range // apply the data range as range
var dataRange = this.getItemRange(); var dataRange = this.getItemRange();
// add 5% on both sides
// add 5% space on both sides
var min = dataRange.min; var min = dataRange.min;
var max = dataRange.max; var max = dataRange.max;
if (min != null && max != null) { if (min != null && max != null) {
@ -189,10 +211,10 @@ Timeline.prototype.setItems = function(items) {
// override specified start and/or end date // override specified start and/or end date
if (this.options.start != undefined) { if (this.options.start != undefined) {
min = new Date(this.options.start.valueOf());
min = util.convert(this.options.start, 'Date');
} }
if (this.options.end != undefined) { if (this.options.end != undefined) {
max = new Date(this.options.end.valueOf());
max = util.convert(this.options.end, 'Date');
} }
// apply range if there is a min or max available // apply range if there is a min or max available

+ 255
- 0
src/timeline/component/CustomTime.js View File

@ -0,0 +1,255 @@
/**
* A custom time bar
* @param {Component} parent
* @param {Component[]} [depends] Components on which this components depends
* (except for the parent)
* @param {Object} [options] Available parameters:
* {Boolean} [showCustomTime]
* @constructor CustomTime
* @extends Component
*/
function CustomTime (parent, depends, options) {
this.id = util.randomUUID();
this.parent = parent;
this.depends = depends;
this.options = options || {};
this.defaultOptions = {
showCustomTime: false
};
this.listeners = [];
this.customTime = new Date();
}
CustomTime.prototype = new Component();
CustomTime.prototype.setOptions = Component.prototype.setOptions;
/**
* Get the container element of the bar, which can be used by a child to
* add its own widgets.
* @returns {HTMLElement} container
*/
CustomTime.prototype.getContainer = function () {
return this.frame;
};
/**
* Repaint the component
* @return {Boolean} changed
*/
CustomTime.prototype.repaint = function () {
var bar = this.frame,
parent = this.parent,
parentContainer = parent.parent.getContainer();
if (!parent) {
throw new Error('Cannot repaint bar: no parent attached');
}
if (!parentContainer) {
throw new Error('Cannot repaint bar: parent has no container element');
}
if (!this.getOption('showCustomTime')) {
if (bar) {
parentContainer.removeChild(bar);
delete this.frame;
}
return;
}
if (!bar) {
bar = document.createElement('div');
bar.className = 'customtime';
bar.style.position = 'absolute';
bar.style.top = '0px';
bar.style.height = '100%';
parentContainer.appendChild(bar);
var drag = document.createElement('div');
drag.style.position = 'relative';
drag.style.top = '0px';
drag.style.left = '-10px';
drag.style.height = '100%';
drag.style.width = '20px';
bar.appendChild(drag);
this.frame = bar;
this.subscribe(this, 'movetime');
}
if (!parent.conversion) {
parent._updateConversion();
}
var x = parent.toScreen(this.customTime);
bar.style.left = x + 'px';
bar.title = 'Time: ' + this.customTime;
return false;
};
/**
* Set custom time.
* @param {Date} time
*/
CustomTime.prototype._setCustomTime = function(time) {
this.customTime = new Date(time.valueOf());
this.repaint();
};
/**
* Retrieve the current custom time.
* @return {Date} customTime
*/
CustomTime.prototype._getCustomTime = function() {
return new Date(this.customTime.valueOf());
};
/**
* Add listeners for mouse and touch events to the component
* @param {Component} component
*/
CustomTime.prototype.subscribe = function (component, event) {
var me = this;
var listener = {
component: component,
event: event,
callback: function (event) {
me._onMouseDown(event, listener);
},
params: {}
};
component.on('mousedown', listener.callback);
me.listeners.push(listener);
};
/**
* Event handler
* @param {String} event name of the event, for example 'click', 'mousemove'
* @param {function} callback callback handler, invoked with the raw HTML Event
* as parameter.
*/
CustomTime.prototype.on = function (event, callback) {
var bar = this.frame;
if (!bar) {
throw new Error('Cannot add event listener: no parent attached');
}
events.addListener(this, event, callback);
util.addEventListener(bar, event, callback);
};
/**
* Start moving horizontally
* @param {Event} event
* @param {Object} listener Listener containing the component and params
* @private
*/
CustomTime.prototype._onMouseDown = function(event, listener) {
event = event || window.event;
var params = listener.params;
// only react on left mouse button down
var leftButtonDown = event.which ? (event.which == 1) : (event.button == 1);
if (!leftButtonDown) {
return;
}
// get mouse position
params.mouseX = util.getPageX(event);
params.moved = false;
params.customTime = this.customTime;
// add event listeners to handle moving the custom time bar
var me = this;
if (!params.onMouseMove) {
params.onMouseMove = function (event) {
me._onMouseMove(event, listener);
};
util.addEventListener(document, 'mousemove', params.onMouseMove);
}
if (!params.onMouseUp) {
params.onMouseUp = function (event) {
me._onMouseUp(event, listener);
};
util.addEventListener(document, 'mouseup', params.onMouseUp);
}
util.stopPropagation(event);
util.preventDefault(event);
};
/**
* Perform moving operating.
* This function activated from within the funcion CustomTime._onMouseDown().
* @param {Event} event
* @param {Object} listener
* @private
*/
CustomTime.prototype._onMouseMove = function (event, listener) {
event = event || window.event;
var params = listener.params;
var parent = this.parent;
// calculate change in mouse position
var mouseX = util.getPageX(event);
if (params.mouseX === undefined) {
params.mouseX = mouseX;
}
var diff = mouseX - params.mouseX;
// if mouse movement is big enough, register it as a "moved" event
if (Math.abs(diff) >= 1) {
params.moved = true;
}
var x = parent.toScreen(params.customTime);
var xnew = x + diff;
var time = parent.toTime(xnew);
this._setCustomTime(time);
// fire a timechange event
events.trigger(this, 'timechange', {customTime: this.customTime});
util.preventDefault(event);
};
/**
* Stop moving operating.
* This function activated from within the function CustomTime._onMouseDown().
* @param {event} event
* @param {Object} listener
* @private
*/
CustomTime.prototype._onMouseUp = function (event, listener) {
event = event || window.event;
var params = listener.params;
// remove event listeners here, important for Safari
if (params.onMouseMove) {
util.removeEventListener(document, 'mousemove', params.onMouseMove);
params.onMouseMove = null;
}
if (params.onMouseUp) {
util.removeEventListener(document, 'mouseup', params.onMouseUp);
params.onMouseUp = null;
}
if (params.moved) {
// fire a timechanged event
events.trigger(this, 'timechanged', {customTime: this.customTime});
}
};

+ 7
- 6
src/timeline/component/TimeAxis.js View File

@ -487,12 +487,13 @@ TimeAxis.prototype.reflow = function () {
// calculate range and step // calculate range and step
this._updateConversion(); this._updateConversion();
var start = util.convert(range.start, 'Date'),
end = util.convert(range.end, 'Date'),
minimumStep = this.toTime((props.minorCharWidth || 10) * 5) - this.toTime(0);
this.step = new TimeStep(start, end, minimumStep);
changed += update(props.range, 'start', start.valueOf());
changed += update(props.range, 'end', end.valueOf());
var start = util.convert(range.start, 'Number'),
end = util.convert(range.end, 'Number'),
minimumStep = this.toTime((props.minorCharWidth || 10) * 5).valueOf()
-this.toTime(0).valueOf();
this.step = new TimeStep(new Date(start), new Date(end), minimumStep);
changed += update(props.range, 'start', start);
changed += update(props.range, 'end', end);
changed += update(props.range, 'minimumStep', minimumStep.valueOf()); changed += update(props.range, 'minimumStep', minimumStep.valueOf());
} }

+ 6
- 0
src/timeline/component/css/customtime.css View File

@ -0,0 +1,6 @@
.vis.timeline .customtime {
background-color: #6E94FF;
width: 2px;
cursor: move;
z-index: 9;
}

+ 7
- 4
test/timeline.html View File

@ -2,6 +2,7 @@
<html> <html>
<head> <head>
<title></title> <title></title>
<script src="../node_modules/moment/moment.js"></script>
<script src="../vis.js"></script> <script src="../vis.js"></script>
<style type="text/css"> <style type="text/css">
@ -62,11 +63,13 @@
var container = document.getElementById('visualization'); var container = document.getElementById('visualization');
var options = { var options = {
//orientation: 'top', //orientation: 'top',
//start: now.clone().add('days', -7).valueOf(),
//end: now.clone().add('days', 7).valueOf(),
start: now.clone().add('days', -7),
end: now.clone().add('days', 7),
//maxHeight: 200, //maxHeight: 200,
min: moment('2013-01-01').valueOf(),
max: moment('2013-12-31').valueOf(),
//start: moment('2013-01-01'),
//end: moment('2013-12-31'),
min: moment('2013-01-01'),
max: moment('2013-12-31'),
zoomMin: 1000 * 60 * 60 * 24, // 1 day zoomMin: 1000 * 60 * 60 * 24, // 1 day
zoomMax: 1000 * 60 * 60 * 24 * 30 * 6 // 6 months zoomMax: 1000 * 60 * 60 * 24 * 30 * 6 // 6 months
}; };

Loading…
Cancel
Save