-
Notifications
You must be signed in to change notification settings - Fork 16
Expand file tree
/
Copy pathPile.js
More file actions
148 lines (128 loc) · 3.85 KB
/
Pile.js
File metadata and controls
148 lines (128 loc) · 3.85 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
/**
* Creates pile structure used to sort and stack segments on top of each other
* so they occupy minimum number of rows.
* @class Pile
*
* @returns {Pile} Returns pile structure
*/
export default function Pile () {
var _items = [];
/**
* Adds new segment to pile object.
*
* @param {number} from Left margin of segment.
* @param {number} to Right margin of segment.
* @param {object} context Any reference to user object. It is returned as parameter in callback function of resolve method.
*/
function add(from, to, context) {
if (from < to) {
_items.push(new Segment(from, to, context, 1));
} else {
_items.push(new Segment(to, from, context, -1));
}
}
/**
* Callback function or iterating result offsets of the pile items in the stack.
*
* @callback onPileItemCallback
* @param {number} from The left margin of the segment
* @param {number} to The right margin of the segment
* @param {object} context The context of the pile item
* @param {number} offset Index of the pile item in the stack
*/
/**
* Sorts and stack segments on top of each other so they occupy minimum number of rows.
*
* @param {object} thisArg A context object of the callback function invocation.
* @param {onPileItemCallback} onItem Callback function for setting segments offsets in the pile.
* @returns {number} Number of stacked rows in pile.
*/
function resolve(thisArg, onItem) {
var hash,
backtraceNext,
backtraceTaken,
items, item,
rowItems,
rows,
rowIndex, index,
offset = 0;
if (onItem != null) {
items = _items.slice(0);
items.sort(function (a, b) {
return a.from - b.from;
});
rows = [];
while (items.length > 0) {
hash = {};
backtraceNext = {};
backtraceTaken = {};
getMax(0, items, hash, backtraceNext, backtraceTaken);
rowItems = [];
rows[offset] = [];
index = 0;
while (backtraceNext.hasOwnProperty(index)) {
if (backtraceTaken[index]) {
rowItems.push(index);
rows[offset].push(items[index]);
}
index = backtraceNext[index];
}
for (index = rowItems.length - 1; index >= 0; index -= 1) {
items.splice(rowItems[index], 1);
}
offset += 1;
}
for (rowIndex = 0; rowIndex < offset; rowIndex += 1) {
rowItems = rows[rowIndex];
for (index = 0; index < rowItems.length; index += 1) {
item = rowItems[index];
if (onItem.call(thisArg, item.from, item.to, item.context, rowIndex, offset, item.direction)) {
return offset;
}
}
}
}
return offset;
}
function Segment(from, to, context, direction) {
this.context = context;
this.from = from;
this.to = to;
this.offset = null;
this.direction = direction;
}
function getMax(index, items, hash, backtraceNext, backtraceTaken) {
var result = 0;
if (index >= items.length) {
return 0;
}
if (hash.hasOwnProperty(index)) {
return hash[index];
}
var item = items[index];
var withoutItem = getMax(index + 1, items, hash, backtraceNext, backtraceTaken);
var nextIndex = index + 1;
while (nextIndex < items.length) {
var nextItem = items[nextIndex];
if (nextItem.from >= item.to) {
break;
}
nextIndex += 1;
}
var withItem = 1 + getMax(nextIndex, items, hash, backtraceNext, backtraceTaken);
if (withItem > withoutItem) {
hash[index] = withItem;
backtraceNext[index] = nextIndex;
backtraceTaken[index] = true;
} else {
hash[index] = withoutItem;
backtraceNext[index] = index + 1;
backtraceTaken[index] = false;
}
return hash[index];
}
return {
add: add,
resolve: resolve
};
};