Browse Source

Refactored Itemset.repaint, simplified vertical positioning of items

css_transitions
josdejong 11 years ago
parent
commit
21bb0559e1
7 changed files with 128 additions and 164 deletions
  1. +19
    -32
      src/timeline/Stack.js
  2. +9
    -0
      src/timeline/Timeline.js
  3. +64
    -117
      src/timeline/component/ItemSet.js
  4. +2
    -2
      src/timeline/component/css/item.css
  5. +12
    -11
      src/timeline/component/item/ItemBox.js
  6. +11
    -1
      src/timeline/component/item/ItemPoint.js
  7. +11
    -1
      src/timeline/component/item/ItemRange.js

+ 19
- 32
src/timeline/Stack.js View File

@ -3,12 +3,9 @@
/** /**
* @constructor Stack * @constructor Stack
* Stacks items on top of each other. * Stacks items on top of each other.
* @param {ItemSet} itemset
* @param {Object} [options] * @param {Object} [options]
*/ */
function Stack (itemset, options) {
this.itemset = itemset;
function Stack (options) {
this.options = options || {}; this.options = options || {};
this.defaultOptions = { this.defaultOptions = {
order: function (a, b) { order: function (a, b) {
@ -34,24 +31,21 @@ function Stack (itemset, options) {
} }
}, },
margin: { margin: {
item: 10
item: 10,
axis: 20
} }
}; };
this.ordered = []; // ordered items
} }
/** /**
* Set options for the stack * Set options for the stack
* @param {Object} options Available options: * @param {Object} options Available options:
* {ItemSet} itemset
* {Number} margin
* {function} order Stacking order
* {Number} [margin.item=10]
* {Number} [margin.axis=20]
* {function} [order] Stacking order
*/ */
Stack.prototype.setOptions = function setOptions (options) { Stack.prototype.setOptions = function setOptions (options) {
util.extend(this.options, options); util.extend(this.options, options);
// TODO: register on data changes at the connected itemset, and update the changed part only and immediately
}; };
/** /**
@ -101,16 +95,20 @@ Stack.prototype.stack = function stack (items) {
var i, var i,
iMax, iMax,
options = this.options, options = this.options,
orientation = options.orientation || this.defaultOptions.orientation,
axisOnTop = (orientation == 'top'),
margin,
parentHeight = this.itemset.height;
marginItem,
marginAxis;
if (options.margin && options.margin.item !== undefined) { if (options.margin && options.margin.item !== undefined) {
margin = options.margin.item;
marginItem = options.margin.item;
}
else {
marginItem = this.defaultOptions.margin.item
}
if (options.margin && options.margin.axis !== undefined) {
marginAxis = options.margin.axis;
} }
else { else {
margin = this.defaultOptions.margin.item
marginAxis = this.defaultOptions.margin.axis
} }
// calculate new, non-overlapping positions // calculate new, non-overlapping positions
@ -118,13 +116,7 @@ Stack.prototype.stack = function stack (items) {
var item = items[i]; var item = items[i];
if (item.top === null) { if (item.top === null) {
// initialize top position // initialize top position
if (axisOnTop) {
item.top = margin;
}
else {
// default or 'bottom'
item.top = parentHeight - item.height - 2 * margin;
}
item.top = marginAxis;
do { do {
// TODO: optimize checking for overlap. when there is a gap without items, // TODO: optimize checking for overlap. when there is a gap without items,
@ -132,7 +124,7 @@ Stack.prototype.stack = function stack (items) {
var collidingItem = null; var collidingItem = null;
for (var j = 0, jj = items.length; j < jj; j++) { for (var j = 0, jj = items.length; j < jj; j++) {
var other = items[j]; var other = items[j];
if (other.top !== null && other !== item && this.collision(item, other, margin)) {
if (other.top !== null && other !== item && this.collision(item, other, marginItem)) {
collidingItem = other; collidingItem = other;
break; break;
} }
@ -140,12 +132,7 @@ Stack.prototype.stack = function stack (items) {
if (collidingItem != null) { if (collidingItem != null) {
// There is a collision. Reposition the event above the colliding element // 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;
}
item.top = collidingItem.top + collidingItem.height + marginItem;
} }
} while (collidingItem); } while (collidingItem);
} }

+ 9
- 0
src/timeline/Timeline.js View File

@ -27,6 +27,15 @@ function Timeline (container, items, options) {
showCurrentTime: false, showCurrentTime: false,
showCustomTime: false, showCustomTime: false,
type: 'box',
align: 'center',
orientation: 'bottom',
margin: {
axis: 20,
item: 10
},
padding: 5,
onAdd: function (item, callback) { onAdd: function (item, callback) {
callback(item); callback(item);
}, },

+ 64
- 117
src/timeline/component/ItemSet.js View File

@ -25,17 +25,7 @@ function ItemSet(parent, depends, options) {
// one options object is shared by this itemset and all its items // one options object is shared by this itemset and all its items
this.options = options || {}; this.options = options || {};
this.defaultOptions = {
type: 'box',
align: 'center',
orientation: 'bottom',
margin: {
axis: 20,
item: 10
},
padding: 5
};
this.itemOptions = Object.create(this.options);
this.dom = {}; this.dom = {};
var me = this; var me = this;
@ -65,7 +55,7 @@ function ItemSet(parent, depends, options) {
this.visibleItemsEnd = 0; // start index of visible items in this.orderedItems // TODO: cleanup this.visibleItemsEnd = 0; // start index of visible items in this.orderedItems // TODO: cleanup
this.selection = []; // list with the ids of all selected nodes this.selection = []; // list with the ids of all selected nodes
this.queue = {}; // queue with id/actions: 'add', 'update', 'delete' this.queue = {}; // queue with id/actions: 'add', 'update', 'delete'
this.stack = new Stack(this, Object.create(this.options));
this.stack = new Stack(Object.create(this.options));
this.conversion = null; this.conversion = null;
this.touchParams = {}; // stores properties while dragging this.touchParams = {}; // stores properties while dragging
@ -219,12 +209,10 @@ ItemSet.prototype._deselect = function _deselect(id) {
/** /**
* Repaint the component * Repaint the component
* @return {Boolean} changed
*/ */
ItemSet.prototype.repaint = function repaint() { ItemSet.prototype.repaint = function repaint() {
var changed = 0,
update = util.updateProperty,
asSize = util.option.asSize,
var asSize = util.option.asSize,
asNumber = util.option.asNumber,
options = this.options, options = this.options,
orientation = this.getOption('orientation'), orientation = this.getOption('orientation'),
frame = this.frame; frame = this.frame;
@ -260,7 +248,6 @@ ItemSet.prototype.repaint = function repaint() {
this.dom.axis = axis; this.dom.axis = axis;
this.frame = frame; this.frame = frame;
changed += 1;
} }
if (!this.parent) { if (!this.parent) {
@ -272,27 +259,9 @@ ItemSet.prototype.repaint = function repaint() {
} }
if (!frame.parentNode) { if (!frame.parentNode) {
parentContainer.appendChild(frame); parentContainer.appendChild(frame);
changed += 1;
} }
if (!this.dom.axis.parentNode) { if (!this.dom.axis.parentNode) {
parentContainer.appendChild(this.dom.axis); parentContainer.appendChild(this.dom.axis);
changed += 1;
}
// reposition frame
changed += update(frame.style, 'left', asSize(options.left, '0px'));
changed += update(frame.style, 'top', asSize(options.top, '0px'));
changed += update(frame.style, 'width', asSize(options.width, '100%'));
changed += update(frame.style, 'height', asSize(options.height, this.height + 'px'));
// reposition axis
changed += update(this.dom.axis.style, 'left', asSize(options.left, '0px'));
changed += update(this.dom.axis.style, 'width', asSize(options.width, '100%'));
if (orientation == 'bottom') {
changed += update(this.dom.axis.style, 'top', (this.height + this.top) + 'px');
}
else { // orientation == 'top'
changed += update(this.dom.axis.style, 'top', this.top + 'px');
} }
// check whether zoomed (in that case we need to re-stack everything) // check whether zoomed (in that case we need to re-stack everything)
@ -300,7 +269,7 @@ ItemSet.prototype.repaint = function repaint() {
var zoomed = this.visibleInterval != visibleInterval; var zoomed = this.visibleInterval != visibleInterval;
this.visibleInterval = visibleInterval; this.visibleInterval = visibleInterval;
/*
/* TODO: implement+fix smarter way to update visible items
// find the first visible item // find the first visible item
// TODO: use faster search, not linear // TODO: use faster search, not linear
var byEnd = this.orderedItems.byEnd; var byEnd = this.orderedItems.byEnd;
@ -368,12 +337,65 @@ ItemSet.prototype.repaint = function repaint() {
} }
// reposition visible items vertically // reposition visible items vertically
this.stack.order(this.visibleItems);
//this.stack.order(this.visibleItems); // TODO: solve ordering issue
this.stack.stack(this.visibleItems); this.stack.stack(this.visibleItems);
for (var i = 0, ii = this.visibleItems.length; i < ii; i++) { for (var i = 0, ii = this.visibleItems.length; i < ii; i++) {
this.visibleItems[i].repositionY(); this.visibleItems[i].repositionY();
} }
// recalculate the height of the itemset
var marginAxis = (options.margin && 'axis' in options.margin) ? options.margin.axis : this.itemOptions.margin.axis,
marginItem = (options.margin && 'item' in options.margin) ? options.margin.item : this.itemOptions.margin.item,
maxHeight = asNumber(options.maxHeight),
fixedHeight = (asSize(options.height) != null),
height;
// recalculate the frames size and position
// TODO: request frame's actual top, left, width only when size is changed (mark as dirty)
if (fixedHeight) {
height = frame.offsetHeight;
}
else {
// height is not specified, determine the height from the height and positioned items
var visibleItems = this.visibleItems;
if (visibleItems.length) {
var min = visibleItems[0].top;
var max = visibleItems[0].top + visibleItems[0].height;
util.forEach(visibleItems, function (item) {
min = Math.min(min, item.top);
max = Math.max(max, (item.top + item.height));
});
height = (max - min) + marginAxis + marginItem;
}
else {
height = marginAxis + marginItem;
}
}
if (maxHeight != null) {
height = Math.min(height, maxHeight);
}
this.top = frame.offsetTop;
this.left = frame.offsetLeft;
this.width = frame.offsetWidth;
this.height = height;
// reposition frame
frame.style.left = asSize(options.left, '0px');
frame.style.top = asSize(options.top, '');
frame.style.bottom = asSize(options.bottom, '');
frame.style.width = asSize(options.width, '100%');
frame.style.height = asSize(options.height, this.height + 'px');
// reposition axis
this.dom.axis.style.left = asSize(options.left, '0px');
this.dom.axis.style.width = asSize(options.width, '100%');
if (orientation == 'bottom') {
this.dom.axis.style.top = (this.top + this.height) + 'px';
}
else { // orientation == 'top'
this.dom.axis.style.top = this.top + 'px';
}
return false; return false;
}; };
@ -406,86 +428,21 @@ ItemSet.prototype.getAxis = function getAxis() {
* @return {Boolean} resized * @return {Boolean} resized
*/ */
ItemSet.prototype.reflow = function reflow () { ItemSet.prototype.reflow = function reflow () {
var changed = 0,
options = this.options,
marginAxis = (options.margin && 'axis' in options.margin) ? options.margin.axis : this.defaultOptions.margin.axis,
marginItem = (options.margin && 'item' in options.margin) ? options.margin.item : this.defaultOptions.margin.item,
update = util.updateProperty,
asNumber = util.option.asNumber,
asSize = util.option.asSize,
frame = this.frame;
if (frame) {
this._updateConversion();
/* TODO
util.forEach(this.items, function (item) {
changed += item.reflow();
});
*/
// TODO: stack.update should be triggered via an event, in stack itself
// TODO: only update the stack when there are changed items
//this.stack.update();
var maxHeight = asNumber(options.maxHeight);
var fixedHeight = (asSize(options.height) != null);
var height;
if (fixedHeight) {
height = frame.offsetHeight;
}
else {
// height is not specified, determine the height from the height and positioned items
var visibleItems = this.visibleItems; // TODO: not so nice way to get the filtered items
if (visibleItems.length) { // TODO: calculate max height again
var min = visibleItems[0].top;
var max = visibleItems[0].top + visibleItems[0].height;
util.forEach(visibleItems, function (item) {
min = Math.min(min, item.top);
max = Math.max(max, (item.top + item.height));
});
height = (max - min) + marginAxis + marginItem;
}
else {
height = marginAxis + marginItem;
}
}
if (maxHeight != null) {
height = Math.min(height, maxHeight);
}
height = 200; // TODO: cleanup
changed += update(this, 'height', height);
// calculate height from items
changed += update(this, 'top', frame.offsetTop);
changed += update(this, 'left', frame.offsetLeft);
changed += update(this, 'width', frame.offsetWidth);
}
else {
changed += 1;
}
return false;
// TODO: remove this function
return true;
}; };
/** /**
* Hide this component from the DOM * Hide this component from the DOM
* @return {Boolean} changed
*/ */
ItemSet.prototype.hide = function hide() { ItemSet.prototype.hide = function hide() {
var changed = false;
// remove the DOM // remove the DOM
if (this.frame && this.frame.parentNode) { if (this.frame && this.frame.parentNode) {
this.frame.parentNode.removeChild(this.frame); this.frame.parentNode.removeChild(this.frame);
changed = true;
} }
if (this.dom.axis && this.dom.axis.parentNode) { if (this.dom.axis && this.dom.axis.parentNode) {
this.dom.axis.parentNode.removeChild(this.dom.axis); this.dom.axis.parentNode.removeChild(this.dom.axis);
changed = true;
} }
return changed;
}; };
/** /**
@ -566,16 +523,7 @@ ItemSet.prototype.removeItem = function removeItem (id) {
ItemSet.prototype._onUpdate = function _onUpdate(ids) { ItemSet.prototype._onUpdate = function _onUpdate(ids) {
var me = this, var me = this,
items = this.items, items = this.items,
defaultOptions = {
type: 'box',
align: 'center',
orientation: 'bottom',
margin: {
axis: 20,
item: 10
},
padding: 5
};
itemOptions = this.itemOptions;
ids.forEach(function (id) { ids.forEach(function (id) {
var itemData = me.itemsData.get(id), var itemData = me.itemsData.get(id),
@ -608,7 +556,7 @@ ItemSet.prototype._onUpdate = function _onUpdate(ids) {
if (!item) { if (!item) {
// create item // create item
if (constructor) { if (constructor) {
item = new constructor(me, itemData, options, defaultOptions);
item = new constructor(me, itemData, options, itemOptions);
item.id = id; item.id = id;
} }
else { else {
@ -807,8 +755,7 @@ ItemSet.prototype._onDragEnd = function (event) {
// prepare a change set for the changed items // prepare a change set for the changed items
var changes = [], var changes = [],
me = this, me = this,
dataset = this._myDataSet(),
type;
dataset = this._myDataSet();
this.touchParams.itemProps.forEach(function (props) { this.touchParams.itemProps.forEach(function (props) {
var id = props.item.id, var id = props.item.id,

+ 2
- 2
src/timeline/component/css/item.css View File

@ -7,8 +7,8 @@
display: inline-block; display: inline-block;
padding: 5px; padding: 5px;
-webkit-transition: top .4s ease-in-out, height .4s ease-in-out;
transition: top .4s ease-in-out, height .4s ease-in-out;
-webkit-transition: top .4s ease-in-out, bottom .4s ease-in-out, height .4s ease-in-out;
transition: top .4s ease-in-out, bottom .4s ease-in-out, height .4s ease-in-out;
} }
.vis.timeline .item.selected { .vis.timeline .item.selected {

+ 12
- 11
src/timeline/component/item/ItemBox.js View File

@ -211,21 +211,22 @@ ItemBox.prototype.repositionY = function repositionY () {
line = this.dom.line, line = this.dom.line,
dot = this.dom.dot; dot = this.dom.dot;
// reposition box
box.style.top = (this.top || 0) + 'px';
// reposition line
if (orientation == 'top') { if (orientation == 'top') {
line.style.top = 0 + 'px';
box.style.top = (this.top || 0) + 'px';
box.style.bottom = '';
line.style.top = '0px';
line.style.bottom = '';
line.style.height = this.top + 'px'; line.style.height = this.top + 'px';
} }
else {
// orientation 'bottom'
line.style.top = (this.top + this.height) + 'px';
line.style.height = Math.max(this.parent.height - this.top - this.height +
this.props.dot.height / 2, 0) + 'px';
else { // orientation 'bottom'
box.style.top = '';
box.style.bottom = (this.top || 0) + 'px';
line.style.top = '';
line.style.bottom = '0px';
line.style.height = this.top + 'px';
} }
// reposition dot
dot.style.top = (-this.props.dot.height / 2) + 'px'; dot.style.top = (-this.props.dot.height / 2) + 'px';
} }

+ 11
- 1
src/timeline/component/item/ItemPoint.js View File

@ -176,5 +176,15 @@ ItemPoint.prototype.repositionX = function repositionX() {
* @Override * @Override
*/ */
ItemPoint.prototype.repositionY = function repositionY () { ItemPoint.prototype.repositionY = function repositionY () {
this.dom.point.style.top = this.top + 'px';
var orientation = this.options.orientation || this.defaultOptions.orientation,
point = this.dom.point;
if (orientation == 'top') {
point.style.top = this.top + 'px';
point.style.bottom = '';
}
else {
point.style.top = '';
point.style.bottom = this.top + 'px';
}
} }

+ 11
- 1
src/timeline/component/item/ItemRange.js View File

@ -190,7 +190,17 @@ ItemRange.prototype.repositionX = function repositionX() {
* @Override * @Override
*/ */
ItemRange.prototype.repositionY = function repositionY() { ItemRange.prototype.repositionY = function repositionY() {
this.dom.box.style.top = this.top + 'px';
var orientation = this.options.orientation || this.defaultOptions.orientation,
box = this.dom.box;
if (orientation == 'top') {
box.style.top = this.top + 'px';
box.style.bottom = '';
}
else {
box.style.top = '';
box.style.bottom = this.top + 'px';
}
}; };
/** /**

Loading…
Cancel
Save