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.
 
 
 

420 lines
11 KiB

/**
* An GroupSet holds a set of groups
* @param {Component} parent
* @param {Component[]} [depends] Components on which this components depends
* (except for the parent)
* @param {Object} [options] See GroupSet.setOptions for the available
* options.
* @constructor GroupSet
* @extends Panel
*/
function GroupSet(parent, depends, options) {
this.id = util.randomUUID();
this.parent = parent;
this.depends = depends;
this.options = {};
this.range = null; // Range or Object {start: number, end: number}
this.items = null; // dataset with items
this.groups = null; // dataset with groups
this.contents = []; // array with groups
// changes in groups are queued key/value map containing id/action
this.queue = {};
var me = this;
this.listeners = {
'add': function (event, params) {
me._onAdd(params.items);
},
'update': function (event, params) {
me._onUpdate(params.items);
},
'remove': function (event, params) {
me._onRemove(params.items);
}
};
if (options) {
this.setOptions(options);
}
}
GroupSet.prototype = new Panel();
/**
* Set options for the ItemSet. Existing options will be extended/overwritten.
* @param {Object} [options] The following options are available:
* TODO: describe options
*/
GroupSet.prototype.setOptions = function setOptions(options) {
util.extend(this.options, options);
// TODO: implement options
var me = this;
util.forEach(this.contents, function (group) {
group.itemset.setOptions(me.options);
});
};
GroupSet.prototype.setRange = function (range) {
// TODO: implement setRange
};
/**
* Set items
* @param {vis.DataSet | null} items
*/
GroupSet.prototype.setItems = function setItems(items) {
this.items = items;
util.forEach(this.contents, function (group) {
group.itemset.setItems(items);
});
};
/**
* Get items
* @return {vis.DataSet | null} items
*/
GroupSet.prototype.getItems = function getItems() {
return this.items;
};
/**
* Set range (start and end).
* @param {Range | Object} range A Range or an object containing start and end.
*/
GroupSet.prototype.setRange = function setRange(range) {
this.range = range;
};
/**
* Set groups
* @param {vis.DataSet} groups
*/
GroupSet.prototype.setGroups = function setGroups(groups) {
var me = this,
dataGroups,
ids;
// unsubscribe from current dataset
if (this.groups) {
util.forEach(this.listeners, function (callback, event) {
me.groups.unsubscribe(event, callback);
});
// remove all drawn groups
dataGroups = this.groups.get({fields: ['id']});
ids = [];
util.forEach(dataGroups, function (dataGroup, index) {
ids[index] = dataGroup.id;
});
this._onRemove(ids);
}
// replace the dataset
if (!groups) {
this.groups = null;
}
else if (groups instanceof DataSet) {
this.groups = groups;
}
else {
this.groups = new DataSet({
fieldTypes: {
start: 'Date',
end: 'Date'
}
});
this.groups.add(groups);
}
if (this.groups) {
// subscribe to new dataset
var id = this.id;
util.forEach(this.listeners, function (callback, event) {
me.groups.subscribe(event, callback, id);
});
// draw all new groups
dataGroups = this.groups.get({fields: ['id']});
ids = [];
util.forEach(dataGroups, function (dataGroup, index) {
ids[index] = dataGroup.id;
});
this._onAdd(ids);
}
};
/**
* Get groups
* @return {vis.DataSet | null} groups
*/
GroupSet.prototype.getGroups = function getGroups() {
return this.groups;
};
/**
* Repaint the component
* @return {Boolean} changed
*/
GroupSet.prototype.repaint = function repaint() {
var changed = 0,
update = util.updateProperty,
asSize = util.option.asSize,
options = this.options,
frame = this.frame;
if (!frame) {
frame = document.createElement('div');
frame.className = 'groupset';
if (options.className) {
util.addClassName(frame, util.option.asString(options.className));
}
this.frame = frame;
changed += 1;
}
if (!this.parent) {
throw new Error('Cannot repaint groupset: no parent attached');
}
var parentContainer = this.parent.getContainer();
if (!parentContainer) {
throw new Error('Cannot repaint groupset: parent has no container element');
}
if (!frame.parentNode) {
parentContainer.appendChild(frame);
changed += 1;
}
// reposition frame
changed += update(frame.style, 'height', asSize(options.height, this.height + 'px'));
changed += update(frame.style, 'top', asSize(options.top, '0px'));
changed += update(frame.style, 'left', asSize(options.left, '0px'));
changed += update(frame.style, 'width', asSize(options.width, '100%'));
var me = this,
queue = this.queue,
items = this.items,
contents = this.contents,
groups = this.groups,
dataOptions = {
fields: ['id', 'content']
};
// show/hide added/changed/removed items
var ids = Object.keys(queue);
if (ids.length) {
ids.forEach(function (id) {
var action = queue[id];
// find group
var group = null;
var groupIndex = -1;
for (var i = 0; i < contents.length; i++) {
if (contents[i].id == id) {
group = contents[i];
groupIndex = i;
break;
}
}
//noinspection FallthroughInSwitchStatementJS
switch (action) {
case 'add':
case 'update':
// group does not yet exist
if (!group) {
var itemset = new ItemSet(me);
itemset.setOptions(util.extend({
top: function () {
return 0; // TODO
}
}, me.options));
itemset.setRange(me.range);
itemset.setItems(me.items);
me.controller.add(itemset);
group = {
id: id,
data: groups.get(id),
itemset: itemset
};
contents.push(group);
}
delete queue[id];
break;
case 'remove':
if (group) {
// remove DOM of the group
changed += group.itemset.hide();
group.itemset.setItems();
me.controller.remove(group.itemset);
// remove group itself
contents.splice(groupIndex, 1);
}
// update lists
delete queue[id];
break;
default:
console.log('Error: unknown action "' + action + '"');
}
});
// update the top position (TODO: optimize, needed only when groups are added/removed/reordered
var prevGroup = null;
util.forEach(this.contents, function (group) {
var prevItemset = prevGroup && prevGroup.itemset;
if (prevItemset) {
group.itemset.options.top = function () {
return prevItemset.top + prevItemset.height;
}
}
else {
group.itemset.options.top = 0;
}
prevGroup = group;
});
}
// reposition all groups
util.forEach(this.contents, function (group) {
changed += group.itemset.repaint();
});
return (changed > 0);
};
/**
* Get container element
* @return {HTMLElement} container
*/
GroupSet.prototype.getContainer = function getContainer() {
// TODO: replace later on with container element for holding itemsets
return this.frame;
};
/**
* Reflow the component
* @return {Boolean} resized
*/
GroupSet.prototype.reflow = function reflow() {
var changed = 0,
options = this.options,
update = util.updateProperty,
asNumber = util.option.asNumber,
frame = this.frame;
if (frame) {
// reposition all groups
util.forEach(this.contents, function (group) {
changed += group.itemset.reflow();
});
var maxHeight = asNumber(options.maxHeight);
var height;
if (options.height != null) {
height = frame.offsetHeight;
}
else {
// height is not specified, calculate the sum of the height of all groups
height = 0;
util.forEach(this.contents, function (group) {
height += group.itemset.height;
});
}
if (maxHeight != null) {
height = Math.min(height, maxHeight);
}
changed += update(this, 'height', height);
changed += update(this, 'top', frame.offsetTop);
changed += update(this, 'left', frame.offsetLeft);
changed += update(this, 'width', frame.offsetWidth);
}
return (changed > 0);
};
/**
* Hide the component from the DOM
* @return {Boolean} changed
*/
GroupSet.prototype.hide = function hide() {
if (this.frame && this.frame.parentNode) {
this.frame.parentNode.removeChild(this.frame);
return true;
}
else {
return false;
}
};
/**
* Show the component in the DOM (when not already visible).
* A repaint will be executed when the component is not visible
* @return {Boolean} changed
*/
GroupSet.prototype.show = function show() {
if (!this.frame || !this.frame.parentNode) {
return this.repaint();
}
else {
return false;
}
};
/**
* Handle updated groups
* @param {Number[]} ids
* @private
*/
GroupSet.prototype._onUpdate = function _onUpdate(ids) {
this._toQueue(ids, 'update');
};
/**
* Handle changed groups
* @param {Number[]} ids
* @private
*/
GroupSet.prototype._onAdd = function _onAdd(ids) {
this._toQueue(ids, 'add');
};
/**
* Handle removed groups
* @param {Number[]} ids
* @private
*/
GroupSet.prototype._onRemove = function _onRemove(ids) {
this._toQueue(ids, 'remove');
};
/**
* Put groups in the queue to be added/updated/remove
* @param {Number[]} ids
* @param {String} action can be 'add', 'update', 'remove'
*/
GroupSet.prototype._toQueue = function _toQueue(ids, action) {
var queue = this.queue;
ids.forEach(function (id) {
queue[id] = action;
});
if (this.controller) {
//this.requestReflow();
this.requestRepaint();
}
};