Browse Source

Stacking starts to work

css_transitions
josdejong 10 years ago
parent
commit
62c22f604f
6 changed files with 114 additions and 70 deletions
  1. +1
    -1
      examples/timeline/03_much_data.html
  2. +34
    -26
      src/timeline/Stack.js
  3. +21
    -13
      src/timeline/component/ItemSet.js
  4. +3
    -0
      src/timeline/component/css/item.css
  5. +20
    -5
      src/timeline/component/item/Item.js
  6. +35
    -25
      src/timeline/component/item/ItemBox.js

+ 1
- 1
examples/timeline/03_much_data.html View File

@ -22,7 +22,7 @@
</h1>
<p>
<label for="count">Number of items</label>
<input id="count" value="100">
<input id="count" value="1000">
<input id="draw" type="button" value="draw">
</p>
<div id="visualization"></div>

+ 34
- 26
src/timeline/Stack.js View File

@ -175,17 +175,19 @@ Stack.prototype._stack = function _stack (items) {
};
/**
* Stack an item on top of given set of items
* @param {Item} item
* @param {Item[]} items
* Adjust vertical positions of the events such that they don't overlap each
* other.
* @param {Item[]} items All visible items
* @private
*/
Stack.prototype.stack = function stack (item, items) {
var options = this.options,
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; // TODO: should use the height of the itemsets parent
parentHeight = this.itemset.height;
if (options.margin && options.margin.item !== undefined) {
margin = options.margin.item;
@ -194,30 +196,36 @@ Stack.prototype.stack = function stack (item, items) {
margin = this.defaultOptions.margin.item
}
// initialize top position
if (orientation == 'top') {
item.top = margin;
}
else {
// default or 'bottom'
item.top = parentHeight - item.height - 2 * margin;
}
// calculate new, non-overlapping position
do {
// TODO: optimize checking for overlap. when there is a gap without items,
// you only need to check for items from the next item on, not from zero
var collidingItem = this.checkOverlap(item, items, margin);
if (collidingItem != null) {
// There is a collision. Reposition the event above the colliding element
// calculate new, non-overlapping positions
for (i = 0, iMax = items.length; i < iMax; i++) {
var item = items[i];
if (item.top === null) {
// initialize top position
if (axisOnTop) {
item.top = collidingItem.top + collidingItem.height + margin;
item.top = margin;
}
else {
item.top = collidingItem.top - item.height - margin;
// default or 'bottom'
item.top = parentHeight - item.height - 2 * margin;
}
var collidingItem;
do {
// TODO: optimize checking for overlap. when there is a gap without items,
// you only need to check for items from the next item on, not from zero
collidingItem = this.checkOverlap (item, items, margin);
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;
}
}
} while (collidingItem);
}
} while (collidingItem);
}
};
/**
@ -235,7 +243,7 @@ Stack.prototype.stack = function stack (item, items) {
Stack.prototype.checkOverlap = function checkOverlap (item, items, margin) {
for (var i = 0, ii = items.length; i < ii; i++) {
var b = items[i];
if (b !== item && b.top !== null && this.collision(item, b, margin)) {
if (b.top !== null && b !== item && this.collision(item, b, margin)) {
return b;
}
}

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

@ -232,6 +232,8 @@ ItemSet.prototype.repaint = function repaint() {
orientation = this.getOption('orientation'),
frame = this.frame;
this._updateConversion();
if (!frame) {
frame = document.createElement('div');
frame.className = 'itemset';
@ -296,8 +298,6 @@ ItemSet.prototype.repaint = function repaint() {
changed += update(this.dom.axis.style, 'top', this.top + 'px');
}
this._updateConversion();
// find start of visible items
var start = this.visibleItemsStart;
var item = this.orderedItems[start];
@ -333,18 +333,27 @@ ItemSet.prototype.repaint = function repaint() {
this.visibleItems = this.orderedItems.slice(start, end);
// check whether zoomed (in that case we need to re-stack everything)
var visibleInterval = this.range.end - this.range.start;
var zoomed = this.visibleInterval != visibleInterval;
this.visibleInterval = visibleInterval;
// show visible items
for (var i = start; i < end; i++) {
var item = this.orderedItems[i];
for (var i = 0, ii = this.visibleItems.length; i < ii; i++) {
var item = this.visibleItems[i];
if (!item.displayed) item.show();
item.top = null; // TODO: do not re-stack every time, only on scroll
if (zoomed) item.top = null; // reset stacking position
// reposition item horizontally
item.repositionX();
}
// reposition visible items
for (var i = start; i < end; i++) {
var item = this.orderedItems[i];
this.stack.stack(item, this.visibleItems);
item.reposition();
// reposition visible items vertically
// TODO: improve stacking, when moving the timeline to the right, update stacking in backward order
this.stack.stack(this.visibleItems);
for (var i = 0, ii = this.visibleItems.length; i < ii; i++) {
this.visibleItems[i].repositionY();
}
return false;
@ -409,9 +418,8 @@ ItemSet.prototype.reflow = function reflow () {
}
else {
// height is not specified, determine the height from the height and positioned items
var visibleItems = this.stack.ordered; // TODO: not so nice way to get the filtered items
//if (visibleItems.length) { // TODO: calculate max height again
if (false) {
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) {

+ 3
- 0
src/timeline/component/css/item.css View File

@ -6,6 +6,9 @@
background-color: #D5DDF6;
display: inline-block;
padding: 5px;
-webkit-transition: top .4s, height .4s, background-color .4s, -webkit-transform .4s ease-in-out;
transition: top .4s, height .4s, background-color .4s, transform .4s ease-in-out;
}
.vis.timeline .item.selected {

+ 20
- 5
src/timeline/component/item/Item.js View File

@ -16,11 +16,11 @@ function Item (parent, data, options, defaultOptions) {
this.selected = false;
this.visible = false;
this.top = 0;
this.left = 0;
this.width = 0;
this.height = 0;
this.offset = 0;
this.top = null;
this.left = null;
this.width = null;
this.height = null;
this.offset = 0; // TODO: is offset still used or redundant?
}
/**
@ -73,10 +73,25 @@ Item.prototype.reflow = function reflow() {
return false;
};
/**
* Reposition the Item horizontally
*/
Item.prototype.repositionX = function repositionX() {
// should be implemented by the item
};
/**
* Reposition the Item vertically
*/
Item.prototype.repositionY = function repositionY() {
// should be implemented by the item
};
/**
* Give the item a display offset in pixels
* @param {Number} offset Offset on screen in pixels
*/
// TODO: is setOffset redundant?
Item.prototype.setOffset = function setOffset(offset) {
this.offset = offset;
};

