chaplinks-library-timeline 2.5.5 → 2.9.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5c4e0f75794aac680cd4a37fe7d374291af77a0d
4
- data.tar.gz: b539d0ab3af508943edf526d9154de6336d49461
3
+ metadata.gz: 38fefa2f824f0f0240cf305b0957211958d4feb7
4
+ data.tar.gz: faf9c59c8ab1c628c66ae2c85b7a7b49babe23eb
5
5
  SHA512:
6
- metadata.gz: 84cfd145f6462c1515eb9d9b4277f3f1651c0e13c677c065d197dccc8ca1e9fd9bdff2ec75c2edd2f1103e0664fa69263ea5cb69be09c7ee9618075bc274a9a2
7
- data.tar.gz: 2bf06dfb8696f46dd80afe22482cfe83b159268bce99510ab0f5df4d3e50cdcd7df9b05b022bb9f0dcce08e1a6f23cf2e1444c11fb31022b5360b191fbdb020e
6
+ metadata.gz: 0da5dfa904b50daadace34c2b98933bdf8e1a219d75934df78f654ae9a5d8ef7b67a7e21665f1372a8cef6e2e45615eb74e10fb014bc750422ab3e42d3cf4c6a
7
+ data.tar.gz: 81002371685bc5994f9a29e59f9d0c22c30eddbbdb7f6fd09e35f1510a71986b26330f6c701544452a0528d89fcdb911072e0d560526a0a88943f145d2237ce7
data/Gemfile CHANGED
@@ -2,3 +2,4 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in chaplinks-library-timeline.gemspec
4
4
  gemspec
5
+
@@ -1,7 +1,7 @@
1
1
  module Chaplinks
2
2
  module Library
3
3
  module Timeline
4
- VERSION = "2.5.5"
4
+ VERSION = "2.9.0"
5
5
  end
6
6
  end
7
7
  end
