Browse Source

Only visible items are rendered now

css_transitions
josdejong 11 years ago
parent
commit
040e53b2ee
11 changed files with 1049 additions and 589 deletions
  1. +4
    -4
      examples/timeline/04_a_lot_of_data.html
  2. +48
    -3
      src/component/item/item.js
  3. +146
    -105
      src/component/item/itembox.js
  4. +83
    -47
      src/component/item/itempoint.js
  5. +121
    -83
      src/component/item/itemrange.js
  6. +31
    -23
      src/component/itemset.js
  7. +0
    -1
      src/controller.js
  8. +26
    -4
      src/stack.js
  9. +66
    -25
      src/visualization/timeline.js
  10. +520
    -290
      vis.js
  11. +4
    -4
      vis.min.js

+ 4
- 4
examples/timeline/04_a_lot_of_data.html View File

@ -10,9 +10,6 @@
} }
#visualization { #visualization {
box-sizing: border-box;
width: 100%;
height: 300px;
} }
#visualization .itemset { #visualization .itemset {
@ -60,7 +57,10 @@
var container = document.getElementById('visualization'); var container = document.getElementById('visualization');
var options = { var options = {
start: now.clone().add('days', -3).valueOf(), start: now.clone().add('days', -3).valueOf(),
end: now.clone().add('days', 7).valueOf()
end: now.clone().add('days', 11).valueOf(),
//maxHeight: 300,
//height: '300px',
//orientation: 'top'
}; };
var timeline = new vis.Timeline(container, data, options); var timeline = new vis.Timeline(container, data, options);

+ 48
- 3
src/component/item/item.js View File

@ -10,12 +10,14 @@ function Item (parent, data, options) {
this.parent = parent; this.parent = parent;
this.data = data; this.data = data;
this.selected = false; this.selected = false;
this.visible = true;
this.dom = null; this.dom = null;
this.options = options; this.options = options;
}
Item.prototype = new Component();
this.top = 0;
this.left = 0;
this.width = 0;
this.height = 0;
}
/** /**
* Select current item * Select current item
@ -30,3 +32,46 @@ Item.prototype.select = function () {
Item.prototype.unselect = function () { Item.prototype.unselect = function () {
this.selected = false; this.selected = false;
}; };
/**
* Show the Item in the DOM (when not already visible)
* @return {Boolean} changed
*/
Item.prototype.show = function () {
return false;
};
/**
* Hide the Item from the DOM (when visible)
* @return {Boolean} changed
*/
Item.prototype.hide = function () {
return false;
};
/**
* Determine whether the item is visible in its parent window.
* @return {Boolean} visible
*/
Item.prototype.isVisible = function () {
// should be implemented by the item
return false;
};
/**
* Repaint the item
* @return {Boolean} changed
*/
Item.prototype.repaint = function () {
// should be implemented by the item
return false;
};
/**
* Reflow the item
* @return {Boolean} resized
*/
Item.prototype.reflow = function () {
// should be implemented by the item
return false;
};

+ 146
- 105
src/component/item/itembox.js View File

