vis.js is a dynamic, browser-based visualization library
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

161 lines
4.6 KiB

// TODO: turn Stack into a Mixin?
/**
* @constructor Stack
* Stacks items on top of each other.
* @param {ItemSet} itemset
* @param {Object} [options]
*/
function Stack (itemset, options) {
this.itemset = itemset;
this.options = options || {};
this.defaultOptions = {
order: function (a, b) {
// Order: ranges over non-ranges, ranged ordered by width,
// and non-ranges 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 {
if (!a.data) {
throw new Error('hu')
}
return (a.data.start - b.data.start);
}
}
},
margin: {
item: 10
}
};
this.ordered = []; // ordered items
}
/**
* Set options for the stack
* @param {Object} options Available options:
* {ItemSet} itemset
* {Number} margin
* {function} order Stacking order
*/
Stack.prototype.setOptions = function setOptions (options) {
util.extend(this.options, options);
// TODO: register on data changes at the connected itemset, and update the changed part only and immediately
};
/**
* Order a map with items
* @param {Object<String, Item>} items
* @return {Item[]} sorted items
*/
Stack.prototype.order = function order(items) {
var ordered = [];
// convert map to array
for (var id in items) {
if (items.hasOwnProperty(id)) ordered.push(items[id]);
}
//order the items
var order = this.options.order || this.defaultOptions.order;
if (!(typeof order === 'function')) {
throw new Error('Option order must be a function');
}
ordered.sort(order);
return ordered;
};
/**
* Adjust vertical positions of the events such that they don't overlap each
* other.
* @param {Item[]} items All visible items
* @private
*/
Stack.prototype.stack = function stack (items) {
var i,
iMax,
options = this.options,
orientation = options.orientation || this.defaultOptions.orientation,
axisOnTop = (orientation == 'top'),
margin,
parentHeight = this.itemset.height;
if (options.margin && options.margin.item !== undefined) {
margin = options.margin.item;
}
else {
margin = this.defaultOptions.margin.item
}
// calculate new, non-overlapping positions
for (i = 0, iMax = items.length; i < iMax; i++) {
var item = items[i];
if (item.top === null) {
// initialize top position
if (axisOnTop) {
item.top = margin;
}
else {
// default or 'bottom'
item.top = parentHeight - item.height - 2 * margin;
}
do {
// TODO: optimize checking for overlap. when there is a gap without items,
// you only need to check for items from the next item on, not from zero
var collidingItem = null;
for (var j = 0, jj = items.length; j < jj; j++) {
var other = items[j];
if (other.top !== null && other !== item && this.collision(item, other, margin)) {
collidingItem = other;
break;
}
}
if (collidingItem != null) {
// There is a collision. Reposition the event above the colliding element
if (axisOnTop) {
item.top = collidingItem.top + collidingItem.height + margin;
}
else {
item.top = collidingItem.top - item.height - margin;
}
}
} while (collidingItem);
}
}
};
/**
* Test if the two provided items collide
* The items must have parameters left, width, top, and height.
* @param {Component} a The first item
* @param {Component} b The second item
* @param {Number} margin A minimum required margin.
* If margin is provided, the two items will be
* marked colliding when they overlap or
* when the margin between the two is smaller than
* the requested margin.
* @return {boolean} true if a and b collide, else false
*/
Stack.prototype.collision = function collision (a, b, margin) {
return ((a.left - margin) < (b.left + b.width) &&
(a.left + a.width + margin) > b.left &&
(a.top - margin) < (b.top + b.height) &&
(a.top + a.height + margin) > b.top);
};