@@ -1,4 +1,4 @@
1
- if (typeof links === 'undefined') {
1
+ if (typeof links === 'undefined') {
2
2
  links = {};
3
3
  links.locales = {};
4
4
  } else if (typeof links.locales === 'undefined') {
@@ -7,10 +7,10 @@ if (typeof links === 'undefined') {
7
7
 
8
8
  // English ===================================================
9
9
  links.locales['en'] = {
10
- 'MONTHS': new Array("January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"),
11
- 'MONTHS_SHORT': new Array("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"),
12
- 'DAYS': new Array("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"),
13
- 'DAYS_SHORT': new Array("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"),
10
+ 'MONTHS': ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
11
+ 'MONTHS_SHORT': ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
12
+ 'DAYS': ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
13
+ 'DAYS_SHORT': ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
14
14
  'ZOOM_IN': "Zoom in",
15
15
  'ZOOM_OUT': "Zoom out",
16
16
  'MOVE_LEFT': "Move left",
@@ -24,10 +24,10 @@ links.locales['en_UK'] = links.locales['en'];
24
24
 
25
25
  // French ===================================================
26
26
  links.locales['fr'] = {
27
- 'MONTHS': new Array("Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet", "Août", "Septembre", "Octobre", "Novembre", "Décembre"),
28
- 'MONTHS_SHORT': new Array("Jan", "Fev", "Mar", "Avr", "Mai", "Jun", "Jul", "Aou", "Sep", "Oct", "Nov", "Dec"),
29
- 'DAYS': new Array("Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi"),
30
- 'DAYS_SHORT': new Array("Dim", "Lun", "Mar", "Mer", "Jeu", "Ven", "Sam"),
27
+ 'MONTHS': ["Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet", "Août", "Septembre", "Octobre", "Novembre", "Décembre"],
28
+ 'MONTHS_SHORT': ["Jan", "Fev", "Mar", "Avr", "Mai", "Jun", "Jul", "Aou", "Sep", "Oct", "Nov", "Dec"],
29
+ 'DAYS': ["Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi"],
30
+ 'DAYS_SHORT': ["Dim", "Lun", "Mar", "Mer", "Jeu", "Ven", "Sam"],
31
31
  'ZOOM_IN': "Zoomer",
32
32
  'ZOOM_OUT': "Dézoomer",
33
33
  'MOVE_LEFT': "Déplacer à gauche",
@@ -42,10 +42,10 @@ links.locales['fr_CA'] = links.locales['fr'];
42
42
 
43
43
  // Catalan ===================================================
44
44
  links.locales['ca'] = {
45
- 'MONTHS': new Array("Gener", "Febrer", "Març", "Abril", "Maig", "Juny", "Juliol", "Setembre", "Octubre", "Novembre", "Desembre"),
46
- 'MONTHS_SHORT': new Array("Gen", "Feb", "Mar", "Abr", "Mai", "Jun", "Jul", "Ago", "Set", "Oct", "Nov", "Des"),
47
- 'DAYS': new Array("Diumenge", "Dilluns", "Dimarts", "Dimecres", "Dijous", "Divendres", "Dissabte"),
48
- 'DAYS_SHORT': new Array("Dm.", "Dl.", "Dm.", "Dc.", "Dj.", "Dv.", "Ds."),
45
+ 'MONTHS': ["Gener", "Febrer", "Març", "Abril", "Maig", "Juny", "Juliol", "Setembre", "Octubre", "Novembre", "Desembre"],
46
+ 'MONTHS_SHORT': ["Gen", "Feb", "Mar", "Abr", "Mai", "Jun", "Jul", "Ago", "Set", "Oct", "Nov", "Des"],
47
+ 'DAYS': ["Diumenge", "Dilluns", "Dimarts", "Dimecres", "Dijous", "Divendres", "Dissabte"],
48
+ 'DAYS_SHORT': ["Dm.", "Dl.", "Dm.", "Dc.", "Dj.", "Dv.", "Ds."],
49
49
  'ZOOM_IN': "Augmentar zoom",
50
50
  'ZOOM_OUT': "Disminuir zoom",
51
51
  'MOVE_LEFT': "Moure esquerra",
@@ -57,10 +57,10 @@ links.locales['ca_ES'] = links.locales['ca'];
57
57
 
58
58
  // German ===================================================
59
59
  links.locales['de'] = {
60
- 'MONTHS': new Array("Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"),
61
- 'MONTHS_SHORT': new Array("Jan", "Feb", "Mär", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez"),
62
- 'DAYS': new Array("Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag"),
63
- 'DAYS_SHORT': new Array("Son", "Mon", "Die", "Mit", "Don", "Fre", "Sam"),
60
+ 'MONTHS': ["Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"],
61
+ 'MONTHS_SHORT': ["Jan", "Feb", "Mär", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez"],
62
+ 'DAYS': ["Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag"],
63
+ 'DAYS_SHORT': ["Son", "Mon", "Die", "Mit", "Don", "Fre", "Sam"],
64
64
  'ZOOM_IN': "Vergrößern",
65
65
  'ZOOM_OUT': "Verkleinern",
66
66
  'MOVE_LEFT': "Nach links verschieben",
@@ -74,10 +74,10 @@ links.locales['de_CH'] = links.locales['de'];
74
74
 
75
75
  // Danish ===================================================
76
76
  links.locales['da'] = {
77
- 'MONTHS': new Array("januar", "februar", "marts", "april", "maj", "juni", "juli", "august", "september", "oktober", "november", "december"),
78
- 'MONTHS_SHORT': new Array("jan", "feb", "mar", "apr", "maj", "jun", "jul", "aug", "sep", "okt", "nov", "dec"),
79
- 'DAYS': new Array("søndag", "mandag", "tirsdag", "onsdag", "torsdag", "fredag", "lørdag"),
80
- 'DAYS_SHORT': new Array("søn", "man", "tir", "ons", "tor", "fre", "lør"),
77
+ 'MONTHS': ["januar", "februar", "marts", "april", "maj", "juni", "juli", "august", "september", "oktober", "november", "december"],
78
+ 'MONTHS_SHORT': ["jan", "feb", "mar", "apr", "maj", "jun", "jul", "aug", "sep", "okt", "nov", "dec"],
79
+ 'DAYS': ["søndag", "mandag", "tirsdag", "onsdag", "torsdag", "fredag", "lørdag"],
80
+ 'DAYS_SHORT': ["søn", "man", "tir", "ons", "tor", "fre", "lør"],
81
81
  'ZOOM_IN': "Zoom in",
82
82
  'ZOOM_OUT': "Zoom out",
83
83
  'MOVE_LEFT': "Move left",
@@ -89,10 +89,10 @@ links.locales['da_DK'] = links.locales['da'];
89
89
 
90
90
  // Russian ===================================================
91
91
  links.locales['ru'] = {
92
- 'MONTHS': new Array("Январь", "Февраль", "Март", "Апрель", "Май", "Июнь", "Июль", "Август", "Сентябрь", "Октябрь", "Ноябрь", "Декабрь"),
93
- 'MONTHS_SHORT': new Array("Янв", "Фев", "Мар", "Апр", "Май", "Июн", "Июл", "Авг", "Сен", "Окт", "Ноя", "Дек"),
94
- 'DAYS': new Array("Воскресенье", "Понедельник", "Вторник", "Среда", "Четверг", "Пятница", "Суббота"),
95
- 'DAYS_SHORT': new Array("Вос", "Пон", "Втo", "Срe", "Чет", "Пят", "Суб"),
92
+ 'MONTHS': ["Январь", "Февраль", "Март", "Апрель", "Май", "Июнь", "Июль", "Август", "Сентябрь", "Октябрь", "Ноябрь", "Декабрь"],
93
+ 'MONTHS_SHORT': ["Янв", "Фев", "Мар", "Апр", "Май", "Июн", "Июл", "Авг", "Сен", "Окт", "Ноя", "Дек"],
94
+ 'DAYS': ["Воскресенье", "Понедельник", "Вторник", "Среда", "Четверг", "Пятница", "Суббота"],
95
+ 'DAYS_SHORT': ["Вос", "Пон", "Втo", "Срe", "Чет", "Пят", "Суб"],
96
96
  'ZOOM_IN': "Увeличить",
97
97
  'ZOOM_OUT': "Умeньшить",
98
98
  'MOVE_LEFT': "Сдвинуть налeво",
@@ -104,10 +104,10 @@ links.locales['ru_RU'] = links.locales['ru'];
104
104
 
105
105
  // Spanish ===================================================
106
106
  links.locales['es'] = {
107
- 'MONTHS': new Array("Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio", "Julio", "Agosto", "Septiembre", "Octubre", "Noviembre", "Diciembre"),
108
- 'MONTHS_SHORT': new Array("Ene", "Feb", "Mar", "Abr", "May", "Jun", "Jul", "Ago", "Sep", "Oct", "Nov", "Dic"),
109
- 'DAYS': new Array("Domingo", "Lunes", "Martes", "Miércoles", "Jueves", "Viernes", "Sábado"),
110
- 'DAYS_SHORT': new Array("Dom", "Lun", "Mar", "Mié", "Jue", "Vie", "Sáb"),
107
+ 'MONTHS': ["Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio", "Julio", "Agosto", "Septiembre", "Octubre", "Noviembre", "Diciembre"],
108
+ 'MONTHS_SHORT': ["Ene", "Feb", "Mar", "Abr", "May", "Jun", "Jul", "Ago", "Sep", "Oct", "Nov", "Dic"],
109
+ 'DAYS': ["Domingo", "Lunes", "Martes", "Miércoles", "Jueves", "Viernes", "Sábado"],
110
+ 'DAYS_SHORT': ["Dom", "Lun", "Mar", "Mié", "Jue", "Vie", "Sáb"],
111
111
  'ZOOM_IN': "Aumentar zoom",
112
112
  'ZOOM_OUT': "Disminuir zoom",
113
113
  'MOVE_LEFT': "Mover izquierda",
@@ -120,10 +120,10 @@ links.locales['es_ES'] = links.locales['es'];
120
120
 
121
121
  // Dutch =====================================================
122
122
  links.locales['nl'] = {
123
- 'MONTHS': new Array("januari", "februari", "maart", "april", "mei", "juni", "juli", "augustus", "september", "oktober", "november", "december"),
124
- 'MONTHS_SHORT': new Array("jan", "feb", "mrt", "apr", "mei", "jun", "jul", "aug", "sep", "okt", "nov", "dec"),
125
- 'DAYS': new Array("zondag", "maandag", "dinsdag", "woensdag", "donderdag", "vrijdag", "zaterdag"),
126
- 'DAYS_SHORT': new Array("zo", "ma", "di", "wo", "do", "vr", "za"),
123
+ 'MONTHS': ["januari", "februari", "maart", "april", "mei", "juni", "juli", "augustus", "september", "oktober", "november", "december"],
124
+ 'MONTHS_SHORT': ["jan", "feb", "mrt", "apr", "mei", "jun", "jul", "aug", "sep", "okt", "nov", "dec"],
125
+ 'DAYS': ["zondag", "maandag", "dinsdag", "woensdag", "donderdag", "vrijdag", "zaterdag"],
126
+ 'DAYS_SHORT': ["zo", "ma", "di", "wo", "do", "vr", "za"],
127
127
  'ZOOM_IN': "Inzoomen",
128
128
  'ZOOM_OUT': "Uitzoomen",
129
129
  'MOVE_LEFT': "Naar links",
@@ -134,3 +134,35 @@ links.locales['nl'] = {
134
134
 
135
135
  links.locales['nl_NL'] = links.locales['nl'];
136
136
  links.locales['nl_BE'] = links.locales['nl'];
137
+
138
+ // Turkish ===================================================
139
+ links.locales['tr'] = {
140
+ 'MONTHS': ["Ocak", "Şubat", "Mart", "Nisan", "Mayıs", "Haziran", "Temmuz", "Ağustos", "Eylül", "Ekim", "Kasım", "Aralık"],
141
+ 'MONTHS_SHORT': ["Oca", "Şub", "Mar", "Nis", "May", "Haz", "Tem", "Ağu", "Eyl", "Eki", "Kas", "Ara"],
142
+ 'DAYS': ["Pazar", "Pazartesi", "Salı", "Çarşamba", "Perşembe", "Cuma", "Cumartesi"],
143
+ 'DAYS_SHORT': ["Paz", "Pzt", "Sal", "Çar", "Per", "Cum", "Cmt"],
144
+ 'ZOOM_IN': "Büyült",
145
+ 'ZOOM_OUT': "Küçült",
146
+ 'MOVE_LEFT': "Sola Taşı",
147
+ 'MOVE_RIGHT': "Sağa Taşı",
148
+ 'NEW': "Yeni",
149
+ 'CREATE_NEW_EVENT': "Yeni etkinlik oluştur"
150
+ };
151
+
152
+ links.locales['tr_TR'] = links.locales['tr'];
153
+
154
+ // Hungarian ===================================================
155
+ links.locales['hu'] = {
156
+ 'MONTHS': ["január", "február", "március", "április", "május", "június", "július", "augusztus", "szeptember", "október", "november", "december"],
157
+ 'MONTHS_SHORT': ["jan", "feb", "márc", "ápr", "máj", "jún", "júl", "aug", "szep", "okt", "nov", "dec"],
158
+ 'DAYS': ["vasárnap", "hétfő", "kedd", "szerda", "csütörtök", "péntek", "szombat"],
159
+ 'DAYS_SHORT': ["vas", "hét", "kedd", "sze", "csü", "pé", "szo"],
160
+ 'ZOOM_IN': "Nagyítás",
161
+ 'ZOOM_OUT': "Kicsinyítés",
162
+ 'MOVE_LEFT': "Balra",
163
+ 'MOVE_RIGHT': "Jobbra",
164
+ 'NEW': "Új",
165
+ 'CREATE_NEW_EVENT': "Új esemény készítése"
166
+ };
167
+
168
+ links.locales['hu_HU'] = links.locales['hu'];
@@ -27,11 +27,11 @@
27
27
  * License for the specific language governing permissions and limitations under
28
28
  * the License.
29
29
  *
30
- * Copyright (c) 2011-2013 Almende B.V.
30
+ * Copyright (c) 2011-2014 Almende B.V.
31
31
  *
32
- * @author Jos de Jong, <jos@almende.org>
33
- * @date 2013-08-20
34
- * @version 2.5.0
32
+ * @author Jos de Jong, <jos@almende.org>
33
+ * @date 2014-07-28
34
+ * @version 2.9.0
35
35
  */
36
36
 
37
37
  /*
@@ -43,7 +43,7 @@
43
43
  * TODO
44
44
  *
45
45
  * Add zooming with pinching on Android
46
- *
46
+ *
47
47
  * Bug: when an item contains a javascript onclick or a link, this does not work
48
48
  * when the item is not selected (when the item is being selected,
49
49
  * it is redrawn, which cancels any onclick or link action)
@@ -60,8 +60,8 @@
60
60
  */
61
61
  if (typeof links === 'undefined') {
62
62
  links = {};
63
- // important: do not use var, as "var links = {};" will overwrite
64
- // the existing links variable value with undefined in IE8, IE7.
63
+ // important: do not use var, as "var links = {};" will overwrite
64
+ // the existing links variable value with undefined in IE8, IE7.
65
65
  }
66
66
 
67
67
 
@@ -70,7 +70,7 @@ if (typeof links === 'undefined') {
70
70
  */
71
71
  if (typeof google === 'undefined') {
72
72
  google = undefined;
73
- // important: do not use var, as "var google = undefined;" will overwrite
73
+ // important: do not use var, as "var google = undefined;" will overwrite
74
74
  // the existing google variable value with undefined in IE8, IE7.
75
75
  }
76
76
 
@@ -109,9 +109,11 @@ if (!Array.prototype.forEach) {
109
109
  * The timeline is developed in javascript as a Google Visualization Chart.
110
110
  *
111
111
  * @param {Element} container The DOM element in which the Timeline will
112
- * be created. Normally a div element.
112
+ * be created. Normally a div element.
113
+ * @param {Object} options A name/value map containing settings for the
114
+ * timeline. Optional.
113
115
  */
114
- links.Timeline = function(container) {
116
+ links.Timeline = function(container, options) {
115
117
  if (!container) {
116
118
  // this call was probably only for inheritance, no constructor-code is required
117
119
  return;
@@ -136,7 +138,7 @@ links.Timeline = function(container) {
136
138
 
137
139
  this.listeners = {}; // event listener callbacks
138
140
 
139
- // Initialize sizes.
141
+ // Initialize sizes.
140
142
  // Needed for IE (which gives an error when you try to set an undefined
141
143
  // value in a style)
142
144
  this.size = {
@@ -170,10 +172,14 @@ links.Timeline = function(container) {
170
172
 
171
173
  this.dom.container = container;
172
174
 
175
+ //
176
+ // Let's set the default options first
177
+ //
173
178
  this.options = {
174
179
  'width': "100%",
175
180
  'height': "auto",
176
181
  'minHeight': 0, // minimal height in pixels
182
+ 'groupMinHeight': 0,
177
183
  'autoHeight': true,
178
184
 
179
185
  'eventMargin': 10, // minimal margin between events
@@ -192,28 +198,31 @@ links.Timeline = function(container) {
192
198
  'editable': false,
193
199
  'snapEvents': true,
194
200
  'groupChangeable': true,
201
+ 'timeChangeable': true,
195
202
 
196
203
  'showCurrentTime': true, // show a red bar displaying the current time
197
- 'showCustomTime': false, // show a blue, draggable bar displaying a custom time
204
+ 'showCustomTime': false, // show a blue, draggable bar displaying a custom time
198
205
  'showMajorLabels': true,
199
206
  'showMinorLabels': true,
200
207
  'showNavigation': false,
201
208
  'showButtonNew': false,
202
209
  'groupsOnRight': false,
210
+ 'groupsOrder' : true,
203
211
  'axisOnTop': false,
204
212
  'stackEvents': true,
205
213
  'animate': true,
206
214
  'animateZoom': true,
207
215
  'cluster': false,
216
+ 'clusterMaxItems': 5,
208
217
  'style': 'box',
209
218
  'customStackOrder': false, //a function(a,b) for determining stackorder amongst a group of items. Essentially a comparator, -ve value for "a before b" and vice versa
210
219
 
211
220
  // i18n: Timeline only has built-in English text per default. Include timeline-locales.js to support more localized text.
212
221
  'locale': 'en',
213
- 'MONTHS': new Array("January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"),
214
- 'MONTHS_SHORT': new Array("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"),
215
- 'DAYS': new Array("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"),
216
- 'DAYS_SHORT': new Array("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"),
222
+ 'MONTHS': ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
223
+ 'MONTHS_SHORT': ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
224
+ 'DAYS': ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
225
+ 'DAYS_SHORT': ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
217
226
  'ZOOM_IN': "Zoom in",
218
227
  'ZOOM_OUT': "Zoom out",
219
228
  'MOVE_LEFT': "Move left",
@@ -221,6 +230,11 @@ links.Timeline = function(container) {
221
230
  'NEW': "New",
222
231
  'CREATE_NEW_EVENT': "Create new event"
223
232
  };
233
+
234
+ //
235
+ // Now we can set the givenproperties
236
+ //
237
+ this.setOptions(options);
224
238
 
225
239
  this.clientTimeOffset = 0; // difference between client time and the time
226
240
  // set via Timeline.setCurrentTime()
@@ -236,16 +250,17 @@ links.Timeline = function(container) {
236
250
 
237
251
  // add standard item types
238
252
  this.itemTypes = {
239
- box: links.Timeline.ItemBox,
240
- range: links.Timeline.ItemRange,
241
- dot: links.Timeline.ItemDot
253
+ box: links.Timeline.ItemBox,
254
+ range: links.Timeline.ItemRange,
255
+ floatingRange: links.Timeline.ItemFloatingRange,
256
+ dot: links.Timeline.ItemDot
242
257
  };
243
258
 
244
259
  // initialize data
245
260
  this.data = [];
246
261
  this.firstDraw = true;
247
262
 
248
- // date interval must be initialized
263
+ // date interval must be initialized
249
264
  this.setVisibleChartRange(undefined, undefined, false);
250
265
 
251
266
  // render for the first time
@@ -270,11 +285,16 @@ links.Timeline = function(container) {
270
285
  * Object DataTable is defined in
271
286
  * google.visualization.DataTable
272
287
  * @param {Object} options A name/value map containing settings for the
273
- * timeline. Optional.
288
+ * timeline. Optional. The use of options here
289
+ * is deprecated. Pass timeline options in the
290
+ * constructor or use setOptions()
274
291
  */
275
292
  links.Timeline.prototype.draw = function(data, options) {
293
+ if (options) {
294
+ console.log("WARNING: Passing options in draw() is deprecated. Pass options to the constructur or use setOptions() instead!");
295
+ }
276
296
  this.setOptions(options);
277
-
297
+
278
298
  if (this.options.selectable) {
279
299
  links.Timeline.addClassName(this.dom.frame, "timeline-selectable");
280
300
  }
@@ -308,7 +328,7 @@ links.Timeline.prototype.setOptions = function(options) {
308
328
  this.options[i] = options[i];
309
329
  }
310
330
  }
311
-
331
+
312
332
  // prepare i18n dependent on set locale
313
333
  if (typeof links.locales !== 'undefined' && this.options.locale !== 'en') {
314
334
  var localeOpts = links.locales[this.options.locale];
@@ -344,6 +364,15 @@ links.Timeline.prototype.setOptions = function(options) {
344
364
  this.options.autoHeight = (this.options.height === "auto");
345
365
  };
346
366
 
367
+ /**
368
+ * Get options for the timeline.
369
+ *
370
+ * @return the options object
371
+ */
372
+ links.Timeline.prototype.getOptions = function() {
373
+ return this.options;
374
+ };
375
+
347
376
  /**
348
377
  * Add new type of items
349
378
  * @param {String} typeName Name of new type
@@ -389,10 +418,10 @@ links.Timeline.mapColumnIds = function (dataTable) {
389
418
  cols.start = 0;
390
419
  cols.end = 1;
391
420
  cols.content = 2;
392
- if (colCount >= 3) {cols.group = 3}
393
- if (colCount >= 4) {cols.className = 4}
394
- if (colCount >= 5) {cols.editable = 5}
395
- if (colCount >= 6) {cols.type = 6}
421
+ if (colCount > 3) {cols.group = 3}
422
+ if (colCount > 4) {cols.className = 4}
423
+ if (colCount > 5) {cols.editable = 5}
424
+ if (colCount > 6) {cols.type = 6}
396
425
  }
397
426
 
398
427
  return cols;
@@ -431,7 +460,7 @@ links.Timeline.prototype.setData = function(data) {
431
460
  'group': ((cols.group != undefined) ? data.getValue(row, cols.group) : undefined),
432
461
  'className': ((cols.className != undefined) ? data.getValue(row, cols.className) : undefined),
433
462
  'editable': ((cols.editable != undefined) ? data.getValue(row, cols.editable) : undefined),
434
- 'type': ((cols.editable != undefined) ? data.getValue(row, cols.type) : undefined)
463
+ 'type': ((cols.type != undefined) ? data.getValue(row, cols.type) : undefined)
435
464
  }));
436
465
  }
437
466
  }
@@ -564,6 +593,79 @@ links.Timeline.prototype.getItemIndex = function(element) {
564
593
  return index;
565
594
  };
566
595
 
596
+
597
+ /**
598
+ * Find the cluster index from a given HTML element
599
+ * If no cluster index is found, undefined is returned
600
+ * @param {Element} element
601
+ * @return {Number | undefined} index
602
+ */
603
+ links.Timeline.prototype.getClusterIndex = function(element) {
604
+ var e = element,
605
+ dom = this.dom,
606
+ frame = dom.items.frame,
607
+ clusters = this.clusters,
608
+ index = undefined;
609
+
610
+ if (this.clusters) {
611
+ // try to find the frame where the clusters are located in
612
+ while (e.parentNode && e.parentNode !== frame) {
613
+ e = e.parentNode;
614
+ }
615
+
616
+ if (e.parentNode === frame) {
617
+ // yes! we have found the parent element of all clusters
618
+ // retrieve its id from the array with clusters
619
+ for (var i = 0, iMax = clusters.length; i < iMax; i++) {
620
+ if (clusters[i].dom === e) {
621
+ index = i;
622
+ break;
623
+ }
624
+ }
625
+ }
626
+ }
627
+
628
+ return index;
629
+ };
630
+
631
+ /**
632
+ * Find all elements within the start and end range
633
+ * If no element is found, returns an empty array
634
+ * @param start time
635
+ * @param end time
636
+ * @return Array itemsInRange
637
+ */
638
+ links.Timeline.prototype.getVisibleItems = function (start, end) {
639
+ var items = this.items;
640
+ var itemsInRange = [];
641
+
642
+ if (items) {
643
+ for (var i = 0, iMax = items.length; i < iMax; i++) {
644
+ var item = items[i];
645
+ if (item.end) {
646
+ // Time range object // NH use getLeft and getRight here
647
+ if (start <= item.start && item.end <= end) {
648
+ itemsInRange.push({"row": i});
649
+ }
650
+ } else {
651
+ // Point object
652
+ if (start <= item.start && item.start <= end) {
653
+ itemsInRange.push({"row": i});
654
+ }
655
+ }
656
+ }
657
+ }
658
+
659
+ // var sel = [];
660
+ // if (this.selection) {
661
+ // sel.push({"row": this.selection.index});
662
+ // }
663
+ // return sel;
664
+
665
+ return itemsInRange;
666
+ };
667
+
668
+
567
669
  /**
568
670
  * Set a new size for the timeline
569
671
  * @param {string} width Width in pixels or percentage (for example "800px"
@@ -776,7 +878,6 @@ links.Timeline.prototype.render = function(options) {
776
878
  this.clusterItems();
777
879
  this.filterItems();
778
880
  this.stackItems(animate);
779
-
780
881
  this.recalcItems();
781
882
 
782
883
  // TODO: only repaint when resized or when filterItems or stackItems gave a change?
@@ -851,8 +952,6 @@ links.Timeline.prototype.repaintFrame = function() {
851
952
  if (!dom.frame) {
852
953
  dom.frame = document.createElement("DIV");
853
954
  dom.frame.className = "timeline-frame ui-widget ui-widget-content ui-corner-all";
854
- dom.frame.style.position = "relative";
855
- dom.frame.style.overflow = "hidden";
856
955
  dom.container.appendChild(dom.frame);
857
956
  needsReflow = true;
858
957
  }
@@ -870,8 +969,7 @@ links.Timeline.prototype.repaintFrame = function() {
870
969
  if (!dom.content) {
871
970
  // create content box where the axis and items will be created
872
971
  dom.content = document.createElement("DIV");
873
- dom.content.style.position = "relative";
874
- dom.content.style.overflow = "hidden";
972
+ dom.content.className = "timeline-content";
875
973
  dom.frame.appendChild(dom.content);
876
974
 
877
975
  var timelines = document.createElement("DIV");
@@ -1451,9 +1549,9 @@ links.Timeline.prototype.reflowItems = function() {
1451
1549
  }
1452
1550
 
1453
1551
  if (group) {
1454
- group.itemsHeight = group.itemsHeight ?
1552
+ group.itemsHeight = Math.max(this.options.groupMinHeight,group.itemsHeight ?
1455
1553
  Math.max(group.itemsHeight, item.height) :
1456
- item.height;
1554
+ item.height);
1457
1555
  }
1458
1556
  }
1459
1557
 
@@ -1547,9 +1645,12 @@ links.Timeline.prototype.recalcItems = function () {
1547
1645
  for (i = 0, iMax = groups.length; i < iMax; i++) {
1548
1646
  group = groups[i];
1549
1647
 
1550
- var groupHeight = Math.max(group.labelHeight || 0, group.itemsHeight || 0);
1648
+ //
1649
+ // TODO: Do we want to apply a max height? how ?
1650
+ //
1651
+ var groupHeight = group.itemsHeight;
1551
1652
  resized = resized || (groupHeight != group.height);
1552
- group.height = groupHeight;
1653
+ group.height = Math.max(groupHeight, options.groupMinHeight);
1553
1654
 
1554
1655
  actualHeight += groups[i].height + options.eventMargin;
1555
1656
  }
@@ -1577,16 +1678,6 @@ links.Timeline.prototype.recalcItems = function () {
1577
1678
  }
1578
1679
  }
1579
1680
 
1580
- // calculate top position of the visible items
1581
- for (i = 0, iMax = renderedItems.length; i < iMax; i++) {
1582
- item = renderedItems[i];
1583
- group = item.group;
1584
-
1585
- if (group) {
1586
- item.top = group.top;
1587
- }
1588
- }
1589
-
1590
1681
  resized = true;
1591
1682
  }
1592
1683
 
@@ -1873,7 +1964,7 @@ links.Timeline.prototype.repaintGroups = function() {
1873
1964
  labels.splice(needed, current - needed);
1874
1965
  labelLines.splice(needed, current - needed);
1875
1966
  itemLines.splice(needed, current - needed);
1876
-
1967
+
1877
1968
  links.Timeline.addClassName(frame, options.groupsOnRight ? 'timeline-groups-axis-onright' : 'timeline-groups-axis-onleft');
1878
1969
 
1879
1970
  // position the groups
@@ -2056,8 +2147,8 @@ links.Timeline.prototype.repaintDeleteButton = function () {
2056
2147
  dom.items.deleteButton = deleteButton;
2057
2148
  }
2058
2149
 
2059
- var index = this.selection ? this.selection.index : -1,
2060
- item = this.selection ? this.items[index] : undefined;
2150
+ var index = (this.selection && this.selection.index !== undefined) ? this.selection.index : -1,
2151
+ item = (this.selection && this.selection.index !== undefined) ? this.items[index] : undefined;
2061
2152
  if (item && item.rendered && this.isEditable(item)) {
2062
2153
  var right = item.getRight(this),
2063
2154
  top = item.top;
@@ -2107,12 +2198,12 @@ links.Timeline.prototype.repaintDragAreas = function () {
2107
2198
  }
2108
2199
 
2109
2200
  // reposition left and right drag area
2110
- var index = this.selection ? this.selection.index : -1,
2111
- item = this.selection ? this.items[index] : undefined;
2201
+ var index = (this.selection && this.selection.index !== undefined) ? this.selection.index : -1,
2202
+ item = (this.selection && this.selection.index !== undefined) ? this.items[index] : undefined;
2112
2203
  if (item && item.rendered && this.isEditable(item) &&
2113
- (item instanceof links.Timeline.ItemRange)) {
2114
- var left = this.timeToScreen(item.start),
2115
- right = this.timeToScreen(item.end),
2204
+ (item instanceof links.Timeline.ItemRange || item instanceof links.Timeline.ItemFloatingRange)) {
2205
+ var left = item.getLeft(this), // NH change to getLeft
2206
+ right = item.getRight(this), // NH change to getRight
2116
2207
  top = item.top,
2117
2208
  height = item.height;
2118
2209
 
@@ -2178,9 +2269,9 @@ links.Timeline.prototype.repaintNavigation = function () {
2178
2269
  navBar.addButton.className = "timeline-navigation-new";
2179
2270
  navBar.addButton.title = options.CREATE_NEW_EVENT;
2180
2271
  var addIconSpan = document.createElement("SPAN");
2181
- addIconSpan.className = "ui-icon ui-icon-circle-plus";
2272
+ addIconSpan.className = "ui-icon ui-icon-circle-plus";
2182
2273
  navBar.addButton.appendChild(addIconSpan);
2183
-
2274
+
2184
2275
  var onAdd = function(event) {
2185
2276
  links.Timeline.preventDefault(event);
2186
2277
  links.Timeline.stopPropagation(event);
@@ -2188,11 +2279,9 @@ links.Timeline.prototype.repaintNavigation = function () {
2188
2279
  // create a new event at the center of the frame
2189
2280
  var w = timeline.size.contentWidth;
2190
2281
  var x = w / 2;
2191
- var xstart = timeline.screenToTime(x - w / 10); // subtract 10% of timeline width
2192
- var xend = timeline.screenToTime(x + w / 10); // add 10% of timeline width
2282
+ var xstart = timeline.screenToTime(x);
2193
2283
  if (options.snapEvents) {
2194
2284
  timeline.step.snap(xstart);
2195
- timeline.step.snap(xend);
2196
2285
  }
2197
2286
 
2198
2287
  var content = options.NEW;
@@ -2200,7 +2289,6 @@ links.Timeline.prototype.repaintNavigation = function () {
2200
2289
  var preventRender = true;
2201
2290
  timeline.addItem({
2202
2291
  'start': xstart,
2203
- 'end': xend,
2204
2292
  'content': content,
2205
2293
  'group': group
2206
2294
  }, preventRender);
@@ -2242,7 +2330,7 @@ links.Timeline.prototype.repaintNavigation = function () {
2242
2330
  var ziIconSpan = document.createElement("SPAN");
2243
2331
  ziIconSpan.className = "ui-icon ui-icon-circle-zoomin";
2244
2332
  navBar.zoomInButton.appendChild(ziIconSpan);
2245
-
2333
+
2246
2334
  var onZoomIn = function(event) {
2247
2335
  links.Timeline.preventDefault(event);
2248
2336
  links.Timeline.stopPropagation(event);
@@ -2260,7 +2348,7 @@ links.Timeline.prototype.repaintNavigation = function () {
2260
2348
  var zoIconSpan = document.createElement("SPAN");
2261
2349
  zoIconSpan.className = "ui-icon ui-icon-circle-zoomout";
2262
2350
  navBar.zoomOutButton.appendChild(zoIconSpan);
2263
-
2351
+
2264
2352
  var onZoomOut = function(event) {
2265
2353
  links.Timeline.preventDefault(event);
2266
2354
  links.Timeline.stopPropagation(event);
@@ -2280,7 +2368,7 @@ links.Timeline.prototype.repaintNavigation = function () {
2280
2368
  var mlIconSpan = document.createElement("SPAN");
2281
2369
  mlIconSpan.className = "ui-icon ui-icon-circle-arrow-w";
2282
2370
  navBar.moveLeftButton.appendChild(mlIconSpan);
2283
-
2371
+
2284
2372
  var onMoveLeft = function(event) {
2285
2373
  links.Timeline.preventDefault(event);
2286
2374
  links.Timeline.stopPropagation(event);
@@ -2298,7 +2386,7 @@ links.Timeline.prototype.repaintNavigation = function () {
2298
2386
  var mrIconSpan = document.createElement("SPAN");
2299
2387
  mrIconSpan.className = "ui-icon ui-icon-circle-arrow-e";
2300
2388
  navBar.moveRightButton.appendChild(mrIconSpan);
2301
-
2389
+
2302
2390
  var onMoveRight = function(event) {
2303
2391
  links.Timeline.preventDefault(event);
2304
2392
  links.Timeline.stopPropagation(event);
@@ -2524,7 +2612,7 @@ links.Timeline.prototype.onTouchMove = function(event) {
2524
2612
  }
2525
2613
 
2526
2614
  if (!params.zoomed) {
2527
- // move
2615
+ // move
2528
2616
  this.onMouseMove(event);
2529
2617
  }
2530
2618
  else {
@@ -2628,10 +2716,12 @@ links.Timeline.prototype.onMouseDown = function(event) {
2628
2716
  params.itemDragRight = (params.target === dragRight);
2629
2717
 
2630
2718
  if (params.itemDragLeft || params.itemDragRight) {
2631
- params.itemIndex = this.selection ? this.selection.index : undefined;
2719
+ params.itemIndex = (this.selection && this.selection.index !== undefined) ? this.selection.index : undefined;
2720
+ delete params.clusterIndex;
2632
2721
  }
2633
2722
  else {
2634
2723
  params.itemIndex = this.getItemIndex(params.target);
2724
+ params.clusterIndex = this.getClusterIndex(params.target);
2635
2725
  }
2636
2726
 
2637
2727
  params.customTime = (params.target === dom.customTime ||
@@ -2659,6 +2749,7 @@ links.Timeline.prototype.onMouseDown = function(event) {
2659
2749
  'group': this.getGroupName(group)
2660
2750
  });
2661
2751
  params.itemIndex = (this.items.length - 1);
2752
+ delete params.clusterIndex;
2662
2753
  this.selectItem(params.itemIndex);
2663
2754
  params.itemDragRight = true;
2664
2755
  }
@@ -2670,8 +2761,8 @@ links.Timeline.prototype.onMouseDown = function(event) {
2670
2761
  params.itemStart = item.start;
2671
2762
  params.itemEnd = item.end;
2672
2763
  params.itemGroup = item.group;
2673
- params.itemLeft = item.start ? this.timeToScreen(item.start) : undefined;
2674
- params.itemRight = item.end ? this.timeToScreen(item.end) : undefined;
2764
+ params.itemLeft = item.getLeft(this); // NH Use item.getLeft here
2765
+ params.itemRight = item.getRight(this); // NH Use item.getRight here
2675
2766
  }
2676
2767
  else {
2677
2768
  this.dom.frame.style.cursor = 'move';
@@ -2741,7 +2832,7 @@ links.Timeline.prototype.onMouseMove = function (event) {
2741
2832
  left,
2742
2833
  right;
2743
2834
 
2744
- if (params.itemDragLeft) {
2835
+ if (params.itemDragLeft && options.timeChangeable) {
2745
2836
  // move the start of the item
2746
2837
  left = params.itemLeft + diffX;
2747
2838
  right = params.itemRight;
@@ -2756,8 +2847,9 @@ links.Timeline.prototype.onMouseMove = function (event) {
2756
2847
  left = right;
2757
2848
  item.start = this.screenToTime(left);
2758
2849
  }
2850
+ this.trigger('change');
2759
2851
  }
2760
- else if (params.itemDragRight) {
2852
+ else if (params.itemDragRight && options.timeChangeable) {
2761
2853
  // move the end of the item
2762
2854
  left = params.itemLeft;
2763
2855
  right = params.itemRight + diffX;
@@ -2772,8 +2864,9 @@ links.Timeline.prototype.onMouseMove = function (event) {
2772
2864
  right = left;
2773
2865
  item.end = this.screenToTime(right);
2774
2866
  }
2867
+ this.trigger('change');
2775
2868
  }
2776
- else {
2869
+ else if (options.timeChangeable) {
2777
2870
  // move the item
2778
2871
  left = params.itemLeft + diffX;
2779
2872
  item.start = this.screenToTime(left);
@@ -2786,6 +2879,7 @@ links.Timeline.prototype.onMouseMove = function (event) {
2786
2879
  right = left + (params.itemRight - params.itemLeft);
2787
2880
  item.end = this.screenToTime(right);
2788
2881
  }
2882
+ this.trigger('change');
2789
2883
  }
2790
2884
 
2791
2885
  item.setPosition(left, right);
@@ -2827,7 +2921,7 @@ links.Timeline.prototype.onMouseMove = function (event) {
2827
2921
  this.recalcConversion();
2828
2922
 
2829
2923
  // move the items by changing the left position of their frame.
2830
- // this is much faster than repositioning all elements individually via the
2924
+ // this is much faster than repositioning all elements individually via the
2831
2925
  // repaintFrame() function (which is done once at mouseup)
2832
2926
  // note that we round diffX to prevent wrong positioning on millisecond scale
2833
2927
  var previousLeft = params.previousLeft || 0;
@@ -2894,10 +2988,13 @@ links.Timeline.prototype.onMouseUp = function (event) {
2894
2988
  'end': item.end
2895
2989
  });
2896
2990
 
2897
- // fire an add or change event.
2898
- // Note that the change can be canceled from within an event listener if
2991
+ // fire an add or changed event.
2992
+ // Note that the change can be canceled from within an event listener if
2899
2993
  // this listener calls the method cancelChange().
2900
- this.trigger(params.addItem ? 'add' : 'change');
2994
+ this.trigger(params.addItem ? 'add' : 'changed');
2995
+
2996
+ //retrieve item data again to include changes made to it in the triggered event handlers
2997
+ item = this.items[params.itemIndex];
2901
2998
 
2902
2999
  if (params.addItem) {
2903
3000
  if (this.applyAdd) {
@@ -2933,6 +3030,11 @@ links.Timeline.prototype.onMouseUp = function (event) {
2933
3030
  item.group = params.itemGroup;
2934
3031
  // TODO: original group should be restored too
2935
3032
  item.setPosition(params.itemLeft, params.itemRight);
3033
+
3034
+ this.updateData(params.itemIndex, {
3035
+ 'start': params.itemStart,
3036
+ 'end': params.itemEnd
3037
+ });
2936
3038
  }
2937
3039
  }
2938
3040
 
@@ -2950,7 +3052,7 @@ links.Timeline.prototype.onMouseUp = function (event) {
2950
3052
 
2951
3053
  if (params.target === this.dom.items.deleteButton) {
2952
3054
  // delete item
2953
- if (this.selection) {
3055
+ if (this.selection && this.selection.index !== undefined) {
2954
3056
  this.confirmDeleteItem(this.selection.index);
2955
3057
  }
2956
3058
  }
@@ -2962,6 +3064,10 @@ links.Timeline.prototype.onMouseUp = function (event) {
2962
3064
  this.trigger('select');
2963
3065
  }
2964
3066
  }
3067
+ else if(params.clusterIndex != undefined) {
3068
+ this.selectCluster(params.clusterIndex);
3069
+ this.trigger('select');
3070
+ }
2965
3071
  else {
2966
3072
  if (options.unselectable) {
2967
3073
  this.unselectItem();
@@ -3013,10 +3119,8 @@ links.Timeline.prototype.onDblClick = function (event) {
3013
3119
 
3014
3120
  // create a new event at the current mouse position
3015
3121
  var xstart = this.screenToTime(x);
3016
- var xend = this.screenToTime(x + size.frameWidth / 10); // add 10% of timeline width
3017
3122
  if (options.snapEvents) {
3018
3123
  this.step.snap(xstart);
3019
- this.step.snap(xend);
3020
3124
  }
3021
3125
 
3022
3126
  var content = options.NEW;
@@ -3024,7 +3128,6 @@ links.Timeline.prototype.onDblClick = function (event) {
3024
3128
  var preventRender = true;
3025
3129
  this.addItem({
3026
3130
  'start': xstart,
3027
- 'end': xend,
3028
3131
  'content': content,
3029
3132
  'group': this.getGroupName(group)
3030
3133
  }, preventRender);
@@ -3067,7 +3170,7 @@ links.Timeline.prototype.onMouseWheel = function(event) {
3067
3170
  event = window.event;
3068
3171
  }
3069
3172
 
3070
- // retrieve delta
3173
+ // retrieve delta
3071
3174
  var delta = 0;
3072
3175
  if (event.wheelDelta) { /* IE/Opera. */
3073
3176
  delta = event.wheelDelta/120;
@@ -3081,7 +3184,7 @@ links.Timeline.prototype.onMouseWheel = function(event) {
3081
3184
  // Basically, delta is now positive if wheel was scrolled up,
3082
3185
  // and negative, if wheel was scrolled down.
3083
3186
  if (delta) {
3084
- // TODO: on FireFox, the window is not redrawn within repeated scroll-events
3187
+ // TODO: on FireFox, the window is not redrawn within repeated scroll-events
3085
3188
  // -> use a delayed redraw? Make a zoom queue?
3086
3189
 
3087
3190
  var timeline = this;
@@ -3300,8 +3403,8 @@ links.Timeline.prototype.confirmDeleteItem = function(index) {
3300
3403
  this.selectItem(index);
3301
3404
  }
3302
3405
 
3303
- // fire a delete event trigger.
3304
- // Note that the delete event can be canceled from within an event listener if
3406
+ // fire a delete event trigger.
3407
+ // Note that the delete event can be canceled from within an event listener if
3305
3408
  // this listener calls the method cancelChange().
3306
3409
  this.trigger('delete');
3307
3410
 
@@ -3323,7 +3426,7 @@ links.Timeline.prototype.deleteItem = function(index, preventRender) {
3323
3426
  throw "Cannot delete row, index out of range";
3324
3427
  }
3325
3428
 
3326
- if (this.selection) {
3429
+ if (this.selection && this.selection.index !== undefined) {
3327
3430
  // adjust the selection
3328
3431
  if (this.selection.index == index) {
3329
3432
  // item to be deleted is selected
@@ -3586,6 +3689,16 @@ links.Timeline.Item.prototype.setPosition = function (left, right) {
3586
3689
  // Should be implemented by sub-prototype
3587
3690
  };
3588
3691
 
3692
+ /**
3693
+ * Calculate the left position of the item
3694
+ * @param {links.Timeline} timeline
3695
+ * @return {Number} left
3696
+ */
3697
+ links.Timeline.Item.prototype.getLeft = function (timeline) {
3698
+ // Should be implemented by sub-prototype
3699
+ return 0;
3700
+ };
3701
+
3589
3702
  /**
3590
3703
  * Calculate the right position of the item
3591
3704
  * @param {links.Timeline} timeline
@@ -3871,6 +3984,27 @@ links.Timeline.ItemBox.prototype.setPosition = function (left, right) {
3871
3984
  }
3872
3985
  };
3873
3986
 
3987
+ /**
3988
+ * Calculate the left position of the item
3989
+ * @param {links.Timeline} timeline
3990
+ * @return {Number} left
3991
+ * @override
3992
+ */
3993
+ links.Timeline.ItemBox.prototype.getLeft = function (timeline) {
3994
+ var boxAlign = (timeline.options.box && timeline.options.box.align) ?
3995
+ timeline.options.box.align : undefined;
3996
+
3997
+ var left = timeline.timeToScreen(this.start);
3998
+ if (boxAlign == 'right') {
3999
+ left = left - width;
4000
+ }
4001
+ else { // default or 'center'
4002
+ left = (left - this.width / 2);
4003
+ }
4004
+
4005
+ return left;
4006
+ };
4007
+
3874
4008
  /**
3875
4009
  * Calculate the right position of the item
3876
4010
  * @param {links.Timeline} timeline
@@ -4080,6 +4214,16 @@ links.Timeline.ItemRange.prototype.setPosition = function (left, right) {
4080
4214
  }
4081
4215
  };
4082
4216
 
4217
+ /**
4218
+ * Calculate the left position of the item
4219
+ * @param {links.Timeline} timeline
4220
+ * @return {Number} left
4221
+ * @override
4222
+ */
4223
+ links.Timeline.ItemRange.prototype.getLeft = function (timeline) {
4224
+ return timeline.timeToScreen(this.start);
4225
+ };
4226
+
4083
4227
  /**
4084
4228
  * Calculate the right position of the item
4085
4229
  * @param {links.Timeline} timeline
@@ -4100,6 +4244,237 @@ links.Timeline.ItemRange.prototype.getWidth = function (timeline) {
4100
4244
  return timeline.timeToScreen(this.end) - timeline.timeToScreen(this.start);
4101
4245
  };
4102
4246
 
4247
+ /**
4248
+ * @constructor links.Timeline.ItemFloatingRange
4249
+ * @extends links.Timeline.Item
4250
+ * @param {Object} data Object containing parameters start, end
4251
+ * content, group, type, className, editable.
4252
+ * @param {Object} [options] Options to set initial property values
4253
+ * {Number} top
4254
+ * {Number} left
4255
+ * {Number} width
4256
+ * {Number} height
4257
+ */
4258
+ links.Timeline.ItemFloatingRange = function (data, options) {
4259
+ links.Timeline.Item.call(this, data, options);
4260
+ };
4261
+
4262
+ links.Timeline.ItemFloatingRange.prototype = new links.Timeline.Item();
4263
+
4264
+ /**
4265
+ * Select the item
4266
+ * @override
4267
+ */
4268
+ links.Timeline.ItemFloatingRange.prototype.select = function () {
4269
+ var dom = this.dom;
4270
+ links.Timeline.addClassName(dom, 'timeline-event-selected ui-state-active');
4271
+ };
4272
+
4273
+ /**
4274
+ * Unselect the item
4275
+ * @override
4276
+ */
4277
+ links.Timeline.ItemFloatingRange.prototype.unselect = function () {
4278
+ var dom = this.dom;
4279
+ links.Timeline.removeClassName(dom, 'timeline-event-selected ui-state-active');
4280
+ };
4281
+
4282
+ /**
4283
+ * Creates the DOM for the item, depending on its type
4284
+ * @return {Element | undefined}
4285
+ * @override
4286
+ */
4287
+ links.Timeline.ItemFloatingRange.prototype.createDOM = function () {
4288
+ // background box
4289
+ var divBox = document.createElement("DIV");
4290
+ divBox.style.position = "absolute";
4291
+
4292
+ // contents box
4293
+ var divContent = document.createElement("DIV");
4294
+ divContent.className = "timeline-event-content";
4295
+ divBox.appendChild(divContent);
4296
+
4297
+ this.dom = divBox;
4298
+ this.updateDOM();
4299
+
4300
+ return divBox;
4301
+ };
4302
+
4303
+ /**
4304
+ * Append the items DOM to the given HTML container. If items DOM does not yet
4305
+ * exist, it will be created first.
4306
+ * @param {Element} container
4307
+ * @override
4308
+ */
4309
+ links.Timeline.ItemFloatingRange.prototype.showDOM = function (container) {
4310
+ var dom = this.dom;
4311
+ if (!dom) {
4312
+ dom = this.createDOM();
4313
+ }
4314
+
4315
+ if (dom.parentNode != container) {
4316
+ if (dom.parentNode) {
4317
+ // container changed. remove the item from the old container
4318
+ this.hideDOM();
4319
+ }
4320
+
4321
+ // append to the new container
4322
+ container.appendChild(dom);
4323
+ this.rendered = true;
4324
+ }
4325
+ };
4326
+
4327
+ /**
4328
+ * Remove the items DOM from the current HTML container
4329
+ * The DOM will be kept in memory
4330
+ * @override
4331
+ */
4332
+ links.Timeline.ItemFloatingRange.prototype.hideDOM = function () {
4333
+ var dom = this.dom;
4334
+ if (dom) {
4335
+ if (dom.parentNode) {
4336
+ dom.parentNode.removeChild(dom);
4337
+ }
4338
+ this.rendered = false;
4339
+ }
4340
+ };
4341
+
4342
+ /**
4343
+ * Update the DOM of the item. This will update the content and the classes
4344
+ * of the item
4345
+ * @override
4346
+ */
4347
+ links.Timeline.ItemFloatingRange.prototype.updateDOM = function () {
4348
+ var divBox = this.dom;
4349
+ if (divBox) {
4350
+ // update contents
4351
+ divBox.firstChild.innerHTML = this.content;
4352
+
4353
+ // update class
4354
+ divBox.className = "timeline-event timeline-event-range ui-widget ui-state-default";
4355
+
4356
+ if (this.isCluster) {
4357
+ links.Timeline.addClassName(divBox, 'timeline-event-cluster ui-widget-header');
4358
+ }
4359
+
4360
+ // add item specific class name when provided
4361
+ if (this.className) {
4362
+ links.Timeline.addClassName(divBox, this.className);
4363
+ }
4364
+
4365
+ // TODO: apply selected className?
4366
+ }
4367
+ };
4368
+
4369
+ /**
4370
+ * Reposition the item, recalculate its left, top, and width, using the current
4371
+ * range of the timeline and the timeline options. *
4372
+ * @param {links.Timeline} timeline
4373
+ * @override
4374
+ */
4375
+ links.Timeline.ItemFloatingRange.prototype.updatePosition = function (timeline) {
4376
+ var dom = this.dom;
4377
+ if (dom) {
4378
+ var contentWidth = timeline.size.contentWidth,
4379
+ left = this.getLeft(timeline), // NH use getLeft
4380
+ right = this.getRight(timeline); // NH use getRight;
4381
+
4382
+ // limit the width of the this, as browsers cannot draw very wide divs
4383
+ if (left < -contentWidth) {
4384
+ left = -contentWidth;
4385
+ }
4386
+ if (right > 2 * contentWidth) {
4387
+ right = 2 * contentWidth;
4388
+ }
4389
+
4390
+ dom.style.top = this.top + "px";
4391
+ dom.style.left = left + "px";
4392
+ //dom.style.width = Math.max(right - left - 2 * this.borderWidth, 1) + "px"; // TODO: borderWidth
4393
+ dom.style.width = Math.max(right - left, 1) + "px";
4394
+ }
4395
+ };
4396
+
4397
+ /**
4398
+ * Check if the item is visible in the timeline, and not part of a cluster
4399
+ * @param {Number} start
4400
+ * @param {Number} end
4401
+ * @return {boolean} visible
4402
+ * @override
4403
+ */
4404
+ links.Timeline.ItemFloatingRange.prototype.isVisible = function (start, end) {
4405
+ if (this.cluster) {
4406
+ return false;
4407
+ }
4408
+
4409
+ // NH check for no end value
4410
+ if (this.end && this.start) {
4411
+ return (this.end > start)
4412
+ && (this.start < end);
4413
+ } else if (this.start) {
4414
+ return (this.start < end);
4415
+ } else if (this.end) {
4416
+ return (this.end > start);
4417
+ } else {return true;}
4418
+ };
4419
+
4420
+ /**
4421
+ * Reposition the item
4422
+ * @param {Number} left
4423
+ * @param {Number} right
4424
+ * @override
4425
+ */
4426
+ links.Timeline.ItemFloatingRange.prototype.setPosition = function (left, right) {
4427
+ var dom = this.dom;
4428
+
4429
+ dom.style.left = left + 'px';
4430
+ dom.style.width = (right - left) + 'px';
4431
+
4432
+ if (this.group) {
4433
+ this.top = this.group.top;
4434
+ dom.style.top = this.top + 'px';
4435
+ }
4436
+ };
4437
+
4438
+ /**
4439
+ * Calculate the left position of the item
4440
+ * @param {links.Timeline} timeline
4441
+ * @return {Number} left
4442
+ * @override
4443
+ */
4444
+ links.Timeline.ItemFloatingRange.prototype.getLeft = function (timeline) {
4445
+ // NH check for no start value
4446
+ if (this.start) {
4447
+ return timeline.timeToScreen(this.start);
4448
+ } else {
4449
+ return 0;
4450
+ }
4451
+ };
4452
+
4453
+ /**
4454
+ * Calculate the right position of the item
4455
+ * @param {links.Timeline} timeline
4456
+ * @return {Number} right
4457
+ * @override
4458
+ */
4459
+ links.Timeline.ItemFloatingRange.prototype.getRight = function (timeline) {
4460
+ // NH check for no end value
4461
+ if (this.end) {
4462
+ return timeline.timeToScreen(this.end);
4463
+ } else {
4464
+ return timeline.size.contentWidth;
4465
+ }
4466
+ };
4467
+
4468
+ /**
4469
+ * Calculate the width of the item
4470
+ * @param {links.Timeline} timeline
4471
+ * @return {Number} width
4472
+ * @override
4473
+ */
4474
+ links.Timeline.ItemFloatingRange.prototype.getWidth = function (timeline) {
4475
+ return this.getRight(timeline) - this.getLeft(timeline);
4476
+ };
4477
+
4103
4478
  /**
4104
4479
  * @constructor links.Timeline.ItemDot
4105
4480
  * @extends links.Timeline.Item
@@ -4312,6 +4687,16 @@ links.Timeline.ItemDot.prototype.setPosition = function (left, right) {
4312
4687
  }
4313
4688
  };
4314
4689
 
4690
+ /**
4691
+ * Calculate the left position of the item
4692
+ * @param {links.Timeline} timeline
4693
+ * @return {Number} left
4694
+ * @override
4695
+ */
4696
+ links.Timeline.ItemDot.prototype.getLeft = function (timeline) {
4697
+ return timeline.timeToScreen(this.start);
4698
+ };
4699
+
4315
4700
  /**
4316
4701
  * Calculate the right position of the item
4317
4702
  * @param {links.Timeline} timeline
@@ -4325,7 +4710,7 @@ links.Timeline.ItemDot.prototype.getRight = function (timeline) {
4325
4710
  /**
4326
4711
  * Retrieve the properties of an item.
4327
4712
  * @param {Number} index
4328
- * @return {Object} properties Object containing item properties:<br>
4713
+ * @return {Object} itemData Object containing item properties:<br>
4329
4714
  * {Date} start (required),
4330
4715
  * {Date} end (optional),
4331
4716
  * {String} content (required),
@@ -4339,28 +4724,91 @@ links.Timeline.prototype.getItem = function (index) {
4339
4724
  throw "Cannot get item, index out of range";
4340
4725
  }
4341
4726
 
4727
+ // take the original data as start, includes foreign fields
4728
+ var data = this.data,
4729
+ itemData;
4730
+ if (google && google.visualization &&
4731
+ data instanceof google.visualization.DataTable) {
4732
+ // map the datatable columns
4733
+ var cols = links.Timeline.mapColumnIds(data);
4734
+
4735
+ itemData = {};
4736
+ for (var col in cols) {
4737
+ if (cols.hasOwnProperty(col)) {
4738
+ itemData[col] = this.data.getValue(index, cols[col]);
4739
+ }
4740
+ }
4741
+ }
4742
+ else if (links.Timeline.isArray(this.data)) {
4743
+ // read JSON array
4744
+ itemData = links.Timeline.clone(this.data[index]);
4745
+ }
4746
+ else {
4747
+ throw "Unknown data type. DataTable or Array expected.";
4748
+ }
4749
+
4750
+ // override the data with current settings of the item (should be the same)
4342
4751
  var item = this.items[index];
4343
4752
 
4344
- var properties = {};
4345
- properties.start = new Date(item.start.valueOf());
4753
+ itemData.start = new Date(item.start.valueOf());
4346
4754
  if (item.end) {
4347
- properties.end = new Date(item.end.valueOf());
4755
+ itemData.end = new Date(item.end.valueOf());
4348
4756
  }
4349
- properties.content = item.content;
4757
+ itemData.content = item.content;
4350
4758
  if (item.group) {
4351
- properties.group = this.getGroupName(item.group);
4759
+ itemData.group = this.getGroupName(item.group);
4352
4760
  }
4353
- if ('className' in item) {
4354
- properties.className = this.getGroupName(item.className);
4761
+ if (item.className) {
4762
+ itemData.className = item.className;
4355
4763
  }
4356
- if (item.hasOwnProperty('editable') && (typeof item.editable != 'undefined')) {
4357
- properties.editable = item.editable;
4764
+ if (typeof item.editable !== 'undefined') {
4765
+ itemData.editable = item.editable;
4358
4766
  }
4359
4767
  if (item.type) {
4360
- properties.type = item.type;
4768
+ itemData.type = item.type;
4769
+ }
4770
+
4771
+ return itemData;
4772
+ };
4773
+
4774
+
4775
+ /**
4776
+ * Retrieve the properties of a cluster.
4777
+ * @param {Number} index
4778
+ * @return {Object} clusterdata Object containing cluster properties:<br>
4779
+ * {Date} start (required),
4780
+ * {String} type (optional)
4781
+ * {Array} array with item data as is in getItem()
4782
+ */
4783
+ links.Timeline.prototype.getCluster = function (index) {
4784
+ if (index >= this.clusters.length) {
4785
+ throw "Cannot get cluster, index out of range";
4361
4786
  }
4362
4787
 
4363
- return properties;
4788
+ var clusterData = {},
4789
+ cluster = this.clusters[index],
4790
+ clusterItems = cluster.items;
4791
+
4792
+ clusterData.start = new Date(cluster.start.valueOf());
4793
+ if (cluster.type) {
4794
+ clusterData.type = cluster.type;
4795
+ }
4796
+
4797
+ // push cluster item data
4798
+ clusterData.items = [];
4799
+ for(var i = 0; i < clusterItems.length; i++){
4800
+ for(var j = 0; j < this.items.length; j++){
4801
+ // TODO could be nicer to be able to have the item index into the cluster
4802
+ if(this.items[j] == clusterItems[i])
4803
+ {
4804
+ clusterData.items.push(this.getItem(j));
4805
+ break;
4806
+ }
4807
+
4808
+ }
4809
+ }
4810
+
4811
+ return clusterData;
4364
4812
  };
4365
4813
 
4366
4814
  /**
@@ -4431,15 +4879,9 @@ links.Timeline.prototype.addItems = function (itemsData, preventRender) {
4431
4879
  */
4432
4880
  links.Timeline.prototype.createItem = function(itemData) {
4433
4881
  var type = itemData.type || (itemData.end ? 'range' : this.options.style);
4434
- var data = {
4435
- start: itemData.start,
4436
- end: itemData.end,
4437
- content: itemData.content,
4438
- className: itemData.className,
4439
- editable: itemData.editable,
4440
- group: this.getGroup(itemData.group),
4441
- type: type
4442
- };
4882
+ var data = links.Timeline.clone(itemData);
4883
+ data.type = type;
4884
+ data.group = this.getGroup(itemData.group);
4443
4885
  // TODO: optimize this, when creating an item, all data is copied twice...
4444
4886
 
4445
4887
  // TODO: is initialTop needed?
@@ -4456,7 +4898,7 @@ links.Timeline.prototype.createItem = function(itemData) {
4456
4898
  return new this.itemTypes[type](data, {'top': initialTop})
4457
4899
  }
4458
4900
 
4459
- console.log('ERROR: Unknown event style "' + type + '"');
4901
+ console.log('ERROR: Unknown event type "' + type + '"');
4460
4902
  return new links.Timeline.Item(data, {
4461
4903
  'top': initialTop
4462
4904
  });
@@ -4480,10 +4922,10 @@ links.Timeline.prototype.changeItem = function (index, itemData, preventRender)
4480
4922
 
4481
4923
  // replace item, merge the changes
4482
4924
  var newItem = this.createItem({
4483
- 'start': itemData.hasOwnProperty('start') ? itemData.start : oldItem.start,
4484
- 'end': itemData.hasOwnProperty('end') ? itemData.end : oldItem.end,
4485
- 'content': itemData.hasOwnProperty('content') ? itemData.content : oldItem.content,
4486
- 'group': itemData.hasOwnProperty('group') ? itemData.group : this.getGroupName(oldItem.group),
4925
+ 'start': itemData.hasOwnProperty('start') ? itemData.start : oldItem.start,
4926
+ 'end': itemData.hasOwnProperty('end') ? itemData.end : oldItem.end,
4927
+ 'content': itemData.hasOwnProperty('content') ? itemData.content : oldItem.content,
4928
+ 'group': itemData.hasOwnProperty('group') ? itemData.group : this.getGroupName(oldItem.group),
4487
4929
  'className': itemData.hasOwnProperty('className') ? itemData.className : oldItem.className,
4488
4930
  'editable': itemData.hasOwnProperty('editable') ? itemData.editable : oldItem.editable,
4489
4931
  'type': itemData.hasOwnProperty('type') ? itemData.type : oldItem.type
@@ -4540,20 +4982,24 @@ links.Timeline.prototype.getGroup = function (groupName) {
4540
4982
  'content': groupName,
4541
4983
  'labelTop': 0,
4542
4984
  'lineTop': 0
4543
- // note: this object will lateron get addition information,
4544
- // such as height and width of the group
4985
+ // note: this object will lateron get addition information,
4986
+ // such as height and width of the group
4545
4987
  };
4546
4988
  groups.push(groupObj);
4547
4989
  // sort the groups
4548
- groups = groups.sort(function (a, b) {
4549
- if (a.content > b.content) {
4550
- return 1;
4551
- }
4552
- if (a.content < b.content) {
4553
- return -1;
4554
- }
4555
- return 0;
4556
- });
4990
+ if (this.options.groupsOrder == true) {
4991
+ groups = groups.sort(function (a, b) {
4992
+ if (a.content > b.content) {
4993
+ return 1;
4994
+ }
4995
+ if (a.content < b.content) {
4996
+ return -1;
4997
+ }
4998
+ return 0;
4999
+ });
5000
+ } else if (typeof(this.options.groupsOrder) == "function") {
5001
+ groups = groups.sort(this.options.groupsOrder)
5002
+ }
4557
5003
 
4558
5004
  // rebuilt the groupIndexes
4559
5005
  for (var i = 0, iMax = groups.length; i < iMax; i++) {
@@ -4657,7 +5103,12 @@ links.Timeline.prototype.setSelection = function(selection) {
4657
5103
  links.Timeline.prototype.getSelection = function() {
4658
5104
  var sel = [];
4659
5105
  if (this.selection) {
4660
- sel.push({"row": this.selection.index});
5106
+ if(this.selection.index !== undefined)
5107
+ {
5108
+ sel.push({"row": this.selection.index});
5109
+ } else {
5110
+ sel.push({"cluster": this.selection.cluster});
5111
+ }
4661
5112
  }
4662
5113
  return sel;
4663
5114
  };
@@ -4692,6 +5143,24 @@ links.Timeline.prototype.selectItem = function(index) {
4692
5143
  }
4693
5144
  };
4694
5145
 
5146
+ /**
5147
+ * Select an cluster by its index
5148
+ * @param {Number} index
5149
+ */
5150
+ links.Timeline.prototype.selectCluster = function(index) {
5151
+ this.unselectItem();
5152
+
5153
+ this.selection = undefined;
5154
+
5155
+ if (this.clusters[index] != undefined) {
5156
+ this.selection = {
5157
+ 'cluster': index
5158
+ };
5159
+ this.repaintDeleteButton();
5160
+ this.repaintDragAreas();
5161
+ }
5162
+ };
5163
+
4695
5164
  /**
4696
5165
  * Check if an item is currently selected
4697
5166
  * @param {Number} index
@@ -4705,7 +5174,7 @@ links.Timeline.prototype.isSelected = function (index) {
4705
5174
  * Unselect the currently selected event (if any)
4706
5175
  */
4707
5176
  links.Timeline.prototype.unselectItem = function() {
4708
- if (this.selection) {
5177
+ if (this.selection && this.selection.index !== undefined) {
4709
5178
  var item = this.items[this.selection.index];
4710
5179
 
4711
5180
  if (item && item.dom) {
@@ -4729,12 +5198,6 @@ links.Timeline.prototype.unselectItem = function() {
4729
5198
  * defaults to false.
4730
5199
  */
4731
5200
  links.Timeline.prototype.stackItems = function(animate) {
4732
- if (this.groups.length > 0) {
4733
- // under this conditions we refuse to stack the events
4734
- // TODO: implement support for stacking items per group
4735
- return;
4736
- }
4737
-
4738
5201
  if (animate == undefined) {
4739
5202
  animate = false;
4740
5203
  }
@@ -4785,6 +5248,29 @@ links.Timeline.prototype.stackCancelAnimation = function() {
4785
5248
  }
4786
5249
  };
4787
5250
 
5251
+ links.Timeline.prototype.getItemsByGroup = function(items) {
5252
+ var itemsByGroup = {};
5253
+ for (var i = 0; i < items.length; ++i) {
5254
+ var item = items[i];
5255
+ var group = "undefined";
5256
+
5257
+ if (item.group) {
5258
+ if (item.group.content) {
5259
+ group = item.group.content;
5260
+ } else {
5261
+ group = item.group;
5262
+ }
5263
+ }
5264
+
5265
+ if (!itemsByGroup[group]) {
5266
+ itemsByGroup[group] = [];
5267
+ }
5268
+
5269
+ itemsByGroup[group].push(item);
5270
+ }
5271
+
5272
+ return itemsByGroup;
5273
+ };
4788
5274
 
4789
5275
  /**
4790
5276
  * Order the items in the array this.items. The default order is determined via:
@@ -4798,16 +5284,16 @@ links.Timeline.prototype.stackOrder = function(items) {
4798
5284
  // TODO: store the sorted items, to have less work later on
4799
5285
  var sortedItems = items.concat([]);
4800
5286
 
4801
- //if a customer stack order function exists, use it.
5287
+ //if a customer stack order function exists, use it.
4802
5288
  var f = this.options.customStackOrder && (typeof this.options.customStackOrder === 'function') ? this.options.customStackOrder : function (a, b)
4803
5289
  {
4804
- if ((a instanceof links.Timeline.ItemRange) &&
4805
- !(b instanceof links.Timeline.ItemRange)) {
5290
+ if ((a instanceof links.Timeline.ItemRange || a instanceof links.Timeline.ItemFloatingRange) &&
5291
+ !(b instanceof links.Timeline.ItemRange || b instanceof links.Timeline.ItemFloatingRange)) {
4806
5292
  return -1;
4807
5293
  }
4808
5294
 
4809
- if (!(a instanceof links.Timeline.ItemRange) &&
4810
- (b instanceof links.Timeline.ItemRange)) {
5295
+ if (!(a instanceof links.Timeline.ItemRange || a instanceof links.Timeline.ItemFloatingRange) &&
5296
+ (b instanceof links.Timeline.ItemRange || b instanceof links.Timeline.ItemFloatingRange)) {
4811
5297
  return 1;
4812
5298
  }
4813
5299
 
@@ -4826,56 +5312,83 @@ links.Timeline.prototype.stackOrder = function(items) {
4826
5312
  * @return {Object[]} finalItems
4827
5313
  */
4828
5314
  links.Timeline.prototype.stackCalculateFinal = function(items) {
4829
- var i,
4830
- iMax,
4831
- size = this.size,
4832
- axisTop = size.axis.top,
4833
- axisHeight = size.axis.height,
5315
+ var size = this.size,
4834
5316
  options = this.options,
4835
5317
  axisOnTop = options.axisOnTop,
4836
5318
  eventMargin = options.eventMargin,
4837
5319
  eventMarginAxis = options.eventMarginAxis,
4838
- finalItems = [];
5320
+ groupBase = (axisOnTop)
5321
+ ? size.axis.height + eventMarginAxis + eventMargin/2
5322
+ : size.contentHeight - eventMarginAxis - eventMargin/2,
5323
+ groupedItems, groupFinalItems, finalItems = [];
5324
+
5325
+ groupedItems = this.getItemsByGroup(items);
5326
+
5327
+ //
5328
+ // groupedItems contains all items by group, plus it may contain an
5329
+ // additional "undefined" group which contains all items with no group. We
5330
+ // first process the grouped items, and then the ungrouped
5331
+ //
5332
+ for (j = 0; j<this.groups.length; ++j) {
5333
+ var group = this.groups[j];
5334
+
5335
+ if (!groupedItems[group.content]) {
5336
+ if (axisOnTop) {
5337
+ groupBase += options.groupMinHeight + eventMargin;
5338
+ } else {
5339
+ groupBase -= (options.groupMinHeight + eventMargin);
5340
+ }
5341
+ continue;
5342
+ }
4839
5343
 
4840
- // initialize final positions
4841
- for (i = 0, iMax = items.length; i < iMax; i++) {
4842
- var item = items[i],
4843
- top,
4844
- bottom,
4845
- height = item.height,
4846
- width = item.getWidth(this),
4847
- right = item.getRight(this),
4848
- left = right - width;
5344
+ // initialize final positions and fill finalItems
5345
+ groupFinalItems = this.finalItemsPosition(groupedItems[group.content], groupBase, group);
5346
+ groupFinalItems.forEach(function(item) {
5347
+ finalItems.push(item);
5348
+ });
4849
5349
 
4850
5350
  if (axisOnTop) {
4851
- top = axisHeight + eventMarginAxis + eventMargin / 2;
4852
- }
4853
- else {
4854
- top = axisTop - height - eventMarginAxis - eventMargin / 2;
5351
+ groupBase += group.itemsHeight + eventMargin;
5352
+ } else {
5353
+ groupBase -= (group.itemsHeight + eventMargin);
4855
5354
  }
4856
- bottom = top + height;
5355
+ }
4857
5356
 
4858
- finalItems[i] = {
4859
- 'left': left,
4860
- 'top': top,
4861
- 'right': right,
4862
- 'bottom': bottom,
4863
- 'height': height,
4864
- 'item': item
4865
- };
5357
+ //
5358
+ // Ungrouped items' turn now!
5359
+ //
5360
+ if (groupedItems["undefined"]) {
5361
+ // initialize final positions and fill finalItems
5362
+ groupFinalItems = this.finalItemsPosition(groupedItems["undefined"], groupBase);
5363
+ groupFinalItems.forEach(function(item) {
5364
+ finalItems.push(item);
5365
+ });
4866
5366
  }
4867
5367
 
4868
- if (this.options.stackEvents) {
4869
- // calculate new, non-overlapping positions
4870
- //var items = sortedItems;
4871
- for (i = 0, iMax = finalItems.length; i < iMax; i++) {
4872
- //for (var i = finalItems.length - 1; i >= 0; i--) {
4873
- var finalItem = finalItems[i];
4874
- var collidingItem = null;
5368
+ return finalItems;
5369
+ };
5370
+
5371
+ links.Timeline.prototype.finalItemsPosition = function(items, groupBase, group) {
5372
+ var i,
5373
+ iMax,
5374
+ options = this.options,
5375
+ axisOnTop = options.axisOnTop,
5376
+ eventMargin = options.eventMargin,
5377
+ groupFinalItems;
5378
+
5379
+ // initialize final positions and fill finalItems
5380
+ groupFinalItems = this.initialItemsPosition(items, groupBase);
5381
+
5382
+ // calculate new, non-overlapping positions
5383
+ for (i = 0, iMax = groupFinalItems.length; i < iMax; i++) {
5384
+ var finalItem = groupFinalItems[i];
5385
+ var collidingItem = null;
5386
+
5387
+ if (this.options.stackEvents) {
4875
5388
  do {
4876
5389
  // TODO: optimize checking for overlap. when there is a gap without items,
4877
5390
  // you only need to check for items from the next item on, not from zero
4878
- collidingItem = this.stackItemsCheckOverlap(finalItems, i, 0, i-1);
5391
+ collidingItem = this.stackItemsCheckOverlap(groupFinalItems, i, 0, i-1);
4879
5392
  if (collidingItem != null) {
4880
5393
  // There is a collision. Reposition the event above the colliding element
4881
5394
  if (axisOnTop) {
@@ -4888,11 +5401,54 @@ links.Timeline.prototype.stackCalculateFinal = function(items) {
4888
5401
  }
4889
5402
  } while (collidingItem);
4890
5403
  }
5404
+
5405
+ if (group) {
5406
+ if (axisOnTop) {
5407
+ group.itemsHeight = (group.itemsHeight)
5408
+ ? Math.max(group.itemsHeight, finalItem.bottom - groupBase)
5409
+ : finalItem.height + eventMargin;
5410
+ } else {
5411
+ group.itemsHeight = (group.itemsHeight)
5412
+ ? Math.max(group.itemsHeight, groupBase - finalItem.top)
5413
+ : finalItem.height + eventMargin;
5414
+ }
5415
+ }
4891
5416
  }
4892
5417
 
4893
- return finalItems;
5418
+ return groupFinalItems;
4894
5419
  };
4895
5420
 
5421
+ links.Timeline.prototype.initialItemsPosition = function(items, groupBase) {
5422
+ var options = this.options,
5423
+ axisOnTop = options.axisOnTop,
5424
+ finalItems = [];
5425
+
5426
+ for (var i = 0, iMax = items.length; i < iMax; ++i) {
5427
+ var item = items[i],
5428
+ top,
5429
+ bottom,
5430
+ height = item.height,
5431
+ width = item.getWidth(this),
5432
+ right = item.getRight(this),
5433
+ left = right - width;
5434
+
5435
+ top = (axisOnTop) ? groupBase
5436
+ : groupBase - height;
5437
+
5438
+ bottom = top + height;
5439
+
5440
+ finalItems.push({
5441
+ 'left': left,
5442
+ 'top': top,
5443
+ 'right': right,
5444
+ 'bottom': bottom,
5445
+ 'height': height,
5446
+ 'item': item
5447
+ });
5448
+ }
5449
+
5450
+ return finalItems;
5451
+ };
4896
5452
 
4897
5453
  /**
4898
5454
  * Move the events one step in the direction of their final positions
@@ -4907,7 +5463,7 @@ links.Timeline.prototype.stackMoveOneStep = function(currentItems, finalItems) {
4907
5463
  var arrived = true;
4908
5464
 
4909
5465
  // apply new positions animated
4910
- for (i = 0, iMax = finalItems.length; i < iMax; i++) {
5466
+ for (var i = 0, iMax = finalItems.length; i < iMax; i++) {
4911
5467
  var finalItem = finalItems[i],
4912
5468
  item = finalItem.item;
4913
5469
 
@@ -4949,7 +5505,7 @@ links.Timeline.prototype.stackMoveOneStep = function(currentItems, finalItems) {
4949
5505
  */
4950
5506
  links.Timeline.prototype.stackMoveToFinal = function(currentItems, finalItems) {
4951
5507
  // Put the events directly at there final position
4952
- for (i = 0, iMax = finalItems.length; i < iMax; i++) {
5508
+ for (var i = 0, iMax = finalItems.length; i < iMax; i++) {
4953
5509
  var finalItem = finalItems[i],
4954
5510
  current = finalItem.item;
4955
5511
 
@@ -4976,7 +5532,7 @@ links.Timeline.prototype.stackItemsCheckOverlap = function(items, itemIndex,
4976
5532
  var eventMargin = this.options.eventMargin,
4977
5533
  collision = this.collision;
4978
5534
 
4979
- // we loop from end to start, as we suppose that the chance of a
5535
+ // we loop from end to start, as we suppose that the chance of a
4980
5536
  // collision is larger for items at the end, so check these first.
4981
5537
  var item1 = items[itemIndex];
4982
5538
  for (var i = itemEnd; i >= itemStart; i--) {
@@ -5004,7 +5560,7 @@ links.Timeline.prototype.stackItemsCheckOverlap = function(items, itemIndex,
5004
5560
  * @return {boolean} true if item1 and item2 collide, else false
5005
5561
  */
5006
5562
  links.Timeline.prototype.collision = function(item1, item2, margin) {
5007
- // set margin if not specified
5563
+ // set margin if not specified
5008
5564
  if (margin == undefined) {
5009
5565
  margin = 0;
5010
5566
  }
@@ -5059,7 +5615,7 @@ links.Timeline.prototype.clusterItems = function () {
5059
5615
  return;
5060
5616
  }
5061
5617
 
5062
- var clusters = this.clusterGenerator.getClusters(this.conversion.factor);
5618
+ var clusters = this.clusterGenerator.getClusters(this.conversion.factor, this.options.clusterMaxItems);
5063
5619
  if (this.clusters != clusters) {
5064
5620
  // cluster level changed
5065
5621
  var queue = this.renderQueue;
@@ -5235,20 +5791,14 @@ links.Timeline.ClusterGenerator.prototype.filterData = function () {
5235
5791
  * defined as (windowWidth / (endDate - startDate))
5236
5792
  * @return {Item[]} clusters
5237
5793
  */
5238
- links.Timeline.ClusterGenerator.prototype.getClusters = function (scale) {
5794
+ links.Timeline.ClusterGenerator.prototype.getClusters = function (scale, maxItems) {
5239
5795
  var level = -1,
5240
5796
  granularity = 2, // TODO: what granularity is needed for the cluster levels?
5241
- timeWindow = 0, // milliseconds
5242
- maxItems = 5; // TODO: do not hard code maxItems
5797
+ timeWindow = 0; // milliseconds
5243
5798
 
5244
5799
  if (scale > 0) {
5245
5800
  level = Math.round(Math.log(100 / scale) / Math.log(granularity));
5246
5801
  timeWindow = Math.pow(granularity, level);
5247
-
5248
- // groups must have a larger time window, as the items will not be stacked
5249
- if (this.timeline.groups && this.timeline.groups.length) {
5250
- timeWindow *= 4;
5251
- }
5252
5802
  }
5253
5803
 
5254
5804
  // clear the cache when and re-filter the data when needed.
@@ -5333,7 +5883,7 @@ links.Timeline.ClusterGenerator.prototype.getClusters = function (scale) {
5333
5883
  }
5334
5884
  min = (min != undefined) ? Math.min(min, start) : start;
5335
5885
  max = (max != undefined) ? Math.max(max, end) : end;
5336
- containsRanges = containsRanges || (p instanceof links.Timeline.ItemRange);
5886
+ containsRanges = containsRanges || (p instanceof links.Timeline.ItemRange || p instanceof links.Timeline.ItemFloatingRange);
5337
5887
  count++;
5338
5888
  m++;
5339
5889
  }
@@ -5643,7 +6193,7 @@ links.Timeline.StepDate.prototype.end = function () {
5643
6193
  links.Timeline.StepDate.prototype.next = function() {
5644
6194
  var prev = this.current.valueOf();
5645
6195
 
5646
- // Two cases, needed to prevent issues with switching daylight savings
6196
+ // Two cases, needed to prevent issues with switching daylight savings
5647
6197
  // (end of March and end of October)
5648
6198
  if (this.current.getMonth() < 6) {
5649
6199
  switch (this.scale) {
@@ -5810,7 +6360,7 @@ links.Timeline.StepDate.prototype.snap = function(date) {
5810
6360
  if (date.getDate() > 15) {
5811
6361
  date.setDate(1);
5812
6362
  date.setMonth(date.getMonth() + 1);
5813
- // important: first set Date to 1, after that change the month.
6363
+ // important: first set Date to 1, after that change the month.
5814
6364
  }
5815
6365
  else {
5816
6366
  date.setDate(1);
@@ -6002,7 +6552,7 @@ links.Timeline.StepDate.prototype.addZeros = function(value, len) {
6002
6552
  */
6003
6553
  links.imageloader = (function () {
6004
6554
  var urls = {}; // the loaded urls
6005
- var callbacks = {}; // the urls currently being loaded. Each key contains
6555
+ var callbacks = {}; // the urls currently being loaded. Each key contains
6006
6556
  // an array with callbacks
6007
6557
 
6008
6558
  /**
@@ -6354,7 +6904,7 @@ links.Timeline.getPageX = function (event) {
6354
6904
  links.Timeline.addClassName = function(elem, className) {
6355
6905
  var classes = elem.className.split(' ');
6356
6906
  var classesToAdd = className.split(' ');
6357
-
6907
+
6358
6908
  var added = false;
6359
6909
  for (var i=0; i<classesToAdd.length; i++) {
6360
6910
  if (classes.indexOf(classesToAdd[i]) == -1) {
@@ -6362,7 +6912,7 @@ links.Timeline.addClassName = function(elem, className) {
6362
6912
  added = true;
6363
6913
  }
6364
6914
  }
6365
-
6915
+
6366
6916
  if (added) {
6367
6917
  elem.className = classes.join(' ');
6368
6918
  }
@@ -6376,7 +6926,7 @@ links.Timeline.addClassName = function(elem, className) {
6376
6926
  links.Timeline.removeClassName = function(elem, className) {
6377
6927
  var classes = elem.className.split(' ');
6378
6928
  var classesToRemove = className.split(' ');
6379
-
6929
+
6380
6930
  var removed = false;
6381
6931
  for (var i=0; i<classesToRemove.length; i++) {
6382
6932
  var index = classes.indexOf(classesToRemove[i]);
@@ -6385,7 +6935,7 @@ links.Timeline.removeClassName = function(elem, className) {
6385
6935
  removed = true;
6386
6936
  }
6387
6937
  }
6388
-
6938
+
6389
6939
  if (removed) {
6390
6940
  elem.className = classes.join(' ');
6391
6941
  }
@@ -6404,6 +6954,21 @@ links.Timeline.isArray = function (obj) {
6404
6954
  return (Object.prototype.toString.call(obj) === '[object Array]');
6405
6955
  };
6406
6956
 
6957
+ /**
6958
+ * Shallow clone an object
6959
+ * @param {Object} object
6960
+ * @return {Object} clone
6961
+ */
6962
+ links.Timeline.clone = function (object) {
6963
+ var clone = {};
6964
+ for (var prop in object) {
6965
+ if (object.hasOwnProperty(prop)) {
6966
+ clone[prop] = object[prop];
6967
+ }
6968
+ }
6969
+ return clone;
6970
+ };
6971
+
6407
6972
  /**
6408
6973
  * parse a JSON date
6409
6974
  * @param {Date | String | Number} date Date object to be parsed. Can be: