Skip to content
This repository was archived by the owner on Jul 29, 2019. It is now read-only.

Commit 7f86cad

Browse files
yotamberkmojoaxel
authored andcommitted
feat: new stackSubgroups option (#2519)
* Fix redraw order * Fix error when option is not defined * Allow template labels * Fix boolean types bug * Add subgroup stacking * Almost finish subgroup stacking * Play with examples for subgroup order * Fix stacked subgroups * Fix subgroup stacking * Add stackSubgroups option * Fix example * Add docs * Fix onRemove item subgroups recalculate * Return subgroup example and add stackSubgroup example * Split stackSubgroup example to subgroup/html and expectedVsActualTimesItems.html
1 parent 412f6e3 commit 7f86cad

File tree

8 files changed

+265
-70
lines changed

8 files changed

+265
-70
lines changed

docs/timeline/index.html

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1015,6 +1015,13 @@ <h2 id="Configuration_Options">Configuration Options</h2>
10151015
<td>If true (default), items will be stacked on top of each other such that they do not overlap.</td>
10161016
</tr>
10171017

1018+
<tr>
1019+
<td>stackSubgroups</td>
1020+
<td>boolean</td>
1021+
<td><code>true</code></td>
1022+
<td>If true (default), subgroups will be stacked on top of each other such that they do not overlap.</td>
1023+
</tr>
1024+
10181025
<tr>
10191026
<td>snap</td>
10201027
<td>function or null</td>

examples/timeline/groups/subgroups.html

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
<!DOCTYPE HTML>
22
<html>
33
<head>
4-
<title>Timeline | Background areas</title>
4+
<title>Timeline | Subgroups</title>
5+
6+
<script src="../../../dist/vis.js"></script>
7+
<link href="../../../dist/vis.css" rel="stylesheet" type="text/css" />
58

69
<style>
710
body, html {
@@ -19,14 +22,11 @@
1922
border-left: 2px solid green;
2023
}
2124
</style>
22-
23-
<script src="../../../dist/vis.js"></script>
24-
<link href="../../../dist/vis-timeline-graph2d.min.css" rel="stylesheet" type="text/css" />
25-
<script src="../../googleAnalytics.js"></script>
2625
</head>
2726
<body>
2827

2928
<p>This example shows the workings of the subgroups. Subgroups do not use stacking, and only work when stacking is disabled.</p>
29+
<button onclick="toggleStackSubgroups()">Toggle stackSubgroups</button>
3030

3131
<div id="visualization"></div>
3232

@@ -66,11 +66,16 @@
6666
start: '2014-01-10',
6767
end: '2014-02-10',
6868
editable: true,
69-
stack: false
69+
stack: false,
70+
stackSubgroups: true
7071
};
7172

7273
var timeline = new vis.Timeline(container, items, groups, options);
7374

75+
function toggleStackSubgroups() {
76+
options.stackSubgroups = !options.stackSubgroups;
77+
timeline.setOptions(options);
78+
}
7479
</script>
7580
</body>
7681
</html>
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
<!DOCTYPE HTML>
2+
<html>
3+
<head>
4+
<title>Timeline | expected vs actual times items</title>
5+
6+
<script src="../../../dist/vis.js"></script>
7+
<link href="../../../dist/vis.css" rel="stylesheet" type="text/css" />
8+
9+
<style>
10+
body, html {
11+
font-family: arial, sans-serif;
12+
font-size: 11pt;
13+
}
14+
.vis-item.expected {
15+
background-color: transparent;
16+
border-style: dashed!important;
17+
z-index: 0;
18+
}
19+
.vis-item.vis-selected {
20+
opacity: 0.6;
21+
}
22+
.vis-item.vis-background.marker {
23+
border-left: 2px solid green;
24+
}
25+
</style>
26+
</head>
27+
<body>
28+
29+
<div id="visualization"></div>
30+
31+
<script>
32+
33+
// create a dataset with items
34+
// we specify the type of the fields `start` and `end` here to be strings
35+
// containing an ISO date. The fields will be outputted as ISO dates
36+
// automatically getting data from the DataSet via items.get().
37+
var items = new vis.DataSet({
38+
type: { start: 'ISODate', end: 'ISODate' }
39+
});
40+
41+
var groups = new vis.DataSet([
42+
{
43+
id: 'group1',
44+
content:'group1'
45+
}
46+
]);
47+
48+
// add items to the DataSet
49+
items.add([
50+
// group 1
51+
{
52+
id: 'background1',
53+
start: '2014-01-21',
54+
end: '2014-01-22',
55+
type: 'background',
56+
group:'group1'
57+
},
58+
59+
// subgroup 1
60+
{
61+
id: 1,
62+
content: 'item 1 (expected time)',
63+
className: 'expected',
64+
start: '2014-01-23 12:00:00',
65+
end: '2014-01-26 12:00:00',
66+
group:'group1',
67+
subgroup:'sg_1'
68+
},
69+
{
70+
id: 2,
71+
content: 'item 1 (actual time)',
72+
start: '2014-01-24 12:00:00',
73+
end: '2014-01-27 12:00:00',
74+
group:'group1',
75+
subgroup:'sg_1'
76+
},
77+
78+
// subgroup 2
79+
{
80+
id: 3,
81+
content: 'item 2 (expected time)',
82+
className: 'expected',
83+
start: '2014-01-13 12:00:00',
84+
end: '2014-01-16 12:00:00',
85+
group:'group1',
86+
subgroup:'sg_2'
87+
},
88+
{
89+
id: 4,
90+
content: 'item 2 (actual time)',
91+
start: '2014-01-14 12:00:00',
92+
end: '2014-01-17 12:00:00',
93+
group:'group1',
94+
subgroup:'sg_2'
95+
}
96+
]);
97+
98+
var container = document.getElementById('visualization');
99+
var options = {
100+
start: '2014-01-10',
101+
end: '2014-02-10',
102+
editable: true,
103+
stack: false,
104+
stackSubgroups: false
105+
};
106+
107+
var timeline = new vis.Timeline(container, items, groups, options);
108+
109+
</script>
110+
</body>
111+
</html>