+ 35
- 25
src/timeline/component/item/ItemBox.js View File

@ -170,9 +170,9 @@ ItemBox.prototype.show = function show() {
* Hide the item from the DOM (when visible)
*/
ItemBox.prototype.hide = function hide() {
var dom = this.dom;
if (this.displayed) {
var dom = this.dom;
if (dom.box.parentNode) dom.box.parentNode.removeChild(dom.box);
if (dom.line.parentNode) dom.line.parentNode.removeChild(dom.line);
if (dom.dot.parentNode) dom.dot.parentNode.removeChild(dom.dot);
@ -181,27 +181,22 @@ ItemBox.prototype.hide = function hide() {
}
this.top = null;
this.left = null;
};
/**
* Reposition the item, recalculate its left, top, and width, using the current
* range and size of the items ItemSet
* @override
* Reposition the item horizontally
* @Override
*/
ItemBox.prototype.reposition = function reposition() {
var dom = this.dom,
props = this.props,
options = this.options,
start = this.parent.toScreen(this.data.start) + this.offset,
align = options.align || this.defaultOptions.align,
orientation = this.options.orientation || this.defaultOptions.orientation,
left;
var box = dom.box,
line = dom.line,
dot = dom.dot;
// calculate left and top position of the box
ItemBox.prototype.repositionX = function repositionX() {
var start = this.parent.toScreen(this.data.start) + this.offset,
align = this.options.align || this.defaultOptions.align,
left,
box = this.dom.box,
line = this.dom.line,
dot = this.dom.dot;
// calculate left position of the box
if (align == 'right') {
this.left = start - this.width;
}
@ -213,14 +208,30 @@ ItemBox.prototype.reposition = function reposition() {
this.left = start - this.width / 2;
}
// NOTE: this.top is determined when stacking items
// reposition box
box.style.left = this.left + 'px';
// reposition line
line.style.left = (start - this.props.line.width / 2) + 'px';
// reposition dot
dot.style.left = (start - this.props.dot.width / 2) + 'px';
};
/**
* Reposition the item vertically
* @Override
*/
ItemBox.prototype.repositionY = function repositionY () {
var orientation = this.options.orientation || this.defaultOptions.orientation,
box = this.dom.box,
line = this.dom.line,
dot = this.dom.dot;
// reposition box
box.style.top = (this.top || 0) + 'px';
// reposition line
line.style.left = (start - props.line.width / 2) + 'px';
if (orientation == 'top') {
line.style.top = 0 + 'px';
line.style.height = this.top + 'px';
@ -233,6 +244,5 @@ ItemBox.prototype.reposition = function reposition() {
}
// reposition dot
dot.style.left = (start - props.dot.width / 2) + 'px';
dot.style.top = (-props.dot.height / 2) + 'px';
};
dot.style.top = (-this.props.dot.height / 2) + 'px';
}

Loading…
Cancel
Save