Browse Source

working arrays to hide, fixed zoom, fixed pinch, made seperate volatile object for dynamic date support.

v3_develop
Alex de Mulder 10 years ago
parent
commit
af5af63558
11 changed files with 729 additions and 375 deletions
  1. +425
    -244
      dist/vis.js
  2. +15
    -18
      examples/timeline/hiding_times.html
  3. +1
    -0
      index.js
  4. +29
    -33
      lib/timeline/Core.js
  5. +159
    -0
      lib/timeline/DateUtil.js
  6. +52
    -43
      lib/timeline/Range.js
  7. +6
    -15
      lib/timeline/TimeStep.js
  8. +1
    -0
      lib/timeline/Timeline.js
  9. +39
    -20
      lib/timeline/component/Group.js
  10. +1
    -1
      lib/timeline/component/ItemSet.js
  11. +1
    -1
      lib/timeline/component/TimeAxis.js

+ 425
- 244
dist/vis.js
File diff suppressed because it is too large
View File


+ 15
- 18
examples/timeline/hiding_times.html View File

@ -21,31 +21,28 @@
// Create a DataSet (allows two way data-binding)
var items = new vis.DataSet([
{id: 1, content: 'item 1', start: '2014-04-20'},
{id: 2, content: 'item 2', start: '2014-04-14'},
{id: 1, content: 'item 1', start: '2014-04-19'},
{id: 2, content: 'item 2', start: '2014-04-21'},
{id: 3, content: 'item 3', start: '2014-04-18'},
{id: 4, content: 'item 4', start: '2014-04-16', end: '2014-04-19'},
{id: 4, content: 'item 4', start: '2014-04-16', end: '2014-04-24'},
{id: 5, content: 'item 5', start: '2014-04-26 12:00:00'},
{id: 6, content: 'item 6', start: '2014-04-27', type: 'point'}
]);
// Configuration for the Timeline
var options = {
hide:{
start: '2014-04-20 00:00:00',
end: '2014-04-26 00:00:00'},
// } [
// {
// start: '2014-04-20 00:00:00',
// end: '2014-04-26 00:00:00'
// },
// {
// start: '2014-05-20 00:00:00',
// end: '2014-05-26 00:00:00'
// }
// ],
start: '2014-04-01',
end: '2014-05-10',
hide: [
{
start: '2014-04-20 20:00:00',
end: '2014-04-21 9:00:00'
},
{
start: '2014-04-05 00:00:00',
end: '2014-04-10 00:00:00'
}
],
start: '2014-04-17',
end: '2014-05-01',
height: '200px'
};

+ 1
- 0
index.js View File

