Browse Source

Refactored Itemset.repaint, simplified vertical positioning of items

css_transitions
josdejong 10 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
* Stacks items on top of each other.
* @param {ItemSet} itemset
* @param {Object} [options]
*/
function Stack (itemset, options) {
this.itemset = itemset;
function Stack (options) {
this.options = options || {};
this.defaultOptions = {
order: function (a, b) {
@ -34,24 +31,21 @@ function Stack (itemset, options) {
}
},
margin: {
item: 10
item: 10,
axis: 20
}
};
this.ordered = []; // ordered items
}
/**
* Set options for the stack
* @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) {
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,
iMax,
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) {
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 {
margin = this.defaultOptions.margin.item
marginAxis = this.defaultOptions.margin.axis
}
// calculate new, non-overlapping positions
@ -118,13 +116,7 @@ Stack.prototype.stack = function stack (items) {
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;
}
item.top = marginAxis;
do {
// 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;
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)) {
if (other.top !== null && other !== item && this.collision(item, other, marginItem)) {
collidingItem = other;
break;
}
@ -140,12 +132,7 @@ Stack.prototype.stack = function stack (items) {
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;
}
item.top = collidingItem.top + collidingItem.height + marginItem;
}
} while (collidingItem);
}

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

@ -27,6 +27,15 @@ function Timeline (container, items, options) {
showCurrentTime: false,
showCustomTime: false,
type: 'box',
align: 'center',
orientation: 'bottom',
margin: {
axis: 20,
item: 10
},
padding: 5,
onAdd: function (item, callback) {
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
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 = {};
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.selection = []; // list with the ids of all selected nodes
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.touchParams = {}; // stores properties while dragging
@ -219,12 +209,10 @@ ItemSet.prototype._deselect = function _deselect(id) {
/**
* Repaint the component
* @return {Boolean} changed
*/
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,
orientation = this.getOption('orientation'),
frame = this.frame;
@ -260,7 +248,6 @@ ItemSet.prototype.repaint = function repaint() {
this.dom.axis = axis;
this.frame = frame;
changed += 1;
}
if (!this.parent) {
@ -272,27 +259,9 @@ ItemSet.prototype.repaint = function repaint() {
}
if (!frame.parentNode) {
parentContainer.appendChild(frame);
changed += 1;
}
if (!this.dom.axis.parentNode) {
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)
@ -300,7 +269,7 @@ ItemSet.prototype.repaint = function repaint() {
var zoomed = this.visibleInterval != visibleInterval;
this.visibleInterval = visibleInterval;
/*
/* TODO: implement+fix smarter way to update visible items
// find the first visible item
// TODO: use faster search, not linear
var byEnd = this.orderedItems.byEnd;
@ -368,12 +337,65 @@ ItemSet.prototype.repaint = function repaint() {
}
// reposition visible items vertically
this.stack.order(this.visibleItems);
//this.stack.order(this.visibleItems); // TODO: solve ordering issue
this.stack.stack(this.visibleItems);
for (var i = 0, ii = this.visibleItems.length; i < ii; i++) {
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;
};
@ -406,86 +428,21 @@ ItemSet.prototype.getAxis = function getAxis() {
* @return {Boolean} resized
*/
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
* @return {Boolean} changed
*/
ItemSet.prototype.hide = function hide() {
var changed = false;
// remove the DOM
if (this.frame && this.frame.parentNode) {
this.frame.parentNode.removeChild(this.frame);
changed = true;
}
if (this.dom.axis && this.dom.axis.parentNode) {
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) {
var me = this,
items = this.items,
defaultOptions = {
type: 'box',
align: 'center',
orientation: 'bottom',
margin: {
axis: 20,
item: 10
},
padding: 5
};
itemOptions = this.itemOptions;
ids.forEach(function (id) {
var itemData = me.itemsData.get(id),
@ -608,7 +556,7 @@ ItemSet.prototype._onUpdate = function _onUpdate(ids) {
if (!item) {
// create item
if (constructor) {
item = new constructor(me, itemData, options, defaultOptions);
item = new constructor(me, itemData, options, itemOptions);
item.id = id;
}
else {
@ -807,8 +755,7 @@ ItemSet.prototype._onDragEnd = function (event) {
// prepare a change set for the changed items
var changes = [],
me = this,
dataset = this._myDataSet(),
type;
dataset = this._myDataSet();
this.touchParams.itemProps.forEach(function (props) {
var id = props.item.id,

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

@ -7,8 +7,8 @@
display: inline-block;
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 {

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

@ -211,21 +211,22 @@ ItemBox.prototype.repositionY = function repositionY () {
line = this.dom.line,
dot = this.dom.dot;
// reposition box
box.style.top = (this.top || 0) + 'px';
// reposition line
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';
}
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';
}

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

@ -176,5 +176,15 @@ ItemPoint.prototype.repositionX = function repositionX() {
* @Override
*/
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
*/
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