Mercurial > hg > STI-GWT
comparison war/scripts/Timeplot/timeplot-complete.js @ 3:cf06b77a8bbd
Committed branch of the e4D repos sti-gwt branch 16384.
git-svn-id: http://dev.dariah.eu/svn/repos/eu.dariah.de/ap1/sti-gwt-dariah-geobrowser@36 f2b5be40-def6-11e0-8a09-b3c1cc336c6b
author | StefanFunk <StefanFunk@f2b5be40-def6-11e0-8a09-b3c1cc336c6b> |
---|---|
date | Tue, 17 Jul 2012 13:34:40 +0000 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
2:2897af43ccc6 | 3:cf06b77a8bbd |
---|---|
1 /*================================================== | |
2 * Simile Ajax API | |
3 *================================================== | |
4 */ | |
5 | |
6 if (typeof SimileAjax == "undefined") { | |
7 var SimileAjax = { | |
8 loaded: false, | |
9 loadingScriptsCount: 0, | |
10 error: null, | |
11 params: { bundle:"true" } | |
12 }; | |
13 | |
14 SimileAjax.Platform = new Object(); | |
15 /* | |
16 HACK: We need these 2 things here because we cannot simply append | |
17 a <script> element containing code that accesses SimileAjax.Platform | |
18 to initialize it because IE executes that <script> code first | |
19 before it loads ajax.js and platform.js. | |
20 */ | |
21 | |
22 var getHead = function(doc) { | |
23 return doc.getElementsByTagName("head")[0]; | |
24 }; | |
25 | |
26 SimileAjax.findScript = function(doc, substring) { | |
27 var heads = doc.documentElement.getElementsByTagName("head"); | |
28 for (var h = 0; h < heads.length; h++) { | |
29 var node = heads[h].firstChild; | |
30 while (node != null) { | |
31 if (node.nodeType == 1 && node.tagName.toLowerCase() == "script") { | |
32 var url = node.src; | |
33 var i = url.indexOf(substring); | |
34 if (i >= 0) { | |
35 return url; | |
36 } | |
37 } | |
38 node = node.nextSibling; | |
39 } | |
40 } | |
41 return null; | |
42 }; | |
43 SimileAjax.includeJavascriptFile = function(doc, url, onerror, charset) { | |
44 return; | |
45 onerror = onerror || ""; | |
46 if (doc.body == null) { | |
47 try { | |
48 var q = "'" + onerror.replace( /'/g, '&apos' ) + "'"; // " | |
49 doc.write("<script src='" + url + "' onerror="+ q + | |
50 (charset ? " charset='"+ charset +"'" : "") + | |
51 " type='text/javascript'>"+ onerror + "</script>"); | |
52 return; | |
53 } catch (e) { | |
54 // fall through | |
55 } | |
56 } | |
57 | |
58 var script = doc.createElement("script"); | |
59 if (onerror) { | |
60 try { script.innerHTML = onerror; } catch(e) {} | |
61 script.setAttribute("onerror", onerror); | |
62 } | |
63 if (charset) { | |
64 script.setAttribute("charset", charset); | |
65 } | |
66 script.type = "text/javascript"; | |
67 script.language = "JavaScript"; | |
68 script.src = url; | |
69 return getHead(doc).appendChild(script); | |
70 }; | |
71 SimileAjax.includeJavascriptFiles = function(doc, urlPrefix, filenames) { | |
72 for (var i = 0; i < filenames.length; i++) { | |
73 SimileAjax.includeJavascriptFile(doc, urlPrefix + filenames[i]); | |
74 } | |
75 SimileAjax.loadingScriptsCount += filenames.length; | |
76 SimileAjax.includeJavascriptFile(doc, SimileAjax.urlPrefix + "scripts/signal.js?" + filenames.length); | |
77 }; | |
78 SimileAjax.includeCssFile = function(doc, url) { | |
79 if (doc.body == null) { | |
80 try { | |
81 doc.write("<link rel='stylesheet' href='" + url + "' type='text/css'/>"); | |
82 return; | |
83 } catch (e) { | |
84 // fall through | |
85 } | |
86 } | |
87 | |
88 var link = doc.createElement("link"); | |
89 link.setAttribute("rel", "stylesheet"); | |
90 link.setAttribute("type", "text/css"); | |
91 link.setAttribute("href", url); | |
92 getHead(doc).appendChild(link); | |
93 }; | |
94 SimileAjax.includeCssFiles = function(doc, urlPrefix, filenames) { | |
95 for (var i = 0; i < filenames.length; i++) { | |
96 SimileAjax.includeCssFile(doc, urlPrefix + filenames[i]); | |
97 } | |
98 }; | |
99 | |
100 /** | |
101 * Append into urls each string in suffixes after prefixing it with urlPrefix. | |
102 * @param {Array} urls | |
103 * @param {String} urlPrefix | |
104 * @param {Array} suffixes | |
105 */ | |
106 SimileAjax.prefixURLs = function(urls, urlPrefix, suffixes) { | |
107 for (var i = 0; i < suffixes.length; i++) { | |
108 urls.push(urlPrefix + suffixes[i]); | |
109 } | |
110 }; | |
111 | |
112 /** | |
113 * Parse out the query parameters from a URL | |
114 * @param {String} url the url to parse, or location.href if undefined | |
115 * @param {Object} to optional object to extend with the parameters | |
116 * @param {Object} types optional object mapping keys to value types | |
117 * (String, Number, Boolean or Array, String by default) | |
118 * @return a key/value Object whose keys are the query parameter names | |
119 * @type Object | |
120 */ | |
121 SimileAjax.parseURLParameters = function(url, to, types) { | |
122 to = to || {}; | |
123 types = types || {}; | |
124 | |
125 if (typeof url == "undefined") { | |
126 url = location.href; | |
127 } | |
128 var q = url.indexOf("?"); | |
129 if (q < 0) { | |
130 return to; | |
131 } | |
132 url = (url+"#").slice(q+1, url.indexOf("#")); // toss the URL fragment | |
133 | |
134 var params = url.split("&"), param, parsed = {}; | |
135 var decode = window.decodeURIComponent || unescape; | |
136 for (var i = 0; param = params[i]; i++) { | |
137 var eq = param.indexOf("="); | |
138 var name = decode(param.slice(0,eq)); | |
139 var old = parsed[name]; | |
140 if (typeof old == "undefined") { | |
141 old = []; | |
142 } else if (!(old instanceof Array)) { | |
143 old = [old]; | |
144 } | |
145 parsed[name] = old.concat(decode(param.slice(eq+1))); | |
146 } | |
147 for (var i in parsed) { | |
148 if (!parsed.hasOwnProperty(i)) continue; | |
149 var type = types[i] || String; | |
150 var data = parsed[i]; | |
151 if (!(data instanceof Array)) { | |
152 data = [data]; | |
153 } | |
154 if (type === Boolean && data[0] == "false") { | |
155 to[i] = false; // because Boolean("false") === true | |
156 } else { | |
157 to[i] = type.apply(this, data); | |
158 } | |
159 } | |
160 return to; | |
161 }; | |
162 | |
163 (function() { | |
164 var javascriptFiles = [ | |
165 "platform.js", | |
166 "debug.js", | |
167 "xmlhttp.js", | |
168 "json.js", | |
169 "dom.js", | |
170 "graphics.js", | |
171 "date-time.js", | |
172 "string.js", | |
173 "html.js", | |
174 "data-structure.js", | |
175 "units.js", | |
176 | |
177 "ajax.js", | |
178 "history.js", | |
179 "window-manager.js" | |
180 ]; | |
181 var cssFiles = [ | |
182 "timeplot-single.css" | |
183 ]; | |
184 if (!("jQuery" in window) && !("$" in window)) { | |
185 javascriptFiles.unshift("jquery-1.3.2.min.js"); | |
186 } | |
187 | |
188 if (typeof SimileAjax_urlPrefix == "string") { | |
189 SimileAjax.urlPrefix = SimileAjax_urlPrefix; | |
190 } else { | |
191 var url = SimileAjax.findScript(document, "timeplot-complete.js"); | |
192 if (url == null) { | |
193 SimileAjax.error = new Error("Failed to derive URL prefix for Simile Ajax API code files"); | |
194 return; | |
195 } | |
196 | |
197 SimileAjax.urlPrefix = url.substr(0, url.indexOf("timeplot-complete.js")); | |
198 } | |
199 | |
200 SimileAjax.parseURLParameters(url, SimileAjax.params, {bundle:Boolean}); | |
201 if (SimileAjax.params.bundle) { | |
202 SimileAjax.includeJavascriptFiles(document, SimileAjax.urlPrefix, [ "simile-ajax-bundle.js" ]); | |
203 } else { | |
204 SimileAjax.includeJavascriptFiles(document, SimileAjax.urlPrefix + "scripts/", javascriptFiles); | |
205 } | |
206 SimileAjax.includeCssFiles(document, SimileAjax.urlPrefix + "styles/", cssFiles); | |
207 | |
208 /* We only use it once this way. */ | |
209 SimileAjax.includeCssFile = function(doc, url) { return ; } | |
210 | |
211 SimileAjax.loaded = true; | |
212 })(); | |
213 } | |
214 | |
215 /*================================================== | |
216 * Platform Utility Functions and Constants | |
217 *================================================== | |
218 */ | |
219 | |
220 /* This must be called after our jQuery has been loaded | |
221 but before control returns to user-code. | |
222 */ | |
223 | |
224 | |
225 /*================================================== | |
226 * REMEMBER to update the Version! | |
227 *================================================== | |
228 */ | |
229 SimileAjax.version = '2.2.1'; | |
230 | |
231 SimileAjax.jQuery = jQuery.noConflict(true); | |
232 if (typeof window["$"] == "undefined") { | |
233 window.$ = SimileAjax.jQuery; | |
234 } | |
235 | |
236 SimileAjax.Platform.os = { | |
237 isMac: false, | |
238 isWin: false, | |
239 isWin32: false, | |
240 isUnix: false | |
241 }; | |
242 SimileAjax.Platform.browser = { | |
243 isIE: false, | |
244 isNetscape: false, | |
245 isMozilla: false, | |
246 isFirefox: false, | |
247 isOpera: false, | |
248 isSafari: false, | |
249 | |
250 majorVersion: 0, | |
251 minorVersion: 0 | |
252 }; | |
253 | |
254 (function() { | |
255 var an = navigator.appName.toLowerCase(); | |
256 var ua = navigator.userAgent.toLowerCase(); | |
257 | |
258 /* | |
259 * Operating system | |
260 */ | |
261 SimileAjax.Platform.os.isMac = (ua.indexOf('mac') != -1); | |
262 SimileAjax.Platform.os.isWin = (ua.indexOf('win') != -1); | |
263 SimileAjax.Platform.os.isWin32 = SimileAjax.Platform.isWin && ( | |
264 ua.indexOf('95') != -1 || | |
265 ua.indexOf('98') != -1 || | |
266 ua.indexOf('nt') != -1 || | |
267 ua.indexOf('win32') != -1 || | |
268 ua.indexOf('32bit') != -1 | |
269 ); | |
270 SimileAjax.Platform.os.isUnix = (ua.indexOf('x11') != -1); | |
271 | |
272 /* | |
273 * Browser | |
274 */ | |
275 SimileAjax.Platform.browser.isIE = (an.indexOf("microsoft") != -1); | |
276 SimileAjax.Platform.browser.isNetscape = (an.indexOf("netscape") != -1); | |
277 SimileAjax.Platform.browser.isMozilla = (ua.indexOf("mozilla") != -1); | |
278 SimileAjax.Platform.browser.isFirefox = (ua.indexOf("firefox") != -1); | |
279 SimileAjax.Platform.browser.isOpera = (an.indexOf("opera") != -1); | |
280 SimileAjax.Platform.browser.isSafari = (an.indexOf("safari") != -1); | |
281 | |
282 var parseVersionString = function(s) { | |
283 var a = s.split("."); | |
284 SimileAjax.Platform.browser.majorVersion = parseInt(a[0]); | |
285 SimileAjax.Platform.browser.minorVersion = parseInt(a[1]); | |
286 }; | |
287 var indexOf = function(s, sub, start) { | |
288 var i = s.indexOf(sub, start); | |
289 return i >= 0 ? i : s.length; | |
290 }; | |
291 | |
292 if (SimileAjax.Platform.browser.isMozilla) { | |
293 var offset = ua.indexOf("mozilla/"); | |
294 if (offset >= 0) { | |
295 parseVersionString(ua.substring(offset + 8, indexOf(ua, " ", offset))); | |
296 } | |
297 } | |
298 if (SimileAjax.Platform.browser.isIE) { | |
299 var offset = ua.indexOf("msie "); | |
300 if (offset >= 0) { | |
301 parseVersionString(ua.substring(offset + 5, indexOf(ua, ";", offset))); | |
302 } | |
303 } | |
304 if (SimileAjax.Platform.browser.isNetscape) { | |
305 var offset = ua.indexOf("rv:"); | |
306 if (offset >= 0) { | |
307 parseVersionString(ua.substring(offset + 3, indexOf(ua, ")", offset))); | |
308 } | |
309 } | |
310 if (SimileAjax.Platform.browser.isFirefox) { | |
311 var offset = ua.indexOf("firefox/"); | |
312 if (offset >= 0) { | |
313 parseVersionString(ua.substring(offset + 8, indexOf(ua, " ", offset))); | |
314 } | |
315 } | |
316 | |
317 if (!("localeCompare" in String.prototype)) { | |
318 String.prototype.localeCompare = function (s) { | |
319 if (this < s) return -1; | |
320 else if (this > s) return 1; | |
321 else return 0; | |
322 }; | |
323 } | |
324 })(); | |
325 | |
326 SimileAjax.Platform.getDefaultLocale = function() { | |
327 return SimileAjax.Platform.clientLocale; | |
328 }; | |
329 /*================================================== | |
330 * Debug Utility Functions | |
331 *================================================== | |
332 */ | |
333 | |
334 SimileAjax.Debug = { | |
335 silent: false | |
336 }; | |
337 | |
338 SimileAjax.Debug.log = function(msg) { | |
339 var f; | |
340 if ("console" in window && "log" in window.console) { // FireBug installed | |
341 f = function(msg2) { | |
342 console.log(msg2); | |
343 } | |
344 } else { | |
345 f = function(msg2) { | |
346 if (!SimileAjax.Debug.silent) { | |
347 alert(msg2); | |
348 } | |
349 } | |
350 } | |
351 SimileAjax.Debug.log = f; | |
352 f(msg); | |
353 }; | |
354 | |
355 SimileAjax.Debug.warn = function(msg) { | |
356 var f; | |
357 if ("console" in window && "warn" in window.console) { // FireBug installed | |
358 f = function(msg2) { | |
359 console.warn(msg2); | |
360 } | |
361 } else { | |
362 f = function(msg2) { | |
363 if (!SimileAjax.Debug.silent) { | |
364 alert(msg2); | |
365 } | |
366 } | |
367 } | |
368 SimileAjax.Debug.warn = f; | |
369 f(msg); | |
370 }; | |
371 | |
372 SimileAjax.Debug.exception = function(e, msg) { | |
373 var f, params = SimileAjax.parseURLParameters(); | |
374 if (params.errors == "throw" || SimileAjax.params.errors == "throw") { | |
375 f = function(e2, msg2) { | |
376 throw(e2); // do not hide from browser's native debugging features | |
377 }; | |
378 } else if ("console" in window && "error" in window.console) { // FireBug installed | |
379 f = function(e2, msg2) { | |
380 if (msg2 != null) { | |
381 console.error(msg2 + " %o", e2); | |
382 } else { | |
383 console.error(e2); | |
384 } | |
385 throw(e2); // do not hide from browser's native debugging features | |
386 }; | |
387 } else { | |
388 f = function(e2, msg2) { | |
389 if (!SimileAjax.Debug.silent) { | |
390 alert("Caught exception: " + msg2 + "\n\nDetails: " + ("description" in e2 ? e2.description : e2)); | |
391 } | |
392 throw(e2); // do not hide from browser's native debugging features | |
393 }; | |
394 } | |
395 SimileAjax.Debug.exception = f; | |
396 f(e, msg); | |
397 }; | |
398 | |
399 SimileAjax.Debug.objectToString = function(o) { | |
400 return SimileAjax.Debug._objectToString(o, ""); | |
401 }; | |
402 | |
403 SimileAjax.Debug._objectToString = function(o, indent) { | |
404 var indent2 = indent + " "; | |
405 if (typeof o == "object") { | |
406 var s = "{"; | |
407 for (n in o) { | |
408 s += indent2 + n + ": " + SimileAjax.Debug._objectToString(o[n], indent2) + "\n"; | |
409 } | |
410 s += indent + "}"; | |
411 return s; | |
412 } else if (typeof o == "array") { | |
413 var s = "["; | |
414 for (var n = 0; n < o.length; n++) { | |
415 s += SimileAjax.Debug._objectToString(o[n], indent2) + "\n"; | |
416 } | |
417 s += indent + "]"; | |
418 return s; | |
419 } else { | |
420 return o; | |
421 } | |
422 }; | |
423 /** | |
424 * @fileOverview XmlHttp utility functions | |
425 * @name SimileAjax.XmlHttp | |
426 */ | |
427 | |
428 SimileAjax.XmlHttp = new Object(); | |
429 | |
430 /** | |
431 * Callback for XMLHttp onRequestStateChange. | |
432 */ | |
433 SimileAjax.XmlHttp._onReadyStateChange = function(xmlhttp, fError, fDone) { | |
434 switch (xmlhttp.readyState) { | |
435 // 1: Request not yet made | |
436 // 2: Contact established with server but nothing downloaded yet | |
437 // 3: Called multiple while downloading in progress | |
438 | |
439 // Download complete | |
440 case 4: | |
441 try { | |
442 if (xmlhttp.status == 0 // file:// urls, works on Firefox | |
443 || xmlhttp.status == 200 // http:// urls | |
444 ) { | |
445 if (fDone) { | |
446 fDone(xmlhttp); | |
447 } | |
448 } else { | |
449 if (fError) { | |
450 fError( | |
451 xmlhttp.statusText, | |
452 xmlhttp.status, | |
453 xmlhttp | |
454 ); | |
455 } | |
456 } | |
457 } catch (e) { | |
458 SimileAjax.Debug.exception("XmlHttp: Error handling onReadyStateChange", e); | |
459 } | |
460 break; | |
461 } | |
462 }; | |
463 | |
464 /** | |
465 * Creates an XMLHttpRequest object. On the first run, this | |
466 * function creates a platform-specific function for | |
467 * instantiating an XMLHttpRequest object and then replaces | |
468 * itself with that function. | |
469 */ | |
470 SimileAjax.XmlHttp._createRequest = function() { | |
471 if (SimileAjax.Platform.browser.isIE) { | |
472 var programIDs = [ | |
473 "Msxml2.XMLHTTP", | |
474 "Microsoft.XMLHTTP", | |
475 "Msxml2.XMLHTTP.4.0" | |
476 ]; | |
477 for (var i = 0; i < programIDs.length; i++) { | |
478 try { | |
479 var programID = programIDs[i]; | |
480 var f = function() { | |
481 return new ActiveXObject(programID); | |
482 }; | |
483 var o = f(); | |
484 | |
485 // We are replacing the SimileAjax._createXmlHttpRequest | |
486 // function with this inner function as we've | |
487 // found out that it works. This is so that we | |
488 // don't have to do all the testing over again | |
489 // on subsequent calls. | |
490 SimileAjax.XmlHttp._createRequest = f; | |
491 | |
492 return o; | |
493 } catch (e) { | |
494 // silent | |
495 } | |
496 } | |
497 // fall through to try new XMLHttpRequest(); | |
498 } | |
499 | |
500 try { | |
501 var f = function() { | |
502 return new XMLHttpRequest(); | |
503 }; | |
504 var o = f(); | |
505 | |
506 // We are replacing the SimileAjax._createXmlHttpRequest | |
507 // function with this inner function as we've | |
508 // found out that it works. This is so that we | |
509 // don't have to do all the testing over again | |
510 // on subsequent calls. | |
511 SimileAjax.XmlHttp._createRequest = f; | |
512 | |
513 return o; | |
514 } catch (e) { | |
515 throw new Error("Failed to create an XMLHttpRequest object"); | |
516 } | |
517 }; | |
518 | |
519 /** | |
520 * Performs an asynchronous HTTP GET. | |
521 * | |
522 * @param {Function} fError a function of the form | |
523 function(statusText, statusCode, xmlhttp) | |
524 * @param {Function} fDone a function of the form function(xmlhttp) | |
525 */ | |
526 SimileAjax.XmlHttp.get = function(url, fError, fDone) { | |
527 var xmlhttp = SimileAjax.XmlHttp._createRequest(); | |
528 | |
529 xmlhttp.open("GET", url, true); | |
530 xmlhttp.onreadystatechange = function() { | |
531 SimileAjax.XmlHttp._onReadyStateChange(xmlhttp, fError, fDone); | |
532 }; | |
533 xmlhttp.send(null); | |
534 }; | |
535 | |
536 /** | |
537 * Performs an asynchronous HTTP POST. | |
538 * | |
539 * @param {Function} fError a function of the form | |
540 function(statusText, statusCode, xmlhttp) | |
541 * @param {Function} fDone a function of the form function(xmlhttp) | |
542 */ | |
543 SimileAjax.XmlHttp.post = function(url, body, fError, fDone) { | |
544 var xmlhttp = SimileAjax.XmlHttp._createRequest(); | |
545 | |
546 xmlhttp.open("POST", url, true); | |
547 xmlhttp.onreadystatechange = function() { | |
548 SimileAjax.XmlHttp._onReadyStateChange(xmlhttp, fError, fDone); | |
549 }; | |
550 xmlhttp.send(body); | |
551 }; | |
552 | |
553 SimileAjax.XmlHttp._forceXML = function(xmlhttp) { | |
554 try { | |
555 xmlhttp.overrideMimeType("text/xml"); | |
556 } catch (e) { | |
557 xmlhttp.setrequestheader("Content-Type", "text/xml"); | |
558 } | |
559 }; | |
560 /* | |
561 * Copied directly from http://www.json.org/json.js. | |
562 */ | |
563 | |
564 /* | |
565 json.js | |
566 2006-04-28 | |
567 | |
568 This file adds these methods to JavaScript: | |
569 | |
570 object.toJSONString() | |
571 | |
572 This method produces a JSON text from an object. The | |
573 object must not contain any cyclical references. | |
574 | |
575 array.toJSONString() | |
576 | |
577 This method produces a JSON text from an array. The | |
578 array must not contain any cyclical references. | |
579 | |
580 string.parseJSON() | |
581 | |
582 This method parses a JSON text to produce an object or | |
583 array. It will return false if there is an error. | |
584 */ | |
585 | |
586 SimileAjax.JSON = new Object(); | |
587 | |
588 (function () { | |
589 var m = { | |
590 '\b': '\\b', | |
591 '\t': '\\t', | |
592 '\n': '\\n', | |
593 '\f': '\\f', | |
594 '\r': '\\r', | |
595 '"' : '\\"', | |
596 '\\': '\\\\' | |
597 }; | |
598 var s = { | |
599 array: function (x) { | |
600 var a = ['['], b, f, i, l = x.length, v; | |
601 for (i = 0; i < l; i += 1) { | |
602 v = x[i]; | |
603 f = s[typeof v]; | |
604 if (f) { | |
605 v = f(v); | |
606 if (typeof v == 'string') { | |
607 if (b) { | |
608 a[a.length] = ','; | |
609 } | |
610 a[a.length] = v; | |
611 b = true; | |
612 } | |
613 } | |
614 } | |
615 a[a.length] = ']'; | |
616 return a.join(''); | |
617 }, | |
618 'boolean': function (x) { | |
619 return String(x); | |
620 }, | |
621 'null': function (x) { | |
622 return "null"; | |
623 }, | |
624 number: function (x) { | |
625 return isFinite(x) ? String(x) : 'null'; | |
626 }, | |
627 object: function (x) { | |
628 if (x) { | |
629 if (x instanceof Array) { | |
630 return s.array(x); | |
631 } | |
632 var a = ['{'], b, f, i, v; | |
633 for (i in x) { | |
634 v = x[i]; | |
635 f = s[typeof v]; | |
636 if (f) { | |
637 v = f(v); | |
638 if (typeof v == 'string') { | |
639 if (b) { | |
640 a[a.length] = ','; | |
641 } | |
642 a.push(s.string(i), ':', v); | |
643 b = true; | |
644 } | |
645 } | |
646 } | |
647 a[a.length] = '}'; | |
648 return a.join(''); | |
649 } | |
650 return 'null'; | |
651 }, | |
652 string: function (x) { | |
653 if (/["\\\x00-\x1f]/.test(x)) { | |
654 x = x.replace(/([\x00-\x1f\\"])/g, function(a, b) { | |
655 var c = m[b]; | |
656 if (c) { | |
657 return c; | |
658 } | |
659 c = b.charCodeAt(); | |
660 return '\\u00' + | |
661 Math.floor(c / 16).toString(16) + | |
662 (c % 16).toString(16); | |
663 }); | |
664 } | |
665 return '"' + x + '"'; | |
666 } | |
667 }; | |
668 | |
669 SimileAjax.JSON.toJSONString = function(o) { | |
670 if (o instanceof Object) { | |
671 return s.object(o); | |
672 } else if (o instanceof Array) { | |
673 return s.array(o); | |
674 } else { | |
675 return o.toString(); | |
676 } | |
677 }; | |
678 | |
679 SimileAjax.JSON.parseJSON = function () { | |
680 try { | |
681 return !(/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test( | |
682 this.replace(/"(\\.|[^"\\])*"/g, ''))) && | |
683 eval('(' + this + ')'); | |
684 } catch (e) { | |
685 return false; | |
686 } | |
687 }; | |
688 })(); | |
689 /*================================================== | |
690 * DOM Utility Functions | |
691 *================================================== | |
692 */ | |
693 | |
694 SimileAjax.DOM = new Object(); | |
695 | |
696 SimileAjax.DOM.registerEventWithObject = function(elmt, eventName, obj, handlerName) { | |
697 SimileAjax.DOM.registerEvent(elmt, eventName, function(elmt2, evt, target) { | |
698 return obj[handlerName].call(obj, elmt2, evt, target); | |
699 }); | |
700 }; | |
701 | |
702 SimileAjax.DOM.registerEvent = function(elmt, eventName, handler) { | |
703 var handler2 = function(evt) { | |
704 evt = (evt) ? evt : ((event) ? event : null); | |
705 if (evt) { | |
706 var target = (evt.target) ? | |
707 evt.target : ((evt.srcElement) ? evt.srcElement : null); | |
708 if (target) { | |
709 target = (target.nodeType == 1 || target.nodeType == 9) ? | |
710 target : target.parentNode; | |
711 } | |
712 | |
713 return handler(elmt, evt, target); | |
714 } | |
715 return true; | |
716 } | |
717 | |
718 if (SimileAjax.Platform.browser.isIE) { | |
719 elmt.attachEvent("on" + eventName, handler2); | |
720 } else { | |
721 elmt.addEventListener(eventName, handler2, false); | |
722 } | |
723 }; | |
724 | |
725 SimileAjax.DOM.getPageCoordinates = function(elmt) { | |
726 var left = 0; | |
727 var top = 0; | |
728 | |
729 if (elmt.nodeType != 1) { | |
730 elmt = elmt.parentNode; | |
731 } | |
732 | |
733 var elmt2 = elmt; | |
734 while (elmt2 != null) { | |
735 left += elmt2.offsetLeft; | |
736 top += elmt2.offsetTop; | |
737 elmt2 = elmt2.offsetParent; | |
738 } | |
739 | |
740 var body = document.body; | |
741 while (elmt != null && elmt != body) { | |
742 if ("scrollLeft" in elmt) { | |
743 left -= elmt.scrollLeft; | |
744 top -= elmt.scrollTop; | |
745 } | |
746 elmt = elmt.parentNode; | |
747 } | |
748 | |
749 return { left: left, top: top }; | |
750 }; | |
751 | |
752 SimileAjax.DOM.getSize = function(elmt) { | |
753 var w = this.getStyle(elmt,"width"); | |
754 var h = this.getStyle(elmt,"height"); | |
755 if (w.indexOf("px") > -1) w = w.replace("px",""); | |
756 if (h.indexOf("px") > -1) h = h.replace("px",""); | |
757 return { | |
758 w: w, | |
759 h: h | |
760 } | |
761 } | |
762 | |
763 SimileAjax.DOM.getStyle = function(elmt, styleProp) { | |
764 if (elmt.currentStyle) { // IE | |
765 var style = elmt.currentStyle[styleProp]; | |
766 } else if (window.getComputedStyle) { // standard DOM | |
767 var style = document.defaultView.getComputedStyle(elmt, null).getPropertyValue(styleProp); | |
768 } else { | |
769 var style = ""; | |
770 } | |
771 return style; | |
772 } | |
773 | |
774 SimileAjax.DOM.getEventRelativeCoordinates = function(evt, elmt) { | |
775 if (SimileAjax.Platform.browser.isIE) { | |
776 if (evt.type == "mousewheel") { | |
777 var coords = SimileAjax.DOM.getPageCoordinates(elmt); | |
778 return { | |
779 x: evt.clientX - coords.left, | |
780 y: evt.clientY - coords.top | |
781 }; | |
782 } else { | |
783 return { | |
784 x: evt.offsetX, | |
785 y: evt.offsetY | |
786 }; | |
787 } | |
788 } else { | |
789 var coords = SimileAjax.DOM.getPageCoordinates(elmt); | |
790 | |
791 if ((evt.type == "DOMMouseScroll") && | |
792 SimileAjax.Platform.browser.isFirefox && | |
793 (SimileAjax.Platform.browser.majorVersion == 2)) { | |
794 // Due to: https://bugzilla.mozilla.org/show_bug.cgi?id=352179 | |
795 | |
796 return { | |
797 x: evt.screenX - coords.left, | |
798 y: evt.screenY - coords.top | |
799 }; | |
800 } else { | |
801 return { | |
802 x: evt.pageX - coords.left, | |
803 y: evt.pageY - coords.top | |
804 }; | |
805 } | |
806 } | |
807 }; | |
808 | |
809 SimileAjax.DOM.getEventPageCoordinates = function(evt) { | |
810 if (SimileAjax.Platform.browser.isIE) { | |
811 return { | |
812 x: evt.clientX + document.body.scrollLeft, | |
813 y: evt.clientY + document.body.scrollTop | |
814 }; | |
815 } else { | |
816 return { | |
817 x: evt.pageX, | |
818 y: evt.pageY | |
819 }; | |
820 } | |
821 }; | |
822 | |
823 SimileAjax.DOM.hittest = function(x, y, except) { | |
824 return SimileAjax.DOM._hittest(document.body, x, y, except); | |
825 }; | |
826 | |
827 SimileAjax.DOM._hittest = function(elmt, x, y, except) { | |
828 var childNodes = elmt.childNodes; | |
829 outer: for (var i = 0; i < childNodes.length; i++) { | |
830 var childNode = childNodes[i]; | |
831 for (var j = 0; j < except.length; j++) { | |
832 if (childNode == except[j]) { | |
833 continue outer; | |
834 } | |
835 } | |
836 | |
837 if (childNode.offsetWidth == 0 && childNode.offsetHeight == 0) { | |
838 /* | |
839 * Sometimes SPAN elements have zero width and height but | |
840 * they have children like DIVs that cover non-zero areas. | |
841 */ | |
842 var hitNode = SimileAjax.DOM._hittest(childNode, x, y, except); | |
843 if (hitNode != childNode) { | |
844 return hitNode; | |
845 } | |
846 } else { | |
847 var top = 0; | |
848 var left = 0; | |
849 | |
850 var node = childNode; | |
851 while (node) { | |
852 top += node.offsetTop; | |
853 left += node.offsetLeft; | |
854 node = node.offsetParent; | |
855 } | |
856 | |
857 if (left <= x && top <= y && (x - left) < childNode.offsetWidth && (y - top) < childNode.offsetHeight) { | |
858 return SimileAjax.DOM._hittest(childNode, x, y, except); | |
859 } else if (childNode.nodeType == 1 && childNode.tagName == "TR") { | |
860 /* | |
861 * Table row might have cells that span several rows. | |
862 */ | |
863 var childNode2 = SimileAjax.DOM._hittest(childNode, x, y, except); | |
864 if (childNode2 != childNode) { | |
865 return childNode2; | |
866 } | |
867 } | |
868 } | |
869 } | |
870 return elmt; | |
871 }; | |
872 | |
873 SimileAjax.DOM.cancelEvent = function(evt) { | |
874 evt.returnValue = false; | |
875 evt.cancelBubble = true; | |
876 if ("preventDefault" in evt) { | |
877 evt.preventDefault(); | |
878 } | |
879 }; | |
880 | |
881 SimileAjax.DOM.appendClassName = function(elmt, className) { | |
882 var classes = elmt.className.split(" "); | |
883 for (var i = 0; i < classes.length; i++) { | |
884 if (classes[i] == className) { | |
885 return; | |
886 } | |
887 } | |
888 classes.push(className); | |
889 elmt.className = classes.join(" "); | |
890 }; | |
891 | |
892 SimileAjax.DOM.createInputElement = function(type) { | |
893 var div = document.createElement("div"); | |
894 div.innerHTML = "<input type='" + type + "' />"; | |
895 | |
896 return div.firstChild; | |
897 }; | |
898 | |
899 SimileAjax.DOM.createDOMFromTemplate = function(template) { | |
900 var result = {}; | |
901 result.elmt = SimileAjax.DOM._createDOMFromTemplate(template, result, null); | |
902 | |
903 return result; | |
904 }; | |
905 | |
906 SimileAjax.DOM._createDOMFromTemplate = function(templateNode, result, parentElmt) { | |
907 if (templateNode == null) { | |
908 /* | |
909 var node = doc.createTextNode("--null--"); | |
910 if (parentElmt != null) { | |
911 parentElmt.appendChild(node); | |
912 } | |
913 return node; | |
914 */ | |
915 return null; | |
916 } else if (typeof templateNode != "object") { | |
917 var node = document.createTextNode(templateNode); | |
918 if (parentElmt != null) { | |
919 parentElmt.appendChild(node); | |
920 } | |
921 return node; | |
922 } else { | |
923 var elmt = null; | |
924 if ("tag" in templateNode) { | |
925 var tag = templateNode.tag; | |
926 if (parentElmt != null) { | |
927 if (tag == "tr") { | |
928 elmt = parentElmt.insertRow(parentElmt.rows.length); | |
929 } else if (tag == "td") { | |
930 elmt = parentElmt.insertCell(parentElmt.cells.length); | |
931 } | |
932 } | |
933 if (elmt == null) { | |
934 elmt = tag == "input" ? | |
935 SimileAjax.DOM.createInputElement(templateNode.type) : | |
936 document.createElement(tag); | |
937 | |
938 if (parentElmt != null) { | |
939 parentElmt.appendChild(elmt); | |
940 } | |
941 } | |
942 } else { | |
943 elmt = templateNode.elmt; | |
944 if (parentElmt != null) { | |
945 parentElmt.appendChild(elmt); | |
946 } | |
947 } | |
948 | |
949 for (var attribute in templateNode) { | |
950 var value = templateNode[attribute]; | |
951 | |
952 if (attribute == "field") { | |
953 result[value] = elmt; | |
954 | |
955 } else if (attribute == "className") { | |
956 elmt.className = value; | |
957 } else if (attribute == "id") { | |
958 elmt.id = value; | |
959 } else if (attribute == "title") { | |
960 elmt.title = value; | |
961 } else if (attribute == "type" && elmt.tagName == "input") { | |
962 // do nothing | |
963 } else if (attribute == "style") { | |
964 for (n in value) { | |
965 var v = value[n]; | |
966 if (n == "float") { | |
967 n = SimileAjax.Platform.browser.isIE ? "styleFloat" : "cssFloat"; | |
968 } | |
969 elmt.style[n] = v; | |
970 } | |
971 } else if (attribute == "children") { | |
972 for (var i = 0; i < value.length; i++) { | |
973 SimileAjax.DOM._createDOMFromTemplate(value[i], result, elmt); | |
974 } | |
975 } else if (attribute != "tag" && attribute != "elmt") { | |
976 elmt.setAttribute(attribute, value); | |
977 } | |
978 } | |
979 return elmt; | |
980 } | |
981 } | |
982 | |
983 SimileAjax.DOM._cachedParent = null; | |
984 SimileAjax.DOM.createElementFromString = function(s) { | |
985 if (SimileAjax.DOM._cachedParent == null) { | |
986 SimileAjax.DOM._cachedParent = document.createElement("div"); | |
987 } | |
988 SimileAjax.DOM._cachedParent.innerHTML = s; | |
989 return SimileAjax.DOM._cachedParent.firstChild; | |
990 }; | |
991 | |
992 SimileAjax.DOM.createDOMFromString = function(root, s, fieldElmts) { | |
993 var elmt = typeof root == "string" ? document.createElement(root) : root; | |
994 elmt.innerHTML = s; | |
995 | |
996 var dom = { elmt: elmt }; | |
997 SimileAjax.DOM._processDOMChildrenConstructedFromString(dom, elmt, fieldElmts != null ? fieldElmts : {} ); | |
998 | |
999 return dom; | |
1000 }; | |
1001 | |
1002 SimileAjax.DOM._processDOMConstructedFromString = function(dom, elmt, fieldElmts) { | |
1003 var id = elmt.id; | |
1004 if (id != null && id.length > 0) { | |
1005 elmt.removeAttribute("id"); | |
1006 if (id in fieldElmts) { | |
1007 var parentElmt = elmt.parentNode; | |
1008 parentElmt.insertBefore(fieldElmts[id], elmt); | |
1009 parentElmt.removeChild(elmt); | |
1010 | |
1011 dom[id] = fieldElmts[id]; | |
1012 return; | |
1013 } else { | |
1014 dom[id] = elmt; | |
1015 } | |
1016 } | |
1017 | |
1018 if (elmt.hasChildNodes()) { | |
1019 SimileAjax.DOM._processDOMChildrenConstructedFromString(dom, elmt, fieldElmts); | |
1020 } | |
1021 }; | |
1022 | |
1023 SimileAjax.DOM._processDOMChildrenConstructedFromString = function(dom, elmt, fieldElmts) { | |
1024 var node = elmt.firstChild; | |
1025 while (node != null) { | |
1026 var node2 = node.nextSibling; | |
1027 if (node.nodeType == 1) { | |
1028 SimileAjax.DOM._processDOMConstructedFromString(dom, node, fieldElmts); | |
1029 } | |
1030 node = node2; | |
1031 } | |
1032 }; | |
1033 /** | |
1034 * @fileOverview Graphics utility functions and constants | |
1035 * @name SimileAjax.Graphics | |
1036 */ | |
1037 | |
1038 SimileAjax.Graphics = new Object(); | |
1039 | |
1040 /** | |
1041 * A boolean value indicating whether PNG translucency is supported on the | |
1042 * user's browser or not. | |
1043 * | |
1044 * @type Boolean | |
1045 */ | |
1046 SimileAjax.Graphics.pngIsTranslucent = (!SimileAjax.Platform.browser.isIE) || (SimileAjax.Platform.browser.majorVersion > 6); | |
1047 if (!SimileAjax.Graphics.pngIsTranslucent) { | |
1048 SimileAjax.includeCssFile(document, SimileAjax.urlPrefix + "styles/graphics-ie6.css"); | |
1049 } | |
1050 | |
1051 /*================================================== | |
1052 * Opacity, translucency | |
1053 *================================================== | |
1054 */ | |
1055 SimileAjax.Graphics._createTranslucentImage1 = function(url, verticalAlign) { | |
1056 var elmt = document.createElement("img"); | |
1057 elmt.setAttribute("src", url); | |
1058 if (verticalAlign != null) { | |
1059 elmt.style.verticalAlign = verticalAlign; | |
1060 } | |
1061 return elmt; | |
1062 }; | |
1063 SimileAjax.Graphics._createTranslucentImage2 = function(url, verticalAlign) { | |
1064 var elmt = document.createElement("img"); | |
1065 elmt.style.width = "1px"; // just so that IE will calculate the size property | |
1066 elmt.style.height = "1px"; | |
1067 elmt.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + url +"', sizingMethod='image')"; | |
1068 elmt.style.verticalAlign = (verticalAlign != null) ? verticalAlign : "middle"; | |
1069 return elmt; | |
1070 }; | |
1071 | |
1072 /** | |
1073 * Creates a DOM element for an <code>img</code> tag using the URL given. This | |
1074 * is a convenience method that automatically includes the necessary CSS to | |
1075 * allow for translucency, even on IE. | |
1076 * | |
1077 * @function | |
1078 * @param {String} url the URL to the image | |
1079 * @param {String} verticalAlign the CSS value for the image's vertical-align | |
1080 * @return {Element} a DOM element containing the <code>img</code> tag | |
1081 */ | |
1082 SimileAjax.Graphics.createTranslucentImage = SimileAjax.Graphics.pngIsTranslucent ? | |
1083 SimileAjax.Graphics._createTranslucentImage1 : | |
1084 SimileAjax.Graphics._createTranslucentImage2; | |
1085 | |
1086 SimileAjax.Graphics._createTranslucentImageHTML1 = function(url, verticalAlign) { | |
1087 return "<img src=\"" + url + "\"" + | |
1088 (verticalAlign != null ? " style=\"vertical-align: " + verticalAlign + ";\"" : "") + | |
1089 " />"; | |
1090 }; | |
1091 SimileAjax.Graphics._createTranslucentImageHTML2 = function(url, verticalAlign) { | |
1092 var style = | |
1093 "width: 1px; height: 1px; " + | |
1094 "filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + url +"', sizingMethod='image');" + | |
1095 (verticalAlign != null ? " vertical-align: " + verticalAlign + ";" : ""); | |
1096 | |
1097 return "<img src='" + url + "' style=\"" + style + "\" />"; | |
1098 }; | |
1099 | |
1100 /** | |
1101 * Creates an HTML string for an <code>img</code> tag using the URL given. | |
1102 * This is a convenience method that automatically includes the necessary CSS | |
1103 * to allow for translucency, even on IE. | |
1104 * | |
1105 * @function | |
1106 * @param {String} url the URL to the image | |
1107 * @param {String} verticalAlign the CSS value for the image's vertical-align | |
1108 * @return {String} a string containing the <code>img</code> tag | |
1109 */ | |
1110 SimileAjax.Graphics.createTranslucentImageHTML = SimileAjax.Graphics.pngIsTranslucent ? | |
1111 SimileAjax.Graphics._createTranslucentImageHTML1 : | |
1112 SimileAjax.Graphics._createTranslucentImageHTML2; | |
1113 | |
1114 /** | |
1115 * Sets the opacity on the given DOM element. | |
1116 * | |
1117 * @param {Element} elmt the DOM element to set the opacity on | |
1118 * @param {Number} opacity an integer from 0 to 100 specifying the opacity | |
1119 */ | |
1120 SimileAjax.Graphics.setOpacity = function(elmt, opacity) { | |
1121 if (SimileAjax.Platform.browser.isIE) { | |
1122 elmt.style.filter = "progid:DXImageTransform.Microsoft.Alpha(Style=0,Opacity=" + opacity + ")"; | |
1123 } else { | |
1124 var o = (opacity / 100).toString(); | |
1125 elmt.style.opacity = o; | |
1126 elmt.style.MozOpacity = o; | |
1127 } | |
1128 }; | |
1129 | |
1130 /*================================================== | |
1131 * Bubble | |
1132 *================================================== | |
1133 */ | |
1134 | |
1135 SimileAjax.Graphics.bubbleConfig = { | |
1136 containerCSSClass: "simileAjax-bubble-container", | |
1137 innerContainerCSSClass: "simileAjax-bubble-innerContainer", | |
1138 contentContainerCSSClass: "simileAjax-bubble-contentContainer", | |
1139 | |
1140 borderGraphicSize: 50, | |
1141 borderGraphicCSSClassPrefix: "simileAjax-bubble-border-", | |
1142 | |
1143 arrowGraphicTargetOffset: 33, // from tip of arrow to the side of the graphic that touches the content of the bubble | |
1144 arrowGraphicLength: 100, // dimension of arrow graphic along the direction that the arrow points | |
1145 arrowGraphicWidth: 49, // dimension of arrow graphic perpendicular to the direction that the arrow points | |
1146 arrowGraphicCSSClassPrefix: "simileAjax-bubble-arrow-", | |
1147 | |
1148 closeGraphicCSSClass: "simileAjax-bubble-close", | |
1149 | |
1150 extraPadding: 20 | |
1151 }; | |
1152 | |
1153 /** | |
1154 * Creates a nice, rounded bubble popup with the given content in a div, | |
1155 * page coordinates and a suggested width. The bubble will point to the | |
1156 * location on the page as described by pageX and pageY. All measurements | |
1157 * should be given in pixels. | |
1158 * | |
1159 * @param {Element} the content div | |
1160 * @param {Number} pageX the x coordinate of the point to point to | |
1161 * @param {Number} pageY the y coordinate of the point to point to | |
1162 * @param {Number} contentWidth a suggested width of the content | |
1163 * @param {String} orientation a string ("top", "bottom", "left", or "right") | |
1164 * that describes the orientation of the arrow on the bubble | |
1165 * @param {Number} maxHeight. Add a scrollbar div if bubble would be too tall. | |
1166 * Default of 0 or null means no maximum | |
1167 */ | |
1168 SimileAjax.Graphics.createBubbleForContentAndPoint = function( | |
1169 div, pageX, pageY, contentWidth, orientation, maxHeight) { | |
1170 if (typeof contentWidth != "number") { | |
1171 contentWidth = 300; | |
1172 } | |
1173 if (typeof maxHeight != "number") { | |
1174 maxHeight = 0; | |
1175 } | |
1176 | |
1177 div.style.position = "absolute"; | |
1178 div.style.left = "-5000px"; | |
1179 div.style.top = "0px"; | |
1180 div.style.width = contentWidth + "px"; | |
1181 document.body.appendChild(div); | |
1182 | |
1183 window.setTimeout(function() { | |
1184 var width = div.scrollWidth + 10; | |
1185 var height = div.scrollHeight + 10; | |
1186 var scrollDivW = 0; // width of the possible inner container when we want vertical scrolling | |
1187 if (maxHeight > 0 && height > maxHeight) { | |
1188 height = maxHeight; | |
1189 scrollDivW = width - 25; | |
1190 } | |
1191 | |
1192 var bubble = SimileAjax.Graphics.createBubbleForPoint(pageX, pageY, width, height, orientation); | |
1193 | |
1194 document.body.removeChild(div); | |
1195 div.style.position = "static"; | |
1196 div.style.left = ""; | |
1197 div.style.top = ""; | |
1198 | |
1199 // create a scroll div if needed | |
1200 if (scrollDivW > 0) { | |
1201 var scrollDiv = document.createElement("div"); | |
1202 div.style.width = ""; | |
1203 scrollDiv.style.width = scrollDivW + "px"; | |
1204 scrollDiv.appendChild(div); | |
1205 bubble.content.appendChild(scrollDiv); | |
1206 } else { | |
1207 div.style.width = width + "px"; | |
1208 bubble.content.appendChild(div); | |
1209 } | |
1210 }, 200); | |
1211 }; | |
1212 | |
1213 /** | |
1214 * Creates a nice, rounded bubble popup with the given page coordinates and | |
1215 * content dimensions. The bubble will point to the location on the page | |
1216 * as described by pageX and pageY. All measurements should be given in | |
1217 * pixels. | |
1218 * | |
1219 * @param {Number} pageX the x coordinate of the point to point to | |
1220 * @param {Number} pageY the y coordinate of the point to point to | |
1221 * @param {Number} contentWidth the width of the content box in the bubble | |
1222 * @param {Number} contentHeight the height of the content box in the bubble | |
1223 * @param {String} orientation a string ("top", "bottom", "left", or "right") | |
1224 * that describes the orientation of the arrow on the bubble | |
1225 * @return {Element} a DOM element for the newly created bubble | |
1226 */ | |
1227 SimileAjax.Graphics.createBubbleForPoint = function(pageX, pageY, contentWidth, contentHeight, orientation) { | |
1228 contentWidth = parseInt(contentWidth, 10); // harden against bad input bugs | |
1229 contentHeight = parseInt(contentHeight, 10); // getting numbers-as-strings | |
1230 | |
1231 var bubbleConfig = SimileAjax.Graphics.bubbleConfig; | |
1232 var pngTransparencyClassSuffix = | |
1233 SimileAjax.Graphics.pngIsTranslucent ? "pngTranslucent" : "pngNotTranslucent"; | |
1234 | |
1235 var bubbleWidth = contentWidth + 2 * bubbleConfig.borderGraphicSize; | |
1236 var bubbleHeight = contentHeight + 2 * bubbleConfig.borderGraphicSize; | |
1237 | |
1238 var generatePngSensitiveClass = function(className) { | |
1239 return className + " " + className + "-" + pngTransparencyClassSuffix; | |
1240 }; | |
1241 | |
1242 /* | |
1243 * Render container divs | |
1244 */ | |
1245 var div = document.createElement("div"); | |
1246 div.className = generatePngSensitiveClass(bubbleConfig.containerCSSClass); | |
1247 div.style.width = contentWidth + "px"; | |
1248 div.style.height = contentHeight + "px"; | |
1249 | |
1250 var divInnerContainer = document.createElement("div"); | |
1251 divInnerContainer.className = generatePngSensitiveClass(bubbleConfig.innerContainerCSSClass); | |
1252 div.appendChild(divInnerContainer); | |
1253 | |
1254 /* | |
1255 * Create layer for bubble | |
1256 */ | |
1257 var close = function() { | |
1258 if (!bubble._closed) { | |
1259 document.body.removeChild(bubble._div); | |
1260 bubble._doc = null; | |
1261 bubble._div = null; | |
1262 bubble._content = null; | |
1263 bubble._closed = true; | |
1264 } | |
1265 } | |
1266 var bubble = { _closed: false }; | |
1267 var layer = SimileAjax.WindowManager.pushLayer(close, true, div); | |
1268 bubble._div = div; | |
1269 bubble.close = function() { SimileAjax.WindowManager.popLayer(layer); } | |
1270 | |
1271 /* | |
1272 * Render border graphics | |
1273 */ | |
1274 var createBorder = function(classNameSuffix) { | |
1275 var divBorderGraphic = document.createElement("div"); | |
1276 divBorderGraphic.className = generatePngSensitiveClass(bubbleConfig.borderGraphicCSSClassPrefix + classNameSuffix); | |
1277 divInnerContainer.appendChild(divBorderGraphic); | |
1278 }; | |
1279 createBorder("top-left"); | |
1280 createBorder("top-right"); | |
1281 createBorder("bottom-left"); | |
1282 createBorder("bottom-right"); | |
1283 createBorder("left"); | |
1284 createBorder("right"); | |
1285 createBorder("top"); | |
1286 createBorder("bottom"); | |
1287 | |
1288 /* | |
1289 * Render content | |
1290 */ | |
1291 var divContentContainer = document.createElement("div"); | |
1292 divContentContainer.className = generatePngSensitiveClass(bubbleConfig.contentContainerCSSClass); | |
1293 divInnerContainer.appendChild(divContentContainer); | |
1294 bubble.content = divContentContainer; | |
1295 | |
1296 /* | |
1297 * Render close button | |
1298 */ | |
1299 var divClose = document.createElement("div"); | |
1300 divClose.className = generatePngSensitiveClass(bubbleConfig.closeGraphicCSSClass); | |
1301 divInnerContainer.appendChild(divClose); | |
1302 SimileAjax.WindowManager.registerEventWithObject(divClose, "click", bubble, "close"); | |
1303 | |
1304 (function() { | |
1305 var dims = SimileAjax.Graphics.getWindowDimensions(); | |
1306 var docWidth = dims.w; | |
1307 var docHeight = dims.h; | |
1308 | |
1309 var halfArrowGraphicWidth = Math.ceil(bubbleConfig.arrowGraphicWidth / 2); | |
1310 | |
1311 var createArrow = function(classNameSuffix) { | |
1312 var divArrowGraphic = document.createElement("div"); | |
1313 divArrowGraphic.className = generatePngSensitiveClass(bubbleConfig.arrowGraphicCSSClassPrefix + "point-" + classNameSuffix); | |
1314 divInnerContainer.appendChild(divArrowGraphic); | |
1315 return divArrowGraphic; | |
1316 }; | |
1317 | |
1318 if (pageX - halfArrowGraphicWidth - bubbleConfig.borderGraphicSize - bubbleConfig.extraPadding > 0 && | |
1319 pageX + halfArrowGraphicWidth + bubbleConfig.borderGraphicSize + bubbleConfig.extraPadding < docWidth) { | |
1320 | |
1321 /* | |
1322 * Bubble can be positioned above or below the target point. | |
1323 */ | |
1324 | |
1325 var left = pageX - Math.round(contentWidth / 2); | |
1326 left = pageX < (docWidth / 2) ? | |
1327 Math.max(left, bubbleConfig.extraPadding + bubbleConfig.borderGraphicSize) : | |
1328 Math.min(left, docWidth - bubbleConfig.extraPadding - bubbleConfig.borderGraphicSize - contentWidth); | |
1329 | |
1330 if ((orientation && orientation == "top") || | |
1331 (!orientation && | |
1332 (pageY | |
1333 - bubbleConfig.arrowGraphicTargetOffset | |
1334 - contentHeight | |
1335 - bubbleConfig.borderGraphicSize | |
1336 - bubbleConfig.extraPadding > 0))) { | |
1337 | |
1338 /* | |
1339 * Position bubble above the target point. | |
1340 */ | |
1341 | |
1342 var divArrow = createArrow("down"); | |
1343 divArrow.style.left = (pageX - halfArrowGraphicWidth - left) + "px"; | |
1344 | |
1345 div.style.left = left + "px"; | |
1346 div.style.top = (pageY - bubbleConfig.arrowGraphicTargetOffset - contentHeight) + "px"; | |
1347 | |
1348 return; | |
1349 } else if ((orientation && orientation == "bottom") || | |
1350 (!orientation && | |
1351 (pageY | |
1352 + bubbleConfig.arrowGraphicTargetOffset | |
1353 + contentHeight | |
1354 + bubbleConfig.borderGraphicSize | |
1355 + bubbleConfig.extraPadding < docHeight))) { | |
1356 | |
1357 /* | |
1358 * Position bubble below the target point. | |
1359 */ | |
1360 | |
1361 var divArrow = createArrow("up"); | |
1362 divArrow.style.left = (pageX - halfArrowGraphicWidth - left) + "px"; | |
1363 | |
1364 div.style.left = left + "px"; | |
1365 div.style.top = (pageY + bubbleConfig.arrowGraphicTargetOffset) + "px"; | |
1366 | |
1367 return; | |
1368 } | |
1369 } | |
1370 | |
1371 var top = pageY - Math.round(contentHeight / 2); | |
1372 top = pageY < (docHeight / 2) ? | |
1373 Math.max(top, bubbleConfig.extraPadding + bubbleConfig.borderGraphicSize) : | |
1374 Math.min(top, docHeight - bubbleConfig.extraPadding - bubbleConfig.borderGraphicSize - contentHeight); | |
1375 | |
1376 if ((orientation && orientation == "left") || | |
1377 (!orientation && | |
1378 (pageX | |
1379 - bubbleConfig.arrowGraphicTargetOffset | |
1380 - contentWidth | |
1381 - bubbleConfig.borderGraphicSize | |
1382 - bubbleConfig.extraPadding > 0))) { | |
1383 | |
1384 /* | |
1385 * Position bubble left of the target point. | |
1386 */ | |
1387 | |
1388 var divArrow = createArrow("right"); | |
1389 divArrow.style.top = (pageY - halfArrowGraphicWidth - top) + "px"; | |
1390 | |
1391 div.style.top = top + "px"; | |
1392 div.style.left = (pageX - bubbleConfig.arrowGraphicTargetOffset - contentWidth) + "px"; | |
1393 } else { | |
1394 | |
1395 /* | |
1396 * Position bubble right of the target point, as the last resort. | |
1397 */ | |
1398 | |
1399 var divArrow = createArrow("left"); | |
1400 divArrow.style.top = (pageY - halfArrowGraphicWidth - top) + "px"; | |
1401 | |
1402 div.style.top = top + "px"; | |
1403 div.style.left = (pageX + bubbleConfig.arrowGraphicTargetOffset) + "px"; | |
1404 } | |
1405 })(); | |
1406 | |
1407 document.body.appendChild(div); | |
1408 | |
1409 return bubble; | |
1410 }; | |
1411 | |
1412 SimileAjax.Graphics.getWindowDimensions = function() { | |
1413 if (typeof window.innerHeight == 'number') { | |
1414 return { w:window.innerWidth, h:window.innerHeight }; // Non-IE | |
1415 } else if (document.documentElement && document.documentElement.clientHeight) { | |
1416 return { // IE6+, in "standards compliant mode" | |
1417 w:document.documentElement.clientWidth, | |
1418 h:document.documentElement.clientHeight | |
1419 }; | |
1420 } else if (document.body && document.body.clientHeight) { | |
1421 return { // IE 4 compatible | |
1422 w:document.body.clientWidth, | |
1423 h:document.body.clientHeight | |
1424 }; | |
1425 } | |
1426 }; | |
1427 | |
1428 | |
1429 /** | |
1430 * Creates a floating, rounded message bubble in the center of the window for | |
1431 * displaying modal information, e.g. "Loading..." | |
1432 * | |
1433 * @param {Document} doc the root document for the page to render on | |
1434 * @param {Object} an object with two properties, contentDiv and containerDiv, | |
1435 * consisting of the newly created DOM elements | |
1436 */ | |
1437 SimileAjax.Graphics.createMessageBubble = function(doc) { | |
1438 var containerDiv = doc.createElement("div"); | |
1439 if (SimileAjax.Graphics.pngIsTranslucent) { | |
1440 var topDiv = doc.createElement("div"); | |
1441 topDiv.style.height = "33px"; | |
1442 topDiv.style.background = "url(" + SimileAjax.urlPrefix + "images/message-top-left.png) top left no-repeat"; | |
1443 topDiv.style.paddingLeft = "44px"; | |
1444 containerDiv.appendChild(topDiv); | |
1445 | |
1446 var topRightDiv = doc.createElement("div"); | |
1447 topRightDiv.style.height = "33px"; | |
1448 topRightDiv.style.background = "url(" + SimileAjax.urlPrefix + "images/message-top-right.png) top right no-repeat"; | |
1449 topDiv.appendChild(topRightDiv); | |
1450 | |
1451 var middleDiv = doc.createElement("div"); | |
1452 middleDiv.style.background = "url(" + SimileAjax.urlPrefix + "images/message-left.png) top left repeat-y"; | |
1453 middleDiv.style.paddingLeft = "44px"; | |
1454 containerDiv.appendChild(middleDiv); | |
1455 | |
1456 var middleRightDiv = doc.createElement("div"); | |
1457 middleRightDiv.style.background = "url(" + SimileAjax.urlPrefix + "images/message-right.png) top right repeat-y"; | |
1458 middleRightDiv.style.paddingRight = "44px"; | |
1459 middleDiv.appendChild(middleRightDiv); | |
1460 | |
1461 var contentDiv = doc.createElement("div"); | |
1462 middleRightDiv.appendChild(contentDiv); | |
1463 | |
1464 var bottomDiv = doc.createElement("div"); | |
1465 bottomDiv.style.height = "55px"; | |
1466 bottomDiv.style.background = "url(" + SimileAjax.urlPrefix + "images/message-bottom-left.png) bottom left no-repeat"; | |
1467 bottomDiv.style.paddingLeft = "44px"; | |
1468 containerDiv.appendChild(bottomDiv); | |
1469 | |
1470 var bottomRightDiv = doc.createElement("div"); | |
1471 bottomRightDiv.style.height = "55px"; | |
1472 bottomRightDiv.style.background = "url(" + SimileAjax.urlPrefix + "images/message-bottom-right.png) bottom right no-repeat"; | |
1473 bottomDiv.appendChild(bottomRightDiv); | |
1474 } else { | |
1475 containerDiv.style.border = "2px solid #7777AA"; | |
1476 containerDiv.style.padding = "20px"; | |
1477 containerDiv.style.background = "white"; | |
1478 SimileAjax.Graphics.setOpacity(containerDiv, 90); | |
1479 | |
1480 var contentDiv = doc.createElement("div"); | |
1481 containerDiv.appendChild(contentDiv); | |
1482 } | |
1483 | |
1484 return { | |
1485 containerDiv: containerDiv, | |
1486 contentDiv: contentDiv | |
1487 }; | |
1488 }; | |
1489 | |
1490 /*================================================== | |
1491 * Animation | |
1492 *================================================== | |
1493 */ | |
1494 | |
1495 /** | |
1496 * Creates an animation for a function, and an interval of values. The word | |
1497 * "animation" here is used in the sense of repeatedly calling a function with | |
1498 * a current value from within an interval, and a delta value. | |
1499 * | |
1500 * @param {Function} f a function to be called every 50 milliseconds throughout | |
1501 * the animation duration, of the form f(current, delta), where current is | |
1502 * the current value within the range and delta is the current change. | |
1503 * @param {Number} from a starting value | |
1504 * @param {Number} to an ending value | |
1505 * @param {Number} duration the duration of the animation in milliseconds | |
1506 * @param {Function} [cont] an optional function that is called at the end of | |
1507 * the animation, i.e. a continuation. | |
1508 * @return {SimileAjax.Graphics._Animation} a new animation object | |
1509 */ | |
1510 SimileAjax.Graphics.createAnimation = function(f, from, to, duration, cont) { | |
1511 return new SimileAjax.Graphics._Animation(f, from, to, duration, cont); | |
1512 }; | |
1513 | |
1514 SimileAjax.Graphics._Animation = function(f, from, to, duration, cont) { | |
1515 this.f = f; | |
1516 this.cont = (typeof cont == "function") ? cont : function() {}; | |
1517 | |
1518 this.from = from; | |
1519 this.to = to; | |
1520 this.current = from; | |
1521 | |
1522 this.duration = duration; | |
1523 this.start = new Date().getTime(); | |
1524 this.timePassed = 0; | |
1525 }; | |
1526 | |
1527 /** | |
1528 * Runs this animation. | |
1529 */ | |
1530 SimileAjax.Graphics._Animation.prototype.run = function() { | |
1531 var a = this; | |
1532 window.setTimeout(function() { a.step(); }, 50); | |
1533 }; | |
1534 | |
1535 /** | |
1536 * Increments this animation by one step, and then continues the animation with | |
1537 * <code>run()</code>. | |
1538 */ | |
1539 SimileAjax.Graphics._Animation.prototype.step = function() { | |
1540 this.timePassed += 50; | |
1541 | |
1542 var timePassedFraction = this.timePassed / this.duration; | |
1543 var parameterFraction = -Math.cos(timePassedFraction * Math.PI) / 2 + 0.5; | |
1544 var current = parameterFraction * (this.to - this.from) + this.from; | |
1545 | |
1546 try { | |
1547 this.f(current, current - this.current); | |
1548 } catch (e) { | |
1549 } | |
1550 this.current = current; | |
1551 | |
1552 if (this.timePassed < this.duration) { | |
1553 this.run(); | |
1554 } else { | |
1555 this.f(this.to, 0); | |
1556 this["cont"](); | |
1557 } | |
1558 }; | |
1559 | |
1560 /*================================================== | |
1561 * CopyPasteButton | |
1562 * | |
1563 * Adapted from http://spaces.live.com/editorial/rayozzie/demo/liveclip/liveclipsample/techPreview.html. | |
1564 *================================================== | |
1565 */ | |
1566 | |
1567 /** | |
1568 * Creates a button and textarea for displaying structured data and copying it | |
1569 * to the clipboard. The data is dynamically generated by the given | |
1570 * createDataFunction parameter. | |
1571 * | |
1572 * @param {String} image an image URL to use as the background for the | |
1573 * generated box | |
1574 * @param {Number} width the width in pixels of the generated box | |
1575 * @param {Number} height the height in pixels of the generated box | |
1576 * @param {Function} createDataFunction a function that is called with no | |
1577 * arguments to generate the structured data | |
1578 * @return a new DOM element | |
1579 */ | |
1580 SimileAjax.Graphics.createStructuredDataCopyButton = function(image, width, height, createDataFunction) { | |
1581 var div = document.createElement("div"); | |
1582 div.style.position = "relative"; | |
1583 div.style.display = "inline"; | |
1584 div.style.width = width + "px"; | |
1585 div.style.height = height + "px"; | |
1586 div.style.overflow = "hidden"; | |
1587 div.style.margin = "2px"; | |
1588 | |
1589 if (SimileAjax.Graphics.pngIsTranslucent) { | |
1590 div.style.background = "url(" + image + ") no-repeat"; | |
1591 } else { | |
1592 div.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + image +"', sizingMethod='image')"; | |
1593 } | |
1594 | |
1595 var style; | |
1596 if (SimileAjax.Platform.browser.isIE) { | |
1597 style = "filter:alpha(opacity=0)"; | |
1598 } else { | |
1599 style = "opacity: 0"; | |
1600 } | |
1601 div.innerHTML = "<textarea rows='1' autocomplete='off' value='none' style='" + style + "' />"; | |
1602 | |
1603 var textarea = div.firstChild; | |
1604 textarea.style.width = width + "px"; | |
1605 textarea.style.height = height + "px"; | |
1606 textarea.onmousedown = function(evt) { | |
1607 evt = (evt) ? evt : ((event) ? event : null); | |
1608 if (evt.button == 2) { | |
1609 textarea.value = createDataFunction(); | |
1610 textarea.select(); | |
1611 } | |
1612 }; | |
1613 | |
1614 return div; | |
1615 }; | |
1616 | |
1617 /*================================================== | |
1618 * getWidthHeight | |
1619 *================================================== | |
1620 */ | |
1621 SimileAjax.Graphics.getWidthHeight = function(el) { | |
1622 // RETURNS hash {width: w, height: h} in pixels | |
1623 | |
1624 var w, h; | |
1625 // offsetWidth rounds on FF, so doesn't work for us. | |
1626 // See https://bugzilla.mozilla.org/show_bug.cgi?id=458617 | |
1627 if (el.getBoundingClientRect == null) { | |
1628 // use offsetWidth | |
1629 w = el.offsetWidth; | |
1630 h = el.offsetHeight; | |
1631 } else { | |
1632 // use getBoundingClientRect | |
1633 var rect = el.getBoundingClientRect(); | |
1634 w = Math.ceil(rect.right - rect.left); | |
1635 h = Math.ceil(rect.bottom - rect.top); | |
1636 } | |
1637 return { | |
1638 width: w, | |
1639 height: h | |
1640 }; | |
1641 }; | |
1642 | |
1643 | |
1644 /*================================================== | |
1645 * FontRenderingContext | |
1646 *================================================== | |
1647 */ | |
1648 SimileAjax.Graphics.getFontRenderingContext = function(elmt, width) { | |
1649 return new SimileAjax.Graphics._FontRenderingContext(elmt, width); | |
1650 }; | |
1651 | |
1652 SimileAjax.Graphics._FontRenderingContext = function(elmt, width) { | |
1653 this._elmt = elmt; | |
1654 this._elmt.style.visibility = "hidden"; | |
1655 if (typeof width == "string") { | |
1656 this._elmt.style.width = width; | |
1657 } else if (typeof width == "number") { | |
1658 this._elmt.style.width = width + "px"; | |
1659 } | |
1660 }; | |
1661 | |
1662 SimileAjax.Graphics._FontRenderingContext.prototype.dispose = function() { | |
1663 this._elmt = null; | |
1664 }; | |
1665 | |
1666 SimileAjax.Graphics._FontRenderingContext.prototype.update = function() { | |
1667 this._elmt.innerHTML = "A"; | |
1668 this._lineHeight = this._elmt.offsetHeight; | |
1669 }; | |
1670 | |
1671 SimileAjax.Graphics._FontRenderingContext.prototype.computeSize = function(text, className) { | |
1672 // className arg is optional | |
1673 var el = this._elmt; | |
1674 el.innerHTML = text; | |
1675 el.className = className === undefined ? '' : className; | |
1676 var wh = SimileAjax.Graphics.getWidthHeight(el); | |
1677 el.className = ''; // reset for the next guy | |
1678 | |
1679 return wh; | |
1680 }; | |
1681 | |
1682 SimileAjax.Graphics._FontRenderingContext.prototype.getLineHeight = function() { | |
1683 return this._lineHeight; | |
1684 }; | |
1685 | |
1686 /** | |
1687 * @fileOverview A collection of date/time utility functions | |
1688 * @name SimileAjax.DateTime | |
1689 */ | |
1690 | |
1691 SimileAjax.DateTime = new Object(); | |
1692 | |
1693 SimileAjax.DateTime.MILLISECOND = 0; | |
1694 SimileAjax.DateTime.SECOND = 1; | |
1695 SimileAjax.DateTime.MINUTE = 2; | |
1696 SimileAjax.DateTime.HOUR = 3; | |
1697 SimileAjax.DateTime.DAY = 4; | |
1698 SimileAjax.DateTime.WEEK = 5; | |
1699 SimileAjax.DateTime.MONTH = 6; | |
1700 SimileAjax.DateTime.YEAR = 7; | |
1701 SimileAjax.DateTime.DECADE = 8; | |
1702 SimileAjax.DateTime.CENTURY = 9; | |
1703 SimileAjax.DateTime.MILLENNIUM = 10; | |
1704 | |
1705 SimileAjax.DateTime.EPOCH = -1; | |
1706 SimileAjax.DateTime.ERA = -2; | |
1707 | |
1708 /** | |
1709 * An array of unit lengths, expressed in milliseconds, of various lengths of | |
1710 * time. The array indices are predefined and stored as properties of the | |
1711 * SimileAjax.DateTime object, e.g. SimileAjax.DateTime.YEAR. | |
1712 * @type Array | |
1713 */ | |
1714 SimileAjax.DateTime.gregorianUnitLengths = []; | |
1715 (function() { | |
1716 var d = SimileAjax.DateTime; | |
1717 var a = d.gregorianUnitLengths; | |
1718 | |
1719 a[d.MILLISECOND] = 1; | |
1720 a[d.SECOND] = 1000; | |
1721 a[d.MINUTE] = a[d.SECOND] * 60; | |
1722 a[d.HOUR] = a[d.MINUTE] * 60; | |
1723 a[d.DAY] = a[d.HOUR] * 24; | |
1724 a[d.WEEK] = a[d.DAY] * 7; | |
1725 a[d.MONTH] = a[d.DAY] * 31; | |
1726 a[d.YEAR] = a[d.DAY] * 365; | |
1727 a[d.DECADE] = a[d.YEAR] * 10; | |
1728 a[d.CENTURY] = a[d.YEAR] * 100; | |
1729 a[d.MILLENNIUM] = a[d.YEAR] * 1000; | |
1730 })(); | |
1731 | |
1732 SimileAjax.DateTime._dateRegexp = new RegExp( | |
1733 "^(-?)([0-9]{4})(" + [ | |
1734 "(-?([0-9]{2})(-?([0-9]{2}))?)", // -month-dayOfMonth | |
1735 "(-?([0-9]{3}))", // -dayOfYear | |
1736 "(-?W([0-9]{2})(-?([1-7]))?)" // -Wweek-dayOfWeek | |
1737 ].join("|") + ")?$" | |
1738 ); | |
1739 SimileAjax.DateTime._timezoneRegexp = new RegExp( | |
1740 "Z|(([-+])([0-9]{2})(:?([0-9]{2}))?)$" | |
1741 ); | |
1742 SimileAjax.DateTime._timeRegexp = new RegExp( | |
1743 "^([0-9]{2})(:?([0-9]{2})(:?([0-9]{2})(\.([0-9]+))?)?)?$" | |
1744 ); | |
1745 | |
1746 /** | |
1747 * Takes a date object and a string containing an ISO 8601 date and sets the | |
1748 * the date using information parsed from the string. Note that this method | |
1749 * does not parse any time information. | |
1750 * | |
1751 * @param {Date} dateObject the date object to modify | |
1752 * @param {String} string an ISO 8601 string to parse | |
1753 * @return {Date} the modified date object | |
1754 */ | |
1755 SimileAjax.DateTime.setIso8601Date = function(dateObject, string) { | |
1756 /* | |
1757 * This function has been adapted from dojo.date, v.0.3.0 | |
1758 * http://dojotoolkit.org/. | |
1759 */ | |
1760 | |
1761 var d = string.match(SimileAjax.DateTime._dateRegexp); | |
1762 if(!d) { | |
1763 throw new Error("Invalid date string: " + string); | |
1764 } | |
1765 | |
1766 var sign = (d[1] == "-") ? -1 : 1; // BC or AD | |
1767 var year = sign * d[2]; | |
1768 var month = d[5]; | |
1769 var date = d[7]; | |
1770 var dayofyear = d[9]; | |
1771 var week = d[11]; | |
1772 var dayofweek = (d[13]) ? d[13] : 1; | |
1773 | |
1774 dateObject.setUTCFullYear(year); | |
1775 if (dayofyear) { | |
1776 dateObject.setUTCMonth(0); | |
1777 dateObject.setUTCDate(Number(dayofyear)); | |
1778 } else if (week) { | |
1779 dateObject.setUTCMonth(0); | |
1780 dateObject.setUTCDate(1); | |
1781 var gd = dateObject.getUTCDay(); | |
1782 var day = (gd) ? gd : 7; | |
1783 var offset = Number(dayofweek) + (7 * Number(week)); | |
1784 | |
1785 if (day <= 4) { | |
1786 dateObject.setUTCDate(offset + 1 - day); | |
1787 } else { | |
1788 dateObject.setUTCDate(offset + 8 - day); | |
1789 } | |
1790 } else { | |
1791 if (month) { | |
1792 dateObject.setUTCDate(1); | |
1793 dateObject.setUTCMonth(month - 1); | |
1794 } | |
1795 if (date) { | |
1796 dateObject.setUTCDate(date); | |
1797 } | |
1798 } | |
1799 | |
1800 return dateObject; | |
1801 }; | |
1802 | |
1803 /** | |
1804 * Takes a date object and a string containing an ISO 8601 time and sets the | |
1805 * the time using information parsed from the string. Note that this method | |
1806 * does not parse any date information. | |
1807 * | |
1808 * @param {Date} dateObject the date object to modify | |
1809 * @param {String} string an ISO 8601 string to parse | |
1810 * @return {Date} the modified date object | |
1811 */ | |
1812 SimileAjax.DateTime.setIso8601Time = function (dateObject, string) { | |
1813 /* | |
1814 * This function has been adapted from dojo.date, v.0.3.0 | |
1815 * http://dojotoolkit.org/. | |
1816 */ | |
1817 | |
1818 var d = string.match(SimileAjax.DateTime._timeRegexp); | |
1819 if(!d) { | |
1820 SimileAjax.Debug.warn("Invalid time string: " + string); | |
1821 return false; | |
1822 } | |
1823 var hours = d[1]; | |
1824 var mins = Number((d[3]) ? d[3] : 0); | |
1825 var secs = (d[5]) ? d[5] : 0; | |
1826 var ms = d[7] ? (Number("0." + d[7]) * 1000) : 0; | |
1827 | |
1828 dateObject.setUTCHours(hours); | |
1829 dateObject.setUTCMinutes(mins); | |
1830 dateObject.setUTCSeconds(secs); | |
1831 dateObject.setUTCMilliseconds(ms); | |
1832 | |
1833 return dateObject; | |
1834 }; | |
1835 | |
1836 /** | |
1837 * The timezone offset in minutes in the user's browser. | |
1838 * @type Number | |
1839 */ | |
1840 SimileAjax.DateTime.timezoneOffset = new Date().getTimezoneOffset(); | |
1841 | |
1842 /** | |
1843 * Takes a date object and a string containing an ISO 8601 date and time and | |
1844 * sets the date object using information parsed from the string. | |
1845 * | |
1846 * @param {Date} dateObject the date object to modify | |
1847 * @param {String} string an ISO 8601 string to parse | |
1848 * @return {Date} the modified date object | |
1849 */ | |
1850 SimileAjax.DateTime.setIso8601 = function (dateObject, string){ | |
1851 /* | |
1852 * This function has been adapted from dojo.date, v.0.3.0 | |
1853 * http://dojotoolkit.org/. | |
1854 */ | |
1855 | |
1856 var offset = null; | |
1857 var comps = (string.indexOf("T") == -1) ? string.split(" ") : string.split("T"); | |
1858 | |
1859 SimileAjax.DateTime.setIso8601Date(dateObject, comps[0]); | |
1860 if (comps.length == 2) { | |
1861 // first strip timezone info from the end | |
1862 var d = comps[1].match(SimileAjax.DateTime._timezoneRegexp); | |
1863 if (d) { | |
1864 if (d[0] == 'Z') { | |
1865 offset = 0; | |
1866 } else { | |
1867 offset = (Number(d[3]) * 60) + Number(d[5]); | |
1868 offset *= ((d[2] == '-') ? 1 : -1); | |
1869 } | |
1870 comps[1] = comps[1].substr(0, comps[1].length - d[0].length); | |
1871 } | |
1872 | |
1873 SimileAjax.DateTime.setIso8601Time(dateObject, comps[1]); | |
1874 } | |
1875 if (offset == null) { | |
1876 offset = dateObject.getTimezoneOffset(); // local time zone if no tz info | |
1877 } | |
1878 dateObject.setTime(dateObject.getTime() + offset * 60000); | |
1879 | |
1880 return dateObject; | |
1881 }; | |
1882 | |
1883 /** | |
1884 * Takes a string containing an ISO 8601 date and returns a newly instantiated | |
1885 * date object with the parsed date and time information from the string. | |
1886 * | |
1887 * @param {String} string an ISO 8601 string to parse | |
1888 * @return {Date} a new date object created from the string | |
1889 */ | |
1890 SimileAjax.DateTime.parseIso8601DateTime = function (string) { | |
1891 try { | |
1892 return SimileAjax.DateTime.setIso8601(new Date(0), string); | |
1893 } catch (e) { | |
1894 return null; | |
1895 } | |
1896 }; | |
1897 | |
1898 /** | |
1899 * Takes a string containing a Gregorian date and time and returns a newly | |
1900 * instantiated date object with the parsed date and time information from the | |
1901 * string. If the param is actually an instance of Date instead of a string, | |
1902 * simply returns the given date instead. | |
1903 * | |
1904 * @param {Object} o an object, to either return or parse as a string | |
1905 * @return {Date} the date object | |
1906 */ | |
1907 SimileAjax.DateTime.parseGregorianDateTime = function(o) { | |
1908 if (o == null) { | |
1909 return null; | |
1910 } else if (o instanceof Date) { | |
1911 return o; | |
1912 } | |
1913 | |
1914 var s = o.toString(); | |
1915 if (s.length > 0 && s.length < 8) { | |
1916 var space = s.indexOf(" "); | |
1917 if (space > 0) { | |
1918 var year = parseInt(s.substr(0, space)); | |
1919 var suffix = s.substr(space + 1); | |
1920 if (suffix.toLowerCase() == "bc") { | |
1921 year = 1 - year; | |
1922 } | |
1923 } else { | |
1924 var year = parseInt(s); | |
1925 } | |
1926 | |
1927 var d = new Date(0); | |
1928 d.setUTCFullYear(year); | |
1929 | |
1930 return d; | |
1931 } | |
1932 | |
1933 try { | |
1934 return new Date(Date.parse(s)); | |
1935 } catch (e) { | |
1936 return null; | |
1937 } | |
1938 }; | |
1939 | |
1940 /** | |
1941 * Rounds date objects down to the nearest interval or multiple of an interval. | |
1942 * This method modifies the given date object, converting it to the given | |
1943 * timezone if specified. | |
1944 * | |
1945 * @param {Date} date the date object to round | |
1946 * @param {Number} intervalUnit a constant, integer index specifying an | |
1947 * interval, e.g. SimileAjax.DateTime.HOUR | |
1948 * @param {Number} timeZone a timezone shift, given in hours | |
1949 * @param {Number} multiple a multiple of the interval to round by | |
1950 * @param {Number} firstDayOfWeek an integer specifying the first day of the | |
1951 * week, 0 corresponds to Sunday, 1 to Monday, etc. | |
1952 */ | |
1953 SimileAjax.DateTime.roundDownToInterval = function(date, intervalUnit, timeZone, multiple, firstDayOfWeek) { | |
1954 var timeShift = timeZone * | |
1955 SimileAjax.DateTime.gregorianUnitLengths[SimileAjax.DateTime.HOUR]; | |
1956 | |
1957 var date2 = new Date(date.getTime() + timeShift); | |
1958 var clearInDay = function(d) { | |
1959 d.setUTCMilliseconds(0); | |
1960 d.setUTCSeconds(0); | |
1961 d.setUTCMinutes(0); | |
1962 d.setUTCHours(0); | |
1963 }; | |
1964 var clearInYear = function(d) { | |
1965 clearInDay(d); | |
1966 d.setUTCDate(1); | |
1967 d.setUTCMonth(0); | |
1968 }; | |
1969 | |
1970 switch(intervalUnit) { | |
1971 case SimileAjax.DateTime.MILLISECOND: | |
1972 var x = date2.getUTCMilliseconds(); | |
1973 date2.setUTCMilliseconds(x - (x % multiple)); | |
1974 break; | |
1975 case SimileAjax.DateTime.SECOND: | |
1976 date2.setUTCMilliseconds(0); | |
1977 | |
1978 var x = date2.getUTCSeconds(); | |
1979 date2.setUTCSeconds(x - (x % multiple)); | |
1980 break; | |
1981 case SimileAjax.DateTime.MINUTE: | |
1982 date2.setUTCMilliseconds(0); | |
1983 date2.setUTCSeconds(0); | |
1984 | |
1985 var x = date2.getUTCMinutes(); | |
1986 date2.setTime(date2.getTime() - | |
1987 (x % multiple) * SimileAjax.DateTime.gregorianUnitLengths[SimileAjax.DateTime.MINUTE]); | |
1988 break; | |
1989 case SimileAjax.DateTime.HOUR: | |
1990 date2.setUTCMilliseconds(0); | |
1991 date2.setUTCSeconds(0); | |
1992 date2.setUTCMinutes(0); | |
1993 | |
1994 var x = date2.getUTCHours(); | |
1995 date2.setUTCHours(x - (x % multiple)); | |
1996 break; | |
1997 case SimileAjax.DateTime.DAY: | |
1998 clearInDay(date2); | |
1999 break; | |
2000 case SimileAjax.DateTime.WEEK: | |
2001 clearInDay(date2); | |
2002 var d = (date2.getUTCDay() + 7 - firstDayOfWeek) % 7; | |
2003 date2.setTime(date2.getTime() - | |
2004 d * SimileAjax.DateTime.gregorianUnitLengths[SimileAjax.DateTime.DAY]); | |
2005 break; | |
2006 case SimileAjax.DateTime.MONTH: | |
2007 clearInDay(date2); | |
2008 date2.setUTCDate(1); | |
2009 | |
2010 var x = date2.getUTCMonth(); | |
2011 date2.setUTCMonth(x - (x % multiple)); | |
2012 break; | |
2013 case SimileAjax.DateTime.YEAR: | |
2014 clearInYear(date2); | |
2015 | |
2016 var x = date2.getUTCFullYear(); | |
2017 date2.setUTCFullYear(x - (x % multiple)); | |
2018 break; | |
2019 case SimileAjax.DateTime.DECADE: | |
2020 clearInYear(date2); | |
2021 date2.setUTCFullYear(Math.floor(date2.getUTCFullYear() / 10) * 10); | |
2022 break; | |
2023 case SimileAjax.DateTime.CENTURY: | |
2024 clearInYear(date2); | |
2025 date2.setUTCFullYear(Math.floor(date2.getUTCFullYear() / 100) * 100); | |
2026 break; | |
2027 case SimileAjax.DateTime.MILLENNIUM: | |
2028 clearInYear(date2); | |
2029 date2.setUTCFullYear(Math.floor(date2.getUTCFullYear() / 1000) * 1000); | |
2030 break; | |
2031 } | |
2032 date.setTime(date2.getTime() - timeShift); | |
2033 }; | |
2034 | |
2035 /** | |
2036 * Rounds date objects up to the nearest interval or multiple of an interval. | |
2037 * This method modifies the given date object, converting it to the given | |
2038 * timezone if specified. | |
2039 * | |
2040 * @param {Date} date the date object to round | |
2041 * @param {Number} intervalUnit a constant, integer index specifying an | |
2042 * interval, e.g. SimileAjax.DateTime.HOUR | |
2043 * @param {Number} timeZone a timezone shift, given in hours | |
2044 * @param {Number} multiple a multiple of the interval to round by | |
2045 * @param {Number} firstDayOfWeek an integer specifying the first day of the | |
2046 * week, 0 corresponds to Sunday, 1 to Monday, etc. | |
2047 * @see SimileAjax.DateTime.roundDownToInterval | |
2048 */ | |
2049 SimileAjax.DateTime.roundUpToInterval = function(date, intervalUnit, timeZone, multiple, firstDayOfWeek) { | |
2050 var originalTime = date.getTime(); | |
2051 SimileAjax.DateTime.roundDownToInterval(date, intervalUnit, timeZone, multiple, firstDayOfWeek); | |
2052 if (date.getTime() < originalTime) { | |
2053 date.setTime(date.getTime() + | |
2054 SimileAjax.DateTime.gregorianUnitLengths[intervalUnit] * multiple); | |
2055 } | |
2056 }; | |
2057 | |
2058 /** | |
2059 * Increments a date object by a specified interval, taking into | |
2060 * consideration the timezone. | |
2061 * | |
2062 * @param {Date} date the date object to increment | |
2063 * @param {Number} intervalUnit a constant, integer index specifying an | |
2064 * interval, e.g. SimileAjax.DateTime.HOUR | |
2065 * @param {Number} timeZone the timezone offset in hours | |
2066 */ | |
2067 SimileAjax.DateTime.incrementByInterval = function(date, intervalUnit, timeZone) { | |
2068 timeZone = (typeof timeZone == 'undefined') ? 0 : timeZone; | |
2069 | |
2070 var timeShift = timeZone * | |
2071 SimileAjax.DateTime.gregorianUnitLengths[SimileAjax.DateTime.HOUR]; | |
2072 | |
2073 var date2 = new Date(date.getTime() + timeShift); | |
2074 | |
2075 switch(intervalUnit) { | |
2076 case SimileAjax.DateTime.MILLISECOND: | |
2077 date2.setTime(date2.getTime() + 1) | |
2078 break; | |
2079 case SimileAjax.DateTime.SECOND: | |
2080 date2.setTime(date2.getTime() + 1000); | |
2081 break; | |
2082 case SimileAjax.DateTime.MINUTE: | |
2083 date2.setTime(date2.getTime() + | |
2084 SimileAjax.DateTime.gregorianUnitLengths[SimileAjax.DateTime.MINUTE]); | |
2085 break; | |
2086 case SimileAjax.DateTime.HOUR: | |
2087 date2.setTime(date2.getTime() + | |
2088 SimileAjax.DateTime.gregorianUnitLengths[SimileAjax.DateTime.HOUR]); | |
2089 break; | |
2090 case SimileAjax.DateTime.DAY: | |
2091 date2.setUTCDate(date2.getUTCDate() + 1); | |
2092 break; | |
2093 case SimileAjax.DateTime.WEEK: | |
2094 date2.setUTCDate(date2.getUTCDate() + 7); | |
2095 break; | |
2096 case SimileAjax.DateTime.MONTH: | |
2097 date2.setUTCMonth(date2.getUTCMonth() + 1); | |
2098 break; | |
2099 case SimileAjax.DateTime.YEAR: | |
2100 date2.setUTCFullYear(date2.getUTCFullYear() + 1); | |
2101 break; | |
2102 case SimileAjax.DateTime.DECADE: | |
2103 date2.setUTCFullYear(date2.getUTCFullYear() + 10); | |
2104 break; | |
2105 case SimileAjax.DateTime.CENTURY: | |
2106 date2.setUTCFullYear(date2.getUTCFullYear() + 100); | |
2107 break; | |
2108 case SimileAjax.DateTime.MILLENNIUM: | |
2109 date2.setUTCFullYear(date2.getUTCFullYear() + 1000); | |
2110 break; | |
2111 } | |
2112 | |
2113 date.setTime(date2.getTime() - timeShift); | |
2114 }; | |
2115 | |
2116 /** | |
2117 * Returns a new date object with the given time offset removed. | |
2118 * | |
2119 * @param {Date} date the starting date | |
2120 * @param {Number} timeZone a timezone specified in an hour offset to remove | |
2121 * @return {Date} a new date object with the offset removed | |
2122 */ | |
2123 SimileAjax.DateTime.removeTimeZoneOffset = function(date, timeZone) { | |
2124 return new Date(date.getTime() + | |
2125 timeZone * SimileAjax.DateTime.gregorianUnitLengths[SimileAjax.DateTime.HOUR]); | |
2126 }; | |
2127 | |
2128 /** | |
2129 * Returns the timezone of the user's browser. | |
2130 * | |
2131 * @return {Number} the timezone in the user's locale in hours | |
2132 */ | |
2133 SimileAjax.DateTime.getTimezone = function() { | |
2134 var d = new Date().getTimezoneOffset(); | |
2135 return d / -60; | |
2136 }; | |
2137 /*================================================== | |
2138 * String Utility Functions and Constants | |
2139 *================================================== | |
2140 */ | |
2141 | |
2142 String.prototype.trim = function() { | |
2143 return this.replace(/^\s+|\s+$/g, ''); | |
2144 }; | |
2145 | |
2146 String.prototype.startsWith = function(prefix) { | |
2147 return this.length >= prefix.length && this.substr(0, prefix.length) == prefix; | |
2148 }; | |
2149 | |
2150 String.prototype.endsWith = function(suffix) { | |
2151 return this.length >= suffix.length && this.substr(this.length - suffix.length) == suffix; | |
2152 }; | |
2153 | |
2154 String.substitute = function(s, objects) { | |
2155 var result = ""; | |
2156 var start = 0; | |
2157 while (start < s.length - 1) { | |
2158 var percent = s.indexOf("%", start); | |
2159 if (percent < 0 || percent == s.length - 1) { | |
2160 break; | |
2161 } else if (percent > start && s.charAt(percent - 1) == "\\") { | |
2162 result += s.substring(start, percent - 1) + "%"; | |
2163 start = percent + 1; | |
2164 } else { | |
2165 var n = parseInt(s.charAt(percent + 1)); | |
2166 if (isNaN(n) || n >= objects.length) { | |
2167 result += s.substring(start, percent + 2); | |
2168 } else { | |
2169 result += s.substring(start, percent) + objects[n].toString(); | |
2170 } | |
2171 start = percent + 2; | |
2172 } | |
2173 } | |
2174 | |
2175 if (start < s.length) { | |
2176 result += s.substring(start); | |
2177 } | |
2178 return result; | |
2179 }; | |
2180 /*================================================== | |
2181 * HTML Utility Functions | |
2182 *================================================== | |
2183 */ | |
2184 | |
2185 SimileAjax.HTML = new Object(); | |
2186 | |
2187 SimileAjax.HTML._e2uHash = {}; | |
2188 (function() { | |
2189 var e2uHash = SimileAjax.HTML._e2uHash; | |
2190 e2uHash['nbsp']= '\u00A0[space]'; | |
2191 e2uHash['iexcl']= '\u00A1'; | |
2192 e2uHash['cent']= '\u00A2'; | |
2193 e2uHash['pound']= '\u00A3'; | |
2194 e2uHash['curren']= '\u00A4'; | |
2195 e2uHash['yen']= '\u00A5'; | |
2196 e2uHash['brvbar']= '\u00A6'; | |
2197 e2uHash['sect']= '\u00A7'; | |
2198 e2uHash['uml']= '\u00A8'; | |
2199 e2uHash['copy']= '\u00A9'; | |
2200 e2uHash['ordf']= '\u00AA'; | |
2201 e2uHash['laquo']= '\u00AB'; | |
2202 e2uHash['not']= '\u00AC'; | |
2203 e2uHash['shy']= '\u00AD'; | |
2204 e2uHash['reg']= '\u00AE'; | |
2205 e2uHash['macr']= '\u00AF'; | |
2206 e2uHash['deg']= '\u00B0'; | |
2207 e2uHash['plusmn']= '\u00B1'; | |
2208 e2uHash['sup2']= '\u00B2'; | |
2209 e2uHash['sup3']= '\u00B3'; | |
2210 e2uHash['acute']= '\u00B4'; | |
2211 e2uHash['micro']= '\u00B5'; | |
2212 e2uHash['para']= '\u00B6'; | |
2213 e2uHash['middot']= '\u00B7'; | |
2214 e2uHash['cedil']= '\u00B8'; | |
2215 e2uHash['sup1']= '\u00B9'; | |
2216 e2uHash['ordm']= '\u00BA'; | |
2217 e2uHash['raquo']= '\u00BB'; | |
2218 e2uHash['frac14']= '\u00BC'; | |
2219 e2uHash['frac12']= '\u00BD'; | |
2220 e2uHash['frac34']= '\u00BE'; | |
2221 e2uHash['iquest']= '\u00BF'; | |
2222 e2uHash['Agrave']= '\u00C0'; | |
2223 e2uHash['Aacute']= '\u00C1'; | |
2224 e2uHash['Acirc']= '\u00C2'; | |
2225 e2uHash['Atilde']= '\u00C3'; | |
2226 e2uHash['Auml']= '\u00C4'; | |
2227 e2uHash['Aring']= '\u00C5'; | |
2228 e2uHash['AElig']= '\u00C6'; | |
2229 e2uHash['Ccedil']= '\u00C7'; | |
2230 e2uHash['Egrave']= '\u00C8'; | |
2231 e2uHash['Eacute']= '\u00C9'; | |
2232 e2uHash['Ecirc']= '\u00CA'; | |
2233 e2uHash['Euml']= '\u00CB'; | |
2234 e2uHash['Igrave']= '\u00CC'; | |
2235 e2uHash['Iacute']= '\u00CD'; | |
2236 e2uHash['Icirc']= '\u00CE'; | |
2237 e2uHash['Iuml']= '\u00CF'; | |
2238 e2uHash['ETH']= '\u00D0'; | |
2239 e2uHash['Ntilde']= '\u00D1'; | |
2240 e2uHash['Ograve']= '\u00D2'; | |
2241 e2uHash['Oacute']= '\u00D3'; | |
2242 e2uHash['Ocirc']= '\u00D4'; | |
2243 e2uHash['Otilde']= '\u00D5'; | |
2244 e2uHash['Ouml']= '\u00D6'; | |
2245 e2uHash['times']= '\u00D7'; | |
2246 e2uHash['Oslash']= '\u00D8'; | |
2247 e2uHash['Ugrave']= '\u00D9'; | |
2248 e2uHash['Uacute']= '\u00DA'; | |
2249 e2uHash['Ucirc']= '\u00DB'; | |
2250 e2uHash['Uuml']= '\u00DC'; | |
2251 e2uHash['Yacute']= '\u00DD'; | |
2252 e2uHash['THORN']= '\u00DE'; | |
2253 e2uHash['szlig']= '\u00DF'; | |
2254 e2uHash['agrave']= '\u00E0'; | |
2255 e2uHash['aacute']= '\u00E1'; | |
2256 e2uHash['acirc']= '\u00E2'; | |
2257 e2uHash['atilde']= '\u00E3'; | |
2258 e2uHash['auml']= '\u00E4'; | |
2259 e2uHash['aring']= '\u00E5'; | |
2260 e2uHash['aelig']= '\u00E6'; | |
2261 e2uHash['ccedil']= '\u00E7'; | |
2262 e2uHash['egrave']= '\u00E8'; | |
2263 e2uHash['eacute']= '\u00E9'; | |
2264 e2uHash['ecirc']= '\u00EA'; | |
2265 e2uHash['euml']= '\u00EB'; | |
2266 e2uHash['igrave']= '\u00EC'; | |
2267 e2uHash['iacute']= '\u00ED'; | |
2268 e2uHash['icirc']= '\u00EE'; | |
2269 e2uHash['iuml']= '\u00EF'; | |
2270 e2uHash['eth']= '\u00F0'; | |
2271 e2uHash['ntilde']= '\u00F1'; | |
2272 e2uHash['ograve']= '\u00F2'; | |
2273 e2uHash['oacute']= '\u00F3'; | |
2274 e2uHash['ocirc']= '\u00F4'; | |
2275 e2uHash['otilde']= '\u00F5'; | |
2276 e2uHash['ouml']= '\u00F6'; | |
2277 e2uHash['divide']= '\u00F7'; | |
2278 e2uHash['oslash']= '\u00F8'; | |
2279 e2uHash['ugrave']= '\u00F9'; | |
2280 e2uHash['uacute']= '\u00FA'; | |
2281 e2uHash['ucirc']= '\u00FB'; | |
2282 e2uHash['uuml']= '\u00FC'; | |
2283 e2uHash['yacute']= '\u00FD'; | |
2284 e2uHash['thorn']= '\u00FE'; | |
2285 e2uHash['yuml']= '\u00FF'; | |
2286 e2uHash['quot']= '\u0022'; | |
2287 e2uHash['amp']= '\u0026'; | |
2288 e2uHash['lt']= '\u003C'; | |
2289 e2uHash['gt']= '\u003E'; | |
2290 e2uHash['OElig']= ''; | |
2291 e2uHash['oelig']= '\u0153'; | |
2292 e2uHash['Scaron']= '\u0160'; | |
2293 e2uHash['scaron']= '\u0161'; | |
2294 e2uHash['Yuml']= '\u0178'; | |
2295 e2uHash['circ']= '\u02C6'; | |
2296 e2uHash['tilde']= '\u02DC'; | |
2297 e2uHash['ensp']= '\u2002'; | |
2298 e2uHash['emsp']= '\u2003'; | |
2299 e2uHash['thinsp']= '\u2009'; | |
2300 e2uHash['zwnj']= '\u200C'; | |
2301 e2uHash['zwj']= '\u200D'; | |
2302 e2uHash['lrm']= '\u200E'; | |
2303 e2uHash['rlm']= '\u200F'; | |
2304 e2uHash['ndash']= '\u2013'; | |
2305 e2uHash['mdash']= '\u2014'; | |
2306 e2uHash['lsquo']= '\u2018'; | |
2307 e2uHash['rsquo']= '\u2019'; | |
2308 e2uHash['sbquo']= '\u201A'; | |
2309 e2uHash['ldquo']= '\u201C'; | |
2310 e2uHash['rdquo']= '\u201D'; | |
2311 e2uHash['bdquo']= '\u201E'; | |
2312 e2uHash['dagger']= '\u2020'; | |
2313 e2uHash['Dagger']= '\u2021'; | |
2314 e2uHash['permil']= '\u2030'; | |
2315 e2uHash['lsaquo']= '\u2039'; | |
2316 e2uHash['rsaquo']= '\u203A'; | |
2317 e2uHash['euro']= '\u20AC'; | |
2318 e2uHash['fnof']= '\u0192'; | |
2319 e2uHash['Alpha']= '\u0391'; | |
2320 e2uHash['Beta']= '\u0392'; | |
2321 e2uHash['Gamma']= '\u0393'; | |
2322 e2uHash['Delta']= '\u0394'; | |
2323 e2uHash['Epsilon']= '\u0395'; | |
2324 e2uHash['Zeta']= '\u0396'; | |
2325 e2uHash['Eta']= '\u0397'; | |
2326 e2uHash['Theta']= '\u0398'; | |
2327 e2uHash['Iota']= '\u0399'; | |
2328 e2uHash['Kappa']= '\u039A'; | |
2329 e2uHash['Lambda']= '\u039B'; | |
2330 e2uHash['Mu']= '\u039C'; | |
2331 e2uHash['Nu']= '\u039D'; | |
2332 e2uHash['Xi']= '\u039E'; | |
2333 e2uHash['Omicron']= '\u039F'; | |
2334 e2uHash['Pi']= '\u03A0'; | |
2335 e2uHash['Rho']= '\u03A1'; | |
2336 e2uHash['Sigma']= '\u03A3'; | |
2337 e2uHash['Tau']= '\u03A4'; | |
2338 e2uHash['Upsilon']= '\u03A5'; | |
2339 e2uHash['Phi']= '\u03A6'; | |
2340 e2uHash['Chi']= '\u03A7'; | |
2341 e2uHash['Psi']= '\u03A8'; | |
2342 e2uHash['Omega']= '\u03A9'; | |
2343 e2uHash['alpha']= '\u03B1'; | |
2344 e2uHash['beta']= '\u03B2'; | |
2345 e2uHash['gamma']= '\u03B3'; | |
2346 e2uHash['delta']= '\u03B4'; | |
2347 e2uHash['epsilon']= '\u03B5'; | |
2348 e2uHash['zeta']= '\u03B6'; | |
2349 e2uHash['eta']= '\u03B7'; | |
2350 e2uHash['theta']= '\u03B8'; | |
2351 e2uHash['iota']= '\u03B9'; | |
2352 e2uHash['kappa']= '\u03BA'; | |
2353 e2uHash['lambda']= '\u03BB'; | |
2354 e2uHash['mu']= '\u03BC'; | |
2355 e2uHash['nu']= '\u03BD'; | |
2356 e2uHash['xi']= '\u03BE'; | |
2357 e2uHash['omicron']= '\u03BF'; | |
2358 e2uHash['pi']= '\u03C0'; | |
2359 e2uHash['rho']= '\u03C1'; | |
2360 e2uHash['sigmaf']= '\u03C2'; | |
2361 e2uHash['sigma']= '\u03C3'; | |
2362 e2uHash['tau']= '\u03C4'; | |
2363 e2uHash['upsilon']= '\u03C5'; | |
2364 e2uHash['phi']= '\u03C6'; | |
2365 e2uHash['chi']= '\u03C7'; | |
2366 e2uHash['psi']= '\u03C8'; | |
2367 e2uHash['omega']= '\u03C9'; | |
2368 e2uHash['thetasym']= '\u03D1'; | |
2369 e2uHash['upsih']= '\u03D2'; | |
2370 e2uHash['piv']= '\u03D6'; | |
2371 e2uHash['bull']= '\u2022'; | |
2372 e2uHash['hellip']= '\u2026'; | |
2373 e2uHash['prime']= '\u2032'; | |
2374 e2uHash['Prime']= '\u2033'; | |
2375 e2uHash['oline']= '\u203E'; | |
2376 e2uHash['frasl']= '\u2044'; | |
2377 e2uHash['weierp']= '\u2118'; | |
2378 e2uHash['image']= '\u2111'; | |
2379 e2uHash['real']= '\u211C'; | |
2380 e2uHash['trade']= '\u2122'; | |
2381 e2uHash['alefsym']= '\u2135'; | |
2382 e2uHash['larr']= '\u2190'; | |
2383 e2uHash['uarr']= '\u2191'; | |
2384 e2uHash['rarr']= '\u2192'; | |
2385 e2uHash['darr']= '\u2193'; | |
2386 e2uHash['harr']= '\u2194'; | |
2387 e2uHash['crarr']= '\u21B5'; | |
2388 e2uHash['lArr']= '\u21D0'; | |
2389 e2uHash['uArr']= '\u21D1'; | |
2390 e2uHash['rArr']= '\u21D2'; | |
2391 e2uHash['dArr']= '\u21D3'; | |
2392 e2uHash['hArr']= '\u21D4'; | |
2393 e2uHash['forall']= '\u2200'; | |
2394 e2uHash['part']= '\u2202'; | |
2395 e2uHash['exist']= '\u2203'; | |
2396 e2uHash['empty']= '\u2205'; | |
2397 e2uHash['nabla']= '\u2207'; | |
2398 e2uHash['isin']= '\u2208'; | |
2399 e2uHash['notin']= '\u2209'; | |
2400 e2uHash['ni']= '\u220B'; | |
2401 e2uHash['prod']= '\u220F'; | |
2402 e2uHash['sum']= '\u2211'; | |
2403 e2uHash['minus']= '\u2212'; | |
2404 e2uHash['lowast']= '\u2217'; | |
2405 e2uHash['radic']= '\u221A'; | |
2406 e2uHash['prop']= '\u221D'; | |
2407 e2uHash['infin']= '\u221E'; | |
2408 e2uHash['ang']= '\u2220'; | |
2409 e2uHash['and']= '\u2227'; | |
2410 e2uHash['or']= '\u2228'; | |
2411 e2uHash['cap']= '\u2229'; | |
2412 e2uHash['cup']= '\u222A'; | |
2413 e2uHash['int']= '\u222B'; | |
2414 e2uHash['there4']= '\u2234'; | |
2415 e2uHash['sim']= '\u223C'; | |
2416 e2uHash['cong']= '\u2245'; | |
2417 e2uHash['asymp']= '\u2248'; | |
2418 e2uHash['ne']= '\u2260'; | |
2419 e2uHash['equiv']= '\u2261'; | |
2420 e2uHash['le']= '\u2264'; | |
2421 e2uHash['ge']= '\u2265'; | |
2422 e2uHash['sub']= '\u2282'; | |
2423 e2uHash['sup']= '\u2283'; | |
2424 e2uHash['nsub']= '\u2284'; | |
2425 e2uHash['sube']= '\u2286'; | |
2426 e2uHash['supe']= '\u2287'; | |
2427 e2uHash['oplus']= '\u2295'; | |
2428 e2uHash['otimes']= '\u2297'; | |
2429 e2uHash['perp']= '\u22A5'; | |
2430 e2uHash['sdot']= '\u22C5'; | |
2431 e2uHash['lceil']= '\u2308'; | |
2432 e2uHash['rceil']= '\u2309'; | |
2433 e2uHash['lfloor']= '\u230A'; | |
2434 e2uHash['rfloor']= '\u230B'; | |
2435 e2uHash['lang']= '\u2329'; | |
2436 e2uHash['rang']= '\u232A'; | |
2437 e2uHash['loz']= '\u25CA'; | |
2438 e2uHash['spades']= '\u2660'; | |
2439 e2uHash['clubs']= '\u2663'; | |
2440 e2uHash['hearts']= '\u2665'; | |
2441 e2uHash['diams']= '\u2666'; | |
2442 })(); | |
2443 | |
2444 SimileAjax.HTML.deEntify = function(s) { | |
2445 var e2uHash = SimileAjax.HTML._e2uHash; | |
2446 | |
2447 var re = /&(\w+?);/; | |
2448 while (re.test(s)) { | |
2449 var m = s.match(re); | |
2450 s = s.replace(re, e2uHash[m[1]]); | |
2451 } | |
2452 return s; | |
2453 }; | |
2454 /** | |
2455 * A basic set (in the mathematical sense) data structure | |
2456 * | |
2457 * @constructor | |
2458 * @param {Array or SimileAjax.Set} [a] an initial collection | |
2459 */ | |
2460 SimileAjax.Set = function(a) { | |
2461 this._hash = {}; | |
2462 this._count = 0; | |
2463 | |
2464 if (a instanceof Array) { | |
2465 for (var i = 0; i < a.length; i++) { | |
2466 this.add(a[i]); | |
2467 } | |
2468 } else if (a instanceof SimileAjax.Set) { | |
2469 this.addSet(a); | |
2470 } | |
2471 } | |
2472 | |
2473 /** | |
2474 * Adds the given object to this set, assuming there it does not already exist | |
2475 * | |
2476 * @param {Object} o the object to add | |
2477 * @return {Boolean} true if the object was added, false if not | |
2478 */ | |
2479 SimileAjax.Set.prototype.add = function(o) { | |
2480 if (!(o in this._hash)) { | |
2481 this._hash[o] = true; | |
2482 this._count++; | |
2483 return true; | |
2484 } | |
2485 return false; | |
2486 } | |
2487 | |
2488 /** | |
2489 * Adds each element in the given set to this set | |
2490 * | |
2491 * @param {SimileAjax.Set} set the set of elements to add | |
2492 */ | |
2493 SimileAjax.Set.prototype.addSet = function(set) { | |
2494 for (var o in set._hash) { | |
2495 this.add(o); | |
2496 } | |
2497 } | |
2498 | |
2499 /** | |
2500 * Removes the given element from this set | |
2501 * | |
2502 * @param {Object} o the object to remove | |
2503 * @return {Boolean} true if the object was successfully removed, | |
2504 * false otherwise | |
2505 */ | |
2506 SimileAjax.Set.prototype.remove = function(o) { | |
2507 if (o in this._hash) { | |
2508 delete this._hash[o]; | |
2509 this._count--; | |
2510 return true; | |
2511 } | |
2512 return false; | |
2513 } | |
2514 | |
2515 /** | |
2516 * Removes the elements in this set that correspond to the elements in the | |
2517 * given set | |
2518 * | |
2519 * @param {SimileAjax.Set} set the set of elements to remove | |
2520 */ | |
2521 SimileAjax.Set.prototype.removeSet = function(set) { | |
2522 for (var o in set._hash) { | |
2523 this.remove(o); | |
2524 } | |
2525 } | |
2526 | |
2527 /** | |
2528 * Removes all elements in this set that are not present in the given set, i.e. | |
2529 * modifies this set to the intersection of the two sets | |
2530 * | |
2531 * @param {SimileAjax.Set} set the set to intersect | |
2532 */ | |
2533 SimileAjax.Set.prototype.retainSet = function(set) { | |
2534 for (var o in this._hash) { | |
2535 if (!set.contains(o)) { | |
2536 delete this._hash[o]; | |
2537 this._count--; | |
2538 } | |
2539 } | |
2540 } | |
2541 | |
2542 /** | |
2543 * Returns whether or not the given element exists in this set | |
2544 * | |
2545 * @param {SimileAjax.Set} o the object to test for | |
2546 * @return {Boolean} true if the object is present, false otherwise | |
2547 */ | |
2548 SimileAjax.Set.prototype.contains = function(o) { | |
2549 return (o in this._hash); | |
2550 } | |
2551 | |
2552 /** | |
2553 * Returns the number of elements in this set | |
2554 * | |
2555 * @return {Number} the number of elements in this set | |
2556 */ | |
2557 SimileAjax.Set.prototype.size = function() { | |
2558 return this._count; | |
2559 } | |
2560 | |
2561 /** | |
2562 * Returns the elements of this set as an array | |
2563 * | |
2564 * @return {Array} a new array containing the elements of this set | |
2565 */ | |
2566 SimileAjax.Set.prototype.toArray = function() { | |
2567 var a = []; | |
2568 for (var o in this._hash) { | |
2569 a.push(o); | |
2570 } | |
2571 return a; | |
2572 } | |
2573 | |
2574 /** | |
2575 * Iterates through the elements of this set, order unspecified, executing the | |
2576 * given function on each element until the function returns true | |
2577 * | |
2578 * @param {Function} f a function of form f(element) | |
2579 */ | |
2580 SimileAjax.Set.prototype.visit = function(f) { | |
2581 for (var o in this._hash) { | |
2582 if (f(o) == true) { | |
2583 break; | |
2584 } | |
2585 } | |
2586 } | |
2587 | |
2588 /** | |
2589 * A sorted array data structure | |
2590 * | |
2591 * @constructor | |
2592 */ | |
2593 SimileAjax.SortedArray = function(compare, initialArray) { | |
2594 this._a = (initialArray instanceof Array) ? initialArray : []; | |
2595 this._compare = compare; | |
2596 }; | |
2597 | |
2598 SimileAjax.SortedArray.prototype.add = function(elmt) { | |
2599 var sa = this; | |
2600 var index = this.find(function(elmt2) { | |
2601 return sa._compare(elmt2, elmt); | |
2602 }); | |
2603 | |
2604 if (index < this._a.length) { | |
2605 this._a.splice(index, 0, elmt); | |
2606 } else { | |
2607 this._a.push(elmt); | |
2608 } | |
2609 }; | |
2610 | |
2611 SimileAjax.SortedArray.prototype.remove = function(elmt) { | |
2612 var sa = this; | |
2613 var index = this.find(function(elmt2) { | |
2614 return sa._compare(elmt2, elmt); | |
2615 }); | |
2616 | |
2617 while (index < this._a.length && this._compare(this._a[index], elmt) == 0) { | |
2618 if (this._a[index] == elmt) { | |
2619 this._a.splice(index, 1); | |
2620 return true; | |
2621 } else { | |
2622 index++; | |
2623 } | |
2624 } | |
2625 return false; | |
2626 }; | |
2627 | |
2628 SimileAjax.SortedArray.prototype.removeAll = function() { | |
2629 this._a = []; | |
2630 }; | |
2631 | |
2632 SimileAjax.SortedArray.prototype.elementAt = function(index) { | |
2633 return this._a[index]; | |
2634 }; | |
2635 | |
2636 SimileAjax.SortedArray.prototype.length = function() { | |
2637 return this._a.length; | |
2638 }; | |
2639 | |
2640 SimileAjax.SortedArray.prototype.find = function(compare) { | |
2641 var a = 0; | |
2642 var b = this._a.length; | |
2643 | |
2644 while (a < b) { | |
2645 var mid = Math.floor((a + b) / 2); | |
2646 var c = compare(this._a[mid]); | |
2647 if (mid == a) { | |
2648 return c < 0 ? a+1 : a; | |
2649 } else if (c < 0) { | |
2650 a = mid; | |
2651 } else { | |
2652 b = mid; | |
2653 } | |
2654 } | |
2655 return a; | |
2656 }; | |
2657 | |
2658 SimileAjax.SortedArray.prototype.getFirst = function() { | |
2659 return (this._a.length > 0) ? this._a[0] : null; | |
2660 }; | |
2661 | |
2662 SimileAjax.SortedArray.prototype.getLast = function() { | |
2663 return (this._a.length > 0) ? this._a[this._a.length - 1] : null; | |
2664 }; | |
2665 | |
2666 /*================================================== | |
2667 * Event Index | |
2668 *================================================== | |
2669 */ | |
2670 | |
2671 SimileAjax.EventIndex = function(unit) { | |
2672 var eventIndex = this; | |
2673 | |
2674 this._unit = (unit != null) ? unit : SimileAjax.NativeDateUnit; | |
2675 this._events = new SimileAjax.SortedArray( | |
2676 function(event1, event2) { | |
2677 return eventIndex._unit.compare(event1.getStart(), event2.getStart()); | |
2678 } | |
2679 ); | |
2680 this._idToEvent = {}; | |
2681 this._indexed = true; | |
2682 }; | |
2683 | |
2684 SimileAjax.EventIndex.prototype.getUnit = function() { | |
2685 return this._unit; | |
2686 }; | |
2687 | |
2688 SimileAjax.EventIndex.prototype.getEvent = function(id) { | |
2689 return this._idToEvent[id]; | |
2690 }; | |
2691 | |
2692 SimileAjax.EventIndex.prototype.add = function(evt) { | |
2693 this._events.add(evt); | |
2694 this._idToEvent[evt.getID()] = evt; | |
2695 this._indexed = false; | |
2696 }; | |
2697 | |
2698 SimileAjax.EventIndex.prototype.removeAll = function() { | |
2699 this._events.removeAll(); | |
2700 this._idToEvent = {}; | |
2701 this._indexed = false; | |
2702 }; | |
2703 | |
2704 SimileAjax.EventIndex.prototype.getCount = function() { | |
2705 return this._events.length(); | |
2706 }; | |
2707 | |
2708 SimileAjax.EventIndex.prototype.getIterator = function(startDate, endDate) { | |
2709 if (!this._indexed) { | |
2710 this._index(); | |
2711 } | |
2712 return new SimileAjax.EventIndex._Iterator(this._events, startDate, endDate, this._unit); | |
2713 }; | |
2714 | |
2715 SimileAjax.EventIndex.prototype.getReverseIterator = function(startDate, endDate) { | |
2716 if (!this._indexed) { | |
2717 this._index(); | |
2718 } | |
2719 return new SimileAjax.EventIndex._ReverseIterator(this._events, startDate, endDate, this._unit); | |
2720 }; | |
2721 | |
2722 SimileAjax.EventIndex.prototype.getAllIterator = function() { | |
2723 return new SimileAjax.EventIndex._AllIterator(this._events); | |
2724 }; | |
2725 | |
2726 SimileAjax.EventIndex.prototype.getEarliestDate = function() { | |
2727 var evt = this._events.getFirst(); | |
2728 return (evt == null) ? null : evt.getStart(); | |
2729 }; | |
2730 | |
2731 SimileAjax.EventIndex.prototype.getLatestDate = function() { | |
2732 var evt = this._events.getLast(); | |
2733 if (evt == null) { | |
2734 return null; | |
2735 } | |
2736 | |
2737 if (!this._indexed) { | |
2738 this._index(); | |
2739 } | |
2740 | |
2741 var index = evt._earliestOverlapIndex; | |
2742 var date = this._events.elementAt(index).getEnd(); | |
2743 for (var i = index + 1; i < this._events.length(); i++) { | |
2744 date = this._unit.later(date, this._events.elementAt(i).getEnd()); | |
2745 } | |
2746 | |
2747 return date; | |
2748 }; | |
2749 | |
2750 SimileAjax.EventIndex.prototype._index = function() { | |
2751 /* | |
2752 * For each event, we want to find the earliest preceding | |
2753 * event that overlaps with it, if any. | |
2754 */ | |
2755 | |
2756 var l = this._events.length(); | |
2757 for (var i = 0; i < l; i++) { | |
2758 var evt = this._events.elementAt(i); | |
2759 evt._earliestOverlapIndex = i; | |
2760 } | |
2761 | |
2762 var toIndex = 1; | |
2763 for (var i = 0; i < l; i++) { | |
2764 var evt = this._events.elementAt(i); | |
2765 var end = evt.getEnd(); | |
2766 | |
2767 toIndex = Math.max(toIndex, i + 1); | |
2768 while (toIndex < l) { | |
2769 var evt2 = this._events.elementAt(toIndex); | |
2770 var start2 = evt2.getStart(); | |
2771 | |
2772 if (this._unit.compare(start2, end) < 0) { | |
2773 evt2._earliestOverlapIndex = i; | |
2774 toIndex++; | |
2775 } else { | |
2776 break; | |
2777 } | |
2778 } | |
2779 } | |
2780 this._indexed = true; | |
2781 }; | |
2782 | |
2783 SimileAjax.EventIndex._Iterator = function(events, startDate, endDate, unit) { | |
2784 this._events = events; | |
2785 this._startDate = startDate; | |
2786 this._endDate = endDate; | |
2787 this._unit = unit; | |
2788 | |
2789 this._currentIndex = events.find(function(evt) { | |
2790 return unit.compare(evt.getStart(), startDate); | |
2791 }); | |
2792 if (this._currentIndex - 1 >= 0) { | |
2793 this._currentIndex = this._events.elementAt(this._currentIndex - 1)._earliestOverlapIndex; | |
2794 } | |
2795 this._currentIndex--; | |
2796 | |
2797 this._maxIndex = events.find(function(evt) { | |
2798 return unit.compare(evt.getStart(), endDate); | |
2799 }); | |
2800 | |
2801 this._hasNext = false; | |
2802 this._next = null; | |
2803 this._findNext(); | |
2804 }; | |
2805 | |
2806 SimileAjax.EventIndex._Iterator.prototype = { | |
2807 hasNext: function() { return this._hasNext; }, | |
2808 next: function() { | |
2809 if (this._hasNext) { | |
2810 var next = this._next; | |
2811 this._findNext(); | |
2812 | |
2813 return next; | |
2814 } else { | |
2815 return null; | |
2816 } | |
2817 }, | |
2818 _findNext: function() { | |
2819 var unit = this._unit; | |
2820 while ((++this._currentIndex) < this._maxIndex) { | |
2821 var evt = this._events.elementAt(this._currentIndex); | |
2822 if (unit.compare(evt.getStart(), this._endDate) < 0 && | |
2823 unit.compare(evt.getEnd(), this._startDate) > 0) { | |
2824 | |
2825 this._next = evt; | |
2826 this._hasNext = true; | |
2827 return; | |
2828 } | |
2829 } | |
2830 this._next = null; | |
2831 this._hasNext = false; | |
2832 } | |
2833 }; | |
2834 | |
2835 SimileAjax.EventIndex._ReverseIterator = function(events, startDate, endDate, unit) { | |
2836 this._events = events; | |
2837 this._startDate = startDate; | |
2838 this._endDate = endDate; | |
2839 this._unit = unit; | |
2840 | |
2841 this._minIndex = events.find(function(evt) { | |
2842 return unit.compare(evt.getStart(), startDate); | |
2843 }); | |
2844 if (this._minIndex - 1 >= 0) { | |
2845 this._minIndex = this._events.elementAt(this._minIndex - 1)._earliestOverlapIndex; | |
2846 } | |
2847 | |
2848 this._maxIndex = events.find(function(evt) { | |
2849 return unit.compare(evt.getStart(), endDate); | |
2850 }); | |
2851 | |
2852 this._currentIndex = this._maxIndex; | |
2853 this._hasNext = false; | |
2854 this._next = null; | |
2855 this._findNext(); | |
2856 }; | |
2857 | |
2858 SimileAjax.EventIndex._ReverseIterator.prototype = { | |
2859 hasNext: function() { return this._hasNext; }, | |
2860 next: function() { | |
2861 if (this._hasNext) { | |
2862 var next = this._next; | |
2863 this._findNext(); | |
2864 | |
2865 return next; | |
2866 } else { | |
2867 return null; | |
2868 } | |
2869 }, | |
2870 _findNext: function() { | |
2871 var unit = this._unit; | |
2872 while ((--this._currentIndex) >= this._minIndex) { | |
2873 var evt = this._events.elementAt(this._currentIndex); | |
2874 if (unit.compare(evt.getStart(), this._endDate) < 0 && | |
2875 unit.compare(evt.getEnd(), this._startDate) > 0) { | |
2876 | |
2877 this._next = evt; | |
2878 this._hasNext = true; | |
2879 return; | |
2880 } | |
2881 } | |
2882 this._next = null; | |
2883 this._hasNext = false; | |
2884 } | |
2885 }; | |
2886 | |
2887 SimileAjax.EventIndex._AllIterator = function(events) { | |
2888 this._events = events; | |
2889 this._index = 0; | |
2890 }; | |
2891 | |
2892 SimileAjax.EventIndex._AllIterator.prototype = { | |
2893 hasNext: function() { | |
2894 return this._index < this._events.length(); | |
2895 }, | |
2896 next: function() { | |
2897 return this._index < this._events.length() ? | |
2898 this._events.elementAt(this._index++) : null; | |
2899 } | |
2900 }; | |
2901 /*================================================== | |
2902 * Default Unit | |
2903 *================================================== | |
2904 */ | |
2905 | |
2906 SimileAjax.NativeDateUnit = new Object(); | |
2907 | |
2908 SimileAjax.NativeDateUnit.makeDefaultValue = function() { | |
2909 return new Date(); | |
2910 }; | |
2911 | |
2912 SimileAjax.NativeDateUnit.cloneValue = function(v) { | |
2913 return new Date(v.getTime()); | |
2914 }; | |
2915 | |
2916 SimileAjax.NativeDateUnit.getParser = function(format) { | |
2917 if (typeof format == "string") { | |
2918 format = format.toLowerCase(); | |
2919 } | |
2920 return (format == "iso8601" || format == "iso 8601") ? | |
2921 SimileAjax.DateTime.parseIso8601DateTime : | |
2922 SimileAjax.DateTime.parseGregorianDateTime; | |
2923 }; | |
2924 | |
2925 SimileAjax.NativeDateUnit.parseFromObject = function(o) { | |
2926 return SimileAjax.DateTime.parseGregorianDateTime(o); | |
2927 }; | |
2928 | |
2929 SimileAjax.NativeDateUnit.toNumber = function(v) { | |
2930 return v.getTime(); | |
2931 }; | |
2932 | |
2933 SimileAjax.NativeDateUnit.fromNumber = function(n) { | |
2934 return new Date(n); | |
2935 }; | |
2936 | |
2937 SimileAjax.NativeDateUnit.compare = function(v1, v2) { | |
2938 var n1, n2; | |
2939 if (typeof v1 == "object") { | |
2940 n1 = v1.getTime(); | |
2941 } else { | |
2942 n1 = Number(v1); | |
2943 } | |
2944 if (typeof v2 == "object") { | |
2945 n2 = v2.getTime(); | |
2946 } else { | |
2947 n2 = Number(v2); | |
2948 } | |
2949 | |
2950 return n1 - n2; | |
2951 }; | |
2952 | |
2953 SimileAjax.NativeDateUnit.earlier = function(v1, v2) { | |
2954 return SimileAjax.NativeDateUnit.compare(v1, v2) < 0 ? v1 : v2; | |
2955 }; | |
2956 | |
2957 SimileAjax.NativeDateUnit.later = function(v1, v2) { | |
2958 return SimileAjax.NativeDateUnit.compare(v1, v2) > 0 ? v1 : v2; | |
2959 }; | |
2960 | |
2961 SimileAjax.NativeDateUnit.change = function(v, n) { | |
2962 return new Date(v.getTime() + n); | |
2963 }; | |
2964 | |
2965 /*================================================== | |
2966 * General, miscellaneous SimileAjax stuff | |
2967 *================================================== | |
2968 */ | |
2969 | |
2970 SimileAjax.ListenerQueue = function(wildcardHandlerName) { | |
2971 this._listeners = []; | |
2972 this._wildcardHandlerName = wildcardHandlerName; | |
2973 }; | |
2974 | |
2975 SimileAjax.ListenerQueue.prototype.add = function(listener) { | |
2976 this._listeners.push(listener); | |
2977 }; | |
2978 | |
2979 SimileAjax.ListenerQueue.prototype.remove = function(listener) { | |
2980 var listeners = this._listeners; | |
2981 for (var i = 0; i < listeners.length; i++) { | |
2982 if (listeners[i] == listener) { | |
2983 listeners.splice(i, 1); | |
2984 break; | |
2985 } | |
2986 } | |
2987 }; | |
2988 | |
2989 SimileAjax.ListenerQueue.prototype.fire = function(handlerName, args) { | |
2990 var listeners = [].concat(this._listeners); | |
2991 for (var i = 0; i < listeners.length; i++) { | |
2992 var listener = listeners[i]; | |
2993 if (handlerName in listener) { | |
2994 try { | |
2995 listener[handlerName].apply(listener, args); | |
2996 } catch (e) { | |
2997 SimileAjax.Debug.exception("Error firing event of name " + handlerName, e); | |
2998 } | |
2999 } else if (this._wildcardHandlerName != null && | |
3000 this._wildcardHandlerName in listener) { | |
3001 try { | |
3002 listener[this._wildcardHandlerName].apply(listener, [ handlerName ]); | |
3003 } catch (e) { | |
3004 SimileAjax.Debug.exception("Error firing event of name " + handlerName + " to wildcard handler", e); | |
3005 } | |
3006 } | |
3007 } | |
3008 }; | |
3009 | |
3010 /*====================================================================== | |
3011 * History | |
3012 * | |
3013 * This is a singleton that keeps track of undoable user actions and | |
3014 * performs undos and redos in response to the browser's Back and | |
3015 * Forward buttons. | |
3016 * | |
3017 * Call addAction(action) to register an undoable user action. action | |
3018 * must have 4 fields: | |
3019 * | |
3020 * perform: an argument-less function that carries out the action | |
3021 * undo: an argument-less function that undos the action | |
3022 * label: a short, user-friendly string describing the action | |
3023 * uiLayer: the UI layer on which the action takes place | |
3024 * | |
3025 * By default, the history keeps track of upto 10 actions. You can | |
3026 * configure this behavior by setting | |
3027 * SimileAjax.History.maxHistoryLength | |
3028 * to a different number. | |
3029 * | |
3030 * An iframe is inserted into the document's body element to track | |
3031 * onload events. | |
3032 *====================================================================== | |
3033 */ | |
3034 | |
3035 SimileAjax.History = { | |
3036 maxHistoryLength: 10, | |
3037 historyFile: "__history__.html", | |
3038 enabled: true, | |
3039 | |
3040 _initialized: false, | |
3041 _listeners: new SimileAjax.ListenerQueue(), | |
3042 | |
3043 _actions: [], | |
3044 _baseIndex: 0, | |
3045 _currentIndex: 0, | |
3046 | |
3047 _plainDocumentTitle: document.title | |
3048 }; | |
3049 | |
3050 SimileAjax.History.formatHistoryEntryTitle = function(actionLabel) { | |
3051 return SimileAjax.History._plainDocumentTitle + " {" + actionLabel + "}"; | |
3052 }; | |
3053 | |
3054 SimileAjax.History.initialize = function() { | |
3055 if (SimileAjax.History._initialized) { | |
3056 return; | |
3057 } | |
3058 | |
3059 if (SimileAjax.History.enabled) { | |
3060 var iframe = document.createElement("iframe"); | |
3061 iframe.id = "simile-ajax-history"; | |
3062 iframe.style.position = "absolute"; | |
3063 iframe.style.width = "10px"; | |
3064 iframe.style.height = "10px"; | |
3065 iframe.style.top = "0px"; | |
3066 iframe.style.left = "0px"; | |
3067 iframe.style.visibility = "hidden"; | |
3068 iframe.src = SimileAjax.History.historyFile + "?0"; | |
3069 | |
3070 document.body.appendChild(iframe); | |
3071 SimileAjax.DOM.registerEvent(iframe, "load", SimileAjax.History._handleIFrameOnLoad); | |
3072 | |
3073 SimileAjax.History._iframe = iframe; | |
3074 } | |
3075 SimileAjax.History._initialized = true; | |
3076 }; | |
3077 | |
3078 SimileAjax.History.addListener = function(listener) { | |
3079 SimileAjax.History.initialize(); | |
3080 | |
3081 SimileAjax.History._listeners.add(listener); | |
3082 }; | |
3083 | |
3084 SimileAjax.History.removeListener = function(listener) { | |
3085 SimileAjax.History.initialize(); | |
3086 | |
3087 SimileAjax.History._listeners.remove(listener); | |
3088 }; | |
3089 | |
3090 SimileAjax.History.addAction = function(action) { | |
3091 SimileAjax.History.initialize(); | |
3092 | |
3093 SimileAjax.History._listeners.fire("onBeforePerform", [ action ]); | |
3094 window.setTimeout(function() { | |
3095 try { | |
3096 action.perform(); | |
3097 SimileAjax.History._listeners.fire("onAfterPerform", [ action ]); | |
3098 | |
3099 if (SimileAjax.History.enabled) { | |
3100 SimileAjax.History._actions = SimileAjax.History._actions.slice( | |
3101 0, SimileAjax.History._currentIndex - SimileAjax.History._baseIndex); | |
3102 | |
3103 SimileAjax.History._actions.push(action); | |
3104 SimileAjax.History._currentIndex++; | |
3105 | |
3106 var diff = SimileAjax.History._actions.length - SimileAjax.History.maxHistoryLength; | |
3107 if (diff > 0) { | |
3108 SimileAjax.History._actions = SimileAjax.History._actions.slice(diff); | |
3109 SimileAjax.History._baseIndex += diff; | |
3110 } | |
3111 | |
3112 try { | |
3113 SimileAjax.History._iframe.contentWindow.location.search = | |
3114 "?" + SimileAjax.History._currentIndex; | |
3115 } catch (e) { | |
3116 /* | |
3117 * We can't modify location.search most probably because it's a file:// url. | |
3118 * We'll just going to modify the document's title. | |
3119 */ | |
3120 var title = SimileAjax.History.formatHistoryEntryTitle(action.label); | |
3121 document.title = title; | |
3122 } | |
3123 } | |
3124 } catch (e) { | |
3125 SimileAjax.Debug.exception(e, "Error adding action {" + action.label + "} to history"); | |
3126 } | |
3127 }, 0); | |
3128 }; | |
3129 | |
3130 SimileAjax.History.addLengthyAction = function(perform, undo, label) { | |
3131 SimileAjax.History.addAction({ | |
3132 perform: perform, | |
3133 undo: undo, | |
3134 label: label, | |
3135 uiLayer: SimileAjax.WindowManager.getBaseLayer(), | |
3136 lengthy: true | |
3137 }); | |
3138 }; | |
3139 | |
3140 SimileAjax.History._handleIFrameOnLoad = function() { | |
3141 /* | |
3142 * This function is invoked when the user herself | |
3143 * navigates backward or forward. We need to adjust | |
3144 * the application's state accordingly. | |
3145 */ | |
3146 | |
3147 try { | |
3148 var q = SimileAjax.History._iframe.contentWindow.location.search; | |
3149 var c = (q.length == 0) ? 0 : Math.max(0, parseInt(q.substr(1))); | |
3150 | |
3151 var finishUp = function() { | |
3152 var diff = c - SimileAjax.History._currentIndex; | |
3153 SimileAjax.History._currentIndex += diff; | |
3154 SimileAjax.History._baseIndex += diff; | |
3155 | |
3156 SimileAjax.History._iframe.contentWindow.location.search = "?" + c; | |
3157 }; | |
3158 | |
3159 if (c < SimileAjax.History._currentIndex) { // need to undo | |
3160 SimileAjax.History._listeners.fire("onBeforeUndoSeveral", []); | |
3161 window.setTimeout(function() { | |
3162 while (SimileAjax.History._currentIndex > c && | |
3163 SimileAjax.History._currentIndex > SimileAjax.History._baseIndex) { | |
3164 | |
3165 SimileAjax.History._currentIndex--; | |
3166 | |
3167 var action = SimileAjax.History._actions[SimileAjax.History._currentIndex - SimileAjax.History._baseIndex]; | |
3168 | |
3169 try { | |
3170 action.undo(); | |
3171 } catch (e) { | |
3172 SimileAjax.Debug.exception(e, "History: Failed to undo action {" + action.label + "}"); | |
3173 } | |
3174 } | |
3175 | |
3176 SimileAjax.History._listeners.fire("onAfterUndoSeveral", []); | |
3177 finishUp(); | |
3178 }, 0); | |
3179 } else if (c > SimileAjax.History._currentIndex) { // need to redo | |
3180 SimileAjax.History._listeners.fire("onBeforeRedoSeveral", []); | |
3181 window.setTimeout(function() { | |
3182 while (SimileAjax.History._currentIndex < c && | |
3183 SimileAjax.History._currentIndex - SimileAjax.History._baseIndex < SimileAjax.History._actions.length) { | |
3184 | |
3185 var action = SimileAjax.History._actions[SimileAjax.History._currentIndex - SimileAjax.History._baseIndex]; | |
3186 | |
3187 try { | |
3188 action.perform(); | |
3189 } catch (e) { | |
3190 SimileAjax.Debug.exception(e, "History: Failed to redo action {" + action.label + "}"); | |
3191 } | |
3192 | |
3193 SimileAjax.History._currentIndex++; | |
3194 } | |
3195 | |
3196 SimileAjax.History._listeners.fire("onAfterRedoSeveral", []); | |
3197 finishUp(); | |
3198 }, 0); | |
3199 } else { | |
3200 var index = SimileAjax.History._currentIndex - SimileAjax.History._baseIndex - 1; | |
3201 var title = (index >= 0 && index < SimileAjax.History._actions.length) ? | |
3202 SimileAjax.History.formatHistoryEntryTitle(SimileAjax.History._actions[index].label) : | |
3203 SimileAjax.History._plainDocumentTitle; | |
3204 | |
3205 SimileAjax.History._iframe.contentWindow.document.title = title; | |
3206 document.title = title; | |
3207 } | |
3208 } catch (e) { | |
3209 // silent | |
3210 } | |
3211 }; | |
3212 | |
3213 SimileAjax.History.getNextUndoAction = function() { | |
3214 try { | |
3215 var index = SimileAjax.History._currentIndex - SimileAjax.History._baseIndex - 1; | |
3216 return SimileAjax.History._actions[index]; | |
3217 } catch (e) { | |
3218 return null; | |
3219 } | |
3220 }; | |
3221 | |
3222 SimileAjax.History.getNextRedoAction = function() { | |
3223 try { | |
3224 var index = SimileAjax.History._currentIndex - SimileAjax.History._baseIndex; | |
3225 return SimileAjax.History._actions[index]; | |
3226 } catch (e) { | |
3227 return null; | |
3228 } | |
3229 }; | |
3230 /** | |
3231 * @fileOverview UI layers and window-wide dragging | |
3232 * @name SimileAjax.WindowManager | |
3233 */ | |
3234 | |
3235 /** | |
3236 * This is a singleton that keeps track of UI layers (modal and | |
3237 * modeless) and enables/disables UI elements based on which layers | |
3238 * they belong to. It also provides window-wide dragging | |
3239 * implementation. | |
3240 */ | |
3241 SimileAjax.WindowManager = { | |
3242 _initialized: false, | |
3243 _listeners: [], | |
3244 | |
3245 _draggedElement: null, | |
3246 _draggedElementCallback: null, | |
3247 _dropTargetHighlightElement: null, | |
3248 _lastCoords: null, | |
3249 _ghostCoords: null, | |
3250 _draggingMode: "", | |
3251 _dragging: false, | |
3252 | |
3253 _layers: [] | |
3254 }; | |
3255 | |
3256 SimileAjax.WindowManager.initialize = function() { | |
3257 if (SimileAjax.WindowManager._initialized) { | |
3258 return; | |
3259 } | |
3260 | |
3261 SimileAjax.DOM.registerEvent(document.body, "mousedown", SimileAjax.WindowManager._onBodyMouseDown); | |
3262 SimileAjax.DOM.registerEvent(document.body, "mousemove", SimileAjax.WindowManager._onBodyMouseMove); | |
3263 SimileAjax.DOM.registerEvent(document.body, "mouseup", SimileAjax.WindowManager._onBodyMouseUp); | |
3264 SimileAjax.DOM.registerEvent(document, "keydown", SimileAjax.WindowManager._onBodyKeyDown); | |
3265 SimileAjax.DOM.registerEvent(document, "keyup", SimileAjax.WindowManager._onBodyKeyUp); | |
3266 | |
3267 SimileAjax.WindowManager._layers.push({index: 0}); | |
3268 | |
3269 SimileAjax.WindowManager._historyListener = { | |
3270 onBeforeUndoSeveral: function() {}, | |
3271 onAfterUndoSeveral: function() {}, | |
3272 onBeforeUndo: function() {}, | |
3273 onAfterUndo: function() {}, | |
3274 | |
3275 onBeforeRedoSeveral: function() {}, | |
3276 onAfterRedoSeveral: function() {}, | |
3277 onBeforeRedo: function() {}, | |
3278 onAfterRedo: function() {} | |
3279 }; | |
3280 SimileAjax.History.addListener(SimileAjax.WindowManager._historyListener); | |
3281 | |
3282 SimileAjax.WindowManager._initialized = true; | |
3283 }; | |
3284 | |
3285 SimileAjax.WindowManager.getBaseLayer = function() { | |
3286 SimileAjax.WindowManager.initialize(); | |
3287 return SimileAjax.WindowManager._layers[0]; | |
3288 }; | |
3289 | |
3290 SimileAjax.WindowManager.getHighestLayer = function() { | |
3291 SimileAjax.WindowManager.initialize(); | |
3292 return SimileAjax.WindowManager._layers[SimileAjax.WindowManager._layers.length - 1]; | |
3293 }; | |
3294 | |
3295 SimileAjax.WindowManager.registerEventWithObject = function(elmt, eventName, obj, handlerName, layer) { | |
3296 SimileAjax.WindowManager.registerEvent( | |
3297 elmt, | |
3298 eventName, | |
3299 function(elmt2, evt, target) { | |
3300 return obj[handlerName].call(obj, elmt2, evt, target); | |
3301 }, | |
3302 layer | |
3303 ); | |
3304 }; | |
3305 | |
3306 SimileAjax.WindowManager.registerEvent = function(elmt, eventName, handler, layer) { | |
3307 if (layer == null) { | |
3308 layer = SimileAjax.WindowManager.getHighestLayer(); | |
3309 } | |
3310 | |
3311 var handler2 = function(elmt, evt, target) { | |
3312 if (SimileAjax.WindowManager._canProcessEventAtLayer(layer)) { | |
3313 SimileAjax.WindowManager._popToLayer(layer.index); | |
3314 try { | |
3315 handler(elmt, evt, target); | |
3316 } catch (e) { | |
3317 SimileAjax.Debug.exception(e); | |
3318 } | |
3319 } | |
3320 SimileAjax.DOM.cancelEvent(evt); | |
3321 return false; | |
3322 } | |
3323 | |
3324 SimileAjax.DOM.registerEvent(elmt, eventName, handler2); | |
3325 }; | |
3326 | |
3327 SimileAjax.WindowManager.pushLayer = function(f, ephemeral, elmt) { | |
3328 var layer = { onPop: f, index: SimileAjax.WindowManager._layers.length, ephemeral: (ephemeral), elmt: elmt }; | |
3329 SimileAjax.WindowManager._layers.push(layer); | |
3330 | |
3331 return layer; | |
3332 }; | |
3333 | |
3334 SimileAjax.WindowManager.popLayer = function(layer) { | |
3335 for (var i = 1; i < SimileAjax.WindowManager._layers.length; i++) { | |
3336 if (SimileAjax.WindowManager._layers[i] == layer) { | |
3337 SimileAjax.WindowManager._popToLayer(i - 1); | |
3338 break; | |
3339 } | |
3340 } | |
3341 }; | |
3342 | |
3343 SimileAjax.WindowManager.popAllLayers = function() { | |
3344 SimileAjax.WindowManager._popToLayer(0); | |
3345 }; | |
3346 | |
3347 SimileAjax.WindowManager.registerForDragging = function(elmt, callback, layer) { | |
3348 SimileAjax.WindowManager.registerEvent( | |
3349 elmt, | |
3350 "mousedown", | |
3351 function(elmt, evt, target) { | |
3352 SimileAjax.WindowManager._handleMouseDown(elmt, evt, callback); | |
3353 }, | |
3354 layer | |
3355 ); | |
3356 }; | |
3357 | |
3358 SimileAjax.WindowManager._popToLayer = function(level) { | |
3359 while (level+1 < SimileAjax.WindowManager._layers.length) { | |
3360 try { | |
3361 var layer = SimileAjax.WindowManager._layers.pop(); | |
3362 if (layer.onPop != null) { | |
3363 layer.onPop(); | |
3364 } | |
3365 } catch (e) { | |
3366 } | |
3367 } | |
3368 }; | |
3369 | |
3370 SimileAjax.WindowManager._canProcessEventAtLayer = function(layer) { | |
3371 if (layer.index == (SimileAjax.WindowManager._layers.length - 1)) { | |
3372 return true; | |
3373 } | |
3374 for (var i = layer.index + 1; i < SimileAjax.WindowManager._layers.length; i++) { | |
3375 if (!SimileAjax.WindowManager._layers[i].ephemeral) { | |
3376 return false; | |
3377 } | |
3378 } | |
3379 return true; | |
3380 }; | |
3381 | |
3382 SimileAjax.WindowManager.cancelPopups = function(evt) { | |
3383 var evtCoords = (evt) ? SimileAjax.DOM.getEventPageCoordinates(evt) : { x: -1, y: -1 }; | |
3384 | |
3385 var i = SimileAjax.WindowManager._layers.length - 1; | |
3386 while (i > 0 && SimileAjax.WindowManager._layers[i].ephemeral) { | |
3387 var layer = SimileAjax.WindowManager._layers[i]; | |
3388 if (layer.elmt != null) { // if event falls within main element of layer then don't cancel | |
3389 var elmt = layer.elmt; | |
3390 var elmtCoords = SimileAjax.DOM.getPageCoordinates(elmt); | |
3391 if (evtCoords.x >= elmtCoords.left && evtCoords.x < (elmtCoords.left + elmt.offsetWidth) && | |
3392 evtCoords.y >= elmtCoords.top && evtCoords.y < (elmtCoords.top + elmt.offsetHeight)) { | |
3393 break; | |
3394 } | |
3395 } | |
3396 i--; | |
3397 } | |
3398 SimileAjax.WindowManager._popToLayer(i); | |
3399 }; | |
3400 | |
3401 SimileAjax.WindowManager._onBodyMouseDown = function(elmt, evt, target) { | |
3402 if (!("eventPhase" in evt) || evt.eventPhase == evt.BUBBLING_PHASE) { | |
3403 SimileAjax.WindowManager.cancelPopups(evt); | |
3404 } | |
3405 }; | |
3406 | |
3407 SimileAjax.WindowManager._handleMouseDown = function(elmt, evt, callback) { | |
3408 SimileAjax.WindowManager._draggedElement = elmt; | |
3409 SimileAjax.WindowManager._draggedElementCallback = callback; | |
3410 SimileAjax.WindowManager._lastCoords = { x: evt.clientX, y: evt.clientY }; | |
3411 | |
3412 SimileAjax.DOM.cancelEvent(evt); | |
3413 return false; | |
3414 }; | |
3415 | |
3416 SimileAjax.WindowManager._onBodyKeyDown = function(elmt, evt, target) { | |
3417 if (SimileAjax.WindowManager._dragging) { | |
3418 if (evt.keyCode == 27) { // esc | |
3419 SimileAjax.WindowManager._cancelDragging(); | |
3420 } else if ((evt.keyCode == 17 || evt.keyCode == 16) && SimileAjax.WindowManager._draggingMode != "copy") { | |
3421 SimileAjax.WindowManager._draggingMode = "copy"; | |
3422 | |
3423 var img = SimileAjax.Graphics.createTranslucentImage(SimileAjax.urlPrefix + "images/copy.png"); | |
3424 img.style.position = "absolute"; | |
3425 img.style.left = (SimileAjax.WindowManager._ghostCoords.left - 16) + "px"; | |
3426 img.style.top = (SimileAjax.WindowManager._ghostCoords.top) + "px"; | |
3427 document.body.appendChild(img); | |
3428 | |
3429 SimileAjax.WindowManager._draggingModeIndicatorElmt = img; | |
3430 } | |
3431 } | |
3432 }; | |
3433 | |
3434 SimileAjax.WindowManager._onBodyKeyUp = function(elmt, evt, target) { | |
3435 if (SimileAjax.WindowManager._dragging) { | |
3436 if (evt.keyCode == 17 || evt.keyCode == 16) { | |
3437 SimileAjax.WindowManager._draggingMode = ""; | |
3438 if (SimileAjax.WindowManager._draggingModeIndicatorElmt != null) { | |
3439 document.body.removeChild(SimileAjax.WindowManager._draggingModeIndicatorElmt); | |
3440 SimileAjax.WindowManager._draggingModeIndicatorElmt = null; | |
3441 } | |
3442 } | |
3443 } | |
3444 }; | |
3445 | |
3446 SimileAjax.WindowManager._onBodyMouseMove = function(elmt, evt, target) { | |
3447 if (SimileAjax.WindowManager._draggedElement != null) { | |
3448 var callback = SimileAjax.WindowManager._draggedElementCallback; | |
3449 | |
3450 var lastCoords = SimileAjax.WindowManager._lastCoords; | |
3451 var diffX = evt.clientX - lastCoords.x; | |
3452 var diffY = evt.clientY - lastCoords.y; | |
3453 | |
3454 if (!SimileAjax.WindowManager._dragging) { | |
3455 if (Math.abs(diffX) > 5 || Math.abs(diffY) > 5) { | |
3456 try { | |
3457 if ("onDragStart" in callback) { | |
3458 callback.onDragStart(); | |
3459 } | |
3460 | |
3461 if ("ghost" in callback && callback.ghost) { | |
3462 var draggedElmt = SimileAjax.WindowManager._draggedElement; | |
3463 | |
3464 SimileAjax.WindowManager._ghostCoords = SimileAjax.DOM.getPageCoordinates(draggedElmt); | |
3465 SimileAjax.WindowManager._ghostCoords.left += diffX; | |
3466 SimileAjax.WindowManager._ghostCoords.top += diffY; | |
3467 | |
3468 var ghostElmt = draggedElmt.cloneNode(true); | |
3469 ghostElmt.style.position = "absolute"; | |
3470 ghostElmt.style.left = SimileAjax.WindowManager._ghostCoords.left + "px"; | |
3471 ghostElmt.style.top = SimileAjax.WindowManager._ghostCoords.top + "px"; | |
3472 ghostElmt.style.zIndex = 1000; | |
3473 SimileAjax.Graphics.setOpacity(ghostElmt, 50); | |
3474 | |
3475 document.body.appendChild(ghostElmt); | |
3476 callback._ghostElmt = ghostElmt; | |
3477 } | |
3478 | |
3479 SimileAjax.WindowManager._dragging = true; | |
3480 SimileAjax.WindowManager._lastCoords = { x: evt.clientX, y: evt.clientY }; | |
3481 | |
3482 document.body.focus(); | |
3483 } catch (e) { | |
3484 SimileAjax.Debug.exception("WindowManager: Error handling mouse down", e); | |
3485 SimileAjax.WindowManager._cancelDragging(); | |
3486 } | |
3487 } | |
3488 } else { | |
3489 try { | |
3490 SimileAjax.WindowManager._lastCoords = { x: evt.clientX, y: evt.clientY }; | |
3491 | |
3492 if ("onDragBy" in callback) { | |
3493 callback.onDragBy(diffX, diffY); | |
3494 } | |
3495 | |
3496 if ("_ghostElmt" in callback) { | |
3497 var ghostElmt = callback._ghostElmt; | |
3498 | |
3499 SimileAjax.WindowManager._ghostCoords.left += diffX; | |
3500 SimileAjax.WindowManager._ghostCoords.top += diffY; | |
3501 | |
3502 ghostElmt.style.left = SimileAjax.WindowManager._ghostCoords.left + "px"; | |
3503 ghostElmt.style.top = SimileAjax.WindowManager._ghostCoords.top + "px"; | |
3504 if (SimileAjax.WindowManager._draggingModeIndicatorElmt != null) { | |
3505 var indicatorElmt = SimileAjax.WindowManager._draggingModeIndicatorElmt; | |
3506 | |
3507 indicatorElmt.style.left = (SimileAjax.WindowManager._ghostCoords.left - 16) + "px"; | |
3508 indicatorElmt.style.top = SimileAjax.WindowManager._ghostCoords.top + "px"; | |
3509 } | |
3510 | |
3511 if ("droppable" in callback && callback.droppable) { | |
3512 var coords = SimileAjax.DOM.getEventPageCoordinates(evt); | |
3513 var target = SimileAjax.DOM.hittest( | |
3514 coords.x, coords.y, | |
3515 [ SimileAjax.WindowManager._ghostElmt, | |
3516 SimileAjax.WindowManager._dropTargetHighlightElement | |
3517 ] | |
3518 ); | |
3519 target = SimileAjax.WindowManager._findDropTarget(target); | |
3520 | |
3521 if (target != SimileAjax.WindowManager._potentialDropTarget) { | |
3522 if (SimileAjax.WindowManager._dropTargetHighlightElement != null) { | |
3523 document.body.removeChild(SimileAjax.WindowManager._dropTargetHighlightElement); | |
3524 | |
3525 SimileAjax.WindowManager._dropTargetHighlightElement = null; | |
3526 SimileAjax.WindowManager._potentialDropTarget = null; | |
3527 } | |
3528 | |
3529 var droppable = false; | |
3530 if (target != null) { | |
3531 if ((!("canDropOn" in callback) || callback.canDropOn(target)) && | |
3532 (!("canDrop" in target) || target.canDrop(SimileAjax.WindowManager._draggedElement))) { | |
3533 | |
3534 droppable = true; | |
3535 } | |
3536 } | |
3537 | |
3538 if (droppable) { | |
3539 var border = 4; | |
3540 var targetCoords = SimileAjax.DOM.getPageCoordinates(target); | |
3541 var highlight = document.createElement("div"); | |
3542 highlight.style.border = border + "px solid yellow"; | |
3543 highlight.style.backgroundColor = "yellow"; | |
3544 highlight.style.position = "absolute"; | |
3545 highlight.style.left = targetCoords.left + "px"; | |
3546 highlight.style.top = targetCoords.top + "px"; | |
3547 highlight.style.width = (target.offsetWidth - border * 2) + "px"; | |
3548 highlight.style.height = (target.offsetHeight - border * 2) + "px"; | |
3549 SimileAjax.Graphics.setOpacity(highlight, 30); | |
3550 document.body.appendChild(highlight); | |
3551 | |
3552 SimileAjax.WindowManager._potentialDropTarget = target; | |
3553 SimileAjax.WindowManager._dropTargetHighlightElement = highlight; | |
3554 } | |
3555 } | |
3556 } | |
3557 } | |
3558 } catch (e) { | |
3559 SimileAjax.Debug.exception("WindowManager: Error handling mouse move", e); | |
3560 SimileAjax.WindowManager._cancelDragging(); | |
3561 } | |
3562 } | |
3563 | |
3564 SimileAjax.DOM.cancelEvent(evt); | |
3565 return false; | |
3566 } | |
3567 }; | |
3568 | |
3569 SimileAjax.WindowManager._onBodyMouseUp = function(elmt, evt, target) { | |
3570 if (SimileAjax.WindowManager._draggedElement != null) { | |
3571 try { | |
3572 if (SimileAjax.WindowManager._dragging) { | |
3573 var callback = SimileAjax.WindowManager._draggedElementCallback; | |
3574 if ("onDragEnd" in callback) { | |
3575 callback.onDragEnd(); | |
3576 } | |
3577 if ("droppable" in callback && callback.droppable) { | |
3578 var dropped = false; | |
3579 | |
3580 var target = SimileAjax.WindowManager._potentialDropTarget; | |
3581 if (target != null) { | |
3582 if ((!("canDropOn" in callback) || callback.canDropOn(target)) && | |
3583 (!("canDrop" in target) || target.canDrop(SimileAjax.WindowManager._draggedElement))) { | |
3584 | |
3585 if ("onDropOn" in callback) { | |
3586 callback.onDropOn(target); | |
3587 } | |
3588 target.ondrop(SimileAjax.WindowManager._draggedElement, SimileAjax.WindowManager._draggingMode); | |
3589 | |
3590 dropped = true; | |
3591 } | |
3592 } | |
3593 | |
3594 if (!dropped) { | |
3595 // TODO: do holywood explosion here | |
3596 } | |
3597 } | |
3598 } | |
3599 } finally { | |
3600 SimileAjax.WindowManager._cancelDragging(); | |
3601 } | |
3602 | |
3603 SimileAjax.DOM.cancelEvent(evt); | |
3604 return false; | |
3605 } | |
3606 }; | |
3607 | |
3608 SimileAjax.WindowManager._cancelDragging = function() { | |
3609 var callback = SimileAjax.WindowManager._draggedElementCallback; | |
3610 if ("_ghostElmt" in callback) { | |
3611 var ghostElmt = callback._ghostElmt; | |
3612 document.body.removeChild(ghostElmt); | |
3613 | |
3614 delete callback._ghostElmt; | |
3615 } | |
3616 if (SimileAjax.WindowManager._dropTargetHighlightElement != null) { | |
3617 document.body.removeChild(SimileAjax.WindowManager._dropTargetHighlightElement); | |
3618 SimileAjax.WindowManager._dropTargetHighlightElement = null; | |
3619 } | |
3620 if (SimileAjax.WindowManager._draggingModeIndicatorElmt != null) { | |
3621 document.body.removeChild(SimileAjax.WindowManager._draggingModeIndicatorElmt); | |
3622 SimileAjax.WindowManager._draggingModeIndicatorElmt = null; | |
3623 } | |
3624 | |
3625 SimileAjax.WindowManager._draggedElement = null; | |
3626 SimileAjax.WindowManager._draggedElementCallback = null; | |
3627 SimileAjax.WindowManager._potentialDropTarget = null; | |
3628 SimileAjax.WindowManager._dropTargetHighlightElement = null; | |
3629 SimileAjax.WindowManager._lastCoords = null; | |
3630 SimileAjax.WindowManager._ghostCoords = null; | |
3631 SimileAjax.WindowManager._draggingMode = ""; | |
3632 SimileAjax.WindowManager._dragging = false; | |
3633 }; | |
3634 | |
3635 SimileAjax.WindowManager._findDropTarget = function(elmt) { | |
3636 while (elmt != null) { | |
3637 if ("ondrop" in elmt && (typeof elmt.ondrop) == "function") { | |
3638 break; | |
3639 } | |
3640 elmt = elmt.parentNode; | |
3641 } | |
3642 return elmt; | |
3643 }; | |
3644 /*================================================== | |
3645 * | |
3646 * Timeline API | |
3647 * ------------ | |
3648 * | |
3649 * This file will load all the Javascript files | |
3650 * necessary to make the standard timeline work. | |
3651 * It also detects the default locale. | |
3652 * | |
3653 * To run Timeline directly from the www.simile-widgets.org server | |
3654 * include this fragment in your HTML file as follows: | |
3655 * | |
3656 * <script src="http://api.simile-widgets.org/timeline/2.3.1/timeline-api.js" | |
3657 * type="text/javascript"></script> | |
3658 * | |
3659 * | |
3660 * To host the Timeline files on your own server: | |
3661 * | |
3662 * 1) Install the Timeline files onto your webserver using | |
3663 * the minimal distribution "timeline_<version>_minimal.(zip|tar.gz)" found at | |
3664 * | |
3665 * http://code.google.com/p/simile-widgets/downloads/list | |
3666 * | |
3667 * 2) Set the following global js variable used to send parameters to this script: | |
3668 * var Timeline_ajax_url -- URL for simile-ajax-api.js | |
3669 * var Timeline_urlPrefix -- URL for the *directory* that contains timeline-api.js | |
3670 * on your web site (including the trailing slash!) | |
3671 * | |
3672 * eg your HTML page would include | |
3673 * | |
3674 * <script> | |
3675 * var Timeline_ajax_url="http://YOUR_SERVER/apis/timeline/ajax/simile-ajax-api.js"; | |
3676 * var Timeline_urlPrefix='http://YOUR_SERVER/apis/timeline/'; | |
3677 * </script> | |
3678 * <script src="http://YOUR_SERVER/javascripts/timeline/timeline-api.js" | |
3679 * type="text/javascript"> | |
3680 * </script> | |
3681 * | |
3682 * SCRIPT PARAMETERS | |
3683 * | |
3684 * This script auto-magically figures out locale and has defaults for other parameters | |
3685 * To set parameters explicity, set js global variable Timeline_parameters or include as | |
3686 * parameters on the url using GET style. Eg the two next lines pass the same parameters: | |
3687 * Timeline_parameters='bundle=true'; // pass parameter via js variable | |
3688 * <script src="http://....timeline-api.js?bundle=true" // pass parameter via url | |
3689 * | |
3690 * Parameters | |
3691 * timeline-use-local-resources -- | |
3692 * bundle -- true: use the single js bundle file; false: load individual files (for debugging) | |
3693 * locales -- | |
3694 * defaultLocale -- | |
3695 * forceLocale -- force locale to be a particular value--used for debugging. Normally locale is determined | |
3696 * by browser's and server's locale settings. | |
3697 * | |
3698 * DEBUGGING | |
3699 * | |
3700 * If you have a problem with Timeline, the first step is to use the unbundled Javascript files. To do so: | |
3701 * To use the unbundled Timeline and Ajax libraries | |
3702 * Change | |
3703 * <script src="http://api.simile-widgets.org/timeline/2.3.1/api/timeline-api.js?bundle=true" type="text/javascript"></script> | |
3704 * To | |
3705 * <script>var Timeline_ajax_url = "http://api.simile-widgets.org/ajax/2.2.1/simile-ajax-api.js?bundle=false"</script> | |
3706 * <script src="http://api.simile-widgets.org/timeline/2.3.1/api/timeline-api.js?bundle=false" type="text/javascript"></script> | |
3707 * | |
3708 * Note that the Ajax version is usually NOT the same as the Timeline version. | |
3709 * See variable simile_ajax_ver below for the current version | |
3710 * | |
3711 *================================================== | |
3712 */ | |
3713 | |
3714 (function() { | |
3715 | |
3716 var simile_ajax_ver = "2.2.1"; // ===========>>> current Simile-Ajax version | |
3717 | |
3718 var useLocalResources = false; | |
3719 if (document.location.search.length > 0) { | |
3720 var params = document.location.search.substr(1).split("&"); | |
3721 for (var i = 0; i < params.length; i++) { | |
3722 if (params[i] == "timeline-use-local-resources") { | |
3723 useLocalResources = true; | |
3724 } | |
3725 } | |
3726 }; | |
3727 | |
3728 var loadMe = function() { | |
3729 if ("Timeline" in window) { | |
3730 return; | |
3731 } | |
3732 | |
3733 window.Timeline = new Object(); | |
3734 window.Timeline.DateTime = window.SimileAjax.DateTime; // for backward compatibility | |
3735 | |
3736 var bundle = false; | |
3737 var javascriptFiles = [ | |
3738 "timeline.js", | |
3739 "band.js", | |
3740 "themes.js", | |
3741 "ethers.js", | |
3742 "ether-painters.js", | |
3743 "event-utils.js", | |
3744 "labellers.js", | |
3745 "sources.js", | |
3746 "original-painter.js", | |
3747 "detailed-painter.js", | |
3748 "overview-painter.js", | |
3749 "compact-painter.js", | |
3750 "decorators.js", | |
3751 "units.js" | |
3752 ]; | |
3753 var cssFiles = [ | |
3754 "timeline.css", | |
3755 "ethers.css", | |
3756 "events.css" | |
3757 ]; | |
3758 | |
3759 var localizedJavascriptFiles = [ | |
3760 "timeline.js", | |
3761 "labellers.js" | |
3762 ]; | |
3763 var localizedCssFiles = [ | |
3764 ]; | |
3765 | |
3766 // ISO-639 language codes, ISO-3166 country codes (2 characters) | |
3767 var supportedLocales = [ | |
3768 "cs", // Czech | |
3769 "de", // German | |
3770 "en", // English | |
3771 "es", // Spanish | |
3772 "fr", // French | |
3773 "it", // Italian | |
3774 "nl", // Dutch (The Netherlands) | |
3775 "ru", // Russian | |
3776 "se", // Swedish | |
3777 "tr", // Turkish | |
3778 "vi", // Vietnamese | |
3779 "zh" // Chinese | |
3780 ]; | |
3781 | |
3782 try { | |
3783 var desiredLocales = [ "en" ], | |
3784 defaultServerLocale = "en", | |
3785 forceLocale = null; | |
3786 | |
3787 var parseURLParameters = function(parameters) { | |
3788 var params = parameters.split("&"); | |
3789 for (var p = 0; p < params.length; p++) { | |
3790 var pair = params[p].split("="); | |
3791 if (pair[0] == "locales") { | |
3792 desiredLocales = desiredLocales.concat(pair[1].split(",")); | |
3793 } else if (pair[0] == "defaultLocale") { | |
3794 defaultServerLocale = pair[1]; | |
3795 } else if (pair[0] == "forceLocale") { | |
3796 forceLocale = pair[1]; | |
3797 desiredLocales = desiredLocales.concat(pair[1].split(",")); | |
3798 } else if (pair[0] == "bundle") { | |
3799 bundle = pair[1] != "false"; | |
3800 } | |
3801 } | |
3802 }; | |
3803 | |
3804 (function() { | |
3805 if (typeof Timeline_urlPrefix == "string") { | |
3806 Timeline.urlPrefix = Timeline_urlPrefix; | |
3807 if (typeof Timeline_parameters == "string") { | |
3808 parseURLParameters(Timeline_parameters); | |
3809 } | |
3810 } else { | |
3811 var heads = document.documentElement.getElementsByTagName("head"); | |
3812 for (var h = 0; h < heads.length; h++) { | |
3813 var scripts = heads[h].getElementsByTagName("script"); | |
3814 for (var s = 0; s < scripts.length; s++) { | |
3815 var url = scripts[s].src; | |
3816 var i = url.indexOf("timeplot-complete.js"); | |
3817 if (i >= 0) { | |
3818 Timeline.urlPrefix = url.substr(0, i); | |
3819 var q = url.indexOf("?"); | |
3820 if (q > 0) { | |
3821 parseURLParameters(url.substr(q + 1)); | |
3822 } | |
3823 return; | |
3824 } | |
3825 } | |
3826 } | |
3827 throw new Error("Failed to derive URL prefix for Timeline API code files"); | |
3828 } | |
3829 })(); | |
3830 | |
3831 var includeJavascriptFiles = function(urlPrefix, filenames) { | |
3832 SimileAjax.includeJavascriptFiles(document, urlPrefix, filenames); | |
3833 } | |
3834 var includeCssFiles = function(urlPrefix, filenames) { | |
3835 SimileAjax.includeCssFiles(document, urlPrefix, filenames); | |
3836 } | |
3837 | |
3838 /* | |
3839 * Include non-localized files | |
3840 */ | |
3841 if (bundle) { | |
3842 includeJavascriptFiles(Timeline.urlPrefix, [ "timeline-bundle.js" ]); | |
3843 includeCssFiles(Timeline.urlPrefix, [ "timeline-bundle.css" ]); | |
3844 } else { | |
3845 includeJavascriptFiles(Timeline.urlPrefix + "scripts/", javascriptFiles); | |
3846 includeCssFiles(Timeline.urlPrefix + "styles/", cssFiles); | |
3847 } | |
3848 | |
3849 /* | |
3850 * Include localized files | |
3851 */ | |
3852 var loadLocale = []; | |
3853 loadLocale[defaultServerLocale] = true; | |
3854 | |
3855 var tryExactLocale = function(locale) { | |
3856 for (var l = 0; l < supportedLocales.length; l++) { | |
3857 if (locale == supportedLocales[l]) { | |
3858 loadLocale[locale] = true; | |
3859 return true; | |
3860 } | |
3861 } | |
3862 return false; | |
3863 } | |
3864 var tryLocale = function(locale) { | |
3865 if (tryExactLocale(locale)) { | |
3866 return locale; | |
3867 } | |
3868 | |
3869 var dash = locale.indexOf("-"); | |
3870 if (dash > 0 && tryExactLocale(locale.substr(0, dash))) { | |
3871 return locale.substr(0, dash); | |
3872 } | |
3873 | |
3874 return null; | |
3875 } | |
3876 | |
3877 for (var l = 0; l < desiredLocales.length; l++) { | |
3878 tryLocale(desiredLocales[l]); | |
3879 } | |
3880 | |
3881 var defaultClientLocale = defaultServerLocale; | |
3882 var defaultClientLocales = ("language" in navigator ? navigator.language : navigator.browserLanguage).split(";"); | |
3883 for (var l = 0; l < defaultClientLocales.length; l++) { | |
3884 var locale = tryLocale(defaultClientLocales[l]); | |
3885 if (locale != null) { | |
3886 defaultClientLocale = locale; | |
3887 break; | |
3888 } | |
3889 } | |
3890 | |
3891 for (var l = 0; l < supportedLocales.length; l++) { | |
3892 var locale = supportedLocales[l]; | |
3893 if (loadLocale[locale]) { | |
3894 includeJavascriptFiles(Timeline.urlPrefix + "scripts/l10n/" + locale + "/", localizedJavascriptFiles); | |
3895 includeCssFiles(Timeline.urlPrefix + "styles/l10n/" + locale + "/", localizedCssFiles); | |
3896 } | |
3897 } | |
3898 | |
3899 if (forceLocale == null) { | |
3900 Timeline.serverLocale = defaultServerLocale; | |
3901 Timeline.clientLocale = defaultClientLocale; | |
3902 } else { | |
3903 Timeline.serverLocale = forceLocale; | |
3904 Timeline.clientLocale = forceLocale; | |
3905 } | |
3906 } catch (e) { | |
3907 alert(e); | |
3908 } | |
3909 }; | |
3910 | |
3911 /* | |
3912 * Load SimileAjax if it's not already loaded | |
3913 */ | |
3914 if (typeof SimileAjax == "undefined") { | |
3915 window.SimileAjax_onLoad = loadMe; | |
3916 | |
3917 var url = useLocalResources ? | |
3918 "http://127.0.0.1:9999/ajax/api/simile-ajax-api.js?bundle=false" : | |
3919 "http://api.simile-widgets.org/ajax/" + simile_ajax_ver + "/simile-ajax-api.js"; | |
3920 if (typeof Timeline_ajax_url == "string") { | |
3921 url = Timeline_ajax_url; | |
3922 } | |
3923 var createScriptElement = function() { | |
3924 var script = document.createElement("script"); | |
3925 script.type = "text/javascript"; | |
3926 script.language = "JavaScript"; | |
3927 script.src = url; | |
3928 document.getElementsByTagName("head")[0].appendChild(script); | |
3929 } | |
3930 if (document.body == null) { | |
3931 try { | |
3932 document.write("<script src='" + url + "' type='text/javascript'></script>"); | |
3933 } catch (e) { | |
3934 createScriptElement(); | |
3935 } | |
3936 } else { | |
3937 createScriptElement(); | |
3938 } | |
3939 } else { | |
3940 loadMe(); | |
3941 } | |
3942 })(); | |
3943 /*================================================= | |
3944 * | |
3945 * Coding standards: | |
3946 * | |
3947 * We aim towards Douglas Crockford's Javascript conventions. | |
3948 * See: http://javascript.crockford.com/code.html | |
3949 * See also: http://www.crockford.com/javascript/javascript.html | |
3950 * | |
3951 * That said, this JS code was written before some recent JS | |
3952 * support libraries became widely used or available. | |
3953 * In particular, the _ character is used to indicate a class function or | |
3954 * variable that should be considered private to the class. | |
3955 * | |
3956 * The code mostly uses accessor methods for getting/setting the private | |
3957 * class variables. | |
3958 * | |
3959 * Over time, we'd like to formalize the convention by using support libraries | |
3960 * which enforce privacy in objects. | |
3961 * | |
3962 * We also want to use jslint: http://www.jslint.com/ | |
3963 * | |
3964 * | |
3965 *================================================== | |
3966 */ | |
3967 | |
3968 | |
3969 | |
3970 /*================================================== | |
3971 * Timeline VERSION | |
3972 *================================================== | |
3973 */ | |
3974 // Note: version is also stored in the build.xml file | |
3975 Timeline.version = 'pre 2.4.0'; // use format 'pre 1.2.3' for trunk versions | |
3976 Timeline.ajax_lib_version = SimileAjax.version; | |
3977 Timeline.display_version = Timeline.version + ' (with Ajax lib ' + Timeline.ajax_lib_version + ')'; | |
3978 // cf method Timeline.writeVersion | |
3979 | |
3980 /*================================================== | |
3981 * Timeline | |
3982 *================================================== | |
3983 */ | |
3984 Timeline.strings = {}; // localization string tables | |
3985 Timeline.HORIZONTAL = 0; | |
3986 Timeline.VERTICAL = 1; | |
3987 Timeline._defaultTheme = null; | |
3988 | |
3989 Timeline.getDefaultLocale = function() { | |
3990 return Timeline.clientLocale; | |
3991 }; | |
3992 | |
3993 Timeline.create = function(elmt, bandInfos, orientation, unit) { | |
3994 if (Timeline.timelines == null) { | |
3995 Timeline.timelines = []; | |
3996 // Timeline.timelines array can have null members--Timelines that | |
3997 // once existed on the page, but were later disposed of. | |
3998 } | |
3999 | |
4000 var timelineID = Timeline.timelines.length; | |
4001 Timeline.timelines[timelineID] = null; // placeholder until we have the object | |
4002 var new_tl = new Timeline._Impl(elmt, bandInfos, orientation, unit, | |
4003 timelineID); | |
4004 Timeline.timelines[timelineID] = new_tl; | |
4005 return new_tl; | |
4006 }; | |
4007 | |
4008 Timeline.createBandInfo = function(params) { | |
4009 var theme = ("theme" in params) ? params.theme : Timeline.getDefaultTheme(); | |
4010 | |
4011 var eventSource = ("eventSource" in params) ? params.eventSource : null; | |
4012 | |
4013 var ether = new Timeline.LinearEther({ | |
4014 centersOn: ("date" in params) ? params.date : new Date(), | |
4015 interval: SimileAjax.DateTime.gregorianUnitLengths[params.intervalUnit], | |
4016 pixelsPerInterval: params.intervalPixels, | |
4017 theme: theme | |
4018 }); | |
4019 | |
4020 var etherPainter = new Timeline.GregorianEtherPainter({ | |
4021 unit: params.intervalUnit, | |
4022 multiple: ("multiple" in params) ? params.multiple : 1, | |
4023 theme: theme, | |
4024 align: ("align" in params) ? params.align : undefined | |
4025 }); | |
4026 | |
4027 var eventPainterParams = { | |
4028 showText: ("showEventText" in params) ? params.showEventText : true, | |
4029 theme: theme | |
4030 }; | |
4031 // pass in custom parameters for the event painter | |
4032 if ("eventPainterParams" in params) { | |
4033 for (var prop in params.eventPainterParams) { | |
4034 eventPainterParams[prop] = params.eventPainterParams[prop]; | |
4035 } | |
4036 } | |
4037 | |
4038 if ("trackHeight" in params) { | |
4039 eventPainterParams.trackHeight = params.trackHeight; | |
4040 } | |
4041 if ("trackGap" in params) { | |
4042 eventPainterParams.trackGap = params.trackGap; | |
4043 } | |
4044 | |
4045 var layout = ("overview" in params && params.overview) ? "overview" : ("layout" in params ? params.layout : "original"); | |
4046 var eventPainter; | |
4047 if ("eventPainter" in params) { | |
4048 eventPainter = new params.eventPainter(eventPainterParams); | |
4049 } else { | |
4050 switch (layout) { | |
4051 case "overview" : | |
4052 eventPainter = new Timeline.OverviewEventPainter(eventPainterParams); | |
4053 break; | |
4054 case "detailed" : | |
4055 eventPainter = new Timeline.DetailedEventPainter(eventPainterParams); | |
4056 break; | |
4057 default: | |
4058 eventPainter = new Timeline.OriginalEventPainter(eventPainterParams); | |
4059 } | |
4060 } | |
4061 | |
4062 return { | |
4063 width: params.width, | |
4064 eventSource: eventSource, | |
4065 timeZone: ("timeZone" in params) ? params.timeZone : 0, | |
4066 ether: ether, | |
4067 etherPainter: etherPainter, | |
4068 eventPainter: eventPainter, | |
4069 theme: theme, | |
4070 zoomIndex: ("zoomIndex" in params) ? params.zoomIndex : 0, | |
4071 zoomSteps: ("zoomSteps" in params) ? params.zoomSteps : null | |
4072 }; | |
4073 }; | |
4074 | |
4075 Timeline.createHotZoneBandInfo = function(params) { | |
4076 var theme = ("theme" in params) ? params.theme : Timeline.getDefaultTheme(); | |
4077 | |
4078 var eventSource = ("eventSource" in params) ? params.eventSource : null; | |
4079 | |
4080 var ether = new Timeline.HotZoneEther({ | |
4081 centersOn: ("date" in params) ? params.date : new Date(), | |
4082 interval: SimileAjax.DateTime.gregorianUnitLengths[params.intervalUnit], | |
4083 pixelsPerInterval: params.intervalPixels, | |
4084 zones: params.zones, | |
4085 theme: theme | |
4086 }); | |
4087 | |
4088 var etherPainter = new Timeline.HotZoneGregorianEtherPainter({ | |
4089 unit: params.intervalUnit, | |
4090 zones: params.zones, | |
4091 theme: theme, | |
4092 align: ("align" in params) ? params.align : undefined | |
4093 }); | |
4094 | |
4095 var eventPainterParams = { | |
4096 showText: ("showEventText" in params) ? params.showEventText : true, | |
4097 theme: theme | |
4098 }; | |
4099 // pass in custom parameters for the event painter | |
4100 if ("eventPainterParams" in params) { | |
4101 for (var prop in params.eventPainterParams) { | |
4102 eventPainterParams[prop] = params.eventPainterParams[prop]; | |
4103 } | |
4104 } | |
4105 if ("trackHeight" in params) { | |
4106 eventPainterParams.trackHeight = params.trackHeight; | |
4107 } | |
4108 if ("trackGap" in params) { | |
4109 eventPainterParams.trackGap = params.trackGap; | |
4110 } | |
4111 | |
4112 var layout = ("overview" in params && params.overview) ? "overview" : ("layout" in params ? params.layout : "original"); | |
4113 var eventPainter; | |
4114 if ("eventPainter" in params) { | |
4115 eventPainter = new params.eventPainter(eventPainterParams); | |
4116 } else { | |
4117 switch (layout) { | |
4118 case "overview" : | |
4119 eventPainter = new Timeline.OverviewEventPainter(eventPainterParams); | |
4120 break; | |
4121 case "detailed" : | |
4122 eventPainter = new Timeline.DetailedEventPainter(eventPainterParams); | |
4123 break; | |
4124 default: | |
4125 eventPainter = new Timeline.OriginalEventPainter(eventPainterParams); | |
4126 } | |
4127 } | |
4128 return { | |
4129 width: params.width, | |
4130 eventSource: eventSource, | |
4131 timeZone: ("timeZone" in params) ? params.timeZone : 0, | |
4132 ether: ether, | |
4133 etherPainter: etherPainter, | |
4134 eventPainter: eventPainter, | |
4135 theme: theme, | |
4136 zoomIndex: ("zoomIndex" in params) ? params.zoomIndex : 0, | |
4137 zoomSteps: ("zoomSteps" in params) ? params.zoomSteps : null | |
4138 }; | |
4139 }; | |
4140 | |
4141 Timeline.getDefaultTheme = function() { | |
4142 if (Timeline._defaultTheme == null) { | |
4143 Timeline._defaultTheme = Timeline.ClassicTheme.create(Timeline.getDefaultLocale()); | |
4144 } | |
4145 return Timeline._defaultTheme; | |
4146 }; | |
4147 | |
4148 Timeline.setDefaultTheme = function(theme) { | |
4149 Timeline._defaultTheme = theme; | |
4150 }; | |
4151 | |
4152 Timeline.loadXML = function(url, f) { | |
4153 var fError = function(statusText, status, xmlhttp) { | |
4154 alert("Failed to load data xml from " + url + "\n" + statusText); | |
4155 }; | |
4156 var fDone = function(xmlhttp) { | |
4157 var xml = xmlhttp.responseXML; | |
4158 if (!xml.documentElement && xmlhttp.responseStream) { | |
4159 xml.load(xmlhttp.responseStream); | |
4160 } | |
4161 f(xml, url); | |
4162 }; | |
4163 SimileAjax.XmlHttp.get(url, fError, fDone); | |
4164 }; | |
4165 | |
4166 | |
4167 Timeline.loadJSON = function(url, f) { | |
4168 var fError = function(statusText, status, xmlhttp) { | |
4169 alert("Failed to load json data from " + url + "\n" + statusText); | |
4170 }; | |
4171 var fDone = function(xmlhttp) { | |
4172 f(eval('(' + xmlhttp.responseText + ')'), url); | |
4173 }; | |
4174 SimileAjax.XmlHttp.get(url, fError, fDone); | |
4175 }; | |
4176 | |
4177 Timeline.getTimelineFromID = function(timelineID) { | |
4178 return Timeline.timelines[timelineID]; | |
4179 }; | |
4180 | |
4181 // Write the current Timeline version as the contents of element with id el_id | |
4182 Timeline.writeVersion = function(el_id) { | |
4183 document.getElementById(el_id).innerHTML = this.display_version; | |
4184 }; | |
4185 | |
4186 | |
4187 | |
4188 /*================================================== | |
4189 * Timeline Implementation object | |
4190 *================================================== | |
4191 */ | |
4192 Timeline._Impl = function(elmt, bandInfos, orientation, unit, timelineID) { | |
4193 SimileAjax.WindowManager.initialize(); | |
4194 | |
4195 this._containerDiv = elmt; | |
4196 | |
4197 this._bandInfos = bandInfos; | |
4198 this._orientation = orientation == null ? Timeline.HORIZONTAL : orientation; | |
4199 this._unit = (unit != null) ? unit : SimileAjax.NativeDateUnit; | |
4200 this._starting = true; // is the Timeline being created? Used by autoWidth | |
4201 // functions | |
4202 this._autoResizing = false; | |
4203 | |
4204 // autoWidth is a "public" property of the Timeline object | |
4205 this.autoWidth = bandInfos && bandInfos[0] && bandInfos[0].theme && | |
4206 bandInfos[0].theme.autoWidth; | |
4207 this.autoWidthAnimationTime = bandInfos && bandInfos[0] && bandInfos[0].theme && | |
4208 bandInfos[0].theme.autoWidthAnimationTime; | |
4209 this.timelineID = timelineID; // also public attribute | |
4210 this.timeline_start = bandInfos && bandInfos[0] && bandInfos[0].theme && | |
4211 bandInfos[0].theme.timeline_start; | |
4212 this.timeline_stop = bandInfos && bandInfos[0] && bandInfos[0].theme && | |
4213 bandInfos[0].theme.timeline_stop; | |
4214 this.timeline_at_start = false; // already at start or stop? Then won't | |
4215 this.timeline_at_stop = false; // try to move further in the wrong direction | |
4216 | |
4217 this._initialize(); | |
4218 }; | |
4219 | |
4220 // | |
4221 // Public functions used by client sw | |
4222 // | |
4223 Timeline._Impl.prototype.dispose = function() { | |
4224 for (var i = 0; i < this._bands.length; i++) { | |
4225 this._bands[i].dispose(); | |
4226 } | |
4227 this._bands = null; | |
4228 this._bandInfos = null; | |
4229 this._containerDiv.innerHTML = ""; | |
4230 // remove from array of Timelines | |
4231 Timeline.timelines[this.timelineID] = null; | |
4232 }; | |
4233 | |
4234 Timeline._Impl.prototype.getBandCount = function() { | |
4235 return this._bands.length; | |
4236 }; | |
4237 | |
4238 Timeline._Impl.prototype.getBand = function(index) { | |
4239 return this._bands[index]; | |
4240 }; | |
4241 | |
4242 Timeline._Impl.prototype.finishedEventLoading = function() { | |
4243 // Called by client after events have been loaded into Timeline | |
4244 // Only used if the client has set autoWidth | |
4245 // Sets width to Timeline's requested amount and will shrink down the div if | |
4246 // need be. | |
4247 this._autoWidthCheck(true); | |
4248 this._starting = false; | |
4249 }; | |
4250 | |
4251 Timeline._Impl.prototype.layout = function() { | |
4252 // called by client when browser is resized | |
4253 this._autoWidthCheck(true); | |
4254 this._distributeWidths(); | |
4255 }; | |
4256 | |
4257 Timeline._Impl.prototype.paint = function() { | |
4258 for (var i = 0; i < this._bands.length; i++) { | |
4259 this._bands[i].paint(); | |
4260 } | |
4261 }; | |
4262 | |
4263 Timeline._Impl.prototype.getDocument = function() { | |
4264 return this._containerDiv.ownerDocument; | |
4265 }; | |
4266 | |
4267 Timeline._Impl.prototype.addDiv = function(div) { | |
4268 this._containerDiv.appendChild(div); | |
4269 }; | |
4270 | |
4271 Timeline._Impl.prototype.removeDiv = function(div) { | |
4272 this._containerDiv.removeChild(div); | |
4273 }; | |
4274 | |
4275 Timeline._Impl.prototype.isHorizontal = function() { | |
4276 return this._orientation == Timeline.HORIZONTAL; | |
4277 }; | |
4278 | |
4279 Timeline._Impl.prototype.isVertical = function() { | |
4280 return this._orientation == Timeline.VERTICAL; | |
4281 }; | |
4282 | |
4283 Timeline._Impl.prototype.getPixelLength = function() { | |
4284 return this._orientation == Timeline.HORIZONTAL ? | |
4285 this._containerDiv.offsetWidth : this._containerDiv.offsetHeight; | |
4286 }; | |
4287 | |
4288 Timeline._Impl.prototype.getPixelWidth = function() { | |
4289 return this._orientation == Timeline.VERTICAL ? | |
4290 this._containerDiv.offsetWidth : this._containerDiv.offsetHeight; | |
4291 }; | |
4292 | |
4293 Timeline._Impl.prototype.getUnit = function() { | |
4294 return this._unit; | |
4295 }; | |
4296 | |
4297 Timeline._Impl.prototype.getWidthStyle = function() { | |
4298 // which element.style attribute should be changed to affect Timeline's "width" | |
4299 return this._orientation == Timeline.HORIZONTAL ? 'height' : 'width'; | |
4300 }; | |
4301 | |
4302 Timeline._Impl.prototype.loadXML = function(url, f) { | |
4303 var tl = this; | |
4304 | |
4305 | |
4306 var fError = function(statusText, status, xmlhttp) { | |
4307 alert("Failed to load data xml from " + url + "\n" + statusText); | |
4308 tl.hideLoadingMessage(); | |
4309 }; | |
4310 var fDone = function(xmlhttp) { | |
4311 try { | |
4312 var xml = xmlhttp.responseXML; | |
4313 if (!xml.documentElement && xmlhttp.responseStream) { | |
4314 xml.load(xmlhttp.responseStream); | |
4315 } | |
4316 f(xml, url); | |
4317 } finally { | |
4318 tl.hideLoadingMessage(); | |
4319 } | |
4320 }; | |
4321 | |
4322 this.showLoadingMessage(); | |
4323 window.setTimeout(function() { SimileAjax.XmlHttp.get(url, fError, fDone); }, 0); | |
4324 }; | |
4325 | |
4326 Timeline._Impl.prototype.loadJSON = function(url, f) { | |
4327 var tl = this; | |
4328 | |
4329 var fError = function(statusText, status, xmlhttp) { | |
4330 alert("Failed to load json data from " + url + "\n" + statusText); | |
4331 tl.hideLoadingMessage(); | |
4332 }; | |
4333 var fDone = function(xmlhttp) { | |
4334 try { | |
4335 f(eval('(' + xmlhttp.responseText + ')'), url); | |
4336 } finally { | |
4337 tl.hideLoadingMessage(); | |
4338 } | |
4339 }; | |
4340 | |
4341 this.showLoadingMessage(); | |
4342 window.setTimeout(function() { SimileAjax.XmlHttp.get(url, fError, fDone); }, 0); | |
4343 }; | |
4344 | |
4345 | |
4346 // | |
4347 // Private functions used by Timeline object functions | |
4348 // | |
4349 | |
4350 Timeline._Impl.prototype._autoWidthScrollListener = function(band) { | |
4351 band.getTimeline()._autoWidthCheck(false); | |
4352 }; | |
4353 | |
4354 // called to re-calculate auto width and adjust the overall Timeline div if needed | |
4355 Timeline._Impl.prototype._autoWidthCheck = function(okToShrink) { | |
4356 var timeline = this; // this Timeline | |
4357 var immediateChange = timeline._starting; | |
4358 var newWidth = 0; | |
4359 | |
4360 function changeTimelineWidth() { | |
4361 var widthStyle = timeline.getWidthStyle(); | |
4362 if (immediateChange) { | |
4363 timeline._containerDiv.style[widthStyle] = newWidth + 'px'; | |
4364 } else { | |
4365 // animate change | |
4366 timeline._autoResizing = true; | |
4367 var animateParam ={}; | |
4368 animateParam[widthStyle] = newWidth + 'px'; | |
4369 | |
4370 SimileAjax.jQuery(timeline._containerDiv).animate( | |
4371 animateParam, timeline.autoWidthAnimationTime, | |
4372 'linear', function(){timeline._autoResizing = false;}); | |
4373 } | |
4374 } | |
4375 | |
4376 function checkTimelineWidth() { | |
4377 var targetWidth = 0; // the new desired width | |
4378 var currentWidth = timeline.getPixelWidth(); | |
4379 | |
4380 if (timeline._autoResizing) { | |
4381 return; // early return | |
4382 } | |
4383 | |
4384 // compute targetWidth | |
4385 for (var i = 0; i < timeline._bands.length; i++) { | |
4386 timeline._bands[i].checkAutoWidth(); | |
4387 targetWidth += timeline._bandInfos[i].width; | |
4388 } | |
4389 | |
4390 if (targetWidth > currentWidth || okToShrink) { | |
4391 // yes, let's change the size | |
4392 newWidth = targetWidth; | |
4393 changeTimelineWidth(); | |
4394 timeline._distributeWidths(); | |
4395 } | |
4396 } | |
4397 | |
4398 // function's mainline | |
4399 if (!timeline.autoWidth) { | |
4400 return; // early return | |
4401 } | |
4402 | |
4403 checkTimelineWidth(); | |
4404 }; | |
4405 | |
4406 Timeline._Impl.prototype._initialize = function() { | |
4407 var containerDiv = this._containerDiv; | |
4408 var doc = containerDiv.ownerDocument; | |
4409 | |
4410 containerDiv.className = | |
4411 containerDiv.className.split(" ").concat("timeline-container").join(" "); | |
4412 | |
4413 /* | |
4414 * Set css-class on container div that will define orientation | |
4415 */ | |
4416 var orientation = (this.isHorizontal()) ? 'horizontal' : 'vertical' | |
4417 containerDiv.className +=' timeline-'+orientation; | |
4418 | |
4419 | |
4420 while (containerDiv.firstChild) { | |
4421 containerDiv.removeChild(containerDiv.firstChild); | |
4422 } | |
4423 | |
4424 /* | |
4425 * inserting copyright and link | |
4426 */ | |
4427 var elmtCopyright = SimileAjax.Graphics.createTranslucentImage(Timeline.urlPrefix + (this.isHorizontal() ? "images/copyright-vertical.png" : "images/copyright.png")); | |
4428 elmtCopyright.className = "timeline-copyright"; | |
4429 elmtCopyright.title = "SIMILE Timeline - http://www.simile-widgets.org/"; | |
4430 SimileAjax.DOM.registerEvent(elmtCopyright, "click", function() { window.location = "http://www.simile-widgets.org/"; }); | |
4431 containerDiv.appendChild(elmtCopyright); | |
4432 | |
4433 /* | |
4434 * creating bands | |
4435 */ | |
4436 this._bands = []; | |
4437 for (var i = 0; i < this._bandInfos.length; i++) { | |
4438 var band = new Timeline._Band(this, this._bandInfos[i], i); | |
4439 this._bands.push(band); | |
4440 } | |
4441 this._distributeWidths(); | |
4442 | |
4443 /* | |
4444 * sync'ing bands | |
4445 */ | |
4446 for (var i = 0; i < this._bandInfos.length; i++) { | |
4447 var bandInfo = this._bandInfos[i]; | |
4448 if ("syncWith" in bandInfo) { | |
4449 this._bands[i].setSyncWithBand( | |
4450 this._bands[bandInfo.syncWith], | |
4451 ("highlight" in bandInfo) ? bandInfo.highlight : false | |
4452 ); | |
4453 } | |
4454 } | |
4455 | |
4456 | |
4457 if (this.autoWidth) { | |
4458 for (var i = 0; i < this._bands.length; i++) { | |
4459 this._bands[i].addOnScrollListener(this._autoWidthScrollListener); | |
4460 } | |
4461 } | |
4462 | |
4463 | |
4464 /* | |
4465 * creating loading UI | |
4466 */ | |
4467 var message = SimileAjax.Graphics.createMessageBubble(doc); | |
4468 message.containerDiv.className = "timeline-message-container"; | |
4469 containerDiv.appendChild(message.containerDiv); | |
4470 | |
4471 message.contentDiv.className = "timeline-message"; | |
4472 message.contentDiv.innerHTML = "<img src='" + Timeline.urlPrefix + "images/progress-running.gif' /> Loading..."; | |
4473 | |
4474 this.showLoadingMessage = function() { message.containerDiv.style.display = "block"; }; | |
4475 this.hideLoadingMessage = function() { message.containerDiv.style.display = "none"; }; | |
4476 }; | |
4477 | |
4478 Timeline._Impl.prototype._distributeWidths = function() { | |
4479 var length = this.getPixelLength(); | |
4480 var width = this.getPixelWidth(); | |
4481 var cumulativeWidth = 0; | |
4482 | |
4483 for (var i = 0; i < this._bands.length; i++) { | |
4484 var band = this._bands[i]; | |
4485 var bandInfos = this._bandInfos[i]; | |
4486 var widthString = bandInfos.width; | |
4487 var bandWidth; | |
4488 | |
4489 if (typeof widthString == 'string') { | |
4490 var x = widthString.indexOf("%"); | |
4491 if (x > 0) { | |
4492 var percent = parseInt(widthString.substr(0, x)); | |
4493 bandWidth = Math.round(percent * width / 100); | |
4494 } else { | |
4495 bandWidth = parseInt(widthString); | |
4496 } | |
4497 } else { | |
4498 // was given an integer | |
4499 bandWidth = widthString; | |
4500 } | |
4501 | |
4502 band.setBandShiftAndWidth(cumulativeWidth, bandWidth); | |
4503 band.setViewLength(length); | |
4504 | |
4505 cumulativeWidth += bandWidth; | |
4506 } | |
4507 }; | |
4508 | |
4509 Timeline._Impl.prototype.shiftOK = function(index, shift) { | |
4510 // Returns true if the proposed shift is ok | |
4511 // | |
4512 // Positive shift means going back in time | |
4513 var going_back = shift > 0, | |
4514 going_forward = shift < 0; | |
4515 | |
4516 // Is there an edge? | |
4517 if ((going_back && this.timeline_start == null) || | |
4518 (going_forward && this.timeline_stop == null) || | |
4519 (shift == 0)) { | |
4520 return (true); // early return | |
4521 } | |
4522 | |
4523 // If any of the bands has noted that it is changing the others, | |
4524 // then this shift is a secondary shift in reaction to the real shift, | |
4525 // which already happened. In such cases, ignore it. (The issue is | |
4526 // that a positive original shift can cause a negative secondary shift, | |
4527 // as the bands adjust.) | |
4528 var secondary_shift = false; | |
4529 for (var i = 0; i < this._bands.length && !secondary_shift; i++) { | |
4530 secondary_shift = this._bands[i].busy(); | |
4531 } | |
4532 if (secondary_shift) { | |
4533 return(true); // early return | |
4534 } | |
4535 | |
4536 // If we are already at an edge, then don't even think about going any further | |
4537 if ((going_back && this.timeline_at_start) || | |
4538 (going_forward && this.timeline_at_stop)) { | |
4539 return (false); // early return | |
4540 } | |
4541 | |
4542 // Need to check all the bands | |
4543 var ok = false; // return value | |
4544 // If any of the bands will be or are showing an ok date, then let the shift proceed. | |
4545 for (var i = 0; i < this._bands.length && !ok; i++) { | |
4546 var band = this._bands[i]; | |
4547 if (going_back) { | |
4548 ok = (i == index ? band.getMinVisibleDateAfterDelta(shift) : band.getMinVisibleDate()) | |
4549 >= this.timeline_start; | |
4550 } else { | |
4551 ok = (i == index ? band.getMaxVisibleDateAfterDelta(shift) : band.getMaxVisibleDate()) | |
4552 <= this.timeline_stop; | |
4553 } | |
4554 } | |
4555 | |
4556 // process results | |
4557 if (going_back) { | |
4558 this.timeline_at_start = !ok; | |
4559 this.timeline_at_stop = false; | |
4560 } else { | |
4561 this.timeline_at_stop = !ok; | |
4562 this.timeline_at_start = false; | |
4563 } | |
4564 // This is where you could have an effect once per hitting an | |
4565 // edge of the Timeline. Eg jitter the Timeline | |
4566 //if (!ok) { | |
4567 //alert(going_back ? "At beginning" : "At end"); | |
4568 //} | |
4569 return (ok); | |
4570 }; | |
4571 | |
4572 Timeline._Impl.prototype.zoom = function (zoomIn, x, y, target) { | |
4573 var matcher = new RegExp("^timeline-band-([0-9]+)$"); | |
4574 var bandIndex = null; | |
4575 | |
4576 var result = matcher.exec(target.id); | |
4577 if (result) { | |
4578 bandIndex = parseInt(result[1]); | |
4579 } | |
4580 | |
4581 if (bandIndex != null) { | |
4582 this._bands[bandIndex].zoom(zoomIn, x, y, target); | |
4583 } | |
4584 | |
4585 this.paint(); | |
4586 }; | |
4587 | |
4588 /*================================================= | |
4589 * | |
4590 * Coding standards: | |
4591 * | |
4592 * We aim towards Douglas Crockford's Javascript conventions. | |
4593 * See: http://javascript.crockford.com/code.html | |
4594 * See also: http://www.crockford.com/javascript/javascript.html | |
4595 * | |
4596 * That said, this JS code was written before some recent JS | |
4597 * support libraries became widely used or available. | |
4598 * In particular, the _ character is used to indicate a class function or | |
4599 * variable that should be considered private to the class. | |
4600 * | |
4601 * The code mostly uses accessor methods for getting/setting the private | |
4602 * class variables. | |
4603 * | |
4604 * Over time, we'd like to formalize the convention by using support libraries | |
4605 * which enforce privacy in objects. | |
4606 * | |
4607 * We also want to use jslint: http://www.jslint.com/ | |
4608 * | |
4609 * | |
4610 *================================================== | |
4611 */ | |
4612 | |
4613 | |
4614 | |
4615 /*================================================== | |
4616 * Band | |
4617 *================================================== | |
4618 */ | |
4619 Timeline._Band = function(timeline, bandInfo, index) { | |
4620 // Set up the band's object | |
4621 | |
4622 // Munge params: If autoWidth is on for the Timeline, then ensure that | |
4623 // bandInfo.width is an integer | |
4624 if (timeline.autoWidth && typeof bandInfo.width == 'string') { | |
4625 bandInfo.width = bandInfo.width.indexOf("%") > -1 ? 0 : parseInt(bandInfo.width); | |
4626 } | |
4627 | |
4628 this._timeline = timeline; | |
4629 this._bandInfo = bandInfo; | |
4630 | |
4631 this._index = index; | |
4632 | |
4633 this._locale = ("locale" in bandInfo) ? bandInfo.locale : Timeline.getDefaultLocale(); | |
4634 this._timeZone = ("timeZone" in bandInfo) ? bandInfo.timeZone : 0; | |
4635 this._labeller = ("labeller" in bandInfo) ? bandInfo.labeller : | |
4636 (("createLabeller" in timeline.getUnit()) ? | |
4637 timeline.getUnit().createLabeller(this._locale, this._timeZone) : | |
4638 new Timeline.GregorianDateLabeller(this._locale, this._timeZone)); | |
4639 this._theme = bandInfo.theme; | |
4640 this._zoomIndex = ("zoomIndex" in bandInfo) ? bandInfo.zoomIndex : 0; | |
4641 this._zoomSteps = ("zoomSteps" in bandInfo) ? bandInfo.zoomSteps : null; | |
4642 | |
4643 this._dragging = false; | |
4644 this._changing = false; | |
4645 this._originalScrollSpeed = 5; // pixels | |
4646 this._scrollSpeed = this._originalScrollSpeed; | |
4647 this._viewOrthogonalOffset = 0; // vertical offset if the timeline is horizontal, and vice versa | |
4648 this._onScrollListeners = []; | |
4649 this._onOrthogonalScrollListeners = []; | |
4650 | |
4651 var b = this; | |
4652 this._syncWithBand = null; | |
4653 this._syncWithBandHandler = function(band) { | |
4654 b._onHighlightBandScroll(); | |
4655 }; | |
4656 this._syncWithBandOrthogonalScrollHandler = function(band) { | |
4657 b._onHighlightBandOrthogonalScroll(); | |
4658 }; | |
4659 this._selectorListener = function(band) { | |
4660 b._onHighlightBandScroll(); | |
4661 }; | |
4662 | |
4663 /* | |
4664 * Install a textbox to capture keyboard events | |
4665 */ | |
4666 var inputDiv = this._timeline.getDocument().createElement("div"); | |
4667 inputDiv.className = "timeline-band-input"; | |
4668 this._timeline.addDiv(inputDiv); | |
4669 | |
4670 this._keyboardInput = document.createElement("input"); | |
4671 this._keyboardInput.type = "text"; | |
4672 inputDiv.appendChild(this._keyboardInput); | |
4673 SimileAjax.DOM.registerEventWithObject(this._keyboardInput, "keydown", this, "_onKeyDown"); | |
4674 SimileAjax.DOM.registerEventWithObject(this._keyboardInput, "keyup", this, "_onKeyUp"); | |
4675 | |
4676 /* | |
4677 * The band's outer most div that slides with respect to the timeline's div | |
4678 */ | |
4679 this._div = this._timeline.getDocument().createElement("div"); | |
4680 this._div.id = "timeline-band-" + index; | |
4681 this._div.className = "timeline-band timeline-band-" + index; | |
4682 this._timeline.addDiv(this._div); | |
4683 | |
4684 SimileAjax.DOM.registerEventWithObject(this._div, "mousedown", this, "_onMouseDown"); | |
4685 SimileAjax.DOM.registerEventWithObject(this._div, "mousemove", this, "_onMouseMove"); | |
4686 SimileAjax.DOM.registerEventWithObject(this._div, "mouseup", this, "_onMouseUp"); | |
4687 SimileAjax.DOM.registerEventWithObject(this._div, "mouseout", this, "_onMouseOut"); | |
4688 SimileAjax.DOM.registerEventWithObject(this._div, "dblclick", this, "_onDblClick"); | |
4689 | |
4690 var mouseWheel = this._theme!= null ? this._theme.mouseWheel : 'scroll'; // theme is not always defined | |
4691 if (mouseWheel === 'zoom' || mouseWheel === 'scroll' || this._zoomSteps) { | |
4692 // capture mouse scroll | |
4693 if (SimileAjax.Platform.browser.isFirefox) { | |
4694 SimileAjax.DOM.registerEventWithObject(this._div, "DOMMouseScroll", this, "_onMouseScroll"); | |
4695 } else { | |
4696 SimileAjax.DOM.registerEventWithObject(this._div, "mousewheel", this, "_onMouseScroll"); | |
4697 } | |
4698 } | |
4699 | |
4700 /* | |
4701 * The inner div that contains layers | |
4702 */ | |
4703 this._innerDiv = this._timeline.getDocument().createElement("div"); | |
4704 this._innerDiv.className = "timeline-band-inner"; | |
4705 this._div.appendChild(this._innerDiv); | |
4706 | |
4707 /* | |
4708 * Initialize parts of the band | |
4709 */ | |
4710 this._ether = bandInfo.ether; | |
4711 bandInfo.ether.initialize(this, timeline); | |
4712 | |
4713 this._etherPainter = bandInfo.etherPainter; | |
4714 bandInfo.etherPainter.initialize(this, timeline); | |
4715 | |
4716 this._eventSource = bandInfo.eventSource; | |
4717 if (this._eventSource) { | |
4718 this._eventListener = { | |
4719 onAddMany: function() { b._onAddMany(); }, | |
4720 onClear: function() { b._onClear(); } | |
4721 } | |
4722 this._eventSource.addListener(this._eventListener); | |
4723 } | |
4724 | |
4725 this._eventPainter = bandInfo.eventPainter; | |
4726 this._eventTracksNeeded = 0; // set by painter via updateEventTrackInfo | |
4727 this._eventTrackIncrement = 0; | |
4728 bandInfo.eventPainter.initialize(this, timeline); | |
4729 | |
4730 this._decorators = ("decorators" in bandInfo) ? bandInfo.decorators : []; | |
4731 for (var i = 0; i < this._decorators.length; i++) { | |
4732 this._decorators[i].initialize(this, timeline); | |
4733 } | |
4734 | |
4735 this._supportsOrthogonalScrolling = ("supportsOrthogonalScrolling" in this._eventPainter) && | |
4736 this._eventPainter.supportsOrthogonalScrolling(); | |
4737 | |
4738 if (this._supportsOrthogonalScrolling) { | |
4739 this._scrollBar = this._timeline.getDocument().createElement("div"); | |
4740 this._scrollBar.id = "timeline-band-scrollbar-" + index; | |
4741 this._scrollBar.className = "timeline-band-scrollbar"; | |
4742 this._timeline.addDiv(this._scrollBar); | |
4743 | |
4744 this._scrollBar.innerHTML = '<div class="timeline-band-scrollbar-thumb"> </div>' | |
4745 } | |
4746 }; | |
4747 | |
4748 Timeline._Band.SCROLL_MULTIPLES = 5; | |
4749 | |
4750 Timeline._Band.prototype.dispose = function() { | |
4751 this.closeBubble(); | |
4752 | |
4753 if (this._eventSource) { | |
4754 this._eventSource.removeListener(this._eventListener); | |
4755 this._eventListener = null; | |
4756 this._eventSource = null; | |
4757 } | |
4758 | |
4759 this._timeline = null; | |
4760 this._bandInfo = null; | |
4761 | |
4762 this._labeller = null; | |
4763 this._ether = null; | |
4764 this._etherPainter = null; | |
4765 this._eventPainter = null; | |
4766 this._decorators = null; | |
4767 | |
4768 this._onScrollListeners = null; | |
4769 this._syncWithBandHandler = null; | |
4770 this._syncWithBandOrthogonalScrollHandler = null; | |
4771 this._selectorListener = null; | |
4772 | |
4773 this._div = null; | |
4774 this._innerDiv = null; | |
4775 this._keyboardInput = null; | |
4776 this._scrollBar = null; | |
4777 }; | |
4778 | |
4779 Timeline._Band.prototype.addOnScrollListener = function(listener) { | |
4780 this._onScrollListeners.push(listener); | |
4781 }; | |
4782 | |
4783 Timeline._Band.prototype.removeOnScrollListener = function(listener) { | |
4784 for (var i = 0; i < this._onScrollListeners.length; i++) { | |
4785 if (this._onScrollListeners[i] == listener) { | |
4786 this._onScrollListeners.splice(i, 1); | |
4787 break; | |
4788 } | |
4789 } | |
4790 }; | |
4791 | |
4792 Timeline._Band.prototype.addOnOrthogonalScrollListener = function(listener) { | |
4793 this._onOrthogonalScrollListeners.push(listener); | |
4794 }; | |
4795 | |
4796 Timeline._Band.prototype.removeOnOrthogonalScrollListener = function(listener) { | |
4797 for (var i = 0; i < this._onOrthogonalScrollListeners.length; i++) { | |
4798 if (this._onOrthogonalScrollListeners[i] == listener) { | |
4799 this._onOrthogonalScrollListeners.splice(i, 1); | |
4800 break; | |
4801 } | |
4802 } | |
4803 }; | |
4804 | |
4805 Timeline._Band.prototype.setSyncWithBand = function(band, highlight) { | |
4806 if (this._syncWithBand) { | |
4807 this._syncWithBand.removeOnScrollListener(this._syncWithBandHandler); | |
4808 this._syncWithBand.removeOnOrthogonalScrollListener(this._syncWithBandOrthogonalScrollHandler); | |
4809 } | |
4810 | |
4811 this._syncWithBand = band; | |
4812 this._syncWithBand.addOnScrollListener(this._syncWithBandHandler); | |
4813 this._syncWithBand.addOnOrthogonalScrollListener(this._syncWithBandOrthogonalScrollHandler); | |
4814 this._highlight = highlight; | |
4815 this._positionHighlight(); | |
4816 }; | |
4817 | |
4818 Timeline._Band.prototype.getLocale = function() { | |
4819 return this._locale; | |
4820 }; | |
4821 | |
4822 Timeline._Band.prototype.getTimeZone = function() { | |
4823 return this._timeZone; | |
4824 }; | |
4825 | |
4826 Timeline._Band.prototype.getLabeller = function() { | |
4827 return this._labeller; | |
4828 }; | |
4829 | |
4830 Timeline._Band.prototype.getIndex = function() { | |
4831 return this._index; | |
4832 }; | |
4833 | |
4834 Timeline._Band.prototype.getEther = function() { | |
4835 return this._ether; | |
4836 }; | |
4837 | |
4838 Timeline._Band.prototype.getEtherPainter = function() { | |
4839 return this._etherPainter; | |
4840 }; | |
4841 | |
4842 Timeline._Band.prototype.getEventSource = function() { | |
4843 return this._eventSource; | |
4844 }; | |
4845 | |
4846 Timeline._Band.prototype.getEventPainter = function() { | |
4847 return this._eventPainter; | |
4848 }; | |
4849 | |
4850 Timeline._Band.prototype.getTimeline = function() { | |
4851 return this._timeline; | |
4852 }; | |
4853 | |
4854 // Autowidth support | |
4855 Timeline._Band.prototype.updateEventTrackInfo = function(tracks, increment) { | |
4856 this._eventTrackIncrement = increment; // doesn't vary for a specific band | |
4857 | |
4858 if (tracks > this._eventTracksNeeded) { | |
4859 this._eventTracksNeeded = tracks; | |
4860 } | |
4861 }; | |
4862 | |
4863 // Autowidth support | |
4864 Timeline._Band.prototype.checkAutoWidth = function() { | |
4865 // if a new (larger) width is needed by the band | |
4866 // then: a) updates the band's bandInfo.width | |
4867 // | |
4868 // desiredWidth for the band is | |
4869 // (number of tracks + margin) * track increment | |
4870 if (! this._timeline.autoWidth) { | |
4871 return; // early return | |
4872 } | |
4873 | |
4874 var overviewBand = this._eventPainter.getType() == 'overview'; | |
4875 var margin = overviewBand ? | |
4876 this._theme.event.overviewTrack.autoWidthMargin : | |
4877 this._theme.event.track.autoWidthMargin; | |
4878 var desiredWidth = Math.ceil((this._eventTracksNeeded + margin) * | |
4879 this._eventTrackIncrement); | |
4880 // add offset amount (additional margin) | |
4881 desiredWidth += overviewBand ? this._theme.event.overviewTrack.offset : | |
4882 this._theme.event.track.offset; | |
4883 var bandInfo = this._bandInfo; | |
4884 | |
4885 if (desiredWidth != bandInfo.width) { | |
4886 bandInfo.width = desiredWidth; | |
4887 } | |
4888 }; | |
4889 | |
4890 Timeline._Band.prototype.layout = function() { | |
4891 this.paint(); | |
4892 }; | |
4893 | |
4894 Timeline._Band.prototype.paint = function() { | |
4895 this._etherPainter.paint(); | |
4896 this._paintDecorators(); | |
4897 this._paintEvents(); | |
4898 }; | |
4899 | |
4900 Timeline._Band.prototype.softLayout = function() { | |
4901 this.softPaint(); | |
4902 }; | |
4903 | |
4904 Timeline._Band.prototype.softPaint = function() { | |
4905 this._etherPainter.softPaint(); | |
4906 this._softPaintDecorators(); | |
4907 this._softPaintEvents(); | |
4908 }; | |
4909 | |
4910 Timeline._Band.prototype.setBandShiftAndWidth = function(shift, width) { | |
4911 var inputDiv = this._keyboardInput.parentNode; | |
4912 var middle = shift + Math.floor(width / 2); | |
4913 if (this._timeline.isHorizontal()) { | |
4914 this._div.style.top = shift + "px"; | |
4915 this._div.style.height = width + "px"; | |
4916 | |
4917 inputDiv.style.top = middle + "px"; | |
4918 inputDiv.style.left = "-1em"; | |
4919 } else { | |
4920 this._div.style.left = shift + "px"; | |
4921 this._div.style.width = width + "px"; | |
4922 | |
4923 inputDiv.style.left = middle + "px"; | |
4924 inputDiv.style.top = "-1em"; | |
4925 } | |
4926 }; | |
4927 | |
4928 Timeline._Band.prototype.getViewWidth = function() { | |
4929 if (this._timeline.isHorizontal()) { | |
4930 return this._div.offsetHeight; | |
4931 } else { | |
4932 return this._div.offsetWidth; | |
4933 } | |
4934 }; | |
4935 | |
4936 Timeline._Band.prototype.setViewLength = function(length) { | |
4937 this._viewLength = length; | |
4938 this._recenterDiv(); | |
4939 this._onChanging(); | |
4940 }; | |
4941 | |
4942 Timeline._Band.prototype.getViewLength = function() { | |
4943 return this._viewLength; | |
4944 }; | |
4945 | |
4946 Timeline._Band.prototype.getTotalViewLength = function() { | |
4947 return Timeline._Band.SCROLL_MULTIPLES * this._viewLength; | |
4948 }; | |
4949 | |
4950 Timeline._Band.prototype.getViewOffset = function() { | |
4951 return this._viewOffset; | |
4952 }; | |
4953 | |
4954 Timeline._Band.prototype.getMinDate = function() { | |
4955 return this._ether.pixelOffsetToDate(this._viewOffset); | |
4956 }; | |
4957 | |
4958 Timeline._Band.prototype.getMaxDate = function() { | |
4959 return this._ether.pixelOffsetToDate(this._viewOffset + Timeline._Band.SCROLL_MULTIPLES * this._viewLength); | |
4960 }; | |
4961 | |
4962 Timeline._Band.prototype.getMinVisibleDate = function() { | |
4963 return this._ether.pixelOffsetToDate(0); | |
4964 }; | |
4965 | |
4966 Timeline._Band.prototype.getMinVisibleDateAfterDelta = function(delta) { | |
4967 return this._ether.pixelOffsetToDate(delta); | |
4968 }; | |
4969 | |
4970 Timeline._Band.prototype.getMaxVisibleDate = function() { | |
4971 // Max date currently visible on band | |
4972 return this._ether.pixelOffsetToDate(this._viewLength); | |
4973 }; | |
4974 | |
4975 Timeline._Band.prototype.getMaxVisibleDateAfterDelta = function(delta) { | |
4976 // Max date visible on band after delta px view change is applied | |
4977 return this._ether.pixelOffsetToDate(this._viewLength + delta); | |
4978 }; | |
4979 | |
4980 Timeline._Band.prototype.getCenterVisibleDate = function() { | |
4981 return this._ether.pixelOffsetToDate(this._viewLength / 2); | |
4982 }; | |
4983 | |
4984 Timeline._Band.prototype.setMinVisibleDate = function(date) { | |
4985 if (!this._changing) { | |
4986 this._moveEther(Math.round(-this._ether.dateToPixelOffset(date))); | |
4987 } | |
4988 }; | |
4989 | |
4990 Timeline._Band.prototype.setMaxVisibleDate = function(date) { | |
4991 if (!this._changing) { | |
4992 this._moveEther(Math.round(this._viewLength - this._ether.dateToPixelOffset(date))); | |
4993 } | |
4994 }; | |
4995 | |
4996 Timeline._Band.prototype.setCenterVisibleDate = function(date) { | |
4997 if (!this._changing) { | |
4998 this._moveEther(Math.round(this._viewLength / 2 - this._ether.dateToPixelOffset(date))); | |
4999 } | |
5000 }; | |
5001 | |
5002 Timeline._Band.prototype.dateToPixelOffset = function(date) { | |
5003 return this._ether.dateToPixelOffset(date) - this._viewOffset; | |
5004 }; | |
5005 | |
5006 Timeline._Band.prototype.pixelOffsetToDate = function(pixels) { | |
5007 return this._ether.pixelOffsetToDate(pixels + this._viewOffset); | |
5008 }; | |
5009 | |
5010 Timeline._Band.prototype.getViewOrthogonalOffset = function() { | |
5011 return this._viewOrthogonalOffset; | |
5012 }; | |
5013 | |
5014 Timeline._Band.prototype.setViewOrthogonalOffset = function(offset) { | |
5015 this._viewOrthogonalOffset = Math.max(0, offset); | |
5016 }; | |
5017 | |
5018 Timeline._Band.prototype.createLayerDiv = function(zIndex, className) { | |
5019 var div = this._timeline.getDocument().createElement("div"); | |
5020 div.className = "timeline-band-layer" + (typeof className == "string" ? (" " + className) : ""); | |
5021 div.style.zIndex = zIndex; | |
5022 this._innerDiv.appendChild(div); | |
5023 | |
5024 var innerDiv = this._timeline.getDocument().createElement("div"); | |
5025 innerDiv.className = "timeline-band-layer-inner"; | |
5026 if (SimileAjax.Platform.browser.isIE) { | |
5027 innerDiv.style.cursor = "move"; | |
5028 } else { | |
5029 innerDiv.style.cursor = "-moz-grab"; | |
5030 } | |
5031 div.appendChild(innerDiv); | |
5032 | |
5033 return innerDiv; | |
5034 }; | |
5035 | |
5036 Timeline._Band.prototype.removeLayerDiv = function(div) { | |
5037 this._innerDiv.removeChild(div.parentNode); | |
5038 }; | |
5039 | |
5040 Timeline._Band.prototype.scrollToCenter = function(date, f) { | |
5041 var pixelOffset = this._ether.dateToPixelOffset(date); | |
5042 if (pixelOffset < -this._viewLength / 2) { | |
5043 this.setCenterVisibleDate(this.pixelOffsetToDate(pixelOffset + this._viewLength)); | |
5044 } else if (pixelOffset > 3 * this._viewLength / 2) { | |
5045 this.setCenterVisibleDate(this.pixelOffsetToDate(pixelOffset - this._viewLength)); | |
5046 } | |
5047 this._autoScroll(Math.round(this._viewLength / 2 - this._ether.dateToPixelOffset(date)), f); | |
5048 }; | |
5049 | |
5050 Timeline._Band.prototype.showBubbleForEvent = function(eventID) { | |
5051 var evt = this.getEventSource().getEvent(eventID); | |
5052 if (evt) { | |
5053 var self = this; | |
5054 this.scrollToCenter(evt.getStart(), function() { | |
5055 self._eventPainter.showBubble(evt); | |
5056 }); | |
5057 } | |
5058 }; | |
5059 | |
5060 Timeline._Band.prototype.zoom = function(zoomIn, x, y, target) { | |
5061 if (!this._zoomSteps) { | |
5062 // zoom disabled | |
5063 return; | |
5064 } | |
5065 | |
5066 // shift the x value by our offset | |
5067 x += this._viewOffset; | |
5068 | |
5069 var zoomDate = this._ether.pixelOffsetToDate(x); | |
5070 var netIntervalChange = this._ether.zoom(zoomIn); | |
5071 this._etherPainter.zoom(netIntervalChange); | |
5072 | |
5073 // shift our zoom date to the far left | |
5074 this._moveEther(Math.round(-this._ether.dateToPixelOffset(zoomDate))); | |
5075 // then shift it back to where the mouse was | |
5076 this._moveEther(x); | |
5077 }; | |
5078 | |
5079 Timeline._Band.prototype._onMouseDown = function(innerFrame, evt, target) { | |
5080 this.closeBubble(); | |
5081 | |
5082 this._dragging = true; | |
5083 this._dragX = evt.clientX; | |
5084 this._dragY = evt.clientY; | |
5085 }; | |
5086 | |
5087 Timeline._Band.prototype._onMouseMove = function(innerFrame, evt, target) { | |
5088 if (this._dragging) { | |
5089 var diffX = evt.clientX - this._dragX; | |
5090 var diffY = evt.clientY - this._dragY; | |
5091 | |
5092 this._dragX = evt.clientX; | |
5093 this._dragY = evt.clientY; | |
5094 | |
5095 if (this._timeline.isHorizontal()) { | |
5096 this._moveEther(diffX, diffY); | |
5097 } else { | |
5098 this._moveEther(diffY, diffX); | |
5099 } | |
5100 this._positionHighlight(); | |
5101 this._showScrollbar(); | |
5102 } | |
5103 }; | |
5104 | |
5105 Timeline._Band.prototype._onMouseUp = function(innerFrame, evt, target) { | |
5106 this._dragging = false; | |
5107 this._keyboardInput.focus(); | |
5108 this._bounceBack(); | |
5109 }; | |
5110 | |
5111 Timeline._Band.prototype._onMouseOut = function(innerFrame, evt, target) { | |
5112 var coords = SimileAjax.DOM.getEventRelativeCoordinates(evt, innerFrame); | |
5113 coords.x += this._viewOffset; | |
5114 if (coords.x < 0 || coords.x > innerFrame.offsetWidth || | |
5115 coords.y < 0 || coords.y > innerFrame.offsetHeight) { | |
5116 this._dragging = false; | |
5117 this._bounceBack(); | |
5118 } | |
5119 }; | |
5120 | |
5121 Timeline._Band.prototype._onMouseScroll = function(innerFrame, evt, target) { | |
5122 var now = new Date(); | |
5123 now = now.getTime(); | |
5124 | |
5125 if (!this._lastScrollTime || ((now - this._lastScrollTime) > 50)) { | |
5126 // limit 1 scroll per 200ms due to FF3 sending multiple events back to back | |
5127 this._lastScrollTime = now; | |
5128 | |
5129 var delta = 0; | |
5130 if (evt.wheelDelta) { | |
5131 delta = evt.wheelDelta/120; | |
5132 } else if (evt.detail) { | |
5133 delta = -evt.detail/3; | |
5134 } | |
5135 | |
5136 // either scroll or zoom | |
5137 var mouseWheel = this._theme.mouseWheel; | |
5138 | |
5139 if (this._zoomSteps || mouseWheel === 'zoom') { | |
5140 var loc = SimileAjax.DOM.getEventRelativeCoordinates(evt, innerFrame); | |
5141 if (delta != 0) { | |
5142 var zoomIn; | |
5143 if (delta > 0) | |
5144 zoomIn = true; | |
5145 if (delta < 0) | |
5146 zoomIn = false; | |
5147 // call zoom on the timeline so we could zoom multiple bands if desired | |
5148 this._timeline.zoom(zoomIn, loc.x, loc.y, innerFrame); | |
5149 } | |
5150 } | |
5151 else if (mouseWheel === 'scroll') { | |
5152 var move_amt = 50 * (delta < 0 ? -1 : 1); | |
5153 this._moveEther(move_amt); | |
5154 } | |
5155 } | |
5156 | |
5157 // prevent bubble | |
5158 if (evt.stopPropagation) { | |
5159 evt.stopPropagation(); | |
5160 } | |
5161 evt.cancelBubble = true; | |
5162 | |
5163 // prevent the default action | |
5164 if (evt.preventDefault) { | |
5165 evt.preventDefault(); | |
5166 } | |
5167 evt.returnValue = false; | |
5168 }; | |
5169 | |
5170 Timeline._Band.prototype._onDblClick = function(innerFrame, evt, target) { | |
5171 var coords = SimileAjax.DOM.getEventRelativeCoordinates(evt, innerFrame); | |
5172 var distance = coords.x - (this._viewLength / 2 - this._viewOffset); | |
5173 | |
5174 this._autoScroll(-distance); | |
5175 }; | |
5176 | |
5177 Timeline._Band.prototype._onKeyDown = function(keyboardInput, evt, target) { | |
5178 if (!this._dragging) { | |
5179 switch (evt.keyCode) { | |
5180 case 27: // ESC | |
5181 break; | |
5182 case 37: // left arrow | |
5183 case 38: // up arrow | |
5184 this._scrollSpeed = Math.min(50, Math.abs(this._scrollSpeed * 1.05)); | |
5185 this._moveEther(this._scrollSpeed); | |
5186 break; | |
5187 case 39: // right arrow | |
5188 case 40: // down arrow | |
5189 this._scrollSpeed = -Math.min(50, Math.abs(this._scrollSpeed * 1.05)); | |
5190 this._moveEther(this._scrollSpeed); | |
5191 break; | |
5192 default: | |
5193 return true; | |
5194 } | |
5195 this.closeBubble(); | |
5196 | |
5197 SimileAjax.DOM.cancelEvent(evt); | |
5198 return false; | |
5199 } | |
5200 return true; | |
5201 }; | |
5202 | |
5203 Timeline._Band.prototype._onKeyUp = function(keyboardInput, evt, target) { | |
5204 if (!this._dragging) { | |
5205 this._scrollSpeed = this._originalScrollSpeed; | |
5206 | |
5207 switch (evt.keyCode) { | |
5208 case 35: // end | |
5209 this.setCenterVisibleDate(this._eventSource.getLatestDate()); | |
5210 break; | |
5211 case 36: // home | |
5212 this.setCenterVisibleDate(this._eventSource.getEarliestDate()); | |
5213 break; | |
5214 case 33: // page up | |
5215 this._autoScroll(this._timeline.getPixelLength()); | |
5216 break; | |
5217 case 34: // page down | |
5218 this._autoScroll(-this._timeline.getPixelLength()); | |
5219 break; | |
5220 default: | |
5221 return true; | |
5222 } | |
5223 | |
5224 this.closeBubble(); | |
5225 | |
5226 SimileAjax.DOM.cancelEvent(evt); | |
5227 return false; | |
5228 } | |
5229 return true; | |
5230 }; | |
5231 | |
5232 Timeline._Band.prototype._autoScroll = function(distance, f) { | |
5233 var b = this; | |
5234 var a = SimileAjax.Graphics.createAnimation( | |
5235 function(abs, diff) { | |
5236 b._moveEther(diff); | |
5237 }, | |
5238 0, | |
5239 distance, | |
5240 1000, | |
5241 f | |
5242 ); | |
5243 a.run(); | |
5244 }; | |
5245 | |
5246 Timeline._Band.prototype._moveEther = function(shift, orthogonalShift) { | |
5247 if (orthogonalShift === undefined) { | |
5248 orthogonalShift = 0; | |
5249 } | |
5250 | |
5251 this.closeBubble(); | |
5252 | |
5253 // A positive shift means back in time | |
5254 // Check that we're not moving beyond Timeline's limits | |
5255 if (!this._timeline.shiftOK(this._index, shift)) { | |
5256 return; // early return | |
5257 } | |
5258 | |
5259 this._viewOffset += shift; | |
5260 this._ether.shiftPixels(-shift); | |
5261 if (this._timeline.isHorizontal()) { | |
5262 this._div.style.left = this._viewOffset + "px"; | |
5263 } else { | |
5264 this._div.style.top = this._viewOffset + "px"; | |
5265 } | |
5266 | |
5267 if (this._supportsOrthogonalScrolling) { | |
5268 this._viewOrthogonalOffset = this._viewOrthogonalOffset + orthogonalShift; | |
5269 } | |
5270 | |
5271 if (this._viewOffset > -this._viewLength * 0.5 || | |
5272 this._viewOffset < -this._viewLength * (Timeline._Band.SCROLL_MULTIPLES - 1.5)) { | |
5273 | |
5274 this._recenterDiv(); | |
5275 } else { | |
5276 this.softLayout(); | |
5277 } | |
5278 | |
5279 this._onChanging(); | |
5280 } | |
5281 | |
5282 Timeline._Band.prototype._onChanging = function() { | |
5283 this._changing = true; | |
5284 | |
5285 this._fireOnScroll(); | |
5286 this._setSyncWithBandDate(); | |
5287 | |
5288 this._changing = false; | |
5289 }; | |
5290 | |
5291 Timeline._Band.prototype.busy = function() { | |
5292 // Is this band busy changing other bands? | |
5293 return(this._changing); | |
5294 }; | |
5295 | |
5296 Timeline._Band.prototype._fireOnScroll = function() { | |
5297 for (var i = 0; i < this._onScrollListeners.length; i++) { | |
5298 this._onScrollListeners[i](this); | |
5299 } | |
5300 }; | |
5301 | |
5302 Timeline._Band.prototype._fireOnOrthogonalScroll = function() { | |
5303 for (var i = 0; i < this._onOrthogonalScrollListeners.length; i++) { | |
5304 this._onOrthogonalScrollListeners[i](this); | |
5305 } | |
5306 }; | |
5307 | |
5308 Timeline._Band.prototype._setSyncWithBandDate = function() { | |
5309 if (this._syncWithBand) { | |
5310 var centerDate = this._ether.pixelOffsetToDate(this.getViewLength() / 2); | |
5311 this._syncWithBand.setCenterVisibleDate(centerDate); | |
5312 } | |
5313 }; | |
5314 | |
5315 Timeline._Band.prototype._onHighlightBandScroll = function() { | |
5316 if (this._syncWithBand) { | |
5317 var centerDate = this._syncWithBand.getCenterVisibleDate(); | |
5318 var centerPixelOffset = this._ether.dateToPixelOffset(centerDate); | |
5319 | |
5320 this._moveEther(Math.round(this._viewLength / 2 - centerPixelOffset)); | |
5321 this._positionHighlight(); | |
5322 } | |
5323 }; | |
5324 | |
5325 Timeline._Band.prototype._onHighlightBandOrthogonalScroll = function() { | |
5326 if (this._syncWithBand) { | |
5327 this._positionHighlight(); | |
5328 } | |
5329 }; | |
5330 | |
5331 Timeline._Band.prototype._onAddMany = function() { | |
5332 this._paintEvents(); | |
5333 }; | |
5334 | |
5335 Timeline._Band.prototype._onClear = function() { | |
5336 this._paintEvents(); | |
5337 }; | |
5338 | |
5339 Timeline._Band.prototype._positionHighlight = function() { | |
5340 if (this._syncWithBand) { | |
5341 var startDate = this._syncWithBand.getMinVisibleDate(); | |
5342 var endDate = this._syncWithBand.getMaxVisibleDate(); | |
5343 | |
5344 if (this._highlight) { | |
5345 var offset = 0; // percent | |
5346 var extent = 1.0; // percent | |
5347 var syncEventPainter = this._syncWithBand.getEventPainter(); | |
5348 if ("supportsOrthogonalScrolling" in syncEventPainter && | |
5349 syncEventPainter.supportsOrthogonalScrolling()) { | |
5350 | |
5351 var orthogonalExtent = syncEventPainter.getOrthogonalExtent(); | |
5352 var visibleWidth = this._syncWithBand.getViewWidth(); | |
5353 var totalWidth = Math.max(visibleWidth, orthogonalExtent); | |
5354 extent = visibleWidth / totalWidth; | |
5355 offset = -this._syncWithBand.getViewOrthogonalOffset() / totalWidth; | |
5356 } | |
5357 | |
5358 this._etherPainter.setHighlight(startDate, endDate, offset, extent); | |
5359 } | |
5360 } | |
5361 }; | |
5362 | |
5363 Timeline._Band.prototype._recenterDiv = function() { | |
5364 this._viewOffset = -this._viewLength * (Timeline._Band.SCROLL_MULTIPLES - 1) / 2; | |
5365 if (this._timeline.isHorizontal()) { | |
5366 this._div.style.left = this._viewOffset + "px"; | |
5367 this._div.style.width = (Timeline._Band.SCROLL_MULTIPLES * this._viewLength) + "px"; | |
5368 } else { | |
5369 this._div.style.top = this._viewOffset + "px"; | |
5370 this._div.style.height = (Timeline._Band.SCROLL_MULTIPLES * this._viewLength) + "px"; | |
5371 } | |
5372 this.layout(); | |
5373 }; | |
5374 | |
5375 Timeline._Band.prototype._paintEvents = function() { | |
5376 this._eventPainter.paint(); | |
5377 this._showScrollbar(); | |
5378 this._fireOnOrthogonalScroll(); | |
5379 }; | |
5380 | |
5381 Timeline._Band.prototype._softPaintEvents = function() { | |
5382 this._eventPainter.softPaint(); | |
5383 }; | |
5384 | |
5385 Timeline._Band.prototype._paintDecorators = function() { | |
5386 for (var i = 0; i < this._decorators.length; i++) { | |
5387 this._decorators[i].paint(); | |
5388 } | |
5389 }; | |
5390 | |
5391 Timeline._Band.prototype._softPaintDecorators = function() { | |
5392 for (var i = 0; i < this._decorators.length; i++) { | |
5393 this._decorators[i].softPaint(); | |
5394 } | |
5395 }; | |
5396 | |
5397 Timeline._Band.prototype.closeBubble = function() { | |
5398 SimileAjax.WindowManager.cancelPopups(); | |
5399 }; | |
5400 | |
5401 Timeline._Band.prototype._bounceBack = function(f) { | |
5402 if (!this._supportsOrthogonalScrolling) { | |
5403 return; | |
5404 } | |
5405 | |
5406 var target = 0; | |
5407 if (this._viewOrthogonalOffset < 0) { | |
5408 var orthogonalExtent = this._eventPainter.getOrthogonalExtent(); | |
5409 if (this._viewOrthogonalOffset + orthogonalExtent >= this.getViewWidth()) { | |
5410 target = this._viewOrthogonalOffset; | |
5411 } else { | |
5412 target = Math.min(0, this.getViewWidth() - orthogonalExtent); | |
5413 } | |
5414 } | |
5415 | |
5416 if (target != this._viewOrthogonalOffset) { | |
5417 var self = this; | |
5418 SimileAjax.Graphics.createAnimation( | |
5419 function(abs, diff) { | |
5420 self._viewOrthogonalOffset = abs; | |
5421 self._eventPainter.softPaint(); | |
5422 self._showScrollbar(); | |
5423 self._fireOnOrthogonalScroll(); | |
5424 }, | |
5425 this._viewOrthogonalOffset, | |
5426 target, | |
5427 300, | |
5428 function() { | |
5429 self._hideScrollbar(); | |
5430 } | |
5431 ).run(); | |
5432 } else { | |
5433 this._hideScrollbar(); | |
5434 } | |
5435 }; | |
5436 | |
5437 Timeline._Band.prototype._showScrollbar = function() { | |
5438 if (!this._supportsOrthogonalScrolling) { | |
5439 return; | |
5440 } | |
5441 | |
5442 var orthogonalExtent = this._eventPainter.getOrthogonalExtent(); | |
5443 var visibleWidth = this.getViewWidth(); | |
5444 var totalWidth = Math.max(visibleWidth, orthogonalExtent); | |
5445 var ratio = (visibleWidth / totalWidth); | |
5446 var thumbWidth = Math.round(visibleWidth * ratio) + "px"; | |
5447 var thumbOffset = Math.round(-this._viewOrthogonalOffset * ratio) + "px"; | |
5448 | |
5449 var thumb = this._scrollBar.firstChild; | |
5450 if (this._timeline.isHorizontal()) { | |
5451 this._scrollBar.style.top = this._div.style.top; | |
5452 this._scrollBar.style.height = this._div.style.height; | |
5453 | |
5454 this._scrollBar.style.right = "0px"; | |
5455 this._scrollBar.style.width = "10px"; | |
5456 | |
5457 thumb.style.top = thumbOffset; | |
5458 thumb.style.height = thumbWidth; | |
5459 } else { | |
5460 this._scrollBar.style.left = this._div.style.left; | |
5461 this._scrollBar.style.width = this._div.style.width; | |
5462 | |
5463 this._scrollBar.style.bottom = "0px"; | |
5464 this._scrollBar.style.height = "10px"; | |
5465 | |
5466 thumb.style.left = thumbOffset; | |
5467 thumb.style.width = thumbWidth; | |
5468 } | |
5469 | |
5470 if (ratio >= 1 && this._viewOrthogonalOffset == 0) { | |
5471 this._scrollBar.style.display = "none"; | |
5472 } else { | |
5473 this._scrollBar.style.display = "block"; | |
5474 } | |
5475 }; | |
5476 | |
5477 Timeline._Band.prototype._hideScrollbar = function() { | |
5478 if (!this._supportsOrthogonalScrolling) { | |
5479 return; | |
5480 } | |
5481 //this._scrollBar.style.display = "none"; | |
5482 }; | |
5483 | |
5484 /*================================================== | |
5485 * Classic Theme | |
5486 *================================================== | |
5487 */ | |
5488 | |
5489 | |
5490 | |
5491 Timeline.ClassicTheme = new Object(); | |
5492 | |
5493 Timeline.ClassicTheme.implementations = []; | |
5494 | |
5495 Timeline.ClassicTheme.create = function(locale) { | |
5496 if (locale == null) { | |
5497 locale = Timeline.getDefaultLocale(); | |
5498 } | |
5499 | |
5500 var f = Timeline.ClassicTheme.implementations[locale]; | |
5501 if (f == null) { | |
5502 f = Timeline.ClassicTheme._Impl; | |
5503 } | |
5504 return new f(); | |
5505 }; | |
5506 | |
5507 Timeline.ClassicTheme._Impl = function() { | |
5508 this.firstDayOfWeek = 0; // Sunday | |
5509 | |
5510 // Note: Many styles previously set here are now set using CSS | |
5511 // The comments indicate settings controlled by CSS, not | |
5512 // lines to be un-commented. | |
5513 // | |
5514 // | |
5515 // Attributes autoWidth, autoWidthAnimationTime, timeline_start | |
5516 // and timeline_stop must be set on the first band's theme. | |
5517 // The other attributes can be set differently for each | |
5518 // band by using different themes for the bands. | |
5519 this.autoWidth = false; // Should the Timeline automatically grow itself, as | |
5520 // needed when too many events for the available width | |
5521 // are painted on the visible part of the Timeline? | |
5522 this.autoWidthAnimationTime = 500; // mSec | |
5523 this.timeline_start = null; // Setting a date, eg new Date(Date.UTC(2008,0,17,20,00,00,0)) will prevent the | |
5524 // Timeline from being moved to anytime before the date. | |
5525 this.timeline_stop = null; // Use for setting a maximum date. The Timeline will not be able | |
5526 // to be moved to anytime after this date. | |
5527 this.ether = { | |
5528 backgroundColors: [ | |
5529 // "#EEE", | |
5530 // "#DDD", | |
5531 // "#CCC", | |
5532 // "#AAA" | |
5533 ], | |
5534 // highlightColor: "white", | |
5535 highlightOpacity: 50, | |
5536 interval: { | |
5537 line: { | |
5538 show: true, | |
5539 opacity: 25 | |
5540 // color: "#aaa", | |
5541 }, | |
5542 weekend: { | |
5543 opacity: 30 | |
5544 // color: "#FFFFE0", | |
5545 }, | |
5546 marker: { | |
5547 hAlign: "Bottom", | |
5548 vAlign: "Right" | |
5549 /* | |
5550 hBottomStyler: function(elmt) { | |
5551 elmt.className = "timeline-ether-marker-bottom"; | |
5552 }, | |
5553 hBottomEmphasizedStyler: function(elmt) { | |
5554 elmt.className = "timeline-ether-marker-bottom-emphasized"; | |
5555 }, | |
5556 hTopStyler: function(elmt) { | |
5557 elmt.className = "timeline-ether-marker-top"; | |
5558 }, | |
5559 hTopEmphasizedStyler: function(elmt) { | |
5560 elmt.className = "timeline-ether-marker-top-emphasized"; | |
5561 }, | |
5562 */ | |
5563 | |
5564 | |
5565 /* | |
5566 vRightStyler: function(elmt) { | |
5567 elmt.className = "timeline-ether-marker-right"; | |
5568 }, | |
5569 vRightEmphasizedStyler: function(elmt) { | |
5570 elmt.className = "timeline-ether-marker-right-emphasized"; | |
5571 }, | |
5572 vLeftStyler: function(elmt) { | |
5573 elmt.className = "timeline-ether-marker-left"; | |
5574 }, | |
5575 vLeftEmphasizedStyler:function(elmt) { | |
5576 elmt.className = "timeline-ether-marker-left-emphasized"; | |
5577 } | |
5578 */ | |
5579 } | |
5580 } | |
5581 }; | |
5582 | |
5583 this.event = { | |
5584 track: { | |
5585 height: 10, // px. You will need to change the track | |
5586 // height if you change the tape height. | |
5587 gap: 2, // px. Gap between tracks | |
5588 offset: 2, // px. top margin above tapes | |
5589 autoWidthMargin: 1.5 | |
5590 /* autoWidthMargin is only used if autoWidth (see above) is true. | |
5591 The autoWidthMargin setting is used to set how close the bottom of the | |
5592 lowest track is to the edge of the band's div. The units are total track | |
5593 width (tape + label + gap). A min of 0.5 is suggested. Use this setting to | |
5594 move the bottom track's tapes above the axis markers, if needed for your | |
5595 Timeline. | |
5596 */ | |
5597 }, | |
5598 overviewTrack: { | |
5599 offset: 20, // px -- top margin above tapes | |
5600 tickHeight: 6, // px | |
5601 height: 2, // px | |
5602 gap: 1, // px | |
5603 autoWidthMargin: 5 // This attribute is only used if autoWidth (see above) is true. | |
5604 }, | |
5605 tape: { | |
5606 height: 4 // px. For thicker tapes, remember to change track height too. | |
5607 }, | |
5608 instant: { | |
5609 icon: Timeline.urlPrefix + "images/dull-blue-circle.png", | |
5610 // default icon. Icon can also be specified per event | |
5611 iconWidth: 10, | |
5612 iconHeight: 10, | |
5613 impreciseOpacity: 20, // opacity of the tape when durationEvent is false | |
5614 impreciseIconMargin: 3 // A tape and an icon are painted for imprecise instant | |
5615 // events. This attribute is the margin between the | |
5616 // bottom of the tape and the top of the icon in that | |
5617 // case. | |
5618 // color: "#58A0DC", | |
5619 // impreciseColor: "#58A0DC", | |
5620 }, | |
5621 duration: { | |
5622 impreciseOpacity: 20 // tape opacity for imprecise part of duration events | |
5623 // color: "#58A0DC", | |
5624 // impreciseColor: "#58A0DC", | |
5625 }, | |
5626 label: { | |
5627 backgroundOpacity: 50,// only used in detailed painter | |
5628 offsetFromLine: 3 // px left margin amount from icon's right edge | |
5629 // backgroundColor: "white", | |
5630 // lineColor: "#58A0DC", | |
5631 }, | |
5632 highlightColors: [ // Use with getEventPainter().setHighlightMatcher | |
5633 // See webapp/examples/examples.js | |
5634 "#FFFF00", | |
5635 "#FFC000", | |
5636 "#FF0000", | |
5637 "#0000FF" | |
5638 ], | |
5639 highlightLabelBackground: false, // When highlighting an event, also change the event's label background? | |
5640 bubble: { | |
5641 width: 250, // px | |
5642 maxHeight: 0, // px Maximum height of bubbles. 0 means no max height. | |
5643 // scrollbar will be added for taller bubbles | |
5644 titleStyler: function(elmt) { | |
5645 elmt.className = "timeline-event-bubble-title"; | |
5646 }, | |
5647 bodyStyler: function(elmt) { | |
5648 elmt.className = "timeline-event-bubble-body"; | |
5649 }, | |
5650 imageStyler: function(elmt) { | |
5651 elmt.className = "timeline-event-bubble-image"; | |
5652 }, | |
5653 wikiStyler: function(elmt) { | |
5654 elmt.className = "timeline-event-bubble-wiki"; | |
5655 }, | |
5656 timeStyler: function(elmt) { | |
5657 elmt.className = "timeline-event-bubble-time"; | |
5658 } | |
5659 } | |
5660 }; | |
5661 | |
5662 this.mouseWheel = 'scroll'; // 'default', 'zoom', 'scroll' | |
5663 }; | |
5664 /*================================================== | |
5665 * An "ether" is a object that maps date/time to pixel coordinates. | |
5666 *================================================== | |
5667 */ | |
5668 | |
5669 /*================================================== | |
5670 * Linear Ether | |
5671 *================================================== | |
5672 */ | |
5673 | |
5674 Timeline.LinearEther = function(params) { | |
5675 this._params = params; | |
5676 this._interval = params.interval; | |
5677 this._pixelsPerInterval = params.pixelsPerInterval; | |
5678 }; | |
5679 | |
5680 Timeline.LinearEther.prototype.initialize = function(band, timeline) { | |
5681 this._band = band; | |
5682 this._timeline = timeline; | |
5683 this._unit = timeline.getUnit(); | |
5684 | |
5685 if ("startsOn" in this._params) { | |
5686 this._start = this._unit.parseFromObject(this._params.startsOn); | |
5687 } else if ("endsOn" in this._params) { | |
5688 this._start = this._unit.parseFromObject(this._params.endsOn); | |
5689 this.shiftPixels(-this._timeline.getPixelLength()); | |
5690 } else if ("centersOn" in this._params) { | |
5691 this._start = this._unit.parseFromObject(this._params.centersOn); | |
5692 this.shiftPixels(-this._timeline.getPixelLength() / 2); | |
5693 } else { | |
5694 this._start = this._unit.makeDefaultValue(); | |
5695 this.shiftPixels(-this._timeline.getPixelLength() / 2); | |
5696 } | |
5697 }; | |
5698 | |
5699 Timeline.LinearEther.prototype.setDate = function(date) { | |
5700 this._start = this._unit.cloneValue(date); | |
5701 }; | |
5702 | |
5703 Timeline.LinearEther.prototype.shiftPixels = function(pixels) { | |
5704 var numeric = this._interval * pixels / this._pixelsPerInterval; | |
5705 this._start = this._unit.change(this._start, numeric); | |
5706 }; | |
5707 | |
5708 Timeline.LinearEther.prototype.dateToPixelOffset = function(date) { | |
5709 var numeric = this._unit.compare(date, this._start); | |
5710 return this._pixelsPerInterval * numeric / this._interval; | |
5711 }; | |
5712 | |
5713 Timeline.LinearEther.prototype.pixelOffsetToDate = function(pixels) { | |
5714 var numeric = pixels * this._interval / this._pixelsPerInterval; | |
5715 return this._unit.change(this._start, numeric); | |
5716 }; | |
5717 | |
5718 Timeline.LinearEther.prototype.zoom = function(zoomIn) { | |
5719 var netIntervalChange = 0; | |
5720 var currentZoomIndex = this._band._zoomIndex; | |
5721 var newZoomIndex = currentZoomIndex; | |
5722 | |
5723 if (zoomIn && (currentZoomIndex > 0)) { | |
5724 newZoomIndex = currentZoomIndex - 1; | |
5725 } | |
5726 | |
5727 if (!zoomIn && (currentZoomIndex < (this._band._zoomSteps.length - 1))) { | |
5728 newZoomIndex = currentZoomIndex + 1; | |
5729 } | |
5730 | |
5731 this._band._zoomIndex = newZoomIndex; | |
5732 this._interval = | |
5733 SimileAjax.DateTime.gregorianUnitLengths[this._band._zoomSteps[newZoomIndex].unit]; | |
5734 this._pixelsPerInterval = this._band._zoomSteps[newZoomIndex].pixelsPerInterval; | |
5735 netIntervalChange = this._band._zoomSteps[newZoomIndex].unit - | |
5736 this._band._zoomSteps[currentZoomIndex].unit; | |
5737 | |
5738 return netIntervalChange; | |
5739 }; | |
5740 | |
5741 | |
5742 /*================================================== | |
5743 * Hot Zone Ether | |
5744 *================================================== | |
5745 */ | |
5746 | |
5747 Timeline.HotZoneEther = function(params) { | |
5748 this._params = params; | |
5749 this._interval = params.interval; | |
5750 this._pixelsPerInterval = params.pixelsPerInterval; | |
5751 this._theme = params.theme; | |
5752 }; | |
5753 | |
5754 Timeline.HotZoneEther.prototype.initialize = function(band, timeline) { | |
5755 this._band = band; | |
5756 this._timeline = timeline; | |
5757 this._unit = timeline.getUnit(); | |
5758 | |
5759 this._zones = [{ | |
5760 startTime: Number.NEGATIVE_INFINITY, | |
5761 endTime: Number.POSITIVE_INFINITY, | |
5762 magnify: 1 | |
5763 }]; | |
5764 var params = this._params; | |
5765 for (var i = 0; i < params.zones.length; i++) { | |
5766 var zone = params.zones[i]; | |
5767 var zoneStart = this._unit.parseFromObject(zone.start); | |
5768 var zoneEnd = this._unit.parseFromObject(zone.end); | |
5769 | |
5770 for (var j = 0; j < this._zones.length && this._unit.compare(zoneEnd, zoneStart) > 0; j++) { | |
5771 var zone2 = this._zones[j]; | |
5772 | |
5773 if (this._unit.compare(zoneStart, zone2.endTime) < 0) { | |
5774 if (this._unit.compare(zoneStart, zone2.startTime) > 0) { | |
5775 this._zones.splice(j, 0, { | |
5776 startTime: zone2.startTime, | |
5777 endTime: zoneStart, | |
5778 magnify: zone2.magnify | |
5779 }); | |
5780 j++; | |
5781 | |
5782 zone2.startTime = zoneStart; | |
5783 } | |
5784 | |
5785 if (this._unit.compare(zoneEnd, zone2.endTime) < 0) { | |
5786 this._zones.splice(j, 0, { | |
5787 startTime: zoneStart, | |
5788 endTime: zoneEnd, | |
5789 magnify: zone.magnify * zone2.magnify | |
5790 }); | |
5791 j++; | |
5792 | |
5793 zone2.startTime = zoneEnd; | |
5794 zoneStart = zoneEnd; | |
5795 } else { | |
5796 zone2.magnify *= zone.magnify; | |
5797 zoneStart = zone2.endTime; | |
5798 } | |
5799 } // else, try the next existing zone | |
5800 } | |
5801 } | |
5802 | |
5803 if ("startsOn" in this._params) { | |
5804 this._start = this._unit.parseFromObject(this._params.startsOn); | |
5805 } else if ("endsOn" in this._params) { | |
5806 this._start = this._unit.parseFromObject(this._params.endsOn); | |
5807 this.shiftPixels(-this._timeline.getPixelLength()); | |
5808 } else if ("centersOn" in this._params) { | |
5809 this._start = this._unit.parseFromObject(this._params.centersOn); | |
5810 this.shiftPixels(-this._timeline.getPixelLength() / 2); | |
5811 } else { | |
5812 this._start = this._unit.makeDefaultValue(); | |
5813 this.shiftPixels(-this._timeline.getPixelLength() / 2); | |
5814 } | |
5815 }; | |
5816 | |
5817 Timeline.HotZoneEther.prototype.setDate = function(date) { | |
5818 this._start = this._unit.cloneValue(date); | |
5819 }; | |
5820 | |
5821 Timeline.HotZoneEther.prototype.shiftPixels = function(pixels) { | |
5822 this._start = this.pixelOffsetToDate(pixels); | |
5823 }; | |
5824 | |
5825 Timeline.HotZoneEther.prototype.dateToPixelOffset = function(date) { | |
5826 return this._dateDiffToPixelOffset(this._start, date); | |
5827 }; | |
5828 | |
5829 Timeline.HotZoneEther.prototype.pixelOffsetToDate = function(pixels) { | |
5830 return this._pixelOffsetToDate(pixels, this._start); | |
5831 }; | |
5832 | |
5833 Timeline.HotZoneEther.prototype.zoom = function(zoomIn) { | |
5834 var netIntervalChange = 0; | |
5835 var currentZoomIndex = this._band._zoomIndex; | |
5836 var newZoomIndex = currentZoomIndex; | |
5837 | |
5838 if (zoomIn && (currentZoomIndex > 0)) { | |
5839 newZoomIndex = currentZoomIndex - 1; | |
5840 } | |
5841 | |
5842 if (!zoomIn && (currentZoomIndex < (this._band._zoomSteps.length - 1))) { | |
5843 newZoomIndex = currentZoomIndex + 1; | |
5844 } | |
5845 | |
5846 this._band._zoomIndex = newZoomIndex; | |
5847 this._interval = | |
5848 SimileAjax.DateTime.gregorianUnitLengths[this._band._zoomSteps[newZoomIndex].unit]; | |
5849 this._pixelsPerInterval = this._band._zoomSteps[newZoomIndex].pixelsPerInterval; | |
5850 netIntervalChange = this._band._zoomSteps[newZoomIndex].unit - | |
5851 this._band._zoomSteps[currentZoomIndex].unit; | |
5852 | |
5853 return netIntervalChange; | |
5854 }; | |
5855 | |
5856 Timeline.HotZoneEther.prototype._dateDiffToPixelOffset = function(fromDate, toDate) { | |
5857 var scale = this._getScale(); | |
5858 var fromTime = fromDate; | |
5859 var toTime = toDate; | |
5860 | |
5861 var pixels = 0; | |
5862 if (this._unit.compare(fromTime, toTime) < 0) { | |
5863 var z = 0; | |
5864 while (z < this._zones.length) { | |
5865 if (this._unit.compare(fromTime, this._zones[z].endTime) < 0) { | |
5866 break; | |
5867 } | |
5868 z++; | |
5869 } | |
5870 | |
5871 while (this._unit.compare(fromTime, toTime) < 0) { | |
5872 var zone = this._zones[z]; | |
5873 var toTime2 = this._unit.earlier(toTime, zone.endTime); | |
5874 | |
5875 pixels += (this._unit.compare(toTime2, fromTime) / (scale / zone.magnify)); | |
5876 | |
5877 fromTime = toTime2; | |
5878 z++; | |
5879 } | |
5880 } else { | |
5881 var z = this._zones.length - 1; | |
5882 while (z >= 0) { | |
5883 if (this._unit.compare(fromTime, this._zones[z].startTime) > 0) { | |
5884 break; | |
5885 } | |
5886 z--; | |
5887 } | |
5888 | |
5889 while (this._unit.compare(fromTime, toTime) > 0) { | |
5890 var zone = this._zones[z]; | |
5891 var toTime2 = this._unit.later(toTime, zone.startTime); | |
5892 | |
5893 pixels += (this._unit.compare(toTime2, fromTime) / (scale / zone.magnify)); | |
5894 | |
5895 fromTime = toTime2; | |
5896 z--; | |
5897 } | |
5898 } | |
5899 return pixels; | |
5900 }; | |
5901 | |
5902 Timeline.HotZoneEther.prototype._pixelOffsetToDate = function(pixels, fromDate) { | |
5903 var scale = this._getScale(); | |
5904 var time = fromDate; | |
5905 if (pixels > 0) { | |
5906 var z = 0; | |
5907 while (z < this._zones.length) { | |
5908 if (this._unit.compare(time, this._zones[z].endTime) < 0) { | |
5909 break; | |
5910 } | |
5911 z++; | |
5912 } | |
5913 | |
5914 while (pixels > 0) { | |
5915 var zone = this._zones[z]; | |
5916 var scale2 = scale / zone.magnify; | |
5917 | |
5918 if (zone.endTime == Number.POSITIVE_INFINITY) { | |
5919 time = this._unit.change(time, pixels * scale2); | |
5920 pixels = 0; | |
5921 } else { | |
5922 var pixels2 = this._unit.compare(zone.endTime, time) / scale2; | |
5923 if (pixels2 > pixels) { | |
5924 time = this._unit.change(time, pixels * scale2); | |
5925 pixels = 0; | |
5926 } else { | |
5927 time = zone.endTime; | |
5928 pixels -= pixels2; | |
5929 } | |
5930 } | |
5931 z++; | |
5932 } | |
5933 } else { | |
5934 var z = this._zones.length - 1; | |
5935 while (z >= 0) { | |
5936 if (this._unit.compare(time, this._zones[z].startTime) > 0) { | |
5937 break; | |
5938 } | |
5939 z--; | |
5940 } | |
5941 | |
5942 pixels = -pixels; | |
5943 while (pixels > 0) { | |
5944 var zone = this._zones[z]; | |
5945 var scale2 = scale / zone.magnify; | |
5946 | |
5947 if (zone.startTime == Number.NEGATIVE_INFINITY) { | |
5948 time = this._unit.change(time, -pixels * scale2); | |
5949 pixels = 0; | |
5950 } else { | |
5951 var pixels2 = this._unit.compare(time, zone.startTime) / scale2; | |
5952 if (pixels2 > pixels) { | |
5953 time = this._unit.change(time, -pixels * scale2); | |
5954 pixels = 0; | |
5955 } else { | |
5956 time = zone.startTime; | |
5957 pixels -= pixels2; | |
5958 } | |
5959 } | |
5960 z--; | |
5961 } | |
5962 } | |
5963 return time; | |
5964 }; | |
5965 | |
5966 Timeline.HotZoneEther.prototype._getScale = function() { | |
5967 return this._interval / this._pixelsPerInterval; | |
5968 }; | |
5969 /*================================================== | |
5970 * Gregorian Ether Painter | |
5971 *================================================== | |
5972 */ | |
5973 | |
5974 Timeline.GregorianEtherPainter = function(params) { | |
5975 this._params = params; | |
5976 this._theme = params.theme; | |
5977 this._unit = params.unit; | |
5978 this._multiple = ("multiple" in params) ? params.multiple : 1; | |
5979 }; | |
5980 | |
5981 Timeline.GregorianEtherPainter.prototype.initialize = function(band, timeline) { | |
5982 this._band = band; | |
5983 this._timeline = timeline; | |
5984 | |
5985 this._backgroundLayer = band.createLayerDiv(0); | |
5986 this._backgroundLayer.setAttribute("name", "ether-background"); // for debugging | |
5987 this._backgroundLayer.className = 'timeline-ether-bg'; | |
5988 // this._backgroundLayer.style.background = this._theme.ether.backgroundColors[band.getIndex()]; | |
5989 | |
5990 | |
5991 this._markerLayer = null; | |
5992 this._lineLayer = null; | |
5993 | |
5994 var align = ("align" in this._params && this._params.align != undefined) ? this._params.align : | |
5995 this._theme.ether.interval.marker[timeline.isHorizontal() ? "hAlign" : "vAlign"]; | |
5996 var showLine = ("showLine" in this._params) ? this._params.showLine : | |
5997 this._theme.ether.interval.line.show; | |
5998 | |
5999 this._intervalMarkerLayout = new Timeline.EtherIntervalMarkerLayout( | |
6000 this._timeline, this._band, this._theme, align, showLine); | |
6001 | |
6002 this._highlight = new Timeline.EtherHighlight( | |
6003 this._timeline, this._band, this._theme, this._backgroundLayer); | |
6004 } | |
6005 | |
6006 Timeline.GregorianEtherPainter.prototype.setHighlight = function(startDate, endDate, orthogonalOffset, orthogonalExtent) { | |
6007 this._highlight.position(startDate, endDate, orthogonalOffset, orthogonalExtent); | |
6008 } | |
6009 | |
6010 Timeline.GregorianEtherPainter.prototype.paint = function() { | |
6011 if (this._markerLayer) { | |
6012 this._band.removeLayerDiv(this._markerLayer); | |
6013 } | |
6014 this._markerLayer = this._band.createLayerDiv(100); | |
6015 this._markerLayer.setAttribute("name", "ether-markers"); // for debugging | |
6016 this._markerLayer.style.display = "none"; | |
6017 | |
6018 if (this._lineLayer) { | |
6019 this._band.removeLayerDiv(this._lineLayer); | |
6020 } | |
6021 this._lineLayer = this._band.createLayerDiv(1); | |
6022 this._lineLayer.setAttribute("name", "ether-lines"); // for debugging | |
6023 this._lineLayer.style.display = "none"; | |
6024 | |
6025 var minDate = this._band.getMinDate(); | |
6026 var maxDate = this._band.getMaxDate(); | |
6027 | |
6028 var timeZone = this._band.getTimeZone(); | |
6029 var labeller = this._band.getLabeller(); | |
6030 | |
6031 SimileAjax.DateTime.roundDownToInterval(minDate, this._unit, timeZone, this._multiple, this._theme.firstDayOfWeek); | |
6032 | |
6033 var p = this; | |
6034 var incrementDate = function(date) { | |
6035 for (var i = 0; i < p._multiple; i++) { | |
6036 SimileAjax.DateTime.incrementByInterval(date, p._unit); | |
6037 } | |
6038 }; | |
6039 | |
6040 while (minDate.getTime() < maxDate.getTime()) { | |
6041 this._intervalMarkerLayout.createIntervalMarker( | |
6042 minDate, labeller, this._unit, this._markerLayer, this._lineLayer); | |
6043 | |
6044 incrementDate(minDate); | |
6045 } | |
6046 this._markerLayer.style.display = "block"; | |
6047 this._lineLayer.style.display = "block"; | |
6048 }; | |
6049 | |
6050 Timeline.GregorianEtherPainter.prototype.softPaint = function() { | |
6051 }; | |
6052 | |
6053 Timeline.GregorianEtherPainter.prototype.zoom = function(netIntervalChange) { | |
6054 if (netIntervalChange != 0) { | |
6055 this._unit += netIntervalChange; | |
6056 } | |
6057 }; | |
6058 | |
6059 | |
6060 /*================================================== | |
6061 * Hot Zone Gregorian Ether Painter | |
6062 *================================================== | |
6063 */ | |
6064 | |
6065 Timeline.HotZoneGregorianEtherPainter = function(params) { | |
6066 this._params = params; | |
6067 this._theme = params.theme; | |
6068 | |
6069 this._zones = [{ | |
6070 startTime: Number.NEGATIVE_INFINITY, | |
6071 endTime: Number.POSITIVE_INFINITY, | |
6072 unit: params.unit, | |
6073 multiple: 1 | |
6074 }]; | |
6075 for (var i = 0; i < params.zones.length; i++) { | |
6076 var zone = params.zones[i]; | |
6077 var zoneStart = SimileAjax.DateTime.parseGregorianDateTime(zone.start).getTime(); | |
6078 var zoneEnd = SimileAjax.DateTime.parseGregorianDateTime(zone.end).getTime(); | |
6079 | |
6080 for (var j = 0; j < this._zones.length && zoneEnd > zoneStart; j++) { | |
6081 var zone2 = this._zones[j]; | |
6082 | |
6083 if (zoneStart < zone2.endTime) { | |
6084 if (zoneStart > zone2.startTime) { | |
6085 this._zones.splice(j, 0, { | |
6086 startTime: zone2.startTime, | |
6087 endTime: zoneStart, | |
6088 unit: zone2.unit, | |
6089 multiple: zone2.multiple | |
6090 }); | |
6091 j++; | |
6092 | |
6093 zone2.startTime = zoneStart; | |
6094 } | |
6095 | |
6096 if (zoneEnd < zone2.endTime) { | |
6097 this._zones.splice(j, 0, { | |
6098 startTime: zoneStart, | |
6099 endTime: zoneEnd, | |
6100 unit: zone.unit, | |
6101 multiple: (zone.multiple) ? zone.multiple : 1 | |
6102 }); | |
6103 j++; | |
6104 | |
6105 zone2.startTime = zoneEnd; | |
6106 zoneStart = zoneEnd; | |
6107 } else { | |
6108 zone2.multiple = zone.multiple; | |
6109 zone2.unit = zone.unit; | |
6110 zoneStart = zone2.endTime; | |
6111 } | |
6112 } // else, try the next existing zone | |
6113 } | |
6114 } | |
6115 }; | |
6116 | |
6117 Timeline.HotZoneGregorianEtherPainter.prototype.initialize = function(band, timeline) { | |
6118 this._band = band; | |
6119 this._timeline = timeline; | |
6120 | |
6121 this._backgroundLayer = band.createLayerDiv(0); | |
6122 this._backgroundLayer.setAttribute("name", "ether-background"); // for debugging | |
6123 this._backgroundLayer.className ='timeline-ether-bg'; | |
6124 //this._backgroundLayer.style.background = this._theme.ether.backgroundColors[band.getIndex()]; | |
6125 | |
6126 this._markerLayer = null; | |
6127 this._lineLayer = null; | |
6128 | |
6129 var align = ("align" in this._params && this._params.align != undefined) ? this._params.align : | |
6130 this._theme.ether.interval.marker[timeline.isHorizontal() ? "hAlign" : "vAlign"]; | |
6131 var showLine = ("showLine" in this._params) ? this._params.showLine : | |
6132 this._theme.ether.interval.line.show; | |
6133 | |
6134 this._intervalMarkerLayout = new Timeline.EtherIntervalMarkerLayout( | |
6135 this._timeline, this._band, this._theme, align, showLine); | |
6136 | |
6137 this._highlight = new Timeline.EtherHighlight( | |
6138 this._timeline, this._band, this._theme, this._backgroundLayer); | |
6139 } | |
6140 | |
6141 Timeline.HotZoneGregorianEtherPainter.prototype.setHighlight = function(startDate, endDate) { | |
6142 this._highlight.position(startDate, endDate); | |
6143 } | |
6144 | |
6145 Timeline.HotZoneGregorianEtherPainter.prototype.paint = function() { | |
6146 if (this._markerLayer) { | |
6147 this._band.removeLayerDiv(this._markerLayer); | |
6148 } | |
6149 this._markerLayer = this._band.createLayerDiv(100); | |
6150 this._markerLayer.setAttribute("name", "ether-markers"); // for debugging | |
6151 this._markerLayer.style.display = "none"; | |
6152 | |
6153 if (this._lineLayer) { | |
6154 this._band.removeLayerDiv(this._lineLayer); | |
6155 } | |
6156 this._lineLayer = this._band.createLayerDiv(1); | |
6157 this._lineLayer.setAttribute("name", "ether-lines"); // for debugging | |
6158 this._lineLayer.style.display = "none"; | |
6159 | |
6160 var minDate = this._band.getMinDate(); | |
6161 var maxDate = this._band.getMaxDate(); | |
6162 | |
6163 var timeZone = this._band.getTimeZone(); | |
6164 var labeller = this._band.getLabeller(); | |
6165 | |
6166 var p = this; | |
6167 var incrementDate = function(date, zone) { | |
6168 for (var i = 0; i < zone.multiple; i++) { | |
6169 SimileAjax.DateTime.incrementByInterval(date, zone.unit); | |
6170 } | |
6171 }; | |
6172 | |
6173 var zStart = 0; | |
6174 while (zStart < this._zones.length) { | |
6175 if (minDate.getTime() < this._zones[zStart].endTime) { | |
6176 break; | |
6177 } | |
6178 zStart++; | |
6179 } | |
6180 var zEnd = this._zones.length - 1; | |
6181 while (zEnd >= 0) { | |
6182 if (maxDate.getTime() > this._zones[zEnd].startTime) { | |
6183 break; | |
6184 } | |
6185 zEnd--; | |
6186 } | |
6187 | |
6188 for (var z = zStart; z <= zEnd; z++) { | |
6189 var zone = this._zones[z]; | |
6190 | |
6191 var minDate2 = new Date(Math.max(minDate.getTime(), zone.startTime)); | |
6192 var maxDate2 = new Date(Math.min(maxDate.getTime(), zone.endTime)); | |
6193 | |
6194 SimileAjax.DateTime.roundDownToInterval(minDate2, zone.unit, timeZone, zone.multiple, this._theme.firstDayOfWeek); | |
6195 SimileAjax.DateTime.roundUpToInterval(maxDate2, zone.unit, timeZone, zone.multiple, this._theme.firstDayOfWeek); | |
6196 | |
6197 while (minDate2.getTime() < maxDate2.getTime()) { | |
6198 this._intervalMarkerLayout.createIntervalMarker( | |
6199 minDate2, labeller, zone.unit, this._markerLayer, this._lineLayer); | |
6200 | |
6201 incrementDate(minDate2, zone); | |
6202 } | |
6203 } | |
6204 this._markerLayer.style.display = "block"; | |
6205 this._lineLayer.style.display = "block"; | |
6206 }; | |
6207 | |
6208 Timeline.HotZoneGregorianEtherPainter.prototype.softPaint = function() { | |
6209 }; | |
6210 | |
6211 Timeline.HotZoneGregorianEtherPainter.prototype.zoom = function(netIntervalChange) { | |
6212 if (netIntervalChange != 0) { | |
6213 for (var i = 0; i < this._zones.length; ++i) { | |
6214 if (this._zones[i]) { | |
6215 this._zones[i].unit += netIntervalChange; | |
6216 } | |
6217 } | |
6218 } | |
6219 }; | |
6220 | |
6221 /*================================================== | |
6222 * Year Count Ether Painter | |
6223 *================================================== | |
6224 */ | |
6225 | |
6226 Timeline.YearCountEtherPainter = function(params) { | |
6227 this._params = params; | |
6228 this._theme = params.theme; | |
6229 this._startDate = SimileAjax.DateTime.parseGregorianDateTime(params.startDate); | |
6230 this._multiple = ("multiple" in params) ? params.multiple : 1; | |
6231 }; | |
6232 | |
6233 Timeline.YearCountEtherPainter.prototype.initialize = function(band, timeline) { | |
6234 this._band = band; | |
6235 this._timeline = timeline; | |
6236 | |
6237 this._backgroundLayer = band.createLayerDiv(0); | |
6238 this._backgroundLayer.setAttribute("name", "ether-background"); // for debugging | |
6239 this._backgroundLayer.className = 'timeline-ether-bg'; | |
6240 // this._backgroundLayer.style.background = this._theme.ether.backgroundColors[band.getIndex()]; | |
6241 | |
6242 this._markerLayer = null; | |
6243 this._lineLayer = null; | |
6244 | |
6245 var align = ("align" in this._params) ? this._params.align : | |
6246 this._theme.ether.interval.marker[timeline.isHorizontal() ? "hAlign" : "vAlign"]; | |
6247 var showLine = ("showLine" in this._params) ? this._params.showLine : | |
6248 this._theme.ether.interval.line.show; | |
6249 | |
6250 this._intervalMarkerLayout = new Timeline.EtherIntervalMarkerLayout( | |
6251 this._timeline, this._band, this._theme, align, showLine); | |
6252 | |
6253 this._highlight = new Timeline.EtherHighlight( | |
6254 this._timeline, this._band, this._theme, this._backgroundLayer); | |
6255 }; | |
6256 | |
6257 Timeline.YearCountEtherPainter.prototype.setHighlight = function(startDate, endDate) { | |
6258 this._highlight.position(startDate, endDate); | |
6259 }; | |
6260 | |
6261 Timeline.YearCountEtherPainter.prototype.paint = function() { | |
6262 if (this._markerLayer) { | |
6263 this._band.removeLayerDiv(this._markerLayer); | |
6264 } | |
6265 this._markerLayer = this._band.createLayerDiv(100); | |
6266 this._markerLayer.setAttribute("name", "ether-markers"); // for debugging | |
6267 this._markerLayer.style.display = "none"; | |
6268 | |
6269 if (this._lineLayer) { | |
6270 this._band.removeLayerDiv(this._lineLayer); | |
6271 } | |
6272 this._lineLayer = this._band.createLayerDiv(1); | |
6273 this._lineLayer.setAttribute("name", "ether-lines"); // for debugging | |
6274 this._lineLayer.style.display = "none"; | |
6275 | |
6276 var minDate = new Date(this._startDate.getTime()); | |
6277 var maxDate = this._band.getMaxDate(); | |
6278 var yearDiff = this._band.getMinDate().getUTCFullYear() - this._startDate.getUTCFullYear(); | |
6279 minDate.setUTCFullYear(this._band.getMinDate().getUTCFullYear() - yearDiff % this._multiple); | |
6280 | |
6281 var p = this; | |
6282 var incrementDate = function(date) { | |
6283 for (var i = 0; i < p._multiple; i++) { | |
6284 SimileAjax.DateTime.incrementByInterval(date, SimileAjax.DateTime.YEAR); | |
6285 } | |
6286 }; | |
6287 var labeller = { | |
6288 labelInterval: function(date, intervalUnit) { | |
6289 var diff = date.getUTCFullYear() - p._startDate.getUTCFullYear(); | |
6290 return { | |
6291 text: diff, | |
6292 emphasized: diff == 0 | |
6293 }; | |
6294 } | |
6295 }; | |
6296 | |
6297 while (minDate.getTime() < maxDate.getTime()) { | |
6298 this._intervalMarkerLayout.createIntervalMarker( | |
6299 minDate, labeller, SimileAjax.DateTime.YEAR, this._markerLayer, this._lineLayer); | |
6300 | |
6301 incrementDate(minDate); | |
6302 } | |
6303 this._markerLayer.style.display = "block"; | |
6304 this._lineLayer.style.display = "block"; | |
6305 }; | |
6306 | |
6307 Timeline.YearCountEtherPainter.prototype.softPaint = function() { | |
6308 }; | |
6309 | |
6310 /*================================================== | |
6311 * Quarterly Ether Painter | |
6312 *================================================== | |
6313 */ | |
6314 | |
6315 Timeline.QuarterlyEtherPainter = function(params) { | |
6316 this._params = params; | |
6317 this._theme = params.theme; | |
6318 this._startDate = SimileAjax.DateTime.parseGregorianDateTime(params.startDate); | |
6319 }; | |
6320 | |
6321 Timeline.QuarterlyEtherPainter.prototype.initialize = function(band, timeline) { | |
6322 this._band = band; | |
6323 this._timeline = timeline; | |
6324 | |
6325 this._backgroundLayer = band.createLayerDiv(0); | |
6326 this._backgroundLayer.setAttribute("name", "ether-background"); // for debugging | |
6327 this._backgroundLayer.className = 'timeline-ether-bg'; | |
6328 // this._backgroundLayer.style.background = this._theme.ether.backgroundColors[band.getIndex()]; | |
6329 | |
6330 this._markerLayer = null; | |
6331 this._lineLayer = null; | |
6332 | |
6333 var align = ("align" in this._params) ? this._params.align : | |
6334 this._theme.ether.interval.marker[timeline.isHorizontal() ? "hAlign" : "vAlign"]; | |
6335 var showLine = ("showLine" in this._params) ? this._params.showLine : | |
6336 this._theme.ether.interval.line.show; | |
6337 | |
6338 this._intervalMarkerLayout = new Timeline.EtherIntervalMarkerLayout( | |
6339 this._timeline, this._band, this._theme, align, showLine); | |
6340 | |
6341 this._highlight = new Timeline.EtherHighlight( | |
6342 this._timeline, this._band, this._theme, this._backgroundLayer); | |
6343 }; | |
6344 | |
6345 Timeline.QuarterlyEtherPainter.prototype.setHighlight = function(startDate, endDate) { | |
6346 this._highlight.position(startDate, endDate); | |
6347 }; | |
6348 | |
6349 Timeline.QuarterlyEtherPainter.prototype.paint = function() { | |
6350 if (this._markerLayer) { | |
6351 this._band.removeLayerDiv(this._markerLayer); | |
6352 } | |
6353 this._markerLayer = this._band.createLayerDiv(100); | |
6354 this._markerLayer.setAttribute("name", "ether-markers"); // for debugging | |
6355 this._markerLayer.style.display = "none"; | |
6356 | |
6357 if (this._lineLayer) { | |
6358 this._band.removeLayerDiv(this._lineLayer); | |
6359 } | |
6360 this._lineLayer = this._band.createLayerDiv(1); | |
6361 this._lineLayer.setAttribute("name", "ether-lines"); // for debugging | |
6362 this._lineLayer.style.display = "none"; | |
6363 | |
6364 var minDate = new Date(0); | |
6365 var maxDate = this._band.getMaxDate(); | |
6366 | |
6367 minDate.setUTCFullYear(Math.max(this._startDate.getUTCFullYear(), this._band.getMinDate().getUTCFullYear())); | |
6368 minDate.setUTCMonth(this._startDate.getUTCMonth()); | |
6369 | |
6370 var p = this; | |
6371 var incrementDate = function(date) { | |
6372 date.setUTCMonth(date.getUTCMonth() + 3); | |
6373 }; | |
6374 var labeller = { | |
6375 labelInterval: function(date, intervalUnit) { | |
6376 var quarters = (4 + (date.getUTCMonth() - p._startDate.getUTCMonth()) / 3) % 4; | |
6377 if (quarters != 0) { | |
6378 return { text: "Q" + (quarters + 1), emphasized: false }; | |
6379 } else { | |
6380 return { text: "Y" + (date.getUTCFullYear() - p._startDate.getUTCFullYear() + 1), emphasized: true }; | |
6381 } | |
6382 } | |
6383 }; | |
6384 | |
6385 while (minDate.getTime() < maxDate.getTime()) { | |
6386 this._intervalMarkerLayout.createIntervalMarker( | |
6387 minDate, labeller, SimileAjax.DateTime.YEAR, this._markerLayer, this._lineLayer); | |
6388 | |
6389 incrementDate(minDate); | |
6390 } | |
6391 this._markerLayer.style.display = "block"; | |
6392 this._lineLayer.style.display = "block"; | |
6393 }; | |
6394 | |
6395 Timeline.QuarterlyEtherPainter.prototype.softPaint = function() { | |
6396 }; | |
6397 | |
6398 /*================================================== | |
6399 * Ether Interval Marker Layout | |
6400 *================================================== | |
6401 */ | |
6402 | |
6403 Timeline.EtherIntervalMarkerLayout = function(timeline, band, theme, align, showLine) { | |
6404 var horizontal = timeline.isHorizontal(); | |
6405 if (horizontal) { | |
6406 if (align == "Top") { | |
6407 this.positionDiv = function(div, offset) { | |
6408 div.style.left = offset + "px"; | |
6409 div.style.top = "0px"; | |
6410 }; | |
6411 } else { | |
6412 this.positionDiv = function(div, offset) { | |
6413 div.style.left = offset + "px"; | |
6414 div.style.bottom = "0px"; | |
6415 }; | |
6416 } | |
6417 } else { | |
6418 if (align == "Left") { | |
6419 this.positionDiv = function(div, offset) { | |
6420 div.style.top = offset + "px"; | |
6421 div.style.left = "0px"; | |
6422 }; | |
6423 } else { | |
6424 this.positionDiv = function(div, offset) { | |
6425 div.style.top = offset + "px"; | |
6426 div.style.right = "0px"; | |
6427 }; | |
6428 } | |
6429 } | |
6430 | |
6431 var markerTheme = theme.ether.interval.marker; | |
6432 var lineTheme = theme.ether.interval.line; | |
6433 var weekendTheme = theme.ether.interval.weekend; | |
6434 | |
6435 var stylePrefix = (horizontal ? "h" : "v") + align; | |
6436 var labelStyler = markerTheme[stylePrefix + "Styler"]; | |
6437 var emphasizedLabelStyler = markerTheme[stylePrefix + "EmphasizedStyler"]; | |
6438 var day = SimileAjax.DateTime.gregorianUnitLengths[SimileAjax.DateTime.DAY]; | |
6439 | |
6440 this.createIntervalMarker = function(date, labeller, unit, markerDiv, lineDiv) { | |
6441 var offset = Math.round(band.dateToPixelOffset(date)); | |
6442 | |
6443 if (showLine && unit != SimileAjax.DateTime.WEEK) { | |
6444 var divLine = timeline.getDocument().createElement("div"); | |
6445 divLine.className = "timeline-ether-lines"; | |
6446 | |
6447 if (lineTheme.opacity < 100) { | |
6448 SimileAjax.Graphics.setOpacity(divLine, lineTheme.opacity); | |
6449 } | |
6450 | |
6451 if (horizontal) { | |
6452 //divLine.className += " timeline-ether-lines-vertical"; | |
6453 divLine.style.left = offset + "px"; | |
6454 } else { | |
6455 //divLine.className += " timeline-ether-lines-horizontal"; | |
6456 divLine.style.top = offset + "px"; | |
6457 } | |
6458 lineDiv.appendChild(divLine); | |
6459 } | |
6460 if (unit == SimileAjax.DateTime.WEEK) { | |
6461 var firstDayOfWeek = theme.firstDayOfWeek; | |
6462 | |
6463 var saturday = new Date(date.getTime() + (6 - firstDayOfWeek - 7) * day); | |
6464 var monday = new Date(saturday.getTime() + 2 * day); | |
6465 | |
6466 var saturdayPixel = Math.round(band.dateToPixelOffset(saturday)); | |
6467 var mondayPixel = Math.round(band.dateToPixelOffset(monday)); | |
6468 var length = Math.max(1, mondayPixel - saturdayPixel); | |
6469 | |
6470 var divWeekend = timeline.getDocument().createElement("div"); | |
6471 divWeekend.className = 'timeline-ether-weekends' | |
6472 | |
6473 if (weekendTheme.opacity < 100) { | |
6474 SimileAjax.Graphics.setOpacity(divWeekend, weekendTheme.opacity); | |
6475 } | |
6476 | |
6477 if (horizontal) { | |
6478 divWeekend.style.left = saturdayPixel + "px"; | |
6479 divWeekend.style.width = length + "px"; | |
6480 } else { | |
6481 divWeekend.style.top = saturdayPixel + "px"; | |
6482 divWeekend.style.height = length + "px"; | |
6483 } | |
6484 lineDiv.appendChild(divWeekend); | |
6485 } | |
6486 | |
6487 var label = labeller.labelInterval(date, unit); | |
6488 | |
6489 var div = timeline.getDocument().createElement("div"); | |
6490 div.innerHTML = label.text; | |
6491 | |
6492 | |
6493 | |
6494 div.className = 'timeline-date-label' | |
6495 if(label.emphasized) div.className += ' timeline-date-label-em' | |
6496 | |
6497 this.positionDiv(div, offset); | |
6498 markerDiv.appendChild(div); | |
6499 | |
6500 return div; | |
6501 }; | |
6502 }; | |
6503 | |
6504 /*================================================== | |
6505 * Ether Highlight Layout | |
6506 *================================================== | |
6507 */ | |
6508 | |
6509 Timeline.EtherHighlight = function(timeline, band, theme, backgroundLayer) { | |
6510 var horizontal = timeline.isHorizontal(); | |
6511 | |
6512 this._highlightDiv = null; | |
6513 this._createHighlightDiv = function() { | |
6514 if (this._highlightDiv == null) { | |
6515 this._highlightDiv = timeline.getDocument().createElement("div"); | |
6516 this._highlightDiv.setAttribute("name", "ether-highlight"); // for debugging | |
6517 this._highlightDiv.className = 'timeline-ether-highlight' | |
6518 | |
6519 var opacity = theme.ether.highlightOpacity; | |
6520 if (opacity < 100) { | |
6521 SimileAjax.Graphics.setOpacity(this._highlightDiv, opacity); | |
6522 } | |
6523 | |
6524 backgroundLayer.appendChild(this._highlightDiv); | |
6525 } | |
6526 } | |
6527 | |
6528 this.position = function(startDate, endDate, orthogonalOffset, orthogonalExtent) { | |
6529 orthogonalOffset = orthogonalOffset || 0; | |
6530 orthogonalExtent = orthogonalExtent || 1.0; | |
6531 | |
6532 this._createHighlightDiv(); | |
6533 | |
6534 var startPixel = Math.round(band.dateToPixelOffset(startDate)); | |
6535 var endPixel = Math.round(band.dateToPixelOffset(endDate)); | |
6536 var length = Math.max(endPixel - startPixel, 3); | |
6537 var totalWidth = band.getViewWidth() - 4; | |
6538 if (horizontal) { | |
6539 this._highlightDiv.style.left = startPixel + "px"; | |
6540 this._highlightDiv.style.width = length + "px"; | |
6541 this._highlightDiv.style.top = Math.round(orthogonalOffset * totalWidth) + "px"; | |
6542 this._highlightDiv.style.height = Math.round(orthogonalExtent * totalWidth) + "px"; | |
6543 } else { | |
6544 this._highlightDiv.style.top = startPixel + "px"; | |
6545 this._highlightDiv.style.height = length + "px"; | |
6546 this._highlightDiv.style.left = Math.round(orthogonalOffset * totalWidth) + "px"; | |
6547 this._highlightDiv.style.width = Math.round(orthogonalExtent * totalWidth) + "px"; | |
6548 } | |
6549 } | |
6550 }; | |
6551 /*================================================== | |
6552 * Event Utils | |
6553 *================================================== | |
6554 */ | |
6555 Timeline.EventUtils = {}; | |
6556 | |
6557 Timeline.EventUtils.getNewEventID = function() { | |
6558 // global across page | |
6559 if (this._lastEventID == null) { | |
6560 this._lastEventID = 0; | |
6561 } | |
6562 | |
6563 this._lastEventID += 1; | |
6564 return "e" + this._lastEventID; | |
6565 }; | |
6566 | |
6567 Timeline.EventUtils.decodeEventElID = function(elementID) { | |
6568 /*================================================== | |
6569 * | |
6570 * Use this function to decode an event element's id on a band (label div, | |
6571 * tape div or icon img). | |
6572 * | |
6573 * Returns {band: <bandObj>, evt: <eventObj>} | |
6574 * | |
6575 * To enable a single event listener to monitor everything | |
6576 * on a Timeline, a set format is used for the id's of the | |
6577 * elements on the Timeline-- | |
6578 * | |
6579 * element id format for labels, icons, tapes: | |
6580 * labels: label-tl-<timelineID>-<band_index>-<evt.id> | |
6581 * icons: icon-tl-<timelineID>-<band_index>-<evt.id> | |
6582 * tapes: tape1-tl-<timelineID>-<band_index>-<evt.id> | |
6583 * tape2-tl-<timelineID>-<band_index>-<evt.id> | |
6584 * // some events have more than one tape | |
6585 * highlight: highlight1-tl-<timelineID>-<band_index>-<evt.id> | |
6586 * highlight2-tl-<timelineID>-<band_index>-<evt.id> | |
6587 * // some events have more than one highlight div (future) | |
6588 * Note: use split('-') to get array of the format's parts | |
6589 * | |
6590 * You can then retrieve the timeline object and event object | |
6591 * by using Timeline.getTimeline, Timeline.getBand, or | |
6592 * Timeline.getEvent and passing in the element's id | |
6593 * | |
6594 *================================================== | |
6595 */ | |
6596 | |
6597 var parts = elementID.split('-'); | |
6598 if (parts[1] != 'tl') { | |
6599 alert("Internal Timeline problem 101, please consult support"); | |
6600 return {band: null, evt: null}; // early return | |
6601 } | |
6602 | |
6603 var timeline = Timeline.getTimelineFromID(parts[2]); | |
6604 var band = timeline.getBand(parts[3]); | |
6605 var evt = band.getEventSource.getEvent(parts[4]); | |
6606 | |
6607 return {band: band, evt: evt}; | |
6608 }; | |
6609 | |
6610 Timeline.EventUtils.encodeEventElID = function(timeline, band, elType, evt) { | |
6611 // elType should be one of {label | icon | tapeN | highlightN} | |
6612 return elType + "-tl-" + timeline.timelineID + | |
6613 "-" + band.getIndex() + "-" + evt.getID(); | |
6614 }; | |
6615 /*================================================== | |
6616 * Gregorian Date Labeller | |
6617 *================================================== | |
6618 */ | |
6619 | |
6620 Timeline.GregorianDateLabeller = function(locale, timeZone) { | |
6621 this._locale = locale; | |
6622 this._timeZone = timeZone; | |
6623 }; | |
6624 | |
6625 Timeline.GregorianDateLabeller.monthNames = []; | |
6626 Timeline.GregorianDateLabeller.dayNames = []; | |
6627 Timeline.GregorianDateLabeller.labelIntervalFunctions = []; | |
6628 | |
6629 Timeline.GregorianDateLabeller.getMonthName = function(month, locale) { | |
6630 return Timeline.GregorianDateLabeller.monthNames[locale][month]; | |
6631 }; | |
6632 | |
6633 Timeline.GregorianDateLabeller.prototype.labelInterval = function(date, intervalUnit) { | |
6634 var f = Timeline.GregorianDateLabeller.labelIntervalFunctions[this._locale]; | |
6635 if (f == null) { | |
6636 f = Timeline.GregorianDateLabeller.prototype.defaultLabelInterval; | |
6637 } | |
6638 return f.call(this, date, intervalUnit); | |
6639 }; | |
6640 | |
6641 Timeline.GregorianDateLabeller.prototype.labelPrecise = function(date) { | |
6642 return SimileAjax.DateTime.removeTimeZoneOffset( | |
6643 date, | |
6644 this._timeZone //+ (new Date().getTimezoneOffset() / 60) | |
6645 ).toUTCString(); | |
6646 }; | |
6647 | |
6648 Timeline.GregorianDateLabeller.prototype.defaultLabelInterval = function(date, intervalUnit) { | |
6649 var text; | |
6650 var emphasized = false; | |
6651 | |
6652 date = SimileAjax.DateTime.removeTimeZoneOffset(date, this._timeZone); | |
6653 | |
6654 switch(intervalUnit) { | |
6655 case SimileAjax.DateTime.MILLISECOND: | |
6656 text = date.getUTCMilliseconds(); | |
6657 break; | |
6658 case SimileAjax.DateTime.SECOND: | |
6659 text = date.getUTCSeconds(); | |
6660 break; | |
6661 case SimileAjax.DateTime.MINUTE: | |
6662 var m = date.getUTCMinutes(); | |
6663 if (m == 0) { | |
6664 text = date.getUTCHours() + ":00"; | |
6665 emphasized = true; | |
6666 } else { | |
6667 text = m; | |
6668 } | |
6669 break; | |
6670 case SimileAjax.DateTime.HOUR: | |
6671 text = date.getUTCHours() + "hr"; | |
6672 break; | |
6673 case SimileAjax.DateTime.DAY: | |
6674 text = Timeline.GregorianDateLabeller.getMonthName(date.getUTCMonth(), this._locale) + " " + date.getUTCDate(); | |
6675 break; | |
6676 case SimileAjax.DateTime.WEEK: | |
6677 text = Timeline.GregorianDateLabeller.getMonthName(date.getUTCMonth(), this._locale) + " " + date.getUTCDate(); | |
6678 break; | |
6679 case SimileAjax.DateTime.MONTH: | |
6680 var m = date.getUTCMonth(); | |
6681 if (m != 0) { | |
6682 text = Timeline.GregorianDateLabeller.getMonthName(m, this._locale); | |
6683 break; | |
6684 } // else, fall through | |
6685 case SimileAjax.DateTime.YEAR: | |
6686 case SimileAjax.DateTime.DECADE: | |
6687 case SimileAjax.DateTime.CENTURY: | |
6688 case SimileAjax.DateTime.MILLENNIUM: | |
6689 var y = date.getUTCFullYear(); | |
6690 if (y > 0) { | |
6691 text = date.getUTCFullYear(); | |
6692 } else { | |
6693 text = (1 - y) + "BC"; | |
6694 } | |
6695 emphasized = | |
6696 (intervalUnit == SimileAjax.DateTime.MONTH) || | |
6697 (intervalUnit == SimileAjax.DateTime.DECADE && y % 100 == 0) || | |
6698 (intervalUnit == SimileAjax.DateTime.CENTURY && y % 1000 == 0); | |
6699 break; | |
6700 default: | |
6701 text = date.toUTCString(); | |
6702 } | |
6703 return { text: text, emphasized: emphasized }; | |
6704 } | |
6705 | |
6706 /*================================================== | |
6707 * Default Event Source | |
6708 *================================================== | |
6709 */ | |
6710 | |
6711 | |
6712 Timeline.DefaultEventSource = function(eventIndex) { | |
6713 this._events = (eventIndex instanceof Object) ? eventIndex : new SimileAjax.EventIndex(); | |
6714 this._listeners = []; | |
6715 }; | |
6716 | |
6717 Timeline.DefaultEventSource.prototype.addListener = function(listener) { | |
6718 this._listeners.push(listener); | |
6719 }; | |
6720 | |
6721 Timeline.DefaultEventSource.prototype.removeListener = function(listener) { | |
6722 for (var i = 0; i < this._listeners.length; i++) { | |
6723 if (this._listeners[i] == listener) { | |
6724 this._listeners.splice(i, 1); | |
6725 break; | |
6726 } | |
6727 } | |
6728 }; | |
6729 | |
6730 Timeline.DefaultEventSource.prototype.loadXML = function(xml, url) { | |
6731 var base = this._getBaseURL(url); | |
6732 | |
6733 var wikiURL = xml.documentElement.getAttribute("wiki-url"); | |
6734 var wikiSection = xml.documentElement.getAttribute("wiki-section"); | |
6735 | |
6736 var dateTimeFormat = xml.documentElement.getAttribute("date-time-format"); | |
6737 var parseDateTimeFunction = this._events.getUnit().getParser(dateTimeFormat); | |
6738 | |
6739 var node = xml.documentElement.firstChild; | |
6740 var added = false; | |
6741 while (node != null) { | |
6742 if (node.nodeType == 1) { | |
6743 var description = ""; | |
6744 if (node.firstChild != null && node.firstChild.nodeType == 3) { | |
6745 description = node.firstChild.nodeValue; | |
6746 } | |
6747 // instant event: default is true. Or use values from isDuration or durationEvent | |
6748 var instant = (node.getAttribute("isDuration") === null && | |
6749 node.getAttribute("durationEvent") === null) || | |
6750 node.getAttribute("isDuration") == "false" || | |
6751 node.getAttribute("durationEvent") == "false"; | |
6752 | |
6753 var evt = new Timeline.DefaultEventSource.Event( { | |
6754 id: node.getAttribute("id"), | |
6755 start: parseDateTimeFunction(node.getAttribute("start")), | |
6756 end: parseDateTimeFunction(node.getAttribute("end")), | |
6757 latestStart: parseDateTimeFunction(node.getAttribute("latestStart")), | |
6758 earliestEnd: parseDateTimeFunction(node.getAttribute("earliestEnd")), | |
6759 instant: instant, | |
6760 text: node.getAttribute("title"), | |
6761 description: description, | |
6762 image: this._resolveRelativeURL(node.getAttribute("image"), base), | |
6763 link: this._resolveRelativeURL(node.getAttribute("link") , base), | |
6764 icon: this._resolveRelativeURL(node.getAttribute("icon") , base), | |
6765 color: node.getAttribute("color"), | |
6766 textColor: node.getAttribute("textColor"), | |
6767 hoverText: node.getAttribute("hoverText"), | |
6768 classname: node.getAttribute("classname"), | |
6769 tapeImage: node.getAttribute("tapeImage"), | |
6770 tapeRepeat: node.getAttribute("tapeRepeat"), | |
6771 caption: node.getAttribute("caption"), | |
6772 eventID: node.getAttribute("eventID"), | |
6773 trackNum: node.getAttribute("trackNum") | |
6774 }); | |
6775 | |
6776 evt._node = node; | |
6777 evt.getProperty = function(name) { | |
6778 return this._node.getAttribute(name); | |
6779 }; | |
6780 evt.setWikiInfo(wikiURL, wikiSection); | |
6781 | |
6782 this._events.add(evt); | |
6783 | |
6784 added = true; | |
6785 } | |
6786 node = node.nextSibling; | |
6787 } | |
6788 | |
6789 if (added) { | |
6790 this._fire("onAddMany", []); | |
6791 } | |
6792 }; | |
6793 | |
6794 | |
6795 Timeline.DefaultEventSource.prototype.loadJSON = function(data, url) { | |
6796 var base = this._getBaseURL(url); | |
6797 var added = false; | |
6798 if (data && data.events){ | |
6799 var wikiURL = ("wikiURL" in data) ? data.wikiURL : null; | |
6800 var wikiSection = ("wikiSection" in data) ? data.wikiSection : null; | |
6801 | |
6802 var dateTimeFormat = ("dateTimeFormat" in data) ? data.dateTimeFormat : null; | |
6803 var parseDateTimeFunction = this._events.getUnit().getParser(dateTimeFormat); | |
6804 | |
6805 for (var i=0; i < data.events.length; i++){ | |
6806 var evnt = data.events[i]; | |
6807 | |
6808 // New feature: attribute synonyms. The following attribute names are interchangable. | |
6809 // The shorter names enable smaller load files. | |
6810 // eid -- eventID | |
6811 // s -- start | |
6812 // e -- end | |
6813 // ls -- latestStart | |
6814 // ee -- earliestEnd | |
6815 // d -- description | |
6816 // de -- durationEvent | |
6817 // t -- title, | |
6818 // c -- classname | |
6819 | |
6820 // Fixing issue 33: | |
6821 // instant event: default (for JSON only) is false. Or use values from isDuration or durationEvent | |
6822 // isDuration was negated (see issue 33, so keep that interpretation | |
6823 var instant = evnt.isDuration || | |
6824 (('durationEvent' in evnt) && !evnt.durationEvent) || | |
6825 (('de' in evnt) && !evnt.de); | |
6826 var evt = new Timeline.DefaultEventSource.Event({ | |
6827 id: ("id" in evnt) ? evnt.id : undefined, | |
6828 start: parseDateTimeFunction(evnt.start || evnt.s), | |
6829 end: parseDateTimeFunction(evnt.end || evnt.e), | |
6830 latestStart: parseDateTimeFunction(evnt.latestStart || evnt.ls), | |
6831 earliestEnd: parseDateTimeFunction(evnt.earliestEnd || evnt.ee), | |
6832 instant: instant, | |
6833 text: evnt.title || evnt.t, | |
6834 description: evnt.description || evnt.d, | |
6835 image: this._resolveRelativeURL(evnt.image, base), | |
6836 link: this._resolveRelativeURL(evnt.link , base), | |
6837 icon: this._resolveRelativeURL(evnt.icon , base), | |
6838 color: evnt.color, | |
6839 textColor: evnt.textColor, | |
6840 hoverText: evnt.hoverText, | |
6841 classname: evnt.classname || evnt.c, | |
6842 tapeImage: evnt.tapeImage, | |
6843 tapeRepeat: evnt.tapeRepeat, | |
6844 caption: evnt.caption, | |
6845 eventID: evnt.eventID || evnt.eid, | |
6846 trackNum: evnt.trackNum | |
6847 }); | |
6848 evt._obj = evnt; | |
6849 evt.getProperty = function(name) { | |
6850 return this._obj[name]; | |
6851 }; | |
6852 evt.setWikiInfo(wikiURL, wikiSection); | |
6853 | |
6854 this._events.add(evt); | |
6855 added = true; | |
6856 } | |
6857 } | |
6858 | |
6859 if (added) { | |
6860 this._fire("onAddMany", []); | |
6861 } | |
6862 }; | |
6863 | |
6864 /* | |
6865 * Contributed by Morten Frederiksen, http://www.wasab.dk/morten/ | |
6866 */ | |
6867 Timeline.DefaultEventSource.prototype.loadSPARQL = function(xml, url) { | |
6868 var base = this._getBaseURL(url); | |
6869 | |
6870 var dateTimeFormat = 'iso8601'; | |
6871 var parseDateTimeFunction = this._events.getUnit().getParser(dateTimeFormat); | |
6872 | |
6873 if (xml == null) { | |
6874 return; | |
6875 } | |
6876 | |
6877 /* | |
6878 * Find <results> tag | |
6879 */ | |
6880 var node = xml.documentElement.firstChild; | |
6881 while (node != null && (node.nodeType != 1 || node.nodeName != 'results')) { | |
6882 node = node.nextSibling; | |
6883 } | |
6884 | |
6885 var wikiURL = null; | |
6886 var wikiSection = null; | |
6887 if (node != null) { | |
6888 wikiURL = node.getAttribute("wiki-url"); | |
6889 wikiSection = node.getAttribute("wiki-section"); | |
6890 | |
6891 node = node.firstChild; | |
6892 } | |
6893 | |
6894 var added = false; | |
6895 while (node != null) { | |
6896 if (node.nodeType == 1) { | |
6897 var bindings = { }; | |
6898 var binding = node.firstChild; | |
6899 while (binding != null) { | |
6900 if (binding.nodeType == 1 && | |
6901 binding.firstChild != null && | |
6902 binding.firstChild.nodeType == 1 && | |
6903 binding.firstChild.firstChild != null && | |
6904 binding.firstChild.firstChild.nodeType == 3) { | |
6905 bindings[binding.getAttribute('name')] = binding.firstChild.firstChild.nodeValue; | |
6906 } | |
6907 binding = binding.nextSibling; | |
6908 } | |
6909 | |
6910 if (bindings["start"] == null && bindings["date"] != null) { | |
6911 bindings["start"] = bindings["date"]; | |
6912 } | |
6913 | |
6914 // instant event: default is true. Or use values from isDuration or durationEvent | |
6915 var instant = (bindings["isDuration"] === null && | |
6916 bindings["durationEvent"] === null) || | |
6917 bindings["isDuration"] == "false" || | |
6918 bindings["durationEvent"] == "false"; | |
6919 | |
6920 var evt = new Timeline.DefaultEventSource.Event({ | |
6921 id: bindings["id"], | |
6922 start: parseDateTimeFunction(bindings["start"]), | |
6923 end: parseDateTimeFunction(bindings["end"]), | |
6924 latestStart: parseDateTimeFunction(bindings["latestStart"]), | |
6925 earliestEnd: parseDateTimeFunction(bindings["earliestEnd"]), | |
6926 instant: instant, // instant | |
6927 text: bindings["title"], // text | |
6928 description: bindings["description"], | |
6929 image: this._resolveRelativeURL(bindings["image"], base), | |
6930 link: this._resolveRelativeURL(bindings["link"] , base), | |
6931 icon: this._resolveRelativeURL(bindings["icon"] , base), | |
6932 color: bindings["color"], | |
6933 textColor: bindings["textColor"], | |
6934 hoverText: bindings["hoverText"], | |
6935 caption: bindings["caption"], | |
6936 classname: bindings["classname"], | |
6937 tapeImage: bindings["tapeImage"], | |
6938 tapeRepeat: bindings["tapeRepeat"], | |
6939 eventID: bindings["eventID"], | |
6940 trackNum: bindings["trackNum"] | |
6941 }); | |
6942 evt._bindings = bindings; | |
6943 evt.getProperty = function(name) { | |
6944 return this._bindings[name]; | |
6945 }; | |
6946 evt.setWikiInfo(wikiURL, wikiSection); | |
6947 | |
6948 this._events.add(evt); | |
6949 added = true; | |
6950 } | |
6951 node = node.nextSibling; | |
6952 } | |
6953 | |
6954 if (added) { | |
6955 this._fire("onAddMany", []); | |
6956 } | |
6957 }; | |
6958 | |
6959 Timeline.DefaultEventSource.prototype.add = function(evt) { | |
6960 this._events.add(evt); | |
6961 this._fire("onAddOne", [evt]); | |
6962 }; | |
6963 | |
6964 Timeline.DefaultEventSource.prototype.addMany = function(events) { | |
6965 for (var i = 0; i < events.length; i++) { | |
6966 this._events.add(events[i]); | |
6967 } | |
6968 this._fire("onAddMany", []); | |
6969 }; | |
6970 | |
6971 Timeline.DefaultEventSource.prototype.clear = function() { | |
6972 this._events.removeAll(); | |
6973 this._fire("onClear", []); | |
6974 }; | |
6975 | |
6976 Timeline.DefaultEventSource.prototype.getEvent = function(id) { | |
6977 return this._events.getEvent(id); | |
6978 }; | |
6979 | |
6980 Timeline.DefaultEventSource.prototype.getEventIterator = function(startDate, endDate) { | |
6981 return this._events.getIterator(startDate, endDate); | |
6982 }; | |
6983 | |
6984 Timeline.DefaultEventSource.prototype.getEventReverseIterator = function(startDate, endDate) { | |
6985 return this._events.getReverseIterator(startDate, endDate); | |
6986 }; | |
6987 | |
6988 Timeline.DefaultEventSource.prototype.getAllEventIterator = function() { | |
6989 return this._events.getAllIterator(); | |
6990 }; | |
6991 | |
6992 Timeline.DefaultEventSource.prototype.getCount = function() { | |
6993 return this._events.getCount(); | |
6994 }; | |
6995 | |
6996 Timeline.DefaultEventSource.prototype.getEarliestDate = function() { | |
6997 return this._events.getEarliestDate(); | |
6998 }; | |
6999 | |
7000 Timeline.DefaultEventSource.prototype.getLatestDate = function() { | |
7001 return this._events.getLatestDate(); | |
7002 }; | |
7003 | |
7004 Timeline.DefaultEventSource.prototype._fire = function(handlerName, args) { | |
7005 for (var i = 0; i < this._listeners.length; i++) { | |
7006 var listener = this._listeners[i]; | |
7007 if (handlerName in listener) { | |
7008 try { | |
7009 listener[handlerName].apply(listener, args); | |
7010 } catch (e) { | |
7011 SimileAjax.Debug.exception(e); | |
7012 } | |
7013 } | |
7014 } | |
7015 }; | |
7016 | |
7017 Timeline.DefaultEventSource.prototype._getBaseURL = function(url) { | |
7018 if (url.indexOf("://") < 0) { | |
7019 var url2 = this._getBaseURL(document.location.href); | |
7020 if (url.substr(0,1) == "/") { | |
7021 url = url2.substr(0, url2.indexOf("/", url2.indexOf("://") + 3)) + url; | |
7022 } else { | |
7023 url = url2 + url; | |
7024 } | |
7025 } | |
7026 | |
7027 var i = url.lastIndexOf("/"); | |
7028 if (i < 0) { | |
7029 return ""; | |
7030 } else { | |
7031 return url.substr(0, i+1); | |
7032 } | |
7033 }; | |
7034 | |
7035 Timeline.DefaultEventSource.prototype._resolveRelativeURL = function(url, base) { | |
7036 if (url == null || url == "") { | |
7037 return url; | |
7038 } else if (url.indexOf("://") > 0) { | |
7039 return url; | |
7040 } else if (url.substr(0,1) == "/") { | |
7041 return base.substr(0, base.indexOf("/", base.indexOf("://") + 3)) + url; | |
7042 } else { | |
7043 return base + url; | |
7044 } | |
7045 }; | |
7046 | |
7047 | |
7048 Timeline.DefaultEventSource.Event = function(args) { | |
7049 // | |
7050 // Attention developers! | |
7051 // If you add a new event attribute, please be sure to add it to | |
7052 // all three load functions: loadXML, loadSPARCL, loadJSON. | |
7053 // Thanks! | |
7054 // | |
7055 // args is a hash/object. It supports the following keys. Most are optional | |
7056 // id -- an internal id. Really shouldn't be used by events. | |
7057 // Timeline library clients should use eventID | |
7058 // eventID -- For use by library client when writing custom painters or | |
7059 // custom fillInfoBubble | |
7060 // start | |
7061 // end | |
7062 // latestStart | |
7063 // earliestEnd | |
7064 // instant -- boolean. Controls precise/non-precise logic & duration/instant issues | |
7065 // text -- event source attribute 'title' -- used as the label on Timelines and in bubbles. | |
7066 // description -- used in bubbles | |
7067 // image -- used in bubbles | |
7068 // link -- used in bubbles | |
7069 // icon -- on the Timeline | |
7070 // color -- Timeline label and tape color | |
7071 // textColor -- Timeline label color, overrides color attribute | |
7072 // hoverText -- deprecated, here for backwards compatibility. | |
7073 // Superceeded by caption | |
7074 // caption -- tooltip-like caption on the Timeline. Uses HTML title attribute | |
7075 // classname -- used to set classname in Timeline. Enables better CSS selector rules | |
7076 // tapeImage -- background image of the duration event's tape div on the Timeline | |
7077 // tapeRepeat -- repeat attribute for tapeImage. {repeat | repeat-x | repeat-y } | |
7078 | |
7079 function cleanArg(arg) { | |
7080 // clean up an arg | |
7081 return (args[arg] != null && args[arg] != "") ? args[arg] : null; | |
7082 } | |
7083 | |
7084 var id = args.id ? args.id.trim() : ""; | |
7085 this._id = id.length > 0 ? id : Timeline.EventUtils.getNewEventID(); | |
7086 | |
7087 this._instant = args.instant || (args.end == null); | |
7088 | |
7089 this._start = args.start; | |
7090 this._end = (args.end != null) ? args.end : args.start; | |
7091 | |
7092 this._latestStart = (args.latestStart != null) ? | |
7093 args.latestStart : (args.instant ? this._end : this._start); | |
7094 this._earliestEnd = (args.earliestEnd != null) ? args.earliestEnd : this._end; | |
7095 | |
7096 // check sanity of dates since incorrect dates will later cause calculation errors | |
7097 // when painting | |
7098 var err=[]; | |
7099 if (this._start > this._latestStart) { | |
7100 this._latestStart = this._start; | |
7101 err.push("start is > latestStart");} | |
7102 if (this._start > this._earliestEnd) { | |
7103 this._earliestEnd = this._latestStart; | |
7104 err.push("start is > earliestEnd");} | |
7105 if (this._start > this._end) { | |
7106 this._end = this._earliestEnd; | |
7107 err.push("start is > end");} | |
7108 if (this._latestStart > this._earliestEnd) { | |
7109 this._earliestEnd = this._latestStart; | |
7110 err.push("latestStart is > earliestEnd");} | |
7111 if (this._latestStart > this._end) { | |
7112 this._end = this._earliestEnd; | |
7113 err.push("latestStart is > end");} | |
7114 if (this._earliestEnd > this._end) { | |
7115 this._end = this._earliestEnd; | |
7116 err.push("earliestEnd is > end");} | |
7117 | |
7118 this._eventID = cleanArg('eventID'); | |
7119 this._text = (args.text != null) ? SimileAjax.HTML.deEntify(args.text) : ""; // Change blank titles to "" | |
7120 if (err.length > 0) { | |
7121 this._text += " PROBLEM: " + err.join(", "); | |
7122 } | |
7123 | |
7124 this._description = SimileAjax.HTML.deEntify(args.description); | |
7125 this._image = cleanArg('image'); | |
7126 this._link = cleanArg('link'); | |
7127 this._title = cleanArg('hoverText'); | |
7128 this._title = cleanArg('caption'); | |
7129 | |
7130 this._icon = cleanArg('icon'); | |
7131 this._color = cleanArg('color'); | |
7132 this._textColor = cleanArg('textColor'); | |
7133 this._classname = cleanArg('classname'); | |
7134 this._tapeImage = cleanArg('tapeImage'); | |
7135 this._tapeRepeat = cleanArg('tapeRepeat'); | |
7136 this._trackNum = cleanArg('trackNum'); | |
7137 if (this._trackNum != null) { | |
7138 this._trackNum = parseInt(this._trackNum); | |
7139 } | |
7140 | |
7141 this._wikiURL = null; | |
7142 this._wikiSection = null; | |
7143 }; | |
7144 | |
7145 Timeline.DefaultEventSource.Event.prototype = { | |
7146 getID: function() { return this._id; }, | |
7147 | |
7148 isInstant: function() { return this._instant; }, | |
7149 isImprecise: function() { return this._start != this._latestStart || this._end != this._earliestEnd; }, | |
7150 | |
7151 getStart: function() { return this._start; }, | |
7152 getEnd: function() { return this._end; }, | |
7153 getLatestStart: function() { return this._latestStart; }, | |
7154 getEarliestEnd: function() { return this._earliestEnd; }, | |
7155 | |
7156 getEventID: function() { return this._eventID; }, | |
7157 getText: function() { return this._text; }, // title | |
7158 getDescription: function() { return this._description; }, | |
7159 getImage: function() { return this._image; }, | |
7160 getLink: function() { return this._link; }, | |
7161 | |
7162 getIcon: function() { return this._icon; }, | |
7163 getColor: function() { return this._color; }, | |
7164 getTextColor: function() { return this._textColor; }, | |
7165 getClassName: function() { return this._classname; }, | |
7166 getTapeImage: function() { return this._tapeImage; }, | |
7167 getTapeRepeat: function() { return this._tapeRepeat; }, | |
7168 getTrackNum: function() { return this._trackNum; }, | |
7169 | |
7170 getProperty: function(name) { return null; }, | |
7171 | |
7172 getWikiURL: function() { return this._wikiURL; }, | |
7173 getWikiSection: function() { return this._wikiSection; }, | |
7174 setWikiInfo: function(wikiURL, wikiSection) { | |
7175 this._wikiURL = wikiURL; | |
7176 this._wikiSection = wikiSection; | |
7177 }, | |
7178 | |
7179 fillDescription: function(elmt) { | |
7180 elmt.innerHTML = this._description; | |
7181 }, | |
7182 fillWikiInfo: function(elmt) { | |
7183 // Many bubbles will not support a wiki link. | |
7184 // | |
7185 // Strategy: assume no wiki link. If we do have | |
7186 // enough parameters for one, then create it. | |
7187 elmt.style.display = "none"; // default | |
7188 | |
7189 if (this._wikiURL == null || this._wikiSection == null) { | |
7190 return; // EARLY RETURN | |
7191 } | |
7192 | |
7193 // create the wikiID from the property or from the event text (the title) | |
7194 var wikiID = this.getProperty("wikiID"); | |
7195 if (wikiID == null || wikiID.length == 0) { | |
7196 wikiID = this.getText(); // use the title as the backup wiki id | |
7197 } | |
7198 | |
7199 if (wikiID == null || wikiID.length == 0) { | |
7200 return; // No wikiID. Thus EARLY RETURN | |
7201 } | |
7202 | |
7203 // ready to go... | |
7204 elmt.style.display = "inline"; | |
7205 wikiID = wikiID.replace(/\s/g, "_"); | |
7206 var url = this._wikiURL + this._wikiSection.replace(/\s/g, "_") + "/" + wikiID; | |
7207 var a = document.createElement("a"); | |
7208 a.href = url; | |
7209 a.target = "new"; | |
7210 a.innerHTML = Timeline.strings[Timeline.clientLocale].wikiLinkLabel; | |
7211 | |
7212 elmt.appendChild(document.createTextNode("[")); | |
7213 elmt.appendChild(a); | |
7214 elmt.appendChild(document.createTextNode("]")); | |
7215 }, | |
7216 | |
7217 fillTime: function(elmt, labeller) { | |
7218 if (this._instant) { | |
7219 if (this.isImprecise()) { | |
7220 elmt.appendChild(elmt.ownerDocument.createTextNode(labeller.labelPrecise(this._start))); | |
7221 elmt.appendChild(elmt.ownerDocument.createElement("br")); | |
7222 elmt.appendChild(elmt.ownerDocument.createTextNode(labeller.labelPrecise(this._end))); | |
7223 } else { | |
7224 elmt.appendChild(elmt.ownerDocument.createTextNode(labeller.labelPrecise(this._start))); | |
7225 } | |
7226 } else { | |
7227 if (this.isImprecise()) { | |
7228 elmt.appendChild(elmt.ownerDocument.createTextNode( | |
7229 labeller.labelPrecise(this._start) + " ~ " + labeller.labelPrecise(this._latestStart))); | |
7230 elmt.appendChild(elmt.ownerDocument.createElement("br")); | |
7231 elmt.appendChild(elmt.ownerDocument.createTextNode( | |
7232 labeller.labelPrecise(this._earliestEnd) + " ~ " + labeller.labelPrecise(this._end))); | |
7233 } else { | |
7234 elmt.appendChild(elmt.ownerDocument.createTextNode(labeller.labelPrecise(this._start))); | |
7235 elmt.appendChild(elmt.ownerDocument.createElement("br")); | |
7236 elmt.appendChild(elmt.ownerDocument.createTextNode(labeller.labelPrecise(this._end))); | |
7237 } | |
7238 } | |
7239 }, | |
7240 | |
7241 fillInfoBubble: function(elmt, theme, labeller) { | |
7242 var doc = elmt.ownerDocument; | |
7243 | |
7244 var title = this.getText(); | |
7245 var link = this.getLink(); | |
7246 var image = this.getImage(); | |
7247 | |
7248 if (image != null) { | |
7249 var img = doc.createElement("img"); | |
7250 img.src = image; | |
7251 | |
7252 theme.event.bubble.imageStyler(img); | |
7253 elmt.appendChild(img); | |
7254 } | |
7255 | |
7256 var divTitle = doc.createElement("div"); | |
7257 var textTitle = doc.createTextNode(title); | |
7258 if (link != null) { | |
7259 var a = doc.createElement("a"); | |
7260 a.href = link; | |
7261 a.appendChild(textTitle); | |
7262 divTitle.appendChild(a); | |
7263 } else { | |
7264 divTitle.appendChild(textTitle); | |
7265 } | |
7266 theme.event.bubble.titleStyler(divTitle); | |
7267 elmt.appendChild(divTitle); | |
7268 | |
7269 var divBody = doc.createElement("div"); | |
7270 this.fillDescription(divBody); | |
7271 theme.event.bubble.bodyStyler(divBody); | |
7272 elmt.appendChild(divBody); | |
7273 | |
7274 var divTime = doc.createElement("div"); | |
7275 this.fillTime(divTime, labeller); | |
7276 theme.event.bubble.timeStyler(divTime); | |
7277 elmt.appendChild(divTime); | |
7278 | |
7279 var divWiki = doc.createElement("div"); | |
7280 this.fillWikiInfo(divWiki); | |
7281 theme.event.bubble.wikiStyler(divWiki); | |
7282 elmt.appendChild(divWiki); | |
7283 } | |
7284 }; | |
7285 | |
7286 | |
7287 /*================================================== | |
7288 * Original Event Painter | |
7289 *================================================== | |
7290 */ | |
7291 | |
7292 /*================================================== | |
7293 * | |
7294 * To enable a single event listener to monitor everything | |
7295 * on a Timeline, we need a way to map from an event's icon, | |
7296 * label or tape element to the associated timeline, band and | |
7297 * specific event. | |
7298 * | |
7299 * Thus a set format is used for the id's of the | |
7300 * events' elements on the Timeline-- | |
7301 * | |
7302 * element id format for labels, icons, tapes: | |
7303 * labels: label-tl-<timelineID>-<band_index>-<evt.id> | |
7304 * icons: icon-tl-<timelineID>-<band_index>-<evt.id> | |
7305 * tapes: tape1-tl-<timelineID>-<band_index>-<evt.id> | |
7306 * tape2-tl-<timelineID>-<band_index>-<evt.id> | |
7307 * // some events have more than one tape | |
7308 * highlight: highlight1-tl-<timelineID>-<band_index>-<evt.id> | |
7309 * highlight2-tl-<timelineID>-<band_index>-<evt.id> | |
7310 * // some events have more than one highlight div (future) | |
7311 * You can then retrieve the band/timeline objects and event object | |
7312 * by using Timeline.EventUtils.decodeEventElID | |
7313 * | |
7314 *================================================== | |
7315 */ | |
7316 | |
7317 /* | |
7318 * eventPaintListener functions receive calls about painting. | |
7319 * function(band, op, evt, els) | |
7320 * context: 'this' will be an OriginalEventPainter object. | |
7321 * It has properties and methods for obtaining | |
7322 * the relevant band, timeline, etc | |
7323 * band = the band being painted | |
7324 * op = 'paintStarting' // the painter is about to remove | |
7325 * all previously painted events, if any. It will | |
7326 * then start painting all of the visible events that | |
7327 * pass the filter. | |
7328 * evt = null, els = null | |
7329 * op = 'paintEnded' // the painter has finished painting | |
7330 * all of the visible events that passed the filter | |
7331 * evt = null, els = null | |
7332 * op = 'paintedEvent' // the painter just finished painting an event | |
7333 * evt = event just painted | |
7334 * els = array of painted elements' divs. Depending on the event, | |
7335 * the array could be just a tape or icon (if no label). | |
7336 * Or could include label, multiple tape divs (imprecise event), | |
7337 * highlight divs. The array is not ordered. The meaning of | |
7338 * each el is available by decoding the el's id | |
7339 * Note that there may be no paintedEvent calls if no events were visible | |
7340 * or passed the filter. | |
7341 */ | |
7342 | |
7343 Timeline.OriginalEventPainter = function(params) { | |
7344 this._params = params; | |
7345 this._onSelectListeners = []; | |
7346 this._eventPaintListeners = []; | |
7347 | |
7348 this._filterMatcher = null; | |
7349 this._highlightMatcher = null; | |
7350 this._frc = null; | |
7351 | |
7352 this._eventIdToElmt = {}; | |
7353 }; | |
7354 | |
7355 Timeline.OriginalEventPainter.prototype.initialize = function(band, timeline) { | |
7356 this._band = band; | |
7357 this._timeline = timeline; | |
7358 | |
7359 this._backLayer = null; | |
7360 this._eventLayer = null; | |
7361 this._lineLayer = null; | |
7362 this._highlightLayer = null; | |
7363 | |
7364 this._eventIdToElmt = null; | |
7365 }; | |
7366 | |
7367 Timeline.OriginalEventPainter.prototype.getType = function() { | |
7368 return 'original'; | |
7369 }; | |
7370 | |
7371 Timeline.OriginalEventPainter.prototype.supportsOrthogonalScrolling = function() { | |
7372 return true; | |
7373 }; | |
7374 | |
7375 Timeline.OriginalEventPainter.prototype.addOnSelectListener = function(listener) { | |
7376 this._onSelectListeners.push(listener); | |
7377 }; | |
7378 | |
7379 Timeline.OriginalEventPainter.prototype.removeOnSelectListener = function(listener) { | |
7380 for (var i = 0; i < this._onSelectListeners.length; i++) { | |
7381 if (this._onSelectListeners[i] == listener) { | |
7382 this._onSelectListeners.splice(i, 1); | |
7383 break; | |
7384 } | |
7385 } | |
7386 }; | |
7387 | |
7388 Timeline.OriginalEventPainter.prototype.addEventPaintListener = function(listener) { | |
7389 this._eventPaintListeners.push(listener); | |
7390 }; | |
7391 | |
7392 Timeline.OriginalEventPainter.prototype.removeEventPaintListener = function(listener) { | |
7393 for (var i = 0; i < this._eventPaintListeners.length; i++) { | |
7394 if (this._eventPaintListeners[i] == listener) { | |
7395 this._eventPaintListeners.splice(i, 1); | |
7396 break; | |
7397 } | |
7398 } | |
7399 }; | |
7400 | |
7401 Timeline.OriginalEventPainter.prototype.getFilterMatcher = function() { | |
7402 return this._filterMatcher; | |
7403 }; | |
7404 | |
7405 Timeline.OriginalEventPainter.prototype.setFilterMatcher = function(filterMatcher) { | |
7406 this._filterMatcher = filterMatcher; | |
7407 }; | |
7408 | |
7409 Timeline.OriginalEventPainter.prototype.getHighlightMatcher = function() { | |
7410 return this._highlightMatcher; | |
7411 }; | |
7412 | |
7413 Timeline.OriginalEventPainter.prototype.setHighlightMatcher = function(highlightMatcher) { | |
7414 this._highlightMatcher = highlightMatcher; | |
7415 }; | |
7416 | |
7417 Timeline.OriginalEventPainter.prototype.paint = function() { | |
7418 // Paints the events for a given section of the band--what is | |
7419 // visible on screen and some extra. | |
7420 var eventSource = this._band.getEventSource(); | |
7421 if (eventSource == null) { | |
7422 return; | |
7423 } | |
7424 | |
7425 this._eventIdToElmt = {}; | |
7426 this._fireEventPaintListeners('paintStarting', null, null); | |
7427 this._prepareForPainting(); | |
7428 | |
7429 var metrics = this._computeMetrics(); | |
7430 var minDate = this._band.getMinDate(); | |
7431 var maxDate = this._band.getMaxDate(); | |
7432 | |
7433 var filterMatcher = (this._filterMatcher != null) ? | |
7434 this._filterMatcher : | |
7435 function(evt) { return true; }; | |
7436 var highlightMatcher = (this._highlightMatcher != null) ? | |
7437 this._highlightMatcher : | |
7438 function(evt) { return -1; }; | |
7439 | |
7440 var iterator = eventSource.getEventReverseIterator(minDate, maxDate); | |
7441 while (iterator.hasNext()) { | |
7442 var evt = iterator.next(); | |
7443 if (filterMatcher(evt)) { | |
7444 this.paintEvent(evt, metrics, this._params.theme, highlightMatcher(evt)); | |
7445 } | |
7446 } | |
7447 | |
7448 this._highlightLayer.style.display = "block"; | |
7449 this._lineLayer.style.display = "block"; | |
7450 this._eventLayer.style.display = "block"; | |
7451 // update the band object for max number of tracks in this section of the ether | |
7452 this._band.updateEventTrackInfo(this._tracks.length, metrics.trackIncrement); | |
7453 this._fireEventPaintListeners('paintEnded', null, null); | |
7454 | |
7455 this._setOrthogonalOffset(metrics); | |
7456 }; | |
7457 | |
7458 Timeline.OriginalEventPainter.prototype.softPaint = function() { | |
7459 this._setOrthogonalOffset(this._computeMetrics()); | |
7460 }; | |
7461 | |
7462 Timeline.OriginalEventPainter.prototype.getOrthogonalExtent = function() { | |
7463 var metrics = this._computeMetrics(); | |
7464 return 2 * metrics.trackOffset + this._tracks.length * metrics.trackIncrement; | |
7465 }; | |
7466 | |
7467 Timeline.OriginalEventPainter.prototype._setOrthogonalOffset = function(metrics) { | |
7468 var orthogonalOffset = this._band.getViewOrthogonalOffset(); | |
7469 | |
7470 this._highlightLayer.style.top = | |
7471 this._lineLayer.style.top = | |
7472 this._eventLayer.style.top = | |
7473 orthogonalOffset + "px"; | |
7474 }; | |
7475 | |
7476 Timeline.OriginalEventPainter.prototype._computeMetrics = function() { | |
7477 var eventTheme = this._params.theme.event; | |
7478 var trackHeight = Math.max(eventTheme.track.height, eventTheme.tape.height + | |
7479 this._frc.getLineHeight()); | |
7480 var metrics = { | |
7481 trackOffset: eventTheme.track.offset, | |
7482 trackHeight: trackHeight, | |
7483 trackGap: eventTheme.track.gap, | |
7484 trackIncrement: trackHeight + eventTheme.track.gap, | |
7485 icon: eventTheme.instant.icon, | |
7486 iconWidth: eventTheme.instant.iconWidth, | |
7487 iconHeight: eventTheme.instant.iconHeight, | |
7488 labelWidth: eventTheme.label.width, | |
7489 maxLabelChar: eventTheme.label.maxLabelChar, | |
7490 impreciseIconMargin: eventTheme.instant.impreciseIconMargin | |
7491 }; | |
7492 | |
7493 return metrics; | |
7494 }; | |
7495 | |
7496 Timeline.OriginalEventPainter.prototype._prepareForPainting = function() { | |
7497 // Remove everything previously painted: highlight, line and event layers. | |
7498 // Prepare blank layers for painting. | |
7499 var band = this._band; | |
7500 | |
7501 if (this._backLayer == null) { | |
7502 this._backLayer = this._band.createLayerDiv(0, "timeline-band-events"); | |
7503 this._backLayer.style.visibility = "hidden"; | |
7504 | |
7505 var eventLabelPrototype = document.createElement("span"); | |
7506 eventLabelPrototype.className = "timeline-event-label"; | |
7507 this._backLayer.appendChild(eventLabelPrototype); | |
7508 this._frc = SimileAjax.Graphics.getFontRenderingContext(eventLabelPrototype); | |
7509 } | |
7510 this._frc.update(); | |
7511 this._tracks = []; | |
7512 | |
7513 if (this._highlightLayer != null) { | |
7514 band.removeLayerDiv(this._highlightLayer); | |
7515 } | |
7516 this._highlightLayer = band.createLayerDiv(105, "timeline-band-highlights"); | |
7517 this._highlightLayer.style.display = "none"; | |
7518 | |
7519 if (this._lineLayer != null) { | |
7520 band.removeLayerDiv(this._lineLayer); | |
7521 } | |
7522 this._lineLayer = band.createLayerDiv(110, "timeline-band-lines"); | |
7523 this._lineLayer.style.display = "none"; | |
7524 | |
7525 if (this._eventLayer != null) { | |
7526 band.removeLayerDiv(this._eventLayer); | |
7527 } | |
7528 this._eventLayer = band.createLayerDiv(115, "timeline-band-events"); | |
7529 this._eventLayer.style.display = "none"; | |
7530 }; | |
7531 | |
7532 Timeline.OriginalEventPainter.prototype.paintEvent = function(evt, metrics, theme, highlightIndex) { | |
7533 if (evt.isInstant()) { | |
7534 this.paintInstantEvent(evt, metrics, theme, highlightIndex); | |
7535 } else { | |
7536 this.paintDurationEvent(evt, metrics, theme, highlightIndex); | |
7537 } | |
7538 }; | |
7539 | |
7540 Timeline.OriginalEventPainter.prototype.paintInstantEvent = function(evt, metrics, theme, highlightIndex) { | |
7541 if (evt.isImprecise()) { | |
7542 this.paintImpreciseInstantEvent(evt, metrics, theme, highlightIndex); | |
7543 } else { | |
7544 this.paintPreciseInstantEvent(evt, metrics, theme, highlightIndex); | |
7545 } | |
7546 } | |
7547 | |
7548 Timeline.OriginalEventPainter.prototype.paintDurationEvent = function(evt, metrics, theme, highlightIndex) { | |
7549 if (evt.isImprecise()) { | |
7550 this.paintImpreciseDurationEvent(evt, metrics, theme, highlightIndex); | |
7551 } else { | |
7552 this.paintPreciseDurationEvent(evt, metrics, theme, highlightIndex); | |
7553 } | |
7554 } | |
7555 | |
7556 Timeline.OriginalEventPainter.prototype.paintPreciseInstantEvent = function(evt, metrics, theme, highlightIndex) { | |
7557 var doc = this._timeline.getDocument(); | |
7558 var text = evt.getText(); | |
7559 | |
7560 var startDate = evt.getStart(); | |
7561 var startPixel = Math.round(this._band.dateToPixelOffset(startDate)); | |
7562 var iconRightEdge = Math.round(startPixel + metrics.iconWidth / 2); | |
7563 var iconLeftEdge = Math.round(startPixel - metrics.iconWidth / 2); | |
7564 | |
7565 var labelDivClassName = this._getLabelDivClassName(evt); | |
7566 var labelSize = this._frc.computeSize(text, labelDivClassName); | |
7567 var labelLeft = iconRightEdge + theme.event.label.offsetFromLine; | |
7568 var labelRight = labelLeft + labelSize.width; | |
7569 | |
7570 var rightEdge = labelRight; | |
7571 var track = this._findFreeTrack(evt, rightEdge); | |
7572 | |
7573 var labelTop = Math.round( | |
7574 metrics.trackOffset + track * metrics.trackIncrement + | |
7575 metrics.trackHeight / 2 - labelSize.height / 2); | |
7576 | |
7577 var iconElmtData = this._paintEventIcon(evt, track, iconLeftEdge, metrics, theme, 0); | |
7578 var labelElmtData = this._paintEventLabel(evt, text, labelLeft, labelTop, labelSize.width, | |
7579 labelSize.height, theme, labelDivClassName, highlightIndex); | |
7580 var els = [iconElmtData.elmt, labelElmtData.elmt]; | |
7581 | |
7582 var self = this; | |
7583 var clickHandler = function(elmt, domEvt, target) { | |
7584 return self._onClickInstantEvent(iconElmtData.elmt, domEvt, evt); | |
7585 }; | |
7586 SimileAjax.DOM.registerEvent(iconElmtData.elmt, "mousedown", clickHandler); | |
7587 SimileAjax.DOM.registerEvent(labelElmtData.elmt, "mousedown", clickHandler); | |
7588 | |
7589 var hDiv = this._createHighlightDiv(highlightIndex, iconElmtData, theme, evt); | |
7590 if (hDiv != null) {els.push(hDiv);} | |
7591 this._fireEventPaintListeners('paintedEvent', evt, els); | |
7592 | |
7593 | |
7594 this._eventIdToElmt[evt.getID()] = iconElmtData.elmt; | |
7595 this._tracks[track] = iconLeftEdge; | |
7596 }; | |
7597 | |
7598 Timeline.OriginalEventPainter.prototype.paintImpreciseInstantEvent = function(evt, metrics, theme, highlightIndex) { | |
7599 var doc = this._timeline.getDocument(); | |
7600 var text = evt.getText(); | |
7601 | |
7602 var startDate = evt.getStart(); | |
7603 var endDate = evt.getEnd(); | |
7604 var startPixel = Math.round(this._band.dateToPixelOffset(startDate)); | |
7605 var endPixel = Math.round(this._band.dateToPixelOffset(endDate)); | |
7606 | |
7607 var iconRightEdge = Math.round(startPixel + metrics.iconWidth / 2); | |
7608 var iconLeftEdge = Math.round(startPixel - metrics.iconWidth / 2); | |
7609 | |
7610 var labelDivClassName = this._getLabelDivClassName(evt); | |
7611 var labelSize = this._frc.computeSize(text, labelDivClassName); | |
7612 var labelLeft = iconRightEdge + theme.event.label.offsetFromLine; | |
7613 var labelRight = labelLeft + labelSize.width; | |
7614 | |
7615 var rightEdge = Math.max(labelRight, endPixel); | |
7616 var track = this._findFreeTrack(evt, rightEdge); | |
7617 var tapeHeight = theme.event.tape.height; | |
7618 var labelTop = Math.round( | |
7619 metrics.trackOffset + track * metrics.trackIncrement + tapeHeight); | |
7620 | |
7621 var iconElmtData = this._paintEventIcon(evt, track, iconLeftEdge, metrics, theme, tapeHeight); | |
7622 var labelElmtData = this._paintEventLabel(evt, text, labelLeft, labelTop, labelSize.width, | |
7623 labelSize.height, theme, labelDivClassName, highlightIndex); | |
7624 | |
7625 var color = evt.getColor(); | |
7626 color = color != null ? color : theme.event.instant.impreciseColor; | |
7627 | |
7628 var tapeElmtData = this._paintEventTape(evt, track, startPixel, endPixel, | |
7629 color, theme.event.instant.impreciseOpacity, metrics, theme, 0); | |
7630 var els = [iconElmtData.elmt, labelElmtData.elmt, tapeElmtData.elmt]; | |
7631 | |
7632 var self = this; | |
7633 var clickHandler = function(elmt, domEvt, target) { | |
7634 return self._onClickInstantEvent(iconElmtData.elmt, domEvt, evt); | |
7635 }; | |
7636 SimileAjax.DOM.registerEvent(iconElmtData.elmt, "mousedown", clickHandler); | |
7637 SimileAjax.DOM.registerEvent(tapeElmtData.elmt, "mousedown", clickHandler); | |
7638 SimileAjax.DOM.registerEvent(labelElmtData.elmt, "mousedown", clickHandler); | |
7639 | |
7640 var hDiv = this._createHighlightDiv(highlightIndex, iconElmtData, theme, evt); | |
7641 if (hDiv != null) {els.push(hDiv);} | |
7642 this._fireEventPaintListeners('paintedEvent', evt, els); | |
7643 | |
7644 this._eventIdToElmt[evt.getID()] = iconElmtData.elmt; | |
7645 this._tracks[track] = iconLeftEdge; | |
7646 }; | |
7647 | |
7648 Timeline.OriginalEventPainter.prototype.paintPreciseDurationEvent = function(evt, metrics, theme, highlightIndex) { | |
7649 var doc = this._timeline.getDocument(); | |
7650 var text = evt.getText(); | |
7651 | |
7652 var startDate = evt.getStart(); | |
7653 var endDate = evt.getEnd(); | |
7654 var startPixel = Math.round(this._band.dateToPixelOffset(startDate)); | |
7655 var endPixel = Math.round(this._band.dateToPixelOffset(endDate)); | |
7656 | |
7657 var labelDivClassName = this._getLabelDivClassName(evt); | |
7658 var labelSize = this._frc.computeSize(text, labelDivClassName); | |
7659 var labelLeft = startPixel; | |
7660 var labelRight = labelLeft + labelSize.width; | |
7661 | |
7662 var rightEdge = Math.max(labelRight, endPixel); | |
7663 var track = this._findFreeTrack(evt, rightEdge); | |
7664 var labelTop = Math.round( | |
7665 metrics.trackOffset + track * metrics.trackIncrement + theme.event.tape.height); | |
7666 | |
7667 var color = evt.getColor(); | |
7668 color = color != null ? color : theme.event.duration.color; | |
7669 | |
7670 var tapeElmtData = this._paintEventTape(evt, track, startPixel, endPixel, color, 100, metrics, theme, 0); | |
7671 var labelElmtData = this._paintEventLabel(evt, text, labelLeft, labelTop, labelSize.width, | |
7672 labelSize.height, theme, labelDivClassName, highlightIndex); | |
7673 var els = [tapeElmtData.elmt, labelElmtData.elmt]; | |
7674 | |
7675 var self = this; | |
7676 var clickHandler = function(elmt, domEvt, target) { | |
7677 return self._onClickDurationEvent(tapeElmtData.elmt, domEvt, evt); | |
7678 }; | |
7679 SimileAjax.DOM.registerEvent(tapeElmtData.elmt, "mousedown", clickHandler); | |
7680 SimileAjax.DOM.registerEvent(labelElmtData.elmt, "mousedown", clickHandler); | |
7681 | |
7682 var hDiv = this._createHighlightDiv(highlightIndex, tapeElmtData, theme, evt); | |
7683 if (hDiv != null) {els.push(hDiv);} | |
7684 this._fireEventPaintListeners('paintedEvent', evt, els); | |
7685 | |
7686 this._eventIdToElmt[evt.getID()] = tapeElmtData.elmt; | |
7687 this._tracks[track] = startPixel; | |
7688 }; | |
7689 | |
7690 Timeline.OriginalEventPainter.prototype.paintImpreciseDurationEvent = function(evt, metrics, theme, highlightIndex) { | |
7691 var doc = this._timeline.getDocument(); | |
7692 var text = evt.getText(); | |
7693 | |
7694 var startDate = evt.getStart(); | |
7695 var latestStartDate = evt.getLatestStart(); | |
7696 var endDate = evt.getEnd(); | |
7697 var earliestEndDate = evt.getEarliestEnd(); | |
7698 | |
7699 var startPixel = Math.round(this._band.dateToPixelOffset(startDate)); | |
7700 var latestStartPixel = Math.round(this._band.dateToPixelOffset(latestStartDate)); | |
7701 var endPixel = Math.round(this._band.dateToPixelOffset(endDate)); | |
7702 var earliestEndPixel = Math.round(this._band.dateToPixelOffset(earliestEndDate)); | |
7703 | |
7704 var labelDivClassName = this._getLabelDivClassName(evt); | |
7705 var labelSize = this._frc.computeSize(text, labelDivClassName); | |
7706 var labelLeft = latestStartPixel; | |
7707 var labelRight = labelLeft + labelSize.width; | |
7708 | |
7709 var rightEdge = Math.max(labelRight, endPixel); | |
7710 var track = this._findFreeTrack(evt, rightEdge); | |
7711 var labelTop = Math.round( | |
7712 metrics.trackOffset + track * metrics.trackIncrement + theme.event.tape.height); | |
7713 | |
7714 var color = evt.getColor(); | |
7715 color = color != null ? color : theme.event.duration.color; | |
7716 | |
7717 // Imprecise events can have two event tapes | |
7718 // The imprecise dates tape, uses opacity to be dimmer than precise dates | |
7719 var impreciseTapeElmtData = this._paintEventTape(evt, track, startPixel, endPixel, | |
7720 theme.event.duration.impreciseColor, | |
7721 theme.event.duration.impreciseOpacity, metrics, theme, 0); | |
7722 // The precise dates tape, regular (100%) opacity | |
7723 var tapeElmtData = this._paintEventTape(evt, track, latestStartPixel, | |
7724 earliestEndPixel, color, 100, metrics, theme, 1); | |
7725 | |
7726 var labelElmtData = this._paintEventLabel(evt, text, labelLeft, labelTop, | |
7727 labelSize.width, labelSize.height, theme, labelDivClassName, highlightIndex); | |
7728 var els = [impreciseTapeElmtData.elmt, tapeElmtData.elmt, labelElmtData.elmt]; | |
7729 | |
7730 var self = this; | |
7731 var clickHandler = function(elmt, domEvt, target) { | |
7732 return self._onClickDurationEvent(tapeElmtData.elmt, domEvt, evt); | |
7733 }; | |
7734 SimileAjax.DOM.registerEvent(tapeElmtData.elmt, "mousedown", clickHandler); | |
7735 SimileAjax.DOM.registerEvent(labelElmtData.elmt, "mousedown", clickHandler); | |
7736 | |
7737 var hDiv = this._createHighlightDiv(highlightIndex, tapeElmtData, theme, evt); | |
7738 if (hDiv != null) {els.push(hDiv);} | |
7739 this._fireEventPaintListeners('paintedEvent', evt, els); | |
7740 | |
7741 this._eventIdToElmt[evt.getID()] = tapeElmtData.elmt; | |
7742 this._tracks[track] = startPixel; | |
7743 }; | |
7744 | |
7745 Timeline.OriginalEventPainter.prototype._encodeEventElID = function(elType, evt) { | |
7746 return Timeline.EventUtils.encodeEventElID(this._timeline, this._band, elType, evt); | |
7747 }; | |
7748 | |
7749 Timeline.OriginalEventPainter.prototype._findFreeTrack = function(event, rightEdge) { | |
7750 var trackAttribute = event.getTrackNum(); | |
7751 if (trackAttribute != null) { | |
7752 return trackAttribute; // early return since event includes track number | |
7753 } | |
7754 | |
7755 // normal case: find an open track | |
7756 for (var i = 0; i < this._tracks.length; i++) { | |
7757 var t = this._tracks[i]; | |
7758 if (t > rightEdge) { | |
7759 break; | |
7760 } | |
7761 } | |
7762 return i; | |
7763 }; | |
7764 | |
7765 Timeline.OriginalEventPainter.prototype._paintEventIcon = function(evt, iconTrack, left, metrics, theme, tapeHeight) { | |
7766 // If no tape, then paint the icon in the middle of the track. | |
7767 // If there is a tape, paint the icon below the tape + impreciseIconMargin | |
7768 var icon = evt.getIcon(); | |
7769 icon = icon != null ? icon : metrics.icon; | |
7770 | |
7771 var top; // top of the icon | |
7772 if (tapeHeight > 0) { | |
7773 top = metrics.trackOffset + iconTrack * metrics.trackIncrement + | |
7774 tapeHeight + metrics.impreciseIconMargin; | |
7775 } else { | |
7776 var middle = metrics.trackOffset + iconTrack * metrics.trackIncrement + | |
7777 metrics.trackHeight / 2; | |
7778 top = Math.round(middle - metrics.iconHeight / 2); | |
7779 } | |
7780 var img = SimileAjax.Graphics.createTranslucentImage(icon); | |
7781 var iconDiv = this._timeline.getDocument().createElement("div"); | |
7782 iconDiv.className = this._getElClassName('timeline-event-icon', evt, 'icon'); | |
7783 iconDiv.id = this._encodeEventElID('icon', evt); | |
7784 iconDiv.style.left = left + "px"; | |
7785 iconDiv.style.top = top + "px"; | |
7786 iconDiv.appendChild(img); | |
7787 | |
7788 if(evt._title != null) | |
7789 iconDiv.title = evt._title; | |
7790 | |
7791 this._eventLayer.appendChild(iconDiv); | |
7792 | |
7793 return { | |
7794 left: left, | |
7795 top: top, | |
7796 width: metrics.iconWidth, | |
7797 height: metrics.iconHeight, | |
7798 elmt: iconDiv | |
7799 }; | |
7800 }; | |
7801 | |
7802 Timeline.OriginalEventPainter.prototype._paintEventLabel = function(evt, text, left, top, width, | |
7803 height, theme, labelDivClassName, highlightIndex) { | |
7804 var doc = this._timeline.getDocument(); | |
7805 | |
7806 var labelDiv = doc.createElement("div"); | |
7807 labelDiv.className = labelDivClassName; | |
7808 labelDiv.id = this._encodeEventElID('label', evt); | |
7809 labelDiv.style.left = left + "px"; | |
7810 labelDiv.style.width = width + "px"; | |
7811 labelDiv.style.top = top + "px"; | |
7812 labelDiv.innerHTML = text; | |
7813 | |
7814 if(evt._title != null) | |
7815 labelDiv.title = evt._title; | |
7816 | |
7817 var color = evt.getTextColor(); | |
7818 if (color == null) { | |
7819 color = evt.getColor(); | |
7820 } | |
7821 if (color != null) { | |
7822 labelDiv.style.color = color; | |
7823 } | |
7824 if (theme.event.highlightLabelBackground && highlightIndex >= 0) { | |
7825 labelDiv.style.background = this._getHighlightColor(highlightIndex, theme); | |
7826 } | |
7827 | |
7828 this._eventLayer.appendChild(labelDiv); | |
7829 | |
7830 return { | |
7831 left: left, | |
7832 top: top, | |
7833 width: width, | |
7834 height: height, | |
7835 elmt: labelDiv | |
7836 }; | |
7837 }; | |
7838 | |
7839 Timeline.OriginalEventPainter.prototype._paintEventTape = function( | |
7840 evt, iconTrack, startPixel, endPixel, color, opacity, metrics, theme, tape_index) { | |
7841 | |
7842 var tapeWidth = endPixel - startPixel; | |
7843 var tapeHeight = theme.event.tape.height; | |
7844 var top = metrics.trackOffset + iconTrack * metrics.trackIncrement; | |
7845 | |
7846 var tapeDiv = this._timeline.getDocument().createElement("div"); | |
7847 tapeDiv.className = this._getElClassName('timeline-event-tape', evt, 'tape'); | |
7848 tapeDiv.id = this._encodeEventElID('tape' + tape_index, evt); | |
7849 tapeDiv.style.left = startPixel + "px"; | |
7850 tapeDiv.style.width = tapeWidth + "px"; | |
7851 tapeDiv.style.height = tapeHeight + "px"; | |
7852 tapeDiv.style.top = top + "px"; | |
7853 | |
7854 if(evt._title != null) | |
7855 tapeDiv.title = evt._title; | |
7856 | |
7857 if(color != null) { | |
7858 tapeDiv.style.backgroundColor = color; | |
7859 } | |
7860 | |
7861 var backgroundImage = evt.getTapeImage(); | |
7862 var backgroundRepeat = evt.getTapeRepeat(); | |
7863 backgroundRepeat = backgroundRepeat != null ? backgroundRepeat : 'repeat'; | |
7864 if(backgroundImage != null) { | |
7865 tapeDiv.style.backgroundImage = "url(" + backgroundImage + ")"; | |
7866 tapeDiv.style.backgroundRepeat = backgroundRepeat; | |
7867 } | |
7868 | |
7869 SimileAjax.Graphics.setOpacity(tapeDiv, opacity); | |
7870 | |
7871 this._eventLayer.appendChild(tapeDiv); | |
7872 | |
7873 return { | |
7874 left: startPixel, | |
7875 top: top, | |
7876 width: tapeWidth, | |
7877 height: tapeHeight, | |
7878 elmt: tapeDiv | |
7879 }; | |
7880 } | |
7881 | |
7882 Timeline.OriginalEventPainter.prototype._getLabelDivClassName = function(evt) { | |
7883 return this._getElClassName('timeline-event-label', evt, 'label'); | |
7884 }; | |
7885 | |
7886 Timeline.OriginalEventPainter.prototype._getElClassName = function(elClassName, evt, prefix) { | |
7887 // Prefix and '_' is added to the event's classname. Set to null for no prefix | |
7888 var evt_classname = evt.getClassName(), | |
7889 pieces = []; | |
7890 | |
7891 if (evt_classname) { | |
7892 if (prefix) {pieces.push(prefix + '-' + evt_classname + ' ');} | |
7893 pieces.push(evt_classname + ' '); | |
7894 } | |
7895 pieces.push(elClassName); | |
7896 return(pieces.join('')); | |
7897 }; | |
7898 | |
7899 Timeline.OriginalEventPainter.prototype._getHighlightColor = function(highlightIndex, theme) { | |
7900 var highlightColors = theme.event.highlightColors; | |
7901 return highlightColors[Math.min(highlightIndex, highlightColors.length - 1)]; | |
7902 }; | |
7903 | |
7904 Timeline.OriginalEventPainter.prototype._createHighlightDiv = function(highlightIndex, dimensions, theme, evt) { | |
7905 var div = null; | |
7906 if (highlightIndex >= 0) { | |
7907 var doc = this._timeline.getDocument(); | |
7908 var color = this._getHighlightColor(highlightIndex, theme); | |
7909 | |
7910 div = doc.createElement("div"); | |
7911 div.className = this._getElClassName('timeline-event-highlight', evt, 'highlight'); | |
7912 div.id = this._encodeEventElID('highlight0', evt); // in future will have other | |
7913 // highlight divs for tapes + icons | |
7914 div.style.position = "absolute"; | |
7915 div.style.overflow = "hidden"; | |
7916 div.style.left = (dimensions.left - 2) + "px"; | |
7917 div.style.width = (dimensions.width + 4) + "px"; | |
7918 div.style.top = (dimensions.top - 2) + "px"; | |
7919 div.style.height = (dimensions.height + 4) + "px"; | |
7920 div.style.background = color; | |
7921 | |
7922 this._highlightLayer.appendChild(div); | |
7923 } | |
7924 return div; | |
7925 }; | |
7926 | |
7927 Timeline.OriginalEventPainter.prototype._onClickInstantEvent = function(icon, domEvt, evt) { | |
7928 var c = SimileAjax.DOM.getPageCoordinates(icon); | |
7929 this._showBubble( | |
7930 c.left + Math.ceil(icon.offsetWidth / 2), | |
7931 c.top + Math.ceil(icon.offsetHeight / 2), | |
7932 evt | |
7933 ); | |
7934 this._fireOnSelect(evt.getID()); | |
7935 | |
7936 domEvt.cancelBubble = true; | |
7937 SimileAjax.DOM.cancelEvent(domEvt); | |
7938 return false; | |
7939 }; | |
7940 | |
7941 Timeline.OriginalEventPainter.prototype._onClickDurationEvent = function(target, domEvt, evt) { | |
7942 if ("pageX" in domEvt) { | |
7943 var x = domEvt.pageX; | |
7944 var y = domEvt.pageY; | |
7945 } else { | |
7946 var c = SimileAjax.DOM.getPageCoordinates(target); | |
7947 var x = domEvt.offsetX + c.left; | |
7948 var y = domEvt.offsetY + c.top; | |
7949 } | |
7950 this._showBubble(x, y, evt); | |
7951 this._fireOnSelect(evt.getID()); | |
7952 | |
7953 domEvt.cancelBubble = true; | |
7954 SimileAjax.DOM.cancelEvent(domEvt); | |
7955 return false; | |
7956 }; | |
7957 | |
7958 Timeline.OriginalEventPainter.prototype.showBubble = function(evt) { | |
7959 var elmt = this._eventIdToElmt[evt.getID()]; | |
7960 if (elmt) { | |
7961 var c = SimileAjax.DOM.getPageCoordinates(elmt); | |
7962 this._showBubble(c.left + elmt.offsetWidth / 2, c.top + elmt.offsetHeight / 2, evt); | |
7963 } | |
7964 }; | |
7965 | |
7966 Timeline.OriginalEventPainter.prototype._showBubble = function(x, y, evt) { | |
7967 var div = document.createElement("div"); | |
7968 var themeBubble = this._params.theme.event.bubble; | |
7969 evt.fillInfoBubble(div, this._params.theme, this._band.getLabeller()); | |
7970 | |
7971 SimileAjax.WindowManager.cancelPopups(); | |
7972 SimileAjax.Graphics.createBubbleForContentAndPoint(div, x, y, | |
7973 themeBubble.width, null, themeBubble.maxHeight); | |
7974 }; | |
7975 | |
7976 Timeline.OriginalEventPainter.prototype._fireOnSelect = function(eventID) { | |
7977 for (var i = 0; i < this._onSelectListeners.length; i++) { | |
7978 this._onSelectListeners[i](eventID); | |
7979 } | |
7980 }; | |
7981 | |
7982 Timeline.OriginalEventPainter.prototype._fireEventPaintListeners = function(op, evt, els) { | |
7983 for (var i = 0; i < this._eventPaintListeners.length; i++) { | |
7984 this._eventPaintListeners[i](this._band, op, evt, els); | |
7985 } | |
7986 }; | |
7987 /*================================================== | |
7988 * Detailed Event Painter | |
7989 *================================================== | |
7990 */ | |
7991 | |
7992 // Note: a number of features from original-painter | |
7993 // are not yet implemented in detailed painter. | |
7994 // Eg classname, id attributes for icons, labels, tapes | |
7995 | |
7996 Timeline.DetailedEventPainter = function(params) { | |
7997 this._params = params; | |
7998 this._onSelectListeners = []; | |
7999 | |
8000 this._filterMatcher = null; | |
8001 this._highlightMatcher = null; | |
8002 this._frc = null; | |
8003 | |
8004 this._eventIdToElmt = {}; | |
8005 }; | |
8006 | |
8007 Timeline.DetailedEventPainter.prototype.initialize = function(band, timeline) { | |
8008 this._band = band; | |
8009 this._timeline = timeline; | |
8010 | |
8011 this._backLayer = null; | |
8012 this._eventLayer = null; | |
8013 this._lineLayer = null; | |
8014 this._highlightLayer = null; | |
8015 | |
8016 this._eventIdToElmt = null; | |
8017 }; | |
8018 | |
8019 Timeline.DetailedEventPainter.prototype.getType = function() { | |
8020 return 'detailed'; | |
8021 }; | |
8022 | |
8023 Timeline.DetailedEventPainter.prototype.addOnSelectListener = function(listener) { | |
8024 this._onSelectListeners.push(listener); | |
8025 }; | |
8026 | |
8027 Timeline.DetailedEventPainter.prototype.removeOnSelectListener = function(listener) { | |
8028 for (var i = 0; i < this._onSelectListeners.length; i++) { | |
8029 if (this._onSelectListeners[i] == listener) { | |
8030 this._onSelectListeners.splice(i, 1); | |
8031 break; | |
8032 } | |
8033 } | |
8034 }; | |
8035 | |
8036 Timeline.DetailedEventPainter.prototype.getFilterMatcher = function() { | |
8037 return this._filterMatcher; | |
8038 }; | |
8039 | |
8040 Timeline.DetailedEventPainter.prototype.setFilterMatcher = function(filterMatcher) { | |
8041 this._filterMatcher = filterMatcher; | |
8042 }; | |
8043 | |
8044 Timeline.DetailedEventPainter.prototype.getHighlightMatcher = function() { | |
8045 return this._highlightMatcher; | |
8046 }; | |
8047 | |
8048 Timeline.DetailedEventPainter.prototype.setHighlightMatcher = function(highlightMatcher) { | |
8049 this._highlightMatcher = highlightMatcher; | |
8050 }; | |
8051 | |
8052 Timeline.DetailedEventPainter.prototype.paint = function() { | |
8053 var eventSource = this._band.getEventSource(); | |
8054 if (eventSource == null) { | |
8055 return; | |
8056 } | |
8057 | |
8058 this._eventIdToElmt = {}; | |
8059 this._prepareForPainting(); | |
8060 | |
8061 var eventTheme = this._params.theme.event; | |
8062 var trackHeight = Math.max(eventTheme.track.height, this._frc.getLineHeight()); | |
8063 var metrics = { | |
8064 trackOffset: Math.round(this._band.getViewWidth() / 2 - trackHeight / 2), | |
8065 trackHeight: trackHeight, | |
8066 trackGap: eventTheme.track.gap, | |
8067 trackIncrement: trackHeight + eventTheme.track.gap, | |
8068 icon: eventTheme.instant.icon, | |
8069 iconWidth: eventTheme.instant.iconWidth, | |
8070 iconHeight: eventTheme.instant.iconHeight, | |
8071 labelWidth: eventTheme.label.width | |
8072 } | |
8073 | |
8074 var minDate = this._band.getMinDate(); | |
8075 var maxDate = this._band.getMaxDate(); | |
8076 | |
8077 var filterMatcher = (this._filterMatcher != null) ? | |
8078 this._filterMatcher : | |
8079 function(evt) { return true; }; | |
8080 var highlightMatcher = (this._highlightMatcher != null) ? | |
8081 this._highlightMatcher : | |
8082 function(evt) { return -1; }; | |
8083 | |
8084 var iterator = eventSource.getEventReverseIterator(minDate, maxDate); | |
8085 while (iterator.hasNext()) { | |
8086 var evt = iterator.next(); | |
8087 if (filterMatcher(evt)) { | |
8088 this.paintEvent(evt, metrics, this._params.theme, highlightMatcher(evt)); | |
8089 } | |
8090 } | |
8091 | |
8092 this._highlightLayer.style.display = "block"; | |
8093 this._lineLayer.style.display = "block"; | |
8094 this._eventLayer.style.display = "block"; | |
8095 // update the band object for max number of tracks in this section of the ether | |
8096 this._band.updateEventTrackInfo(this._lowerTracks.length + this._upperTracks.length, | |
8097 metrics.trackIncrement); | |
8098 }; | |
8099 | |
8100 Timeline.DetailedEventPainter.prototype.softPaint = function() { | |
8101 }; | |
8102 | |
8103 Timeline.DetailedEventPainter.prototype._prepareForPainting = function() { | |
8104 var band = this._band; | |
8105 | |
8106 if (this._backLayer == null) { | |
8107 this._backLayer = this._band.createLayerDiv(0, "timeline-band-events"); | |
8108 this._backLayer.style.visibility = "hidden"; | |
8109 | |
8110 var eventLabelPrototype = document.createElement("span"); | |
8111 eventLabelPrototype.className = "timeline-event-label"; | |
8112 this._backLayer.appendChild(eventLabelPrototype); | |
8113 this._frc = SimileAjax.Graphics.getFontRenderingContext(eventLabelPrototype); | |
8114 } | |
8115 this._frc.update(); | |
8116 this._lowerTracks = []; | |
8117 this._upperTracks = []; | |
8118 | |
8119 if (this._highlightLayer != null) { | |
8120 band.removeLayerDiv(this._highlightLayer); | |
8121 } | |
8122 this._highlightLayer = band.createLayerDiv(105, "timeline-band-highlights"); | |
8123 this._highlightLayer.style.display = "none"; | |
8124 | |
8125 if (this._lineLayer != null) { | |
8126 band.removeLayerDiv(this._lineLayer); | |
8127 } | |
8128 this._lineLayer = band.createLayerDiv(110, "timeline-band-lines"); | |
8129 this._lineLayer.style.display = "none"; | |
8130 | |
8131 if (this._eventLayer != null) { | |
8132 band.removeLayerDiv(this._eventLayer); | |
8133 } | |
8134 this._eventLayer = band.createLayerDiv(110, "timeline-band-events"); | |
8135 this._eventLayer.style.display = "none"; | |
8136 }; | |
8137 | |
8138 Timeline.DetailedEventPainter.prototype.paintEvent = function(evt, metrics, theme, highlightIndex) { | |
8139 if (evt.isInstant()) { | |
8140 this.paintInstantEvent(evt, metrics, theme, highlightIndex); | |
8141 } else { | |
8142 this.paintDurationEvent(evt, metrics, theme, highlightIndex); | |
8143 } | |
8144 }; | |
8145 | |
8146 Timeline.DetailedEventPainter.prototype.paintInstantEvent = function(evt, metrics, theme, highlightIndex) { | |
8147 if (evt.isImprecise()) { | |
8148 this.paintImpreciseInstantEvent(evt, metrics, theme, highlightIndex); | |
8149 } else { | |
8150 this.paintPreciseInstantEvent(evt, metrics, theme, highlightIndex); | |
8151 } | |
8152 } | |
8153 | |
8154 Timeline.DetailedEventPainter.prototype.paintDurationEvent = function(evt, metrics, theme, highlightIndex) { | |
8155 if (evt.isImprecise()) { | |
8156 this.paintImpreciseDurationEvent(evt, metrics, theme, highlightIndex); | |
8157 } else { | |
8158 this.paintPreciseDurationEvent(evt, metrics, theme, highlightIndex); | |
8159 } | |
8160 } | |
8161 | |
8162 Timeline.DetailedEventPainter.prototype.paintPreciseInstantEvent = function(evt, metrics, theme, highlightIndex) { | |
8163 var doc = this._timeline.getDocument(); | |
8164 var text = evt.getText(); | |
8165 | |
8166 var startDate = evt.getStart(); | |
8167 var startPixel = Math.round(this._band.dateToPixelOffset(startDate)); | |
8168 var iconRightEdge = Math.round(startPixel + metrics.iconWidth / 2); | |
8169 var iconLeftEdge = Math.round(startPixel - metrics.iconWidth / 2); | |
8170 | |
8171 var labelSize = this._frc.computeSize(text); | |
8172 var iconTrack = this._findFreeTrackForSolid(iconRightEdge, startPixel); | |
8173 var iconElmtData = this._paintEventIcon(evt, iconTrack, iconLeftEdge, metrics, theme); | |
8174 | |
8175 var labelLeft = iconRightEdge + theme.event.label.offsetFromLine; | |
8176 var labelTrack = iconTrack; | |
8177 | |
8178 var iconTrackData = this._getTrackData(iconTrack); | |
8179 if (Math.min(iconTrackData.solid, iconTrackData.text) >= labelLeft + labelSize.width) { // label on the same track, to the right of icon | |
8180 iconTrackData.solid = iconLeftEdge; | |
8181 iconTrackData.text = labelLeft; | |
8182 } else { // label on a different track, below icon | |
8183 iconTrackData.solid = iconLeftEdge; | |
8184 | |
8185 labelLeft = startPixel + theme.event.label.offsetFromLine; | |
8186 labelTrack = this._findFreeTrackForText(iconTrack, labelLeft + labelSize.width, function(t) { t.line = startPixel - 2; }); | |
8187 this._getTrackData(labelTrack).text = iconLeftEdge; | |
8188 | |
8189 this._paintEventLine(evt, startPixel, iconTrack, labelTrack, metrics, theme); | |
8190 } | |
8191 | |
8192 var labelTop = Math.round( | |
8193 metrics.trackOffset + labelTrack * metrics.trackIncrement + | |
8194 metrics.trackHeight / 2 - labelSize.height / 2); | |
8195 | |
8196 var labelElmtData = this._paintEventLabel(evt, text, labelLeft, labelTop, labelSize.width, labelSize.height, theme); | |
8197 | |
8198 var self = this; | |
8199 var clickHandler = function(elmt, domEvt, target) { | |
8200 return self._onClickInstantEvent(iconElmtData.elmt, domEvt, evt); | |
8201 }; | |
8202 SimileAjax.DOM.registerEvent(iconElmtData.elmt, "mousedown", clickHandler); | |
8203 SimileAjax.DOM.registerEvent(labelElmtData.elmt, "mousedown", clickHandler); | |
8204 | |
8205 this._createHighlightDiv(highlightIndex, iconElmtData, theme); | |
8206 | |
8207 this._eventIdToElmt[evt.getID()] = iconElmtData.elmt; | |
8208 }; | |
8209 | |
8210 Timeline.DetailedEventPainter.prototype.paintImpreciseInstantEvent = function(evt, metrics, theme, highlightIndex) { | |
8211 var doc = this._timeline.getDocument(); | |
8212 var text = evt.getText(); | |
8213 | |
8214 var startDate = evt.getStart(); | |
8215 var endDate = evt.getEnd(); | |
8216 var startPixel = Math.round(this._band.dateToPixelOffset(startDate)); | |
8217 var endPixel = Math.round(this._band.dateToPixelOffset(endDate)); | |
8218 | |
8219 var iconRightEdge = Math.round(startPixel + metrics.iconWidth / 2); | |
8220 var iconLeftEdge = Math.round(startPixel - metrics.iconWidth / 2); | |
8221 | |
8222 var labelSize = this._frc.computeSize(text); | |
8223 var iconTrack = this._findFreeTrackForSolid(endPixel, startPixel); | |
8224 | |
8225 var tapeElmtData = this._paintEventTape(evt, iconTrack, startPixel, endPixel, | |
8226 theme.event.instant.impreciseColor, theme.event.instant.impreciseOpacity, metrics, theme); | |
8227 var iconElmtData = this._paintEventIcon(evt, iconTrack, iconLeftEdge, metrics, theme); | |
8228 | |
8229 var iconTrackData = this._getTrackData(iconTrack); | |
8230 iconTrackData.solid = iconLeftEdge; | |
8231 | |
8232 var labelLeft = iconRightEdge + theme.event.label.offsetFromLine; | |
8233 var labelRight = labelLeft + labelSize.width; | |
8234 var labelTrack; | |
8235 if (labelRight < endPixel) { | |
8236 labelTrack = iconTrack; | |
8237 } else { | |
8238 labelLeft = startPixel + theme.event.label.offsetFromLine; | |
8239 labelRight = labelLeft + labelSize.width; | |
8240 | |
8241 labelTrack = this._findFreeTrackForText(iconTrack, labelRight, function(t) { t.line = startPixel - 2; }); | |
8242 this._getTrackData(labelTrack).text = iconLeftEdge; | |
8243 | |
8244 this._paintEventLine(evt, startPixel, iconTrack, labelTrack, metrics, theme); | |
8245 } | |
8246 var labelTop = Math.round( | |
8247 metrics.trackOffset + labelTrack * metrics.trackIncrement + | |
8248 metrics.trackHeight / 2 - labelSize.height / 2); | |
8249 | |
8250 var labelElmtData = this._paintEventLabel(evt, text, labelLeft, labelTop, labelSize.width, labelSize.height, theme); | |
8251 | |
8252 var self = this; | |
8253 var clickHandler = function(elmt, domEvt, target) { | |
8254 return self._onClickInstantEvent(iconElmtData.elmt, domEvt, evt); | |
8255 }; | |
8256 SimileAjax.DOM.registerEvent(iconElmtData.elmt, "mousedown", clickHandler); | |
8257 SimileAjax.DOM.registerEvent(tapeElmtData.elmt, "mousedown", clickHandler); | |
8258 SimileAjax.DOM.registerEvent(labelElmtData.elmt, "mousedown", clickHandler); | |
8259 | |
8260 this._createHighlightDiv(highlightIndex, iconElmtData, theme); | |
8261 | |
8262 this._eventIdToElmt[evt.getID()] = iconElmtData.elmt; | |
8263 }; | |
8264 | |
8265 Timeline.DetailedEventPainter.prototype.paintPreciseDurationEvent = function(evt, metrics, theme, highlightIndex) { | |
8266 var doc = this._timeline.getDocument(); | |
8267 var text = evt.getText(); | |
8268 | |
8269 var startDate = evt.getStart(); | |
8270 var endDate = evt.getEnd(); | |
8271 var startPixel = Math.round(this._band.dateToPixelOffset(startDate)); | |
8272 var endPixel = Math.round(this._band.dateToPixelOffset(endDate)); | |
8273 | |
8274 var labelSize = this._frc.computeSize(text); | |
8275 var tapeTrack = this._findFreeTrackForSolid(endPixel); | |
8276 var color = evt.getColor(); | |
8277 color = color != null ? color : theme.event.duration.color; | |
8278 | |
8279 var tapeElmtData = this._paintEventTape(evt, tapeTrack, startPixel, endPixel, color, 100, metrics, theme); | |
8280 | |
8281 var tapeTrackData = this._getTrackData(tapeTrack); | |
8282 tapeTrackData.solid = startPixel; | |
8283 | |
8284 var labelLeft = startPixel + theme.event.label.offsetFromLine; | |
8285 var labelTrack = this._findFreeTrackForText(tapeTrack, labelLeft + labelSize.width, function(t) { t.line = startPixel - 2; }); | |
8286 this._getTrackData(labelTrack).text = startPixel - 2; | |
8287 | |
8288 this._paintEventLine(evt, startPixel, tapeTrack, labelTrack, metrics, theme); | |
8289 | |
8290 var labelTop = Math.round( | |
8291 metrics.trackOffset + labelTrack * metrics.trackIncrement + | |
8292 metrics.trackHeight / 2 - labelSize.height / 2); | |
8293 | |
8294 var labelElmtData = this._paintEventLabel(evt, text, labelLeft, labelTop, labelSize.width, labelSize.height, theme); | |
8295 | |
8296 var self = this; | |
8297 var clickHandler = function(elmt, domEvt, target) { | |
8298 return self._onClickDurationEvent(tapeElmtData.elmt, domEvt, evt); | |
8299 }; | |
8300 SimileAjax.DOM.registerEvent(tapeElmtData.elmt, "mousedown", clickHandler); | |
8301 SimileAjax.DOM.registerEvent(labelElmtData.elmt, "mousedown", clickHandler); | |
8302 | |
8303 this._createHighlightDiv(highlightIndex, tapeElmtData, theme); | |
8304 | |
8305 this._eventIdToElmt[evt.getID()] = tapeElmtData.elmt; | |
8306 }; | |
8307 | |
8308 Timeline.DetailedEventPainter.prototype.paintImpreciseDurationEvent = function(evt, metrics, theme, highlightIndex) { | |
8309 var doc = this._timeline.getDocument(); | |
8310 var text = evt.getText(); | |
8311 | |
8312 var startDate = evt.getStart(); | |
8313 var latestStartDate = evt.getLatestStart(); | |
8314 var endDate = evt.getEnd(); | |
8315 var earliestEndDate = evt.getEarliestEnd(); | |
8316 | |
8317 var startPixel = Math.round(this._band.dateToPixelOffset(startDate)); | |
8318 var latestStartPixel = Math.round(this._band.dateToPixelOffset(latestStartDate)); | |
8319 var endPixel = Math.round(this._band.dateToPixelOffset(endDate)); | |
8320 var earliestEndPixel = Math.round(this._band.dateToPixelOffset(earliestEndDate)); | |
8321 | |
8322 var labelSize = this._frc.computeSize(text); | |
8323 var tapeTrack = this._findFreeTrackForSolid(endPixel); | |
8324 var color = evt.getColor(); | |
8325 color = color != null ? color : theme.event.duration.color; | |
8326 | |
8327 var impreciseTapeElmtData = this._paintEventTape(evt, tapeTrack, startPixel, endPixel, | |
8328 theme.event.duration.impreciseColor, theme.event.duration.impreciseOpacity, metrics, theme); | |
8329 var tapeElmtData = this._paintEventTape(evt, tapeTrack, latestStartPixel, earliestEndPixel, color, 100, metrics, theme); | |
8330 | |
8331 var tapeTrackData = this._getTrackData(tapeTrack); | |
8332 tapeTrackData.solid = startPixel; | |
8333 | |
8334 var labelLeft = latestStartPixel + theme.event.label.offsetFromLine; | |
8335 var labelTrack = this._findFreeTrackForText(tapeTrack, labelLeft + labelSize.width, function(t) { t.line = latestStartPixel - 2; }); | |
8336 this._getTrackData(labelTrack).text = latestStartPixel - 2; | |
8337 | |
8338 this._paintEventLine(evt, latestStartPixel, tapeTrack, labelTrack, metrics, theme); | |
8339 | |
8340 var labelTop = Math.round( | |
8341 metrics.trackOffset + labelTrack * metrics.trackIncrement + | |
8342 metrics.trackHeight / 2 - labelSize.height / 2); | |
8343 | |
8344 var labelElmtData = this._paintEventLabel(evt, text, labelLeft, labelTop, labelSize.width, labelSize.height, theme); | |
8345 | |
8346 var self = this; | |
8347 var clickHandler = function(elmt, domEvt, target) { | |
8348 return self._onClickDurationEvent(tapeElmtData.elmt, domEvt, evt); | |
8349 }; | |
8350 SimileAjax.DOM.registerEvent(tapeElmtData.elmt, "mousedown", clickHandler); | |
8351 SimileAjax.DOM.registerEvent(labelElmtData.elmt, "mousedown", clickHandler); | |
8352 | |
8353 this._createHighlightDiv(highlightIndex, tapeElmtData, theme); | |
8354 | |
8355 this._eventIdToElmt[evt.getID()] = tapeElmtData.elmt; | |
8356 }; | |
8357 | |
8358 Timeline.DetailedEventPainter.prototype._findFreeTrackForSolid = function(solidEdge, softEdge) { | |
8359 for (var i = 0; true; i++) { | |
8360 if (i < this._lowerTracks.length) { | |
8361 var t = this._lowerTracks[i]; | |
8362 if (Math.min(t.solid, t.text) > solidEdge && (!(softEdge) || t.line > softEdge)) { | |
8363 return i; | |
8364 } | |
8365 } else { | |
8366 this._lowerTracks.push({ | |
8367 solid: Number.POSITIVE_INFINITY, | |
8368 text: Number.POSITIVE_INFINITY, | |
8369 line: Number.POSITIVE_INFINITY | |
8370 }); | |
8371 | |
8372 return i; | |
8373 } | |
8374 | |
8375 if (i < this._upperTracks.length) { | |
8376 var t = this._upperTracks[i]; | |
8377 if (Math.min(t.solid, t.text) > solidEdge && (!(softEdge) || t.line > softEdge)) { | |
8378 return -1 - i; | |
8379 } | |
8380 } else { | |
8381 this._upperTracks.push({ | |
8382 solid: Number.POSITIVE_INFINITY, | |
8383 text: Number.POSITIVE_INFINITY, | |
8384 line: Number.POSITIVE_INFINITY | |
8385 }); | |
8386 | |
8387 return -1 - i; | |
8388 } | |
8389 } | |
8390 }; | |
8391 | |
8392 Timeline.DetailedEventPainter.prototype._findFreeTrackForText = function(fromTrack, edge, occupiedTrackVisitor) { | |
8393 var extendUp; | |
8394 var index; | |
8395 var firstIndex; | |
8396 var result; | |
8397 | |
8398 if (fromTrack < 0) { | |
8399 extendUp = true; | |
8400 firstIndex = -fromTrack; | |
8401 | |
8402 index = this._findFreeUpperTrackForText(firstIndex, edge); | |
8403 result = -1 - index; | |
8404 } else if (fromTrack > 0) { | |
8405 extendUp = false; | |
8406 firstIndex = fromTrack + 1; | |
8407 | |
8408 index = this._findFreeLowerTrackForText(firstIndex, edge); | |
8409 result = index; | |
8410 } else { | |
8411 var upIndex = this._findFreeUpperTrackForText(0, edge); | |
8412 var downIndex = this._findFreeLowerTrackForText(1, edge); | |
8413 | |
8414 if (downIndex - 1 <= upIndex) { | |
8415 extendUp = false; | |
8416 firstIndex = 1; | |
8417 index = downIndex; | |
8418 result = index; | |
8419 } else { | |
8420 extendUp = true; | |
8421 firstIndex = 0; | |
8422 index = upIndex; | |
8423 result = -1 - index; | |
8424 } | |
8425 } | |
8426 | |
8427 if (extendUp) { | |
8428 if (index == this._upperTracks.length) { | |
8429 this._upperTracks.push({ | |
8430 solid: Number.POSITIVE_INFINITY, | |
8431 text: Number.POSITIVE_INFINITY, | |
8432 line: Number.POSITIVE_INFINITY | |
8433 }); | |
8434 } | |
8435 for (var i = firstIndex; i < index; i++) { | |
8436 occupiedTrackVisitor(this._upperTracks[i]); | |
8437 } | |
8438 } else { | |
8439 if (index == this._lowerTracks.length) { | |
8440 this._lowerTracks.push({ | |
8441 solid: Number.POSITIVE_INFINITY, | |
8442 text: Number.POSITIVE_INFINITY, | |
8443 line: Number.POSITIVE_INFINITY | |
8444 }); | |
8445 } | |
8446 for (var i = firstIndex; i < index; i++) { | |
8447 occupiedTrackVisitor(this._lowerTracks[i]); | |
8448 } | |
8449 } | |
8450 return result; | |
8451 }; | |
8452 | |
8453 Timeline.DetailedEventPainter.prototype._findFreeLowerTrackForText = function(index, edge) { | |
8454 for (; index < this._lowerTracks.length; index++) { | |
8455 var t = this._lowerTracks[index]; | |
8456 if (Math.min(t.solid, t.text) >= edge) { | |
8457 break; | |
8458 } | |
8459 } | |
8460 return index; | |
8461 }; | |
8462 | |
8463 Timeline.DetailedEventPainter.prototype._findFreeUpperTrackForText = function(index, edge) { | |
8464 for (; index < this._upperTracks.length; index++) { | |
8465 var t = this._upperTracks[index]; | |
8466 if (Math.min(t.solid, t.text) >= edge) { | |
8467 break; | |
8468 } | |
8469 } | |
8470 return index; | |
8471 }; | |
8472 | |
8473 Timeline.DetailedEventPainter.prototype._getTrackData = function(index) { | |
8474 return (index < 0) ? this._upperTracks[-index - 1] : this._lowerTracks[index]; | |
8475 }; | |
8476 | |
8477 Timeline.DetailedEventPainter.prototype._paintEventLine = function(evt, left, startTrack, endTrack, metrics, theme) { | |
8478 var top = Math.round(metrics.trackOffset + startTrack * metrics.trackIncrement + metrics.trackHeight / 2); | |
8479 var height = Math.round(Math.abs(endTrack - startTrack) * metrics.trackIncrement); | |
8480 | |
8481 var lineStyle = "1px solid " + theme.event.label.lineColor; | |
8482 var lineDiv = this._timeline.getDocument().createElement("div"); | |
8483 lineDiv.style.position = "absolute"; | |
8484 lineDiv.style.left = left + "px"; | |
8485 lineDiv.style.width = theme.event.label.offsetFromLine + "px"; | |
8486 lineDiv.style.height = height + "px"; | |
8487 if (startTrack > endTrack) { | |
8488 lineDiv.style.top = (top - height) + "px"; | |
8489 lineDiv.style.borderTop = lineStyle; | |
8490 } else { | |
8491 lineDiv.style.top = top + "px"; | |
8492 lineDiv.style.borderBottom = lineStyle; | |
8493 } | |
8494 lineDiv.style.borderLeft = lineStyle; | |
8495 this._lineLayer.appendChild(lineDiv); | |
8496 }; | |
8497 | |
8498 Timeline.DetailedEventPainter.prototype._paintEventIcon = function(evt, iconTrack, left, metrics, theme) { | |
8499 var icon = evt.getIcon(); | |
8500 icon = icon != null ? icon : metrics.icon; | |
8501 | |
8502 var middle = metrics.trackOffset + iconTrack * metrics.trackIncrement + metrics.trackHeight / 2; | |
8503 var top = Math.round(middle - metrics.iconHeight / 2); | |
8504 | |
8505 var img = SimileAjax.Graphics.createTranslucentImage(icon); | |
8506 var iconDiv = this._timeline.getDocument().createElement("div"); | |
8507 iconDiv.style.position = "absolute"; | |
8508 iconDiv.style.left = left + "px"; | |
8509 iconDiv.style.top = top + "px"; | |
8510 iconDiv.appendChild(img); | |
8511 iconDiv.style.cursor = "pointer"; | |
8512 | |
8513 if(evt._title != null) | |
8514 iconDiv.title = evt._title | |
8515 | |
8516 this._eventLayer.appendChild(iconDiv); | |
8517 | |
8518 return { | |
8519 left: left, | |
8520 top: top, | |
8521 width: metrics.iconWidth, | |
8522 height: metrics.iconHeight, | |
8523 elmt: iconDiv | |
8524 }; | |
8525 }; | |
8526 | |
8527 Timeline.DetailedEventPainter.prototype._paintEventLabel = function(evt, text, left, top, width, height, theme) { | |
8528 var doc = this._timeline.getDocument(); | |
8529 | |
8530 var labelBackgroundDiv = doc.createElement("div"); | |
8531 labelBackgroundDiv.style.position = "absolute"; | |
8532 labelBackgroundDiv.style.left = left + "px"; | |
8533 labelBackgroundDiv.style.width = width + "px"; | |
8534 labelBackgroundDiv.style.top = top + "px"; | |
8535 labelBackgroundDiv.style.height = height + "px"; | |
8536 labelBackgroundDiv.style.backgroundColor = theme.event.label.backgroundColor; | |
8537 SimileAjax.Graphics.setOpacity(labelBackgroundDiv, theme.event.label.backgroundOpacity); | |
8538 this._eventLayer.appendChild(labelBackgroundDiv); | |
8539 | |
8540 var labelDiv = doc.createElement("div"); | |
8541 labelDiv.style.position = "absolute"; | |
8542 labelDiv.style.left = left + "px"; | |
8543 labelDiv.style.width = width + "px"; | |
8544 labelDiv.style.top = top + "px"; | |
8545 labelDiv.innerHTML = text; | |
8546 labelDiv.style.cursor = "pointer"; | |
8547 | |
8548 if(evt._title != null) | |
8549 labelDiv.title = evt._title; | |
8550 | |
8551 var color = evt.getTextColor(); | |
8552 if (color == null) { | |
8553 color = evt.getColor(); | |
8554 } | |
8555 if (color != null) { | |
8556 labelDiv.style.color = color; | |
8557 } | |
8558 | |
8559 this._eventLayer.appendChild(labelDiv); | |
8560 | |
8561 return { | |
8562 left: left, | |
8563 top: top, | |
8564 width: width, | |
8565 height: height, | |
8566 elmt: labelDiv | |
8567 }; | |
8568 }; | |
8569 | |
8570 Timeline.DetailedEventPainter.prototype._paintEventTape = function( | |
8571 evt, iconTrack, startPixel, endPixel, color, opacity, metrics, theme) { | |
8572 | |
8573 var tapeWidth = endPixel - startPixel; | |
8574 var tapeHeight = theme.event.tape.height; | |
8575 var middle = metrics.trackOffset + iconTrack * metrics.trackIncrement + metrics.trackHeight / 2; | |
8576 var top = Math.round(middle - tapeHeight / 2); | |
8577 | |
8578 var tapeDiv = this._timeline.getDocument().createElement("div"); | |
8579 tapeDiv.style.position = "absolute"; | |
8580 tapeDiv.style.left = startPixel + "px"; | |
8581 tapeDiv.style.width = tapeWidth + "px"; | |
8582 tapeDiv.style.top = top + "px"; | |
8583 tapeDiv.style.height = tapeHeight + "px"; | |
8584 tapeDiv.style.backgroundColor = color; | |
8585 tapeDiv.style.overflow = "hidden"; | |
8586 tapeDiv.style.cursor = "pointer"; | |
8587 | |
8588 if(evt._title != null) | |
8589 tapeDiv.title = evt._title; | |
8590 | |
8591 SimileAjax.Graphics.setOpacity(tapeDiv, opacity); | |
8592 | |
8593 this._eventLayer.appendChild(tapeDiv); | |
8594 | |
8595 return { | |
8596 left: startPixel, | |
8597 top: top, | |
8598 width: tapeWidth, | |
8599 height: tapeHeight, | |
8600 elmt: tapeDiv | |
8601 }; | |
8602 } | |
8603 | |
8604 Timeline.DetailedEventPainter.prototype._createHighlightDiv = function(highlightIndex, dimensions, theme) { | |
8605 if (highlightIndex >= 0) { | |
8606 var doc = this._timeline.getDocument(); | |
8607 var eventTheme = theme.event; | |
8608 | |
8609 var color = eventTheme.highlightColors[Math.min(highlightIndex, eventTheme.highlightColors.length - 1)]; | |
8610 | |
8611 var div = doc.createElement("div"); | |
8612 div.style.position = "absolute"; | |
8613 div.style.overflow = "hidden"; | |
8614 div.style.left = (dimensions.left - 2) + "px"; | |
8615 div.style.width = (dimensions.width + 4) + "px"; | |
8616 div.style.top = (dimensions.top - 2) + "px"; | |
8617 div.style.height = (dimensions.height + 4) + "px"; | |
8618 div.style.background = color; | |
8619 | |
8620 this._highlightLayer.appendChild(div); | |
8621 } | |
8622 }; | |
8623 | |
8624 Timeline.DetailedEventPainter.prototype._onClickInstantEvent = function(icon, domEvt, evt) { | |
8625 var c = SimileAjax.DOM.getPageCoordinates(icon); | |
8626 this._showBubble( | |
8627 c.left + Math.ceil(icon.offsetWidth / 2), | |
8628 c.top + Math.ceil(icon.offsetHeight / 2), | |
8629 evt | |
8630 ); | |
8631 this._fireOnSelect(evt.getID()); | |
8632 | |
8633 domEvt.cancelBubble = true; | |
8634 SimileAjax.DOM.cancelEvent(domEvt); | |
8635 return false; | |
8636 }; | |
8637 | |
8638 Timeline.DetailedEventPainter.prototype._onClickDurationEvent = function(target, domEvt, evt) { | |
8639 if ("pageX" in domEvt) { | |
8640 var x = domEvt.pageX; | |
8641 var y = domEvt.pageY; | |
8642 } else { | |
8643 var c = SimileAjax.DOM.getPageCoordinates(target); | |
8644 var x = domEvt.offsetX + c.left; | |
8645 var y = domEvt.offsetY + c.top; | |
8646 } | |
8647 this._showBubble(x, y, evt); | |
8648 this._fireOnSelect(evt.getID()); | |
8649 | |
8650 domEvt.cancelBubble = true; | |
8651 SimileAjax.DOM.cancelEvent(domEvt); | |
8652 return false; | |
8653 }; | |
8654 | |
8655 Timeline.DetailedEventPainter.prototype.showBubble = function(evt) { | |
8656 var elmt = this._eventIdToElmt[evt.getID()]; | |
8657 if (elmt) { | |
8658 var c = SimileAjax.DOM.getPageCoordinates(elmt); | |
8659 this._showBubble(c.left + elmt.offsetWidth / 2, c.top + elmt.offsetHeight / 2, evt); | |
8660 } | |
8661 }; | |
8662 | |
8663 Timeline.DetailedEventPainter.prototype._showBubble = function(x, y, evt) { | |
8664 var div = document.createElement("div"); | |
8665 var themeBubble = this._params.theme.event.bubble; | |
8666 evt.fillInfoBubble(div, this._params.theme, this._band.getLabeller()); | |
8667 | |
8668 SimileAjax.WindowManager.cancelPopups(); | |
8669 SimileAjax.Graphics.createBubbleForContentAndPoint(div, x, y, | |
8670 themeBubble.width, null, themeBubble.maxHeight); | |
8671 }; | |
8672 | |
8673 Timeline.DetailedEventPainter.prototype._fireOnSelect = function(eventID) { | |
8674 for (var i = 0; i < this._onSelectListeners.length; i++) { | |
8675 this._onSelectListeners[i](eventID); | |
8676 } | |
8677 }; | |
8678 /*================================================== | |
8679 * Overview Event Painter | |
8680 *================================================== | |
8681 */ | |
8682 | |
8683 Timeline.OverviewEventPainter = function(params) { | |
8684 this._params = params; | |
8685 this._onSelectListeners = []; | |
8686 | |
8687 this._filterMatcher = null; | |
8688 this._highlightMatcher = null; | |
8689 }; | |
8690 | |
8691 Timeline.OverviewEventPainter.prototype.initialize = function(band, timeline) { | |
8692 this._band = band; | |
8693 this._timeline = timeline; | |
8694 | |
8695 this._eventLayer = null; | |
8696 this._highlightLayer = null; | |
8697 }; | |
8698 | |
8699 Timeline.OverviewEventPainter.prototype.getType = function() { | |
8700 return 'overview'; | |
8701 }; | |
8702 | |
8703 Timeline.OverviewEventPainter.prototype.addOnSelectListener = function(listener) { | |
8704 this._onSelectListeners.push(listener); | |
8705 }; | |
8706 | |
8707 Timeline.OverviewEventPainter.prototype.removeOnSelectListener = function(listener) { | |
8708 for (var i = 0; i < this._onSelectListeners.length; i++) { | |
8709 if (this._onSelectListeners[i] == listener) { | |
8710 this._onSelectListeners.splice(i, 1); | |
8711 break; | |
8712 } | |
8713 } | |
8714 }; | |
8715 | |
8716 Timeline.OverviewEventPainter.prototype.getFilterMatcher = function() { | |
8717 return this._filterMatcher; | |
8718 }; | |
8719 | |
8720 Timeline.OverviewEventPainter.prototype.setFilterMatcher = function(filterMatcher) { | |
8721 this._filterMatcher = filterMatcher; | |
8722 }; | |
8723 | |
8724 Timeline.OverviewEventPainter.prototype.getHighlightMatcher = function() { | |
8725 return this._highlightMatcher; | |
8726 }; | |
8727 | |
8728 Timeline.OverviewEventPainter.prototype.setHighlightMatcher = function(highlightMatcher) { | |
8729 this._highlightMatcher = highlightMatcher; | |
8730 }; | |
8731 | |
8732 Timeline.OverviewEventPainter.prototype.paint = function() { | |
8733 var eventSource = this._band.getEventSource(); | |
8734 if (eventSource == null) { | |
8735 return; | |
8736 } | |
8737 | |
8738 this._prepareForPainting(); | |
8739 | |
8740 var eventTheme = this._params.theme.event; | |
8741 var metrics = { | |
8742 trackOffset: eventTheme.overviewTrack.offset, | |
8743 trackHeight: eventTheme.overviewTrack.height, | |
8744 trackGap: eventTheme.overviewTrack.gap, | |
8745 trackIncrement: eventTheme.overviewTrack.height + eventTheme.overviewTrack.gap | |
8746 } | |
8747 | |
8748 var minDate = this._band.getMinDate(); | |
8749 var maxDate = this._band.getMaxDate(); | |
8750 | |
8751 var filterMatcher = (this._filterMatcher != null) ? | |
8752 this._filterMatcher : | |
8753 function(evt) { return true; }; | |
8754 var highlightMatcher = (this._highlightMatcher != null) ? | |
8755 this._highlightMatcher : | |
8756 function(evt) { return -1; }; | |
8757 | |
8758 var iterator = eventSource.getEventReverseIterator(minDate, maxDate); | |
8759 while (iterator.hasNext()) { | |
8760 var evt = iterator.next(); | |
8761 if (filterMatcher(evt)) { | |
8762 this.paintEvent(evt, metrics, this._params.theme, highlightMatcher(evt)); | |
8763 } | |
8764 } | |
8765 | |
8766 this._highlightLayer.style.display = "block"; | |
8767 this._eventLayer.style.display = "block"; | |
8768 // update the band object for max number of tracks in this section of the ether | |
8769 this._band.updateEventTrackInfo(this._tracks.length, metrics.trackIncrement); | |
8770 }; | |
8771 | |
8772 Timeline.OverviewEventPainter.prototype.softPaint = function() { | |
8773 }; | |
8774 | |
8775 Timeline.OverviewEventPainter.prototype._prepareForPainting = function() { | |
8776 var band = this._band; | |
8777 | |
8778 this._tracks = []; | |
8779 | |
8780 if (this._highlightLayer != null) { | |
8781 band.removeLayerDiv(this._highlightLayer); | |
8782 } | |
8783 this._highlightLayer = band.createLayerDiv(105, "timeline-band-highlights"); | |
8784 this._highlightLayer.style.display = "none"; | |
8785 | |
8786 if (this._eventLayer != null) { | |
8787 band.removeLayerDiv(this._eventLayer); | |
8788 } | |
8789 this._eventLayer = band.createLayerDiv(110, "timeline-band-events"); | |
8790 this._eventLayer.style.display = "none"; | |
8791 }; | |
8792 | |
8793 Timeline.OverviewEventPainter.prototype.paintEvent = function(evt, metrics, theme, highlightIndex) { | |
8794 if (evt.isInstant()) { | |
8795 this.paintInstantEvent(evt, metrics, theme, highlightIndex); | |
8796 } else { | |
8797 this.paintDurationEvent(evt, metrics, theme, highlightIndex); | |
8798 } | |
8799 }; | |
8800 | |
8801 Timeline.OverviewEventPainter.prototype.paintInstantEvent = function(evt, metrics, theme, highlightIndex) { | |
8802 var startDate = evt.getStart(); | |
8803 var startPixel = Math.round(this._band.dateToPixelOffset(startDate)); | |
8804 | |
8805 var color = evt.getColor(), | |
8806 klassName = evt.getClassName(); | |
8807 if (klassName) { | |
8808 color = null; | |
8809 } else { | |
8810 color = color != null ? color : theme.event.duration.color; | |
8811 } | |
8812 | |
8813 var tickElmtData = this._paintEventTick(evt, startPixel, color, 100, metrics, theme); | |
8814 | |
8815 this._createHighlightDiv(highlightIndex, tickElmtData, theme); | |
8816 }; | |
8817 | |
8818 Timeline.OverviewEventPainter.prototype.paintDurationEvent = function(evt, metrics, theme, highlightIndex) { | |
8819 var latestStartDate = evt.getLatestStart(); | |
8820 var earliestEndDate = evt.getEarliestEnd(); | |
8821 | |
8822 var latestStartPixel = Math.round(this._band.dateToPixelOffset(latestStartDate)); | |
8823 var earliestEndPixel = Math.round(this._band.dateToPixelOffset(earliestEndDate)); | |
8824 | |
8825 var tapeTrack = 0; | |
8826 for (; tapeTrack < this._tracks.length; tapeTrack++) { | |
8827 if (earliestEndPixel < this._tracks[tapeTrack]) { | |
8828 break; | |
8829 } | |
8830 } | |
8831 this._tracks[tapeTrack] = earliestEndPixel; | |
8832 | |
8833 var color = evt.getColor(), | |
8834 klassName = evt.getClassName(); | |
8835 if (klassName) { | |
8836 color = null; | |
8837 } else { | |
8838 color = color != null ? color : theme.event.duration.color; | |
8839 } | |
8840 | |
8841 var tapeElmtData = this._paintEventTape(evt, tapeTrack, latestStartPixel, earliestEndPixel, | |
8842 color, 100, metrics, theme, klassName); | |
8843 | |
8844 this._createHighlightDiv(highlightIndex, tapeElmtData, theme); | |
8845 }; | |
8846 | |
8847 Timeline.OverviewEventPainter.prototype._paintEventTape = function( | |
8848 evt, track, left, right, color, opacity, metrics, theme, klassName) { | |
8849 | |
8850 var top = metrics.trackOffset + track * metrics.trackIncrement; | |
8851 var width = right - left; | |
8852 var height = metrics.trackHeight; | |
8853 | |
8854 var tapeDiv = this._timeline.getDocument().createElement("div"); | |
8855 tapeDiv.className = 'timeline-small-event-tape' | |
8856 if (klassName) {tapeDiv.className += ' small-' + klassName;} | |
8857 tapeDiv.style.left = left + "px"; | |
8858 tapeDiv.style.width = width + "px"; | |
8859 tapeDiv.style.top = top + "px"; | |
8860 tapeDiv.style.height = height + "px"; | |
8861 | |
8862 if (color) { | |
8863 tapeDiv.style.backgroundColor = color; // set color here if defined by event. Else use css | |
8864 } | |
8865 // tapeDiv.style.overflow = "hidden"; // now set in css | |
8866 // tapeDiv.style.position = "absolute"; | |
8867 if(opacity<100) SimileAjax.Graphics.setOpacity(tapeDiv, opacity); | |
8868 | |
8869 this._eventLayer.appendChild(tapeDiv); | |
8870 | |
8871 return { | |
8872 left: left, | |
8873 top: top, | |
8874 width: width, | |
8875 height: height, | |
8876 elmt: tapeDiv | |
8877 }; | |
8878 } | |
8879 | |
8880 Timeline.OverviewEventPainter.prototype._paintEventTick = function( | |
8881 evt, left, color, opacity, metrics, theme) { | |
8882 | |
8883 var height = theme.event.overviewTrack.tickHeight; | |
8884 var top = metrics.trackOffset - height; | |
8885 var width = 1; | |
8886 | |
8887 var tickDiv = this._timeline.getDocument().createElement("div"); | |
8888 tickDiv.className = 'timeline-small-event-icon' | |
8889 tickDiv.style.left = left + "px"; | |
8890 tickDiv.style.top = top + "px"; | |
8891 // tickDiv.style.width = width + "px"; | |
8892 // tickDiv.style.position = "absolute"; | |
8893 // tickDiv.style.height = height + "px"; | |
8894 // tickDiv.style.backgroundColor = color; | |
8895 // tickDiv.style.overflow = "hidden"; | |
8896 | |
8897 var klassName = evt.getClassName() | |
8898 if (klassName) {tickDiv.className +=' small-' + klassName}; | |
8899 | |
8900 if(opacity<100) {SimileAjax.Graphics.setOpacity(tickDiv, opacity)}; | |
8901 | |
8902 this._eventLayer.appendChild(tickDiv); | |
8903 | |
8904 return { | |
8905 left: left, | |
8906 top: top, | |
8907 width: width, | |
8908 height: height, | |
8909 elmt: tickDiv | |
8910 }; | |
8911 } | |
8912 | |
8913 Timeline.OverviewEventPainter.prototype._createHighlightDiv = function(highlightIndex, dimensions, theme) { | |
8914 if (highlightIndex >= 0) { | |
8915 var doc = this._timeline.getDocument(); | |
8916 var eventTheme = theme.event; | |
8917 | |
8918 var color = eventTheme.highlightColors[Math.min(highlightIndex, eventTheme.highlightColors.length - 1)]; | |
8919 | |
8920 var div = doc.createElement("div"); | |
8921 div.style.position = "absolute"; | |
8922 div.style.overflow = "hidden"; | |
8923 div.style.left = (dimensions.left - 1) + "px"; | |
8924 div.style.width = (dimensions.width + 2) + "px"; | |
8925 div.style.top = (dimensions.top - 1) + "px"; | |
8926 div.style.height = (dimensions.height + 2) + "px"; | |
8927 div.style.background = color; | |
8928 | |
8929 this._highlightLayer.appendChild(div); | |
8930 } | |
8931 }; | |
8932 | |
8933 Timeline.OverviewEventPainter.prototype.showBubble = function(evt) { | |
8934 // not implemented | |
8935 }; | |
8936 /*================================================== | |
8937 * Compact Event Painter | |
8938 *================================================== | |
8939 */ | |
8940 | |
8941 Timeline.CompactEventPainter = function(params) { | |
8942 this._params = params; | |
8943 this._onSelectListeners = []; | |
8944 | |
8945 this._filterMatcher = null; | |
8946 this._highlightMatcher = null; | |
8947 this._frc = null; | |
8948 | |
8949 this._eventIdToElmt = {}; | |
8950 }; | |
8951 | |
8952 Timeline.CompactEventPainter.prototype.getType = function() { | |
8953 return 'compact'; | |
8954 }; | |
8955 | |
8956 Timeline.CompactEventPainter.prototype.initialize = function(band, timeline) { | |
8957 this._band = band; | |
8958 this._timeline = timeline; | |
8959 | |
8960 this._backLayer = null; | |
8961 this._eventLayer = null; | |
8962 this._lineLayer = null; | |
8963 this._highlightLayer = null; | |
8964 | |
8965 this._eventIdToElmt = null; | |
8966 }; | |
8967 | |
8968 Timeline.CompactEventPainter.prototype.supportsOrthogonalScrolling = function() { | |
8969 return true; | |
8970 }; | |
8971 | |
8972 Timeline.CompactEventPainter.prototype.addOnSelectListener = function(listener) { | |
8973 this._onSelectListeners.push(listener); | |
8974 }; | |
8975 | |
8976 Timeline.CompactEventPainter.prototype.removeOnSelectListener = function(listener) { | |
8977 for (var i = 0; i < this._onSelectListeners.length; i++) { | |
8978 if (this._onSelectListeners[i] == listener) { | |
8979 this._onSelectListeners.splice(i, 1); | |
8980 break; | |
8981 } | |
8982 } | |
8983 }; | |
8984 | |
8985 Timeline.CompactEventPainter.prototype.getFilterMatcher = function() { | |
8986 return this._filterMatcher; | |
8987 }; | |
8988 | |
8989 Timeline.CompactEventPainter.prototype.setFilterMatcher = function(filterMatcher) { | |
8990 this._filterMatcher = filterMatcher; | |
8991 }; | |
8992 | |
8993 Timeline.CompactEventPainter.prototype.getHighlightMatcher = function() { | |
8994 return this._highlightMatcher; | |
8995 }; | |
8996 | |
8997 Timeline.CompactEventPainter.prototype.setHighlightMatcher = function(highlightMatcher) { | |
8998 this._highlightMatcher = highlightMatcher; | |
8999 }; | |
9000 | |
9001 Timeline.CompactEventPainter.prototype.paint = function() { | |
9002 var eventSource = this._band.getEventSource(); | |
9003 if (eventSource == null) { | |
9004 return; | |
9005 } | |
9006 | |
9007 this._eventIdToElmt = {}; | |
9008 this._prepareForPainting(); | |
9009 | |
9010 var metrics = this._computeMetrics(); | |
9011 var minDate = this._band.getMinDate(); | |
9012 var maxDate = this._band.getMaxDate(); | |
9013 | |
9014 var filterMatcher = (this._filterMatcher != null) ? | |
9015 this._filterMatcher : | |
9016 function(evt) { return true; }; | |
9017 | |
9018 var highlightMatcher = (this._highlightMatcher != null) ? | |
9019 this._highlightMatcher : | |
9020 function(evt) { return -1; }; | |
9021 | |
9022 var iterator = eventSource.getEventIterator(minDate, maxDate); | |
9023 | |
9024 var stackConcurrentPreciseInstantEvents = "stackConcurrentPreciseInstantEvents" in this._params && typeof this._params.stackConcurrentPreciseInstantEvents == "object"; | |
9025 var collapseConcurrentPreciseInstantEvents = "collapseConcurrentPreciseInstantEvents" in this._params && this._params.collapseConcurrentPreciseInstantEvents; | |
9026 if (collapseConcurrentPreciseInstantEvents || stackConcurrentPreciseInstantEvents) { | |
9027 var bufferedEvents = []; | |
9028 var previousInstantEvent = null; | |
9029 | |
9030 while (iterator.hasNext()) { | |
9031 var evt = iterator.next(); | |
9032 if (filterMatcher(evt)) { | |
9033 if (!evt.isInstant() || evt.isImprecise()) { | |
9034 this.paintEvent(evt, metrics, this._params.theme, highlightMatcher(evt)); | |
9035 } else if (previousInstantEvent != null && | |
9036 previousInstantEvent.getStart().getTime() == evt.getStart().getTime()) { | |
9037 bufferedEvents[bufferedEvents.length - 1].push(evt); | |
9038 } else { | |
9039 bufferedEvents.push([ evt ]); | |
9040 previousInstantEvent = evt; | |
9041 } | |
9042 } | |
9043 } | |
9044 | |
9045 for (var i = 0; i < bufferedEvents.length; i++) { | |
9046 var compositeEvents = bufferedEvents[i]; | |
9047 if (compositeEvents.length == 1) { | |
9048 this.paintEvent(compositeEvents[0], metrics, this._params.theme, highlightMatcher(evt)); | |
9049 } else { | |
9050 var match = -1; | |
9051 for (var j = 0; match < 0 && j < compositeEvents.length; j++) { | |
9052 match = highlightMatcher(compositeEvents[j]); | |
9053 } | |
9054 | |
9055 if (stackConcurrentPreciseInstantEvents) { | |
9056 this.paintStackedPreciseInstantEvents(compositeEvents, metrics, this._params.theme, match); | |
9057 } else { | |
9058 this.paintCompositePreciseInstantEvents(compositeEvents, metrics, this._params.theme, match); | |
9059 } | |
9060 } | |
9061 } | |
9062 } else { | |
9063 while (iterator.hasNext()) { | |
9064 var evt = iterator.next(); | |
9065 if (filterMatcher(evt)) { | |
9066 this.paintEvent(evt, metrics, this._params.theme, highlightMatcher(evt)); | |
9067 } | |
9068 } | |
9069 } | |
9070 | |
9071 this._highlightLayer.style.display = "block"; | |
9072 this._lineLayer.style.display = "block"; | |
9073 this._eventLayer.style.display = "block"; | |
9074 | |
9075 this._setOrthogonalOffset(metrics); | |
9076 }; | |
9077 | |
9078 Timeline.CompactEventPainter.prototype.softPaint = function() { | |
9079 this._setOrthogonalOffset(this._computeMetrics()); | |
9080 }; | |
9081 | |
9082 Timeline.CompactEventPainter.prototype.getOrthogonalExtent = function() { | |
9083 var metrics = this._computeMetrics(); | |
9084 return 2 * metrics.trackOffset + this._tracks.length * metrics.trackHeight; | |
9085 }; | |
9086 | |
9087 Timeline.CompactEventPainter.prototype._setOrthogonalOffset = function(metrics) { | |
9088 var orthogonalOffset = this._band.getViewOrthogonalOffset(); | |
9089 | |
9090 this._highlightLayer.style.top = | |
9091 this._lineLayer.style.top = | |
9092 this._eventLayer.style.top = | |
9093 orthogonalOffset + "px"; | |
9094 }; | |
9095 | |
9096 Timeline.CompactEventPainter.prototype._computeMetrics = function() { | |
9097 var theme = this._params.theme; | |
9098 var eventTheme = theme.event; | |
9099 | |
9100 var metrics = { | |
9101 trackOffset: "trackOffset" in this._params ? this._params.trackOffset : 10, | |
9102 trackHeight: "trackHeight" in this._params ? this._params.trackHeight : 10, | |
9103 | |
9104 tapeHeight: theme.event.tape.height, | |
9105 tapeBottomMargin: "tapeBottomMargin" in this._params ? this._params.tapeBottomMargin : 2, | |
9106 | |
9107 labelBottomMargin: "labelBottomMargin" in this._params ? this._params.labelBottomMargin : 5, | |
9108 labelRightMargin: "labelRightMargin" in this._params ? this._params.labelRightMargin : 5, | |
9109 | |
9110 defaultIcon: eventTheme.instant.icon, | |
9111 defaultIconWidth: eventTheme.instant.iconWidth, | |
9112 defaultIconHeight: eventTheme.instant.iconHeight, | |
9113 | |
9114 customIconWidth: "iconWidth" in this._params ? this._params.iconWidth : eventTheme.instant.iconWidth, | |
9115 customIconHeight: "iconHeight" in this._params ? this._params.iconHeight : eventTheme.instant.iconHeight, | |
9116 | |
9117 iconLabelGap: "iconLabelGap" in this._params ? this._params.iconLabelGap : 2, | |
9118 iconBottomMargin: "iconBottomMargin" in this._params ? this._params.iconBottomMargin : 2 | |
9119 }; | |
9120 if ("compositeIcon" in this._params) { | |
9121 metrics.compositeIcon = this._params.compositeIcon; | |
9122 metrics.compositeIconWidth = this._params.compositeIconWidth || metrics.customIconWidth; | |
9123 metrics.compositeIconHeight = this._params.compositeIconHeight || metrics.customIconHeight; | |
9124 } else { | |
9125 metrics.compositeIcon = metrics.defaultIcon; | |
9126 metrics.compositeIconWidth = metrics.defaultIconWidth; | |
9127 metrics.compositeIconHeight = metrics.defaultIconHeight; | |
9128 } | |
9129 metrics.defaultStackIcon = "icon" in this._params.stackConcurrentPreciseInstantEvents ? | |
9130 this._params.stackConcurrentPreciseInstantEvents.icon : metrics.defaultIcon; | |
9131 metrics.defaultStackIconWidth = "iconWidth" in this._params.stackConcurrentPreciseInstantEvents ? | |
9132 this._params.stackConcurrentPreciseInstantEvents.iconWidth : metrics.defaultIconWidth; | |
9133 metrics.defaultStackIconHeight = "iconHeight" in this._params.stackConcurrentPreciseInstantEvents ? | |
9134 this._params.stackConcurrentPreciseInstantEvents.iconHeight : metrics.defaultIconHeight; | |
9135 | |
9136 return metrics; | |
9137 }; | |
9138 | |
9139 Timeline.CompactEventPainter.prototype._prepareForPainting = function() { | |
9140 var band = this._band; | |
9141 | |
9142 if (this._backLayer == null) { | |
9143 this._backLayer = this._band.createLayerDiv(0, "timeline-band-events"); | |
9144 this._backLayer.style.visibility = "hidden"; | |
9145 | |
9146 var eventLabelPrototype = document.createElement("span"); | |
9147 eventLabelPrototype.className = "timeline-event-label"; | |
9148 this._backLayer.appendChild(eventLabelPrototype); | |
9149 this._frc = SimileAjax.Graphics.getFontRenderingContext(eventLabelPrototype); | |
9150 } | |
9151 this._frc.update(); | |
9152 this._tracks = []; | |
9153 | |
9154 if (this._highlightLayer != null) { | |
9155 band.removeLayerDiv(this._highlightLayer); | |
9156 } | |
9157 this._highlightLayer = band.createLayerDiv(105, "timeline-band-highlights"); | |
9158 this._highlightLayer.style.display = "none"; | |
9159 | |
9160 if (this._lineLayer != null) { | |
9161 band.removeLayerDiv(this._lineLayer); | |
9162 } | |
9163 this._lineLayer = band.createLayerDiv(110, "timeline-band-lines"); | |
9164 this._lineLayer.style.display = "none"; | |
9165 | |
9166 if (this._eventLayer != null) { | |
9167 band.removeLayerDiv(this._eventLayer); | |
9168 } | |
9169 this._eventLayer = band.createLayerDiv(115, "timeline-band-events"); | |
9170 this._eventLayer.style.display = "none"; | |
9171 }; | |
9172 | |
9173 Timeline.CompactEventPainter.prototype.paintEvent = function(evt, metrics, theme, highlightIndex) { | |
9174 if (evt.isInstant()) { | |
9175 this.paintInstantEvent(evt, metrics, theme, highlightIndex); | |
9176 } else { | |
9177 this.paintDurationEvent(evt, metrics, theme, highlightIndex); | |
9178 } | |
9179 }; | |
9180 | |
9181 Timeline.CompactEventPainter.prototype.paintInstantEvent = function(evt, metrics, theme, highlightIndex) { | |
9182 if (evt.isImprecise()) { | |
9183 this.paintImpreciseInstantEvent(evt, metrics, theme, highlightIndex); | |
9184 } else { | |
9185 this.paintPreciseInstantEvent(evt, metrics, theme, highlightIndex); | |
9186 } | |
9187 } | |
9188 | |
9189 Timeline.CompactEventPainter.prototype.paintDurationEvent = function(evt, metrics, theme, highlightIndex) { | |
9190 if (evt.isImprecise()) { | |
9191 this.paintImpreciseDurationEvent(evt, metrics, theme, highlightIndex); | |
9192 } else { | |
9193 this.paintPreciseDurationEvent(evt, metrics, theme, highlightIndex); | |
9194 } | |
9195 } | |
9196 | |
9197 Timeline.CompactEventPainter.prototype.paintPreciseInstantEvent = function(evt, metrics, theme, highlightIndex) { | |
9198 var commonData = { | |
9199 tooltip: evt.getProperty("tooltip") || evt.getText() | |
9200 }; | |
9201 | |
9202 var iconData = { | |
9203 url: evt.getIcon() | |
9204 }; | |
9205 if (iconData.url == null) { | |
9206 iconData.url = metrics.defaultIcon; | |
9207 iconData.width = metrics.defaultIconWidth; | |
9208 iconData.height = metrics.defaultIconHeight; | |
9209 iconData.className = "timeline-event-icon-default"; | |
9210 } else { | |
9211 iconData.width = evt.getProperty("iconWidth") || metrics.customIconWidth; | |
9212 iconData.height = evt.getProperty("iconHeight") || metrics.customIconHeight; | |
9213 } | |
9214 | |
9215 var labelData = { | |
9216 text: evt.getText(), | |
9217 color: evt.getTextColor() || evt.getColor(), | |
9218 className: evt.getClassName() | |
9219 }; | |
9220 | |
9221 var result = this.paintTapeIconLabel( | |
9222 evt.getStart(), | |
9223 commonData, | |
9224 null, // no tape data | |
9225 iconData, | |
9226 labelData, | |
9227 metrics, | |
9228 theme, | |
9229 highlightIndex | |
9230 ); | |
9231 | |
9232 var self = this; | |
9233 var clickHandler = function(elmt, domEvt, target) { | |
9234 return self._onClickInstantEvent(result.iconElmtData.elmt, domEvt, evt); | |
9235 }; | |
9236 SimileAjax.DOM.registerEvent(result.iconElmtData.elmt, "mousedown", clickHandler); | |
9237 SimileAjax.DOM.registerEvent(result.labelElmtData.elmt, "mousedown", clickHandler); | |
9238 | |
9239 this._eventIdToElmt[evt.getID()] = result.iconElmtData.elmt; | |
9240 }; | |
9241 | |
9242 Timeline.CompactEventPainter.prototype.paintCompositePreciseInstantEvents = function(events, metrics, theme, highlightIndex) { | |
9243 var evt = events[0]; | |
9244 | |
9245 var tooltips = []; | |
9246 for (var i = 0; i < events.length; i++) { | |
9247 tooltips.push(events[i].getProperty("tooltip") || events[i].getText()); | |
9248 } | |
9249 var commonData = { | |
9250 tooltip: tooltips.join("; ") | |
9251 }; | |
9252 | |
9253 var iconData = { | |
9254 url: metrics.compositeIcon, | |
9255 width: metrics.compositeIconWidth, | |
9256 height: metrics.compositeIconHeight, | |
9257 className: "timeline-event-icon-composite" | |
9258 }; | |
9259 | |
9260 var labelData = { | |
9261 text: String.substitute(this._params.compositeEventLabelTemplate, [ events.length ]) | |
9262 }; | |
9263 | |
9264 var result = this.paintTapeIconLabel( | |
9265 evt.getStart(), | |
9266 commonData, | |
9267 null, // no tape data | |
9268 iconData, | |
9269 labelData, | |
9270 metrics, | |
9271 theme, | |
9272 highlightIndex | |
9273 ); | |
9274 | |
9275 var self = this; | |
9276 var clickHandler = function(elmt, domEvt, target) { | |
9277 return self._onClickMultiplePreciseInstantEvent(result.iconElmtData.elmt, domEvt, events); | |
9278 }; | |
9279 | |
9280 SimileAjax.DOM.registerEvent(result.iconElmtData.elmt, "mousedown", clickHandler); | |
9281 SimileAjax.DOM.registerEvent(result.labelElmtData.elmt, "mousedown", clickHandler); | |
9282 | |
9283 for (var i = 0; i < events.length; i++) { | |
9284 this._eventIdToElmt[events[i].getID()] = result.iconElmtData.elmt; | |
9285 } | |
9286 }; | |
9287 | |
9288 Timeline.CompactEventPainter.prototype.paintStackedPreciseInstantEvents = function(events, metrics, theme, highlightIndex) { | |
9289 var limit = "limit" in this._params.stackConcurrentPreciseInstantEvents ? | |
9290 this._params.stackConcurrentPreciseInstantEvents.limit : 10; | |
9291 var moreMessageTemplate = "moreMessageTemplate" in this._params.stackConcurrentPreciseInstantEvents ? | |
9292 this._params.stackConcurrentPreciseInstantEvents.moreMessageTemplate : "%0 More Events"; | |
9293 var showMoreMessage = limit <= events.length - 2; // We want at least 2 more events above the limit. | |
9294 // Otherwise we'd need the singular case of "1 More Event" | |
9295 | |
9296 var band = this._band; | |
9297 var getPixelOffset = function(date) { | |
9298 return Math.round(band.dateToPixelOffset(date)); | |
9299 }; | |
9300 var getIconData = function(evt) { | |
9301 var iconData = { | |
9302 url: evt.getIcon() | |
9303 }; | |
9304 if (iconData.url == null) { | |
9305 iconData.url = metrics.defaultStackIcon; | |
9306 iconData.width = metrics.defaultStackIconWidth; | |
9307 iconData.height = metrics.defaultStackIconHeight; | |
9308 iconData.className = "timeline-event-icon-stack timeline-event-icon-default"; | |
9309 } else { | |
9310 iconData.width = evt.getProperty("iconWidth") || metrics.customIconWidth; | |
9311 iconData.height = evt.getProperty("iconHeight") || metrics.customIconHeight; | |
9312 iconData.className = "timeline-event-icon-stack"; | |
9313 } | |
9314 return iconData; | |
9315 }; | |
9316 | |
9317 var firstIconData = getIconData(events[0]); | |
9318 var horizontalIncrement = 5; | |
9319 var leftIconEdge = 0; | |
9320 var totalLabelWidth = 0; | |
9321 var totalLabelHeight = 0; | |
9322 var totalIconHeight = 0; | |
9323 | |
9324 var records = []; | |
9325 for (var i = 0; i < events.length && (!showMoreMessage || i < limit); i++) { | |
9326 var evt = events[i]; | |
9327 var text = evt.getText(); | |
9328 var iconData = getIconData(evt); | |
9329 var labelSize = this._frc.computeSize(text); | |
9330 var record = { | |
9331 text: text, | |
9332 iconData: iconData, | |
9333 labelSize: labelSize, | |
9334 iconLeft: firstIconData.width + i * horizontalIncrement - iconData.width | |
9335 }; | |
9336 record.labelLeft = firstIconData.width + i * horizontalIncrement + metrics.iconLabelGap; | |
9337 record.top = totalLabelHeight; | |
9338 records.push(record); | |
9339 | |
9340 leftIconEdge = Math.min(leftIconEdge, record.iconLeft); | |
9341 totalLabelHeight += labelSize.height; | |
9342 totalLabelWidth = Math.max(totalLabelWidth, record.labelLeft + labelSize.width); | |
9343 totalIconHeight = Math.max(totalIconHeight, record.top + iconData.height); | |
9344 } | |
9345 if (showMoreMessage) { | |
9346 var moreMessage = String.substitute(moreMessageTemplate, [ events.length - limit ]); | |
9347 | |
9348 var moreMessageLabelSize = this._frc.computeSize(moreMessage); | |
9349 var moreMessageLabelLeft = firstIconData.width + (limit - 1) * horizontalIncrement + metrics.iconLabelGap; | |
9350 var moreMessageLabelTop = totalLabelHeight; | |
9351 | |
9352 totalLabelHeight += moreMessageLabelSize.height; | |
9353 totalLabelWidth = Math.max(totalLabelWidth, moreMessageLabelLeft + moreMessageLabelSize.width); | |
9354 } | |
9355 totalLabelWidth += metrics.labelRightMargin; | |
9356 totalLabelHeight += metrics.labelBottomMargin; | |
9357 totalIconHeight += metrics.iconBottomMargin; | |
9358 | |
9359 var anchorPixel = getPixelOffset(events[0].getStart()); | |
9360 var newTracks = []; | |
9361 | |
9362 var trackCount = Math.ceil(Math.max(totalIconHeight, totalLabelHeight) / metrics.trackHeight); | |
9363 var rightIconEdge = firstIconData.width + (events.length - 1) * horizontalIncrement; | |
9364 for (var i = 0; i < trackCount; i++) { | |
9365 newTracks.push({ start: leftIconEdge, end: rightIconEdge }); | |
9366 } | |
9367 var labelTrackCount = Math.ceil(totalLabelHeight / metrics.trackHeight); | |
9368 for (var i = 0; i < labelTrackCount; i++) { | |
9369 var track = newTracks[i]; | |
9370 track.end = Math.max(track.end, totalLabelWidth); | |
9371 } | |
9372 | |
9373 var firstTrack = this._fitTracks(anchorPixel, newTracks); | |
9374 var verticalPixelOffset = firstTrack * metrics.trackHeight + metrics.trackOffset; | |
9375 | |
9376 var iconStackDiv = this._timeline.getDocument().createElement("div"); | |
9377 iconStackDiv.className = 'timeline-event-icon-stack'; | |
9378 iconStackDiv.style.position = "absolute"; | |
9379 iconStackDiv.style.overflow = "visible"; | |
9380 iconStackDiv.style.left = anchorPixel + "px"; | |
9381 iconStackDiv.style.top = verticalPixelOffset + "px"; | |
9382 iconStackDiv.style.width = rightIconEdge + "px"; | |
9383 iconStackDiv.style.height = totalIconHeight + "px"; | |
9384 iconStackDiv.innerHTML = "<div style='position: relative'></div>"; | |
9385 this._eventLayer.appendChild(iconStackDiv); | |
9386 | |
9387 var self = this; | |
9388 var onMouseOver = function(domEvt) { | |
9389 try { | |
9390 var n = parseInt(this.getAttribute("index")); | |
9391 var childNodes = iconStackDiv.firstChild.childNodes; | |
9392 for (var i = 0; i < childNodes.length; i++) { | |
9393 var child = childNodes[i]; | |
9394 if (i == n) { | |
9395 child.style.zIndex = childNodes.length; | |
9396 } else { | |
9397 child.style.zIndex = childNodes.length - i; | |
9398 } | |
9399 } | |
9400 } catch (e) { | |
9401 } | |
9402 }; | |
9403 var paintEvent = function(index) { | |
9404 var record = records[index]; | |
9405 var evt = events[index]; | |
9406 var tooltip = evt.getProperty("tooltip") || evt.getText(); | |
9407 | |
9408 var labelElmtData = self._paintEventLabel( | |
9409 { tooltip: tooltip }, | |
9410 { text: record.text }, | |
9411 anchorPixel + record.labelLeft, | |
9412 verticalPixelOffset + record.top, | |
9413 record.labelSize.width, | |
9414 record.labelSize.height, | |
9415 theme | |
9416 ); | |
9417 labelElmtData.elmt.setAttribute("index", index); | |
9418 labelElmtData.elmt.onmouseover = onMouseOver; | |
9419 | |
9420 var img = SimileAjax.Graphics.createTranslucentImage(record.iconData.url); | |
9421 var iconDiv = self._timeline.getDocument().createElement("div"); | |
9422 iconDiv.className = 'timeline-event-icon' + ("className" in record.iconData ? (" " + record.iconData.className) : ""); | |
9423 iconDiv.style.left = record.iconLeft + "px"; | |
9424 iconDiv.style.top = record.top + "px"; | |
9425 iconDiv.style.zIndex = (records.length - index); | |
9426 iconDiv.appendChild(img); | |
9427 iconDiv.setAttribute("index", index); | |
9428 iconDiv.onmouseover = onMouseOver; | |
9429 | |
9430 iconStackDiv.firstChild.appendChild(iconDiv); | |
9431 | |
9432 var clickHandler = function(elmt, domEvt, target) { | |
9433 return self._onClickInstantEvent(labelElmtData.elmt, domEvt, evt); | |
9434 }; | |
9435 | |
9436 SimileAjax.DOM.registerEvent(iconDiv, "mousedown", clickHandler); | |
9437 SimileAjax.DOM.registerEvent(labelElmtData.elmt, "mousedown", clickHandler); | |
9438 | |
9439 self._eventIdToElmt[evt.getID()] = iconDiv; | |
9440 }; | |
9441 for (var i = 0; i < records.length; i++) { | |
9442 paintEvent(i); | |
9443 } | |
9444 | |
9445 if (showMoreMessage) { | |
9446 var moreEvents = events.slice(limit); | |
9447 var moreMessageLabelElmtData = this._paintEventLabel( | |
9448 { tooltip: moreMessage }, | |
9449 { text: moreMessage }, | |
9450 anchorPixel + moreMessageLabelLeft, | |
9451 verticalPixelOffset + moreMessageLabelTop, | |
9452 moreMessageLabelSize.width, | |
9453 moreMessageLabelSize.height, | |
9454 theme | |
9455 ); | |
9456 | |
9457 var moreMessageClickHandler = function(elmt, domEvt, target) { | |
9458 return self._onClickMultiplePreciseInstantEvent(moreMessageLabelElmtData.elmt, domEvt, moreEvents); | |
9459 }; | |
9460 SimileAjax.DOM.registerEvent(moreMessageLabelElmtData.elmt, "mousedown", moreMessageClickHandler); | |
9461 | |
9462 for (var i = 0; i < moreEvents.length; i++) { | |
9463 this._eventIdToElmt[moreEvents[i].getID()] = moreMessageLabelElmtData.elmt; | |
9464 } | |
9465 } | |
9466 //this._createHighlightDiv(highlightIndex, iconElmtData, theme); | |
9467 }; | |
9468 | |
9469 Timeline.CompactEventPainter.prototype.paintImpreciseInstantEvent = function(evt, metrics, theme, highlightIndex) { | |
9470 var commonData = { | |
9471 tooltip: evt.getProperty("tooltip") || evt.getText() | |
9472 }; | |
9473 | |
9474 var tapeData = { | |
9475 start: evt.getStart(), | |
9476 end: evt.getEnd(), | |
9477 latestStart: evt.getLatestStart(), | |
9478 earliestEnd: evt.getEarliestEnd(), | |
9479 isInstant: true | |
9480 }; | |
9481 | |
9482 var iconData = { | |
9483 url: evt.getIcon() | |
9484 }; | |
9485 if (iconData.url == null) { | |
9486 iconData = null; | |
9487 } else { | |
9488 iconData.width = evt.getProperty("iconWidth") || metrics.customIconWidth; | |
9489 iconData.height = evt.getProperty("iconHeight") || metrics.customIconHeight; | |
9490 } | |
9491 | |
9492 var labelData = { | |
9493 text: evt.getText(), | |
9494 color: evt.getTextColor() || evt.getColor(), | |
9495 className: evt.getClassName() | |
9496 }; | |
9497 | |
9498 var result = this.paintTapeIconLabel( | |
9499 evt.getStart(), | |
9500 commonData, | |
9501 tapeData, // no tape data | |
9502 iconData, | |
9503 labelData, | |
9504 metrics, | |
9505 theme, | |
9506 highlightIndex | |
9507 ); | |
9508 | |
9509 var self = this; | |
9510 var clickHandler = iconData != null ? | |
9511 function(elmt, domEvt, target) { | |
9512 return self._onClickInstantEvent(result.iconElmtData.elmt, domEvt, evt); | |
9513 } : | |
9514 function(elmt, domEvt, target) { | |
9515 return self._onClickInstantEvent(result.labelElmtData.elmt, domEvt, evt); | |
9516 }; | |
9517 | |
9518 SimileAjax.DOM.registerEvent(result.labelElmtData.elmt, "mousedown", clickHandler); | |
9519 SimileAjax.DOM.registerEvent(result.impreciseTapeElmtData.elmt, "mousedown", clickHandler); | |
9520 | |
9521 if (iconData != null) { | |
9522 SimileAjax.DOM.registerEvent(result.iconElmtData.elmt, "mousedown", clickHandler); | |
9523 this._eventIdToElmt[evt.getID()] = result.iconElmtData.elmt; | |
9524 } else { | |
9525 this._eventIdToElmt[evt.getID()] = result.labelElmtData.elmt; | |
9526 } | |
9527 }; | |
9528 | |
9529 Timeline.CompactEventPainter.prototype.paintPreciseDurationEvent = function(evt, metrics, theme, highlightIndex) { | |
9530 var commonData = { | |
9531 tooltip: evt.getProperty("tooltip") || evt.getText() | |
9532 }; | |
9533 | |
9534 var tapeData = { | |
9535 start: evt.getStart(), | |
9536 end: evt.getEnd(), | |
9537 isInstant: false | |
9538 }; | |
9539 | |
9540 var iconData = { | |
9541 url: evt.getIcon() | |
9542 }; | |
9543 if (iconData.url == null) { | |
9544 iconData = null; | |
9545 } else { | |
9546 iconData.width = evt.getProperty("iconWidth") || metrics.customIconWidth; | |
9547 iconData.height = evt.getProperty("iconHeight") || metrics.customIconHeight; | |
9548 } | |
9549 | |
9550 var labelData = { | |
9551 text: evt.getText(), | |
9552 color: evt.getTextColor() || evt.getColor(), | |
9553 className: evt.getClassName() | |
9554 }; | |
9555 | |
9556 var result = this.paintTapeIconLabel( | |
9557 evt.getLatestStart(), | |
9558 commonData, | |
9559 tapeData, // no tape data | |
9560 iconData, | |
9561 labelData, | |
9562 metrics, | |
9563 theme, | |
9564 highlightIndex | |
9565 ); | |
9566 | |
9567 var self = this; | |
9568 var clickHandler = iconData != null ? | |
9569 function(elmt, domEvt, target) { | |
9570 return self._onClickInstantEvent(result.iconElmtData.elmt, domEvt, evt); | |
9571 } : | |
9572 function(elmt, domEvt, target) { | |
9573 return self._onClickInstantEvent(result.labelElmtData.elmt, domEvt, evt); | |
9574 }; | |
9575 | |
9576 SimileAjax.DOM.registerEvent(result.labelElmtData.elmt, "mousedown", clickHandler); | |
9577 SimileAjax.DOM.registerEvent(result.tapeElmtData.elmt, "mousedown", clickHandler); | |
9578 | |
9579 if (iconData != null) { | |
9580 SimileAjax.DOM.registerEvent(result.iconElmtData.elmt, "mousedown", clickHandler); | |
9581 this._eventIdToElmt[evt.getID()] = result.iconElmtData.elmt; | |
9582 } else { | |
9583 this._eventIdToElmt[evt.getID()] = result.labelElmtData.elmt; | |
9584 } | |
9585 }; | |
9586 | |
9587 Timeline.CompactEventPainter.prototype.paintImpreciseDurationEvent = function(evt, metrics, theme, highlightIndex) { | |
9588 var commonData = { | |
9589 tooltip: evt.getProperty("tooltip") || evt.getText() | |
9590 }; | |
9591 | |
9592 var tapeData = { | |
9593 start: evt.getStart(), | |
9594 end: evt.getEnd(), | |
9595 latestStart: evt.getLatestStart(), | |
9596 earliestEnd: evt.getEarliestEnd(), | |
9597 isInstant: false | |
9598 }; | |
9599 | |
9600 var iconData = { | |
9601 url: evt.getIcon() | |
9602 }; | |
9603 if (iconData.url == null) { | |
9604 iconData = null; | |
9605 } else { | |
9606 iconData.width = evt.getProperty("iconWidth") || metrics.customIconWidth; | |
9607 iconData.height = evt.getProperty("iconHeight") || metrics.customIconHeight; | |
9608 } | |
9609 | |
9610 var labelData = { | |
9611 text: evt.getText(), | |
9612 color: evt.getTextColor() || evt.getColor(), | |
9613 className: evt.getClassName() | |
9614 }; | |
9615 | |
9616 var result = this.paintTapeIconLabel( | |
9617 evt.getLatestStart(), | |
9618 commonData, | |
9619 tapeData, // no tape data | |
9620 iconData, | |
9621 labelData, | |
9622 metrics, | |
9623 theme, | |
9624 highlightIndex | |
9625 ); | |
9626 | |
9627 var self = this; | |
9628 var clickHandler = iconData != null ? | |
9629 function(elmt, domEvt, target) { | |
9630 return self._onClickInstantEvent(result.iconElmtData.elmt, domEvt, evt); | |
9631 } : | |
9632 function(elmt, domEvt, target) { | |
9633 return self._onClickInstantEvent(result.labelElmtData.elmt, domEvt, evt); | |
9634 }; | |
9635 | |
9636 SimileAjax.DOM.registerEvent(result.labelElmtData.elmt, "mousedown", clickHandler); | |
9637 SimileAjax.DOM.registerEvent(result.tapeElmtData.elmt, "mousedown", clickHandler); | |
9638 | |
9639 if (iconData != null) { | |
9640 SimileAjax.DOM.registerEvent(result.iconElmtData.elmt, "mousedown", clickHandler); | |
9641 this._eventIdToElmt[evt.getID()] = result.iconElmtData.elmt; | |
9642 } else { | |
9643 this._eventIdToElmt[evt.getID()] = result.labelElmtData.elmt; | |
9644 } | |
9645 }; | |
9646 | |
9647 Timeline.CompactEventPainter.prototype.paintTapeIconLabel = function( | |
9648 anchorDate, | |
9649 commonData, | |
9650 tapeData, | |
9651 iconData, | |
9652 labelData, | |
9653 metrics, | |
9654 theme, | |
9655 highlightIndex | |
9656 ) { | |
9657 var band = this._band; | |
9658 var getPixelOffset = function(date) { | |
9659 return Math.round(band.dateToPixelOffset(date)); | |
9660 }; | |
9661 | |
9662 var anchorPixel = getPixelOffset(anchorDate); | |
9663 var newTracks = []; | |
9664 | |
9665 var tapeHeightOccupied = 0; // how many pixels (vertically) the tape occupies, including bottom margin | |
9666 var tapeTrackCount = 0; // how many tracks the tape takes up, usually just 1 | |
9667 var tapeLastTrackExtraSpace = 0; // on the last track that the tape occupies, how many pixels are left (for icon and label to occupy as well) | |
9668 if (tapeData != null) { | |
9669 tapeHeightOccupied = metrics.tapeHeight + metrics.tapeBottomMargin; | |
9670 tapeTrackCount = Math.ceil(metrics.tapeHeight / metrics.trackHeight); | |
9671 | |
9672 var tapeEndPixelOffset = getPixelOffset(tapeData.end) - anchorPixel; | |
9673 var tapeStartPixelOffset = getPixelOffset(tapeData.start) - anchorPixel; | |
9674 | |
9675 for (var t = 0; t < tapeTrackCount; t++) { | |
9676 newTracks.push({ start: tapeStartPixelOffset, end: tapeEndPixelOffset }); | |
9677 } | |
9678 | |
9679 tapeLastTrackExtraSpace = metrics.trackHeight - (tapeHeightOccupied % metrics.tapeHeight); | |
9680 } | |
9681 | |
9682 var iconStartPixelOffset = 0; // where the icon starts compared to the anchor pixel; | |
9683 // this can be negative if the icon is center-aligned around the anchor | |
9684 var iconHorizontalSpaceOccupied = 0; // how many pixels the icon take up from the anchor pixel, | |
9685 // including the gap between the icon and the label | |
9686 if (iconData != null) { | |
9687 if ("iconAlign" in iconData && iconData.iconAlign == "center") { | |
9688 iconStartPixelOffset = -Math.floor(iconData.width / 2); | |
9689 } | |
9690 iconHorizontalSpaceOccupied = iconStartPixelOffset + iconData.width + metrics.iconLabelGap; | |
9691 | |
9692 if (tapeTrackCount > 0) { | |
9693 newTracks[tapeTrackCount - 1].end = Math.max(newTracks[tapeTrackCount - 1].end, iconHorizontalSpaceOccupied); | |
9694 } | |
9695 | |
9696 var iconHeight = iconData.height + metrics.iconBottomMargin + tapeLastTrackExtraSpace; | |
9697 while (iconHeight > 0) { | |
9698 newTracks.push({ start: iconStartPixelOffset, end: iconHorizontalSpaceOccupied }); | |
9699 iconHeight -= metrics.trackHeight; | |
9700 } | |
9701 } | |
9702 | |
9703 var text = labelData.text; | |
9704 var labelSize = this._frc.computeSize(text); | |
9705 var labelHeight = labelSize.height + metrics.labelBottomMargin + tapeLastTrackExtraSpace; | |
9706 var labelEndPixelOffset = iconHorizontalSpaceOccupied + labelSize.width + metrics.labelRightMargin; | |
9707 if (tapeTrackCount > 0) { | |
9708 newTracks[tapeTrackCount - 1].end = Math.max(newTracks[tapeTrackCount - 1].end, labelEndPixelOffset); | |
9709 } | |
9710 for (var i = 0; labelHeight > 0; i++) { | |
9711 if (tapeTrackCount + i < newTracks.length) { | |
9712 var track = newTracks[tapeTrackCount + i]; | |
9713 track.end = labelEndPixelOffset; | |
9714 } else { | |
9715 newTracks.push({ start: 0, end: labelEndPixelOffset }); | |
9716 } | |
9717 labelHeight -= metrics.trackHeight; | |
9718 } | |
9719 | |
9720 /* | |
9721 * Try to fit the new track on top of the existing tracks, then | |
9722 * render the various elements. | |
9723 */ | |
9724 var firstTrack = this._fitTracks(anchorPixel, newTracks); | |
9725 var verticalPixelOffset = firstTrack * metrics.trackHeight + metrics.trackOffset; | |
9726 var result = {}; | |
9727 | |
9728 result.labelElmtData = this._paintEventLabel( | |
9729 commonData, | |
9730 labelData, | |
9731 anchorPixel + iconHorizontalSpaceOccupied, | |
9732 verticalPixelOffset + tapeHeightOccupied, | |
9733 labelSize.width, | |
9734 labelSize.height, | |
9735 theme | |
9736 ); | |
9737 | |
9738 if (tapeData != null) { | |
9739 if ("latestStart" in tapeData || "earliestEnd" in tapeData) { | |
9740 result.impreciseTapeElmtData = this._paintEventTape( | |
9741 commonData, | |
9742 tapeData, | |
9743 metrics.tapeHeight, | |
9744 verticalPixelOffset, | |
9745 getPixelOffset(tapeData.start), | |
9746 getPixelOffset(tapeData.end), | |
9747 theme.event.duration.impreciseColor, | |
9748 theme.event.duration.impreciseOpacity, | |
9749 metrics, | |
9750 theme | |
9751 ); | |
9752 } | |
9753 if (!tapeData.isInstant && "start" in tapeData && "end" in tapeData) { | |
9754 result.tapeElmtData = this._paintEventTape( | |
9755 commonData, | |
9756 tapeData, | |
9757 metrics.tapeHeight, | |
9758 verticalPixelOffset, | |
9759 anchorPixel, | |
9760 getPixelOffset("earliestEnd" in tapeData ? tapeData.earliestEnd : tapeData.end), | |
9761 tapeData.color, | |
9762 100, | |
9763 metrics, | |
9764 theme | |
9765 ); | |
9766 } | |
9767 } | |
9768 | |
9769 if (iconData != null) { | |
9770 result.iconElmtData = this._paintEventIcon( | |
9771 commonData, | |
9772 iconData, | |
9773 verticalPixelOffset + tapeHeightOccupied, | |
9774 anchorPixel + iconStartPixelOffset, | |
9775 metrics, | |
9776 theme | |
9777 ); | |
9778 } | |
9779 //this._createHighlightDiv(highlightIndex, iconElmtData, theme); | |
9780 | |
9781 return result; | |
9782 }; | |
9783 | |
9784 Timeline.CompactEventPainter.prototype._fitTracks = function(anchorPixel, newTracks) { | |
9785 var firstTrack; | |
9786 for (firstTrack = 0; firstTrack < this._tracks.length; firstTrack++) { | |
9787 var fit = true; | |
9788 for (var j = 0; j < newTracks.length && (firstTrack + j) < this._tracks.length; j++) { | |
9789 var existingTrack = this._tracks[firstTrack + j]; | |
9790 var newTrack = newTracks[j]; | |
9791 if (anchorPixel + newTrack.start < existingTrack) { | |
9792 fit = false; | |
9793 break; | |
9794 } | |
9795 } | |
9796 | |
9797 if (fit) { | |
9798 break; | |
9799 } | |
9800 } | |
9801 for (var i = 0; i < newTracks.length; i++) { | |
9802 this._tracks[firstTrack + i] = anchorPixel + newTracks[i].end; | |
9803 } | |
9804 | |
9805 return firstTrack; | |
9806 }; | |
9807 | |
9808 | |
9809 Timeline.CompactEventPainter.prototype._paintEventIcon = function(commonData, iconData, top, left, metrics, theme) { | |
9810 var img = SimileAjax.Graphics.createTranslucentImage(iconData.url); | |
9811 var iconDiv = this._timeline.getDocument().createElement("div"); | |
9812 iconDiv.className = 'timeline-event-icon' + ("className" in iconData ? (" " + iconData.className) : ""); | |
9813 iconDiv.style.left = left + "px"; | |
9814 iconDiv.style.top = top + "px"; | |
9815 iconDiv.appendChild(img); | |
9816 | |
9817 if ("tooltip" in commonData && typeof commonData.tooltip == "string") { | |
9818 iconDiv.title = commonData.tooltip; | |
9819 } | |
9820 | |
9821 this._eventLayer.appendChild(iconDiv); | |
9822 | |
9823 return { | |
9824 left: left, | |
9825 top: top, | |
9826 width: metrics.iconWidth, | |
9827 height: metrics.iconHeight, | |
9828 elmt: iconDiv | |
9829 }; | |
9830 }; | |
9831 | |
9832 Timeline.CompactEventPainter.prototype._paintEventLabel = function(commonData, labelData, left, top, width, height, theme) { | |
9833 var doc = this._timeline.getDocument(); | |
9834 | |
9835 var labelDiv = doc.createElement("div"); | |
9836 labelDiv.className = 'timeline-event-label'; | |
9837 | |
9838 labelDiv.style.left = left + "px"; | |
9839 labelDiv.style.width = (width + 1) + "px"; | |
9840 labelDiv.style.top = top + "px"; | |
9841 labelDiv.innerHTML = labelData.text; | |
9842 | |
9843 if ("tooltip" in commonData && typeof commonData.tooltip == "string") { | |
9844 labelDiv.title = commonData.tooltip; | |
9845 } | |
9846 if ("color" in labelData && typeof labelData.color == "string") { | |
9847 labelDiv.style.color = labelData.color; | |
9848 } | |
9849 if ("className" in labelData && typeof labelData.className == "string") { | |
9850 labelDiv.className += ' ' + labelData.className; | |
9851 } | |
9852 | |
9853 this._eventLayer.appendChild(labelDiv); | |
9854 | |
9855 return { | |
9856 left: left, | |
9857 top: top, | |
9858 width: width, | |
9859 height: height, | |
9860 elmt: labelDiv | |
9861 }; | |
9862 }; | |
9863 | |
9864 Timeline.CompactEventPainter.prototype._paintEventTape = function( | |
9865 commonData, tapeData, height, top, startPixel, endPixel, color, opacity, metrics, theme) { | |
9866 | |
9867 var width = endPixel - startPixel; | |
9868 | |
9869 var tapeDiv = this._timeline.getDocument().createElement("div"); | |
9870 tapeDiv.className = "timeline-event-tape" | |
9871 | |
9872 tapeDiv.style.left = startPixel + "px"; | |
9873 tapeDiv.style.top = top + "px"; | |
9874 tapeDiv.style.width = width + "px"; | |
9875 tapeDiv.style.height = height + "px"; | |
9876 | |
9877 if ("tooltip" in commonData && typeof commonData.tooltip == "string") { | |
9878 tapeDiv.title = commonData.tooltip; | |
9879 } | |
9880 if (color != null && typeof tapeData.color == "string") { | |
9881 tapeDiv.style.backgroundColor = color; | |
9882 } | |
9883 | |
9884 if ("backgroundImage" in tapeData && typeof tapeData.backgroundImage == "string") { | |
9885 tapeDiv.style.backgroundImage = "url(" + backgroundImage + ")"; | |
9886 tapeDiv.style.backgroundRepeat = | |
9887 ("backgroundRepeat" in tapeData && typeof tapeData.backgroundRepeat == "string") | |
9888 ? tapeData.backgroundRepeat : 'repeat'; | |
9889 } | |
9890 | |
9891 SimileAjax.Graphics.setOpacity(tapeDiv, opacity); | |
9892 | |
9893 if ("className" in tapeData && typeof tapeData.className == "string") { | |
9894 tapeDiv.className += ' ' + tapeData.className; | |
9895 } | |
9896 | |
9897 this._eventLayer.appendChild(tapeDiv); | |
9898 | |
9899 return { | |
9900 left: startPixel, | |
9901 top: top, | |
9902 width: width, | |
9903 height: height, | |
9904 elmt: tapeDiv | |
9905 }; | |
9906 } | |
9907 | |
9908 Timeline.CompactEventPainter.prototype._createHighlightDiv = function(highlightIndex, dimensions, theme) { | |
9909 if (highlightIndex >= 0) { | |
9910 var doc = this._timeline.getDocument(); | |
9911 var eventTheme = theme.event; | |
9912 | |
9913 var color = eventTheme.highlightColors[Math.min(highlightIndex, eventTheme.highlightColors.length - 1)]; | |
9914 | |
9915 var div = doc.createElement("div"); | |
9916 div.style.position = "absolute"; | |
9917 div.style.overflow = "hidden"; | |
9918 div.style.left = (dimensions.left - 2) + "px"; | |
9919 div.style.width = (dimensions.width + 4) + "px"; | |
9920 div.style.top = (dimensions.top - 2) + "px"; | |
9921 div.style.height = (dimensions.height + 4) + "px"; | |
9922 // div.style.background = color; | |
9923 | |
9924 this._highlightLayer.appendChild(div); | |
9925 } | |
9926 }; | |
9927 | |
9928 Timeline.CompactEventPainter.prototype._onClickMultiplePreciseInstantEvent = function(icon, domEvt, events) { | |
9929 var c = SimileAjax.DOM.getPageCoordinates(icon); | |
9930 this._showBubble( | |
9931 c.left + Math.ceil(icon.offsetWidth / 2), | |
9932 c.top + Math.ceil(icon.offsetHeight / 2), | |
9933 events | |
9934 ); | |
9935 | |
9936 var ids = []; | |
9937 for (var i = 0; i < events.length; i++) { | |
9938 ids.push(events[i].getID()); | |
9939 } | |
9940 this._fireOnSelect(ids); | |
9941 | |
9942 domEvt.cancelBubble = true; | |
9943 SimileAjax.DOM.cancelEvent(domEvt); | |
9944 | |
9945 return false; | |
9946 }; | |
9947 | |
9948 Timeline.CompactEventPainter.prototype._onClickInstantEvent = function(icon, domEvt, evt) { | |
9949 var c = SimileAjax.DOM.getPageCoordinates(icon); | |
9950 this._showBubble( | |
9951 c.left + Math.ceil(icon.offsetWidth / 2), | |
9952 c.top + Math.ceil(icon.offsetHeight / 2), | |
9953 [evt] | |
9954 ); | |
9955 this._fireOnSelect([evt.getID()]); | |
9956 | |
9957 domEvt.cancelBubble = true; | |
9958 SimileAjax.DOM.cancelEvent(domEvt); | |
9959 return false; | |
9960 }; | |
9961 | |
9962 Timeline.CompactEventPainter.prototype._onClickDurationEvent = function(target, domEvt, evt) { | |
9963 if ("pageX" in domEvt) { | |
9964 var x = domEvt.pageX; | |
9965 var y = domEvt.pageY; | |
9966 } else { | |
9967 var c = SimileAjax.DOM.getPageCoordinates(target); | |
9968 var x = domEvt.offsetX + c.left; | |
9969 var y = domEvt.offsetY + c.top; | |
9970 } | |
9971 this._showBubble(x, y, [evt]); | |
9972 this._fireOnSelect([evt.getID()]); | |
9973 | |
9974 domEvt.cancelBubble = true; | |
9975 SimileAjax.DOM.cancelEvent(domEvt); | |
9976 return false; | |
9977 }; | |
9978 | |
9979 Timeline.CompactEventPainter.prototype.showBubble = function(evt) { | |
9980 var elmt = this._eventIdToElmt[evt.getID()]; | |
9981 if (elmt) { | |
9982 var c = SimileAjax.DOM.getPageCoordinates(elmt); | |
9983 this._showBubble(c.left + elmt.offsetWidth / 2, c.top + elmt.offsetHeight / 2, [evt]); | |
9984 } | |
9985 }; | |
9986 | |
9987 Timeline.CompactEventPainter.prototype._showBubble = function(x, y, evts) { | |
9988 var div = document.createElement("div"); | |
9989 | |
9990 evts = ("fillInfoBubble" in evts) ? [evts] : evts; | |
9991 for (var i = 0; i < evts.length; i++) { | |
9992 var div2 = document.createElement("div"); | |
9993 div.appendChild(div2); | |
9994 | |
9995 evts[i].fillInfoBubble(div2, this._params.theme, this._band.getLabeller()); | |
9996 } | |
9997 | |
9998 SimileAjax.WindowManager.cancelPopups(); | |
9999 SimileAjax.Graphics.createBubbleForContentAndPoint(div, x, y, this._params.theme.event.bubble.width); | |
10000 }; | |
10001 | |
10002 Timeline.CompactEventPainter.prototype._fireOnSelect = function(eventIDs) { | |
10003 for (var i = 0; i < this._onSelectListeners.length; i++) { | |
10004 this._onSelectListeners[i](eventIDs); | |
10005 } | |
10006 }; | |
10007 /*================================================== | |
10008 * Span Highlight Decorator | |
10009 *================================================== | |
10010 */ | |
10011 | |
10012 Timeline.SpanHighlightDecorator = function(params) { | |
10013 // When evaluating params, test against null. Not "p in params". Testing against | |
10014 // null enables caller to explicitly request the default. Testing against "in" means | |
10015 // that the param has to be ommitted to get the default. | |
10016 this._unit = params.unit != null ? params.unit : SimileAjax.NativeDateUnit; | |
10017 this._startDate = (typeof params.startDate == "string") ? | |
10018 this._unit.parseFromObject(params.startDate) : params.startDate; | |
10019 this._endDate = (typeof params.endDate == "string") ? | |
10020 this._unit.parseFromObject(params.endDate) : params.endDate; | |
10021 this._startLabel = params.startLabel != null ? params.startLabel : ""; // not null! | |
10022 this._endLabel = params.endLabel != null ? params.endLabel : ""; // not null! | |
10023 this._color = params.color; | |
10024 this._cssClass = params.cssClass != null ? params.cssClass : null; | |
10025 this._opacity = params.opacity != null ? params.opacity : 100; | |
10026 // Default z is 10, behind everything but background grid. | |
10027 // If inFront, then place just behind events, in front of everything else | |
10028 this._zIndex = (params.inFront != null && params.inFront) ? 113 : 10; | |
10029 }; | |
10030 | |
10031 Timeline.SpanHighlightDecorator.prototype.initialize = function(band, timeline) { | |
10032 this._band = band; | |
10033 this._timeline = timeline; | |
10034 | |
10035 this._layerDiv = null; | |
10036 }; | |
10037 | |
10038 Timeline.SpanHighlightDecorator.prototype.paint = function() { | |
10039 if (this._layerDiv != null) { | |
10040 this._band.removeLayerDiv(this._layerDiv); | |
10041 } | |
10042 this._layerDiv = this._band.createLayerDiv(this._zIndex); | |
10043 this._layerDiv.setAttribute("name", "span-highlight-decorator"); // for debugging | |
10044 this._layerDiv.style.display = "none"; | |
10045 | |
10046 var minDate = this._band.getMinDate(); | |
10047 var maxDate = this._band.getMaxDate(); | |
10048 | |
10049 if (this._unit.compare(this._startDate, maxDate) < 0 && | |
10050 this._unit.compare(this._endDate, minDate) > 0) { | |
10051 | |
10052 minDate = this._unit.later(minDate, this._startDate); | |
10053 maxDate = this._unit.earlier(maxDate, this._endDate); | |
10054 | |
10055 var minPixel = this._band.dateToPixelOffset(minDate); | |
10056 var maxPixel = this._band.dateToPixelOffset(maxDate); | |
10057 | |
10058 var doc = this._timeline.getDocument(); | |
10059 | |
10060 var createTable = function() { | |
10061 var table = doc.createElement("table"); | |
10062 table.insertRow(0).insertCell(0); | |
10063 return table; | |
10064 }; | |
10065 | |
10066 var div = doc.createElement("div"); | |
10067 div.className='timeline-highlight-decorator' | |
10068 if(this._cssClass) { | |
10069 div.className += ' ' + this._cssClass; | |
10070 } | |
10071 if(this._color != null) { | |
10072 div.style.backgroundColor = this._color; | |
10073 } | |
10074 if (this._opacity < 100) { | |
10075 SimileAjax.Graphics.setOpacity(div, this._opacity); | |
10076 } | |
10077 this._layerDiv.appendChild(div); | |
10078 | |
10079 var tableStartLabel = createTable(); | |
10080 tableStartLabel.className = 'timeline-highlight-label timeline-highlight-label-start' | |
10081 var tdStart = tableStartLabel.rows[0].cells[0] | |
10082 tdStart.innerHTML = this._startLabel; | |
10083 if (this._cssClass) { | |
10084 tdStart.className = 'label_' + this._cssClass; | |
10085 } | |
10086 this._layerDiv.appendChild(tableStartLabel); | |
10087 | |
10088 var tableEndLabel = createTable(); | |
10089 tableEndLabel.className = 'timeline-highlight-label timeline-highlight-label-end' | |
10090 var tdEnd = tableEndLabel.rows[0].cells[0] | |
10091 tdEnd.innerHTML = this._endLabel; | |
10092 if (this._cssClass) { | |
10093 tdEnd.className = 'label_' + this._cssClass; | |
10094 } | |
10095 this._layerDiv.appendChild(tableEndLabel); | |
10096 | |
10097 if (this._timeline.isHorizontal()){ | |
10098 div.style.left = minPixel + "px"; | |
10099 div.style.width = (maxPixel - minPixel) + "px"; | |
10100 | |
10101 tableStartLabel.style.right = (this._band.getTotalViewLength() - minPixel) + "px"; | |
10102 tableStartLabel.style.width = (this._startLabel.length) + "em"; | |
10103 | |
10104 tableEndLabel.style.left = maxPixel + "px"; | |
10105 tableEndLabel.style.width = (this._endLabel.length) + "em"; | |
10106 | |
10107 } else { | |
10108 div.style.top = minPixel + "px"; | |
10109 div.style.height = (maxPixel - minPixel) + "px"; | |
10110 | |
10111 tableStartLabel.style.bottom = minPixel + "px"; | |
10112 tableStartLabel.style.height = "1.5px"; | |
10113 | |
10114 tableEndLabel.style.top = maxPixel + "px"; | |
10115 tableEndLabel.style.height = "1.5px"; | |
10116 } | |
10117 } | |
10118 this._layerDiv.style.display = "block"; | |
10119 }; | |
10120 | |
10121 Timeline.SpanHighlightDecorator.prototype.softPaint = function() { | |
10122 }; | |
10123 | |
10124 /*================================================== | |
10125 * Point Highlight Decorator | |
10126 *================================================== | |
10127 */ | |
10128 | |
10129 Timeline.PointHighlightDecorator = function(params) { | |
10130 this._unit = params.unit != null ? params.unit : SimileAjax.NativeDateUnit; | |
10131 this._date = (typeof params.date == "string") ? | |
10132 this._unit.parseFromObject(params.date) : params.date; | |
10133 this._width = params.width != null ? params.width : 10; | |
10134 // Since the width is used to calculate placements (see minPixel, below), we | |
10135 // specify width here, not in css. | |
10136 this._color = params.color; | |
10137 this._cssClass = params.cssClass != null ? params.cssClass : ''; | |
10138 this._opacity = params.opacity != null ? params.opacity : 100; | |
10139 }; | |
10140 | |
10141 Timeline.PointHighlightDecorator.prototype.initialize = function(band, timeline) { | |
10142 this._band = band; | |
10143 this._timeline = timeline; | |
10144 this._layerDiv = null; | |
10145 }; | |
10146 | |
10147 Timeline.PointHighlightDecorator.prototype.paint = function() { | |
10148 if (this._layerDiv != null) { | |
10149 this._band.removeLayerDiv(this._layerDiv); | |
10150 } | |
10151 this._layerDiv = this._band.createLayerDiv(10); | |
10152 this._layerDiv.setAttribute("name", "span-highlight-decorator"); // for debugging | |
10153 this._layerDiv.style.display = "none"; | |
10154 | |
10155 var minDate = this._band.getMinDate(); | |
10156 var maxDate = this._band.getMaxDate(); | |
10157 | |
10158 if (this._unit.compare(this._date, maxDate) < 0 && | |
10159 this._unit.compare(this._date, minDate) > 0) { | |
10160 | |
10161 var pixel = this._band.dateToPixelOffset(this._date); | |
10162 var minPixel = pixel - Math.round(this._width / 2); | |
10163 | |
10164 var doc = this._timeline.getDocument(); | |
10165 | |
10166 var div = doc.createElement("div"); | |
10167 div.className='timeline-highlight-point-decorator'; | |
10168 div.className += ' ' + this._cssClass; | |
10169 | |
10170 if(this._color != null) { | |
10171 div.style.backgroundColor = this._color; | |
10172 } | |
10173 if (this._opacity < 100) { | |
10174 SimileAjax.Graphics.setOpacity(div, this._opacity); | |
10175 } | |
10176 this._layerDiv.appendChild(div); | |
10177 | |
10178 if (this._timeline.isHorizontal()) { | |
10179 div.style.left = minPixel + "px"; | |
10180 div.style.width = this._width; | |
10181 } else { | |
10182 div.style.top = minPixel + "px"; | |
10183 div.style.height = this._width; | |
10184 } | |
10185 } | |
10186 this._layerDiv.style.display = "block"; | |
10187 }; | |
10188 | |
10189 Timeline.PointHighlightDecorator.prototype.softPaint = function() { | |
10190 }; | |
10191 /*================================================== | |
10192 * Default Unit | |
10193 *================================================== | |
10194 */ | |
10195 | |
10196 Timeline.NativeDateUnit = new Object(); | |
10197 | |
10198 Timeline.NativeDateUnit.createLabeller = function(locale, timeZone) { | |
10199 return new Timeline.GregorianDateLabeller(locale, timeZone); | |
10200 }; | |
10201 | |
10202 Timeline.NativeDateUnit.makeDefaultValue = function() { | |
10203 return new Date(); | |
10204 }; | |
10205 | |
10206 Timeline.NativeDateUnit.cloneValue = function(v) { | |
10207 return new Date(v.getTime()); | |
10208 }; | |
10209 | |
10210 Timeline.NativeDateUnit.getParser = function(format) { | |
10211 if (typeof format == "string") { | |
10212 format = format.toLowerCase(); | |
10213 } | |
10214 return (format == "iso8601" || format == "iso 8601") ? | |
10215 Timeline.DateTime.parseIso8601DateTime : | |
10216 Timeline.DateTime.parseGregorianDateTime; | |
10217 }; | |
10218 | |
10219 Timeline.NativeDateUnit.parseFromObject = function(o) { | |
10220 return Timeline.DateTime.parseGregorianDateTime(o); | |
10221 }; | |
10222 | |
10223 Timeline.NativeDateUnit.toNumber = function(v) { | |
10224 return v.getTime(); | |
10225 }; | |
10226 | |
10227 Timeline.NativeDateUnit.fromNumber = function(n) { | |
10228 return new Date(n); | |
10229 }; | |
10230 | |
10231 Timeline.NativeDateUnit.compare = function(v1, v2) { | |
10232 var n1, n2; | |
10233 if (typeof v1 == "object") { | |
10234 n1 = v1.getTime(); | |
10235 } else { | |
10236 n1 = Number(v1); | |
10237 } | |
10238 if (typeof v2 == "object") { | |
10239 n2 = v2.getTime(); | |
10240 } else { | |
10241 n2 = Number(v2); | |
10242 } | |
10243 | |
10244 return n1 - n2; | |
10245 }; | |
10246 | |
10247 Timeline.NativeDateUnit.earlier = function(v1, v2) { | |
10248 return Timeline.NativeDateUnit.compare(v1, v2) < 0 ? v1 : v2; | |
10249 }; | |
10250 | |
10251 Timeline.NativeDateUnit.later = function(v1, v2) { | |
10252 return Timeline.NativeDateUnit.compare(v1, v2) > 0 ? v1 : v2; | |
10253 }; | |
10254 | |
10255 Timeline.NativeDateUnit.change = function(v, n) { | |
10256 return new Date(v.getTime() + n); | |
10257 }; | |
10258 | |
10259 /*================================================== | |
10260 * Simile Timeplot API | |
10261 * | |
10262 * Include Timeplot in your HTML file as follows: | |
10263 * <script src="http://static.simile.mit.edu/timeplot/api/1.0/timeplot-api.js" type="text/javascript"></script> | |
10264 * | |
10265 *==================================================*/ | |
10266 | |
10267 (function() { | |
10268 | |
10269 var local = false; | |
10270 | |
10271 // obtain local mode from the document URL | |
10272 if (document.location.search.length > 0) { | |
10273 var params = document.location.search.substr(1).split("&"); | |
10274 for (var i = 0; i < params.length; i++) { | |
10275 if (params[i] == "local") { | |
10276 local = true; | |
10277 } | |
10278 } | |
10279 } | |
10280 | |
10281 // obtain local mode from the script URL params attribute | |
10282 if (!local) { | |
10283 var heads = document.documentElement.getElementsByTagName("head"); | |
10284 for (var h = 0; h < heads.length; h++) { | |
10285 var node = heads[h].firstChild; | |
10286 while (node != null) { | |
10287 if (node.nodeType == 1 && node.tagName.toLowerCase() == "script") { | |
10288 var url = node.src; | |
10289 if (url.indexOf("timeplot-complete") >= 0) { | |
10290 local = (url.indexOf("local") >= 0); | |
10291 } | |
10292 } | |
10293 node = node.nextSibling; | |
10294 } | |
10295 } | |
10296 } | |
10297 | |
10298 // Load Timeplot if it's not already loaded (after SimileAjax and Timeline) | |
10299 var loadTimeplot = function() { | |
10300 | |
10301 if (typeof window.Timeplot != "undefined") { | |
10302 return; | |
10303 } | |
10304 | |
10305 window.Timeplot = { | |
10306 loaded: false, | |
10307 params: { bundle: true, autoCreate: true }, | |
10308 namespace: "http://simile.mit.edu/2007/06/timeplot#", | |
10309 importers: {} | |
10310 }; | |
10311 | |
10312 var javascriptFiles = [ | |
10313 "timeplot.js", | |
10314 "plot.js", | |
10315 "sources.js", | |
10316 "geometry.js", | |
10317 "color.js", | |
10318 "math.js", | |
10319 "processor.js" | |
10320 ]; | |
10321 var cssFiles = [ | |
10322 "timeplot.css" | |
10323 ]; | |
10324 | |
10325 var locales = [ "en" ]; | |
10326 | |
10327 var defaultClientLocales = ("language" in navigator ? navigator.language : navigator.browserLanguage).split(";"); | |
10328 for (var l = 0; l < defaultClientLocales.length; l++) { | |
10329 var locale = defaultClientLocales[l]; | |
10330 if (locale != "en") { | |
10331 var segments = locale.split("-"); | |
10332 if (segments.length > 1 && segments[0] != "en") { | |
10333 locales.push(segments[0]); | |
10334 } | |
10335 locales.push(locale); | |
10336 } | |
10337 } | |
10338 | |
10339 var paramTypes = { bundle:Boolean, js:Array, css:Array, autoCreate:Boolean }; | |
10340 if (typeof Timeplot_urlPrefix == "string") { | |
10341 Timeplot.urlPrefix = Timeplot_urlPrefix; | |
10342 if ("Timeplot_parameters" in window) { | |
10343 SimileAjax.parseURLParameters(Timeplot_parameters, Timeplot.params, paramTypes); | |
10344 } | |
10345 } else { | |
10346 var url = SimileAjax.findScript(document, "timeplot-complete.js"); | |
10347 if (url == null) { | |
10348 Timeplot.error = new Error("Failed to derive URL prefix for Simile Timeplot API code files"); | |
10349 return; | |
10350 } | |
10351 Timeplot.urlPrefix = url.substr(0, url.indexOf("timeplot-complete.js")); | |
10352 SimileAjax.parseURLParameters(url, Timeplot.params, paramTypes); | |
10353 } | |
10354 | |
10355 if (Timeplot.params.locale) { // ISO-639 language codes, | |
10356 // optional ISO-3166 country codes (2 characters) | |
10357 if (Timeplot.params.locale != "en") { | |
10358 var segments = Timeplot.params.locale.split("-"); | |
10359 if (segments.length > 1 && segments[0] != "en") { | |
10360 locales.push(segments[0]); | |
10361 } | |
10362 locales.push(Timeplot.params.locale); | |
10363 } | |
10364 } | |
10365 | |
10366 var timeplotURLPrefix = (local) ? "/timeplot/api/1.0/" : Timeplot.urlPrefix; | |
10367 | |
10368 if (local && !("console" in window)) { | |
10369 var firebug = [ timeplotURLPrefix + "lib/firebug/firebug.js" ]; | |
10370 SimileAjax.includeJavascriptFiles(document, "", firebug); | |
10371 } | |
10372 | |
10373 var canvas = document.createElement("canvas"); | |
10374 | |
10375 if (!canvas.getContext) { | |
10376 var excanvas = [ timeplotURLPrefix + "lib/excanvas.js" ]; | |
10377 SimileAjax.includeJavascriptFiles(document, "", excanvas); | |
10378 } | |
10379 | |
10380 var scriptURLs = Timeplot.params.js || []; | |
10381 var cssURLs = Timeplot.params.css || []; | |
10382 | |
10383 // Core scripts and styles | |
10384 if (Timeplot.params.bundle && !local) { | |
10385 scriptURLs.push(timeplotURLPrefix + "timeplot-bundle.js"); | |
10386 cssURLs.push(timeplotURLPrefix + "timeplot-bundle.css"); | |
10387 } else { | |
10388 SimileAjax.prefixURLs(scriptURLs, timeplotURLPrefix + "scripts/", javascriptFiles); | |
10389 SimileAjax.prefixURLs(cssURLs, timeplotURLPrefix + "styles/", cssFiles); | |
10390 } | |
10391 | |
10392 // Localization | |
10393 //for (var i = 0; i < locales.length; i++) { | |
10394 // scriptURLs.push(Timeplot.urlPrefix + "locales/" + locales[i] + "/locale.js"); | |
10395 //}; | |
10396 | |
10397 window.SimileAjax_onLoad = function() { | |
10398 if (local && window.console.open) window.console.open(); | |
10399 if (Timeplot.params.callback) { | |
10400 eval(Timeplot.params.callback + "()"); | |
10401 } | |
10402 } | |
10403 | |
10404 SimileAjax.includeJavascriptFiles(document, "", scriptURLs); | |
10405 SimileAjax.includeCssFiles(document, "", cssURLs); | |
10406 Timeplot.loaded = true; | |
10407 }; | |
10408 | |
10409 // Load Timeline if it's not already loaded (after SimileAjax and before Timeplot) | |
10410 var loadTimeline = function() { | |
10411 if (typeof Timeline != "undefined") { | |
10412 loadTimeplot(); | |
10413 } else { | |
10414 var timelineURL = (local) ? "/timeline/api-2.0/timeline-api.js?bundle=false" : "http://static.simile.mit.edu/timeline/api-2.0/timeline-api.js"; | |
10415 window.SimileAjax_onLoad = loadTimeplot; | |
10416 SimileAjax.includeJavascriptFile(document, timelineURL); | |
10417 } | |
10418 }; | |
10419 | |
10420 // Load SimileAjax if it's not already loaded | |
10421 if (typeof SimileAjax == "undefined") { | |
10422 window.SimileAjax_onLoad = loadTimeline; | |
10423 | |
10424 var url = local ? | |
10425 "/ajax/api-2.0/simile-ajax-api.js?bundle=false" : | |
10426 "http://static.simile.mit.edu/ajax/api-2.0/simile-ajax-api.js?bundle=true"; | |
10427 | |
10428 var createScriptElement = function() { | |
10429 var script = document.createElement("script"); | |
10430 script.type = "text/javascript"; | |
10431 script.language = "JavaScript"; | |
10432 script.src = url; | |
10433 document.getElementsByTagName("head")[0].appendChild(script); | |
10434 } | |
10435 | |
10436 if (document.body == null) { | |
10437 try { | |
10438 document.write("<script src='" + url + "' type='text/javascript'></script>"); | |
10439 } catch (e) { | |
10440 createScriptElement(); | |
10441 } | |
10442 } else { | |
10443 createScriptElement(); | |
10444 } | |
10445 } else { | |
10446 loadTimeline(); | |
10447 } | |
10448 })(); | |
10449 /** | |
10450 * Timeplot | |
10451 * | |
10452 * @fileOverview Timeplot | |
10453 * @name Timeplot | |
10454 */ | |
10455 | |
10456 Timeline.Debug = SimileAjax.Debug; // timeline uses it's own debug system which is not as advanced | |
10457 var log = SimileAjax.Debug.log; // shorter name is easier to use | |
10458 | |
10459 /* | |
10460 * This function is used to implement a raw but effective OOP-like inheritance | |
10461 * in various Timeplot classes. | |
10462 */ | |
10463 Object.extend = function(destination, source) { | |
10464 for (var property in source) { | |
10465 destination[property] = source[property]; | |
10466 } | |
10467 return destination; | |
10468 } | |
10469 | |
10470 // --------------------------------------------- | |
10471 | |
10472 /** | |
10473 * Create a timeplot attached to the given element and using the configuration from the given array of PlotInfos | |
10474 */ | |
10475 Timeplot.create = function(elmt, plotInfos) { | |
10476 return new Timeplot._Impl(elmt, plotInfos); | |
10477 }; | |
10478 | |
10479 /** | |
10480 * Create a PlotInfo configuration from the given map of params | |
10481 */ | |
10482 Timeplot.createPlotInfo = function(params) { | |
10483 return { | |
10484 id: ("id" in params) ? params.id : "p" + Math.round(Math.random() * 1000000), | |
10485 dataSource: ("dataSource" in params) ? params.dataSource : null, | |
10486 eventSource: ("eventSource" in params) ? params.eventSource : null, | |
10487 timeGeometry: ("timeGeometry" in params) ? params.timeGeometry : new Timeplot.DefaultTimeGeometry(), | |
10488 valueGeometry: ("valueGeometry" in params) ? params.valueGeometry : new Timeplot.DefaultValueGeometry(), | |
10489 timeZone: ("timeZone" in params) ? params.timeZone : 0, | |
10490 fillColor: ("fillColor" in params) ? ((params.fillColor == "string") ? new Timeplot.Color(params.fillColor) : params.fillColor) : null, | |
10491 fillGradient: ("fillGradient" in params) ? params.fillGradient : true, | |
10492 fillFrom: ("fillFrom" in params) ? params.fillFrom : Number.NEGATIVE_INFINITY, | |
10493 lineColor: ("lineColor" in params) ? ((params.lineColor == "string") ? new Timeplot.Color(params.lineColor) : params.lineColor) : new Timeplot.Color("#606060"), | |
10494 lineWidth: ("lineWidth" in params) ? params.lineWidth : 1.0, | |
10495 dotRadius: ("dotRadius" in params) ? params.dotRadius : 2.0, | |
10496 dotColor: ("dotColor" in params) ? params.dotColor : null, | |
10497 eventLineWidth: ("eventLineWidth" in params) ? params.eventLineWidth : 1.0, | |
10498 showValues: ("showValues" in params) ? params.showValues : false, | |
10499 roundValues: ("roundValues" in params) ? params.roundValues : true, | |
10500 valuesOpacity: ("valuesOpacity" in params) ? params.valuesOpacity : 75, | |
10501 bubbleWidth: ("bubbleWidth" in params) ? params.bubbleWidth : 300, | |
10502 bubbleHeight: ("bubbleHeight" in params) ? params.bubbleHeight : 200 | |
10503 }; | |
10504 }; | |
10505 | |
10506 // ------------------------------------------------------- | |
10507 | |
10508 /** | |
10509 * This is the implementation of the Timeplot object. | |
10510 * | |
10511 * @constructor | |
10512 */ | |
10513 Timeplot._Impl = function(elmt, plotInfos) { | |
10514 this._id = "t" + Math.round(Math.random() * 1000000); | |
10515 this._containerDiv = elmt; | |
10516 this._plotInfos = plotInfos; | |
10517 this._painters = { | |
10518 background: [], | |
10519 foreground: [] | |
10520 }; | |
10521 this._painter = null; | |
10522 this._active = false; | |
10523 this._upright = false; | |
10524 this._initialize(); | |
10525 }; | |
10526 | |
10527 Timeplot._Impl.prototype = { | |
10528 | |
10529 dispose: function() { | |
10530 for (var i = 0; i < this._plots.length; i++) { | |
10531 this._plots[i].dispose(); | |
10532 } | |
10533 this._plots = null; | |
10534 this._plotsInfos = null; | |
10535 this._containerDiv.innerHTML = ""; | |
10536 }, | |
10537 | |
10538 /** | |
10539 * Returns the main container div this timeplot is operating on. | |
10540 */ | |
10541 getElement: function() { | |
10542 return this._containerDiv; | |
10543 }, | |
10544 | |
10545 /** | |
10546 * Returns document this timeplot belongs to. | |
10547 */ | |
10548 getDocument: function() { | |
10549 return this._containerDiv.ownerDocument; | |
10550 }, | |
10551 | |
10552 /** | |
10553 * Append the given element to the timeplot DOM | |
10554 */ | |
10555 add: function(div) { | |
10556 this._containerDiv.appendChild(div); | |
10557 }, | |
10558 | |
10559 /** | |
10560 * Remove the given element to the timeplot DOM | |
10561 */ | |
10562 remove: function(div) { | |
10563 this._containerDiv.removeChild(div); | |
10564 }, | |
10565 | |
10566 /** | |
10567 * Add a painter to the timeplot | |
10568 */ | |
10569 addPainter: function(layerName, painter) { | |
10570 var layer = this._painters[layerName]; | |
10571 if (layer) { | |
10572 for (var i = 0; i < layer.length; i++) { | |
10573 if (layer[i].context._id == painter.context._id) { | |
10574 return; | |
10575 } | |
10576 } | |
10577 layer.push(painter); | |
10578 } | |
10579 }, | |
10580 | |
10581 /** | |
10582 * Remove a painter from the timeplot | |
10583 */ | |
10584 removePainter: function(layerName, painter) { | |
10585 var layer = this._painters[layerName]; | |
10586 if (layer) { | |
10587 for (var i = 0; i < layer.length; i++) { | |
10588 if (layer[i].context._id == painter.context._id) { | |
10589 layer.splice(i, 1); | |
10590 break; | |
10591 } | |
10592 } | |
10593 } | |
10594 }, | |
10595 | |
10596 /** | |
10597 * Get the width in pixels of the area occupied by the entire timeplot in the page | |
10598 */ | |
10599 getWidth: function() { | |
10600 return this._containerDiv.clientWidth; | |
10601 }, | |
10602 | |
10603 /** | |
10604 * Get the height in pixels of the area occupied by the entire timeplot in the page | |
10605 */ | |
10606 getHeight: function() { | |
10607 return this._containerDiv.clientHeight; | |
10608 }, | |
10609 | |
10610 /** | |
10611 * Get the drawing canvas associated with this timeplot | |
10612 */ | |
10613 getCanvas: function() { | |
10614 return this._canvas; | |
10615 }, | |
10616 | |
10617 /** | |
10618 * <p>Load the data from the given url into the given eventSource, using | |
10619 * the given separator to parse the columns and preprocess it before parsing | |
10620 * thru the optional filter function. The filter is useful for when | |
10621 * the data is row-oriented but the format is not compatible with the | |
10622 * one that Timeplot expects.</p> | |
10623 * | |
10624 * <p>Here is an example of a filter that changes dates in the form 'yyyy/mm/dd' | |
10625 * in the required 'yyyy-mm-dd' format: | |
10626 * <pre>var dataFilter = function(data) { | |
10627 * for (var i = 0; i < data.length; i++) { | |
10628 * var row = data[i]; | |
10629 * row[0] = row[0].replace(/\//g,"-"); | |
10630 * } | |
10631 * return data; | |
10632 * };</pre></p> | |
10633 */ | |
10634 loadText: function(url, separator, eventSource, filter, format) { | |
10635 if (this._active) { | |
10636 var tp = this; | |
10637 | |
10638 var fError = function(statusText, status, xmlhttp) { | |
10639 alert("Failed to load data xml from " + url + "\n" + statusText); | |
10640 tp.hideLoadingMessage(); | |
10641 }; | |
10642 | |
10643 var fDone = function(xmlhttp) { | |
10644 try { | |
10645 eventSource.loadText(xmlhttp.responseText, separator, url, filter, format); | |
10646 } catch (e) { | |
10647 SimileAjax.Debug.exception(e); | |
10648 } finally { | |
10649 tp.hideLoadingMessage(); | |
10650 } | |
10651 }; | |
10652 | |
10653 this.showLoadingMessage(); | |
10654 window.setTimeout(function() { SimileAjax.XmlHttp.get(url, fError, fDone); }, 0); | |
10655 } | |
10656 }, | |
10657 | |
10658 /** | |
10659 * Load event data from the given url into the given eventSource, using | |
10660 * the Timeline XML event format. | |
10661 */ | |
10662 loadXML: function(url, eventSource) { | |
10663 if (this._active) { | |
10664 var tl = this; | |
10665 | |
10666 var fError = function(statusText, status, xmlhttp) { | |
10667 alert("Failed to load data xml from " + url + "\n" + statusText); | |
10668 tl.hideLoadingMessage(); | |
10669 }; | |
10670 | |
10671 var fDone = function(xmlhttp) { | |
10672 try { | |
10673 var xml = xmlhttp.responseXML; | |
10674 if (!xml.documentElement && xmlhttp.responseStream) { | |
10675 xml.load(xmlhttp.responseStream); | |
10676 } | |
10677 eventSource.loadXML(xml, url); | |
10678 } finally { | |
10679 tl.hideLoadingMessage(); | |
10680 } | |
10681 }; | |
10682 | |
10683 this.showLoadingMessage(); | |
10684 window.setTimeout(function() { SimileAjax.XmlHttp.get(url, fError, fDone); }, 0); | |
10685 } | |
10686 }, | |
10687 | |
10688 /** | |
10689 * Overlay a 'div' element filled with the given text and styles to this timeplot | |
10690 * This is used to implement labels since canvas does not support drawing text. | |
10691 */ | |
10692 putText: function(id, text, clazz, styles) { | |
10693 var div = this.putDiv(id, "timeplot-div " + clazz, styles); | |
10694 div.innerHTML = text; | |
10695 return div; | |
10696 }, | |
10697 | |
10698 /** | |
10699 * Overlay a 'div' element, with the given class and the given styles to this timeplot. | |
10700 * This is used for labels and horizontal and vertical grids. | |
10701 */ | |
10702 putDiv: function(id, clazz, styles) { | |
10703 var tid = this._id + "-" + id; | |
10704 var div = document.getElementById(tid); | |
10705 if (!div) { | |
10706 var container = this._containerDiv.firstChild; // get the divs container | |
10707 div = document.createElement("div"); | |
10708 div.setAttribute("id",tid); | |
10709 container.appendChild(div); | |
10710 } | |
10711 div.setAttribute("class","timeplot-div " + clazz); | |
10712 div.setAttribute("className","timeplot-div " + clazz); | |
10713 this.placeDiv(div,styles); | |
10714 return div; | |
10715 }, | |
10716 | |
10717 /** | |
10718 * Associate the given map of styles to the given element. | |
10719 * In case such styles indicate position (left,right,top,bottom) correct them | |
10720 * with the padding information so that they align to the 'internal' area | |
10721 * of the timeplot. | |
10722 */ | |
10723 placeDiv: function(div, styles) { | |
10724 if (styles) { | |
10725 for (style in styles) { | |
10726 if (style == "left") { | |
10727 styles[style] += this._paddingX; | |
10728 styles[style] += "px"; | |
10729 } else if (style == "right") { | |
10730 styles[style] += this._paddingX; | |
10731 styles[style] += "px"; | |
10732 } else if (style == "top") { | |
10733 styles[style] += this._paddingY; | |
10734 styles[style] += "px"; | |
10735 } else if (style == "bottom") { | |
10736 styles[style] += this._paddingY; | |
10737 styles[style] += "px"; | |
10738 } else if (style == "width") { | |
10739 if (styles[style] < 0) styles[style] = 0; | |
10740 styles[style] += "px"; | |
10741 } else if (style == "height") { | |
10742 if (styles[style] < 0) styles[style] = 0; | |
10743 styles[style] += "px"; | |
10744 } | |
10745 div.style[style] = styles[style]; | |
10746 } | |
10747 } | |
10748 }, | |
10749 | |
10750 /** | |
10751 * return a {x,y} map with the location of the given element relative to the 'internal' area of the timeplot | |
10752 * (that is, without the container padding) | |
10753 */ | |
10754 locate: function(div) { | |
10755 return { | |
10756 x: div.offsetLeft - this._paddingX, | |
10757 y: div.offsetTop - this._paddingY | |
10758 } | |
10759 }, | |
10760 | |
10761 /** | |
10762 * Forces timeplot to re-evaluate the various value and time geometries | |
10763 * associated with its plot layers and repaint accordingly. This should | |
10764 * be invoked after the data in any of the data sources has been | |
10765 * modified. | |
10766 */ | |
10767 update: function() { | |
10768 if (this._active) { | |
10769 for (var i = 0; i < this._plots.length; i++) { | |
10770 var plot = this._plots[i]; | |
10771 var dataSource = plot.getDataSource(); | |
10772 if (dataSource) { | |
10773 var range = dataSource.getRange(); | |
10774 if (range) { | |
10775 plot._valueGeometry.setRange(range); | |
10776 plot._timeGeometry.setRange(range); | |
10777 } | |
10778 } | |
10779 plot.hideValues(); | |
10780 } | |
10781 this.paint(); | |
10782 } | |
10783 }, | |
10784 | |
10785 /** | |
10786 * Forces timeplot to re-evaluate its own geometry, clear itself and paint. | |
10787 * This should be used instead of paint() when you're not sure if the | |
10788 * geometry of the page has changed or not. | |
10789 */ | |
10790 repaint: function() { | |
10791 if (this._active) { | |
10792 this._prepareCanvas(); | |
10793 for (var i = 0; i < this._plots.length; i++) { | |
10794 var plot = this._plots[i]; | |
10795 if (plot._timeGeometry) plot._timeGeometry.reset(); | |
10796 if (plot._valueGeometry) plot._valueGeometry.reset(); | |
10797 } | |
10798 this.paint(); | |
10799 } | |
10800 }, | |
10801 | |
10802 /** | |
10803 * Calls all the painters that were registered to this timeplot and makes them | |
10804 * paint the timeplot. This should be used only when you're sure that the geometry | |
10805 * of the page hasn't changed. | |
10806 * NOTE: painting is performed by a different thread and it's safe to call this | |
10807 * function in bursts (as in mousemove or during window resizing | |
10808 */ | |
10809 paint: function() { | |
10810 if (this._active && this._painter == null) { | |
10811 var timeplot = this; | |
10812 this._painter = window.setTimeout(function() { | |
10813 timeplot._clearCanvas(); | |
10814 | |
10815 var run = function(action,context) { | |
10816 try { | |
10817 if (context.setTimeplot) context.setTimeplot(timeplot); | |
10818 action.apply(context,[]); | |
10819 } catch (e) { | |
10820 SimileAjax.Debug.exception(e); | |
10821 } | |
10822 } | |
10823 | |
10824 var background = timeplot._painters.background; | |
10825 for (var i = 0; i < background.length; i++) { | |
10826 run(background[i].action, background[i].context); | |
10827 } | |
10828 var foreground = timeplot._painters.foreground; | |
10829 for (var i = 0; i < foreground.length; i++) { | |
10830 run(foreground[i].action, foreground[i].context); | |
10831 } | |
10832 | |
10833 timeplot._painter = null; | |
10834 }, 20); | |
10835 } | |
10836 }, | |
10837 | |
10838 _clearCanvas: function() { | |
10839 var canvas = this.getCanvas(); | |
10840 var ctx = canvas.getContext('2d'); | |
10841 ctx.clearRect(0,0,canvas.width,canvas.height); | |
10842 }, | |
10843 | |
10844 _clearLabels: function() { | |
10845 var labels = this._containerDiv.firstChild; | |
10846 if (labels) this._containerDiv.removeChild(labels); | |
10847 labels = document.createElement("div"); | |
10848 this._containerDiv.appendChild(labels); | |
10849 }, | |
10850 | |
10851 _prepareCanvas: function() { | |
10852 var canvas = this.getCanvas(); | |
10853 | |
10854 // using jQuery. note we calculate the average padding; if your | |
10855 // padding settings are not symmetrical, the labels will be off | |
10856 // since they expect to be centered on the canvas. | |
10857 var con = SimileAjax.jQuery(this._containerDiv); | |
10858 this._paddingX = (parseInt(con.css('paddingLeft')) + | |
10859 parseInt(con.css('paddingRight'))) / 2; | |
10860 this._paddingY = (parseInt(con.css('paddingTop')) + | |
10861 parseInt(con.css('paddingBottom'))) / 2; | |
10862 | |
10863 canvas.width = this.getWidth() - (this._paddingX * 2); | |
10864 canvas.height = this.getHeight() - (this._paddingY * 2); | |
10865 | |
10866 var ctx = canvas.getContext('2d'); | |
10867 this._setUpright(ctx, canvas); | |
10868 ctx.globalCompositeOperation = 'source-over'; | |
10869 }, | |
10870 | |
10871 _setUpright: function(ctx, canvas) { | |
10872 // excanvas+IE requires this to be done only once, ever; actual canvas | |
10873 // implementations reset and require this for each call to re-layout | |
10874 if (!SimileAjax.Platform.browser.isIE) this._upright = false; | |
10875 if (!this._upright) { | |
10876 this._upright = true; | |
10877 ctx.translate(0, canvas.height); | |
10878 ctx.scale(1,-1); | |
10879 } | |
10880 }, | |
10881 | |
10882 _isBrowserSupported: function(canvas) { | |
10883 var browser = SimileAjax.Platform.browser; | |
10884 if ((canvas.getContext && window.getComputedStyle) || | |
10885 (browser.isIE && browser.majorVersion >= 6)) { | |
10886 return true; | |
10887 } else { | |
10888 return false; | |
10889 } | |
10890 }, | |
10891 | |
10892 _initialize: function() { | |
10893 | |
10894 // initialize the window manager (used to handle the popups) | |
10895 // NOTE: this is a singleton and it's safe to call multiple times | |
10896 SimileAjax.WindowManager.initialize(); | |
10897 | |
10898 var containerDiv = this._containerDiv; | |
10899 var doc = containerDiv.ownerDocument; | |
10900 | |
10901 // make sure the timeplot div has the right class | |
10902 containerDiv.className = "timeplot-container " + containerDiv.className; | |
10903 | |
10904 // clean it up if it contains some content | |
10905 while (containerDiv.firstChild) { | |
10906 containerDiv.removeChild(containerDiv.firstChild); | |
10907 } | |
10908 | |
10909 var canvas = doc.createElement("canvas"); | |
10910 | |
10911 if (this._isBrowserSupported(canvas)) { | |
10912 this._clearLabels(); | |
10913 | |
10914 this._canvas = canvas; | |
10915 canvas.className = "timeplot-canvas"; | |
10916 containerDiv.appendChild(canvas); | |
10917 if(!canvas.getContext && G_vmlCanvasManager) { | |
10918 canvas = G_vmlCanvasManager.initElement(this._canvas); | |
10919 this._canvas = canvas; | |
10920 } | |
10921 this._prepareCanvas(); | |
10922 | |
10923 // inserting copyright and link to simile | |
10924 var elmtCopyright = SimileAjax.Graphics.createTranslucentImage(Timeplot.urlPrefix + "images/copyright.png"); | |
10925 elmtCopyright.className = "timeplot-copyright"; | |
10926 elmtCopyright.title = "Timeplot (c) SIMILE - http://simile.mit.edu/timeplot/"; | |
10927 SimileAjax.DOM.registerEvent(elmtCopyright, "click", function() { window.location = "http://simile.mit.edu/timeplot/"; }); | |
10928 containerDiv.appendChild(elmtCopyright); | |
10929 | |
10930 var timeplot = this; | |
10931 var painter = { | |
10932 onAddMany: function() { timeplot.update(); }, | |
10933 onClear: function() { timeplot.update(); } | |
10934 } | |
10935 | |
10936 // creating painters | |
10937 this._plots = []; | |
10938 if (this._plotInfos) { | |
10939 for (var i = 0; i < this._plotInfos.length; i++) { | |
10940 var plot = new Timeplot.Plot(this, this._plotInfos[i]); | |
10941 var dataSource = plot.getDataSource(); | |
10942 if (dataSource) { | |
10943 dataSource.addListener(painter); | |
10944 } | |
10945 this.addPainter("background", { | |
10946 context: plot.getTimeGeometry(), | |
10947 action: plot.getTimeGeometry().paint | |
10948 }); | |
10949 this.addPainter("background", { | |
10950 context: plot.getValueGeometry(), | |
10951 action: plot.getValueGeometry().paint | |
10952 }); | |
10953 this.addPainter("foreground", { | |
10954 context: plot, | |
10955 action: plot.paint | |
10956 }); | |
10957 this._plots.push(plot); | |
10958 plot.initialize(); | |
10959 } | |
10960 } | |
10961 | |
10962 // creating loading UI | |
10963 var message = SimileAjax.Graphics.createMessageBubble(doc); | |
10964 message.containerDiv.className = "timeplot-message-container"; | |
10965 containerDiv.appendChild(message.containerDiv); | |
10966 | |
10967 message.contentDiv.className = "timeplot-message"; | |
10968 message.contentDiv.innerHTML = "<img src='" + Timeplot.urlPrefix + "images/progress-running.gif' /> Loading..."; | |
10969 | |
10970 this.showLoadingMessage = function() { message.containerDiv.style.display = "block"; }; | |
10971 this.hideLoadingMessage = function() { message.containerDiv.style.display = "none"; }; | |
10972 | |
10973 this._active = true; | |
10974 | |
10975 } else { | |
10976 | |
10977 this._message = SimileAjax.Graphics.createMessageBubble(doc); | |
10978 this._message.containerDiv.className = "timeplot-message-container"; | |
10979 this._message.containerDiv.style.top = "15%"; | |
10980 this._message.containerDiv.style.left = "20%"; | |
10981 this._message.containerDiv.style.right = "20%"; | |
10982 this._message.containerDiv.style.minWidth = "20em"; | |
10983 this._message.contentDiv.className = "timeplot-message"; | |
10984 this._message.contentDiv.innerHTML = "We're terribly sorry, but your browser is not currently supported by <a href='http://simile.mit.edu/timeplot/'>Timeplot</a>.<br><br> We are working on supporting it in the near future but, for now, see the <a href='http://simile.mit.edu/wiki/Timeplot_Limitations'>list of currently supported browsers</a>."; | |
10985 this._message.containerDiv.style.display = "block"; | |
10986 | |
10987 containerDiv.appendChild(this._message.containerDiv); | |
10988 | |
10989 } | |
10990 } | |
10991 }; | |
10992 /** | |
10993 * Plot Layer | |
10994 * | |
10995 * @fileOverview Plot Layer | |
10996 * @name Plot | |
10997 */ | |
10998 | |
10999 /** | |
11000 * A plot layer is the main building block for timeplots and it's the object | |
11001 * that is responsible for painting the plot itself. Each plot needs to have | |
11002 * a time geometry, either a DataSource (for time series | |
11003 * plots) or an EventSource (for event plots) and a value geometry in case | |
11004 * of time series plots. Such parameters are passed along | |
11005 * in the 'plotInfo' map. | |
11006 * | |
11007 * @constructor | |
11008 */ | |
11009 Timeplot.Plot = function(timeplot, plotInfo) { | |
11010 this._timeplot = timeplot; | |
11011 this._canvas = timeplot.getCanvas(); | |
11012 this._plotInfo = plotInfo; | |
11013 this._id = plotInfo.id; | |
11014 this._timeGeometry = plotInfo.timeGeometry; | |
11015 this._valueGeometry = plotInfo.valueGeometry; | |
11016 this._theme = new Timeline.getDefaultTheme(); | |
11017 this._dataSource = plotInfo.dataSource; | |
11018 this._eventSource = plotInfo.eventSource; | |
11019 this._bubble = null; | |
11020 }; | |
11021 | |
11022 Timeplot.Plot.prototype = { | |
11023 | |
11024 /** | |
11025 * Initialize the plot layer | |
11026 */ | |
11027 initialize: function() { | |
11028 if (this._dataSource && this._dataSource.getValue) { | |
11029 this._timeFlag = this._timeplot.putDiv("timeflag","timeplot-timeflag"); | |
11030 this._valueFlag = this._timeplot.putDiv(this._id + "valueflag","timeplot-valueflag"); | |
11031 this._valueFlagLineLeft = this._timeplot.putDiv(this._id + "valueflagLineLeft","timeplot-valueflag-line"); | |
11032 this._valueFlagLineRight = this._timeplot.putDiv(this._id + "valueflagLineRight","timeplot-valueflag-line"); | |
11033 if (!this._valueFlagLineLeft.firstChild) { | |
11034 this._valueFlagLineLeft.appendChild(SimileAjax.Graphics.createTranslucentImage(Timeplot.urlPrefix + "images/line_left.png")); | |
11035 this._valueFlagLineRight.appendChild(SimileAjax.Graphics.createTranslucentImage(Timeplot.urlPrefix + "images/line_right.png")); | |
11036 } | |
11037 this._valueFlagPole = this._timeplot.putDiv(this._id + "valuepole","timeplot-valueflag-pole"); | |
11038 | |
11039 var opacity = this._plotInfo.valuesOpacity; | |
11040 | |
11041 SimileAjax.Graphics.setOpacity(this._timeFlag, opacity); | |
11042 SimileAjax.Graphics.setOpacity(this._valueFlag, opacity); | |
11043 SimileAjax.Graphics.setOpacity(this._valueFlagLineLeft, opacity); | |
11044 SimileAjax.Graphics.setOpacity(this._valueFlagLineRight, opacity); | |
11045 SimileAjax.Graphics.setOpacity(this._valueFlagPole, opacity); | |
11046 | |
11047 var plot = this; | |
11048 | |
11049 var mouseOverHandler = function(elmt, evt, target) { | |
11050 if (plot._plotInfo.showValues) { | |
11051 plot._valueFlag.style.display = "block"; | |
11052 mouseMoveHandler(elmt, evt, target); | |
11053 } | |
11054 } | |
11055 | |
11056 var day = 24 * 60 * 60 * 1000; | |
11057 var month = 30 * day; | |
11058 | |
11059 var mouseMoveHandler = function(elmt, evt, target) { | |
11060 if (typeof SimileAjax != "undefined" && plot._plotInfo.showValues) { | |
11061 var c = plot._canvas; | |
11062 var x = Math.round(SimileAjax.DOM.getEventRelativeCoordinates(evt,plot._canvas).x); | |
11063 if (x > c.width) x = c.width; | |
11064 if (isNaN(x) || x < 0) x = 0; | |
11065 var t = plot._timeGeometry.fromScreen(x); | |
11066 if (t == 0) { // something is wrong | |
11067 plot._valueFlag.style.display = "none"; | |
11068 return; | |
11069 } | |
11070 | |
11071 var validTime = plot._dataSource.getClosestValidTime(t); | |
11072 x = plot._timeGeometry.toScreen(validTime); | |
11073 var v = plot._dataSource.getValue(validTime); | |
11074 if (plot._plotInfo.roundValues) v = Math.round(v); | |
11075 plot._valueFlag.innerHTML = new String(v); | |
11076 var d = new Date(validTime); | |
11077 var p = plot._timeGeometry.getPeriod(); | |
11078 if (p < day) { | |
11079 plot._timeFlag.innerHTML = d.toLocaleTimeString(); | |
11080 } else if (p > month) { | |
11081 plot._timeFlag.innerHTML = d.toLocaleDateString(); | |
11082 } else { | |
11083 plot._timeFlag.innerHTML = d.toLocaleString(); | |
11084 } | |
11085 | |
11086 var tw = plot._timeFlag.clientWidth; | |
11087 var th = plot._timeFlag.clientHeight; | |
11088 var tdw = Math.round(tw / 2); | |
11089 var vw = plot._valueFlag.clientWidth; | |
11090 var vh = plot._valueFlag.clientHeight; | |
11091 var y = plot._valueGeometry.toScreen(v); | |
11092 | |
11093 if (x + tdw > c.width) { | |
11094 var tx = c.width - tdw; | |
11095 } else if (x - tdw < 0) { | |
11096 var tx = tdw; | |
11097 } else { | |
11098 var tx = x; | |
11099 } | |
11100 | |
11101 if (plot._timeGeometry._timeValuePosition == "top") { | |
11102 plot._timeplot.placeDiv(plot._valueFlagPole, { | |
11103 left: x, | |
11104 top: th - 5, | |
11105 height: c.height - y - th + 6, | |
11106 display: "block" | |
11107 }); | |
11108 plot._timeplot.placeDiv(plot._timeFlag,{ | |
11109 left: tx - tdw, | |
11110 top: -6, | |
11111 display: "block" | |
11112 }); | |
11113 } else { | |
11114 plot._timeplot.placeDiv(plot._valueFlagPole, { | |
11115 left: x, | |
11116 bottom: th - 5, | |
11117 height: y - th + 6, | |
11118 display: "block" | |
11119 }); | |
11120 plot._timeplot.placeDiv(plot._timeFlag,{ | |
11121 left: tx - tdw, | |
11122 bottom: -6, | |
11123 display: "block" | |
11124 }); | |
11125 } | |
11126 | |
11127 if (x + vw + 14 > c.width && y + vh + 4 > c.height) { | |
11128 plot._valueFlagLineLeft.style.display = "none"; | |
11129 plot._timeplot.placeDiv(plot._valueFlagLineRight,{ | |
11130 left: x - 14, | |
11131 bottom: y - 14, | |
11132 display: "block" | |
11133 }); | |
11134 plot._timeplot.placeDiv(plot._valueFlag,{ | |
11135 left: x - vw - 13, | |
11136 bottom: y - vh - 13, | |
11137 display: "block" | |
11138 }); | |
11139 } else if (x + vw + 14 > c.width && y + vh + 4 < c.height) { | |
11140 plot._valueFlagLineRight.style.display = "none"; | |
11141 plot._timeplot.placeDiv(plot._valueFlagLineLeft,{ | |
11142 left: x - 14, | |
11143 bottom: y, | |
11144 display: "block" | |
11145 }); | |
11146 plot._timeplot.placeDiv(plot._valueFlag,{ | |
11147 left: x - vw - 13, | |
11148 bottom: y + 13, | |
11149 display: "block" | |
11150 }); | |
11151 } else if (x + vw + 14 < c.width && y + vh + 4 > c.height) { | |
11152 plot._valueFlagLineRight.style.display = "none"; | |
11153 plot._timeplot.placeDiv(plot._valueFlagLineLeft,{ | |
11154 left: x, | |
11155 bottom: y - 13, | |
11156 display: "block" | |
11157 }); | |
11158 plot._timeplot.placeDiv(plot._valueFlag,{ | |
11159 left: x + 13, | |
11160 bottom: y - 13, | |
11161 display: "block" | |
11162 }); | |
11163 } else { | |
11164 plot._valueFlagLineLeft.style.display = "none"; | |
11165 plot._timeplot.placeDiv(plot._valueFlagLineRight,{ | |
11166 left: x, | |
11167 bottom: y, | |
11168 display: "block" | |
11169 }); | |
11170 plot._timeplot.placeDiv(plot._valueFlag,{ | |
11171 left: x + 13, | |
11172 bottom: y + 13, | |
11173 display: "block" | |
11174 }); | |
11175 } | |
11176 } | |
11177 } | |
11178 | |
11179 var timeplotElement = this._timeplot.getElement(); | |
11180 SimileAjax.DOM.registerEvent(timeplotElement, "mouseover", mouseOverHandler); | |
11181 SimileAjax.DOM.registerEvent(timeplotElement, "mousemove", mouseMoveHandler); | |
11182 } | |
11183 }, | |
11184 | |
11185 /** | |
11186 * Dispose the plot layer and all the data sources and listeners associated to it | |
11187 */ | |
11188 dispose: function() { | |
11189 if (this._dataSource) { | |
11190 this._dataSource.removeListener(this._paintingListener); | |
11191 this._paintingListener = null; | |
11192 this._dataSource.dispose(); | |
11193 this._dataSource = null; | |
11194 } | |
11195 }, | |
11196 | |
11197 /** | |
11198 * Hide the values | |
11199 */ | |
11200 hideValues: function() { | |
11201 if (this._valueFlag) this._valueFlag.style.display = "none"; | |
11202 if (this._timeFlag) this._timeFlag.style.display = "none"; | |
11203 if (this._valueFlagLineLeft) this._valueFlagLineLeft.style.display = "none"; | |
11204 if (this._valueFlagLineRight) this._valueFlagLineRight.style.display = "none"; | |
11205 if (this._valueFlagPole) this._valueFlagPole.style.display = "none"; | |
11206 }, | |
11207 | |
11208 /** | |
11209 * Return the data source of this plot layer (it could be either a DataSource or an EventSource) | |
11210 */ | |
11211 getDataSource: function() { | |
11212 return (this._dataSource) ? this._dataSource : this._eventSource; | |
11213 }, | |
11214 | |
11215 /** | |
11216 * Return the time geometry associated with this plot layer | |
11217 */ | |
11218 getTimeGeometry: function() { | |
11219 return this._timeGeometry; | |
11220 }, | |
11221 | |
11222 /** | |
11223 * Return the value geometry associated with this plot layer | |
11224 */ | |
11225 getValueGeometry: function() { | |
11226 return this._valueGeometry; | |
11227 }, | |
11228 | |
11229 /** | |
11230 * Paint this plot layer | |
11231 */ | |
11232 paint: function() { | |
11233 var ctx = this._canvas.getContext('2d'); | |
11234 | |
11235 ctx.lineWidth = this._plotInfo.lineWidth; | |
11236 ctx.lineJoin = 'miter'; | |
11237 | |
11238 if (this._dataSource) { | |
11239 if (this._plotInfo.fillColor) { | |
11240 if (this._plotInfo.fillGradient) { | |
11241 var gradient = ctx.createLinearGradient(0,this._canvas.height,0,0); | |
11242 gradient.addColorStop(0,this._plotInfo.fillColor.toString()); | |
11243 gradient.addColorStop(0.5,this._plotInfo.fillColor.toString()); | |
11244 gradient.addColorStop(1, 'rgba(255,255,255,0)'); | |
11245 | |
11246 ctx.fillStyle = gradient; | |
11247 } else { | |
11248 ctx.fillStyle = this._plotInfo.fillColor.toString(); | |
11249 } | |
11250 | |
11251 ctx.beginPath(); | |
11252 ctx.moveTo(0,0); | |
11253 this._plot(function(x,y) { | |
11254 ctx.lineTo(x,y); | |
11255 }); | |
11256 if (this._plotInfo.fillFrom == Number.NEGATIVE_INFINITY) { | |
11257 ctx.lineTo(this._canvas.width, 0); | |
11258 } else if (this._plotInfo.fillFrom == Number.POSITIVE_INFINITY) { | |
11259 ctx.lineTo(this._canvas.width, this._canvas.height); | |
11260 ctx.lineTo(0, this._canvas.height); | |
11261 } else { | |
11262 ctx.lineTo(this._canvas.width, this._valueGeometry.toScreen(this._plotInfo.fillFrom)); | |
11263 ctx.lineTo(0, this._valueGeometry.toScreen(this._plotInfo.fillFrom)); | |
11264 } | |
11265 ctx.fill(); | |
11266 } | |
11267 | |
11268 if (this._plotInfo.lineColor) { | |
11269 ctx.strokeStyle = this._plotInfo.lineColor.toString(); | |
11270 ctx.beginPath(); | |
11271 var first = true; | |
11272 this._plot(function(x,y) { | |
11273 if (first) { | |
11274 first = false; | |
11275 ctx.moveTo(x,y); | |
11276 } | |
11277 ctx.lineTo(x,y); | |
11278 }); | |
11279 ctx.stroke(); | |
11280 } | |
11281 | |
11282 if (this._plotInfo.dotColor) { | |
11283 ctx.fillStyle = this._plotInfo.dotColor.toString(); | |
11284 var r = this._plotInfo.dotRadius; | |
11285 this._plot(function(x,y) { | |
11286 ctx.beginPath(); | |
11287 ctx.arc(x,y,r,0,2*Math.PI,true); | |
11288 ctx.fill(); | |
11289 }); | |
11290 } | |
11291 } | |
11292 | |
11293 if (this._eventSource) { | |
11294 var gradient = ctx.createLinearGradient(0,0,0,this._canvas.height); | |
11295 gradient.addColorStop(1, 'rgba(255,255,255,0)'); | |
11296 | |
11297 ctx.strokeStyle = gradient; | |
11298 ctx.fillStyle = gradient; | |
11299 ctx.lineWidth = this._plotInfo.eventLineWidth; | |
11300 ctx.lineJoin = 'miter'; | |
11301 | |
11302 var i = this._eventSource.getAllEventIterator(); | |
11303 while (i.hasNext()) { | |
11304 var event = i.next(); | |
11305 var color = event.getColor(); | |
11306 color = (color) ? new Timeplot.Color(color) : this._plotInfo.lineColor; | |
11307 var eventStart = event.getStart().getTime(); | |
11308 var eventEnd = event.getEnd().getTime(); | |
11309 if (eventStart == eventEnd) { | |
11310 var c = color.toString(); | |
11311 gradient.addColorStop(0, c); | |
11312 var start = this._timeGeometry.toScreen(eventStart); | |
11313 start = Math.floor(start) + 0.5; // center it between two pixels (makes the rendering nicer) | |
11314 var end = start; | |
11315 ctx.beginPath(); | |
11316 ctx.moveTo(start,0); | |
11317 ctx.lineTo(start,this._canvas.height); | |
11318 ctx.stroke(); | |
11319 var x = start - 4; | |
11320 var w = 7; | |
11321 } else { | |
11322 var c = color.toString(0.5); | |
11323 gradient.addColorStop(0, c); | |
11324 var start = this._timeGeometry.toScreen(eventStart); | |
11325 start = Math.floor(start) + 0.5; // center it between two pixels (makes the rendering nicer) | |
11326 var end = this._timeGeometry.toScreen(eventEnd); | |
11327 end = Math.floor(end) + 0.5; // center it between two pixels (makes the rendering nicer) | |
11328 ctx.fillRect(start,0,end - start, this._canvas.height); | |
11329 var x = start; | |
11330 var w = end - start - 1; | |
11331 } | |
11332 | |
11333 var div = this._timeplot.putDiv(event.getID(),"timeplot-event-box",{ | |
11334 left: Math.round(x), | |
11335 width: Math.round(w), | |
11336 top: 0, | |
11337 height: this._canvas.height - 1 | |
11338 }); | |
11339 | |
11340 var plot = this; | |
11341 var clickHandler = function(event) { | |
11342 return function(elmt, evt, target) { | |
11343 var doc = plot._timeplot.getDocument(); | |
11344 plot._closeBubble(); | |
11345 var coords = SimileAjax.DOM.getEventPageCoordinates(evt); | |
11346 var elmtCoords = SimileAjax.DOM.getPageCoordinates(elmt); | |
11347 plot._bubble = SimileAjax.Graphics.createBubbleForPoint(coords.x, elmtCoords.top + plot._canvas.height, plot._plotInfo.bubbleWidth, plot._plotInfo.bubbleHeight, "bottom"); | |
11348 event.fillInfoBubble(plot._bubble.content, plot._theme, plot._timeGeometry.getLabeler()); | |
11349 } | |
11350 }; | |
11351 var mouseOverHandler = function(elmt, evt, target) { | |
11352 elmt.oldClass = elmt.className; | |
11353 elmt.className = elmt.className + " timeplot-event-box-highlight"; | |
11354 }; | |
11355 var mouseOutHandler = function(elmt, evt, target) { | |
11356 elmt.className = elmt.oldClass; | |
11357 elmt.oldClass = null; | |
11358 } | |
11359 | |
11360 if (!div.instrumented) { | |
11361 SimileAjax.DOM.registerEvent(div, "click" , clickHandler(event)); | |
11362 SimileAjax.DOM.registerEvent(div, "mouseover", mouseOverHandler); | |
11363 SimileAjax.DOM.registerEvent(div, "mouseout" , mouseOutHandler); | |
11364 div.instrumented = true; | |
11365 } | |
11366 } | |
11367 } | |
11368 }, | |
11369 | |
11370 _plot: function(f) { | |
11371 var data = this._dataSource.getData(); | |
11372 if (data) { | |
11373 var times = data.times; | |
11374 var values = data.values; | |
11375 var T = times.length; | |
11376 for (var t = 0; t < T; t++) { | |
11377 var x = this._timeGeometry.toScreen(times[t]); | |
11378 var y = this._valueGeometry.toScreen(values[t]); | |
11379 f(x, y); | |
11380 } | |
11381 } | |
11382 }, | |
11383 | |
11384 _closeBubble: function() { | |
11385 if (this._bubble != null) { | |
11386 this._bubble.close(); | |
11387 this._bubble = null; | |
11388 } | |
11389 } | |
11390 | |
11391 } | |
11392 /** | |
11393 * Sources | |
11394 * | |
11395 * @fileOverview Sources | |
11396 * @name Sources | |
11397 */ | |
11398 | |
11399 /** | |
11400 * Timeplot.DefaultEventSource is an extension of Timeline.DefaultEventSource | |
11401 * and therefore reuses the exact same event loading subsystem that | |
11402 * Timeline uses. | |
11403 * | |
11404 * @constructor | |
11405 */ | |
11406 Timeplot.DefaultEventSource = function(eventIndex) { | |
11407 Timeline.DefaultEventSource.apply(this, arguments); | |
11408 }; | |
11409 | |
11410 Object.extend(Timeplot.DefaultEventSource.prototype, Timeline.DefaultEventSource.prototype); | |
11411 | |
11412 /** | |
11413 * Function used by Timeplot to load time series data from a text file. | |
11414 */ | |
11415 Timeplot.DefaultEventSource.prototype.loadText = function(text, separator, url, filter, format) { | |
11416 | |
11417 if (text == null) { | |
11418 return; | |
11419 } | |
11420 | |
11421 this._events.maxValues = new Array(); | |
11422 var base = this._getBaseURL(url); | |
11423 | |
11424 if (!format) format = 'iso8601'; | |
11425 var parseDateTimeFunction = this._events.getUnit().getParser(format); | |
11426 | |
11427 var data = this._parseText(text, separator); | |
11428 | |
11429 var added = false; | |
11430 | |
11431 if (filter) { | |
11432 data = filter(data); | |
11433 } | |
11434 | |
11435 if (data) { | |
11436 for (var i = 0; i < data.length; i++){ | |
11437 var row = data[i]; | |
11438 if (row.length > 1) { | |
11439 var dateStr = SimileAjax.jQuery.trim(row[0]); | |
11440 var date = parseDateTimeFunction(dateStr); | |
11441 if (date) { | |
11442 var evt = new Timeplot.DefaultEventSource.NumericEvent(date,row.slice(1)); | |
11443 this._events.add(evt); | |
11444 added = true; | |
11445 } | |
11446 } | |
11447 } | |
11448 } | |
11449 | |
11450 if (added) { | |
11451 this._fire("onAddMany", []); | |
11452 } | |
11453 } | |
11454 | |
11455 /* | |
11456 * Parse the data file. | |
11457 * | |
11458 * Adapted from http://www.kawa.net/works/js/jkl/js/jkl-parsexml.js by Yusuke Kawasaki | |
11459 */ | |
11460 Timeplot.DefaultEventSource.prototype._parseText = function (text, separator) { | |
11461 text = text.replace( /\r\n?/g, "\n" ); // normalize newlines | |
11462 var pos = 0; | |
11463 var len = text.length; | |
11464 var table = []; | |
11465 while (pos < len) { | |
11466 var line = []; | |
11467 if (text.charAt(pos) != '#') { // if it's not a comment, process | |
11468 while (pos < len) { | |
11469 if (text.charAt(pos) == '"') { // "..." quoted column | |
11470 var nextquote = text.indexOf('"', pos+1 ); | |
11471 while (nextquote<len && nextquote > -1) { | |
11472 if (text.charAt(nextquote+1) != '"') { | |
11473 break; // end of column | |
11474 } | |
11475 nextquote = text.indexOf('"', nextquote + 2); | |
11476 } | |
11477 if ( nextquote < 0 ) { | |
11478 // unclosed quote | |
11479 } else if (text.charAt(nextquote + 1) == separator) { // end of column | |
11480 var quoted = text.substr(pos + 1, nextquote-pos - 1); | |
11481 quoted = quoted.replace(/""/g,'"'); | |
11482 line[line.length] = quoted; | |
11483 pos = nextquote + 2; | |
11484 continue; | |
11485 } else if (text.charAt(nextquote + 1) == "\n" || // end of line | |
11486 len == nextquote + 1 ) { // end of file | |
11487 var quoted = text.substr(pos + 1, nextquote-pos - 1); | |
11488 quoted = quoted.replace(/""/g,'"'); | |
11489 line[line.length] = quoted; | |
11490 pos = nextquote + 2; | |
11491 break; | |
11492 } else { | |
11493 // invalid column | |
11494 } | |
11495 } | |
11496 var nextseparator = text.indexOf(separator, pos); | |
11497 var nextnline = text.indexOf("\n", pos); | |
11498 if (nextnline < 0) nextnline = len; | |
11499 if (nextseparator > -1 && nextseparator < nextnline) { | |
11500 line[line.length] = text.substr(pos, nextseparator-pos); | |
11501 pos = nextseparator + 1; | |
11502 } else { // end of line | |
11503 line[line.length] = text.substr(pos, nextnline-pos); | |
11504 pos = nextnline + 1; | |
11505 break; | |
11506 } | |
11507 } | |
11508 } else { // if it's a comment, ignore | |
11509 var nextnline = text.indexOf("\n", pos); | |
11510 pos = (nextnline > -1) ? nextnline + 1 : cur; | |
11511 } | |
11512 if (line.length > 0) { | |
11513 table[table.length] = line; // push line | |
11514 } | |
11515 } | |
11516 if (table.length < 0) return; // null data | |
11517 return table; | |
11518 } | |
11519 | |
11520 /** | |
11521 * Return the range of the loaded data | |
11522 */ | |
11523 Timeplot.DefaultEventSource.prototype.getRange = function() { | |
11524 var earliestDate = this.getEarliestDate(); | |
11525 var latestDate = this.getLatestDate(); | |
11526 return { | |
11527 earliestDate: (earliestDate) ? earliestDate : null, | |
11528 latestDate: (latestDate) ? latestDate : null, | |
11529 min: 0, | |
11530 max: 0 | |
11531 }; | |
11532 } | |
11533 | |
11534 // ----------------------------------------------------------------------- | |
11535 | |
11536 /** | |
11537 * A NumericEvent is an Event that also contains an array of values, | |
11538 * one for each columns in the loaded data file. | |
11539 * | |
11540 * @constructor | |
11541 */ | |
11542 Timeplot.DefaultEventSource.NumericEvent = function(time, values) { | |
11543 this._id = "e" + Math.round(Math.random() * 1000000); | |
11544 this._time = time; | |
11545 this._values = values; | |
11546 }; | |
11547 | |
11548 Timeplot.DefaultEventSource.NumericEvent.prototype = { | |
11549 getID: function() { return this._id; }, | |
11550 getTime: function() { return this._time; }, | |
11551 getValues: function() { return this._values; }, | |
11552 | |
11553 // these are required by the EventSource | |
11554 getStart: function() { return this._time; }, | |
11555 getEnd: function() { return this._time; } | |
11556 }; | |
11557 | |
11558 // ----------------------------------------------------------------------- | |
11559 | |
11560 /** | |
11561 * A DataSource represent an abstract class that represents a monodimensional time series. | |
11562 * | |
11563 * @constructor | |
11564 */ | |
11565 Timeplot.DataSource = function(eventSource) { | |
11566 this._eventSource = eventSource; | |
11567 var source = this; | |
11568 this._processingListener = { | |
11569 onAddMany: function() { source._process(); }, | |
11570 onClear: function() { source._clear(); } | |
11571 } | |
11572 this.addListener(this._processingListener); | |
11573 this._listeners = []; | |
11574 this._data = null; | |
11575 this._range = null; | |
11576 }; | |
11577 | |
11578 Timeplot.DataSource.prototype = { | |
11579 | |
11580 _clear: function() { | |
11581 this._data = null; | |
11582 this._range = null; | |
11583 }, | |
11584 | |
11585 _process: function() { | |
11586 this._data = { | |
11587 times: new Array(), | |
11588 values: new Array() | |
11589 }; | |
11590 this._range = { | |
11591 earliestDate: null, | |
11592 latestDate: null, | |
11593 min: 0, | |
11594 max: 0 | |
11595 }; | |
11596 }, | |
11597 | |
11598 /** | |
11599 * Return the range of this data source | |
11600 */ | |
11601 getRange: function() { | |
11602 return this._range; | |
11603 }, | |
11604 | |
11605 /** | |
11606 * Return the actual data that this data source represents. | |
11607 * NOTE: _data = { times: [], values: [] } | |
11608 */ | |
11609 getData: function() { | |
11610 return this._data; | |
11611 }, | |
11612 | |
11613 /** | |
11614 * Return the value associated with the given time in this time series | |
11615 */ | |
11616 getValue: function(t) { | |
11617 if (this._data) { | |
11618 for (var i = 0; i < this._data.times.length; i++) { | |
11619 var l = this._data.times[i]; | |
11620 if (l >= t) { | |
11621 return this._data.values[i]; | |
11622 } | |
11623 } | |
11624 } | |
11625 return 0; | |
11626 }, | |
11627 | |
11628 /** | |
11629 * Return the time of the data point closest to the given time. | |
11630 */ | |
11631 getClosestValidTime: function(t) { | |
11632 if (this._data) { | |
11633 for (var i = 0; i < this._data.times.length; i++) { | |
11634 var currentTime = this._data.times[i]; | |
11635 if (currentTime >= t) { | |
11636 if (i <= 0) { | |
11637 return currentTime; | |
11638 } else { | |
11639 var lastTime = this._data.times[i - 1]; | |
11640 // t must be between currentTime and lastTime. | |
11641 // Find the closest one. | |
11642 if (t - lastTime < currentTime - t) { | |
11643 return lastTime; | |
11644 } else { | |
11645 return currentTime; | |
11646 } | |
11647 } | |
11648 } | |
11649 } | |
11650 } | |
11651 return 0; | |
11652 }, | |
11653 | |
11654 /** | |
11655 * Add a listener to the underlying event source | |
11656 */ | |
11657 addListener: function(listener) { | |
11658 this._eventSource.addListener(listener); | |
11659 }, | |
11660 | |
11661 /** | |
11662 * Remove a listener from the underlying event source | |
11663 */ | |
11664 removeListener: function(listener) { | |
11665 this._eventSource.removeListener(listener); | |
11666 }, | |
11667 | |
11668 /** | |
11669 * Replace a listener from the underlying event source | |
11670 */ | |
11671 replaceListener: function(oldListener, newListener) { | |
11672 this.removeListener(oldListener); | |
11673 this.addListener(newListener); | |
11674 } | |
11675 | |
11676 } | |
11677 | |
11678 // ----------------------------------------------------------------------- | |
11679 | |
11680 /** | |
11681 * Implementation of a DataSource that extracts the time series out of a | |
11682 * single column from the events | |
11683 * | |
11684 * @constructor | |
11685 */ | |
11686 Timeplot.ColumnSource = function(eventSource, column) { | |
11687 Timeplot.DataSource.apply(this, arguments); | |
11688 this._column = column - 1; | |
11689 }; | |
11690 | |
11691 Object.extend(Timeplot.ColumnSource.prototype,Timeplot.DataSource.prototype); | |
11692 | |
11693 Timeplot.ColumnSource.prototype.dispose = function() { | |
11694 this.removeListener(this._processingListener); | |
11695 this._clear(); | |
11696 } | |
11697 | |
11698 Timeplot.ColumnSource.prototype._process = function() { | |
11699 var count = this._eventSource.getCount(); | |
11700 var times = new Array(count); | |
11701 var values = new Array(count); | |
11702 var min = Number.MAX_VALUE; | |
11703 var max = Number.MIN_VALUE; | |
11704 var i = 0; | |
11705 | |
11706 var iterator = this._eventSource.getAllEventIterator(); | |
11707 while (iterator.hasNext()) { | |
11708 var event = iterator.next(); | |
11709 var time = event.getTime(); | |
11710 times[i] = time; | |
11711 var value = this._getValue(event); | |
11712 if (!isNaN(value)) { | |
11713 if (value < min) { | |
11714 min = value; | |
11715 } | |
11716 if (value > max) { | |
11717 max = value; | |
11718 } | |
11719 values[i] = value; | |
11720 } | |
11721 i++; | |
11722 } | |
11723 | |
11724 this._data = { | |
11725 times: times, | |
11726 values: values | |
11727 }; | |
11728 | |
11729 if (max == Number.MIN_VALUE) max = 1; | |
11730 | |
11731 this._range = { | |
11732 earliestDate: this._eventSource.getEarliestDate(), | |
11733 latestDate: this._eventSource.getLatestDate(), | |
11734 min: min, | |
11735 max: max | |
11736 }; | |
11737 } | |
11738 | |
11739 Timeplot.ColumnSource.prototype._getValue = function(event) { | |
11740 return parseFloat(event.getValues()[this._column]); | |
11741 } | |
11742 | |
11743 // --------------------------------------------------------------- | |
11744 | |
11745 /** | |
11746 * Data Source that generates the time series out of the difference | |
11747 * between the first and the second column | |
11748 * | |
11749 * @constructor | |
11750 */ | |
11751 Timeplot.ColumnDiffSource = function(eventSource, column1, column2) { | |
11752 Timeplot.ColumnSource.apply(this, arguments); | |
11753 this._column2 = column2 - 1; | |
11754 }; | |
11755 | |
11756 Object.extend(Timeplot.ColumnDiffSource.prototype,Timeplot.ColumnSource.prototype); | |
11757 | |
11758 Timeplot.ColumnDiffSource.prototype._getValue = function(event) { | |
11759 var a = parseFloat(event.getValues()[this._column]); | |
11760 var b = parseFloat(event.getValues()[this._column2]); | |
11761 return a - b; | |
11762 } | |
11763 /** | |
11764 * Geometries | |
11765 * | |
11766 * @fileOverview Geometries | |
11767 * @name Geometries | |
11768 */ | |
11769 | |
11770 /** | |
11771 * This is the constructor for the default value geometry. | |
11772 * A value geometry is what regulates mapping of the plot values to the screen y coordinate. | |
11773 * If two plots share the same value geometry, they will be drawn using the same scale. | |
11774 * If "min" and "max" parameters are not set, the geometry will stretch itself automatically | |
11775 * so that the entire plot will be drawn without overflowing. The stretching happens also | |
11776 * when a geometry is shared between multiple plots, the one with the biggest range will | |
11777 * win over the others. | |
11778 * | |
11779 * @constructor | |
11780 */ | |
11781 Timeplot.DefaultValueGeometry = function(params) { | |
11782 if (!params) params = {}; | |
11783 this._id = ("id" in params) ? params.id : "g" + Math.round(Math.random() * 1000000); | |
11784 this._axisColor = ("axisColor" in params) ? ((typeof params.axisColor == "string") ? new Timeplot.Color(params.axisColor) : params.axisColor) : new Timeplot.Color("#606060"), | |
11785 this._gridColor = ("gridColor" in params) ? ((typeof params.gridColor == "string") ? new Timeplot.Color(params.gridColor) : params.gridColor) : null, | |
11786 this._gridLineWidth = ("gridLineWidth" in params) ? params.gridLineWidth : 0.5; | |
11787 this._axisLabelsPlacement = ("axisLabelsPlacement" in params) ? params.axisLabelsPlacement : "right"; | |
11788 this._gridSpacing = ("gridSpacing" in params) ? params.gridStep : 50; | |
11789 this._gridType = ("gridType" in params) ? params.gridType : "short"; | |
11790 this._gridShortSize = ("gridShortSize" in params) ? params.gridShortSize : 10; | |
11791 this._minValue = ("min" in params) ? params.min : null; | |
11792 this._maxValue = ("max" in params) ? params.max : null; | |
11793 this._linMap = { | |
11794 direct: function(v) { | |
11795 return v; | |
11796 }, | |
11797 inverse: function(y) { | |
11798 return y; | |
11799 } | |
11800 } | |
11801 this._map = this._linMap; | |
11802 this._labels = []; | |
11803 this._grid = []; | |
11804 } | |
11805 | |
11806 Timeplot.DefaultValueGeometry.prototype = { | |
11807 | |
11808 /** | |
11809 * Since geometries can be reused across timeplots, we need to call this function | |
11810 * before we can paint using this geometry. | |
11811 */ | |
11812 setTimeplot: function(timeplot) { | |
11813 this._timeplot = timeplot; | |
11814 this._canvas = timeplot.getCanvas(); | |
11815 this.reset(); | |
11816 }, | |
11817 | |
11818 /** | |
11819 * Called by all the plot layers this geometry is associated with | |
11820 * to update the value range. Unless min/max values are specified | |
11821 * in the parameters, the biggest value range will be used. | |
11822 */ | |
11823 setRange: function(range) { | |
11824 if ((this._minValue == null) || ((this._minValue != null) && (range.min < this._minValue))) { | |
11825 this._minValue = range.min; | |
11826 } | |
11827 if ((this._maxValue == null) || ((this._maxValue != null) && (range.max * 1.05 > this._maxValue))) { | |
11828 this._maxValue = range.max * 1.05; // get a little more head room to avoid hitting the ceiling | |
11829 } | |
11830 | |
11831 this._updateMappedValues(); | |
11832 | |
11833 if (!(this._minValue == 0 && this._maxValue == 0)) { | |
11834 this._grid = this._calculateGrid(); | |
11835 } | |
11836 }, | |
11837 | |
11838 /** | |
11839 * Called after changing ranges or canvas size to reset the grid values | |
11840 */ | |
11841 reset: function() { | |
11842 this._clearLabels(); | |
11843 this._updateMappedValues(); | |
11844 this._grid = this._calculateGrid(); | |
11845 }, | |
11846 | |
11847 /** | |
11848 * Map the given value to a y screen coordinate. | |
11849 */ | |
11850 toScreen: function(value) { | |
11851 if (this._canvas && this._maxValue) { | |
11852 var v = value - this._minValue; | |
11853 return this._canvas.height * (this._map.direct(v)) / this._mappedRange; | |
11854 } else { | |
11855 return -50; | |
11856 } | |
11857 }, | |
11858 | |
11859 /** | |
11860 * Map the given y screen coordinate to a value | |
11861 */ | |
11862 fromScreen: function(y) { | |
11863 if (this._canvas) { | |
11864 return this._map.inverse(this._mappedRange * y / this._canvas.height) + this._minValue; | |
11865 } else { | |
11866 return 0; | |
11867 } | |
11868 }, | |
11869 | |
11870 /** | |
11871 * Each geometry is also a painter and paints the value grid and grid labels. | |
11872 */ | |
11873 paint: function() { | |
11874 if (this._timeplot) { | |
11875 var ctx = this._canvas.getContext('2d'); | |
11876 | |
11877 ctx.lineJoin = 'miter'; | |
11878 | |
11879 // paint grid | |
11880 if (this._gridColor) { | |
11881 var gridGradient = ctx.createLinearGradient(0,0,0,this._canvas.height); | |
11882 gridGradient.addColorStop(0, this._gridColor.toHexString()); | |
11883 gridGradient.addColorStop(0.3, this._gridColor.toHexString()); | |
11884 gridGradient.addColorStop(1, "rgba(255,255,255,0.5)"); | |
11885 | |
11886 ctx.lineWidth = this._gridLineWidth; | |
11887 ctx.strokeStyle = gridGradient; | |
11888 | |
11889 for (var i = 0; i < this._grid.length; i++) { | |
11890 var tick = this._grid[i]; | |
11891 var y = Math.floor(tick.y) + 0.5; | |
11892 if (typeof tick.label != "undefined") { | |
11893 if (this._axisLabelsPlacement == "left") { | |
11894 var div = this._timeplot.putText(this._id + "-" + i, tick.label,"timeplot-grid-label",{ | |
11895 left: 4, | |
11896 bottom: y + 2, | |
11897 color: this._gridColor.toHexString(), | |
11898 visibility: "hidden" | |
11899 }); | |
11900 this._labels.push(div); | |
11901 } else if (this._axisLabelsPlacement == "right") { | |
11902 var div = this._timeplot.putText(this._id + "-" + i, tick.label, "timeplot-grid-label",{ | |
11903 right: 4, | |
11904 bottom: y + 2, | |
11905 color: this._gridColor.toHexString(), | |
11906 visibility: "hidden" | |
11907 }); | |
11908 this._labels.push(div); | |
11909 } | |
11910 if (y + div.clientHeight < this._canvas.height + 10) { | |
11911 div.style.visibility = "visible"; // avoid the labels that would overflow | |
11912 } | |
11913 } | |
11914 | |
11915 // draw grid | |
11916 ctx.beginPath(); | |
11917 if (this._gridType == "long" || tick.label == 0) { | |
11918 ctx.moveTo(0, y); | |
11919 ctx.lineTo(this._canvas.width, y); | |
11920 } else if (this._gridType == "short") { | |
11921 if (this._axisLabelsPlacement == "left") { | |
11922 ctx.moveTo(0, y); | |
11923 ctx.lineTo(this._gridShortSize, y); | |
11924 } else if (this._axisLabelsPlacement == "right") { | |
11925 ctx.moveTo(this._canvas.width, y); | |
11926 ctx.lineTo(this._canvas.width - this._gridShortSize, y); | |
11927 } | |
11928 } | |
11929 ctx.stroke(); | |
11930 } | |
11931 } | |
11932 | |
11933 // paint axis | |
11934 var axisGradient = ctx.createLinearGradient(0,0,0,this._canvas.height); | |
11935 axisGradient.addColorStop(0, this._axisColor.toString()); | |
11936 axisGradient.addColorStop(0.5, this._axisColor.toString()); | |
11937 axisGradient.addColorStop(1, "rgba(255,255,255,0.5)"); | |
11938 | |
11939 ctx.lineWidth = 1; | |
11940 ctx.strokeStyle = axisGradient; | |
11941 | |
11942 // left axis | |
11943 ctx.beginPath(); | |
11944 ctx.moveTo(0,this._canvas.height); | |
11945 ctx.lineTo(0,0); | |
11946 ctx.stroke(); | |
11947 | |
11948 // right axis | |
11949 ctx.beginPath(); | |
11950 ctx.moveTo(this._canvas.width,0); | |
11951 ctx.lineTo(this._canvas.width,this._canvas.height); | |
11952 ctx.stroke(); | |
11953 } | |
11954 }, | |
11955 | |
11956 /** | |
11957 * Removes all the labels that were added by this geometry | |
11958 */ | |
11959 _clearLabels: function() { | |
11960 for (var i = 0; i < this._labels.length; i++) { | |
11961 var l = this._labels[i]; | |
11962 var parent = l.parentNode; | |
11963 if (parent) parent.removeChild(l); | |
11964 } | |
11965 }, | |
11966 | |
11967 /* | |
11968 * This function calculates the grid spacing that it will be used | |
11969 * by this geometry to draw the grid in order to reduce clutter. | |
11970 */ | |
11971 _calculateGrid: function() { | |
11972 var grid = []; | |
11973 | |
11974 if (!this._canvas || this._valueRange == 0) return grid; | |
11975 | |
11976 var power = 0; | |
11977 if (this._valueRange > 1) { | |
11978 while (Math.pow(10,power) < this._valueRange) { | |
11979 power++; | |
11980 } | |
11981 power--; | |
11982 } else { | |
11983 while (Math.pow(10,power) > this._valueRange) { | |
11984 power--; | |
11985 } | |
11986 } | |
11987 | |
11988 var unit = Math.pow(10,power); | |
11989 var inc = unit; | |
11990 while (true) { | |
11991 var dy = this.toScreen(this._minValue + inc); | |
11992 | |
11993 while (dy < this._gridSpacing) { | |
11994 inc += unit; | |
11995 dy = this.toScreen(this._minValue + inc); | |
11996 } | |
11997 | |
11998 if (dy > 2 * this._gridSpacing) { // grids are too spaced out | |
11999 unit /= 10; | |
12000 inc = unit; | |
12001 } else { | |
12002 break; | |
12003 } | |
12004 } | |
12005 | |
12006 var v = 0; | |
12007 var y = this.toScreen(v); | |
12008 if (this._minValue >= 0) { | |
12009 while (y < this._canvas.height) { | |
12010 if (y > 0) { | |
12011 grid.push({ y: y, label: v }); | |
12012 } | |
12013 v += inc; | |
12014 y = this.toScreen(v); | |
12015 } | |
12016 } else if (this._maxValue <= 0) { | |
12017 while (y > 0) { | |
12018 if (y < this._canvas.height) { | |
12019 grid.push({ y: y, label: v }); | |
12020 } | |
12021 v -= inc; | |
12022 y = this.toScreen(v); | |
12023 } | |
12024 } else { | |
12025 while (y < this._canvas.height) { | |
12026 if (y > 0) { | |
12027 grid.push({ y: y, label: v }); | |
12028 } | |
12029 v += inc; | |
12030 y = this.toScreen(v); | |
12031 } | |
12032 v = -inc; | |
12033 y = this.toScreen(v); | |
12034 while (y > 0) { | |
12035 if (y < this._canvas.height) { | |
12036 grid.push({ y: y, label: v }); | |
12037 } | |
12038 v -= inc; | |
12039 y = this.toScreen(v); | |
12040 } | |
12041 } | |
12042 | |
12043 return grid; | |
12044 }, | |
12045 | |
12046 /* | |
12047 * Update the values that are used by the paint function so that | |
12048 * we don't have to calculate them at every repaint. | |
12049 */ | |
12050 _updateMappedValues: function() { | |
12051 this._valueRange = Math.abs(this._maxValue - this._minValue); | |
12052 this._mappedRange = this._map.direct(this._valueRange); | |
12053 } | |
12054 | |
12055 } | |
12056 | |
12057 // -------------------------------------------------- | |
12058 | |
12059 /** | |
12060 * This is the constructor for a Logarithmic value geometry, which | |
12061 * is useful when plots have values in different magnitudes but | |
12062 * exhibit similar trends and such trends want to be shown on the same | |
12063 * plot (here a cartesian geometry would make the small magnitudes | |
12064 * disappear). | |
12065 * | |
12066 * NOTE: this class extends Timeplot.DefaultValueGeometry and inherits | |
12067 * all of the methods of that class. So refer to that class. | |
12068 * | |
12069 * @constructor | |
12070 */ | |
12071 Timeplot.LogarithmicValueGeometry = function(params) { | |
12072 Timeplot.DefaultValueGeometry.apply(this, arguments); | |
12073 this._logMap = { | |
12074 direct: function(v) { | |
12075 return Math.log(v + 1) / Math.log(10); | |
12076 }, | |
12077 inverse: function(y) { | |
12078 return Math.exp(Math.log(10) * y) - 1; | |
12079 } | |
12080 } | |
12081 this._mode = "log"; | |
12082 this._map = this._logMap; | |
12083 this._calculateGrid = this._logarithmicCalculateGrid; | |
12084 }; | |
12085 | |
12086 Timeplot.LogarithmicValueGeometry.prototype._linearCalculateGrid = Timeplot.DefaultValueGeometry.prototype._calculateGrid; | |
12087 | |
12088 Object.extend(Timeplot.LogarithmicValueGeometry.prototype,Timeplot.DefaultValueGeometry.prototype); | |
12089 | |
12090 /* | |
12091 * This function calculates the grid spacing that it will be used | |
12092 * by this geometry to draw the grid in order to reduce clutter. | |
12093 */ | |
12094 Timeplot.LogarithmicValueGeometry.prototype._logarithmicCalculateGrid = function() { | |
12095 var grid = []; | |
12096 | |
12097 if (!this._canvas || this._valueRange == 0) return grid; | |
12098 | |
12099 var v = 1; | |
12100 var y = this.toScreen(v); | |
12101 while (y < this._canvas.height || isNaN(y)) { | |
12102 if (y > 0) { | |
12103 grid.push({ y: y, label: v }); | |
12104 } | |
12105 v *= 10; | |
12106 y = this.toScreen(v); | |
12107 } | |
12108 | |
12109 return grid; | |
12110 }; | |
12111 | |
12112 /** | |
12113 * Turn the logarithmic scaling off. | |
12114 */ | |
12115 Timeplot.LogarithmicValueGeometry.prototype.actLinear = function() { | |
12116 this._mode = "lin"; | |
12117 this._map = this._linMap; | |
12118 this._calculateGrid = this._linearCalculateGrid; | |
12119 this.reset(); | |
12120 } | |
12121 | |
12122 /** | |
12123 * Turn the logarithmic scaling on. | |
12124 */ | |
12125 Timeplot.LogarithmicValueGeometry.prototype.actLogarithmic = function() { | |
12126 this._mode = "log"; | |
12127 this._map = this._logMap; | |
12128 this._calculateGrid = this._logarithmicCalculateGrid; | |
12129 this.reset(); | |
12130 } | |
12131 | |
12132 /** | |
12133 * Toggle logarithmic scaling seeting it to on if off and viceversa. | |
12134 */ | |
12135 Timeplot.LogarithmicValueGeometry.prototype.toggle = function() { | |
12136 if (this._mode == "log") { | |
12137 this.actLinear(); | |
12138 } else { | |
12139 this.actLogarithmic(); | |
12140 } | |
12141 } | |
12142 | |
12143 // ----------------------------------------------------- | |
12144 | |
12145 /** | |
12146 * This is the constructor for the default time geometry. | |
12147 * | |
12148 * @constructor | |
12149 */ | |
12150 Timeplot.DefaultTimeGeometry = function(params) { | |
12151 if (!params) params = {}; | |
12152 this._id = ("id" in params) ? params.id : "g" + Math.round(Math.random() * 1000000); | |
12153 this._locale = ("locale" in params) ? params.locale : "en"; | |
12154 this._timeZone = ("timeZone" in params) ? params.timeZone : SimileAjax.DateTime.getTimezone(); | |
12155 this._labeler = ("labeller" in params) ? params.labeller : null; | |
12156 this._axisColor = ("axisColor" in params) ? ((params.axisColor == "string") ? new Timeplot.Color(params.axisColor) : params.axisColor) : new Timeplot.Color("#606060"), | |
12157 this._gridColor = ("gridColor" in params) ? ((params.gridColor == "string") ? new Timeplot.Color(params.gridColor) : params.gridColor) : null, | |
12158 this._gridLineWidth = ("gridLineWidth" in params) ? params.gridLineWidth : 0.5; | |
12159 this._axisLabelsPlacement = ("axisLabelsPlacement" in params) ? params.axisLabelsPlacement : "bottom"; | |
12160 this._gridStep = ("gridStep" in params) ? params.gridStep : 100; | |
12161 this._gridStepRange = ("gridStepRange" in params) ? params.gridStepRange : 20; | |
12162 this._min = ("min" in params) ? params.min : null; | |
12163 this._max = ("max" in params) ? params.max : null; | |
12164 this._timeValuePosition =("timeValuePosition" in params) ? params.timeValuePosition : "bottom"; | |
12165 this._unit = ("unit" in params) ? params.unit : Timeline.NativeDateUnit; | |
12166 this._linMap = { | |
12167 direct: function(t) { | |
12168 return t; | |
12169 }, | |
12170 inverse: function(x) { | |
12171 return x; | |
12172 } | |
12173 } | |
12174 this._map = this._linMap; | |
12175 if (!this._labeler) | |
12176 this._labeler = this._unit.createLabeller(this._locale, this._timeZone); | |
12177 var dateParser = this._unit.getParser("iso8601"); | |
12178 if (this._min && !this._min.getTime) { | |
12179 this._min = dateParser(this._min); | |
12180 } | |
12181 if (this._max && !this._max.getTime) { | |
12182 this._max = dateParser(this._max); | |
12183 } | |
12184 this._labels = []; | |
12185 this._grid = []; | |
12186 } | |
12187 | |
12188 Timeplot.DefaultTimeGeometry.prototype = { | |
12189 | |
12190 /** | |
12191 * Since geometries can be reused across timeplots, we need to call this function | |
12192 * before we can paint using this geometry. | |
12193 */ | |
12194 setTimeplot: function(timeplot) { | |
12195 this._timeplot = timeplot; | |
12196 this._canvas = timeplot.getCanvas(); | |
12197 this.reset(); | |
12198 }, | |
12199 | |
12200 /** | |
12201 * Called by all the plot layers this geometry is associated with | |
12202 * to update the time range. Unless min/max values are specified | |
12203 * in the parameters, the biggest range will be used. | |
12204 */ | |
12205 setRange: function(range) { | |
12206 if (this._min) { | |
12207 this._earliestDate = this._min; | |
12208 } else if (range.earliestDate && ((this._earliestDate == null) || ((this._earliestDate != null) && (range.earliestDate.getTime() < this._earliestDate.getTime())))) { | |
12209 this._earliestDate = range.earliestDate; | |
12210 } | |
12211 | |
12212 if (this._max) { | |
12213 this._latestDate = this._max; | |
12214 } else if (range.latestDate && ((this._latestDate == null) || ((this._latestDate != null) && (range.latestDate.getTime() > this._latestDate.getTime())))) { | |
12215 this._latestDate = range.latestDate; | |
12216 } | |
12217 | |
12218 if (!this._earliestDate && !this._latestDate) { | |
12219 this._grid = []; | |
12220 } else { | |
12221 this.reset(); | |
12222 } | |
12223 }, | |
12224 | |
12225 /** | |
12226 * Called after changing ranges or canvas size to reset the grid values | |
12227 */ | |
12228 reset: function() { | |
12229 this._updateMappedValues(); | |
12230 if (this._canvas) this._grid = this._calculateGrid(); | |
12231 }, | |
12232 | |
12233 /** | |
12234 * Map the given date to a x screen coordinate. | |
12235 */ | |
12236 toScreen: function(time) { | |
12237 if (this._canvas && this._latestDate) { | |
12238 var t = time - this._earliestDate.getTime(); | |
12239 var fraction = (this._mappedPeriod > 0) ? this._map.direct(t) / this._mappedPeriod : 0; | |
12240 return this._canvas.width * fraction; | |
12241 } else { | |
12242 return -50; | |
12243 } | |
12244 }, | |
12245 | |
12246 /** | |
12247 * Map the given x screen coordinate to a date. | |
12248 */ | |
12249 fromScreen: function(x) { | |
12250 if (this._canvas) { | |
12251 return this._map.inverse(this._mappedPeriod * x / this._canvas.width) + this._earliestDate.getTime(); | |
12252 } else { | |
12253 return 0; | |
12254 } | |
12255 }, | |
12256 | |
12257 /** | |
12258 * Get a period (in milliseconds) this time geometry spans. | |
12259 */ | |
12260 getPeriod: function() { | |
12261 return this._period; | |
12262 }, | |
12263 | |
12264 /** | |
12265 * Return the labeler that has been associated with this time geometry | |
12266 */ | |
12267 getLabeler: function() { | |
12268 return this._labeler; | |
12269 }, | |
12270 | |
12271 /** | |
12272 * Return the time unit associated with this time geometry | |
12273 */ | |
12274 getUnit: function() { | |
12275 return this._unit; | |
12276 }, | |
12277 | |
12278 /** | |
12279 * Each geometry is also a painter and paints the value grid and grid labels. | |
12280 */ | |
12281 paint: function() { | |
12282 if (this._canvas) { | |
12283 var unit = this._unit; | |
12284 var ctx = this._canvas.getContext('2d'); | |
12285 | |
12286 var gradient = ctx.createLinearGradient(0,0,0,this._canvas.height); | |
12287 | |
12288 ctx.strokeStyle = gradient; | |
12289 ctx.lineWidth = this._gridLineWidth; | |
12290 ctx.lineJoin = 'miter'; | |
12291 | |
12292 // paint grid | |
12293 if (this._gridColor) { | |
12294 gradient.addColorStop(0, this._gridColor.toString()); | |
12295 gradient.addColorStop(1, "rgba(255,255,255,0.9)"); | |
12296 | |
12297 for (var i = 0; i < this._grid.length; i++) { | |
12298 var tick = this._grid[i]; | |
12299 var x = Math.floor(tick.x) + 0.5; | |
12300 if (this._axisLabelsPlacement == "top") { | |
12301 var div = this._timeplot.putText(this._id + "-" + i, tick.label,"timeplot-grid-label",{ | |
12302 left: x + 4, | |
12303 top: 2, | |
12304 visibility: "hidden" | |
12305 }); | |
12306 this._labels.push(div); | |
12307 } else if (this._axisLabelsPlacement == "bottom") { | |
12308 var div = this._timeplot.putText(this._id + "-" + i, tick.label, "timeplot-grid-label",{ | |
12309 left: x + 4, | |
12310 bottom: 2, | |
12311 visibility: "hidden" | |
12312 }); | |
12313 this._labels.push(div); | |
12314 } | |
12315 if (x + div.clientWidth < this._canvas.width + 10) { | |
12316 div.style.visibility = "visible"; // avoid the labels that would overflow | |
12317 } | |
12318 | |
12319 // draw separator | |
12320 ctx.beginPath(); | |
12321 ctx.moveTo(x,0); | |
12322 ctx.lineTo(x,this._canvas.height); | |
12323 ctx.stroke(); | |
12324 } | |
12325 } | |
12326 | |
12327 // paint axis | |
12328 gradient.addColorStop(0, this._axisColor.toString()); | |
12329 gradient.addColorStop(1, "rgba(255,255,255,0.5)"); | |
12330 | |
12331 ctx.lineWidth = 1; | |
12332 gradient.addColorStop(0, this._axisColor.toString()); | |
12333 | |
12334 ctx.beginPath(); | |
12335 ctx.moveTo(0,0); | |
12336 ctx.lineTo(this._canvas.width,0); | |
12337 ctx.stroke(); | |
12338 } | |
12339 }, | |
12340 | |
12341 /* | |
12342 * This function calculates the grid spacing that it will be used | |
12343 * by this geometry to draw the grid in order to reduce clutter. | |
12344 */ | |
12345 _calculateGrid: function() { | |
12346 var grid = []; | |
12347 | |
12348 var time = SimileAjax.DateTime; | |
12349 var u = this._unit; | |
12350 var p = this._period; | |
12351 | |
12352 if (p == 0) return grid; | |
12353 | |
12354 // find the time units nearest to the time period | |
12355 if (p > time.gregorianUnitLengths[time.MILLENNIUM]) { | |
12356 unit = time.MILLENNIUM; | |
12357 } else { | |
12358 for (var unit = time.MILLENNIUM; unit > 0; unit--) { | |
12359 if (time.gregorianUnitLengths[unit-1] <= p && p < time.gregorianUnitLengths[unit]) { | |
12360 unit--; | |
12361 break; | |
12362 } | |
12363 } | |
12364 } | |
12365 | |
12366 var t = u.cloneValue(this._earliestDate); | |
12367 | |
12368 do { | |
12369 time.roundDownToInterval(t, unit, this._timeZone, 1, 0); | |
12370 var x = this.toScreen(u.toNumber(t)); | |
12371 switch (unit) { | |
12372 case time.SECOND: | |
12373 var l = t.toLocaleTimeString(); | |
12374 break; | |
12375 case time.MINUTE: | |
12376 var m = t.getMinutes(); | |
12377 var l = t.getHours() + ":" + ((m < 10) ? "0" : "") + m; | |
12378 break; | |
12379 case time.HOUR: | |
12380 var l = t.getHours() + ":00"; | |
12381 break; | |
12382 case time.DAY: | |
12383 case time.WEEK: | |
12384 case time.MONTH: | |
12385 var l = t.toLocaleDateString(); | |
12386 break; | |
12387 case time.YEAR: | |
12388 case time.DECADE: | |
12389 case time.CENTURY: | |
12390 case time.MILLENNIUM: | |
12391 var tmpd = new Date(t.getTime()); | |
12392 tmpd.setDate(t.getDate() + 1); | |
12393 var l = tmpd.getUTCFullYear(); | |
12394 break; | |
12395 } | |
12396 if (x > 0) { | |
12397 grid.push({ x: x, label: l }); | |
12398 } | |
12399 time.incrementByInterval(t, unit, this._timeZone); | |
12400 } while (t.getTime() < this._latestDate.getTime()); | |
12401 | |
12402 return grid; | |
12403 }, | |
12404 | |
12405 /* | |
12406 * Clear labels generated by this time geometry. | |
12407 */ | |
12408 _clearLabels: function() { | |
12409 for (var i = 0; i < this._labels.length; i++) { | |
12410 var l = this._labels[i]; | |
12411 var parent = l.parentNode; | |
12412 if (parent) parent.removeChild(l); | |
12413 } | |
12414 }, | |
12415 | |
12416 /* | |
12417 * Update the values that are used by the paint function so that | |
12418 * we don't have to calculate them at every repaint. | |
12419 */ | |
12420 _updateMappedValues: function() { | |
12421 if (this._latestDate && this._earliestDate) { | |
12422 this._period = this._latestDate.getTime() - this._earliestDate.getTime(); | |
12423 this._mappedPeriod = this._map.direct(this._period); | |
12424 } else { | |
12425 this._period = 0; | |
12426 this._mappedPeriod = 0; | |
12427 } | |
12428 } | |
12429 | |
12430 } | |
12431 | |
12432 // -------------------------------------------------------------- | |
12433 | |
12434 /** | |
12435 * This is the constructor for the magnifying time geometry. | |
12436 * Users can interact with this geometry and 'magnify' certain areas of the | |
12437 * plot to see the plot enlarged and resolve details that would otherwise | |
12438 * get lost or cluttered with a linear time geometry. | |
12439 * | |
12440 * @constructor | |
12441 */ | |
12442 Timeplot.MagnifyingTimeGeometry = function(params) { | |
12443 Timeplot.DefaultTimeGeometry.apply(this, arguments); | |
12444 | |
12445 var g = this; | |
12446 this._MagnifyingMap = { | |
12447 direct: function(t) { | |
12448 if (t < g._leftTimeMargin) { | |
12449 var x = t * g._leftRate; | |
12450 } else if ( g._leftTimeMargin < t && t < g._rightTimeMargin ) { | |
12451 var x = t * g._expandedRate + g._expandedTimeTranslation; | |
12452 } else { | |
12453 var x = t * g._rightRate + g._rightTimeTranslation; | |
12454 } | |
12455 return x; | |
12456 }, | |
12457 inverse: function(x) { | |
12458 if (x < g._leftScreenMargin) { | |
12459 var t = x / g._leftRate; | |
12460 } else if ( g._leftScreenMargin < x && x < g._rightScreenMargin ) { | |
12461 var t = x / g._expandedRate + g._expandedScreenTranslation; | |
12462 } else { | |
12463 var t = x / g._rightRate + g._rightScreenTranslation; | |
12464 } | |
12465 return t; | |
12466 } | |
12467 } | |
12468 | |
12469 this._mode = "lin"; | |
12470 this._map = this._linMap; | |
12471 }; | |
12472 | |
12473 Object.extend(Timeplot.MagnifyingTimeGeometry.prototype,Timeplot.DefaultTimeGeometry.prototype); | |
12474 | |
12475 /** | |
12476 * Initialize this geometry associating it with the given timeplot and | |
12477 * register the geometry event handlers to the timeplot so that it can | |
12478 * interact with the user. | |
12479 */ | |
12480 Timeplot.MagnifyingTimeGeometry.prototype.initialize = function(timeplot) { | |
12481 Timeplot.DefaultTimeGeometry.prototype.initialize.apply(this, arguments); | |
12482 | |
12483 if (!this._lens) { | |
12484 this._lens = this._timeplot.putDiv("lens","timeplot-lens"); | |
12485 } | |
12486 | |
12487 var period = 1000 * 60 * 60 * 24 * 30; // a month in the magnifying lens | |
12488 | |
12489 var geometry = this; | |
12490 | |
12491 var magnifyWith = function(lens) { | |
12492 var aperture = lens.clientWidth; | |
12493 var loc = geometry._timeplot.locate(lens); | |
12494 geometry.setMagnifyingParams(loc.x + aperture / 2, aperture, period); | |
12495 geometry.actMagnifying(); | |
12496 geometry._timeplot.paint(); | |
12497 } | |
12498 | |
12499 var canvasMouseDown = function(elmt, evt, target) { | |
12500 geometry._canvas.startCoords = SimileAjax.DOM.getEventRelativeCoordinates(evt,elmt); | |
12501 geometry._canvas.pressed = true; | |
12502 } | |
12503 | |
12504 var canvasMouseUp = function(elmt, evt, target) { | |
12505 geometry._canvas.pressed = false; | |
12506 var coords = SimileAjax.DOM.getEventRelativeCoordinates(evt,elmt); | |
12507 if (Timeplot.Math.isClose(coords,geometry._canvas.startCoords,5)) { | |
12508 geometry._lens.style.display = "none"; | |
12509 geometry.actLinear(); | |
12510 geometry._timeplot.paint(); | |
12511 } else { | |
12512 geometry._lens.style.cursor = "move"; | |
12513 magnifyWith(geometry._lens); | |
12514 } | |
12515 } | |
12516 | |
12517 var canvasMouseMove = function(elmt, evt, target) { | |
12518 if (geometry._canvas.pressed) { | |
12519 var coords = SimileAjax.DOM.getEventRelativeCoordinates(evt,elmt); | |
12520 if (coords.x < 0) coords.x = 0; | |
12521 if (coords.x > geometry._canvas.width) coords.x = geometry._canvas.width; | |
12522 geometry._timeplot.placeDiv(geometry._lens, { | |
12523 left: geometry._canvas.startCoords.x, | |
12524 width: coords.x - geometry._canvas.startCoords.x, | |
12525 bottom: 0, | |
12526 height: geometry._canvas.height, | |
12527 display: "block" | |
12528 }); | |
12529 } | |
12530 } | |
12531 | |
12532 var lensMouseDown = function(elmt, evt, target) { | |
12533 geometry._lens.startCoords = SimileAjax.DOM.getEventRelativeCoordinates(evt,elmt);; | |
12534 geometry._lens.pressed = true; | |
12535 } | |
12536 | |
12537 var lensMouseUp = function(elmt, evt, target) { | |
12538 geometry._lens.pressed = false; | |
12539 } | |
12540 | |
12541 var lensMouseMove = function(elmt, evt, target) { | |
12542 if (geometry._lens.pressed) { | |
12543 var coords = SimileAjax.DOM.getEventRelativeCoordinates(evt,elmt); | |
12544 var lens = geometry._lens; | |
12545 var left = lens.offsetLeft + coords.x - lens.startCoords.x; | |
12546 if (left < geometry._timeplot._paddingX) left = geometry._timeplot._paddingX; | |
12547 if (left + lens.clientWidth > geometry._canvas.width - geometry._timeplot._paddingX) left = geometry._canvas.width - lens.clientWidth + geometry._timeplot._paddingX; | |
12548 lens.style.left = left; | |
12549 magnifyWith(lens); | |
12550 } | |
12551 } | |
12552 | |
12553 if (!this._canvas.instrumented) { | |
12554 SimileAjax.DOM.registerEvent(this._canvas, "mousedown", canvasMouseDown); | |
12555 SimileAjax.DOM.registerEvent(this._canvas, "mousemove", canvasMouseMove); | |
12556 SimileAjax.DOM.registerEvent(this._canvas, "mouseup" , canvasMouseUp); | |
12557 SimileAjax.DOM.registerEvent(this._canvas, "mouseup" , lensMouseUp); | |
12558 this._canvas.instrumented = true; | |
12559 } | |
12560 | |
12561 if (!this._lens.instrumented) { | |
12562 SimileAjax.DOM.registerEvent(this._lens, "mousedown", lensMouseDown); | |
12563 SimileAjax.DOM.registerEvent(this._lens, "mousemove", lensMouseMove); | |
12564 SimileAjax.DOM.registerEvent(this._lens, "mouseup" , lensMouseUp); | |
12565 SimileAjax.DOM.registerEvent(this._lens, "mouseup" , canvasMouseUp); | |
12566 this._lens.instrumented = true; | |
12567 } | |
12568 } | |
12569 | |
12570 /** | |
12571 * Set the Magnifying parameters. c is the location in pixels where the Magnifying | |
12572 * center should be located in the timeplot, a is the aperture in pixel of | |
12573 * the Magnifying and b is the time period in milliseconds that the Magnifying | |
12574 * should span. | |
12575 */ | |
12576 Timeplot.MagnifyingTimeGeometry.prototype.setMagnifyingParams = function(c,a,b) { | |
12577 a = a / 2; | |
12578 b = b / 2; | |
12579 | |
12580 var w = this._canvas.width; | |
12581 var d = this._period; | |
12582 | |
12583 if (c < 0) c = 0; | |
12584 if (c > w) c = w; | |
12585 | |
12586 if (c - a < 0) a = c; | |
12587 if (c + a > w) a = w - c; | |
12588 | |
12589 var ct = this.fromScreen(c) - this._earliestDate.getTime(); | |
12590 if (ct - b < 0) b = ct; | |
12591 if (ct + b > d) b = d - ct; | |
12592 | |
12593 this._centerX = c; | |
12594 this._centerTime = ct; | |
12595 this._aperture = a; | |
12596 this._aperturePeriod = b; | |
12597 | |
12598 this._leftScreenMargin = this._centerX - this._aperture; | |
12599 this._rightScreenMargin = this._centerX + this._aperture; | |
12600 this._leftTimeMargin = this._centerTime - this._aperturePeriod; | |
12601 this._rightTimeMargin = this._centerTime + this._aperturePeriod; | |
12602 | |
12603 this._leftRate = (c - a) / (ct - b); | |
12604 this._expandedRate = a / b; | |
12605 this._rightRate = (w - c - a) / (d - ct - b); | |
12606 | |
12607 this._expandedTimeTranslation = this._centerX - this._centerTime * this._expandedRate; | |
12608 this._expandedScreenTranslation = this._centerTime - this._centerX / this._expandedRate; | |
12609 this._rightTimeTranslation = (c + a) - (ct + b) * this._rightRate; | |
12610 this._rightScreenTranslation = (ct + b) - (c + a) / this._rightRate; | |
12611 | |
12612 this._updateMappedValues(); | |
12613 } | |
12614 | |
12615 /* | |
12616 * Turn magnification off. | |
12617 */ | |
12618 Timeplot.MagnifyingTimeGeometry.prototype.actLinear = function() { | |
12619 this._mode = "lin"; | |
12620 this._map = this._linMap; | |
12621 this.reset(); | |
12622 } | |
12623 | |
12624 /* | |
12625 * Turn magnification on. | |
12626 */ | |
12627 Timeplot.MagnifyingTimeGeometry.prototype.actMagnifying = function() { | |
12628 this._mode = "Magnifying"; | |
12629 this._map = this._MagnifyingMap; | |
12630 this.reset(); | |
12631 } | |
12632 | |
12633 /* | |
12634 * Toggle magnification. | |
12635 */ | |
12636 Timeplot.MagnifyingTimeGeometry.prototype.toggle = function() { | |
12637 if (this._mode == "Magnifying") { | |
12638 this.actLinear(); | |
12639 } else { | |
12640 this.actMagnifying(); | |
12641 } | |
12642 } | |
12643 | |
12644 /** | |
12645 * Color | |
12646 * | |
12647 * @fileOverview Color | |
12648 * @name Color | |
12649 */ | |
12650 | |
12651 /* | |
12652 * Inspired by Plotr | |
12653 * Copyright 2007 (c) Bas Wenneker <sabmann[a]gmail[d]com> | |
12654 * For use under the BSD license. <http://www.solutoire.com/plotr> | |
12655 */ | |
12656 | |
12657 /** | |
12658 * Create a Color object that can be used to manipulate colors programmatically. | |
12659 */ | |
12660 Timeplot.Color = function(color) { | |
12661 this._fromHex(color); | |
12662 }; | |
12663 | |
12664 Timeplot.Color.prototype = { | |
12665 | |
12666 /** | |
12667 * Sets the RGB values of this coor | |
12668 * | |
12669 * @param {Number} r,g,b Red green and blue values (between 0 and 255) | |
12670 */ | |
12671 set: function (r,g,b,a) { | |
12672 this.r = r; | |
12673 this.g = g; | |
12674 this.b = b; | |
12675 this.a = (a) ? a : 1.0; | |
12676 return this.check(); | |
12677 }, | |
12678 | |
12679 /** | |
12680 * Set the color transparency | |
12681 * | |
12682 * @param {float} a Transparency value, between 0.0 (fully transparent) and 1.0 (fully opaque). | |
12683 */ | |
12684 transparency: function(a) { | |
12685 this.a = a; | |
12686 return this.check(); | |
12687 }, | |
12688 | |
12689 /** | |
12690 * Lightens the color. | |
12691 * | |
12692 * @param {integer} level Level to lighten the color with. | |
12693 */ | |
12694 lighten: function(level) { | |
12695 var color = new Timeplot.Color(); | |
12696 return color.set( | |
12697 this.r += parseInt(level, 10), | |
12698 this.g += parseInt(level, 10), | |
12699 this.b += parseInt(level, 10) | |
12700 ); | |
12701 }, | |
12702 | |
12703 /** | |
12704 * Darkens the color. | |
12705 * | |
12706 * @param {integer} level Level to darken the color with. | |
12707 */ | |
12708 darken: function(level){ | |
12709 var color = new Timeplot.Color(); | |
12710 return color.set( | |
12711 this.r -= parseInt(level, 10), | |
12712 this.g -= parseInt(level, 10), | |
12713 this.b -= parseInt(level, 10) | |
12714 ); | |
12715 }, | |
12716 | |
12717 /** | |
12718 * Checks and validates if the hex values r, g and b are | |
12719 * between 0 and 255. | |
12720 */ | |
12721 check: function() { | |
12722 if (this.r > 255) { | |
12723 this.r = 255; | |
12724 } else if (this.r < 0){ | |
12725 this.r = 0; | |
12726 } | |
12727 if (this.g > 255) { | |
12728 this.g = 255; | |
12729 } else if (this.g < 0) { | |
12730 this.g = 0; | |
12731 } | |
12732 if (this.b > 255){ | |
12733 this.b = 255; | |
12734 } else if (this.b < 0){ | |
12735 this.b = 0; | |
12736 } | |
12737 if (this.a > 1.0){ | |
12738 this.a = 1.0; | |
12739 } else if (this.a < 0.0){ | |
12740 this.a = 0.0; | |
12741 } | |
12742 return this; | |
12743 }, | |
12744 | |
12745 /** | |
12746 * Returns a string representation of this color. | |
12747 * | |
12748 * @param {float} alpha (optional) Transparency value, between 0.0 (fully transparent) and 1.0 (fully opaque). | |
12749 */ | |
12750 toString: function(alpha) { | |
12751 var a = (alpha) ? alpha : ((this.a) ? this.a : 1.0); | |
12752 return 'rgba(' + this.r + ',' + this.g + ',' + this.b + ',' + a + ')'; | |
12753 }, | |
12754 | |
12755 /** | |
12756 * Returns the hexadecimal representation of this color (without the alpha channel as hex colors don't support it) | |
12757 */ | |
12758 toHexString: function() { | |
12759 return "#" + this._toHex(this.r) + this._toHex(this.g) + this._toHex(this.b); | |
12760 }, | |
12761 | |
12762 /* | |
12763 * Parses and stores the hex values of the input color string. | |
12764 * | |
12765 * @param {String} color Hex or rgb() css string. | |
12766 */ | |
12767 _fromHex: function(color) { | |
12768 if(/^#?([\da-f]{3}|[\da-f]{6})$/i.test(color)){ | |
12769 color = color.replace(/^#/, '').replace(/^([\da-f])([\da-f])([\da-f])$/i, "$1$1$2$2$3$3"); | |
12770 this.r = parseInt(color.substr(0,2), 16); | |
12771 this.g = parseInt(color.substr(2,2), 16); | |
12772 this.b = parseInt(color.substr(4,2), 16); | |
12773 } else if(/^rgb *\( *\d{0,3} *, *\d{0,3} *, *\d{0,3} *\)$/i.test(color)){ | |
12774 color = color.match(/^rgb *\( *(\d{0,3}) *, *(\d{0,3}) *, *(\d{0,3}) *\)$/i); | |
12775 this.r = parseInt(color[1], 10); | |
12776 this.g = parseInt(color[2], 10); | |
12777 this.b = parseInt(color[3], 10); | |
12778 } | |
12779 this.a = 1.0; | |
12780 return this.check(); | |
12781 }, | |
12782 | |
12783 /* | |
12784 * Returns an hexadecimal representation of a 8 bit integer | |
12785 */ | |
12786 _toHex: function(dec) { | |
12787 var hex = "0123456789ABCDEF" | |
12788 if (dec < 0) return "00"; | |
12789 if (dec > 255) return "FF"; | |
12790 var i = Math.floor(dec / 16); | |
12791 var j = dec % 16; | |
12792 return hex.charAt(i) + hex.charAt(j); | |
12793 } | |
12794 | |
12795 }; | |
12796 /** | |
12797 * Math Utility functions | |
12798 * | |
12799 * @fileOverview Math Utility functions | |
12800 * @name Math | |
12801 */ | |
12802 | |
12803 Timeplot.Math = { | |
12804 | |
12805 /** | |
12806 * Evaluates the range (min and max values) of the given array | |
12807 */ | |
12808 range: function(f) { | |
12809 var F = f.length; | |
12810 var min = Number.MAX_VALUE; | |
12811 var max = Number.MIN_VALUE; | |
12812 | |
12813 for (var t = 0; t < F; t++) { | |
12814 var value = f[t]; | |
12815 if (value < min) { | |
12816 min = value; | |
12817 } | |
12818 if (value > max) { | |
12819 max = value; | |
12820 } | |
12821 } | |
12822 | |
12823 return { | |
12824 min: min, | |
12825 max: max | |
12826 } | |
12827 }, | |
12828 | |
12829 /** | |
12830 * Evaluates the windows average of a given array based on the | |
12831 * given window size | |
12832 */ | |
12833 movingAverage: function(f, size) { | |
12834 var F = f.length; | |
12835 var g = new Array(F); | |
12836 for (var n = 0; n < F; n++) { | |
12837 var value = 0; | |
12838 for (var m = n - size; m < n + size; m++) { | |
12839 if (m < 0) { | |
12840 var v = f[0]; | |
12841 } else if (m >= F) { | |
12842 var v = g[n-1]; | |
12843 } else { | |
12844 var v = f[m]; | |
12845 } | |
12846 value += v; | |
12847 } | |
12848 g[n] = value / (2 * size); | |
12849 } | |
12850 return g; | |
12851 }, | |
12852 | |
12853 /** | |
12854 * Returns an array with the integral of the given array | |
12855 */ | |
12856 integral: function(f) { | |
12857 var F = f.length; | |
12858 | |
12859 var g = new Array(F); | |
12860 var sum = 0; | |
12861 | |
12862 for (var t = 0; t < F; t++) { | |
12863 sum += f[t]; | |
12864 g[t] = sum; | |
12865 } | |
12866 | |
12867 return g; | |
12868 }, | |
12869 | |
12870 /** | |
12871 * Normalizes an array so that its complete integral is 1. | |
12872 * This is useful to obtain arrays that preserve the overall | |
12873 * integral of a convolution. | |
12874 */ | |
12875 normalize: function(f) { | |
12876 var F = f.length; | |
12877 var sum = 0.0; | |
12878 | |
12879 for (var t = 0; t < F; t++) { | |
12880 sum += f[t]; | |
12881 } | |
12882 | |
12883 for (var t = 0; t < F; t++) { | |
12884 f[t] /= sum; | |
12885 } | |
12886 | |
12887 return f; | |
12888 }, | |
12889 | |
12890 /** | |
12891 * Calculates the convolution between two arrays | |
12892 */ | |
12893 convolution: function(f,g) { | |
12894 var F = f.length; | |
12895 var G = g.length; | |
12896 | |
12897 var c = new Array(F); | |
12898 | |
12899 for (var m = 0; m < F; m++) { | |
12900 var r = 0; | |
12901 var end = (m + G < F) ? m + G : F; | |
12902 for (var n = m; n < end; n++) { | |
12903 var a = f[n - G]; | |
12904 var b = g[n - m]; | |
12905 r += a * b; | |
12906 } | |
12907 c[m] = r; | |
12908 } | |
12909 | |
12910 return c; | |
12911 }, | |
12912 | |
12913 // ------ Array generators ------------------------------------------------- | |
12914 // Functions that generate arrays based on mathematical functions | |
12915 // Normally these are used to produce operators by convolving them with the input array | |
12916 // The returned arrays have the property of having | |
12917 | |
12918 /** | |
12919 * Generate the heavyside step function of given size | |
12920 */ | |
12921 heavyside: function(size) { | |
12922 var f = new Array(size); | |
12923 var value = 1 / size; | |
12924 for (var t = 0; t < size; t++) { | |
12925 f[t] = value; | |
12926 } | |
12927 return f; | |
12928 }, | |
12929 | |
12930 /** | |
12931 * Generate the gaussian function so that at the given 'size' it has value 'threshold' | |
12932 * and make sure its integral is one. | |
12933 */ | |
12934 gaussian: function(size, threshold) { | |
12935 with (Math) { | |
12936 var radius = size / 2; | |
12937 var variance = radius * radius / log(threshold); | |
12938 var g = new Array(size); | |
12939 for (var t = 0; t < size; t++) { | |
12940 var l = t - radius; | |
12941 g[t] = exp(-variance * l * l); | |
12942 } | |
12943 } | |
12944 | |
12945 return this.normalize(g); | |
12946 }, | |
12947 | |
12948 // ---- Utility Methods -------------------------------------------------- | |
12949 | |
12950 /** | |
12951 * Return x with n significant figures | |
12952 */ | |
12953 round: function(x,n) { | |
12954 with (Math) { | |
12955 if (abs(x) > 1) { | |
12956 var l = floor(log(x)/log(10)); | |
12957 var d = round(exp((l-n+1)*log(10))); | |
12958 var y = round(round(x / d) * d); | |
12959 return y; | |
12960 } else { | |
12961 log("FIXME(SM): still to implement for 0 < abs(x) < 1"); | |
12962 return x; | |
12963 } | |
12964 } | |
12965 }, | |
12966 | |
12967 /** | |
12968 * Return the hyperbolic tangent of x | |
12969 */ | |
12970 tanh: function(x) { | |
12971 if (x > 5) { | |
12972 return 1; | |
12973 } else if (x < 5) { | |
12974 return -1; | |
12975 } else { | |
12976 var expx2 = Math.exp(2 * x); | |
12977 return (expx2 - 1) / (expx2 + 1); | |
12978 } | |
12979 }, | |
12980 | |
12981 /** | |
12982 * Returns true if |a.x - b.x| < value && | a.y - b.y | < value | |
12983 */ | |
12984 isClose: function(a,b,value) { | |
12985 return (a && b && Math.abs(a.x - b.x) < value && Math.abs(a.y - b.y) < value); | |
12986 } | |
12987 | |
12988 } | |
12989 /** | |
12990 * Processing Data Source | |
12991 * | |
12992 * @fileOverview Processing Data Source and Operators | |
12993 * @name Processor | |
12994 */ | |
12995 | |
12996 /* ----------------------------------------------------------------------------- | |
12997 * Operators | |
12998 * | |
12999 * These are functions that can be used directly as Timeplot.Processor operators | |
13000 * ----------------------------------------------------------------------------- */ | |
13001 | |
13002 Timeplot.Operator = { | |
13003 | |
13004 /** | |
13005 * This is the operator used when you want to draw the cumulative sum | |
13006 * of a time series and not, for example, their daily values. | |
13007 */ | |
13008 sum: function(data, params) { | |
13009 return Timeplot.Math.integral(data.values); | |
13010 }, | |
13011 | |
13012 /** | |
13013 * This is the operator that is used to 'smooth' a given time series | |
13014 * by taking the average value of a moving window centered around | |
13015 * each value. The size of the moving window is influenced by the 'size' | |
13016 * parameters in the params map. | |
13017 */ | |
13018 average: function(data, params) { | |
13019 var size = ("size" in params) ? params.size : 30; | |
13020 var result = Timeplot.Math.movingAverage(data.values, size); | |
13021 return result; | |
13022 } | |
13023 } | |
13024 | |
13025 /*================================================== | |
13026 * Processing Data Source | |
13027 *==================================================*/ | |
13028 | |
13029 /** | |
13030 * A Processor is a special DataSource that can apply an Operator | |
13031 * to the DataSource values and thus return a different one. | |
13032 * | |
13033 * @constructor | |
13034 */ | |
13035 Timeplot.Processor = function(dataSource, operator, params) { | |
13036 this._dataSource = dataSource; | |
13037 this._operator = operator; | |
13038 this._params = params; | |
13039 | |
13040 this._data = { | |
13041 times: new Array(), | |
13042 values: new Array() | |
13043 }; | |
13044 | |
13045 this._range = { | |
13046 earliestDate: null, | |
13047 latestDate: null, | |
13048 min: 0, | |
13049 max: 0 | |
13050 }; | |
13051 | |
13052 var processor = this; | |
13053 this._processingListener = { | |
13054 onAddMany: function() { processor._process(); }, | |
13055 onClear: function() { processor._clear(); } | |
13056 } | |
13057 this.addListener(this._processingListener); | |
13058 }; | |
13059 | |
13060 Timeplot.Processor.prototype = { | |
13061 | |
13062 _clear: function() { | |
13063 this.removeListener(this._processingListener); | |
13064 this._dataSource._clear(); | |
13065 }, | |
13066 | |
13067 _process: function() { | |
13068 // this method requires the dataSource._process() method to be | |
13069 // called first as to setup the data and range used below | |
13070 // this should be guaranteed by the order of the listener registration | |
13071 | |
13072 var data = this._dataSource.getData(); | |
13073 var range = this._dataSource.getRange(); | |
13074 | |
13075 var newValues = this._operator(data, this._params); | |
13076 var newValueRange = Timeplot.Math.range(newValues); | |
13077 | |
13078 this._data = { | |
13079 times: data.times, | |
13080 values: newValues | |
13081 }; | |
13082 | |
13083 this._range = { | |
13084 earliestDate: range.earliestDate, | |
13085 latestDate: range.latestDate, | |
13086 min: newValueRange.min, | |
13087 max: newValueRange.max | |
13088 }; | |
13089 }, | |
13090 | |
13091 getRange: function() { | |
13092 return this._range; | |
13093 }, | |
13094 | |
13095 getData: function() { | |
13096 return this._data; | |
13097 }, | |
13098 | |
13099 getValue: Timeplot.DataSource.prototype.getValue, | |
13100 | |
13101 getClosestValidTime: Timeplot.DataSource.prototype.getClosestValidTime, | |
13102 | |
13103 addListener: function(listener) { | |
13104 this._dataSource.addListener(listener); | |
13105 }, | |
13106 | |
13107 removeListener: function(listener) { | |
13108 this._dataSource.removeListener(listener); | |
13109 } | |
13110 } |