@ -55,90 +55,129 @@ ItemBox.prototype.repaint = function () {
var changed = false; var changed = false;
var dom = this.dom; var dom = this.dom;
if (this.visible) {
if (!dom) {
this._create();
if (!dom) {
this._create();
dom = this.dom;
changed = true;
}
if (dom) {
if (!this.options && !this.parent) {
throw new Error('Cannot repaint item: no parent attached');
}
var foreground = this.parent.getForeground();
if (!foreground) {
throw new Error('Cannot repaint time axis: ' +
'parent has no foreground container element');
}
var background = this.parent.getBackground();
if (!background) {
throw new Error('Cannot repaint time axis: ' +
'parent has no background container element');
}
var axis = this.parent.getAxis();
if (!background) {
throw new Error('Cannot repaint time axis: ' +
'parent has no axis container element');
}
if (!dom.box.parentNode) {
foreground.appendChild(dom.box);
changed = true;
}
if (!dom.line.parentNode) {
background.appendChild(dom.line);
changed = true;
}
if (!dom.dot.parentNode) {
axis.appendChild(dom.dot);
changed = true; changed = true;
} }
dom = this.dom;
if (dom) {
if (!this.options && !this.parent) {
throw new Error('Cannot repaint item: no parent attached');
// update contents
if (this.data.content != this.content) {
this.content = this.data.content;
if (this.content instanceof Element) {
dom.content.innerHTML = '';
dom.content.appendChild(this.content);
} }
var foreground = this.parent.getForeground();
if (!foreground) {
throw new Error('Cannot repaint time axis: ' +
'parent has no foreground container element');
else if (this.data.content != undefined) {
dom.content.innerHTML = this.content;
} }
var background = this.parent.getBackground();
if (!background) {
throw new Error('Cannot repaint time axis: ' +
'parent has no background container element');
else {
throw new Error('Property "content" missing in item ' + this.data.id);
} }
changed = true;
}
if (!dom.box.parentNode) {
foreground.appendChild(dom.box);
changed = true;
}
if (!dom.line.parentNode) {
background.appendChild(dom.line);
changed = true;
}
if (!dom.dot.parentNode) {
this.parent.dom.axis.appendChild(dom.dot);
changed = true;
}
// update class
var className = (this.data.className? ' ' + this.data.className : '') +
(this.selected ? ' selected' : '');
if (this.className != className) {
this.className = className;
dom.box.className = 'item box' + className;
dom.line.className = 'item line' + className;
dom.dot.className = 'item dot' + className;
changed = true;
}
}
// update contents
if (this.data.content != this.content) {
this.content = this.data.content;
if (this.content instanceof Element) {
dom.content.innerHTML = '';
dom.content.appendChild(this.content);
}
else if (this.data.content != undefined) {
dom.content.innerHTML = this.content;
}
else {
throw new Error('Property "content" missing in item ' + this.data.id);
}
changed = true;
}
return changed;
};
// update class
var className = (this.data.className? ' ' + this.data.className : '') +
(this.selected ? ' selected' : '');
if (this.className != className) {
this.className = className;
dom.box.className = 'item box' + className;
dom.line.className = 'item line' + className;
dom.dot.className = 'item dot' + className;
changed = true;
}
}
/**
* Show the item in the DOM (when not already visible). The items DOM will
* be created when needed.
* @return {Boolean} changed
*/
ItemBox.prototype.show = function () {
if (!this.dom || !this.dom.box.parentNode) {
return this.repaint();
} }
else { else {
// hide when visible
if (dom) {
if (dom.box.parentNode) {
dom.box.parentNode.removeChild(dom.box);
changed = true;
}
if (dom.line.parentNode) {
dom.line.parentNode.removeChild(dom.line);
changed = true;
}
if (dom.dot.parentNode) {
dom.dot.parentNode.removeChild(dom.dot);
changed = true;
}
}
return false;
} }
};
/**
* Hide the item from the DOM (when visible)
* @return {Boolean} changed
*/
ItemBox.prototype.hide = function () {
var changed = false,
dom = this.dom;
if (dom) {
if (dom.box.parentNode) {
dom.box.parentNode.removeChild(dom.box);
changed = true;
}
if (dom.line.parentNode) {
dom.line.parentNode.removeChild(dom.line);
}
if (dom.dot.parentNode) {
dom.dot.parentNode.removeChild(dom.dot);
}
}
return changed; return changed;
}; };
/**
* Determine whether the item is visible in its parent window.
* @return {Boolean} visible
*/
// TODO: determine isVisible during the reflow
ItemBox.prototype.isVisible = function () {
var data = this.data,
range = this.parent && this.parent.range;
if (data && range) {
// TODO: account for the width of the item. Take some margin
return (data.start > range.start) && (data.start < range.end);
}
else {
return false;
}
};
/** /**
* Reflow the item: calculate its actual size and position from the DOM * Reflow the item: calculate its actual size and position from the DOM
* @return {boolean} resized returns true if the axis is resized * @return {boolean} resized returns true if the axis is resized
@ -160,49 +199,51 @@ ItemBox.prototype.reflow = function () {
top, top,
left; left;
if (dom) {
changed += update(props.dot, 'height', dom.dot.offsetHeight);
changed += update(props.dot, 'width', dom.dot.offsetWidth);
changed += update(props.line, 'width', dom.line.offsetWidth);
changed += update(props.line, 'width', dom.line.offsetWidth);
changed += update(this, 'width', dom.box.offsetWidth);
changed += update(this, 'height', dom.box.offsetHeight);
if (align == 'right') {
left = start - this.width;
}
else if (align == 'left') {
left = start;
}
else {
// default or 'center'
left = start - this.width / 2;
}
changed += update(this, 'left', left);
if (this.isVisible()) {
if (dom) {
changed += update(props.dot, 'height', dom.dot.offsetHeight);
changed += update(props.dot, 'width', dom.dot.offsetWidth);
changed += update(props.line, 'width', dom.line.offsetWidth);
changed += update(props.line, 'width', dom.line.offsetWidth);
changed += update(this, 'width', dom.box.offsetWidth);
changed += update(this, 'height', dom.box.offsetHeight);
if (align == 'right') {
left = start - this.width;
}
else if (align == 'left') {
left = start;
}
else {
// default or 'center'
left = start - this.width / 2;
}
update(this, 'left', left);
changed += update(props.line, 'left', start - props.line.width / 2);
changed += update(props.dot, 'left', start - props.dot.width / 2);
if (orientation == 'top') {
top = options.margin.axis;
update(props.line, 'left', start - props.line.width / 2);
update(props.dot, 'left', start - props.dot.width / 2);
if (orientation == 'top') {
top = options.margin.axis;
update(this, 'top', top);
update(props.line, 'top', 0);
update(props.line, 'height', top);
update(props.dot, 'top', -props.dot.height / 2);
}
else {
// default or 'bottom'
var parentHeight = this.parent.height;
top = parentHeight - this.height - options.margin.axis;
changed += update(this, 'top', top);
changed += update(props.line, 'top', 0);
changed += update(props.line, 'height', top);
changed += update(props.dot, 'top', -props.dot.height / 2);
update(this, 'top', top);
update(props.line, 'top', top + this.height);
update(props.line, 'height', Math.max(options.margin.axis, 0));
update(props.dot, 'top', parentHeight - props.dot.height / 2);
}
} }
else { else {
// default or 'bottom'
var parentHeight = this.parent.height;
top = parentHeight - this.height - options.margin.axis;
changed += update(this, 'top', top);
changed += update(props.line, 'top', top + this.height);
changed += update(props.line, 'height', Math.max(options.margin.axis, 0));
changed += update(props.dot, 'top', parentHeight - props.dot.height / 2);
changed += 1;
} }
} }
else {
changed += 1;
}
return (changed > 0); return (changed > 0);
}; };

+ 83
- 47
src/component/item/itempoint.js View File

@ -52,68 +52,104 @@ ItemPoint.prototype.repaint = function () {
var changed = false; var changed = false;
var dom = this.dom; var dom = this.dom;
if (this.visible) {
if (!dom) {
this._create();
if (!dom) {
this._create();
dom = this.dom;
changed = true;
}
if (dom) {
if (!this.options && !this.options.parent) {
throw new Error('Cannot repaint item: no parent attached');
}
var foreground = this.parent.getForeground();
if (!foreground) {
throw new Error('Cannot repaint time axis: ' +
'parent has no foreground container element');
}
if (!dom.point.parentNode) {
foreground.appendChild(dom.point);
foreground.appendChild(dom.point);
changed = true; changed = true;
} }
dom = this.dom;
if (dom) {
if (!this.options && !this.options.parent) {
throw new Error('Cannot repaint item: no parent attached');
// update contents
if (this.data.content != this.content) {
this.content = this.data.content;
if (this.content instanceof Element) {
dom.content.innerHTML = '';
dom.content.appendChild(this.content);
} }
var foreground = this.parent.getForeground();
if (!foreground) {
throw new Error('Cannot repaint time axis: ' +
'parent has no foreground container element');
else if (this.data.content != undefined) {
dom.content.innerHTML = this.content;
} }
if (!dom.point.parentNode) {
foreground.appendChild(dom.point);
foreground.appendChild(dom.point);
changed = true;
}
// update contents
if (this.data.content != this.content) {
this.content = this.data.content;
if (this.content instanceof Element) {
dom.content.innerHTML = '';
dom.content.appendChild(this.content);
}
else if (this.data.content != undefined) {
dom.content.innerHTML = this.content;
}
else {
throw new Error('Property "content" missing in item ' + this.data.id);
}
changed = true;
else {
throw new Error('Property "content" missing in item ' + this.data.id);
} }
changed = true;
}
// update class
var className = (this.data.className? ' ' + this.data.className : '') +
(this.selected ? ' selected' : '');
if (this.className != className) {
this.className = className;
dom.point.className = 'item point' + className;
changed = true;
}
// update class
var className = (this.data.className? ' ' + this.data.className : '') +
(this.selected ? ' selected' : '');
if (this.className != className) {
this.className = className;
dom.point.className = 'item point' + className;
changed = true;
} }
} }
return changed;
};
/**
* Show the item in the DOM (when not already visible). The items DOM will
* be created when needed.
* @return {Boolean} changed
*/
ItemPoint.prototype.show = function () {
if (!this.dom || !this.dom.point.parentNode) {
return this.repaint();
}
else { else {
// hide when visible
if (dom) {
if (dom.point.parentNode) {
dom.point.parentNode.removeChild(dom.point);
changed = true;
}
}
return false;
} }
};
/**
* Hide the item from the DOM (when visible)
* @return {Boolean} changed
*/
ItemPoint.prototype.hide = function () {
var changed = false,
dom = this.dom;
if (dom) {
if (dom.point.parentNode) {
dom.point.parentNode.removeChild(dom.point);
changed = true;
}
}
return changed; return changed;
}; };
/**
* Determine whether the item is visible in its parent window.
* @return {Boolean} visible
*/
// TODO: determine isVisible during the reflow
ItemPoint.prototype.isVisible = function () {
var data = this.data,
range = this.parent && this.parent.range;
if (data && range) {
// TODO: account for the width of the item. Take some margin
return (data.start > range.start) && (data.start < range.end);
}
else {
return false;
}
};
/** /**
* Reflow the item: calculate its actual size from the DOM * Reflow the item: calculate its actual size from the DOM
* @return {boolean} resized returns true if the axis is resized * @return {boolean} resized returns true if the axis is resized

+ 121
- 83
src/component/item/itemrange.js View File

@ -47,66 +47,102 @@ ItemRange.prototype.repaint = function () {
var changed = false; var changed = false;
var dom = this.dom; var dom = this.dom;
if (this.visible) {
if (!dom) {
this._create();
if (!dom) {
this._create();
dom = this.dom;
changed = true;
}
if (dom) {
if (!this.options && !this.options.parent) {
throw new Error('Cannot repaint item: no parent attached');
}
var foreground = this.parent.getForeground();
if (!foreground) {
throw new Error('Cannot repaint time axis: ' +
'parent has no foreground container element');
}
if (!dom.box.parentNode) {
foreground.appendChild(dom.box);
changed = true; changed = true;
} }
dom = this.dom;
if (dom) {
if (!this.options && !this.options.parent) {
throw new Error('Cannot repaint item: no parent attached');
// update content
if (this.data.content != this.content) {
this.content = this.data.content;
if (this.content instanceof Element) {
dom.content.innerHTML = '';
dom.content.appendChild(this.content);
} }
var foreground = this.parent.getForeground();
if (!foreground) {
throw new Error('Cannot repaint time axis: ' +
'parent has no foreground container element');
}
if (!dom.box.parentNode) {
foreground.appendChild(dom.box);
changed = true;
else if (this.data.content != undefined) {
dom.content.innerHTML = this.content;
} }
// update content
if (this.data.content != this.content) {
this.content = this.data.content;
if (this.content instanceof Element) {
dom.content.innerHTML = '';
dom.content.appendChild(this.content);
}
else if (this.data.content != undefined) {
dom.content.innerHTML = this.content;
}
else {
throw new Error('Property "content" missing in item ' + this.data.id);
}
changed = true;
else {
throw new Error('Property "content" missing in item ' + this.data.id);
} }
changed = true;
}
// update class
var className = this.data.className ? ('' + this.data.className) : '';
if (this.className != className) {
this.className = className;
dom.box.className = 'item range' + className;
changed = true;
}
// update class
var className = this.data.className ? ('' + this.data.className) : '';
if (this.className != className) {
this.className = className;
dom.box.className = 'item range' + className;
changed = true;
} }
} }
return changed;
};
/**
* Show the item in the DOM (when not already visible). The items DOM will
* be created when needed.
* @return {Boolean} changed
*/
ItemRange.prototype.show = function () {
if (!this.dom || !this.dom.box.parentNode) {
return this.repaint();
}
else { else {
// hide when visible
if (dom) {
if (dom.box.parentNode) {
dom.box.parentNode.removeChild(dom.box);
changed = true;
}
}
return false;
} }
};
/**
* Hide the item from the DOM (when visible)
* @return {Boolean} changed
*/
ItemRange.prototype.hide = function () {
var changed = false,
dom = this.dom;
if (dom) {
if (dom.box.parentNode) {
dom.box.parentNode.removeChild(dom.box);
changed = true;
}
}
return changed; return changed;
}; };
/**
* Determine whether the item is visible in its parent window.
* @return {Boolean} visible
*/
// TODO: determine isVisible during the reflow
ItemRange.prototype.isVisible = function () {
var data = this.data,
range = this.parent && this.parent.range;
if (data && range) {
// TODO: account for the width of the item. Take some margin
return (data.start < range.end) && (data.end > range.start);
}
else {
return false;
}
};
/** /**
* Reflow the item: calculate its actual size from the DOM * Reflow the item: calculate its actual size from the DOM
* @return {boolean} resized returns true if the axis is resized * @return {boolean} resized returns true if the axis is resized
@ -128,52 +164,54 @@ ItemRange.prototype.reflow = function () {
end = parent.toScreen(this.data.end), end = parent.toScreen(this.data.end),
changed = 0; changed = 0;
if (dom) {
var update = util.updateProperty,
box = dom.box,
parentWidth = parent.width,
orientation = options.orientation,
contentLeft,
top;
if (this.isVisible()) {
if (dom) {
var update = util.updateProperty,
box = dom.box,
parentWidth = parent.width,
orientation = options.orientation,
contentLeft,
top;
changed += update(props.content, 'width', dom.content.offsetWidth);
changed += update(props.content, 'width', dom.content.offsetWidth);
changed += update(this, 'height', box.offsetHeight);
changed += update(this, 'height', box.offsetHeight);
// limit the width of the this, as browsers cannot draw very wide divs
if (start < -parentWidth) {
start = -parentWidth;
}
if (end > 2 * parentWidth) {
end = 2 * parentWidth;
}
// limit the width of the this, as browsers cannot draw very wide divs
if (start < -parentWidth) {
start = -parentWidth;
}
if (end > 2 * parentWidth) {
end = 2 * parentWidth;
}
// when range exceeds left of the window, position the contents at the left of the visible area
if (start < 0) {
contentLeft = Math.min(-start,
(end - start - props.content.width - 2 * options.padding));
// TODO: remove the need for options.padding. it's terrible.
}
else {
contentLeft = 0;
}
changed += update(props.content, 'left', contentLeft);
// when range exceeds left of the window, position the contents at the left of the visible area
if (start < 0) {
contentLeft = Math.min(-start,
(end - start - props.content.width - 2 * options.padding));
// TODO: remove the need for options.padding. it's terrible.
}
else {
contentLeft = 0;
}
changed += update(props.content, 'left', contentLeft);
if (orientation == 'top') {
top = options.margin.axis;
changed += update(this, 'top', top);
}
else {
// default or 'bottom'
top = parent.height - this.height - options.margin.axis;
changed += update(this, 'top', top);
}
if (orientation == 'top') {
top = options.margin.axis;
changed += update(this, 'top', top);
changed += update(this, 'left', start);
changed += update(this, 'width', Math.max(end - start, 1)); // TODO: reckon with border width;
} }
else { else {
// default or 'bottom'
top = parent.height - this.height - options.margin.axis;
changed += update(this, 'top', top);
changed += 1;
} }
changed += update(this, 'left', start);
changed += update(this, 'width', Math.max(end - start, 1)); // TODO: reckon with border width;
}
else {
changed += 1;
} }
return (changed > 0); return (changed > 0);

+ 31
- 23
src/component/itemset.js View File

@ -202,14 +202,13 @@ ItemSet.prototype.repaint = function () {
if (item) { if (item) {
// update item // update item
if (!constructor || !(item instanceof constructor)) { if (!constructor || !(item instanceof constructor)) {
// item type has changed, delete the item
item.visible = false;
changed += item.repaint();
// item type has changed, hide and delete the item
changed += item.hide();
item = null; item = null;
} }
else { else {
item.data = itemData; // TODO: create a method item.setData ? item.data = itemData; // TODO: create a method item.setData ?
changed += item.repaint();
changed++;
} }
} }
@ -217,7 +216,7 @@ ItemSet.prototype.repaint = function () {
// create item // create item
if (constructor) { if (constructor) {
item = new constructor(me, itemData, options); item = new constructor(me, itemData, options);
changed += item.repaint();
changed++;
} }
else { else {
throw new TypeError('Unknown item type "' + type + '"'); throw new TypeError('Unknown item type "' + type + '"');
@ -231,9 +230,8 @@ ItemSet.prototype.repaint = function () {
case 'remove': case 'remove':
if (item) { if (item) {
// TODO: remove dom of the item
item.visible = false;
changed += item.repaint();
// remove DOM of the item
changed += item.hide();
} }
// update lists // update lists
@ -246,9 +244,15 @@ ItemSet.prototype.repaint = function () {
} }
}); });
// reposition all items
// reposition all items. Show items only when in the visible area
util.forEach(this.items, function (item) { util.forEach(this.items, function (item) {
item.reposition();
if (item.isVisible()) {
changed += item.show();
item.reposition();
}
else {
changed += item.hide();
}
}); });
return (changed > 0); return (changed > 0);
@ -270,6 +274,14 @@ ItemSet.prototype.getBackground = function () {
return this.dom.background; return this.dom.background;
}; };
/**
* Get the axis container element
* @return {HTMLElement} axis
*/
ItemSet.prototype.getAxis = function () {
return this.dom.axis;
};
/** /**
* Reflow the component * Reflow the component
* @return {Boolean} resized * @return {Boolean} resized
@ -296,34 +308,29 @@ ItemSet.prototype.reflow = function () {
var height; var height;
if (options.height != null) { if (options.height != null) {
height = frame.offsetHeight; height = frame.offsetHeight;
if (maxHeight != null) {
height = Math.min(height, maxHeight);
}
changed += update(this, 'height', height);
} }
else { else {
// height is not specified, determine the height from the height and positioned items // height is not specified, determine the height from the height and positioned items
var frameHeight = this.height;
height = 0; height = 0;
var visibleItems = this.stack.ordered; // TODO: not so nice way to get the filtered items
if (options.orientation == 'top') { if (options.orientation == 'top') {
util.forEach(this.items, function (item) {
util.forEach(visibleItems, function (item) {
height = Math.max(height, item.top + item.height); height = Math.max(height, item.top + item.height);
}); });
} }
else { else {
// orientation == 'bottom' // orientation == 'bottom'
util.forEach(this.items, function (item) {
var frameHeight = this.height;
util.forEach(visibleItems, function (item) {
height = Math.max(height, frameHeight - item.top); height = Math.max(height, frameHeight - item.top);
}); });
} }
height += options.margin.axis; height += options.margin.axis;
if (maxHeight != null) {
height = Math.min(height, maxHeight);
}
changed += update(this, 'height', height);
} }
if (maxHeight != null) {
height = Math.min(height, maxHeight);
}
changed += update(this, 'height', height);
// calculate height from items // calculate height from items
changed += update(this, 'top', frame.offsetTop); changed += update(this, 'top', frame.offsetTop);
@ -350,6 +357,7 @@ ItemSet.prototype.setData = function(data) {
}); });
} }
// TODO: first remove current data
if (data instanceof DataSet) { if (data instanceof DataSet) {
this.data = data; this.data = data;
} }

+ 0
- 1
src/controller.js View File

@ -68,7 +68,6 @@ Controller.prototype.requestRepaint = function (force) {
}, 0); }, 0);
} }
} }
}; };
/** /**

+ 26
- 4
src/stack.js View File

@ -8,7 +8,27 @@ function Stack (parent, options) {
this.parent = parent; this.parent = parent;
this.options = { this.options = {
order: function (a, b) { order: function (a, b) {
return (b.width - a.width) || (a.left - b.left);
//return (b.width - a.width) || (a.left - b.left); // TODO: cleanup
// Order: ranges over non-ranges, ranged ordered by width, and
// lastly ordered by start.
if (a instanceof ItemRange) {
if (b instanceof ItemRange) {
var aInt = (a.data.end - a.data.start);
var bInt = (b.data.end - b.data.start);
return (aInt - bInt) || (a.data.start - b.data.start);
}
else {
return -1;
}
}
else {
if (b instanceof ItemRange) {
return 1;
}
else {
return (a.data.start - b.data.start);
}
}
} }
}; };
@ -55,9 +75,11 @@ Stack.prototype._order = function() {
// TODO: store the sorted items, to have less work later on // TODO: store the sorted items, to have less work later on
var ordered = []; var ordered = [];
var index = 0; var index = 0;
util.forEach(items, function (item, id) {
ordered[index] = item;
index++;
util.forEach(items, function (item) {
if (item.isVisible()) {
ordered[index] = item;
index++;
}
}); });
//if a customer stack order function exists, use it. //if a customer stack order function exists, use it.

+ 66
- 25
src/visualization/timeline.js View File

@ -23,10 +23,7 @@ function Timeline (container, data, options) {
throw new Error('No container element provided'); throw new Error('No container element provided');
} }
this.main = new RootPanel(container, { this.main = new RootPanel(container, {
autoResize: false,
height: function () {
return me.timeaxis.height + me.itemset.height;
}
autoResize: false
}); });
this.controller.add(this.main); this.controller.add(this.main);
@ -65,12 +62,13 @@ function Timeline (container, data, options) {
this.itemset.setRange(this.range); this.itemset.setRange(this.range);
this.controller.add(this.itemset); this.controller.add(this.itemset);
// set options (must take place before setting the data)
this.setOptions(options);
// set data // set data
if (data) { if (data) {
this.setData(data); this.setData(data);
} }
this.setOptions(options);
} }
/** /**
@ -87,21 +85,54 @@ Timeline.prototype.setOptions = function (options) {
this.range.setOptions(this.options); this.range.setOptions(this.options);
// update options the itemset // update options the itemset
var top,
var itemsTop,
itemsHeight,
mainHeight,
maxHeight,
me = this; me = this;
if (this.options.orientation == 'top') { if (this.options.orientation == 'top') {
top = function () {
itemsTop = function () {
return me.timeaxis.height; return me.timeaxis.height;
} }
} }
else { else {
top = function () {
itemsTop = function () {
return me.main.height - me.timeaxis.height - me.itemset.height; return me.main.height - me.timeaxis.height - me.itemset.height;
} }
} }
if (options.height) {
// fixed height
mainHeight = options.height;
itemsHeight = function () {
return me.main.height - me.timeaxis.height;
};
}
else {
// auto height
mainHeight = function () {
return me.timeaxis.height + me.itemset.height;
};
itemsHeight = null;
}
// TODO: maxHeight should be a string in px or %
if (options.maxHeight) {
maxHeight = function () {
return options.maxHeight - me.timeaxis.height;
}
}
this.main.setOptions({
height: mainHeight
});
this.itemset.setOptions({ this.itemset.setOptions({
orientation: this.options.orientation, orientation: this.options.orientation,
top: top
top: itemsTop,
height: itemsHeight,
maxHeight: maxHeight
}); });
this.controller.repaint(); this.controller.repaint();
@ -117,21 +148,31 @@ Timeline.prototype.setData = function(data) {
// first load of data // first load of data
this.itemset.setData(data); this.itemset.setData(data);
// apply the data range as range
var dataRange = this.itemset.getDataRange();
// add 5% on both sides
var min = dataRange.min;
var max = dataRange.max;
if (min != null && max != null) {
var interval = (max.valueOf() - min.valueOf());
min = new Date(min.valueOf() - interval * 0.05);
max = new Date(max.valueOf() + interval * 0.05);
}
// apply range if there is a min or max available
if (min != null || max != null) {
this.range.setRange(min, max);
if (this.options.start == undefined || this.options.end == undefined) {
// apply the data range as range
var dataRange = this.itemset.getDataRange();
// add 5% on both sides
var min = dataRange.min;
var max = dataRange.max;
if (min != null && max != null) {
var interval = (max.valueOf() - min.valueOf());
min = new Date(min.valueOf() - interval * 0.05);
max = new Date(max.valueOf() + interval * 0.05);
}
// override specified start and/or end date
if (this.options.start != undefined) {
min = new Date(this.options.start.valueOf());
}
if (this.options.end != undefined) {
max = new Date(this.options.end.valueOf());
}
// apply range if there is a min or max available
if (min != null || max != null) {
this.range.setRange(min, max);
}
} }
} }
else { else {

+ 520
- 290
vis.js
File diff suppressed because it is too large
View File


+ 4
- 4
vis.min.js
File diff suppressed because it is too large
View File


Loading…
Cancel
Save