Browse Source

Fixes in updates from DataSets

css_transitions
jos 10 years ago
parent
commit
0bec54d13a
6 changed files with 580 additions and 1377 deletions
  1. +17
    -9
      dist/vis.css
  2. +502
    -876
      dist/vis.js
  3. +6
    -10
      src/timeline/component/Group.js
  4. +0
    -467
      src/timeline/component/GroupSet.js
  5. +53
    -13
      src/timeline/component/ItemSet.js
  6. +2
    -2
      src/timeline/component/TimeAxis.js

+ 17
- 9
dist/vis.css View File

@ -32,9 +32,6 @@
display: none;
}
.vis.timeline .groupset {
position: relative;
}
.vis.timeline .labelset {
position: relative;
@ -57,16 +54,12 @@
box-sizing: border-box;
}
.vis.timeline.bottom .labelset .vlabel,
.vis.timeline.top .vpanel.side-content,
.vis.timeline.top .groupset .itemset {
.vis.timeline.top .labelset .vlabel {
border-top: 1px solid #bfbfbf;
border-bottom: none;
}
.vis.timeline.top .labelset .vlabel,
.vis.timeline.bottom .vpanel.side-content,
.vis.timeline.bottom .groupset .itemset {
.vis.timeline.bottom .labelset .vlabel {
border-top: none;
border-bottom: 1px solid #bfbfbf;
}
@ -101,6 +94,21 @@
overflow: visible;
}
.vis.timeline .group {
position: relative;
box-sizing: border-box;
}
.vis.timeline.top .group {
border-top: 1px solid #bfbfbf;
border-bottom: none;
}
.vis.timeline.bottom .group {
border-top: none;
border-bottom: 1px solid #bfbfbf;
}
.vis.timeline .item {
position: absolute;

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


+ 6
- 10
src/timeline/component/Group.js View File

@ -118,7 +118,7 @@ Group.prototype.getLabelWidth = function getLabelWidth() {
* @return {boolean} Returns true if the group is resized
*/
Group.prototype.repaint = function repaint(range, margin, restack) {
var resized;
var resized = false;
if (typeof margin === 'number') {
margin = {
@ -152,19 +152,17 @@ Group.prototype.repaint = function repaint(range, margin, restack) {
else {
height = margin.axis + margin.item;
}
resized = (this.height != height);
// calculate actual size and position
var foreground = this.dom.foreground;
this.top = foreground.offsetTop;
this.left = foreground.offsetLeft;
this.width = foreground.offsetWidth;
this.height = height;
resized = util.updateProperty(this, 'height', height) || resized;
// recalculate size of label
// TODO: if changed, return resized=true
this.props.label.width = this.dom.inner.clientWidth;
this.props.label.height = this.dom.inner.clientHeight;
resized = util.updateProperty(this.props.label, 'width', this.dom.inner.clientWidth) || resized;
resized = util.updateProperty(this.props.label, 'height', this.dom.inner.clientHeight) || resized;
// apply new height
foreground.style.height = height + 'px';
@ -257,15 +255,13 @@ Group.prototype.removeFromDataSet = function removeFromDataSet(item) {
};
/**
* Order the items
* @private
* Reorder the items
*/
Group.prototype._order = function _order() {
Group.prototype.order = function order() {
var array = util.toArray(this.items);
this.orderedItems.byStart = array;
this.orderedItems.byEnd = this._constructByEndArray(array);
// reorder the items
stack.orderByStart(this.orderedItems.byStart);
stack.orderByEnd(this.orderedItems.byEnd);
};

+ 0
- 467
src/timeline/component/GroupSet.js View File

@ -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;
};

+ 53
- 13
src/timeline/component/ItemSet.js View File

@ -284,6 +284,7 @@ ItemSet.prototype.repaint = function repaint() {
asString = util.option.asString,
options = this.options,
orientation = this.getOption('orientation'),
resized = false,
frame = this.frame;
// update className
@ -299,11 +300,14 @@ ItemSet.prototype.repaint = function repaint() {
var restack = zoomed || this.stackDirty;
var height = 0;
util.forEach(this.groups, function (group) {
group.repaint(range, margin, restack);
resized = group.repaint(range, margin, restack) || resized;
height += group.height;
});
this.stackDirty = false;
// reorder the groups (if needed)
resized = this._orderGroups() || resized;
// reposition frame
frame.style.left = asSize(options.left, '');
frame.style.right = asSize(options.right, '');
@ -327,7 +331,10 @@ ItemSet.prototype.repaint = function repaint() {
this.dom.axis.style.top = asSize((orientation == 'top') ? '0' : '');
this.dom.axis.style.bottom = asSize((orientation == 'top') ? '' : '0');
return this._isResized();
// check if this component is resized
resized = this._isResized() || resized;
return resized;
};
/**
@ -496,6 +503,9 @@ ItemSet.prototype.setGroups = function setGroups(groups) {
// update the group holding all ungrouped items
this._updateUngrouped();
// update the order of all items in each group
this._order();
this.emit('change');
};
@ -616,7 +626,7 @@ ItemSet.prototype._order = function _order() {
// reorder the items in all groups
// TODO: optimization: only reorder groups affected by the changed items
util.forEach(this.groups, function (group) {
group._order();
group.order();
});
};
@ -638,7 +648,9 @@ ItemSet.prototype._onAddGroups = function _onAddGroups(ids) {
var me = this;
ids.forEach(function (id) {
var groupData = me.groupsData.get(id);
var group = me.groups[id];
if (!group) {
// check for reserved ids
if (id == UNGROUPED) {
@ -650,8 +662,7 @@ ItemSet.prototype._onAddGroups = function _onAddGroups(ids) {
height: null
});
var data = me.groupsData.get(id);
group = new Group(id, data, me);
group = new Group(id, groupData, me);
me.groups[id] = group;
// add items with this groupId to the new group
@ -664,11 +675,16 @@ ItemSet.prototype._onAddGroups = function _onAddGroups(ids) {
}
}
group.order();
group.show();
}
else {
// update group
group.setData(groupData);
}
});
this._updateGroupIds();
this.emit('change');
};
/**
@ -687,18 +703,42 @@ ItemSet.prototype._onRemoveGroups = function _onRemoveGroups(ids) {
}
});
this._updateGroupIds();
this.emit('change');
};
/**
* Update the groupIds. Requires a repaint afterwards
* Reorder the groups if needed
* @return {boolean} changed
* @private
*/
ItemSet.prototype._updateGroupIds = function () {
// reorder the groups
this.groupIds = this.groupsData.getIds({
order: this.options.groupOrder
});
ItemSet.prototype._orderGroups = function () {
if (this.groupsData) {
// reorder the groups
var groupIds = this.groupsData.getIds({
order: this.options.groupOrder
});
var changed = !util.equalArray(groupIds, this.groupIds);
if (changed) {
// hide all groups, removes them from the DOM
var groups = this.groups;
groupIds.forEach(function (groupId) {
groups[groupId].hide();
});
// show the groups again, attach them to the DOM in correct order
groupIds.forEach(function (groupId) {
groups[groupId].show();
});
this.groupIds = groupIds;
}
return changed;
}
else {
return false;
}
};
/**

+ 2
- 2
src/timeline/component/TimeAxis.js View File

@ -153,10 +153,10 @@ TimeAxis.prototype.repaint = function () {
TimeAxis.prototype._repaintLabels = function () {
var orientation = this.getOption('orientation');
// calculate range and step
// calculate range and step (step such that we have space for 7 characters per label)
var start = util.convert(this.range.start, 'Number'),
end = util.convert(this.range.end, 'Number'),
minimumStep = this.options.toTime((this.props.minorCharWidth || 10) * 5).valueOf()
minimumStep = this.options.toTime((this.props.minorCharWidth || 10) * 7).valueOf()
-this.options.toTime(0).valueOf();
var step = new TimeStep(new Date(start), new Date(end), minimumStep);
this.step = step;

Loading…
Cancel
Save