|
|
@ -1,467 +0,0 @@ |
|
|
|
// TODO: remove groupset
|
|
|
|
|
|
|
|
/** |
|
|
|
* An GroupSet holds a set of groups |
|
|
|
* @param {Panel} contentPanel Panel where the ItemSets will be created |
|
|
|
* @param {Panel} labelPanel Panel where the labels will be created |
|
|
|
* @param {Panel} backgroundPanel Panel where the vertical lines of box |
|
|
|
* items are created |
|
|
|
* @param {Panel} axisPanel Panel on the axis where the dots of box |
|
|
|
* items will be created |
|
|
|
* @param {Object} [options] See GroupSet.setOptions for the available |
|
|
|
* options. |
|
|
|
* @constructor GroupSet |
|
|
|
* @extends Panel |
|
|
|
*/ |
|
|
|
function GroupSet(contentPanel, labelPanel, backgroundPanel, axisPanel, options) { |
|
|
|
this.id = util.randomUUID(); |
|
|
|
|
|
|
|
this.contentPanel = contentPanel; |
|
|
|
this.labelPanel = labelPanel; |
|
|
|
this.backgroundPanel = backgroundPanel; |
|
|
|
this.axisPanel = axisPanel; |
|
|
|
this.options = options || {}; |
|
|
|
|
|
|
|
this.range = null; // Range or Object {start: number, end: number}
|
|
|
|
this.itemsData = null; // DataSet with items
|
|
|
|
this.groupsData = null; // DataSet with groups
|
|
|
|
|
|
|
|
this.groups = {}; // map with groups
|
|
|
|
this.groupIds = []; // list with ordered group ids
|
|
|
|
|
|
|
|
this.dom = {}; |
|
|
|
this.props = { |
|
|
|
labels: { |
|
|
|
width: 0 |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
// TODO: implement right orientation of the labels (left/right)
|
|
|
|
|
|
|
|
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); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
// create HTML DOM
|
|
|
|
this._create(); |
|
|
|
} |
|
|
|
|
|
|
|
GroupSet.prototype = new Panel(); |
|
|
|
|
|
|
|
/** |
|
|
|
* Create the HTML DOM elements for the GroupSet |
|
|
|
* @private |
|
|
|
*/ |
|
|
|
GroupSet.prototype._create = function _create () { |
|
|
|
// TODO: reimplement groupSet DOM elements
|
|
|
|
var frame = document.createElement('div'); |
|
|
|
frame.className = 'groupset'; |
|
|
|
frame['timeline-groupset'] = this; |
|
|
|
this.frame = frame; |
|
|
|
|
|
|
|
this.labelSet = new Panel({ |
|
|
|
className: 'labelset', |
|
|
|
width: '100%', |
|
|
|
height: '100%' |
|
|
|
}); |
|
|
|
this.labelPanel.appendChild(this.labelSet); |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Get the frame element of component |
|
|
|
* @returns {null} Get frame is not supported by GroupSet |
|
|
|
*/ |
|
|
|
GroupSet.prototype.getFrame = function getFrame() { |
|
|
|
return this.frame; |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Set options for the GroupSet. Existing options will be extended/overwritten. |
|
|
|
* @param {Object} [options] The following options are available: |
|
|
|
* {String | function} groupsOrder |
|
|
|
* TODO: describe options |
|
|
|
*/ |
|
|
|
GroupSet.prototype.setOptions = Component.prototype.setOptions; |
|
|
|
|
|
|
|
/** |
|
|
|
* Set range (start and end). |
|
|
|
* @param {Range | Object} range A Range or an object containing start and end. |
|
|
|
*/ |
|
|
|
GroupSet.prototype.setRange = function (range) { |
|
|
|
this.range = range; |
|
|
|
|
|
|
|
for (var id in this.groups) { |
|
|
|
if (this.groups.hasOwnProperty(id)) { |
|
|
|
this.groups[id].setRange(range); |
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Set items |
|
|
|
* @param {vis.DataSet | null} items |
|
|
|
*/ |
|
|
|
GroupSet.prototype.setItems = function setItems(items) { |
|
|
|
this.itemsData = items; |
|
|
|
|
|
|
|
for (var id in this.groups) { |
|
|
|
if (this.groups.hasOwnProperty(id)) { |
|
|
|
var group = this.groups[id]; |
|
|
|
// TODO: every group will emit a change event, causing a lot of unnecessary repaints. improve this.
|
|
|
|
group.setItems(items); |
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Get items |
|
|
|
* @return {vis.DataSet | null} items |
|
|
|
*/ |
|
|
|
GroupSet.prototype.getItems = function getItems() { |
|
|
|
return this.itemsData; |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* 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, |
|
|
|
ids; |
|
|
|
|
|
|
|
// unsubscribe from current dataset
|
|
|
|
if (this.groupsData) { |
|
|
|
util.forEach(this.listeners, function (callback, event) { |
|
|
|
me.groupsData.unsubscribe(event, callback); |
|
|
|
}); |
|
|
|
|
|
|
|
// remove all drawn groups
|
|
|
|
ids = this.groupsData.getIds(); |
|
|
|
this._onRemove(ids); |
|
|
|
} |
|
|
|
|
|
|
|
// replace the dataset
|
|
|
|
if (!groups) { |
|
|
|
this.groupsData = null; |
|
|
|
} |
|
|
|
else if (groups instanceof DataSet) { |
|
|
|
this.groupsData = groups; |
|
|
|
} |
|
|
|
else { |
|
|
|
this.groupsData = new DataSet({ |
|
|
|
convert: { |
|
|
|
start: 'Date', |
|
|
|
end: 'Date' |
|
|
|
} |
|
|
|
}); |
|
|
|
this.groupsData.add(groups); |
|
|
|
} |
|
|
|
|
|
|
|
if (this.groupsData) { |
|
|
|
// subscribe to new dataset
|
|
|
|
var id = this.id; |
|
|
|
util.forEach(this.listeners, function (callback, event) { |
|
|
|
me.groupsData.on(event, callback, id); |
|
|
|
}); |
|
|
|
|
|
|
|
// draw all new groups
|
|
|
|
ids = this.groupsData.getIds(); |
|
|
|
this._onAdd(ids); |
|
|
|
} |
|
|
|
|
|
|
|
this.emit('change'); |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Get groups |
|
|
|
* @return {vis.DataSet | null} groups |
|
|
|
*/ |
|
|
|
GroupSet.prototype.getGroups = function getGroups() { |
|
|
|
return this.groupsData; |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Set selected items by their id. Replaces the current selection. |
|
|
|
* Unknown id's are silently ignored. |
|
|
|
* @param {Array} [ids] An array with zero or more id's of the items to be |
|
|
|
* selected. If ids is an empty array, all items will be |
|
|
|
* unselected. |
|
|
|
*/ |
|
|
|
GroupSet.prototype.setSelection = function setSelection(ids) { |
|
|
|
var selection = [], |
|
|
|
groups = this.groups; |
|
|
|
|
|
|
|
// iterate over each of the groups
|
|
|
|
for (var id in groups) { |
|
|
|
if (groups.hasOwnProperty(id)) { |
|
|
|
var group = groups[id]; |
|
|
|
group.setSelection(ids); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return selection; |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Get the selected items by their id |
|
|
|
* @return {Array} ids The ids of the selected items |
|
|
|
*/ |
|
|
|
GroupSet.prototype.getSelection = function getSelection() { |
|
|
|
var selection = [], |
|
|
|
groups = this.groups; |
|
|
|
|
|
|
|
// iterate over each of the groups
|
|
|
|
for (var id in groups) { |
|
|
|
if (groups.hasOwnProperty(id)) { |
|
|
|
var group = groups[id]; |
|
|
|
selection = selection.concat(group.getSelection()); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return selection; |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Repaint the component |
|
|
|
* @return {boolean} Returns true if the component was resized since previous repaint |
|
|
|
*/ |
|
|
|
GroupSet.prototype.repaint = function repaint() { |
|
|
|
var i, id, group, |
|
|
|
asSize = util.option.asSize, |
|
|
|
asString = util.option.asString, |
|
|
|
options = this.options, |
|
|
|
orientation = this.getOption('orientation'), |
|
|
|
frame = this.frame, |
|
|
|
resized = false, |
|
|
|
groups = this.groups; |
|
|
|
|
|
|
|
// repaint all groups in order
|
|
|
|
this.groupIds.forEach(function (id) { |
|
|
|
var groupResized = groups[id].repaint(); |
|
|
|
resized = resized || groupResized; |
|
|
|
}); |
|
|
|
|
|
|
|
// reposition the labels and calculate the maximum label width
|
|
|
|
var maxWidth = 0; |
|
|
|
for (id in groups) { |
|
|
|
if (groups.hasOwnProperty(id)) { |
|
|
|
group = groups[id]; |
|
|
|
maxWidth = Math.max(maxWidth, group.props.label.width); |
|
|
|
} |
|
|
|
} |
|
|
|
resized = util.updateProperty(this.props.labels, 'width', maxWidth) || resized; |
|
|
|
|
|
|
|
// recalculate the height of the groupset, and recalculate top positions of the groups
|
|
|
|
var fixedHeight = (asSize(options.height) != null); |
|
|
|
var height; |
|
|
|
if (!fixedHeight) { |
|
|
|
// height is not specified, calculate the sum of the height of all groups
|
|
|
|
height = 0; |
|
|
|
|
|
|
|
this.groupIds.forEach(function (id) { |
|
|
|
var group = groups[id]; |
|
|
|
group.top = height; |
|
|
|
if (group.itemSet) group.itemSet.top = group.top; // TODO: this is an ugly hack
|
|
|
|
height += group.height; |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
// update classname
|
|
|
|
frame.className = 'groupset' + (options.className ? (' ' + asString(options.className)) : ''); |
|
|
|
|
|
|
|
// calculate actual size and position
|
|
|
|
this.top = frame.offsetTop; |
|
|
|
this.left = frame.offsetLeft; |
|
|
|
this.width = frame.offsetWidth; |
|
|
|
this.height = height; |
|
|
|
|
|
|
|
return resized; |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Update the groupIds. Requires a repaint afterwards |
|
|
|
* @private |
|
|
|
*/ |
|
|
|
GroupSet.prototype._updateGroupIds = function () { |
|
|
|
// reorder the groups
|
|
|
|
this.groupIds = this.groupsData.getIds({ |
|
|
|
order: this.options.groupOrder |
|
|
|
}); |
|
|
|
|
|
|
|
// hide the groups now, they will be shown again in the next repaint
|
|
|
|
// in correct order
|
|
|
|
var groups = this.groups; |
|
|
|
this.groupIds.forEach(function (id) { |
|
|
|
groups[id].hide(); |
|
|
|
}); |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Get the width of the group labels |
|
|
|
* @return {Number} width |
|
|
|
*/ |
|
|
|
GroupSet.prototype.getLabelsWidth = function getLabelsWidth() { |
|
|
|
return this.props.labels.width; |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Hide the component from the DOM |
|
|
|
*/ |
|
|
|
GroupSet.prototype.hide = function hide() { |
|
|
|
// hide labelset
|
|
|
|
this.labelPanel.removeChild(this.labelSet); |
|
|
|
|
|
|
|
// hide each of the groups
|
|
|
|
for (var groupId in this.groups) { |
|
|
|
if (this.groups.hasOwnProperty(groupId)) { |
|
|
|
this.groups[groupId].hide(); |
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Show the component in the DOM (when not already visible). |
|
|
|
* @return {Boolean} changed |
|
|
|
*/ |
|
|
|
GroupSet.prototype.show = function show() { |
|
|
|
// show label set
|
|
|
|
if (!this.labelPanel.hasChild(this.labelSet)) { |
|
|
|
this.labelPanel.removeChild(this.labelSet); |
|
|
|
} |
|
|
|
|
|
|
|
// show each of the groups
|
|
|
|
for (var groupId in this.groups) { |
|
|
|
if (this.groups.hasOwnProperty(groupId)) { |
|
|
|
this.groups[groupId].show(); |
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Handle updated groups |
|
|
|
* @param {Number[]} ids |
|
|
|
* @private |
|
|
|
*/ |
|
|
|
GroupSet.prototype._onUpdate = function _onUpdate(ids) { |
|
|
|
this._onAdd(ids); |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Handle changed groups |
|
|
|
* @param {Number[]} ids |
|
|
|
* @private |
|
|
|
*/ |
|
|
|
GroupSet.prototype._onAdd = function _onAdd(ids) { |
|
|
|
var me = this; |
|
|
|
|
|
|
|
ids.forEach(function (id) { |
|
|
|
var group = me.groups[id]; |
|
|
|
if (!group) { |
|
|
|
var groupOptions = Object.create(me.options); |
|
|
|
util.extend(groupOptions, { |
|
|
|
height: null |
|
|
|
}); |
|
|
|
|
|
|
|
group = new Group(me, me.labelSet, me.backgroundPanel, me.axisPanel, id, groupOptions); |
|
|
|
group.on('change', me.emit.bind(me, 'change')); // propagate change event
|
|
|
|
group.setRange(me.range); |
|
|
|
group.setItems(me.itemsData); // attach items data
|
|
|
|
me.groups[id] = group; |
|
|
|
group.parent = me; |
|
|
|
} |
|
|
|
|
|
|
|
// update group data
|
|
|
|
group.setData(me.groupsData.get(id)); |
|
|
|
}); |
|
|
|
|
|
|
|
this._updateGroupIds(); |
|
|
|
|
|
|
|
this.emit('change'); |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Handle removed groups |
|
|
|
* @param {Number[]} ids |
|
|
|
* @private |
|
|
|
*/ |
|
|
|
GroupSet.prototype._onRemove = function _onRemove(ids) { |
|
|
|
var groups = this.groups; |
|
|
|
ids.forEach(function (id) { |
|
|
|
var group = groups[id]; |
|
|
|
|
|
|
|
if (group) { |
|
|
|
group.setItems(); // detach items data
|
|
|
|
group.hide(); // FIXME: for some reason when doing setItems after hide, setItems again makes the label visible
|
|
|
|
delete groups[id]; |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
this._updateGroupIds(); |
|
|
|
|
|
|
|
this.emit('change'); |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Find the GroupSet from an event target: |
|
|
|
* searches for the attribute 'timeline-groupset' in the event target's element |
|
|
|
* tree, then finds the right group in this groupset |
|
|
|
* @param {Event} event |
|
|
|
* @return {Group | null} group |
|
|
|
*/ |
|
|
|
GroupSet.groupSetFromTarget = function groupSetFromTarget (event) { |
|
|
|
var target = event.target; |
|
|
|
while (target) { |
|
|
|
if (target.hasOwnProperty('timeline-groupset')) { |
|
|
|
return target['timeline-groupset']; |
|
|
|
} |
|
|
|
target = target.parentNode; |
|
|
|
} |
|
|
|
|
|
|
|
return null; |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Find the Group from an event target: |
|
|
|
* searches for the two elements having attributes 'timeline-groupset' and |
|
|
|
* 'timeline-itemset' in the event target's element, then finds the right group. |
|
|
|
* @param {Event} event |
|
|
|
* @return {Group | null} group |
|
|
|
*/ |
|
|
|
GroupSet.groupFromTarget = function groupFromTarget (event) { |
|
|
|
// find the groupSet
|
|
|
|
var groupSet = GroupSet.groupSetFromTarget(event); |
|
|
|
|
|
|
|
// find the ItemSet
|
|
|
|
var itemSet = ItemSet.itemSetFromTarget(event); |
|
|
|
|
|
|
|
// find the right group
|
|
|
|
if (groupSet && itemSet) { |
|
|
|
for (var groupId in groupSet.groups) { |
|
|
|
if (groupSet.groups.hasOwnProperty(groupId)) { |
|
|
|
var group = groupSet.groups[groupId]; |
|
|
|
if (group.itemSet == itemSet) { |
|
|
|
return group; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return null; |
|
|
|
}; |