@ -21,6 +21,7 @@ exports.graph3d = {
exports.Timeline = require('./lib/timeline/Timeline');
exports.Graph2d = require('./lib/timeline/Graph2d');
exports.timeline = {
DateUtil: require('./lib/timeline/DateUtil'),
DataStep: require('./lib/timeline/DataStep'),
Range: require('./lib/timeline/Range'),
stack: require('./lib/timeline/Stack'),

+ 29
- 33
lib/timeline/Core.js View File

@ -9,6 +9,7 @@ var CurrentTime = require('./component/CurrentTime');
var CustomTime = require('./component/CustomTime');
var ItemSet = require('./component/ItemSet');
var Activator = require('../shared/Activator');
var DateUtil = require('./DateUtil');
/**
* Create a timeline visualization
@ -176,6 +177,10 @@ Core.prototype.setOptions = function (options) {
var fields = ['width', 'height', 'minHeight', 'maxHeight', 'autoResize', 'start', 'end', 'orientation', 'clickToUse', 'dataAttributes', 'hide'];
util.selectiveExtend(fields, this.options, options);
if ('hide' in this.options) {
DateUtil.convertHiddenOptions(this);
}
if ('clickToUse' in options) {
if (options.clickToUse) {
this.activator = new Activator(this.dom.root);
@ -616,22 +621,23 @@ Core.prototype.getCurrentTime = function() {
*/
// TODO: move this function to Range
Core.prototype._toTime = function(x) {
var startDate = new Date(this.options.hide.start).getTime();
var endDate = new Date(this.options.hide.end).getTime();
var duration = endDate - startDate;
if (!(startDate >= this.range.start && endDate < this.range.end)) {
duration = 0;
}
var conversion = this.range.conversion(this.props.center.width, duration);
var time = new Date(x / conversion.scale + conversion.offset);
if (time >= endDate && startDate >= this.range.start && endDate < this.range.end) {
time -= duration;
}
return time;
return DateUtil.toTime(this.body, this.range, x, this.props.center.width);
//var startDate = new Date(this.options.hide.start).getTime();
//var endDate = new Date(this.options.hide.end).getTime();
//var duration = endDate - startDate;
//if (!(startDate >= this.range.start && endDate < this.range.end)) {
// duration = 0;
//}
//
//var conversion = this.range.conversion(this.props.center.width, duration);
//var time = new Date(x / conversion.scale + conversion.offset);
//
//if (time >= endDate && startDate >= this.range.start && endDate < this.range.end) {
// time -= duration;
//}
//
//
//return time;
};
/**
@ -642,8 +648,9 @@ Core.prototype._toTime = function(x) {
*/
// TODO: move this function to Range
Core.prototype._toGlobalTime = function(x) {
var conversion = this.range.conversion(this.props.root.width);
return new Date(x / conversion.scale + conversion.offset);
return DateUtil.toTime(this.body, this.range, x, this.props.root.width);
//var conversion = this.range.conversion(this.props.root.width);
//return new Date(x / conversion.scale + conversion.offset);
};
/**
@ -655,19 +662,7 @@ Core.prototype._toGlobalTime = function(x) {
*/
// TODO: move this function to Range
Core.prototype._toScreen = function(time) {
var startDate = new Date(this.options.hide.start).getTime();
var endDate = new Date(this.options.hide.end).getTime();
var duration = endDate - startDate;
// if time after the cutout, and the
if (time >= endDate && startDate >= this.range.start && endDate < this.range.end) {
time -= duration;
}
else if (!(startDate >= this.range.start && endDate < this.range.end)) {
duration = 0;
}
var conversion = this.range.conversion(this.props.center.width, duration);
return (time.valueOf() - conversion.offset) * conversion.scale;
return DateUtil.toScreen(this, time, this.props.center.width);
};
@ -682,8 +677,9 @@ Core.prototype._toScreen = function(time) {
*/
// TODO: move this function to Range
Core.prototype._toGlobalScreen = function(time) {
var conversion = this.range.conversion(this.props.root.width);
return (time.valueOf() - conversion.offset) * conversion.scale;
return DateUtil.toScreen(this, time, this.props.root.width);
//var conversion = this.range.conversion(this.props.root.width);
//return (time.valueOf() - conversion.offset) * conversion.scale;
};

+ 159
- 0
lib/timeline/DateUtil.js View File

@ -0,0 +1,159 @@
/**
* Created by Alex on 10/3/2014.
*/
var moment = require('../module/moment');
exports.convertHiddenOptions = function(timeline) {
var hiddenTimes = timeline.options.hide;
if (Array.isArray(hiddenTimes) == true) {
for (var i = 0; i < hiddenTimes.length; i++) {
var dateItem = {};
dateItem.start = moment(hiddenTimes[i].start).toDate().valueOf();
dateItem.end = moment(hiddenTimes[i].end).toDate().valueOf();
timeline.body.hiddenDates.push(dateItem);
}
timeline.body.hiddenDates.sort(function(a,b) {return a.start - b.start;}); // sort by start time
}
else {
timeline.body.hiddenDates = [{
start:moment(hiddenTimes.start).toDate().valueOf(),
end:moment(hiddenTimes.end).toDate().valueOf()
}
];
}
}
exports.stepOverHiddenDates = function(timeStep, previousTime) {
var stepInHidden = false;
var currentValue = timeStep.current.valueOf();
for (var i = 0; i < timeStep.hiddenDates.length; i++) {
var startDate = timeStep.hiddenDates[i].start;
var endDate = timeStep.hiddenDates[i].end;
if (currentValue >= startDate && currentValue < endDate) {
stepInHidden = true;
break;
}
}
if (stepInHidden == true && currentValue < timeStep._end.valueOf() && currentValue != previousTime) {
timeStep.current = moment(endDate).toDate();
}
}
exports.toScreen = function(timeline, time, width) {
var hidden = exports.isHidden(time, timeline.body.hiddenDates)
if (hidden.hidden == true) {
time = hidden.startDate;
}
var res = exports.correctTimeForDuration(timeline.body.hiddenDates, timeline.range, time);
var duration = res.duration;
time = res.time;
var conversion = timeline.range.conversion(width, duration);
return (time.valueOf() - conversion.offset) * conversion.scale;
}
exports.toTime = function(body, range, x, width) {
var duration = exports.getHiddenDuration(body.hiddenDates, range);
var conversion = range.conversion(width, duration);
var time = new Date(x / conversion.scale + conversion.offset);
//var hidden = exports.isHidden(time, timeline.body.hiddenDates)
//if (hidden.hidden == true) {
// time = hidden.startDate;
//}
//time = exports.correctTimeForDuration(body.hiddenDates, range, time).time;
return time;
}
exports.getHiddenDuration = function(hiddenTimes, range) {
var duration = 0;
for (var i = 0; i < hiddenTimes.length; i++) {
var startDate = hiddenTimes[i].start;
var endDate = hiddenTimes[i].end;
// if time after the cutout, and the
if (startDate >= range.start && endDate < range.end) {
duration += endDate - startDate;
}
}
return duration;
}
exports.correctTimeForDuration = function(hiddenTimes, range, time) {
var duration = 0;
var timeOffset = 0;
time = moment(time).toDate().valueOf()
for (var i = 0; i < hiddenTimes.length; i++) {
var startDate = hiddenTimes[i].start;
var endDate = hiddenTimes[i].end;
// if time after the cutout, and the
if (startDate >= range.start && endDate < range.end) {
duration += (endDate - startDate);
if (time >= endDate) {
timeOffset += (endDate - startDate);
}
}
}
time -= timeOffset;
return {duration: duration, time:time, offset: timeOffset};
}
exports.snapAwayFromHidden = function(hiddenTimes, range, start, end, delta, zoom) {
zoom = zoom || false;
var newStart = start;
var newEnd = end;
for (var i = 0; i < hiddenTimes.length; i++) {
var startDate = hiddenTimes[i].start;
var endDate = hiddenTimes[i].end;
if (start >= startDate && start < endDate) { // if the start is entering a hidden zone
range.deltaDifference += delta;
if (range.previousDelta - delta > 0 && zoom == false || zoom == true && range.previousDelta - delta < 0) { // from the left
console.log("start from left, snap to right")
newStart = endDate + 1;
}
else { // from the right
console.log("start from right, snap to left")
newStart = startDate - 1;
}
return {newStart: newStart, newEnd: newEnd};
}
else if (end >= startDate && end < endDate) { // if the start is entering a hidden zone
range.deltaDifference += delta;
if (range.previousDelta - delta < 0) { // from the right
console.log("end from right, snap to left")
newEnd = startDate - 1;
}
else { // from the left
console.log("end from left, snap to right")
newEnd = endDate + 1;
}
return {newStart: newStart, newEnd: newEnd};
}
}
return false;
}
exports.isHidden = function(time, hiddenTimes) {
var isHidden = false;
for (var i = 0; i < hiddenTimes.length; i++) {
var startDate = hiddenTimes[i].start;
var endDate = hiddenTimes[i].end;
if (time >= startDate && time < endDate) { // if the start is entering a hidden zone
isHidden = true;
break;
}
}
return {hidden: isHidden, startDate: startDate, endDate: endDate};
}

+ 52
- 43
lib/timeline/Range.js View File

@ -2,6 +2,7 @@ var util = require('../util');
var hammerUtil = require('../hammerUtil');
var moment = require('../module/moment');
var Component = require('./component/Component');
var DateUtil = require('./DateUtil');
/**
* @constructor Range
@ -78,7 +79,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', 'activate','hide'];
var fields = ['direction', 'min', 'max', 'zoomMin', 'zoomMax', 'moveable', 'zoomable', 'activate', 'hide'];
util.selectiveExtend(fields, this.options, options);
if ('start' in options || 'end' in options) {
@ -375,51 +376,30 @@ Range.prototype._onDrag = function (event) {
delta -= this.deltaDifference;
var interval = (this.props.touch.end - this.props.touch.start);
// normalize dragging speed if cutout is in between.
var startDate = new Date(this.options.hide.start).getTime();
var endDate = new Date(this.options.hide.end).getTime();
var duration = endDate - startDate;
if (startDate >= this.start && endDate < this.end) {
interval -= duration;
}
var duration = DateUtil.getHiddenDuration(this.body.hiddenDates, this);
interval -= duration;
var width = (direction == 'horizontal') ? this.body.domProps.center.width : this.body.domProps.center.height;
var diffRange = -delta / width * interval;
var newStart = this.props.touch.start + diffRange;
var newEnd = this.props.touch.end + diffRange;
// snapping times away from hidden zones
var start = this.props.touch.start + diffRange;
var end = this.props.touch.end + diffRange;
if (start >= startDate && start < endDate && this.previousDelta - delta > 0) { // if the start is entering the zone from the left
this.deltaDifference += delta;
this.props.touch.start = endDate + 1;
this.props.touch.end = end + duration; // to cancel the time subtraction events;
this._onDrag(event);
return;
}
else if (start >= startDate && start < endDate && this.previousDelta - delta < 0) { // if the start is entering the zone from the right
this.deltaDifference += delta;
this.props.touch.start = startDate - 1;
this.props.touch.end = end - duration; // to cancel the time subtraction events;
this._onDrag(event);
return;
}
else if (end >= startDate && end < endDate && this.previousDelta - delta > 0) { // if the start is entering the zone from the right
this.deltaDifference += delta;
this.props.touch.end = endDate + 1;
this.props.touch.start = start; // to cancel the time subtraction events;
this._onDrag(event);
return;
}
else if (end >= startDate && end < endDate && this.previousDelta - delta < 0) { // if the start is entering the zone from the right
this.deltaDifference += delta;
this.props.touch.end = startDate-1;
this.props.touch.start = start; // to cancel the time subtraction events;
var safeDates = DateUtil.snapAwayFromHidden(this.body.hiddenDates, this, newStart, newEnd, delta);
if (safeDates !== false) {
this.props.touch.start = safeDates.newStart;
this.props.touch.end = safeDates.newEnd;
this._onDrag(event);
return;
}
this.previousDelta = delta;
this._applyRange(start, end);
this._applyRange(newStart, newEnd);
// fire a rangechange event
this.body.emitter.emit('rangechange', {
@ -494,7 +474,7 @@ Range.prototype._onMouseWheel = function(event) {
pointer = getPointer(gesture.center, this.body.dom.center),
pointerDate = this._pointerToDate(pointer);
this.zoom(scale, pointerDate);
this.zoom(scale, pointerDate, delta);
}
// Prevent default actions caused by mouse wheel
@ -537,12 +517,24 @@ Range.prototype._onPinch = function (event) {
this.props.touch.center = getPointer(event.gesture.center, this.body.dom.center);
}
var scale = 1 / event.gesture.scale,
initDate = this._pointerToDate(this.props.touch.center);
var scale = 1 / event.gesture.scale;
var center = this._pointerToDate(this.props.touch.center);
var hiddenDuration = DateUtil.getHiddenDuration(this.body.hiddenDates, this);
// calculate new start and end
var newStart = parseInt(initDate + (this.props.touch.start - initDate) * scale);
var newEnd = parseInt(initDate + (this.props.touch.end - initDate) * scale);
var newStart = center + (this.props.touch.start - center) * scale;
var newEnd = (center+hiddenDuration) + (this.props.touch.end - (center+hiddenDuration)) * scale;
this.previousDelta = 1;
var safeDates = DateUtil.snapAwayFromHidden(this.body.hiddenDates, this, newStart, newEnd, event.gesture.scale, true);
if (safeDates !== false) {
this.props.touch.start = safeDates.newStart;
this.props.touch.end = safeDates.newEnd;
newStart = safeDates.newStart;
newEnd = safeDates.newEnd;
}
// apply new range
this.setRange(newStart, newEnd);
@ -563,7 +555,10 @@ Range.prototype._pointerToDate = function (pointer) {
if (direction == 'horizontal') {
var width = this.body.domProps.center.width;
conversion = this.conversion(width);
var duration = DateUtil.getHiddenDuration(this.body.hiddenDates, this);
//return DateUtil.toTime(this.body, this, pointer.x, width);
conversion = this.conversion(width, duration);
//console.log(new Date(pointer.x / conversion.scale + conversion.offset + duration));
return pointer.x / conversion.scale + conversion.offset;
}
else {
@ -597,19 +592,33 @@ function getPointer (touch, element) {
* @param {Number} [center] Value representing a date around which will
* be zoomed.
*/
Range.prototype.zoom = function(scale, center) {
Range.prototype.zoom = function(scale, center, delta) {
// if centerDate is not provided, take it half between start Date and end Date
if (center == null) {
center = (this.start + this.end) / 2;
}
var hiddenDuration = DateUtil.getHiddenDuration(this.body.hiddenDates, this);
// calculate new start and end
var newStart = center + (this.start - center) * scale;
var newEnd = center + (this.end - center) * scale;
var newEnd = (center+hiddenDuration) + (this.end - (center+hiddenDuration)) * scale;
this.previousDelta = 0;
// snapping times away from hidden zones
var safeDates = DateUtil.snapAwayFromHidden(this.body.hiddenDates, this, newStart, newEnd, delta, true);
//console.log(new Date(this.start), new Date(this.end), new Date(newStart), new Date(newEnd),new Date(safeDates.newStart), new Date(safeDates.newEnd));
if (safeDates !== false) {
newStart = safeDates.newStart;
newEnd = safeDates.newEnd;
}
this.setRange(newStart, newEnd);
};
/**
* Move the range with a given delta to the left or right. Start and end
* value will be adjusted. For example, try delta = 0.1 or -0.1

+ 6
- 15
lib/timeline/TimeStep.js View File

@ -1,4 +1,5 @@
var moment = require('../module/moment');
var DateUtil = require('./DateUtil');
/**
* @constructor TimeStep
@ -26,7 +27,7 @@ var moment = require('../module/moment');
* @param {Date} [end] The end date
* @param {Number} [minimumStep] Optional. Minimum step size in milliseconds
*/
function TimeStep(start, end, minimumStep, hide) {
function TimeStep(start, end, minimumStep, hiddenDates) {
// variables
this.current = new Date();
this._start = new Date();
@ -39,9 +40,9 @@ function TimeStep(start, end, minimumStep, hide) {
// initialize the range
this.setRange(start, end, minimumStep);
this.hide = hide;
if (hide === undefined) {
this.hide = [];
this.hiddenDates = hiddenDates;
if (hiddenDates === undefined) {
this.hiddenDates = [];
}
}
@ -196,17 +197,7 @@ TimeStep.prototype.next = function() {
this.current = new Date(this._end.valueOf());
}
var startDate = new Date(this.hide.start).getTime();
var endDate = new Date(this.hide.end).getTime();
if (this.current.valueOf() >= startDate &&
this.current.valueOf() < endDate &&
this.current.valueOf() < this._end.valueOf() &&
this.current.valueOf() != prev) {
//this.current = endDate;
this.next();
}
DateUtil.stepOverHiddenDates(this, prev);
};

+ 1
- 0
lib/timeline/Timeline.js View File

@ -59,6 +59,7 @@ function Timeline (container, items, groups, options) {
off: this.off.bind(this),
emit: this.emit.bind(this)
},
hiddenDates: [],
util: {
snap: null, // will be specified after TimeAxis is created
toScreen: me._toScreen.bind(me),

+ 39
- 20
lib/timeline/component/Group.js View File

@ -1,6 +1,7 @@
var util = require('../../util');
var stack = require('../Stack');
var RangeItem = require('./item/RangeItem');
var DateUtil = require('../DateUtil');
/**
* @constructor Group
@ -395,18 +396,29 @@ Group.prototype._updateVisibleItems = function(orderedItems, visibleItems, range
* @private
*/
Group.prototype._checkIfInvisible = function(item, visibleItems, range) {
if (item.isVisible(range)) {
if (!item.displayed) item.show();
item.repositionX();
if (visibleItems.indexOf(item) == -1) {
visibleItems.push(item);
//if (DateUtil.isHidden(item.data.start,this.itemSet.body.hiddenDates).hidden == false) {
if (item.isVisible(range)) {
if (!item.displayed) item.show();
item.repositionX();
if (visibleItems.indexOf(item) == -1) {
visibleItems.push(item);
}
return false;
}
return false;
}
else {
if (item.displayed) item.hide();
return true;
}
else {
if (item.displayed) item.hide();
return true;
}
//}
//else {
// if (item.isVisible(range)) {
// return false;
// }
// else {
// if (item.displayed) item.hide();
// return true;
// }
//}
};
/**
@ -421,15 +433,22 @@ Group.prototype._checkIfInvisible = function(item, visibleItems, range) {
* @private
*/
Group.prototype._checkIfVisible = function(item, visibleItems, range) {
if (item.isVisible(range)) {
if (!item.displayed) item.show();
// reposition item horizontally
item.repositionX();
visibleItems.push(item);
}
else {
if (item.displayed) item.hide();
}
//if (DateUtil.isHidden(item.data.start,this.itemSet.body.hiddenDates).hidden == false) {
if (item.isVisible(range)) {
if (!item.displayed) item.show();
// reposition item horizontally
item.repositionX();
visibleItems.push(item);
}
else {
if (item.displayed) item.hide();
}
//}
//else {
// if (!item.isVisible(range)) {
// if (item.displayed) item.hide();
// }
//}
};
module.exports = Group;

+ 1
- 1
lib/timeline/component/ItemSet.js View File

@ -264,7 +264,7 @@ ItemSet.prototype._create = function(){
ItemSet.prototype.setOptions = function(options) {
if (options) {
// copy all options that we know
var fields = ['type', 'align', 'orientation', 'padding', 'stack', 'selectable', 'groupOrder', 'dataAttributes', 'template'];
var fields = ['type', 'align', 'orientation', 'padding', 'stack', 'selectable', 'groupOrder', 'dataAttributes', 'template','hide'];
util.selectiveExtend(fields, this.options, options);
if ('margin' in options) {

+ 1
- 1
lib/timeline/component/TimeAxis.js View File

@ -178,7 +178,7 @@ TimeAxis.prototype._repaintLabels = function () {
end = util.convert(this.body.range.end, 'Number'),
minimumStep = this.body.util.toTime((this.props.minorCharWidth || 10) * 7).valueOf()
-this.body.util.toTime(0).valueOf();
var step = new TimeStep(new Date(start), new Date(end), minimumStep, this.options.hide);
var step = new TimeStep(new Date(start), new Date(end), minimumStep, this.body.hiddenDates);
this.step = step;
// Move all DOM elements to a "redundant" list, where they

Loading…
Cancel
Save