lib/timeline/Stack.js

Lines changed: 69 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,15 @@ exports.orderByEnd = function(items) {
3737
* items having a top===null will be re-stacked
3838
*/
3939
exports.stack = function(items, margin, force) {
40-
var i, iMax;
4140
if (force) {
4241
// reset top position of all items
43-
for (i = 0, iMax = items.length; i < iMax; i++) {
42+
for (var i = 0; i < items.length; i++) {
4443
items[i].top = null;
4544
}
4645
}
4746

4847
// calculate new, non-overlapping positions
49-
for (i = 0, iMax = items.length; i < iMax; i++) {
48+
for (var i = 0; i < items.length; i++) {
5049
var item = items[i];
5150
if (item.stack && item.top === null) {
5251
// initialize top position
@@ -80,29 +79,70 @@ exports.stack = function(items, margin, force) {
8079
* All visible items
8180
* @param {{item: {horizontal: number, vertical: number}, axis: number}} margin
8281
* Margins between items and between items and the axis.
82+
* @param {subgroups[]} subgroups
83+
* All subgroups
8384
*/
84-
exports.nostack = function(items, margin, subgroups) {
85-
var i, iMax, newTop;
86-
87-
// reset top position of all items
88-
for (i = 0, iMax = items.length; i < iMax; i++) {
89-
if (items[i].data.subgroup !== undefined) {
90-
newTop = margin.axis;
85+
exports.nostack = function(items, margin, subgroups, stackSubgroups) {
86+
for (var i = 0; i < items.length; i++) {
87+
if (items[i].data.subgroup == undefined) {
88+
items[i].top = margin.item.vertical;
89+
} else if (items[i].data.subgroup !== undefined && stackSubgroups) {
90+
var newTop = 0;
9191
for (var subgroup in subgroups) {
9292
if (subgroups.hasOwnProperty(subgroup)) {
9393
if (subgroups[subgroup].visible == true && subgroups[subgroup].index < subgroups[items[i].data.subgroup].index) {
94-
newTop += subgroups[subgroup].height + margin.item.vertical;
94+
newTop += subgroups[subgroup].height;
95+
subgroups[items[i].data.subgroup].top = newTop;
9596
}
9697
}
9798
}
98-
items[i].top = newTop;
99-
}
100-
else {
101-
items[i].top = margin.axis;
99+
items[i].top = newTop + 0.5 * margin.item.vertical;
102100
}
103101
}
102+
if (!stackSubgroups) {
103+
exports.stackSubgroups(items, margin, subgroups)
104+
}
104105
};
105106

107+
/**
108+
* Adjust vertical positions of the subgroups such that they don't overlap each
109+
* other.
110+
* @param {subgroups[]} subgroups
111+
* All subgroups
112+
* @param {{item: {horizontal: number, vertical: number}, axis: number}} margin
113+
* Margins between items and between items and the axis.
114+
*/
115+
exports.stackSubgroups = function(items, margin, subgroups) {
116+
for (var subgroup in subgroups) {
117+
if (subgroups.hasOwnProperty(subgroup)) {
118+
119+
120+
subgroups[subgroup].top = 0;
121+
do {
122+
// TODO: optimize checking for overlap. when there is a gap without items,
123+
// you only need to check for items from the next item on, not from zero
124+
var collidingItem = null;
125+
for (var otherSubgroup in subgroups) {
126+
if (subgroups[otherSubgroup].top !== null && otherSubgroup !== subgroup && subgroups[subgroup].index > subgroups[otherSubgroup].index && exports.collisionByTimes(subgroups[subgroup], subgroups[otherSubgroup])) {
127+
collidingItem = subgroups[otherSubgroup];
128+
break;
129+
}
130+
}
131+
132+
if (collidingItem != null) {
133+
// There is a collision. Reposition the subgroups above the colliding element
134+
subgroups[subgroup].top = collidingItem.top + collidingItem.height;
135+
}
136+
} while (collidingItem);
137+
}
138+
}
139+
for (var i = 0; i < items.length; i++) {
140+
if (items[i].data.subgroup !== undefined) {
141+
items[i].top = subgroups[items[i].data.subgroup].top + 0.5 * margin.item.vertical;
142+
}
143+
}
144+
}
145+
106146
/**
107147
* Test if the two provided items collide
108148
* The items must have parameters left, width, top, and height.
@@ -127,3 +167,17 @@ exports.collision = function(a, b, margin, rtl) {
127167
(a.top + a.height + margin.vertical - EPSILON) > b.top);
128168
}
129169
};
170+
171+
/**
172+
* Test if the two provided objects collide
173+
* The objects must have parameters start, end, top, and height.
174+
* @param {Object} a The first Object
175+
* @param {Object} b The second Object
176+
* @return {boolean} true if a and b collide, else false
177+
*/
178+
exports.collisionByTimes = function(a, b) {
179+
return (
180+
(a.start < b.start && a.end > b.start && a.top < (b.top + b.height) && (a.top + a.height) > b.top ) ||
181+
(b.start < a.start && b.end > a.start && b.top < (a.top + a.height) && (b.top + b.height) > a.top )
182+
)
183+
}

0 commit comments

Comments
 (0)