File Index
- - -timeline.js
- --
-
-
-
-
-
- - -
diff --git a/static/lib/timeline/CHANGELOG b/static/lib/timeline/CHANGELOG deleted file mode 100644 index 5e196ef4..00000000 --- a/static/lib/timeline/CHANGELOG +++ /dev/null @@ -1,199 +0,0 @@ -CHAP Links Library - Timeline - - -2015-03-04, version 2.9.1 - -- Fixed broken option `groupsWidth`. -- Fixed group height collapsing when there are no items, height now reckons - with the label height. -- Added Brazilian Portuguese locale, thanks @gmmoreira. -- Added locales Portuguese, Chinese, Arabic, Japanese, Korean. Thanks Mario - Fischer. -- Added Polish locale, thanks @pbrzoski. - - -2014-07-28, version 2.9.0 - -- Implemented function `getCluster()`, `getClusterIndex()` and `getSelection()`. - Thanks @igui. -- Added option `clusterMaxItems`. Thanks @igui. -- Fixed 'change' event not being fired when dragging the left or right side - of a range. -- Fixed restoring original dates after canceling a changed item. - - -2014-05-16, version 2.8.0 - -- Implemented a new item type, `FloatingRange`. Thanks Nick Hardy (@NickHardy). -- Implemented options `groupMinHeight`. Thanks @sapeish. -- Fixed event `add` not triggered in case of ctrl+click to add a new item. - Thanks @sapeish. - - -2014-04-09, version 2.7.0 - -- Implemented option `timeChangeable` which allows to have items which can be - moved to a different group but not moved in time. - Thanks Martin Fischer (hansmaulwurf23). -- Implemented function `getVisibleItems(start, end)`. Thanks StephanieHe. -- Added Finish locale. Thanks magandrez. -- Changed default type of a newly created item from range to box. - - -2014-01-14, version 2.6.1 - -- Function `Timeline.getItem` now also returns foreign fields available in the - items data. - - -2014-01-13, version 2.6.0 - -- Implemented support for stacking items within groups. Thanks Stefano Fornari. -- Added Turkish locale. Thanks Batuhan Kucukali (LadyArch3r). - - -2013-12-13, version 2.5.1 - -- Added option groupsOrder, which is `true` by default and can be used to provide - a custom group order (or no ordering at all by setting the option to `false`). - (Thanks hansmaulwurf23). -- Rename event `change` to `changed`, added event `change` which fires - repeatedly when an item is being changed. (Thanks bensleveritt). -- Fixed an off-by-one error in mapping columns of a Google DataTable. -- Fixed `type` not being read correctly from a Google DataTable. - Thanks boblepepeur. -- Fixed getting the className of an item. Thanks Pedro Heliodoro. - - -2013-08-20, version 2.5.0 - -- Added French and Dutch localization. Thanks sp0ken and jeroenvg. -- Integration with jQuery Themeroller. Thanks Oleg Varaksin. -- Implemented Shift+Scroll to move the timeline. Thanks Olivier Aubert (oaubert). -- Implemented support for a field `type` to give events a type individually. -- Improved: Timeline is more robust against invalid data (data without - start and end fields). Thanks Roberto Tyley. -- Added an option 'unselectable'. Thanks judge. -- Fixed: when updating a selected item using changeItem, the selection was not - restored correctly. -- Fixed not being able to define columns for fields `group`, `className`, and - `editable` in a Google SpreadSheet. -- Fixed non-working touch events. - - -2013-04-18, version 2.4.2 - -- Implemented localization. (Thanks bjarkebech and José Renato). -- Changed: renamed option intervalMin to zoomMin and intervalMax to zoomMax - (Thanks Oleg Varaksin). -- Fixed: could not change an items field editable using method changeItem. -- Fixed: fields editable and className could not be retrieved from method - getItem (Thanks Oleg Varaksin). - - -2013-03-04, version 2.4.1 - -- Fixed: issue with calculating absolute positions of mouse and elements in - scrolled elements or body. - - -2013-02-26, version 2.4.0 - -- Added a new scale "WEEKDAY", which shows the weekday in the minor label. -- Implemented clustering of items. -- Implemented support for custom item types (besides the built in types box, - range, and dot). See example 25. Thanks Alexander Parshin. -- events can be made editable/read-only on an individual basis by providing - them with a field "editable". -- changed: data can now have custom fields (like an id). -- changed: method addItem has a second, optional parameter "preventRedraw", - which can be used to prevent redrawing the timeline after every addItem. -- changed: option showButtonAdd renamed to showButtonNew, and is now false by - default. -- changed: new items created via a double-click or the "Add" button are now - rendered after add trigger (which can cancel the create action). -- fixed: issues with dates on millisecond scale or with negative years - (issues #19 and #54). -- fixed: options scale and step did not work (method setScale worked correctly - though). -- fixed: alignment of the delete button for selected events with style box, - when the option box.align was "left" or "right" instead of the default - "center". -- fixed: DataTable columns may now have their name defined as label or id, - and the first three columns can have any order now. More robust and flexible. -- fixed: when deleting an item via deleteItem, the currently selection is - maintained instead of unselected (issue #47). -- fixed: When dragging the edges of a range, it was possible to move the range - to another group. -- fixed: non reachable event 'ready" (issue #69). - - -2012-09-18, version 2.3.2 - -- fixed mouseover/dragging of the vertical lines with current time and custom - time, which did only work in the axis area but not in the contents area. - - -2012-09-13, version 2.3.1 - -- added: double tap events on mobile devices will now fire the edit/new event. -- fixed/updated example 09, 15, 17, and 18. -- created example 21. - - -2012-09-05, version 2.3.0 - -- new: items can now have an individual class name which allows styling of - individual items. This gives a lot of extra flexibility. -- fixed issue #26: pinching on Ipad did throw an error "ReferenceError: Can't - find variable: timeline". -- fixed issue #28: in some cases, clicking an item would not select the item - but move the timeline slightly (less than 1px). - - -2012-07-30, version 2.2.1 - -- fixed the 'select' event not being fired when an item gets unselected. -- fixed method changeItem not dealing with changing the type of the item - from box to range or vice versa. - - -2012-07-27, version 2.2.0 - -- added an option showMinorLabels. By setting both showMinorLabels and - showMajorLabels, true, the axis will not be visible. -- fixed issue #6: items and axis did not move with the speed when moving - the Timeline under IE8 and older. -- fixed issue #8: start, end, min, max not working correctly around Date(0). -- fixed issue #9. It is now possible to specify only start or end in the - options, instead of having to specify both. -- fixed issue #12: the group of an item was not restored after canceling a - change. - - -2012-06-18, version 2.1.2 - -- fixed issue #4 again: sorting groups still not working correctly - - -2012-06-15, version 2.1.1 - -- fixed issue #3: broken option stackEvents=false -- fixed issue #4: sorting groups not working correctly - - -2012-06-04, version 2.1 - -- Documentation has a new layout -- Source code cleaned up - - -2012-05-02, version 2.0.1 - -- bug fix: areas on the left and right end of a range (for changing start/end) - where displayed below the range instead of on top of it. - - -2012-05-02, version 2.0 - -- initial upload to github (formerly located at sourceforge) diff --git a/static/lib/timeline/LICENSE b/static/lib/timeline/LICENSE deleted file mode 100644 index ea2712c0..00000000 --- a/static/lib/timeline/LICENSE +++ /dev/null @@ -1,176 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS diff --git a/static/lib/timeline/NOTICE b/static/lib/timeline/NOTICE deleted file mode 100644 index f5e1fe8a..00000000 --- a/static/lib/timeline/NOTICE +++ /dev/null @@ -1,14 +0,0 @@ -CHAP Links Timeline -Copyright 2010-2015 Almende B.V. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/static/lib/timeline/README b/static/lib/timeline/README deleted file mode 100644 index 07dbd2b5..00000000 --- a/static/lib/timeline/README +++ /dev/null @@ -1,66 +0,0 @@ -CHAP Links Network - -http://www.almende.com -http://almende.github.com/chap-links-library/ - -DESCRIPTION - -The Timeline is an interactive visualization chart to visualize events in time. -The events can take place on a single date, or have a start and end date -(a range). You can freely move and zoom in the timeline by dragging and -scrolling in the Timeline. Events can be created, edited, and deleted in the -timeline. The time scale on the axis is adjusted automatically, and supports -scales ranging from milliseconds to years. - -When the timeline is defined as editable, events can be moved to another time -by dragging them. By double clicking, the contents of an event can be changed. -An event can be deleted by clicking the delete button on the upper right. A new -event can be added in different ways: by double clicking in the timeline, or by -keeping the Ctrl key down and clicking or dragging in the timeline, or by -clicking the add button in the upper left of the timeline, and then clicking or -dragging at the right location in the timeline. - -The Timeline is developed as a Google Visualization Chart in javascript. It -runs in every browser without additional requirements. There is a GWT wrapper -available to use the Timeline in GWT (Google Web Toolkit), you can find relevant -documentation here. - -The Timeline is designed to display up to 100 events smoothly on any modern -browser. - - -USAGE - -The Timeline is no built-in visualization of Google. To load the Timeline, -download the file timeline.zip and unzip it in a sub directory timeline on your -html page. Include the google API and the two downloaded files in the head of -your html code: - - - - - -The google visualization needs to be loaded in order to use DataTable. - - google.load("visualization", "1"); - google.setOnLoadCallback(drawTimeline); - function drawTimeline() { - // load data and create the timeline here - } - -The class name of the Timeline is links.Timeline - - var timeline = new links.Timeline(container); - -After being loaded, the timeline can be drawn via the function draw(), provided -with data and options. - - timeline.draw(data, options); - -where data is a DataTable, and options is a name-value map in the JSON format. - - -DOCUMENTATION - -Documentation can be found in the directory doc -Examples can be found in the directory examples diff --git a/static/lib/timeline/doc/default.css b/static/lib/timeline/doc/default.css deleted file mode 100644 index 85f3bd3f..00000000 --- a/static/lib/timeline/doc/default.css +++ /dev/null @@ -1,92 +0,0 @@ -html, body { - width: 100%; - height: 100%; - padding: 0; - margin: 0; -} - -body, td, th { - font-family: arial, sans-serif; - font-size: 11pt; - color: #4D4D4D; - line-height: 1.7em; -} - -#container { - margin: 0 auto; - padding-bottom: 50px; - width: 900px; -} - -h1 { - font-size: 180%; - font-weight: bold; - padding: 0; - margin: 1em 0 1em 0; -} - -h2 { - padding-top: 20px; - padding-bottom: 10px; - border-bottom: 1px solid #a0c0f0; - color: #2B7CE9; -} - -h3 { - font-size: 140%; -} - - -a { - color: #2B7CE9; - text-decoration: none; -} -a:visited { - color: #2E60A4; -} -a:hover { - color: red; - text-decoration: underline; -} - -hr { - border: none 0; - border-top: 1px solid #abc; - height: 1px; -} - -pre { - display: block; - font-size: 10pt; - line-height: 1.5em; - font-family: monospace; -} - -pre, code { - background-color: #f5f5f5; -} - -table -{ - border-collapse: collapse; -} - -th { - font-weight: bold; - border: 1px solid lightgray; - background-color: #E5E5E5; - text-align: left; - vertical-align: top; - padding: 5px; -} - -td { - border: 1px solid lightgray; - padding: 5px; - vertical-align: top; -} - -img.structure { - border: 1px solid gray; - background-color: #f5f5f5; -} \ No newline at end of file diff --git a/static/lib/timeline/doc/img/structure_box.png b/static/lib/timeline/doc/img/structure_box.png deleted file mode 100644 index 323199a0..00000000 Binary files a/static/lib/timeline/doc/img/structure_box.png and /dev/null differ diff --git a/static/lib/timeline/doc/img/structure_box.svg b/static/lib/timeline/doc/img/structure_box.svg deleted file mode 100644 index 95153c19..00000000 --- a/static/lib/timeline/doc/img/structure_box.svg +++ /dev/null @@ -1,310 +0,0 @@ - - - - diff --git a/static/lib/timeline/doc/img/structure_dot.png b/static/lib/timeline/doc/img/structure_dot.png deleted file mode 100644 index 668ae55c..00000000 Binary files a/static/lib/timeline/doc/img/structure_dot.png and /dev/null differ diff --git a/static/lib/timeline/doc/img/structure_dot.svg b/static/lib/timeline/doc/img/structure_dot.svg deleted file mode 100644 index f6c4720f..00000000 --- a/static/lib/timeline/doc/img/structure_dot.svg +++ /dev/null @@ -1,286 +0,0 @@ - - - - diff --git a/static/lib/timeline/doc/img/structure_range.png b/static/lib/timeline/doc/img/structure_range.png deleted file mode 100644 index 3189b766..00000000 Binary files a/static/lib/timeline/doc/img/structure_range.png and /dev/null differ diff --git a/static/lib/timeline/doc/img/structure_range.svg b/static/lib/timeline/doc/img/structure_range.svg deleted file mode 100644 index 7646f2b1..00000000 --- a/static/lib/timeline/doc/img/structure_range.svg +++ /dev/null @@ -1,327 +0,0 @@ - - - - diff --git a/static/lib/timeline/doc/index.html b/static/lib/timeline/doc/index.html deleted file mode 100644 index bb953696..00000000 --- a/static/lib/timeline/doc/index.html +++ /dev/null @@ -1,1696 +0,0 @@ - - -
-Author | -Jos de Jong, Almende B.V. | -
Webpage | -Chap Links Library | -
License | -Apache License, Version 2.0 | -
- The Timeline is an interactive visualization chart to visualize events in time. - The events can take place on a single date, or have a start and end date (a range). - You can freely move and zoom in the timeline by dragging and scrolling in the - Timeline. Events can be created, edited, and deleted in the timeline. - The time scale on the axis is adjusted automatically, and supports scales ranging - from milliseconds to years. -
- -- When the timeline is defined as editable (and groupsChangeable and timeChangeable are set accordingly), events can be moved to another time - by dragging them. By double clicking, the contents of an event can be changed. - An event can be deleted by clicking the delete button on the upper right. - A new event can be added in different - ways: by double clicking in the timeline, or by keeping the Ctrl key down and - clicking or dragging in the timeline, or by clicking the add button in the - upper left of the timeline, and then clicking or dragging at the right location - in the timeline. - -
- -- The Timeline is developed as a Google Visualization Chart in javascript. - It runs in every browser without additional requirements. - There is a GWT wrapper available to use the Timeline in GWT (Google Web Toolkit), - you can find relevant documentation here. -
- -- The Timeline is designed to display up to 1000 events smoothly on any modern browser. -
- -- Here a timeline example. Click and drag to move the timeline, scroll to zoom the timeline. -
-- More examples can be found in the examples directory. -
- - - -<html> - <head> - <title>Timeline demo</title> - - <style> - body {font: 10pt arial;} - </style> - - <script type="text/javascript" src="http://www.google.com/jsapi"></script> - <script type="text/javascript" src="../timeline.js"></script> - <link rel="stylesheet" type="text/css" href="../timeline.css"> - - <script type="text/javascript"> - google.load("visualization", "1"); - - // Set callback to run when API is loaded - google.setOnLoadCallback(drawVisualization); - - // Called when the Visualization API is loaded. - function drawVisualization() { - // Create and populate a data table. - var data = new google.visualization.DataTable(); - data.addColumn('datetime', 'start'); - data.addColumn('datetime', 'end'); - data.addColumn('string', 'content'); - - data.addRows([ - [new Date(2010,7,23), , 'Conversation<br>' + - '<img src="img/comments-icon.png" style="width:32px; height:32px;">'], - [new Date(2010,7,23,23,0,0), , 'Mail from boss<br>' + - '<img src="img/mail-icon.png" style="width:32px; height:32px;">'], - [new Date(2010,7,24,16,0,0), , 'Report'], - [new Date(2010,7,26), new Date(2010,8,2), 'Traject A'], - [new Date(2010,7,28), , 'Memo<br>' + - '<img src="img/notes-edit-icon.png" style="width:48px; height:48px;">'], - [new Date(2010,7,29), , 'Phone call<br>' + - '<img src="img/Hardware-Mobile-Phone-icon.png" style="width:32px; height:32px;">'], - [new Date(2010,7,31), new Date(2010,8,3), 'Traject B'], - [new Date(2010,8,4,12,0,0), , 'Report<br>' + - '<img src="img/attachment-icon.png" style="width:32px; height:32px;">'] - ]); - - // specify options - var options = { - "width": "100%", - "height": "99%", - "style": "box" // optional - }; - - // Instantiate our timeline object. - var timeline = new links.Timeline(document.getElementById('mytimeline')); - - // Draw our timeline with the created data and options - timeline.draw(data, options); - } - </script> - </head> - - <body> - <div id="mytimeline"></div> - </body> -</html> -- - -
- To load the Timeline, download the file
- timeline.zip
- and unzip it in a sub directory timeline on your html page.
- Include the two downloaded files (timeline.js and timeline.css) in the head of your html code.
- When you use a Google DataTable for providing the data,
- the Google API must be included too.
-
- Note that the Google API is only available online, so it is not possible
- to use it in an offline application.
-
<script type="text/javascript" src="http://www.google.com/jsapi"></script> -<script type="text/javascript" src="timeline/timeline.js"></script> -<link rel="stylesheet" type="text/css" href="timeline/timeline.css">- -
- When the Google API is used, the google visualization tools needs to be - loaded. This is not needed when using a JSON Array as data type. -
-google.load("visualization", "1"); -google.setOnLoadCallback(drawTimeline); -function drawTimeline() { - // load data and create the timeline here -} -- -The class name of the Timeline is
links.Timeline
-var timeline = new links.Timeline(container);- -After being loaded, the timeline can be drawn via the method
draw
,
-provided with data and options.
-timeline.draw(data, options);-
- where data is a DataTable
or a JSON Array
,
- and options is a name-value map in the JSON format.
-
- The Timeline stores a link to the original data table, and applies changes
- made from within the Timeline to this data table.
- When the data table is changed externally, the Timeline can be updated by executing
- redraw()
. The Timeline can be linked to an other/new table
- via draw(data)
(without providing options).
- When the website layout has been changed or resized, use checkResize()
- to update the size of the timeline.
-
- The Timeline supports two data types: a JSON Array or a Google DataTable. -
- - -
- The Timeline supports a JSON Array as data format. The Array must contain
- JSON Objects with fields start
, end
(optional),
- content
, group
(optional), and
- className
(optional).
- When JSON is used as data format (instead of Google DataTable), the
- Timeline can be used offline.
-
- A table is constructed as: -
- --var data = []; - -data.push({ - 'start': new Date(2010, 7, 15), - 'end': new Date(2010, 8, 2), // end is optional - 'content': 'Trajectory A' - // Optional: a field 'group' - // Optional: a field 'className' - // Optional: a field 'editable' -}); -- - -
- The Timeline requires a data table with two required and three optional - columns. - It is possible to use a Google DataTable or DataView, and the data - can be queried from an external data source using Google Query. - Note that the needed Google API is only available online and cannot be - downloaded for offline usage. -
- -- A table is constructed as: -
- --var data = new google.visualization.DataTable(); -data.addColumn('datetime', 'start'); -data.addColumn('datetime', 'end'); -data.addColumn('string', 'content'); -// Optional: a column 'group' -// Optional: a column 'className' -// Optional: a column 'editable' -// Optional: a column 'type' - -data.addRow([new Date(2010,7,15), new Date(2010,8,2), "Trajectory A"]); -// ... -- -
- The Timeline checks the type of the columns by first reading the - column id, and if not provided read the column label. - If both the column id's and label's are undefined, the Timeline will use - column 0 as start, column 1 as end, column 2 as content, column 3 as group, - column 4 as className, and column 5 as editable. -
- -- The fields are defined as: -
- -Name | -Type | -Required | -Description | -
---|---|---|---|
start | -Date | -yes* | -
- The start date of the event, for example new Date(2010,2,23) .
- - - Remark: Months in the JavaScript Date object are zero-based,- * If the type is set to floatingRange, the start is optional. - |
-
end | -Date | -no | -The end date of the event. The end date is optional, and can be left null .
- If end date is provided, the event is displayed as a range.
- If not, the event is displayed as a box. |
-
content | -String | -yes | -The contents of the event. This can be plain text or html code. | -
group | -any type | -no | -This field is optional. When the group column is provided,
- all events with the same group are placed on one line.
- A vertical axis is displayed showing the groups.
- Grouping events can be useful for example when showing availability of multiple
- people, rooms, or other resources next to each other. - - If none of the events has a group, - the events will be drawn on top of the horizontal axis. - When events overlap each other, the will be stacked automatically. - |
-
className | -String | -no | -This field is optional. A className can be used to give events
- and individual css style. For example, when an event has className
- 'red', one can define a css style
-
- .red {
- background-color: red;
- border-color: dark-red;
- }
- .
- Depending on how you apply your css styles, it may be needed to
- (re)define the style for selected events
- (class timeline-event-selected ).
- More details on how to style events can be found in the section
- Styles.
- |
-
editable | -Boolean | -no | -This field is optional. By providing the field editable, events - can be made editable or read-only on an individual basis. - If editable is true, the event can be edited and deleted. - The editable setting of an individual event overwrites the global - setting options.editable. So when the Timeline as a whole is - read-only, individual events may be made editable, and vice versa. - | -
type | -String | -no | -Type of the event, optional. Available values: "box", "range", "dot", "floatingRange". - By default, the type is "range" for events having a start and end - date, and "box" for events with a start date. This default can be - overwritten by the global option "style". - | -
getItem
, these custom fields
-will be returned along with the known fields.
-
-
-- Options can be used to customize the timeline. Options are defined as a JSON object. - All options are optional. -
- --var options = { - "width": "100%", - "height": "auto", - "style": "box", - "editable": true -}; -- -
- The following options are available. -
- -Name | -Type | -Default | -Description | -
---|---|---|---|
animate | -boolean | -true | -When true, events are moved animated when resizing or moving them. - This is very pleasing for the eye, but does require more computational power. | -
animateZoom | -boolean | -true | -When true, events are moved animated when zooming the Timeline. - This looks cool, but does require more computational power. | -
axisOnTop | -boolean | -false | -If false (default), the horizontal axis is drawn at the bottom. - If true, the axis is drawn on top. | -
cluster | -boolean | -false | -If true, events will be clustered together when zooming out.
- This keeps the Timeline clear and fast also with a larger amount of
- events.
- experimental - |
-
clusterMaxItems | -Number | -5 | -The maximum quantity of items that can be shown outside a cluster. Setting a low value is useful to limit the
- height of the container element. This value must be at least one.
- experimental - |
-
customStackOrder | -function | -none | -Provide a custom sort function to order the items. The order of the - items is determining the way they are stacked. The function - customStackOrder is called with two parameters, both of type - `timeline.Item`. - | -
box.align | -String | -"center" | -Alignment of items with style "box". Available values are - "center" (default), "left", or "right"). | -
dragAreaWidth | -Number | -10 | -The width of the drag areas in pixels. - When an event range is selected, it has a drag area on the left and right - side, with which the start or end time of the even can be manipulated. | -
editable | -boolean | -false | -If true, the events can be edited, created and deleted.
- Events can only be editable when th option selectable is true
- (default). When editable is true, the Timeline can fire events
- change , changed , edit ,
- add , delete .
- - This global setting editable can be overwritten for individual events - by providing the events with a field editable, - as described in section Data Format. - |
-
end | -Date | -none | -The initial end date for the axis of the timeline. - If not provided, the latest date present in the events is taken as end date. | -
eventMargin | -int | -10 | -The minimal margin in pixels between events. | -
eventMarginAxis | -int | -10 | -The minimal margin in pixels between events and the horizontal axis. | -
groupsChangeable | -boolean | -false | -If true, items can be moved from one group to another.
- Only applicable when groups are used. See also option timeChangeable . |
-
groupsOnRight | -boolean | -false | -If false, the groups legend is drawn at the left side of the timeline. - If true, the groups legend is drawn on the right side. | -
groupsOrder | -boolean | function | -true | -- Allows to customize the way groups are ordered. - When true (default), groups will be ordered alphabetically. - When false, groups will not be ordered at all. - When a function, groups will be ordered using this sort function. - While sorting, the sort function is called with two groups to compare - as parameters, and the function must return 0 when a and b are equal, - a value larger than 0 when a is larger than b, - and a value smaller than 0 when a is smaller than b. - | -
groupsWidth | -string | -none | -By default, the width of the groups legend is adjusted to the group - names. A fixed width can be set for the groups legend by specifying the - groupsWidth as a string, for example "200px". | -
groupMinHeight | -int | -0 | -The minimum height of each individual group even if they have no items. - The group height is set as the greatest value between items height and the groupMinHeight. | -
height | -string | -"auto" | -The height of the timeline in pixels, as a percentage, or "auto".
- When the height is set to "auto", the height of the timeline is automatically
- adjusted to fit the contents. If not, it is possible that events get stacked
- so high, that they are not visible in the timeline.
- When height is set to "auto", a minimum height can be specified with the
- option minHeight .
- |
-
locale | -string | -"en" | -Choose locale for the Timeline.
- The Built-in locale is english (en ).
- More locales are available in the file timeline-locales.js :
-
-
|
-
max | -Date | -none | -Set a maximum Date for the visible range. - It will not be possible to move beyond this maximum. - The maximum date itself is excluded. - | -
min | -Date | -none | -Set a minimum Date for the visible range. - It will not be possible to move beyond this minimum. - | -
minHeight | -Number | -0 | -Specifies a minimum height for the Timeline in pixels.
- Useful when height is set to "auto" .
- |
-
moveable | -boolean | -true | -If true, the timeline is movable.
- When the timeline moved, the rangechange events are fired.
- |
-
scale | -links.Timeline.StepDate.SCALE | -none | -Set a custom scale. Automatic scaling will be disabled.
- Both options scale and step must be set.
-
- For example scale=SCALE.MINUTES and step=5 will result in minor steps of
- 5 minutes, and major steps of an hour.
- Available scales: MILLISECOND , SECOND ,
- MINUTE , HOUR , WEEKDAY ,
- DAY , MONTH , YEAR .
- As step size, choose for example 1, 2, 5, or 10.
- |
-
selectable | -boolean | -true | -If true, the events on the timeline are selectable.
- When an event is selected, the select event is fired.
- |
-
snapEvents | -boolean | -true | -If true, the start and end of an event will be snapped nice integer - values when moving or resizing the event. - | -
stackEvents | -boolean | -true | -If true, the events are stacked above each other to prevent overlapping events. - This option cannot be used in combination with grouped events. | -
start | -Date | -none | -The initial start date for the axis of the timeline. - If not provided, the earliest date present in the events is taken as start date. | -
step | -number | -none | -See option scale .
- |
-
style | -string | -"box" | -Specifies the style for the timeline events.
- Choose from "dot" or "box".
- Note that the content of the events may contain additional html formatting.
- It is possible to implement custom styles using the method addItemType .
- |
-
showCurrentTime | -boolean | -true | -If true, the timeline shows a red, vertical line displaying the current
- time. This time can be synchronized with a server via the method
- setCurrentTime . |
-
showCustomTime | -boolean | -false | -If true, the timeline shows a blue vertical line displaying a custom - time. This line can be dragged by the user. - The custom time can be utilized to show a state in the past or in the future. - When the custom time bar is dragged by the user, an event is triggered, on - which the contents of the timeline can be changed in to the state at that - moment in time. - | -
showMajorLabels | -boolean | -true | -By default, the timeline shows both minor and major date labels on the
- horizontal axis.
- For example the minor labels show minutes and the major labels show hours.
- When showMajorLabels is false , no major labels
- are shown. |
-
showMinorLabels | -boolean | -true | -By default, the timeline shows both minor and major date labels on the
- horizontal axis.
- For example the minor labels show minutes and the major labels show hours.
- When showMinorLabels is false , no minor labels
- are shown. When both showMajorLabels and
- showMinorLabels are false, no horizontal axis will be
- visible. |
-
showButtonNew | -boolean | -false | -Show the button "Create new event" in the a navigation menu. - Only applicable when option `editable` is true. | -
showNavigation | -boolean | -false | -Show a navigation menu with buttons to move and zoom the timeline. - The zoom buttons are only visible when option `zoomable` is true, - and and move buttons are only visible when option `moveable` is true. - | -
timeChangeable | -boolean | -true | -If false, items can not be moved or dragged horizontally (neither start time nor end time is changable).
- This is useful when items should be editable but can only be changed regarding group or content (typical use case: scheduling events). See also the option groupsChangeable . |
-
unselectable | -boolean | -true | -If true, a selected event on the timeline can be un unselected.
- If false, a selected event cannot be unselected unless when selecting
- an other event. This means that as soon as the Timeline has a selected
- event, it will remain in a state where there is always an event selected.
- See also the option selectable .
- |
-
width | -string | -"100%" | -The width of the timeline in pixels or as a percentage. | -
zoomable | -boolean | -true | -If true, the timeline is zoomable.
- When the timeline is zoomed, the rangechange event is fired.
- |
-
zoomMax | -Number | -315360000000000 | -Set a maximum zoom interval for the visible range in milliseconds. - It will not be possible to zoom out further than this maximum. - Default value equals about 10000 years. - | -
zoomMin | -Number | -10 | -Set a minimum zoom interval for the visible range in milliseconds. - It will not be possible to zoom in further than this minimum. - | -
- The Timeline supports the following methods. -
- -Method | -Return Type | -Description | -
---|---|---|
addItem(properties) | -none | -Add an item to the Timeline.
- The provided parameter properties is an Object,
- containing parameters start (Date), end (Date),
- content (String), group (String),
- className (String), and editable (Boolean).
- Parameters start and content are required,
- the others are optional.
- |
-
addItemType(typeName, typeFactory) | -none | -Add a custom item type to the Timeline, extending the built-in types
- "box" and "dot".
- Parameter typeName is a String,
- and typeFactory is a constructor implementing the abstract
- prototype links.Timeline.Item . To use a custom type,
- specify the option style with the name of the custom type.
- - - See - example25_new_item_type.html - for an example, or study the implementations of links.Timeline.ItemBox ,
- links.Timeline.ItemRange ,
- and links.Timeline.ItemDot in the sourcecode of the Timeline.
- |
-
cancelAdd() | -none | -An add event can be canceled by calling the method
- cancelAdd from within an event listener that listens for
- add events. This is useful when additions need to be approved.
- |
-
cancelChange() | -none | -A changed event can be canceled by calling the method
- cancelChange from within an event listener that listens for
- changed events. This is useful when changes need to be approved.
- |
-
cancelDelete() | -none | -A delete event can be canceled by calling the method
- cancelDelete from within an event listener that listens for
- delete events. This is useful when deletions need to be
- approved.
- |
-
changeItem(index, properties) | -none | -Change properties of an existing item in the Timeline.
- index (Number) is the index of the item.
- The provided parameter properties is an Object,
- and can contain parameters start (Date),
- end (Date), content (String),
- group (String), className (String),
- and editable (Boolean).
- |
-
checkResize() | -none | -Check if the timeline container is resized, and if so, resize the timeline. - Useful when the webpage is resized. | -
deleteAllItems() | -none | -Delete all items from the timeline. - | -
deleteItem(index, preventRender) | -none | -Delete an existing item.
- index (Number) is the index of the item.
- preventRender (Boolean) is optional parameter to prevent re-render timeline immediately after delete (for multiple deletions). Default is false.
- |
-
draw(data, options) | -none | -Loads data, sets options, adjusts the visible range,
- and lastly (re)draws the Timeline.
- data is a Google DataTable or a JSON Array.
- options is an (optional) JSON Object containing values for options.
- |
-
getCluster(index) | -Object | -Retrieve the properties of a single cluster. The returned object can
- contain parameters start (Date), and items that has
- each item contained in the cluster. Each item have the same properties as in a call to
- getItem .
- |
getCustomTime() | -Date | -Retrieve the custom time.
- Only applicable when the option showCustomTime is true.
-
- time is a Date object.
- |
-
getData() | -Google DataTable or JSON Array | -Retrieve the current datatable from the Timeline. | -
getItem(index) | -Object | -Retrieve the properties of a single item. The returned object can
- contain parameters start (Date), end (Date),
- content (String), group (String). |
-
getSelection() | -Array of selection elements | -Standard getSelection() implementation.
- Returns an array with one or multiple selections. Each selection contains
- the property row (if an item is selected) or cluster if
- a cluster is selected.
- |
-
getVisibleChartRange() | -An object with start and end properties |
- Returns an object with start and end properties,
- which each one of them is a Date object,
- representing the currently visible time range. |
-
getVisibleItems(start, end) | -Array of row indices | -Returns an array of item indices whose range or value falls within the
- start and end properties, which each one of
- them is a Date object.
- |
-
redraw() | -none | -Redraw the timeline. - Reloads the (linked) data table and redraws the timeline when resized. - See also the method checkResize. | -
setAutoScale(enable) | -none | -Enable or disable autoscaling.
- If enable true or not defined, autoscaling is enabled.
- If false, autoscaling is disabled.
- |
-
setCurrentTime(time) | -none | -Adjust the current time of the timeline. This can for example be
- changed to match the time of a server or a time offset of another time zone.
- time is a Date object.
- |
-
setCustomTime(time) | -none | -Adjust the custom time in the timeline.
- Only applicable when the option showCustomTime is true.
- time is a Date object.
- |
-
setData(data) | -none | -Set new data in the Timeline. All settings (such as visible range) stay
- unchanged, and the timeline must be redrawn afterwards with the method
- redraw .
- data is a Google DataTable object or a JSON Array.
- |
-
setSelection(selection) | -none | -Standard setSelection(selection) implementation.
- selection is an array with selection elements. The timeline
- accepts only one selection element, which must have the property row .
- The visible chart range will be moved such that the selected event is placed in the middle.
- To unselect all items, use set selection with an empty array.
- Example usage: timeline.setSelection([{row: 3}]); .
- |
-
setSize(width, height) | -none | -Parameters width and height are strings,
- containing a new size for the timeline. Size can be provided in pixels
- or in percentages. |
-
setScale(scale, step) | -none | -Set a custom scale. Automatic scaling will be disabled.
- For example setScale(SCALE.MINUTES, 5) will result in minor steps of
- 5 minutes, and major steps of an hour.
- Available scales: MILLISECOND , SECOND ,
- MINUTE , HOUR , WEEKDAY ,
- DAY , MONTH , YEAR .
- As step size, choose for example 1, 2, 5, or 10.
- |
-
setVisibleChartRange(start, end) | -none | -Sets the visible range (zoom) to the specified range. - Accepts two parameters of type Date that represent the first and last times - of the wanted selected visible range. - Set start to null to include everything from the earliest date to end; - set end to null to include everything from start to the last date. | -
setVisibleChartRangeNow() | -none | -Move the visible range such that the current time is located in the
- center of the timeline. This method does not trigger a
- rangechange event. |
-
setVisibleChartRangeAuto() | -none | -Adjust the visible time range such that all events are visible. | -
move(moveFactor) | -none | -Move the timeline the given movefactor to the left or right.
- Start and end date will be adjusted, and the timeline will be redrawn.
- For example, try moveFactor = 0.1 or -0.1.
- moveFactor is a Number that determines the moving amount.
- A positive value will move right, a negative value will move left.
- |
-
zoom(zoomFactor, zoomAroundDate) | -none | -Zoom the timeline the given zoomfactor in or out. Start and end date
- will be adjusted, and the timeline will be redrawn.
- You can optionally give a date around which to zoom.
- For example, try zoomfactor = 0.1 or -0.1.
- zoomFactor is a Number that determines the zooming amount.
- Positive value will zoom in, negative value will zoom out.
- zoomAroundDate is a Date around which to zoom and it is optional.
- |
-
- The Timeline fires events after an event is selected, the visible range changed, - or when an event is changed. The events can be cached by creating a listener. - Listeners can be registered using the event messages from the Google API - or event messages from the CHAP Links library. -
-
- Here an example on how to catch a select
event.
-
-function onselect() { - var sel = mytimeline.getSelection(); - if (sel.length) { - if (sel[0].row != undefined) { - var row = sel[0].row; - document.title = "event " + row + " selected"; - } - } -} - -google.visualization.events.addListener(mytimeline, 'select', onselect); -// Or, when not using the Google API: -// links.events.addListener(mytimeline, 'select', onselect); -- -
- The following events are available. -
- -name | -Description | -Properties | -
---|---|---|
add | -An event is about the be added.
- Fired after the user has clicked the button "Add event" and created a new
- event by clicking or moving an event into the Timeline.
- - The selected row can be retrieved via the method getSelection ,
- and new start and end data can be read in the according row in the data table.
- - The add event can be canceled by calling the method
- cancelAdd from within the event listener. This is useful
- when additions need to be approved.
- |
- - none - | -
change | -The properties of an event are changing.
- Fired repeatedly while the user is modifying the start date or end
- date of an event by moving (dragging) the event in the Timeline.
- - The selected row can be retrieved via the method getSelection ,
- and new start and end data can be read in the according row in the data table.
- - |
- - none - | -
changed | -The properties of an event changed.
- Fired after the user modified the start date or end date of an event
- by moving (dragging) the event in the Timeline.
- - The selected row can be retrieved via the method getSelection ,
- and new start and end data can be read in the according row in the data table.
- - The changed event can be canceled by calling the method
- cancelChange from within the event listener. This is useful
- when changes need to be approved.
- |
- - none - | -
edit | -An event is about to be edited.
- This event is fired when the user double clicks on an event.
- The selected row can be retrieved via the method getSelection .
- |
- - none - | -
delete | -An event is about to be deleted.
- Fired after the user clicked the "Delete Event" button on the right of
- an event.
- - The selected row can be retrieved via the method getSelection ,
- and new start and end data can be read in the according row in the data table.
- - The delete event can be canceled by calling the method
- cancelDelete from within the event listener. This is useful
- when deletions need to be approved.
- |
- - none - | -
rangechange | -Visible range is changing. Fired repeatedly while the user is modifying
- the visible time by moving (dragging) the timeline, or by zooming (scrolling),
- but not after a call to setVisibleChartRange or
- setRangeToCurrentTime methods.
- The new range can be retrieved by calling getVisibleChartRange
- method. |
-
-
|
-
rangechanged | -Visible range has been changed. Fired once after the user has modified
- the visible time by moving (dragging) the timeline, or by zooming (scrolling),
- but not after a call to setVisibleChartRange or
- setRangeToCurrentTime methods.
- The new range can be retrieved by calling getVisibleChartRange
- method. |
-
-
|
-
ready | -The chart is ready for external method calls. - If you want to interact with the chart, and call methods after you draw it, - you should set up a listener for this event before you call the draw method, - and call them only after the event was fired. | -none | -
select | -When the user clicks on an event or a cluster on the timeline,
- the corresponding row in the data table is selected.
- The visualization then fires this event.
- - The selected row or cluster can be retrieved via the method getSelection .
- |
- none | -
timechange | -The custom time bar is changing. Fired repeatedly when the user is
- dragging the blue custom time bar, but not after a call to the
- setCustomTime method.
- The new custom time can be retrieved by calling getCustomTime
- method. |
-
-
|
-
timechanged | -The custom time bar has been changed. Fired once after the user has
- dragged the blue custom time bar, but not after a call to the
- setCustomTime method. The new custom time can be retrieved by
- calling getCustomTime method. |
-
-
|
-
- All parts of the Timeline have a class name and a default css style. - The styles can be overwritten, which enables full customization of the layout - of the Timeline. -
- -For example, to change the border and background color of all events, include the - following code inside the head of your html code or in a separate stylesheet.
--<style> -div.timeline-event { - border-color: orange; - background-color: yellow; -} -</style> -- - -
- The events of type box
, range
, and dot
- have the following structure.
-
Class name | -Description | -Default style | -
---|---|---|
div.timeline-frame | -The frame contains the canvas. - It determines the size and the border of the Timeline. | -
- border: 1px solid #BEBEBE; - overflow: hidden; - |
-
div.timeline-axis | -A horizontal line forms the axis. | -
- border-color: #BEBEBE; - border-width: 1px; - border-top-style: solid; - |
-
div.timeline-axis-grid | -The axis has a horizontal grid. | -
- border-left-style: solid; - border-width: 1px; - |
-
div.timeline-axis-grid-minor | -The axis has two grid lines: minor and major. When the scale is in days, - each day gets a minor grid line, and each month a major grid line. | -
- border-color: #e5e5e5; - |
-
div.timeline-axis-grid-major | -See div.timeline-axis-grid-major |
-
- border-color: #bfbfbf; - |
-
div.timeline-axis-text | -Both div.timeline-axis-text-minor and div.timeline-axis-text-major have
- also the class div.timeline-axis-text . Use this class to set font styles
- for both classes at once. |
-
- color: #4D4D4D; - padding: 3px; - white-space: nowrap; - |
-
div.timeline-axis-text-minor | -The axis has two grid types: minor and major. When the scale is in days, - each day gets a minor text, and each month a major text. | -- | -
div.timeline-axis-text-major | -See div.timeline-axis-text-minor |
- - | -
div.timeline-event | -All different events (box, dot, range, line) have the class div.timeline-event. - Use this class for example to set background and foreground colors. | -
- color: #1A1A1A; - border-color: #97B0F8; - background-color: #D5DDF6; - |
-
div.timeline-event-selected | -All different events (box, dot, range, line) get the class - div.timeline-event-selected when they are currently selected. - Use this class to visually show the currently selected event. | -
- border-color: #FFC200; - background-color: #FFF785; - z-index: 999; - |
-
div.timeline-event-box | -By default (option style="box"), events with only a start-date are drawn as a Box, having - this class name. | -
- text-align: center; - border-style: solid; - border-width: 1px; - border-radius: 5px; - -moz-border-radius: 5px; - |
-
div.timeline-event-dot | -Divs with the class div.timeline-event-dot are used
- when the option style="dot" is used: a dot is drawn left from the event text.
- Dots are also drawn with style="box", to draw the dot at the axis below each event. |
-
- border-style: solid; - border-width: 5px; - border-radius: 5px; - -moz-border-radius: 5px; - |
-
div.timeline-event-range | -A range is drawn when an event has both start date and end date provided. | -
- border-width: 1px; - border-style: solid; - border-radius: 2px; - -moz-border-radius: 2px; - |
-
div.timeline-event-range-drag-left | -Drag area on the left side of the range | -
- cursor: w-resize; - z-index: 1000; - |
-
div.timeline-event-range-drag-right | -Drag area on the right side of the range | -
- cursor: e-resize; - z-index: 1000; - |
-
div.timeline-event-line | -When option style="box" is used (the default value), each event
- is drawn as a box with a vertical line towards the axis. This line has the
- class div.timeline-event-line . |
-
- border-left-width: 1px; - border-left-style: solid; - |
-
div.timeline-event-content | -Each events from class box, dot, and range contain a div with class
- div.timeline-event-content . This class contains the text of the event. |
-
- margin: 5px; - white-space: nowrap; - overflow: hidden; - |
-
div.timeline-groups-axis | -The right border of the vertical axis showing the different event groups. | -
- border-color: #BEBEBE; - border-width: 1px; - |
-
div.timeline-groups-text | -The text labels of the event groups on the vertical axis. | -
- color: #4D4D4D; - padding-left: 10px; - padding-right: 10px; - |
-
div.timeline-currenttime | -The vertical line showing the current time. | -
- border-color: #FF7F6E; - border-right-width: 2px; - border-right-style: solid; - |
-
div.timeline-navigation | -The navigation menu. Only visible when option showNavigation
- is true. |
-
- font-family: arial; - font-size: 20px; - font-weight: bold; - color: gray; - border: 1px solid #BEBEBE; - background-color: #F5F5F5; - border-radius: 5px; - -moz-border-radius: 5px; - |
-
div.timeline-navigation-new, div.timeline-navigation-delete, - div.timeline-navigation-zoom-in, div.timeline-navigation-zoom-out, - div.timeline-navigation-move-left, div.timeline-navigation-move-right | -The menu buttons in the navigation menu. - You can change the images to your own icon set. - | -
- cursor: pointer; - margin: 2px 10px; - float: left; - text-decoration: none; - border-color: #BEBEBE; - |
-
div.timeline-navigation-new | -Menu button to create a new event. - | -
- background: url('img/16/new.png') no-repeat center; - |
-
div.timeline-navigation-delete | -Button to delete a selected event. - The button is displayed at the top right of a selected event. - | -
- padding: 0px; - padding-left: 5px; - background: url('img/16/delete.png') no-repeat center; - |
-
div.timeline-navigation-zoom-in | -Button to zoom in on the timeline. - | -
- background: url('img/16/zoomin.png') no-repeat center; - |
-
div.timeline-navigation-zoom-out | -Button to zoom out on the timeline. - | -
- background: url('img/16/zoomout.png') no-repeat center; - |
-
div.timeline-navigation-move-left | -Button to move the timeline to the right, - such that more of the left side of the timeline becomes visible. - | -
- background: url('img/16/moveleft.png') no-repeat center; - |
-
div.timeline-navigation-move-right | -Button to move the timeline to the left, - such that more of the right side of the timeline becomes visible. - | -
- background: url('img/16/moveright.png') no-repeat center; - |
-
- Most of jQuery widgets are theme aware in terms of jQuery Themeroller CSS framework. - If you would like to make the Timeline aware of jQuery themes, please include in your page a Themeroller theme of your choice - (ui.theme.css) and the CSS file timeline-theme.css. The file timeline.css should not be included. - These two files are enough to style the Timeline according to the selected theme. -
- - -- All code and data are processed and rendered in the browser. No data is sent to any server. -
- -- - - - - - -
- - - - - - - - - - - -Method Attributes | -Method Name and Description | -
---|---|
- |
- forEach(fn, scope)
-
-
- |
-
- |
- indexOf(obj)
-
-
- |
-
- - - - - - -
- - - - - - - - -Field Attributes | -Field Name and Description | -
---|---|
- |
-
- listeners
-
- Remove all registered event listeners
- |
-
-
-
-
-
-
-
-
Defined in: timeline.js.
-
-
Constructor Attributes | -Constructor Name and Description | -
---|---|
- |
-
- links.Timeline.ClusterGenerator(timeline)
-
-
- |
-
Method Attributes | -Method Name and Description | -
---|---|
- |
- clear()
-
- Clear all cached clusters and data, and initialize all variables
- |
-
- |
- clearCache()
-
- Clear the cached clusters
- |
-
- |
- getClusters(scale, maxItems)
-
- Cluster the events which are too close together
- |
-
- |
- setData(items, options)
-
- Set the items to be clustered.
- |
-
- |
- updateData()
-
- Update the current data set: clear cache, and recalculate the clustering for
-the current level
- |
-
-
-
-
-
-
-
-
Defined in: timeline.js.
-
-
Constructor Attributes | -Constructor Name and Description | -
---|---|
- |
-
- links.Timeline.Item(data, options)
-
-
- |
-
Method Attributes | -Method Name and Description | -
---|---|
- |
- createDOM()
-
- Creates the DOM for the item, depending on its type
- |
-
- |
- getImageUrls(imageUrls)
-
- Append all image urls present in the items DOM to the provided array
- |
-
- |
- getLeft(timeline)
-
- Calculate the left position of the item
- |
-
- |
- getRight(timeline)
-
- Calculate the right position of the item
- |
-
- |
- getWidth(timeline)
-
- Calculate the width of the item
- |
-
- |
- hideDOM(container)
-
- Remove the items DOM from the current HTML container
- |
-
- |
- isRendered()
-
- Check if the item is drawn in the timeline (i.e.
- |
-
- |
- isVisible(start, end)
-
- Check if the item is located in the visible area of the timeline, and
-not part of a cluster
- |
-
- |
- reflow()
-
- Reflow the Item: retrieve its actual size from the DOM
- |
-
- |
- select()
-
- Select the item
- |
-
- |
- setPosition(left, right)
-
- Reposition the item
- |
-
- |
- showDOM(container)
-
- Append the items DOM to the given HTML container.
- |
-
- |
- unselect()
-
- Unselect the item
- |
-
- |
- updateDOM()
-
- Update the DOM of the item.
- |
-
- |
- updatePosition(timeline)
-
- Reposition the item, recalculate its left, top, and width, using the current
-range of the timeline and the timeline options.
- |
-
-
-
Extends
- links.Timeline.Item.
-
-
-
-
-
-
Defined in: timeline.js.
-
-
Constructor Attributes | -Constructor Name and Description | -
---|---|
- |
-
- links.Timeline.ItemBox(data, options)
-
-
- |
-
Method Attributes | -Method Name and Description | -
---|---|
- |
- createDOM()
-
- Creates the DOM for the item, depending on its type
- |
-
- |
- getLeft(timeline)
-
- Calculate the left position of the item
- |
-
- |
- getRight(timeline)
-
- Calculate the right position of the item
- |
-
- |
- hideDOM()
-
- Remove the items DOM from the current HTML container, but keep the DOM in
-memory
- |
-
- |
- isVisible(start, end)
-
- Check if the item is visible in the timeline, and not part of a cluster
- |
-
- |
- reflow()
-
- Reflow the Item: retrieve its actual size from the DOM
- |
-
- |
- select()
-
- Select the item
- |
-
- |
- setPosition(left, right)
-
- Reposition the item
- |
-
- |
- showDOM(container)
-
- Append the items DOM to the given HTML container.
- |
-
- |
- unselect()
-
- Unselect the item
- |
-
- |
- updateDOM()
-
- Update the DOM of the item.
- |
-
- |
- updatePosition(timeline)
-
- Reposition the item, recalculate its left, top, and width, using the current
-range of the timeline and the timeline options.
- |
-
-
-
Extends
- links.Timeline.Item.
-
-
-
-
-
-
Defined in: timeline.js.
-
-
Constructor Attributes | -Constructor Name and Description | -
---|---|
- |
-
- links.Timeline.ItemDot(data, options)
-
-
- |
-
Method Attributes | -Method Name and Description | -
---|---|
- |
- createDOM()
-
- Creates the DOM for the item, depending on its type
- |
-
- |
- getLeft(timeline)
-
- Calculate the left position of the item
- |
-
- |
- getRight(timeline)
-
- Calculate the right position of the item
- |
-
- |
- hideDOM()
-
- Remove the items DOM from the current HTML container
- |
-
- |
- isVisible(start, end)
-
- Check if the item is visible in the timeline, and not part of a cluster.
- |
-
- |
- reflow()
-
- Reflow the Item: retrieve its actual size from the DOM
- |
-
- |
- select()
-
- Select the item
- |
-
- |
- setPosition(left, right)
-
- Reposition the item
- |
-
- |
- showDOM(container)
-
- Append the items DOM to the given HTML container.
- |
-
- |
- unselect()
-
- Unselect the item
- |
-
- |
- updateDOM()
-
- Update the DOM of the item.
- |
-
- |
- updatePosition(timeline)
-
- Reposition the item, recalculate its left, top, and width, using the current
-range of the timeline and the timeline options.
- |
-
-
-
Extends
- links.Timeline.Item.
-
-
-
-
-
-
Defined in: timeline.js.
-
-
Constructor Attributes | -Constructor Name and Description | -
---|---|
- |
-
- links.Timeline.ItemFloatingRange(data, options)
-
-
- |
-
Method Attributes | -Method Name and Description | -
---|---|
- |
- createDOM()
-
- Creates the DOM for the item, depending on its type
- |
-
- |
- getLeft(timeline)
-
- Calculate the left position of the item
- |
-
- |
- getRight(timeline)
-
- Calculate the right position of the item
- |
-
- |
- getWidth(timeline)
-
- Calculate the width of the item
- |
-
- |
- hideDOM()
-
- Remove the items DOM from the current HTML container
-The DOM will be kept in memory
- |
-
- |
- isVisible(start, end)
-
- Check if the item is visible in the timeline, and not part of a cluster
- |
-
- |
- select()
-
- Select the item
- |
-
- |
- setPosition(left, right)
-
- Reposition the item
- |
-
- |
- showDOM(container)
-
- Append the items DOM to the given HTML container.
- |
-
- |
- unselect()
-
- Unselect the item
- |
-
- |
- updateDOM()
-
- Update the DOM of the item.
- |
-
- |
- updatePosition(timeline)
-
- Reposition the item, recalculate its left, top, and width, using the current
-range of the timeline and the timeline options.
- |
-
-
-
Extends
- links.Timeline.Item.
-
-
-
-
-
-
Defined in: timeline.js.
-
-
Constructor Attributes | -Constructor Name and Description | -
---|---|
- |
-
- links.Timeline.ItemRange(data, options)
-
-
- |
-
Method Attributes | -Method Name and Description | -
---|---|
- |
- createDOM()
-
- Creates the DOM for the item, depending on its type
- |
-
- |
- getLeft(timeline)
-
- Calculate the left position of the item
- |
-
- |
- getRight(timeline)
-
- Calculate the right position of the item
- |
-
- |
- getWidth(timeline)
-
- Calculate the width of the item
- |
-
- |
- hideDOM()
-
- Remove the items DOM from the current HTML container
-The DOM will be kept in memory
- |
-
- |
- isVisible(start, end)
-
- Check if the item is visible in the timeline, and not part of a cluster
- |
-
- |
- select()
-
- Select the item
- |
-
- |
- setPosition(left, right)
-
- Reposition the item
- |
-
- |
- showDOM(container)
-
- Append the items DOM to the given HTML container.
- |
-
- |
- unselect()
-
- Unselect the item
- |
-
- |
- updateDOM()
-
- Update the DOM of the item.
- |
-
- |
- updatePosition(timeline)
-
- Reposition the item, recalculate its left, top, and width, using the current
-range of the timeline and the timeline options.
- |
-
-
-
-
-
-
-
-
Defined in: timeline.js.
-
-
Constructor Attributes | -Constructor Name and Description | -
---|---|
- |
-
- links.Timeline.StepDate(start, end, minimumStep)
-
-
- |
-
Method Attributes | -Method Name and Description | -
---|---|
- |
- addZeros(value, len)
-
- Add leading zeros to the given value to match the desired length.
- |
-
- |
- end()
-
- Check if the end date is reached
- |
-
- |
- getCurrent()
-
- Get the current datetime
- |
-
- |
- getLabelMajor(options, date)
-
- Returns formatted text for the major axislabel, depending on the current
-date and the scale.
- |
-
- |
- getLabelMinor(options, date)
-
- Returns formatted text for the minor axislabel, depending on the current
-date and the scale.
- |
-
- |
- isMajor()
-
- Check if the current step is a major step (for example when the step
-is DAY, a major step is each first day of the MONTH)
- |
-
- |
- next()
-
- Do the next step
- |
-
- |
- roundToMinor()
-
- Round the current date to the first minor date value
-This must be executed once when the current date is set to start Date
- |
-
- |
- setAutoScale(enable)
-
- Enable or disable autoscaling
- |
-
- |
- setMinimumStep(minimumStep)
-
- Automatically determine the scale that bests fits the provided minimum step
- |
-
- |
- setRange(start, end, minimumStep)
-
- Set a new range
-If minimumStep is provided, the step size is chosen as close as possible
-to the minimumStep but larger than minimumStep.
- |
-
- |
- setScale(newScale, newStep)
-
- Set a custom scale.
- |
-
- |
- snap(date)
-
- Snap a date to a rounded value.
- |
-
- |
- start()
-
- Set the step iterator to the start date.
- |
-
-
-
-
-
-
-
-
Defined in: timeline.js.
-
-
Constructor Attributes | -Constructor Name and Description | -
---|---|
- |
-
- links.Timeline(container, options)
-
-
- |
-
Method Attributes | -Method Name and Description | -
---|---|
<static> | -
- links.Timeline.addClassName(elem, className)
-
- Adds one or more className's to the given elements style
- |
-
<static> | -
- links.Timeline.addEventListener(element, action, listener, useCapture)
-
- Add and event listener.
- |
-
- |
- addItem(itemData, preventRender)
-
- Add a new item.
- |
-
- |
- addItems(itemsData, preventRender)
-
- Add new items.
- |
-
- |
- addItemType(typeName, typeFactory)
-
- Add new type of items
- |
-
- |
- applyRange(start, end, zoomAroundDate)
-
- Apply a visible range.
- |
-
- |
- cancelAdd()
-
- Cancel creation of a new item
-This method can be called insed an event listener which catches the "new"
-event.
- |
-
- |
- cancelChange()
-
- Cancel a change item
-This method can be called insed an event listener which catches the "change"
-event.
- |
-
- |
- cancelDelete()
-
- Cancel deletion of an item
-This method can be called insed an event listener which catches the "delete"
-event.
- |
-
- |
- changeItem(index, itemData, preventRender)
-
- Edit an item
- |
-
- |
- checkResize()
-
- Check if the timeline is resized, and if so, redraw the timeline.
- |
-
- |
- clearItems()
-
- This method clears the (internal) array this.items in a safe way: neatly
-cleaning up the DOM, and accompanying arrays this.renderedItems and
-the created clusters.
- |
-
<static> | -
- links.Timeline.clone(object)
-
- Shallow clone an object
- |
-
- |
- clusterItems()
-
- Cluster the events
- |
-
- |
- collision(item1, item2, margin)
-
- Test if the two provided items collide
-The items must have parameters left, right, top, and bottom.
- |
-
- |
- confirmDeleteItem(index)
-
- Delete an item after a confirmation.
- |
-
- |
- createItem(itemData)
-
- Create an item object, containing all needed parameters
- |
-
- |
- deleteAllItems()
-
- Delete all items
- |
-
- |
- deleteGroups()
-
- Delete all groups
- |
-
- |
- deleteItem(index, preventRender)
-
- Delete an item
- |
-
- |
- draw(data, options)
-
- Main drawing logic.
- |
-
- |
- filterItems()
-
- Filter the visible events
- |
-
- |
- finalItemsPosition(items, groupBase, group)
-
-
- |
-
<static> | -
- links.Timeline.getAbsoluteLeft(elem)
-
- Retrieve the absolute left value of a DOM element
- |
-
<static> | -
- links.Timeline.getAbsoluteTop(elem)
-
- Retrieve the absolute top value of a DOM element
- |
-
- |
- getCluster(index)
-
- Retrieve the properties of a cluster.
- |
-
- |
- getClusterIndex(element)
-
- Find the cluster index from a given HTML element
-If no cluster index is found, undefined is returned
- |
-
- |
- getCurrentTime()
-
- Get current time.
- |
-
- |
- getCustomTime()
-
- Retrieve the current custom time.
- |
-
- |
- getData()
-
- Return the original data table.
- |
-
- |
- getDataRange(withMargin)
-
- Get the date range of the items.
- |
-
- |
- getGroup(groupName)
-
- Get a group by the group name.
- |
-
- |
- getGroupFromHeight(height)
-
- Find the group from a given height in the timeline
- |
-
- |
- getGroupName(groupObj)
-
- Get the group name from a group object.
- |
-
- |
- getItem(index)
-
- Retrieve the properties of an item.
- |
-
- |
- getItemIndex(element)
-
- Find the item index from a given HTML element
-If no item index is found, undefined is returned
- |
-
- |
- getItemsByGroup(items)
-
-
- |
-
- |
- getOptions()
-
- Get options for the timeline.
- |
-
<static> | -
- links.Timeline.getPageX(event)
-
- Get the absolute, horizontal mouse position from an event.
- |
-
<static> | -
- links.Timeline.getPageY(event)
-
- Get the absolute, vertical mouse position from an event.
- |
-
- |
- getSelection()
-
- Retrieve the currently selected event
- |
-
<static> | -
- links.Timeline.getTarget(event)
-
- Get HTML element which is the target of the event
- |
-
- |
-
- Retrieve the current visible range in the timeline.
- |
-
- |
- getVisibleItems(start, end)
-
- Find all elements within the start and end range
-If no element is found, returns an empty array
- |
-
- |
- initialItemsPosition(items, groupBase)
-
-
- |
-
<static> | -
- links.Timeline.isArray(obj)
-
- Check if given object is a Javascript Array
- |
-
- |
- isEditable(item)
-
- Check whether a given item is editable
- |
-
- |
- isSelected(index)
-
- Check if an item is currently selected
- |
-
<static> | -
- links.Timeline.mapColumnIds(dataTable)
-
- Retrieve a map with the column indexes of the columns by column name.
- |
-
- |
- move(moveFactor)
-
- Move the timeline the given movefactor to the left or right.
- |
-
- |
- onDblClick(event)
-
- Double click event occurred for an item
- |
-
- |
- onMouseDown(event)
-
- Start a moving operation inside the provided parent element
- |
-
- |
- onMouseMove(event)
-
- Perform moving operating.
- |
-
- |
- onMouseUp(event)
-
- Stop moving operating.
- |
-
- |
- onMouseWheel(event)
-
- Event handler for mouse wheel event, used to zoom the timeline
-Code from http://adomas.org/javascript-mouse-wheel/
- |
-
- |
- onTouchEnd(event)
-
- Event handler for touchend event on mobile devices
- |
-
- |
- onTouchMove(event)
-
- Event handler for touchmove event on mobile devices
- |
-
- |
- onTouchStart(event)
-
- Event handler for touchstart event on mobile devices
- |
-
<static> | -
- links.Timeline.parseJSONDate(date)
-
- parse a JSON date
- |
-
<static> | -
- links.Timeline.preventDefault(event)
-
- Cancels the event if it is cancelable, without stopping further propagation of the event.
- |
-
- |
- recalcConversion()
-
- Calculate the factor and offset to convert a position on screen to the
-corresponding date and vice versa.
- |
-
- |
- recalcItems()
-
- Recalculate item properties:
-- the height of each group.
- |
-
- |
- redraw()
-
- Redraw the timeline
-Reloads the (linked) data table and redraws the timeline when resized.
- |
-
- |
- reflowAxis()
-
- Reflow the timeline axis.
- |
-
- |
- reflowFrame()
-
- Reflow the timeline frame
- |
-
- |
- reflowGroups()
-
- Reflow the size of the groups
- |
-
- |
- reflowItems()
-
- Reflow all items, retrieve their actual size
- |
-
<static> | -
- links.Timeline.removeClassName(elem, className)
-
- Removes one or more className's from the given elements style
- |
-
<static> | -
- links.Timeline.removeEventListener(element, action, listener, useCapture)
-
- Remove an event listener from an element
- |
-
- |
- render(options)
-
- Re-render (reflow and repaint) all components of the Timeline: frame, axis,
-items, .
- |
-
- |
- repaint()
-
- Repaint all components of the Timeline
- |
-
- |
- repaintAxis()
-
- Redraw the timeline axis with minor and major labels
- |
-
- |
-
- Create characters used to determine the size of text on the axis
- |
-
- |
-
- End of overwriting HTML DOM elements of the axis.
- |
-
- |
-
- Repaint the horizontal line and background of the axis
- |
-
- |
- repaintAxisMajorLine(x)
-
- Create a Major line for the axis at position x
- |
-
- |
- repaintAxisMajorText(x, text)
-
- Create a Major label for the axis at position x
- |
-
- |
- repaintAxisMinorLine(x)
-
- Create a minor line for the axis at position x
- |
-
- |
- repaintAxisMinorText(x, text)
-
- Create a minor label for the axis at position x
- |
-
- |
-
- Initialize redraw of the axis.
- |
-
- |
-
- Redraw the current time bar
- |
-
- |
-
- Redraw the custom time bar
- |
-
- |
-
- Redraw the delete button, on the top right of the currently selected item
-if there is no item selected, the button is hidden.
- |
-
- |
- repaintDragAreas()
-
- Redraw the drag areas.
- |
-
- |
- repaintFrame()
-
- repaint the Timeline frame
- |
-
- |
- repaintGroups()
-
- Redraw the group labels
- |
-
- |
- repaintItems()
-
- Repaint all items
- |
-
- |
-
- Create the navigation buttons for zooming and moving
- |
-
- |
- screenToTime(x)
-
- Convert a position on screen (pixels) to a datetime
-Before this method can be used, the method calcConversionFactor must be
-executed once.
- |
-
- |
- selectCluster(index)
-
- Select an cluster by its index
- |
-
- |
- selectItem(index)
-
- Select an item by its index
- |
-
- |
- setAutoScale(enable)
-
- Enable or disable autoscaling
- |
-
- |
- setCurrentTime(time)
-
- Set current time.
- |
-
- |
- setCustomTime(time)
-
- Set custom time.
- |
-
- |
- setData(data)
-
- Set data for the timeline
- |
-
- |
- setOptions(options)
-
- Set options for the timeline.
- |
-
- |
- setScale(scale, step)
-
- Set a custom scale.
- |
-
- |
- setSelection(selection)
-
- Select an event.
- |
-
- |
- setSize(width, height)
-
- Set a new size for the timeline
- |
-
- |
- setVisibleChartRange(start, end, redraw)
-
- Set a new value for the visible range int the timeline.
- |
-
- |
-
- Change the visible chart range such that all items become visible
- |
-
- |
-
- Adjust the visible range such that the current time is located in the center
-of the timeline
- |
-
- |
- stackCalculateFinal(items)
-
- Adjust vertical positions of the events such that they don't overlap each
-other.
- |
-
- |
-
- Cancel any running animation
- |
-
- |
- stackItems(animate)
-
- Stack the items such that they don't overlap.
- |
-
- |
- stackItemsCheckOverlap(items, itemIndex, itemStart, itemEnd)
-
- Check if the destiny position of given item overlaps with any
-of the other items from index itemStart to itemEnd.
- |
-
- |
- stackMoveOneStep(currentItems, finalItems)
-
- Move the events one step in the direction of their final positions
- |
-
- |
- stackMoveToFinal(currentItems, finalItems)
-
- Move the events from their current position to the final position
- |
-
- |
- stackOrder(items)
-
- Order the items in the array this.items.
- |
-
<static> | -
- links.Timeline.stopPropagation(event)
-
- Stop event propagation
- |
-
- |
- timeToScreen(time)
-
- Convert a datetime (Date object) into a position on the screen
-Before this method can be used, the method calcConversionFactor must be
-executed once.
- |
-
- |
- trigger(event)
-
- fire an event
- |
-
- |
- unselectItem()
-
- Unselect the currently selected event (if any)
- |
-
- |
- updateData(index, values)
-
- Update the original data with changed start, end or group.
- |
-
- |
- zoom(zoomFactor, zoomAroundDate)
-
- Zoom the timeline the given zoomfactor in or out.
- |
-
1 /** - 2 * @file timeline.js - 3 * - 4 * @brief - 5 * The Timeline is an interactive visualization chart to visualize events in - 6 * time, having a start and end date. - 7 * You can freely move and zoom in the timeline by dragging - 8 * and scrolling in the Timeline. Items are optionally dragable. The time - 9 * scale on the axis is adjusted automatically, and supports scales ranging - 10 * from milliseconds to years. - 11 * - 12 * Timeline is part of the CHAP Links library. - 13 * - 14 * Timeline is tested on Firefox 3.6, Safari 5.0, Chrome 6.0, Opera 10.6, and - 15 * Internet Explorer 6+. - 16 * - 17 * @license - 18 * Licensed under the Apache License, Version 2.0 (the "License"); you may not - 19 * use this file except in compliance with the License. You may obtain a copy - 20 * of the License at - 21 * - 22 * http://www.apache.org/licenses/LICENSE-2.0 - 23 * - 24 * Unless required by applicable law or agreed to in writing, software - 25 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - 26 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - 27 * License for the specific language governing permissions and limitations under - 28 * the License. - 29 * - 30 * Copyright (c) 2011-2015 Almende B.V. - 31 * - 32 * @author Jos de Jong, <jos@almende.org> - 33 * @date 2015-03-04 - 34 * @version 2.9.1 - 35 */ - 36 - 37 /* - 38 * i18n mods by github user iktuz (https://gist.github.com/iktuz/3749287/) - 39 * added to v2.4.1 with da_DK language by @bjarkebech - 40 */ - 41 - 42 /* - 43 * TODO - 44 * - 45 * Add zooming with pinching on Android - 46 * - 47 * Bug: when an item contains a javascript onclick or a link, this does not work - 48 * when the item is not selected (when the item is being selected, - 49 * it is redrawn, which cancels any onclick or link action) - 50 * Bug: when an item contains an image without size, or a css max-width, it is not sized correctly - 51 * Bug: neglect items when they have no valid start/end, instead of throwing an error - 52 * Bug: Pinching on ipad does not work very well, sometimes the page will zoom when pinching vertically - 53 * Bug: cannot set max width for an item, like div.timeline-event-content {white-space: normal; max-width: 100px;} - 54 * Bug on IE in Quirks mode. When you have groups, and delete an item, the groups become invisible - 55 */ - 56 - 57 /** - 58 * Declare a unique namespace for CHAP's Common Hybrid Visualisation Library, - 59 * "links" - 60 */ - 61 if (typeof links === 'undefined') { - 62 links = {}; - 63 // important: do not use var, as "var links = {};" will overwrite - 64 // the existing links variable value with undefined in IE8, IE7. - 65 } - 66 - 67 - 68 /** - 69 * Ensure the variable google exists - 70 */ - 71 if (typeof google === 'undefined') { - 72 google = undefined; - 73 // important: do not use var, as "var google = undefined;" will overwrite - 74 // the existing google variable value with undefined in IE8, IE7. - 75 } - 76 - 77 - 78 - 79 // Internet Explorer 8 and older does not support Array.indexOf, - 80 // so we define it here in that case - 81 // http://soledadpenades.com/2007/05/17/arrayindexof-in-internet-explorer/ - 82 if(!Array.prototype.indexOf) { - 83 Array.prototype.indexOf = function(obj){ - 84 for(var i = 0; i < this.length; i++){ - 85 if(this[i] == obj){ - 86 return i; - 87 } - 88 } - 89 return -1; - 90 } - 91 } - 92 - 93 // Internet Explorer 8 and older does not support Array.forEach, - 94 // so we define it here in that case - 95 // https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/forEach - 96 if (!Array.prototype.forEach) { - 97 Array.prototype.forEach = function(fn, scope) { - 98 for(var i = 0, len = this.length; i < len; ++i) { - 99 fn.call(scope || this, this[i], i, this); -100 } -101 } -102 } -103 -104 -105 /** -106 * @constructor links.Timeline -107 * The timeline is a visualization chart to visualize events in time. -108 * -109 * The timeline is developed in javascript as a Google Visualization Chart. -110 * -111 * @param {Element} container The DOM element in which the Timeline will -112 * be created. Normally a div element. -113 * @param {Object} options A name/value map containing settings for the -114 * timeline. Optional. -115 */ -116 links.Timeline = function(container, options) { -117 if (!container) { -118 // this call was probably only for inheritance, no constructor-code is required -119 return; -120 } -121 -122 // create variables and set default values -123 this.dom = {}; -124 this.conversion = {}; -125 this.eventParams = {}; // stores parameters for mouse events -126 this.groups = []; -127 this.groupIndexes = {}; -128 this.items = []; -129 this.renderQueue = { -130 show: [], // Items made visible but not yet added to DOM -131 hide: [], // Items currently visible but not yet removed from DOM -132 update: [] // Items with changed data but not yet adjusted DOM -133 }; -134 this.renderedItems = []; // Items currently rendered in the DOM -135 this.clusterGenerator = new links.Timeline.ClusterGenerator(this); -136 this.currentClusters = []; -137 this.selection = undefined; // stores index and item which is currently selected -138 -139 this.listeners = {}; // event listener callbacks -140 -141 // Initialize sizes. -142 // Needed for IE (which gives an error when you try to set an undefined -143 // value in a style) -144 this.size = { -145 'actualHeight': 0, -146 'axis': { -147 'characterMajorHeight': 0, -148 'characterMajorWidth': 0, -149 'characterMinorHeight': 0, -150 'characterMinorWidth': 0, -151 'height': 0, -152 'labelMajorTop': 0, -153 'labelMinorTop': 0, -154 'line': 0, -155 'lineMajorWidth': 0, -156 'lineMinorHeight': 0, -157 'lineMinorTop': 0, -158 'lineMinorWidth': 0, -159 'top': 0 -160 }, -161 'contentHeight': 0, -162 'contentLeft': 0, -163 'contentWidth': 0, -164 'frameHeight': 0, -165 'frameWidth': 0, -166 'groupsLeft': 0, -167 'groupsWidth': 0, -168 'items': { -169 'top': 0 -170 } -171 }; -172 -173 this.dom.container = container; -174 -175 // -176 // Let's set the default options first -177 // -178 this.options = { -179 'width': "100%", -180 'height': "auto", -181 'minHeight': 0, // minimal height in pixels -182 'groupMinHeight': 0, -183 'autoHeight': true, -184 -185 'eventMargin': 10, // minimal margin between events -186 'eventMarginAxis': 20, // minimal margin between events and the axis -187 'dragAreaWidth': 10, // pixels -188 -189 'min': undefined, -190 'max': undefined, -191 'zoomMin': 10, // milliseconds -192 'zoomMax': 1000 * 60 * 60 * 24 * 365 * 10000, // milliseconds -193 -194 'moveable': true, -195 'zoomable': true, -196 'selectable': true, -197 'unselectable': true, -198 'editable': false, -199 'snapEvents': true, -200 'groupsChangeable': true, -201 'timeChangeable': true, -202 -203 'showCurrentTime': true, // show a red bar displaying the current time -204 'showCustomTime': false, // show a blue, draggable bar displaying a custom time -205 'showMajorLabels': true, -206 'showMinorLabels': true, -207 'showNavigation': false, -208 'showButtonNew': false, -209 'groupsOnRight': false, -210 'groupsOrder' : true, -211 'axisOnTop': false, -212 'stackEvents': true, -213 'animate': true, -214 'animateZoom': true, -215 'cluster': false, -216 'clusterMaxItems': 5, -217 'style': 'box', -218 'customStackOrder': false, //a function(a,b) for determining stackorder amongst a group of items. Essentially a comparator, -ve value for "a before b" and vice versa -219 -220 // i18n: Timeline only has built-in English text per default. Include timeline-locales.js to support more localized text. -221 'locale': 'en', -222 'MONTHS': ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], -223 'MONTHS_SHORT': ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], -224 'DAYS': ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], -225 'DAYS_SHORT': ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], -226 'ZOOM_IN': "Zoom in", -227 'ZOOM_OUT': "Zoom out", -228 'MOVE_LEFT': "Move left", -229 'MOVE_RIGHT': "Move right", -230 'NEW': "New", -231 'CREATE_NEW_EVENT': "Create new event" -232 }; -233 -234 // -235 // Now we can set the givenproperties -236 // -237 this.setOptions(options); -238 -239 this.clientTimeOffset = 0; // difference between client time and the time -240 // set via Timeline.setCurrentTime() -241 var dom = this.dom; -242 -243 // remove all elements from the container element. -244 while (dom.container.hasChildNodes()) { -245 dom.container.removeChild(dom.container.firstChild); -246 } -247 -248 // create a step for drawing the axis -249 this.step = new links.Timeline.StepDate(); -250 -251 // add standard item types -252 this.itemTypes = { -253 box: links.Timeline.ItemBox, -254 range: links.Timeline.ItemRange, -255 floatingRange: links.Timeline.ItemFloatingRange, -256 dot: links.Timeline.ItemDot -257 }; -258 -259 // initialize data -260 this.data = []; -261 this.firstDraw = true; -262 -263 // date interval must be initialized -264 this.setVisibleChartRange(undefined, undefined, false); -265 -266 // render for the first time -267 this.render(); -268 -269 // fire the ready event -270 var me = this; -271 setTimeout(function () { -272 me.trigger('ready'); -273 }, 0); -274 }; -275 -276 -277 /** -278 * Main drawing logic. This is the function that needs to be called -279 * in the html page, to draw the timeline. -280 * -281 * A data table with the events must be provided, and an options table. -282 * -283 * @param {google.visualization.DataTable} data -284 * The data containing the events for the timeline. -285 * Object DataTable is defined in -286 * google.visualization.DataTable -287 * @param {Object} options A name/value map containing settings for the -288 * timeline. Optional. The use of options here -289 * is deprecated. Pass timeline options in the -290 * constructor or use setOptions() -291 */ -292 links.Timeline.prototype.draw = function(data, options) { -293 if (options) { -294 console.log("WARNING: Passing options in draw() is deprecated. Pass options to the constructur or use setOptions() instead!"); -295 this.setOptions(options); -296 } -297 -298 if (this.options.selectable) { -299 links.Timeline.addClassName(this.dom.frame, "timeline-selectable"); -300 } -301 -302 // read the data -303 this.setData(data); -304 -305 if (this.firstDraw) { -306 this.setVisibleChartRangeAuto(); -307 } -308 -309 this.firstDraw = false; -310 }; -311 -312 -313 /** -314 * Set options for the timeline. -315 * Timeline must be redrawn afterwards -316 * @param {Object} options A name/value map containing settings for the -317 * timeline. Optional. -318 */ -319 links.Timeline.prototype.setOptions = function(options) { -320 if (options) { -321 // retrieve parameter values -322 for (var i in options) { -323 if (options.hasOwnProperty(i)) { -324 this.options[i] = options[i]; -325 } -326 } -327 -328 // prepare i18n dependent on set locale -329 if (typeof links.locales !== 'undefined' && this.options.locale !== 'en') { -330 var localeOpts = links.locales[this.options.locale]; -331 if(localeOpts) { -332 for (var l in localeOpts) { -333 if (localeOpts.hasOwnProperty(l)) { -334 this.options[l] = localeOpts[l]; -335 } -336 } -337 } -338 } -339 -340 // check for deprecated options -341 if (options.showButtonAdd != undefined) { -342 this.options.showButtonNew = options.showButtonAdd; -343 console.log('WARNING: Option showButtonAdd is deprecated. Use showButtonNew instead'); -344 } -345 if (options.intervalMin != undefined) { -346 this.options.zoomMin = options.intervalMin; -347 console.log('WARNING: Option intervalMin is deprecated. Use zoomMin instead'); -348 } -349 if (options.intervalMax != undefined) { -350 this.options.zoomMax = options.intervalMax; -351 console.log('WARNING: Option intervalMax is deprecated. Use zoomMax instead'); -352 } -353 -354 if (options.scale && options.step) { -355 this.step.setScale(options.scale, options.step); -356 } -357 } -358 -359 // validate options -360 this.options.autoHeight = (this.options.height === "auto"); -361 }; -362 -363 /** -364 * Get options for the timeline. -365 * -366 * @return the options object -367 */ -368 links.Timeline.prototype.getOptions = function() { -369 return this.options; -370 }; -371 -372 /** -373 * Add new type of items -374 * @param {String} typeName Name of new type -375 * @param {links.Timeline.Item} typeFactory Constructor of items -376 */ -377 links.Timeline.prototype.addItemType = function (typeName, typeFactory) { -378 this.itemTypes[typeName] = typeFactory; -379 }; -380 -381 /** -382 * Retrieve a map with the column indexes of the columns by column name. -383 * For example, the method returns the map -384 * { -385 * start: 0, -386 * end: 1, -387 * content: 2, -388 * group: undefined, -389 * className: undefined -390 * editable: undefined -391 * type: undefined -392 * } -393 * @param {google.visualization.DataTable} dataTable -394 * @type {Object} map -395 */ -396 links.Timeline.mapColumnIds = function (dataTable) { -397 var cols = {}, -398 colCount = dataTable.getNumberOfColumns(), -399 allUndefined = true; -400 -401 // loop over the columns, and map the column id's to the column indexes -402 for (var col = 0; col < colCount; col++) { -403 var id = dataTable.getColumnId(col) || dataTable.getColumnLabel(col); -404 cols[id] = col; -405 if (id == 'start' || id == 'end' || id == 'content' || id == 'group' || -406 id == 'className' || id == 'editable' || id == 'type') { -407 allUndefined = false; -408 } -409 } -410 -411 // if no labels or ids are defined, use the default mapping -412 // for start, end, content, group, className, editable, type -413 if (allUndefined) { -414 cols.start = 0; -415 cols.end = 1; -416 cols.content = 2; -417 if (colCount > 3) {cols.group = 3} -418 if (colCount > 4) {cols.className = 4} -419 if (colCount > 5) {cols.editable = 5} -420 if (colCount > 6) {cols.type = 6} -421 } -422 -423 return cols; -424 }; -425 -426 /** -427 * Set data for the timeline -428 * @param {google.visualization.DataTable | Array} data -429 */ -430 links.Timeline.prototype.setData = function(data) { -431 // unselect any previously selected item -432 this.unselectItem(); -433 -434 if (!data) { -435 data = []; -436 } -437 -438 // clear all data -439 this.stackCancelAnimation(); -440 this.clearItems(); -441 this.data = data; -442 var items = this.items; -443 this.deleteGroups(); -444 -445 if (google && google.visualization && -446 data instanceof google.visualization.DataTable) { -447 // map the datatable columns -448 var cols = links.Timeline.mapColumnIds(data); -449 -450 // read DataTable -451 for (var row = 0, rows = data.getNumberOfRows(); row < rows; row++) { -452 items.push(this.createItem({ -453 'start': ((cols.start != undefined) ? data.getValue(row, cols.start) : undefined), -454 'end': ((cols.end != undefined) ? data.getValue(row, cols.end) : undefined), -455 'content': ((cols.content != undefined) ? data.getValue(row, cols.content) : undefined), -456 'group': ((cols.group != undefined) ? data.getValue(row, cols.group) : undefined), -457 'className': ((cols.className != undefined) ? data.getValue(row, cols.className) : undefined), -458 'editable': ((cols.editable != undefined) ? data.getValue(row, cols.editable) : undefined), -459 'type': ((cols.type != undefined) ? data.getValue(row, cols.type) : undefined) -460 })); -461 } -462 } -463 else if (links.Timeline.isArray(data)) { -464 // read JSON array -465 for (var row = 0, rows = data.length; row < rows; row++) { -466 var itemData = data[row]; -467 var item = this.createItem(itemData); -468 items.push(item); -469 } -470 } -471 else { -472 throw "Unknown data type. DataTable or Array expected."; -473 } -474 -475 // prepare data for clustering, by filtering and sorting by type -476 if (this.options.cluster) { -477 this.clusterGenerator.setData(this.items); -478 } -479 -480 this.render({ -481 animate: false -482 }); -483 }; -484 -485 /** -486 * Return the original data table. -487 * @return {google.visualization.DataTable | Array} data -488 */ -489 links.Timeline.prototype.getData = function () { -490 return this.data; -491 }; -492 -493 -494 /** -495 * Update the original data with changed start, end or group. -496 * -497 * @param {Number} index -498 * @param {Object} values An object containing some of the following parameters: -499 * {Date} start, -500 * {Date} end, -501 * {String} content, -502 * {String} group -503 */ -504 links.Timeline.prototype.updateData = function (index, values) { -505 var data = this.data, -506 prop; -507 -508 if (google && google.visualization && -509 data instanceof google.visualization.DataTable) { -510 // update the original google DataTable -511 var missingRows = (index + 1) - data.getNumberOfRows(); -512 if (missingRows > 0) { -513 data.addRows(missingRows); -514 } -515 -516 // map the column id's by name -517 var cols = links.Timeline.mapColumnIds(data); -518 -519 // merge all fields from the provided data into the current data -520 for (prop in values) { -521 if (values.hasOwnProperty(prop)) { -522 var col = cols[prop]; -523 if (col == undefined) { -524 // create new column -525 var value = values[prop]; -526 var valueType = 'string'; -527 if (typeof(value) == 'number') {valueType = 'number';} -528 else if (typeof(value) == 'boolean') {valueType = 'boolean';} -529 else if (value instanceof Date) {valueType = 'datetime';} -530 col = data.addColumn(valueType, prop); -531 } -532 data.setValue(index, col, values[prop]); -533 -534 // TODO: correctly serialize the start and end Date to the desired type (Date, String, or Number) -535 } -536 } -537 } -538 else if (links.Timeline.isArray(data)) { -539 // update the original JSON table -540 var row = data[index]; -541 if (row == undefined) { -542 row = {}; -543 data[index] = row; -544 } -545 -546 // merge all fields from the provided data into the current data -547 for (prop in values) { -548 if (values.hasOwnProperty(prop)) { -549 row[prop] = values[prop]; -550 -551 // TODO: correctly serialize the start and end Date to the desired type (Date, String, or Number) -552 } -553 } -554 } -555 else { -556 throw "Cannot update data, unknown type of data"; -557 } -558 }; -559 -560 /** -561 * Find the item index from a given HTML element -562 * If no item index is found, undefined is returned -563 * @param {Element} element -564 * @return {Number | undefined} index -565 */ -566 links.Timeline.prototype.getItemIndex = function(element) { -567 var e = element, -568 dom = this.dom, -569 frame = dom.items.frame, -570 items = this.items, -571 index = undefined; -572 -573 // try to find the frame where the items are located in -574 while (e.parentNode && e.parentNode !== frame) { -575 e = e.parentNode; -576 } -577 -578 if (e.parentNode === frame) { -579 // yes! we have found the parent element of all items -580 // retrieve its id from the array with items -581 for (var i = 0, iMax = items.length; i < iMax; i++) { -582 if (items[i].dom === e) { -583 index = i; -584 break; -585 } -586 } -587 } -588 -589 return index; -590 }; -591 -592 -593 /** -594 * Find the cluster index from a given HTML element -595 * If no cluster index is found, undefined is returned -596 * @param {Element} element -597 * @return {Number | undefined} index -598 */ -599 links.Timeline.prototype.getClusterIndex = function(element) { -600 var e = element, -601 dom = this.dom, -602 frame = dom.items.frame, -603 clusters = this.clusters, -604 index = undefined; -605 -606 if (this.clusters) { -607 // try to find the frame where the clusters are located in -608 while (e.parentNode && e.parentNode !== frame) { -609 e = e.parentNode; -610 } -611 -612 if (e.parentNode === frame) { -613 // yes! we have found the parent element of all clusters -614 // retrieve its id from the array with clusters -615 for (var i = 0, iMax = clusters.length; i < iMax; i++) { -616 if (clusters[i].dom === e) { -617 index = i; -618 break; -619 } -620 } -621 } -622 } -623 -624 return index; -625 }; -626 -627 /** -628 * Find all elements within the start and end range -629 * If no element is found, returns an empty array -630 * @param start time -631 * @param end time -632 * @return Array itemsInRange -633 */ -634 links.Timeline.prototype.getVisibleItems = function (start, end) { -635 var items = this.items; -636 var itemsInRange = []; -637 -638 if (items) { -639 for (var i = 0, iMax = items.length; i < iMax; i++) { -640 var item = items[i]; -641 if (item.end) { -642 // Time range object // NH use getLeft and getRight here -643 if (start <= item.start && item.end <= end) { -644 itemsInRange.push({"row": i}); -645 } -646 } else { -647 // Point object -648 if (start <= item.start && item.start <= end) { -649 itemsInRange.push({"row": i}); -650 } -651 } -652 } -653 } -654 -655 // var sel = []; -656 // if (this.selection) { -657 // sel.push({"row": this.selection.index}); -658 // } -659 // return sel; -660 -661 return itemsInRange; -662 }; -663 -664 -665 /** -666 * Set a new size for the timeline -667 * @param {string} width Width in pixels or percentage (for example "800px" -668 * or "50%") -669 * @param {string} height Height in pixels or percentage (for example "400px" -670 * or "30%") -671 */ -672 links.Timeline.prototype.setSize = function(width, height) { -673 if (width) { -674 this.options.width = width; -675 this.dom.frame.style.width = width; -676 } -677 if (height) { -678 this.options.height = height; -679 this.options.autoHeight = (this.options.height === "auto"); -680 if (height !== "auto" ) { -681 this.dom.frame.style.height = height; -682 } -683 } -684 -685 this.render({ -686 animate: false -687 }); -688 }; -689 -690 -691 /** -692 * Set a new value for the visible range int the timeline. -693 * Set start undefined to include everything from the earliest date to end. -694 * Set end undefined to include everything from start to the last date. -695 * Example usage: -696 * myTimeline.setVisibleChartRange(new Date("2010-08-22"), -697 * new Date("2010-09-13")); -698 * @param {Date} start The start date for the timeline. optional -699 * @param {Date} end The end date for the timeline. optional -700 * @param {boolean} redraw Optional. If true (default) the Timeline is -701 * directly redrawn -702 */ -703 links.Timeline.prototype.setVisibleChartRange = function(start, end, redraw) { -704 var range = {}; -705 if (!start || !end) { -706 // retrieve the date range of the items -707 range = this.getDataRange(true); -708 } -709 -710 if (!start) { -711 if (end) { -712 if (range.min && range.min.valueOf() < end.valueOf()) { -713 // start of the data -714 start = range.min; -715 } -716 else { -717 // 7 days before the end -718 start = new Date(end.valueOf()); -719 start.setDate(start.getDate() - 7); -720 } -721 } -722 else { -723 // default of 3 days ago -724 start = new Date(); -725 start.setDate(start.getDate() - 3); -726 } -727 } -728 -729 if (!end) { -730 if (range.max) { -731 // end of the data -732 end = range.max; -733 } -734 else { -735 // 7 days after start -736 end = new Date(start.valueOf()); -737 end.setDate(end.getDate() + 7); -738 } -739 } -740 -741 // prevent start Date <= end Date -742 if (end <= start) { -743 end = new Date(start.valueOf()); -744 end.setDate(end.getDate() + 7); -745 } -746 -747 // limit to the allowed range (don't let this do by applyRange, -748 // because that method will try to maintain the interval (end-start) -749 var min = this.options.min ? this.options.min : undefined; // date -750 if (min != undefined && start.valueOf() < min.valueOf()) { -751 start = new Date(min.valueOf()); // date -752 } -753 var max = this.options.max ? this.options.max : undefined; // date -754 if (max != undefined && end.valueOf() > max.valueOf()) { -755 end = new Date(max.valueOf()); // date -756 } -757 -758 this.applyRange(start, end); -759 -760 if (redraw == undefined || redraw == true) { -761 this.render({ -762 animate: false -763 }); // TODO: optimize, no reflow needed -764 } -765 else { -766 this.recalcConversion(); -767 } -768 }; -769 -770 -771 /** -772 * Change the visible chart range such that all items become visible -773 */ -774 links.Timeline.prototype.setVisibleChartRangeAuto = function() { -775 var range = this.getDataRange(true); -776 this.setVisibleChartRange(range.min, range.max); -777 }; -778 -779 /** -780 * Adjust the visible range such that the current time is located in the center -781 * of the timeline -782 */ -783 links.Timeline.prototype.setVisibleChartRangeNow = function() { -784 var now = new Date(); -785 -786 var diff = (this.end.valueOf() - this.start.valueOf()); -787 -788 var startNew = new Date(now.valueOf() - diff/2); -789 var endNew = new Date(startNew.valueOf() + diff); -790 this.setVisibleChartRange(startNew, endNew); -791 }; -792 -793 -794 /** -795 * Retrieve the current visible range in the timeline. -796 * @return {Object} An object with start and end properties -797 */ -798 links.Timeline.prototype.getVisibleChartRange = function() { -799 return { -800 'start': new Date(this.start.valueOf()), -801 'end': new Date(this.end.valueOf()) -802 }; -803 }; -804 -805 /** -806 * Get the date range of the items. -807 * @param {boolean} [withMargin] If true, 5% of whitespace is added to the -808 * left and right of the range. Default is false. -809 * @return {Object} range An object with parameters min and max. -810 * - {Date} min is the lowest start date of the items -811 * - {Date} max is the highest start or end date of the items -812 * If no data is available, the values of min and max -813 * will be undefined -814 */ -815 links.Timeline.prototype.getDataRange = function (withMargin) { -816 var items = this.items, -817 min = undefined, // number -818 max = undefined; // number -819 -820 if (items) { -821 for (var i = 0, iMax = items.length; i < iMax; i++) { -822 var item = items[i], -823 start = item.start != undefined ? item.start.valueOf() : undefined, -824 end = item.end != undefined ? item.end.valueOf() : start; -825 -826 if (start != undefined) { -827 min = (min != undefined) ? Math.min(min.valueOf(), start.valueOf()) : start; -828 } -829 -830 if (end != undefined) { -831 max = (max != undefined) ? Math.max(max.valueOf(), end.valueOf()) : end; -832 } -833 } -834 } -835 -836 if (min && max && withMargin) { -837 // zoom out 5% such that you have a little white space on the left and right -838 var diff = (max - min); -839 min = min - diff * 0.05; -840 max = max + diff * 0.05; -841 } -842 -843 return { -844 'min': min != undefined ? new Date(min) : undefined, -845 'max': max != undefined ? new Date(max) : undefined -846 }; -847 }; -848 -849 /** -850 * Re-render (reflow and repaint) all components of the Timeline: frame, axis, -851 * items, ... -852 * @param {Object} [options] Available options: -853 * {boolean} renderTimesLeft Number of times the -854 * render may be repeated -855 * 5 times by default. -856 * {boolean} animate takes options.animate -857 * as default value -858 */ -859 links.Timeline.prototype.render = function(options) { -860 var frameResized = this.reflowFrame(); -861 var axisResized = this.reflowAxis(); -862 var groupsResized = this.reflowGroups(); -863 var itemsResized = this.reflowItems(); -864 var resized = (frameResized || axisResized || groupsResized || itemsResized); -865 -866 // TODO: only stackEvents/filterItems when resized or changed. (gives a bootstrap issue). -867 // if (resized) { -868 var animate = this.options.animate; -869 if (options && options.animate != undefined) { -870 animate = options.animate; -871 } -872 -873 this.recalcConversion(); -874 this.clusterItems(); -875 this.filterItems(); -876 this.stackItems(animate); -877 this.recalcItems(); -878 -879 // TODO: only repaint when resized or when filterItems or stackItems gave a change? -880 var needsReflow = this.repaint(); -881 -882 // re-render once when needed (prevent endless re-render loop) -883 if (needsReflow) { -884 var renderTimesLeft = options ? options.renderTimesLeft : undefined; -885 if (renderTimesLeft == undefined) { -886 renderTimesLeft = 5; -887 } -888 if (renderTimesLeft > 0) { -889 this.render({ -890 'animate': options ? options.animate: undefined, -891 'renderTimesLeft': (renderTimesLeft - 1) -892 }); -893 } -894 } -895 }; -896 -897 /** -898 * Repaint all components of the Timeline -899 * @return {boolean} needsReflow Returns true if the DOM is changed such that -900 * a reflow is needed. -901 */ -902 links.Timeline.prototype.repaint = function() { -903 var frameNeedsReflow = this.repaintFrame(); -904 var axisNeedsReflow = this.repaintAxis(); -905 var groupsNeedsReflow = this.repaintGroups(); -906 var itemsNeedsReflow = this.repaintItems(); -907 this.repaintCurrentTime(); -908 this.repaintCustomTime(); -909 -910 return (frameNeedsReflow || axisNeedsReflow || groupsNeedsReflow || itemsNeedsReflow); -911 }; -912 -913 /** -914 * Reflow the timeline frame -915 * @return {boolean} resized Returns true if any of the frame elements -916 * have been resized. -917 */ -918 links.Timeline.prototype.reflowFrame = function() { -919 var dom = this.dom, -920 options = this.options, -921 size = this.size, -922 resized = false; -923 -924 // Note: IE7 has issues with giving frame.clientWidth, therefore I use offsetWidth instead -925 var frameWidth = dom.frame ? dom.frame.offsetWidth : 0, -926 frameHeight = dom.frame ? dom.frame.clientHeight : 0; -927 -928 resized = resized || (size.frameWidth !== frameWidth); -929 resized = resized || (size.frameHeight !== frameHeight); -930 size.frameWidth = frameWidth; -931 size.frameHeight = frameHeight; -932 -933 return resized; -934 }; -935 -936 /** -937 * repaint the Timeline frame -938 * @return {boolean} needsReflow Returns true if the DOM is changed such that -939 * a reflow is needed. -940 */ -941 links.Timeline.prototype.repaintFrame = function() { -942 var needsReflow = false, -943 dom = this.dom, -944 options = this.options, -945 size = this.size; -946 -947 // main frame -948 if (!dom.frame) { -949 dom.frame = document.createElement("DIV"); -950 dom.frame.className = "timeline-frame ui-widget ui-widget-content ui-corner-all"; -951 dom.container.appendChild(dom.frame); -952 needsReflow = true; -953 } -954 -955 var height = options.autoHeight ? -956 (size.actualHeight + "px") : -957 (options.height || "100%"); -958 var width = options.width || "100%"; -959 needsReflow = needsReflow || (dom.frame.style.height != height); -960 needsReflow = needsReflow || (dom.frame.style.width != width); -961 dom.frame.style.height = height; -962 dom.frame.style.width = width; -963 -964 // contents -965 if (!dom.content) { -966 // create content box where the axis and items will be created -967 dom.content = document.createElement("DIV"); -968 dom.content.className = "timeline-content"; -969 dom.frame.appendChild(dom.content); -970 -971 var timelines = document.createElement("DIV"); -972 timelines.style.position = "absolute"; -973 timelines.style.left = "0px"; -974 timelines.style.top = "0px"; -975 timelines.style.height = "100%"; -976 timelines.style.width = "0px"; -977 dom.content.appendChild(timelines); -978 dom.contentTimelines = timelines; -979 -980 var params = this.eventParams, -981 me = this; -982 if (!params.onMouseDown) { -983 params.onMouseDown = function (event) {me.onMouseDown(event);}; -984 links.Timeline.addEventListener(dom.content, "mousedown", params.onMouseDown); -985 } -986 if (!params.onTouchStart) { -987 params.onTouchStart = function (event) {me.onTouchStart(event);}; -988 links.Timeline.addEventListener(dom.content, "touchstart", params.onTouchStart); -989 } -990 if (!params.onMouseWheel) { -991 params.onMouseWheel = function (event) {me.onMouseWheel(event);}; -992 links.Timeline.addEventListener(dom.content, "mousewheel", params.onMouseWheel); -993 } -994 if (!params.onDblClick) { -995 params.onDblClick = function (event) {me.onDblClick(event);}; -996 links.Timeline.addEventListener(dom.content, "dblclick", params.onDblClick); -997 } -998 -999 needsReflow = true; -1000 } -1001 dom.content.style.left = size.contentLeft + "px"; -1002 dom.content.style.top = "0px"; -1003 dom.content.style.width = size.contentWidth + "px"; -1004 dom.content.style.height = size.frameHeight + "px"; -1005 -1006 this.repaintNavigation(); -1007 -1008 return needsReflow; -1009 }; -1010 -1011 /** -1012 * Reflow the timeline axis. Calculate its height, width, positioning, etc... -1013 * @return {boolean} resized returns true if the axis is resized -1014 */ -1015 links.Timeline.prototype.reflowAxis = function() { -1016 var resized = false, -1017 dom = this.dom, -1018 options = this.options, -1019 size = this.size, -1020 axisDom = dom.axis; -1021 -1022 var characterMinorWidth = (axisDom && axisDom.characterMinor) ? axisDom.characterMinor.clientWidth : 0, -1023 characterMinorHeight = (axisDom && axisDom.characterMinor) ? axisDom.characterMinor.clientHeight : 0, -1024 characterMajorWidth = (axisDom && axisDom.characterMajor) ? axisDom.characterMajor.clientWidth : 0, -1025 characterMajorHeight = (axisDom && axisDom.characterMajor) ? axisDom.characterMajor.clientHeight : 0, -1026 axisHeight = (options.showMinorLabels ? characterMinorHeight : 0) + -1027 (options.showMajorLabels ? characterMajorHeight : 0); -1028 -1029 var axisTop = options.axisOnTop ? 0 : size.frameHeight - axisHeight, -1030 axisLine = options.axisOnTop ? axisHeight : axisTop; -1031 -1032 resized = resized || (size.axis.top !== axisTop); -1033 resized = resized || (size.axis.line !== axisLine); -1034 resized = resized || (size.axis.height !== axisHeight); -1035 size.axis.top = axisTop; -1036 size.axis.line = axisLine; -1037 size.axis.height = axisHeight; -1038 size.axis.labelMajorTop = options.axisOnTop ? 0 : axisLine + -1039 (options.showMinorLabels ? characterMinorHeight : 0); -1040 size.axis.labelMinorTop = options.axisOnTop ? -1041 (options.showMajorLabels ? characterMajorHeight : 0) : -1042 axisLine; -1043 size.axis.lineMinorTop = options.axisOnTop ? size.axis.labelMinorTop : 0; -1044 size.axis.lineMinorHeight = options.showMajorLabels ? -1045 size.frameHeight - characterMajorHeight: -1046 size.frameHeight; -1047 if (axisDom && axisDom.minorLines && axisDom.minorLines.length) { -1048 size.axis.lineMinorWidth = axisDom.minorLines[0].offsetWidth; -1049 } -1050 else { -1051 size.axis.lineMinorWidth = 1; -1052 } -1053 if (axisDom && axisDom.majorLines && axisDom.majorLines.length) { -1054 size.axis.lineMajorWidth = axisDom.majorLines[0].offsetWidth; -1055 } -1056 else { -1057 size.axis.lineMajorWidth = 1; -1058 } -1059 -1060 resized = resized || (size.axis.characterMinorWidth !== characterMinorWidth); -1061 resized = resized || (size.axis.characterMinorHeight !== characterMinorHeight); -1062 resized = resized || (size.axis.characterMajorWidth !== characterMajorWidth); -1063 resized = resized || (size.axis.characterMajorHeight !== characterMajorHeight); -1064 size.axis.characterMinorWidth = characterMinorWidth; -1065 size.axis.characterMinorHeight = characterMinorHeight; -1066 size.axis.characterMajorWidth = characterMajorWidth; -1067 size.axis.characterMajorHeight = characterMajorHeight; -1068 -1069 var contentHeight = Math.max(size.frameHeight - axisHeight, 0); -1070 size.contentLeft = options.groupsOnRight ? 0 : size.groupsWidth; -1071 size.contentWidth = Math.max(size.frameWidth - size.groupsWidth, 0); -1072 size.contentHeight = contentHeight; -1073 -1074 return resized; -1075 }; -1076 -1077 /** -1078 * Redraw the timeline axis with minor and major labels -1079 * @return {boolean} needsReflow Returns true if the DOM is changed such -1080 * that a reflow is needed. -1081 */ -1082 links.Timeline.prototype.repaintAxis = function() { -1083 var needsReflow = false, -1084 dom = this.dom, -1085 options = this.options, -1086 size = this.size, -1087 step = this.step; -1088 -1089 var axis = dom.axis; -1090 if (!axis) { -1091 axis = {}; -1092 dom.axis = axis; -1093 } -1094 if (!size.axis.properties) { -1095 size.axis.properties = {}; -1096 } -1097 if (!axis.minorTexts) { -1098 axis.minorTexts = []; -1099 } -1100 if (!axis.minorLines) { -1101 axis.minorLines = []; -1102 } -1103 if (!axis.majorTexts) { -1104 axis.majorTexts = []; -1105 } -1106 if (!axis.majorLines) { -1107 axis.majorLines = []; -1108 } -1109 -1110 if (!axis.frame) { -1111 axis.frame = document.createElement("DIV"); -1112 axis.frame.style.position = "absolute"; -1113 axis.frame.style.left = "0px"; -1114 axis.frame.style.top = "0px"; -1115 dom.content.appendChild(axis.frame); -1116 } -1117 -1118 // take axis offline -1119 dom.content.removeChild(axis.frame); -1120 -1121 axis.frame.style.width = (size.contentWidth) + "px"; -1122 axis.frame.style.height = (size.axis.height) + "px"; -1123 -1124 // the drawn axis is more wide than the actual visual part, such that -1125 // the axis can be dragged without having to redraw it each time again. -1126 var start = this.screenToTime(0); -1127 var end = this.screenToTime(size.contentWidth); -1128 -1129 // calculate minimum step (in milliseconds) based on character size -1130 if (size.axis.characterMinorWidth) { -1131 this.minimumStep = this.screenToTime(size.axis.characterMinorWidth * 6) - -1132 this.screenToTime(0); -1133 -1134 step.setRange(start, end, this.minimumStep); -1135 } -1136 -1137 var charsNeedsReflow = this.repaintAxisCharacters(); -1138 needsReflow = needsReflow || charsNeedsReflow; -1139 -1140 // The current labels on the axis will be re-used (much better performance), -1141 // therefore, the repaintAxis method uses the mechanism with -1142 // repaintAxisStartOverwriting, repaintAxisEndOverwriting, and -1143 // this.size.axis.properties is used. -1144 this.repaintAxisStartOverwriting(); -1145 -1146 step.start(); -1147 var xFirstMajorLabel = undefined; -1148 var max = 0; -1149 while (!step.end() && max < 1000) { -1150 max++; -1151 var cur = step.getCurrent(), -1152 x = this.timeToScreen(cur), -1153 isMajor = step.isMajor(); -1154 -1155 if (options.showMinorLabels) { -1156 this.repaintAxisMinorText(x, step.getLabelMinor(options)); -1157 } -1158 -1159 if (isMajor && options.showMajorLabels) { -1160 if (x > 0) { -1161 if (xFirstMajorLabel == undefined) { -1162 xFirstMajorLabel = x; -1163 } -1164 this.repaintAxisMajorText(x, step.getLabelMajor(options)); -1165 } -1166 this.repaintAxisMajorLine(x); -1167 } -1168 else { -1169 this.repaintAxisMinorLine(x); -1170 } -1171 -1172 step.next(); -1173 } -1174 -1175 // create a major label on the left when needed -1176 if (options.showMajorLabels) { -1177 var leftTime = this.screenToTime(0), -1178 leftText = this.step.getLabelMajor(options, leftTime), -1179 width = leftText.length * size.axis.characterMajorWidth + 10; // upper bound estimation -1180 -1181 if (xFirstMajorLabel == undefined || width < xFirstMajorLabel) { -1182 this.repaintAxisMajorText(0, leftText, leftTime); -1183 } -1184 } -1185 -1186 // cleanup left over labels -1187 this.repaintAxisEndOverwriting(); -1188 -1189 this.repaintAxisHorizontal(); -1190 -1191 // put axis online -1192 dom.content.insertBefore(axis.frame, dom.content.firstChild); -1193 -1194 return needsReflow; -1195 }; -1196 -1197 /** -1198 * Create characters used to determine the size of text on the axis -1199 * @return {boolean} needsReflow Returns true if the DOM is changed such that -1200 * a reflow is needed. -1201 */ -1202 links.Timeline.prototype.repaintAxisCharacters = function () { -1203 // calculate the width and height of a single character -1204 // this is used to calculate the step size, and also the positioning of the -1205 // axis -1206 var needsReflow = false, -1207 dom = this.dom, -1208 axis = dom.axis, -1209 text; -1210 -1211 if (!axis.characterMinor) { -1212 text = document.createTextNode("0"); -1213 var characterMinor = document.createElement("DIV"); -1214 characterMinor.className = "timeline-axis-text timeline-axis-text-minor"; -1215 characterMinor.appendChild(text); -1216 characterMinor.style.position = "absolute"; -1217 characterMinor.style.visibility = "hidden"; -1218 characterMinor.style.paddingLeft = "0px"; -1219 characterMinor.style.paddingRight = "0px"; -1220 axis.frame.appendChild(characterMinor); -1221 -1222 axis.characterMinor = characterMinor; -1223 needsReflow = true; -1224 } -1225 -1226 if (!axis.characterMajor) { -1227 text = document.createTextNode("0"); -1228 var characterMajor = document.createElement("DIV"); -1229 characterMajor.className = "timeline-axis-text timeline-axis-text-major"; -1230 characterMajor.appendChild(text); -1231 characterMajor.style.position = "absolute"; -1232 characterMajor.style.visibility = "hidden"; -1233 characterMajor.style.paddingLeft = "0px"; -1234 characterMajor.style.paddingRight = "0px"; -1235 axis.frame.appendChild(characterMajor); -1236 -1237 axis.characterMajor = characterMajor; -1238 needsReflow = true; -1239 } -1240 -1241 return needsReflow; -1242 }; -1243 -1244 /** -1245 * Initialize redraw of the axis. All existing labels and lines will be -1246 * overwritten and reused. -1247 */ -1248 links.Timeline.prototype.repaintAxisStartOverwriting = function () { -1249 var properties = this.size.axis.properties; -1250 -1251 properties.minorTextNum = 0; -1252 properties.minorLineNum = 0; -1253 properties.majorTextNum = 0; -1254 properties.majorLineNum = 0; -1255 }; -1256 -1257 /** -1258 * End of overwriting HTML DOM elements of the axis. -1259 * remaining elements will be removed -1260 */ -1261 links.Timeline.prototype.repaintAxisEndOverwriting = function () { -1262 var dom = this.dom, -1263 props = this.size.axis.properties, -1264 frame = this.dom.axis.frame, -1265 num; -1266 -1267 // remove leftovers -1268 var minorTexts = dom.axis.minorTexts; -1269 num = props.minorTextNum; -1270 while (minorTexts.length > num) { -1271 var minorText = minorTexts[num]; -1272 frame.removeChild(minorText); -1273 minorTexts.splice(num, 1); -1274 } -1275 -1276 var minorLines = dom.axis.minorLines; -1277 num = props.minorLineNum; -1278 while (minorLines.length > num) { -1279 var minorLine = minorLines[num]; -1280 frame.removeChild(minorLine); -1281 minorLines.splice(num, 1); -1282 } -1283 -1284 var majorTexts = dom.axis.majorTexts; -1285 num = props.majorTextNum; -1286 while (majorTexts.length > num) { -1287 var majorText = majorTexts[num]; -1288 frame.removeChild(majorText); -1289 majorTexts.splice(num, 1); -1290 } -1291 -1292 var majorLines = dom.axis.majorLines; -1293 num = props.majorLineNum; -1294 while (majorLines.length > num) { -1295 var majorLine = majorLines[num]; -1296 frame.removeChild(majorLine); -1297 majorLines.splice(num, 1); -1298 } -1299 }; -1300 -1301 /** -1302 * Repaint the horizontal line and background of the axis -1303 */ -1304 links.Timeline.prototype.repaintAxisHorizontal = function() { -1305 var axis = this.dom.axis, -1306 size = this.size, -1307 options = this.options; -1308 -1309 // line behind all axis elements (possibly having a background color) -1310 var hasAxis = (options.showMinorLabels || options.showMajorLabels); -1311 if (hasAxis) { -1312 if (!axis.backgroundLine) { -1313 // create the axis line background (for a background color or so) -1314 var backgroundLine = document.createElement("DIV"); -1315 backgroundLine.className = "timeline-axis"; -1316 backgroundLine.style.position = "absolute"; -1317 backgroundLine.style.left = "0px"; -1318 backgroundLine.style.width = "100%"; -1319 backgroundLine.style.border = "none"; -1320 axis.frame.insertBefore(backgroundLine, axis.frame.firstChild); -1321 -1322 axis.backgroundLine = backgroundLine; -1323 } -1324 -1325 if (axis.backgroundLine) { -1326 axis.backgroundLine.style.top = size.axis.top + "px"; -1327 axis.backgroundLine.style.height = size.axis.height + "px"; -1328 } -1329 } -1330 else { -1331 if (axis.backgroundLine) { -1332 axis.frame.removeChild(axis.backgroundLine); -1333 delete axis.backgroundLine; -1334 } -1335 } -1336 -1337 // line before all axis elements -1338 if (hasAxis) { -1339 if (axis.line) { -1340 // put this line at the end of all childs -1341 var line = axis.frame.removeChild(axis.line); -1342 axis.frame.appendChild(line); -1343 } -1344 else { -1345 // make the axis line -1346 var line = document.createElement("DIV"); -1347 line.className = "timeline-axis"; -1348 line.style.position = "absolute"; -1349 line.style.left = "0px"; -1350 line.style.width = "100%"; -1351 line.style.height = "0px"; -1352 axis.frame.appendChild(line); -1353 -1354 axis.line = line; -1355 } -1356 -1357 axis.line.style.top = size.axis.line + "px"; -1358 } -1359 else { -1360 if (axis.line && axis.line.parentElement) { -1361 axis.frame.removeChild(axis.line); -1362 delete axis.line; -1363 } -1364 } -1365 }; -1366 -1367 /** -1368 * Create a minor label for the axis at position x -1369 * @param {Number} x -1370 * @param {String} text -1371 */ -1372 links.Timeline.prototype.repaintAxisMinorText = function (x, text) { -1373 var size = this.size, -1374 dom = this.dom, -1375 props = size.axis.properties, -1376 frame = dom.axis.frame, -1377 minorTexts = dom.axis.minorTexts, -1378 index = props.minorTextNum, -1379 label; -1380 -1381 if (index < minorTexts.length) { -1382 label = minorTexts[index] -1383 } -1384 else { -1385 // create new label -1386 var content = document.createTextNode(""); -1387 label = document.createElement("DIV"); -1388 label.appendChild(content); -1389 label.className = "timeline-axis-text timeline-axis-text-minor"; -1390 label.style.position = "absolute"; -1391 -1392 frame.appendChild(label); -1393 -1394 minorTexts.push(label); -1395 } -1396 -1397 label.childNodes[0].nodeValue = text; -1398 label.style.left = x + "px"; -1399 label.style.top = size.axis.labelMinorTop + "px"; -1400 //label.title = title; // TODO: this is a heavy operation -1401 -1402 props.minorTextNum++; -1403 }; -1404 -1405 /** -1406 * Create a minor line for the axis at position x -1407 * @param {Number} x -1408 */ -1409 links.Timeline.prototype.repaintAxisMinorLine = function (x) { -1410 var axis = this.size.axis, -1411 dom = this.dom, -1412 props = axis.properties, -1413 frame = dom.axis.frame, -1414 minorLines = dom.axis.minorLines, -1415 index = props.minorLineNum, -1416 line; -1417 -1418 if (index < minorLines.length) { -1419 line = minorLines[index]; -1420 } -1421 else { -1422 // create vertical line -1423 line = document.createElement("DIV"); -1424 line.className = "timeline-axis-grid timeline-axis-grid-minor"; -1425 line.style.position = "absolute"; -1426 line.style.width = "0px"; -1427 -1428 frame.appendChild(line); -1429 minorLines.push(line); -1430 } -1431 -1432 line.style.top = axis.lineMinorTop + "px"; -1433 line.style.height = axis.lineMinorHeight + "px"; -1434 line.style.left = (x - axis.lineMinorWidth/2) + "px"; -1435 -1436 props.minorLineNum++; -1437 }; -1438 -1439 /** -1440 * Create a Major label for the axis at position x -1441 * @param {Number} x -1442 * @param {String} text -1443 */ -1444 links.Timeline.prototype.repaintAxisMajorText = function (x, text) { -1445 var size = this.size, -1446 props = size.axis.properties, -1447 frame = this.dom.axis.frame, -1448 majorTexts = this.dom.axis.majorTexts, -1449 index = props.majorTextNum, -1450 label; -1451 -1452 if (index < majorTexts.length) { -1453 label = majorTexts[index]; -1454 } -1455 else { -1456 // create label -1457 var content = document.createTextNode(text); -1458 label = document.createElement("DIV"); -1459 label.className = "timeline-axis-text timeline-axis-text-major"; -1460 label.appendChild(content); -1461 label.style.position = "absolute"; -1462 label.style.top = "0px"; -1463 -1464 frame.appendChild(label); -1465 majorTexts.push(label); -1466 } -1467 -1468 label.childNodes[0].nodeValue = text; -1469 label.style.top = size.axis.labelMajorTop + "px"; -1470 label.style.left = x + "px"; -1471 //label.title = title; // TODO: this is a heavy operation -1472 -1473 props.majorTextNum ++; -1474 }; -1475 -1476 /** -1477 * Create a Major line for the axis at position x -1478 * @param {Number} x -1479 */ -1480 links.Timeline.prototype.repaintAxisMajorLine = function (x) { -1481 var size = this.size, -1482 props = size.axis.properties, -1483 axis = this.size.axis, -1484 frame = this.dom.axis.frame, -1485 majorLines = this.dom.axis.majorLines, -1486 index = props.majorLineNum, -1487 line; -1488 -1489 if (index < majorLines.length) { -1490 line = majorLines[index]; -1491 } -1492 else { -1493 // create vertical line -1494 line = document.createElement("DIV"); -1495 line.className = "timeline-axis-grid timeline-axis-grid-major"; -1496 line.style.position = "absolute"; -1497 line.style.top = "0px"; -1498 line.style.width = "0px"; -1499 -1500 frame.appendChild(line); -1501 majorLines.push(line); -1502 } -1503 -1504 line.style.left = (x - axis.lineMajorWidth/2) + "px"; -1505 line.style.height = size.frameHeight + "px"; -1506 -1507 props.majorLineNum ++; -1508 }; -1509 -1510 /** -1511 * Reflow all items, retrieve their actual size -1512 * @return {boolean} resized returns true if any of the items is resized -1513 */ -1514 links.Timeline.prototype.reflowItems = function() { -1515 var resized = false, -1516 i, -1517 iMax, -1518 group, -1519 groups = this.groups, -1520 renderedItems = this.renderedItems; -1521 -1522 if (groups) { // TODO: need to check if labels exists? -1523 // loop through all groups to reset the items height -1524 groups.forEach(function (group) { -1525 group.itemsHeight = group.labelHeight || 0; -1526 }); -1527 } -1528 -1529 // loop through the width and height of all visible items -1530 for (i = 0, iMax = renderedItems.length; i < iMax; i++) { -1531 var item = renderedItems[i], -1532 domItem = item.dom; -1533 group = item.group; -1534 -1535 if (domItem) { -1536 // TODO: move updating width and height into item.reflow -1537 var width = domItem ? domItem.clientWidth : 0; -1538 var height = domItem ? domItem.clientHeight : 0; -1539 resized = resized || (item.width != width); -1540 resized = resized || (item.height != height); -1541 item.width = width; -1542 item.height = height; -1543 //item.borderWidth = (domItem.offsetWidth - domItem.clientWidth - 2) / 2; // TODO: borderWidth -1544 item.reflow(); -1545 } -1546 -1547 if (group) { -1548 group.itemsHeight = Math.max(this.options.groupMinHeight,group.itemsHeight ? -1549 Math.max(group.itemsHeight, item.height) : -1550 item.height); -1551 } -1552 } -1553 -1554 return resized; -1555 }; -1556 -1557 /** -1558 * Recalculate item properties: -1559 * - the height of each group. -1560 * - the actualHeight, from the stacked items or the sum of the group heights -1561 * @return {boolean} resized returns true if any of the items properties is -1562 * changed -1563 */ -1564 links.Timeline.prototype.recalcItems = function () { -1565 var resized = false, -1566 i, -1567 iMax, -1568 item, -1569 finalItem, -1570 finalItems, -1571 group, -1572 groups = this.groups, -1573 size = this.size, -1574 options = this.options, -1575 renderedItems = this.renderedItems; -1576 -1577 var actualHeight = 0; -1578 if (groups.length == 0) { -1579 // calculate actual height of the timeline when there are no groups -1580 // but stacked items -1581 if (options.autoHeight || options.cluster) { -1582 var min = 0, -1583 max = 0; -1584 -1585 if (this.stack && this.stack.finalItems) { -1586 // adjust the offset of all finalItems when the actualHeight has been changed -1587 finalItems = this.stack.finalItems; -1588 finalItem = finalItems[0]; -1589 if (finalItem && finalItem.top) { -1590 min = finalItem.top; -1591 max = finalItem.top + finalItem.height; -1592 } -1593 for (i = 1, iMax = finalItems.length; i < iMax; i++) { -1594 finalItem = finalItems[i]; -1595 min = Math.min(min, finalItem.top); -1596 max = Math.max(max, finalItem.top + finalItem.height); -1597 } -1598 } -1599 else { -1600 item = renderedItems[0]; -1601 if (item && item.top) { -1602 min = item.top; -1603 max = item.top + item.height; -1604 } -1605 for (i = 1, iMax = renderedItems.length; i < iMax; i++) { -1606 item = renderedItems[i]; -1607 if (item.top) { -1608 min = Math.min(min, item.top); -1609 max = Math.max(max, (item.top + item.height)); -1610 } -1611 } -1612 } -1613 -1614 actualHeight = (max - min) + 2 * options.eventMarginAxis + size.axis.height; -1615 if (actualHeight < options.minHeight) { -1616 actualHeight = options.minHeight; -1617 } -1618 -1619 if (size.actualHeight != actualHeight && options.autoHeight && !options.axisOnTop) { -1620 // adjust the offset of all items when the actualHeight has been changed -1621 var diff = actualHeight - size.actualHeight; -1622 if (this.stack && this.stack.finalItems) { -1623 finalItems = this.stack.finalItems; -1624 for (i = 0, iMax = finalItems.length; i < iMax; i++) { -1625 finalItems[i].top += diff; -1626 finalItems[i].item.top += diff; -1627 } -1628 } -1629 else { -1630 for (i = 0, iMax = renderedItems.length; i < iMax; i++) { -1631 renderedItems[i].top += diff; -1632 } -1633 } -1634 } -1635 } -1636 } -1637 else { -1638 // loop through all groups to get the height of each group, and the -1639 // total height -1640 actualHeight = size.axis.height + 2 * options.eventMarginAxis; -1641 for (i = 0, iMax = groups.length; i < iMax; i++) { -1642 group = groups[i]; -1643 -1644 // -1645 // TODO: Do we want to apply a max height? how ? -1646 // -1647 var groupHeight = group.itemsHeight; -1648 resized = resized || (groupHeight != group.height); -1649 group.height = Math.max(groupHeight, options.groupMinHeight); -1650 -1651 actualHeight += groups[i].height + options.eventMargin; -1652 } -1653 -1654 // calculate top positions of the group labels and lines -1655 var eventMargin = options.eventMargin, -1656 top = options.axisOnTop ? -1657 options.eventMarginAxis + eventMargin/2 : -1658 size.contentHeight - options.eventMarginAxis + eventMargin/ 2, -1659 axisHeight = size.axis.height; -1660 -1661 for (i = 0, iMax = groups.length; i < iMax; i++) { -1662 group = groups[i]; -1663 if (options.axisOnTop) { -1664 group.top = top + axisHeight; -1665 group.labelTop = top + axisHeight + (group.height - group.labelHeight) / 2; -1666 group.lineTop = top + axisHeight + group.height + eventMargin/2; -1667 top += group.height + eventMargin; -1668 } -1669 else { -1670 top -= group.height + eventMargin; -1671 group.top = top; -1672 group.labelTop = top + (group.height - group.labelHeight) / 2; -1673 group.lineTop = top - eventMargin/2; -1674 } -1675 } -1676 -1677 resized = true; -1678 } -1679 -1680 if (actualHeight < options.minHeight) { -1681 actualHeight = options.minHeight; -1682 } -1683 resized = resized || (actualHeight != size.actualHeight); -1684 size.actualHeight = actualHeight; -1685 -1686 return resized; -1687 }; -1688 -1689 /** -1690 * This method clears the (internal) array this.items in a safe way: neatly -1691 * cleaning up the DOM, and accompanying arrays this.renderedItems and -1692 * the created clusters. -1693 */ -1694 links.Timeline.prototype.clearItems = function() { -1695 // add all visible items to the list to be hidden -1696 var hideItems = this.renderQueue.hide; -1697 this.renderedItems.forEach(function (item) { -1698 hideItems.push(item); -1699 }); -1700 -1701 // clear the cluster generator -1702 this.clusterGenerator.clear(); -1703 -1704 // actually clear the items -1705 this.items = []; -1706 }; -1707 -1708 /** -1709 * Repaint all items -1710 * @return {boolean} needsReflow Returns true if the DOM is changed such that -1711 * a reflow is needed. -1712 */ -1713 links.Timeline.prototype.repaintItems = function() { -1714 var i, iMax, item, index; -1715 -1716 var needsReflow = false, -1717 dom = this.dom, -1718 size = this.size, -1719 timeline = this, -1720 renderedItems = this.renderedItems; -1721 -1722 if (!dom.items) { -1723 dom.items = {}; -1724 } -1725 -1726 // draw the frame containing the items -1727 var frame = dom.items.frame; -1728 if (!frame) { -1729 frame = document.createElement("DIV"); -1730 frame.style.position = "relative"; -1731 dom.content.appendChild(frame); -1732 dom.items.frame = frame; -1733 } -1734 -1735 frame.style.left = "0px"; -1736 frame.style.top = size.items.top + "px"; -1737 frame.style.height = "0px"; -1738 -1739 // Take frame offline (for faster manipulation of the DOM) -1740 dom.content.removeChild(frame); -1741 -1742 // process the render queue with changes -1743 var queue = this.renderQueue; -1744 var newImageUrls = []; -1745 needsReflow = needsReflow || -1746 (queue.show.length > 0) || -1747 (queue.update.length > 0) || -1748 (queue.hide.length > 0); // TODO: reflow needed on hide of items? -1749 -1750 while (item = queue.show.shift()) { -1751 item.showDOM(frame); -1752 item.getImageUrls(newImageUrls); -1753 renderedItems.push(item); -1754 } -1755 while (item = queue.update.shift()) { -1756 item.updateDOM(frame); -1757 item.getImageUrls(newImageUrls); -1758 index = this.renderedItems.indexOf(item); -1759 if (index == -1) { -1760 renderedItems.push(item); -1761 } -1762 } -1763 while (item = queue.hide.shift()) { -1764 item.hideDOM(frame); -1765 index = this.renderedItems.indexOf(item); -1766 if (index != -1) { -1767 renderedItems.splice(index, 1); -1768 } -1769 } -1770 -1771 // reposition all visible items -1772 renderedItems.forEach(function (item) { -1773 item.updatePosition(timeline); -1774 }); -1775 -1776 // redraw the delete button and dragareas of the selected item (if any) -1777 this.repaintDeleteButton(); -1778 this.repaintDragAreas(); -1779 -1780 // put frame online again -1781 dom.content.appendChild(frame); -1782 -1783 if (newImageUrls.length) { -1784 // retrieve all image sources from the items, and set a callback once -1785 // all images are retrieved -1786 var callback = function () { -1787 timeline.render(); -1788 }; -1789 var sendCallbackWhenAlreadyLoaded = false; -1790 links.imageloader.loadAll(newImageUrls, callback, sendCallbackWhenAlreadyLoaded); -1791 } -1792 -1793 return needsReflow; -1794 }; -1795 -1796 /** -1797 * Reflow the size of the groups -1798 * @return {boolean} resized Returns true if any of the frame elements -1799 * have been resized. -1800 */ -1801 links.Timeline.prototype.reflowGroups = function() { -1802 var resized = false, -1803 options = this.options, -1804 size = this.size, -1805 dom = this.dom; -1806 -1807 // calculate the groups width and height -1808 // TODO: only update when data is changed! -> use an updateSeq -1809 var groupsWidth = 0; -1810 -1811 // loop through all groups to get the labels width and height -1812 var groups = this.groups; -1813 var labels = this.dom.groups ? this.dom.groups.labels : []; -1814 for (var i = 0, iMax = groups.length; i < iMax; i++) { -1815 var group = groups[i]; -1816 var label = labels[i]; -1817 group.labelWidth = label ? label.clientWidth : 0; -1818 group.labelHeight = label ? label.clientHeight : 0; -1819 group.width = group.labelWidth; // TODO: group.width is redundant with labelWidth -1820 -1821 groupsWidth = Math.max(groupsWidth, group.width); -1822 } -1823 -1824 // limit groupsWidth to the groups width in the options -1825 if (options.groupsWidth !== undefined) { -1826 groupsWidth = dom.groups && dom.groups.frame ? dom.groups.frame.clientWidth : 0; -1827 } -1828 -1829 // compensate for the border width. TODO: calculate the real border width -1830 groupsWidth += 1; -1831 -1832 var groupsLeft = options.groupsOnRight ? size.frameWidth - groupsWidth : 0; -1833 resized = resized || (size.groupsWidth !== groupsWidth); -1834 resized = resized || (size.groupsLeft !== groupsLeft); -1835 size.groupsWidth = groupsWidth; -1836 size.groupsLeft = groupsLeft; -1837 -1838 return resized; -1839 }; -1840 -1841 /** -1842 * Redraw the group labels -1843 */ -1844 links.Timeline.prototype.repaintGroups = function() { -1845 var dom = this.dom, -1846 timeline = this, -1847 options = this.options, -1848 size = this.size, -1849 groups = this.groups; -1850 -1851 if (dom.groups === undefined) { -1852 dom.groups = {}; -1853 } -1854 -1855 var labels = dom.groups.labels; -1856 if (!labels) { -1857 labels = []; -1858 dom.groups.labels = labels; -1859 } -1860 var labelLines = dom.groups.labelLines; -1861 if (!labelLines) { -1862 labelLines = []; -1863 dom.groups.labelLines = labelLines; -1864 } -1865 var itemLines = dom.groups.itemLines; -1866 if (!itemLines) { -1867 itemLines = []; -1868 dom.groups.itemLines = itemLines; -1869 } -1870 -1871 // create the frame for holding the groups -1872 var frame = dom.groups.frame; -1873 if (!frame) { -1874 frame = document.createElement("DIV"); -1875 frame.className = "timeline-groups-axis"; -1876 frame.style.position = "absolute"; -1877 frame.style.overflow = "hidden"; -1878 frame.style.top = "0px"; -1879 frame.style.height = "100%"; -1880 -1881 dom.frame.appendChild(frame); -1882 dom.groups.frame = frame; -1883 } -1884 -1885 frame.style.left = size.groupsLeft + "px"; -1886 frame.style.width = (options.groupsWidth !== undefined) ? -1887 options.groupsWidth : -1888 size.groupsWidth + "px"; -1889 -1890 // hide groups axis when there are no groups -1891 if (groups.length == 0) { -1892 frame.style.display = 'none'; -1893 } -1894 else { -1895 frame.style.display = ''; -1896 } -1897 -1898 // TODO: only create/update groups when data is changed. -1899 -1900 // create the items -1901 var current = labels.length, -1902 needed = groups.length; -1903 -1904 // overwrite existing group labels -1905 for (var i = 0, iMax = Math.min(current, needed); i < iMax; i++) { -1906 var group = groups[i]; -1907 var label = labels[i]; -1908 label.innerHTML = this.getGroupName(group); -1909 label.style.display = ''; -1910 } -1911 -1912 // append new items when needed -1913 for (var i = current; i < needed; i++) { -1914 var group = groups[i]; -1915 -1916 // create text label -1917 var label = document.createElement("DIV"); -1918 label.className = "timeline-groups-text"; -1919 label.style.position = "absolute"; -1920 if (options.groupsWidth === undefined) { -1921 label.style.whiteSpace = "nowrap"; -1922 } -1923 label.innerHTML = this.getGroupName(group); -1924 frame.appendChild(label); -1925 labels[i] = label; -1926 -1927 // create the grid line between the group labels -1928 var labelLine = document.createElement("DIV"); -1929 labelLine.className = "timeline-axis-grid timeline-axis-grid-minor"; -1930 labelLine.style.position = "absolute"; -1931 labelLine.style.left = "0px"; -1932 labelLine.style.width = "100%"; -1933 labelLine.style.height = "0px"; -1934 labelLine.style.borderTopStyle = "solid"; -1935 frame.appendChild(labelLine); -1936 labelLines[i] = labelLine; -1937 -1938 // create the grid line between the items -1939 var itemLine = document.createElement("DIV"); -1940 itemLine.className = "timeline-axis-grid timeline-axis-grid-minor"; -1941 itemLine.style.position = "absolute"; -1942 itemLine.style.left = "0px"; -1943 itemLine.style.width = "100%"; -1944 itemLine.style.height = "0px"; -1945 itemLine.style.borderTopStyle = "solid"; -1946 dom.content.insertBefore(itemLine, dom.content.firstChild); -1947 itemLines[i] = itemLine; -1948 } -1949 -1950 // remove redundant items from the DOM when needed -1951 for (var i = needed; i < current; i++) { -1952 var label = labels[i], -1953 labelLine = labelLines[i], -1954 itemLine = itemLines[i]; -1955 -1956 frame.removeChild(label); -1957 frame.removeChild(labelLine); -1958 dom.content.removeChild(itemLine); -1959 } -1960 labels.splice(needed, current - needed); -1961 labelLines.splice(needed, current - needed); -1962 itemLines.splice(needed, current - needed); -1963 -1964 links.Timeline.addClassName(frame, options.groupsOnRight ? 'timeline-groups-axis-onright' : 'timeline-groups-axis-onleft'); -1965 -1966 // position the groups -1967 for (var i = 0, iMax = groups.length; i < iMax; i++) { -1968 var group = groups[i], -1969 label = labels[i], -1970 labelLine = labelLines[i], -1971 itemLine = itemLines[i]; -1972 -1973 label.style.top = group.labelTop + "px"; -1974 labelLine.style.top = group.lineTop + "px"; -1975 itemLine.style.top = group.lineTop + "px"; -1976 itemLine.style.width = size.contentWidth + "px"; -1977 } -1978 -1979 if (!dom.groups.background) { -1980 // create the axis grid line background -1981 var background = document.createElement("DIV"); -1982 background.className = "timeline-axis"; -1983 background.style.position = "absolute"; -1984 background.style.left = "0px"; -1985 background.style.width = "100%"; -1986 background.style.border = "none"; -1987 -1988 frame.appendChild(background); -1989 dom.groups.background = background; -1990 } -1991 dom.groups.background.style.top = size.axis.top + 'px'; -1992 dom.groups.background.style.height = size.axis.height + 'px'; -1993 -1994 if (!dom.groups.line) { -1995 // create the axis grid line -1996 var line = document.createElement("DIV"); -1997 line.className = "timeline-axis"; -1998 line.style.position = "absolute"; -1999 line.style.left = "0px"; -2000 line.style.width = "100%"; -2001 line.style.height = "0px"; -2002 -2003 frame.appendChild(line); -2004 dom.groups.line = line; -2005 } -2006 dom.groups.line.style.top = size.axis.line + 'px'; -2007 -2008 // create a callback when there are images which are not yet loaded -2009 // TODO: more efficiently load images in the groups -2010 if (dom.groups.frame && groups.length) { -2011 var imageUrls = []; -2012 links.imageloader.filterImageUrls(dom.groups.frame, imageUrls); -2013 if (imageUrls.length) { -2014 // retrieve all image sources from the items, and set a callback once -2015 // all images are retrieved -2016 var callback = function () { -2017 timeline.render(); -2018 }; -2019 var sendCallbackWhenAlreadyLoaded = false; -2020 links.imageloader.loadAll(imageUrls, callback, sendCallbackWhenAlreadyLoaded); -2021 } -2022 } -2023 }; -2024 -2025 -2026 /** -2027 * Redraw the current time bar -2028 */ -2029 links.Timeline.prototype.repaintCurrentTime = function() { -2030 var options = this.options, -2031 dom = this.dom, -2032 size = this.size; -2033 -2034 if (!options.showCurrentTime) { -2035 if (dom.currentTime) { -2036 dom.contentTimelines.removeChild(dom.currentTime); -2037 delete dom.currentTime; -2038 } -2039 -2040 return; -2041 } -2042 -2043 if (!dom.currentTime) { -2044 // create the current time bar -2045 var currentTime = document.createElement("DIV"); -2046 currentTime.className = "timeline-currenttime"; -2047 currentTime.style.position = "absolute"; -2048 currentTime.style.top = "0px"; -2049 currentTime.style.height = "100%"; -2050 -2051 dom.contentTimelines.appendChild(currentTime); -2052 dom.currentTime = currentTime; -2053 } -2054 -2055 var now = new Date(); -2056 var nowOffset = new Date(now.valueOf() + this.clientTimeOffset); -2057 var x = this.timeToScreen(nowOffset); -2058 -2059 var visible = (x > -size.contentWidth && x < 2 * size.contentWidth); -2060 dom.currentTime.style.display = visible ? '' : 'none'; -2061 dom.currentTime.style.left = x + "px"; -2062 dom.currentTime.title = "Current time: " + nowOffset; -2063 -2064 // start a timer to adjust for the new time -2065 if (this.currentTimeTimer != undefined) { -2066 clearTimeout(this.currentTimeTimer); -2067 delete this.currentTimeTimer; -2068 } -2069 var timeline = this; -2070 var onTimeout = function() { -2071 timeline.repaintCurrentTime(); -2072 }; -2073 // the time equal to the width of one pixel, divided by 2 for more smoothness -2074 var interval = 1 / this.conversion.factor / 2; -2075 if (interval < 30) interval = 30; -2076 this.currentTimeTimer = setTimeout(onTimeout, interval); -2077 }; -2078 -2079 /** -2080 * Redraw the custom time bar -2081 */ -2082 links.Timeline.prototype.repaintCustomTime = function() { -2083 var options = this.options, -2084 dom = this.dom, -2085 size = this.size; -2086 -2087 if (!options.showCustomTime) { -2088 if (dom.customTime) { -2089 dom.contentTimelines.removeChild(dom.customTime); -2090 delete dom.customTime; -2091 } -2092 -2093 return; -2094 } -2095 -2096 if (!dom.customTime) { -2097 var customTime = document.createElement("DIV"); -2098 customTime.className = "timeline-customtime"; -2099 customTime.style.position = "absolute"; -2100 customTime.style.top = "0px"; -2101 customTime.style.height = "100%"; -2102 -2103 var drag = document.createElement("DIV"); -2104 drag.style.position = "relative"; -2105 drag.style.top = "0px"; -2106 drag.style.left = "-10px"; -2107 drag.style.height = "100%"; -2108 drag.style.width = "20px"; -2109 customTime.appendChild(drag); -2110 -2111 dom.contentTimelines.appendChild(customTime); -2112 dom.customTime = customTime; -2113 -2114 // initialize parameter -2115 this.customTime = new Date(); -2116 } -2117 -2118 var x = this.timeToScreen(this.customTime), -2119 visible = (x > -size.contentWidth && x < 2 * size.contentWidth); -2120 dom.customTime.style.display = visible ? '' : 'none'; -2121 dom.customTime.style.left = x + "px"; -2122 dom.customTime.title = "Time: " + this.customTime; -2123 }; -2124 -2125 -2126 /** -2127 * Redraw the delete button, on the top right of the currently selected item -2128 * if there is no item selected, the button is hidden. -2129 */ -2130 links.Timeline.prototype.repaintDeleteButton = function () { -2131 var timeline = this, -2132 dom = this.dom, -2133 frame = dom.items.frame; -2134 -2135 var deleteButton = dom.items.deleteButton; -2136 if (!deleteButton) { -2137 // create a delete button -2138 deleteButton = document.createElement("DIV"); -2139 deleteButton.className = "timeline-navigation-delete"; -2140 deleteButton.style.position = "absolute"; -2141 -2142 frame.appendChild(deleteButton); -2143 dom.items.deleteButton = deleteButton; -2144 } -2145 -2146 var index = (this.selection && this.selection.index !== undefined) ? this.selection.index : -1, -2147 item = (this.selection && this.selection.index !== undefined) ? this.items[index] : undefined; -2148 if (item && item.rendered && this.isEditable(item)) { -2149 var right = item.getRight(this), -2150 top = item.top; -2151 -2152 deleteButton.style.left = right + 'px'; -2153 deleteButton.style.top = top + 'px'; -2154 deleteButton.style.display = ''; -2155 frame.removeChild(deleteButton); -2156 frame.appendChild(deleteButton); -2157 } -2158 else { -2159 deleteButton.style.display = 'none'; -2160 } -2161 }; -2162 -2163 -2164 /** -2165 * Redraw the drag areas. When an item (ranges only) is selected, -2166 * it gets a drag area on the left and right side, to change its width -2167 */ -2168 links.Timeline.prototype.repaintDragAreas = function () { -2169 var timeline = this, -2170 options = this.options, -2171 dom = this.dom, -2172 frame = this.dom.items.frame; -2173 -2174 // create left drag area -2175 var dragLeft = dom.items.dragLeft; -2176 if (!dragLeft) { -2177 dragLeft = document.createElement("DIV"); -2178 dragLeft.className="timeline-event-range-drag-left"; -2179 dragLeft.style.position = "absolute"; -2180 -2181 frame.appendChild(dragLeft); -2182 dom.items.dragLeft = dragLeft; -2183 } -2184 -2185 // create right drag area -2186 var dragRight = dom.items.dragRight; -2187 if (!dragRight) { -2188 dragRight = document.createElement("DIV"); -2189 dragRight.className="timeline-event-range-drag-right"; -2190 dragRight.style.position = "absolute"; -2191 -2192 frame.appendChild(dragRight); -2193 dom.items.dragRight = dragRight; -2194 } -2195 -2196 // reposition left and right drag area -2197 var index = (this.selection && this.selection.index !== undefined) ? this.selection.index : -1, -2198 item = (this.selection && this.selection.index !== undefined) ? this.items[index] : undefined; -2199 if (item && item.rendered && this.isEditable(item) && -2200 (item instanceof links.Timeline.ItemRange || item instanceof links.Timeline.ItemFloatingRange)) { -2201 var left = item.getLeft(this), // NH change to getLeft -2202 right = item.getRight(this), // NH change to getRight -2203 top = item.top, -2204 height = item.height; -2205 -2206 dragLeft.style.left = left + 'px'; -2207 dragLeft.style.top = top + 'px'; -2208 dragLeft.style.width = options.dragAreaWidth + "px"; -2209 dragLeft.style.height = height + 'px'; -2210 dragLeft.style.display = ''; -2211 frame.removeChild(dragLeft); -2212 frame.appendChild(dragLeft); -2213 -2214 dragRight.style.left = (right - options.dragAreaWidth) + 'px'; -2215 dragRight.style.top = top + 'px'; -2216 dragRight.style.width = options.dragAreaWidth + "px"; -2217 dragRight.style.height = height + 'px'; -2218 dragRight.style.display = ''; -2219 frame.removeChild(dragRight); -2220 frame.appendChild(dragRight); -2221 } -2222 else { -2223 dragLeft.style.display = 'none'; -2224 dragRight.style.display = 'none'; -2225 } -2226 }; -2227 -2228 /** -2229 * Create the navigation buttons for zooming and moving -2230 */ -2231 links.Timeline.prototype.repaintNavigation = function () { -2232 var timeline = this, -2233 options = this.options, -2234 dom = this.dom, -2235 frame = dom.frame, -2236 navBar = dom.navBar; -2237 -2238 if (!navBar) { -2239 var showButtonNew = options.showButtonNew && options.editable; -2240 var showNavigation = options.showNavigation && (options.zoomable || options.moveable); -2241 if (showNavigation || showButtonNew) { -2242 // create a navigation bar containing the navigation buttons -2243 navBar = document.createElement("DIV"); -2244 navBar.style.position = "absolute"; -2245 navBar.className = "timeline-navigation ui-widget ui-state-highlight ui-corner-all"; -2246 if (options.groupsOnRight) { -2247 navBar.style.left = '10px'; -2248 } -2249 else { -2250 navBar.style.right = '10px'; -2251 } -2252 if (options.axisOnTop) { -2253 navBar.style.bottom = '10px'; -2254 } -2255 else { -2256 navBar.style.top = '10px'; -2257 } -2258 dom.navBar = navBar; -2259 frame.appendChild(navBar); -2260 } -2261 -2262 if (showButtonNew) { -2263 // create a new in button -2264 navBar.addButton = document.createElement("DIV"); -2265 navBar.addButton.className = "timeline-navigation-new"; -2266 navBar.addButton.title = options.CREATE_NEW_EVENT; -2267 var addIconSpan = document.createElement("SPAN"); -2268 addIconSpan.className = "ui-icon ui-icon-circle-plus"; -2269 navBar.addButton.appendChild(addIconSpan); -2270 -2271 var onAdd = function(event) { -2272 links.Timeline.preventDefault(event); -2273 links.Timeline.stopPropagation(event); -2274 -2275 // create a new event at the center of the frame -2276 var w = timeline.size.contentWidth; -2277 var x = w / 2; -2278 var xstart = timeline.screenToTime(x); -2279 if (options.snapEvents) { -2280 timeline.step.snap(xstart); -2281 } -2282 -2283 var content = options.NEW; -2284 var group = timeline.groups.length ? timeline.groups[0].content : undefined; -2285 var preventRender = true; -2286 timeline.addItem({ -2287 'start': xstart, -2288 'content': content, -2289 'group': group -2290 }, preventRender); -2291 var index = (timeline.items.length - 1); -2292 timeline.selectItem(index); -2293 -2294 timeline.applyAdd = true; -2295 -2296 // fire an add event. -2297 // Note that the change can be canceled from within an event listener if -2298 // this listener calls the method cancelAdd(). -2299 timeline.trigger('add'); -2300 -2301 if (timeline.applyAdd) { -2302 // render and select the item -2303 timeline.render({animate: false}); -2304 timeline.selectItem(index); -2305 } -2306 else { -2307 // undo an add -2308 timeline.deleteItem(index); -2309 } -2310 }; -2311 links.Timeline.addEventListener(navBar.addButton, "mousedown", onAdd); -2312 navBar.appendChild(navBar.addButton); -2313 } -2314 -2315 if (showButtonNew && showNavigation) { -2316 // create a separator line -2317 links.Timeline.addClassName(navBar.addButton, 'timeline-navigation-new-line'); -2318 } -2319 -2320 if (showNavigation) { -2321 if (options.zoomable) { -2322 // create a zoom in button -2323 navBar.zoomInButton = document.createElement("DIV"); -2324 navBar.zoomInButton.className = "timeline-navigation-zoom-in"; -2325 navBar.zoomInButton.title = this.options.ZOOM_IN; -2326 var ziIconSpan = document.createElement("SPAN"); -2327 ziIconSpan.className = "ui-icon ui-icon-circle-zoomin"; -2328 navBar.zoomInButton.appendChild(ziIconSpan); -2329 -2330 var onZoomIn = function(event) { -2331 links.Timeline.preventDefault(event); -2332 links.Timeline.stopPropagation(event); -2333 timeline.zoom(0.4); -2334 timeline.trigger("rangechange"); -2335 timeline.trigger("rangechanged"); -2336 }; -2337 links.Timeline.addEventListener(navBar.zoomInButton, "mousedown", onZoomIn); -2338 navBar.appendChild(navBar.zoomInButton); -2339 -2340 // create a zoom out button -2341 navBar.zoomOutButton = document.createElement("DIV"); -2342 navBar.zoomOutButton.className = "timeline-navigation-zoom-out"; -2343 navBar.zoomOutButton.title = this.options.ZOOM_OUT; -2344 var zoIconSpan = document.createElement("SPAN"); -2345 zoIconSpan.className = "ui-icon ui-icon-circle-zoomout"; -2346 navBar.zoomOutButton.appendChild(zoIconSpan); -2347 -2348 var onZoomOut = function(event) { -2349 links.Timeline.preventDefault(event); -2350 links.Timeline.stopPropagation(event); -2351 timeline.zoom(-0.4); -2352 timeline.trigger("rangechange"); -2353 timeline.trigger("rangechanged"); -2354 }; -2355 links.Timeline.addEventListener(navBar.zoomOutButton, "mousedown", onZoomOut); -2356 navBar.appendChild(navBar.zoomOutButton); -2357 } -2358 -2359 if (options.moveable) { -2360 // create a move left button -2361 navBar.moveLeftButton = document.createElement("DIV"); -2362 navBar.moveLeftButton.className = "timeline-navigation-move-left"; -2363 navBar.moveLeftButton.title = this.options.MOVE_LEFT; -2364 var mlIconSpan = document.createElement("SPAN"); -2365 mlIconSpan.className = "ui-icon ui-icon-circle-arrow-w"; -2366 navBar.moveLeftButton.appendChild(mlIconSpan); -2367 -2368 var onMoveLeft = function(event) { -2369 links.Timeline.preventDefault(event); -2370 links.Timeline.stopPropagation(event); -2371 timeline.move(-0.2); -2372 timeline.trigger("rangechange"); -2373 timeline.trigger("rangechanged"); -2374 }; -2375 links.Timeline.addEventListener(navBar.moveLeftButton, "mousedown", onMoveLeft); -2376 navBar.appendChild(navBar.moveLeftButton); -2377 -2378 // create a move right button -2379 navBar.moveRightButton = document.createElement("DIV"); -2380 navBar.moveRightButton.className = "timeline-navigation-move-right"; -2381 navBar.moveRightButton.title = this.options.MOVE_RIGHT; -2382 var mrIconSpan = document.createElement("SPAN"); -2383 mrIconSpan.className = "ui-icon ui-icon-circle-arrow-e"; -2384 navBar.moveRightButton.appendChild(mrIconSpan); -2385 -2386 var onMoveRight = function(event) { -2387 links.Timeline.preventDefault(event); -2388 links.Timeline.stopPropagation(event); -2389 timeline.move(0.2); -2390 timeline.trigger("rangechange"); -2391 timeline.trigger("rangechanged"); -2392 }; -2393 links.Timeline.addEventListener(navBar.moveRightButton, "mousedown", onMoveRight); -2394 navBar.appendChild(navBar.moveRightButton); -2395 } -2396 } -2397 } -2398 }; -2399 -2400 -2401 /** -2402 * Set current time. This function can be used to set the time in the client -2403 * timeline equal with the time on a server. -2404 * @param {Date} time -2405 */ -2406 links.Timeline.prototype.setCurrentTime = function(time) { -2407 var now = new Date(); -2408 this.clientTimeOffset = (time.valueOf() - now.valueOf()); -2409 -2410 this.repaintCurrentTime(); -2411 }; -2412 -2413 /** -2414 * Get current time. The time can have an offset from the real time, when -2415 * the current time has been changed via the method setCurrentTime. -2416 * @return {Date} time -2417 */ -2418 links.Timeline.prototype.getCurrentTime = function() { -2419 var now = new Date(); -2420 return new Date(now.valueOf() + this.clientTimeOffset); -2421 }; -2422 -2423 -2424 /** -2425 * Set custom time. -2426 * The custom time bar can be used to display events in past or future. -2427 * @param {Date} time -2428 */ -2429 links.Timeline.prototype.setCustomTime = function(time) { -2430 this.customTime = new Date(time.valueOf()); -2431 this.repaintCustomTime(); -2432 }; -2433 -2434 /** -2435 * Retrieve the current custom time. -2436 * @return {Date} customTime -2437 */ -2438 links.Timeline.prototype.getCustomTime = function() { -2439 return new Date(this.customTime.valueOf()); -2440 }; -2441 -2442 /** -2443 * Set a custom scale. Autoscaling will be disabled. -2444 * For example setScale(SCALE.MINUTES, 5) will result -2445 * in minor steps of 5 minutes, and major steps of an hour. -2446 * -2447 * @param {links.Timeline.StepDate.SCALE} scale -2448 * A scale. Choose from SCALE.MILLISECOND, -2449 * SCALE.SECOND, SCALE.MINUTE, SCALE.HOUR, -2450 * SCALE.WEEKDAY, SCALE.DAY, SCALE.MONTH, -2451 * SCALE.YEAR. -2452 * @param {int} step A step size, by default 1. Choose for -2453 * example 1, 2, 5, or 10. -2454 */ -2455 links.Timeline.prototype.setScale = function(scale, step) { -2456 this.step.setScale(scale, step); -2457 this.render(); // TODO: optimize: only reflow/repaint axis -2458 }; -2459 -2460 /** -2461 * Enable or disable autoscaling -2462 * @param {boolean} enable If true or not defined, autoscaling is enabled. -2463 * If false, autoscaling is disabled. -2464 */ -2465 links.Timeline.prototype.setAutoScale = function(enable) { -2466 this.step.setAutoScale(enable); -2467 this.render(); // TODO: optimize: only reflow/repaint axis -2468 }; -2469 -2470 /** -2471 * Redraw the timeline -2472 * Reloads the (linked) data table and redraws the timeline when resized. -2473 * See also the method checkResize -2474 */ -2475 links.Timeline.prototype.redraw = function() { -2476 this.setData(this.data); -2477 }; -2478 -2479 -2480 /** -2481 * Check if the timeline is resized, and if so, redraw the timeline. -2482 * Useful when the webpage is resized. -2483 */ -2484 links.Timeline.prototype.checkResize = function() { -2485 // TODO: re-implement the method checkResize, or better, make it redundant as this.render will be smarter -2486 this.render(); -2487 }; -2488 -2489 /** -2490 * Check whether a given item is editable -2491 * @param {links.Timeline.Item} item -2492 * @return {boolean} editable -2493 */ -2494 links.Timeline.prototype.isEditable = function (item) { -2495 if (item) { -2496 if (item.editable != undefined) { -2497 return item.editable; -2498 } -2499 else { -2500 return this.options.editable; -2501 } -2502 } -2503 return false; -2504 }; -2505 -2506 /** -2507 * Calculate the factor and offset to convert a position on screen to the -2508 * corresponding date and vice versa. -2509 * After the method calcConversionFactor is executed once, the methods screenToTime and -2510 * timeToScreen can be used. -2511 */ -2512 links.Timeline.prototype.recalcConversion = function() { -2513 this.conversion.offset = this.start.valueOf(); -2514 this.conversion.factor = this.size.contentWidth / -2515 (this.end.valueOf() - this.start.valueOf()); -2516 }; -2517 -2518 -2519 /** -2520 * Convert a position on screen (pixels) to a datetime -2521 * Before this method can be used, the method calcConversionFactor must be -2522 * executed once. -2523 * @param {int} x Position on the screen in pixels -2524 * @return {Date} time The datetime the corresponds with given position x -2525 */ -2526 links.Timeline.prototype.screenToTime = function(x) { -2527 var conversion = this.conversion; -2528 return new Date(x / conversion.factor + conversion.offset); -2529 }; -2530 -2531 /** -2532 * Convert a datetime (Date object) into a position on the screen -2533 * Before this method can be used, the method calcConversionFactor must be -2534 * executed once. -2535 * @param {Date} time A date -2536 * @return {int} x The position on the screen in pixels which corresponds -2537 * with the given date. -2538 */ -2539 links.Timeline.prototype.timeToScreen = function(time) { -2540 var conversion = this.conversion; -2541 return (time.valueOf() - conversion.offset) * conversion.factor; -2542 }; -2543 -2544 -2545 -2546 /** -2547 * Event handler for touchstart event on mobile devices -2548 */ -2549 links.Timeline.prototype.onTouchStart = function(event) { -2550 var params = this.eventParams, -2551 me = this; -2552 -2553 if (params.touchDown) { -2554 // if already moving, return -2555 return; -2556 } -2557 -2558 params.touchDown = true; -2559 params.zoomed = false; -2560 -2561 this.onMouseDown(event); -2562 -2563 if (!params.onTouchMove) { -2564 params.onTouchMove = function (event) {me.onTouchMove(event);}; -2565 links.Timeline.addEventListener(document, "touchmove", params.onTouchMove); -2566 } -2567 if (!params.onTouchEnd) { -2568 params.onTouchEnd = function (event) {me.onTouchEnd(event);}; -2569 links.Timeline.addEventListener(document, "touchend", params.onTouchEnd); -2570 } -2571 -2572 /* TODO -2573 // check for double tap event -2574 var delta = 500; // ms -2575 var doubleTapStart = (new Date()).valueOf(); -2576 var target = links.Timeline.getTarget(event); -2577 var doubleTapItem = this.getItemIndex(target); -2578 if (params.doubleTapStart && -2579 (doubleTapStart - params.doubleTapStart) < delta && -2580 doubleTapItem == params.doubleTapItem) { -2581 delete params.doubleTapStart; -2582 delete params.doubleTapItem; -2583 me.onDblClick(event); -2584 params.touchDown = false; -2585 } -2586 params.doubleTapStart = doubleTapStart; -2587 params.doubleTapItem = doubleTapItem; -2588 */ -2589 // store timing for double taps -2590 var target = links.Timeline.getTarget(event); -2591 var item = this.getItemIndex(target); -2592 params.doubleTapStartPrev = params.doubleTapStart; -2593 params.doubleTapStart = (new Date()).valueOf(); -2594 params.doubleTapItemPrev = params.doubleTapItem; -2595 params.doubleTapItem = item; -2596 -2597 links.Timeline.preventDefault(event); -2598 }; -2599 -2600 /** -2601 * Event handler for touchmove event on mobile devices -2602 */ -2603 links.Timeline.prototype.onTouchMove = function(event) { -2604 var params = this.eventParams; -2605 -2606 if (event.scale && event.scale !== 1) { -2607 params.zoomed = true; -2608 } -2609 -2610 if (!params.zoomed) { -2611 // move -2612 this.onMouseMove(event); -2613 } -2614 else { -2615 if (this.options.zoomable) { -2616 // pinch -2617 // TODO: pinch only supported on iPhone/iPad. Create something manually for Android? -2618 params.zoomed = true; -2619 -2620 var scale = event.scale, -2621 oldWidth = (params.end.valueOf() - params.start.valueOf()), -2622 newWidth = oldWidth / scale, -2623 diff = newWidth - oldWidth, -2624 start = new Date(parseInt(params.start.valueOf() - diff/2)), -2625 end = new Date(parseInt(params.end.valueOf() + diff/2)); -2626 -2627 // TODO: determine zoom-around-date from touch positions? -2628 -2629 this.setVisibleChartRange(start, end); -2630 this.trigger("rangechange"); -2631 } -2632 } -2633 -2634 links.Timeline.preventDefault(event); -2635 }; -2636 -2637 /** -2638 * Event handler for touchend event on mobile devices -2639 */ -2640 links.Timeline.prototype.onTouchEnd = function(event) { -2641 var params = this.eventParams; -2642 var me = this; -2643 params.touchDown = false; -2644 -2645 if (params.zoomed) { -2646 this.trigger("rangechanged"); -2647 } -2648 -2649 if (params.onTouchMove) { -2650 links.Timeline.removeEventListener(document, "touchmove", params.onTouchMove); -2651 delete params.onTouchMove; -2652 -2653 } -2654 if (params.onTouchEnd) { -2655 links.Timeline.removeEventListener(document, "touchend", params.onTouchEnd); -2656 delete params.onTouchEnd; -2657 } -2658 -2659 this.onMouseUp(event); -2660 -2661 // check for double tap event -2662 var delta = 500; // ms -2663 var doubleTapEnd = (new Date()).valueOf(); -2664 var target = links.Timeline.getTarget(event); -2665 var doubleTapItem = this.getItemIndex(target); -2666 if (params.doubleTapStartPrev && -2667 (doubleTapEnd - params.doubleTapStartPrev) < delta && -2668 params.doubleTapItem == params.doubleTapItemPrev) { -2669 params.touchDown = true; -2670 me.onDblClick(event); -2671 params.touchDown = false; -2672 } -2673 -2674 links.Timeline.preventDefault(event); -2675 }; -2676 -2677 -2678 /** -2679 * Start a moving operation inside the provided parent element -2680 * @param {Event} event The event that occurred (required for -2681 * retrieving the mouse position) -2682 */ -2683 links.Timeline.prototype.onMouseDown = function(event) { -2684 event = event || window.event; -2685 -2686 var params = this.eventParams, -2687 options = this.options, -2688 dom = this.dom; -2689 -2690 // only react on left mouse button down -2691 var leftButtonDown = event.which ? (event.which == 1) : (event.button == 1); -2692 if (!leftButtonDown && !params.touchDown) { -2693 return; -2694 } -2695 -2696 // get mouse position -2697 params.mouseX = links.Timeline.getPageX(event); -2698 params.mouseY = links.Timeline.getPageY(event); -2699 params.frameLeft = links.Timeline.getAbsoluteLeft(this.dom.content); -2700 params.frameTop = links.Timeline.getAbsoluteTop(this.dom.content); -2701 params.previousLeft = 0; -2702 params.previousOffset = 0; -2703 -2704 params.moved = false; -2705 params.start = new Date(this.start.valueOf()); -2706 params.end = new Date(this.end.valueOf()); -2707 -2708 params.target = links.Timeline.getTarget(event); -2709 var dragLeft = (dom.items && dom.items.dragLeft) ? dom.items.dragLeft : undefined; -2710 var dragRight = (dom.items && dom.items.dragRight) ? dom.items.dragRight : undefined; -2711 params.itemDragLeft = (params.target === dragLeft); -2712 params.itemDragRight = (params.target === dragRight); -2713 -2714 if (params.itemDragLeft || params.itemDragRight) { -2715 params.itemIndex = (this.selection && this.selection.index !== undefined) ? this.selection.index : undefined; -2716 delete params.clusterIndex; -2717 } -2718 else { -2719 params.itemIndex = this.getItemIndex(params.target); -2720 params.clusterIndex = this.getClusterIndex(params.target); -2721 } -2722 -2723 params.customTime = (params.target === dom.customTime || -2724 params.target.parentNode === dom.customTime) ? -2725 this.customTime : -2726 undefined; -2727 -2728 params.addItem = (options.editable && event.ctrlKey); -2729 if (params.addItem) { -2730 // create a new event at the current mouse position -2731 var x = params.mouseX - params.frameLeft; -2732 var y = params.mouseY - params.frameTop; -2733 -2734 var xstart = this.screenToTime(x); -2735 if (options.snapEvents) { -2736 this.step.snap(xstart); -2737 } -2738 var xend = new Date(xstart.valueOf()); -2739 var content = options.NEW; -2740 var group = this.getGroupFromHeight(y); -2741 this.addItem({ -2742 'start': xstart, -2743 'end': xend, -2744 'content': content, -2745 'group': this.getGroupName(group) -2746 }); -2747 params.itemIndex = (this.items.length - 1); -2748 delete params.clusterIndex; -2749 this.selectItem(params.itemIndex); -2750 params.itemDragRight = true; -2751 } -2752 -2753 var item = this.items[params.itemIndex]; -2754 var isSelected = this.isSelected(params.itemIndex); -2755 params.editItem = isSelected && this.isEditable(item); -2756 if (params.editItem) { -2757 params.itemStart = item.start; -2758 params.itemEnd = item.end; -2759 params.itemGroup = item.group; -2760 params.itemLeft = item.getLeft(this); // NH Use item.getLeft here -2761 params.itemRight = item.getRight(this); // NH Use item.getRight here -2762 } -2763 else { -2764 this.dom.frame.style.cursor = 'move'; -2765 } -2766 if (!params.touchDown) { -2767 // add event listeners to handle moving the contents -2768 // we store the function onmousemove and onmouseup in the timeline, so we can -2769 // remove the eventlisteners lateron in the function mouseUp() -2770 var me = this; -2771 if (!params.onMouseMove) { -2772 params.onMouseMove = function (event) {me.onMouseMove(event);}; -2773 links.Timeline.addEventListener(document, "mousemove", params.onMouseMove); -2774 } -2775 if (!params.onMouseUp) { -2776 params.onMouseUp = function (event) {me.onMouseUp(event);}; -2777 links.Timeline.addEventListener(document, "mouseup", params.onMouseUp); -2778 } -2779 -2780 links.Timeline.preventDefault(event); -2781 } -2782 }; -2783 -2784 -2785 /** -2786 * Perform moving operating. -2787 * This function activated from within the funcion links.Timeline.onMouseDown(). -2788 * @param {Event} event Well, eehh, the event -2789 */ -2790 links.Timeline.prototype.onMouseMove = function (event) { -2791 event = event || window.event; -2792 -2793 var params = this.eventParams, -2794 size = this.size, -2795 dom = this.dom, -2796 options = this.options; -2797 -2798 // calculate change in mouse position -2799 var mouseX = links.Timeline.getPageX(event); -2800 var mouseY = links.Timeline.getPageY(event); -2801 -2802 if (params.mouseX == undefined) { -2803 params.mouseX = mouseX; -2804 } -2805 if (params.mouseY == undefined) { -2806 params.mouseY = mouseY; -2807 } -2808 -2809 var diffX = mouseX - params.mouseX; -2810 var diffY = mouseY - params.mouseY; -2811 -2812 // if mouse movement is big enough, register it as a "moved" event -2813 if (Math.abs(diffX) >= 1) { -2814 params.moved = true; -2815 } -2816 -2817 if (params.customTime) { -2818 var x = this.timeToScreen(params.customTime); -2819 var xnew = x + diffX; -2820 this.customTime = this.screenToTime(xnew); -2821 this.repaintCustomTime(); -2822 -2823 // fire a timechange event -2824 this.trigger('timechange'); -2825 } -2826 else if (params.editItem) { -2827 var item = this.items[params.itemIndex], -2828 left, -2829 right; -2830 -2831 if (params.itemDragLeft && options.timeChangeable) { -2832 // move the start of the item -2833 left = params.itemLeft + diffX; -2834 right = params.itemRight; -2835 -2836 item.start = this.screenToTime(left); -2837 if (options.snapEvents) { -2838 this.step.snap(item.start); -2839 left = this.timeToScreen(item.start); -2840 } -2841 -2842 if (left > right) { -2843 left = right; -2844 item.start = this.screenToTime(left); -2845 } -2846 this.trigger('change'); -2847 } -2848 else if (params.itemDragRight && options.timeChangeable) { -2849 // move the end of the item -2850 left = params.itemLeft; -2851 right = params.itemRight + diffX; -2852 -2853 item.end = this.screenToTime(right); -2854 if (options.snapEvents) { -2855 this.step.snap(item.end); -2856 right = this.timeToScreen(item.end); -2857 } -2858 -2859 if (right < left) { -2860 right = left; -2861 item.end = this.screenToTime(right); -2862 } -2863 this.trigger('change'); -2864 } -2865 else if (options.timeChangeable) { -2866 // move the item -2867 left = params.itemLeft + diffX; -2868 item.start = this.screenToTime(left); -2869 if (options.snapEvents) { -2870 this.step.snap(item.start); -2871 left = this.timeToScreen(item.start); -2872 } -2873 -2874 if (item.end) { -2875 right = left + (params.itemRight - params.itemLeft); -2876 item.end = this.screenToTime(right); -2877 } -2878 this.trigger('change'); -2879 } -2880 -2881 item.setPosition(left, right); -2882 -2883 var dragging = params.itemDragLeft || params.itemDragRight; -2884 if (this.groups.length && !dragging) { -2885 // move item from one group to another when needed -2886 var y = mouseY - params.frameTop; -2887 var group = this.getGroupFromHeight(y); -2888 if (options.groupsChangeable && item.group !== group) { -2889 // move item to the other group -2890 var index = this.items.indexOf(item); -2891 this.changeItem(index, {'group': this.getGroupName(group)}); -2892 } -2893 else { -2894 this.repaintDeleteButton(); -2895 this.repaintDragAreas(); -2896 } -2897 } -2898 else { -2899 // TODO: does not work well in FF, forces redraw with every mouse move it seems -2900 this.render(); // TODO: optimize, only redraw the items? -2901 // Note: when animate==true, no redraw is needed here, its done by stackItems animation -2902 } -2903 } -2904 else if (options.moveable) { -2905 var interval = (params.end.valueOf() - params.start.valueOf()); -2906 var diffMillisecs = Math.round((-diffX) / size.contentWidth * interval); -2907 var newStart = new Date(params.start.valueOf() + diffMillisecs); -2908 var newEnd = new Date(params.end.valueOf() + diffMillisecs); -2909 this.applyRange(newStart, newEnd); -2910 // if the applied range is moved due to a fixed min or max, -2911 // change the diffMillisecs accordingly -2912 var appliedDiff = (this.start.valueOf() - newStart.valueOf()); -2913 if (appliedDiff) { -2914 diffMillisecs += appliedDiff; -2915 } -2916 -2917 this.recalcConversion(); -2918 -2919 // move the items by changing the left position of their frame. -2920 // this is much faster than repositioning all elements individually via the -2921 // repaintFrame() function (which is done once at mouseup) -2922 // note that we round diffX to prevent wrong positioning on millisecond scale -2923 var previousLeft = params.previousLeft || 0; -2924 var currentLeft = parseFloat(dom.items.frame.style.left) || 0; -2925 var previousOffset = params.previousOffset || 0; -2926 var frameOffset = previousOffset + (currentLeft - previousLeft); -2927 var frameLeft = -diffMillisecs / interval * size.contentWidth + frameOffset; -2928 -2929 dom.items.frame.style.left = (frameLeft) + "px"; -2930 -2931 // read the left again from DOM (IE8- rounds the value) -2932 params.previousOffset = frameOffset; -2933 params.previousLeft = parseFloat(dom.items.frame.style.left) || frameLeft; -2934 -2935 this.repaintCurrentTime(); -2936 this.repaintCustomTime(); -2937 this.repaintAxis(); -2938 -2939 // fire a rangechange event -2940 this.trigger('rangechange'); -2941 } -2942 -2943 links.Timeline.preventDefault(event); -2944 }; -2945 -2946 -2947 /** -2948 * Stop moving operating. -2949 * This function activated from within the funcion links.Timeline.onMouseDown(). -2950 * @param {event} event The event -2951 */ -2952 links.Timeline.prototype.onMouseUp = function (event) { -2953 var params = this.eventParams, -2954 options = this.options; -2955 -2956 event = event || window.event; -2957 -2958 this.dom.frame.style.cursor = 'auto'; -2959 -2960 // remove event listeners here, important for Safari -2961 if (params.onMouseMove) { -2962 links.Timeline.removeEventListener(document, "mousemove", params.onMouseMove); -2963 delete params.onMouseMove; -2964 } -2965 if (params.onMouseUp) { -2966 links.Timeline.removeEventListener(document, "mouseup", params.onMouseUp); -2967 delete params.onMouseUp; -2968 } -2969 //links.Timeline.preventDefault(event); -2970 -2971 if (params.customTime) { -2972 // fire a timechanged event -2973 this.trigger('timechanged'); -2974 } -2975 else if (params.editItem) { -2976 var item = this.items[params.itemIndex]; -2977 -2978 if (params.moved || params.addItem) { -2979 this.applyChange = true; -2980 this.applyAdd = true; -2981 -2982 this.updateData(params.itemIndex, { -2983 'start': item.start, -2984 'end': item.end -2985 }); -2986 -2987 // fire an add or changed event. -2988 // Note that the change can be canceled from within an event listener if -2989 // this listener calls the method cancelChange(). -2990 this.trigger(params.addItem ? 'add' : 'changed'); -2991 -2992 //retrieve item data again to include changes made to it in the triggered event handlers -2993 item = this.items[params.itemIndex]; -2994 -2995 if (params.addItem) { -2996 if (this.applyAdd) { -2997 this.updateData(params.itemIndex, { -2998 'start': item.start, -2999 'end': item.end, -3000 'content': item.content, -3001 'group': this.getGroupName(item.group) -3002 }); -3003 } -3004 else { -3005 // undo an add -3006 this.deleteItem(params.itemIndex); -3007 } -3008 } -3009 else { -3010 if (this.applyChange) { -3011 this.updateData(params.itemIndex, { -3012 'start': item.start, -3013 'end': item.end -3014 }); -3015 } -3016 else { -3017 // undo a change -3018 delete this.applyChange; -3019 delete this.applyAdd; -3020 -3021 var item = this.items[params.itemIndex], -3022 domItem = item.dom; -3023 -3024 item.start = params.itemStart; -3025 item.end = params.itemEnd; -3026 item.group = params.itemGroup; -3027 // TODO: original group should be restored too -3028 item.setPosition(params.itemLeft, params.itemRight); -3029 -3030 this.updateData(params.itemIndex, { -3031 'start': params.itemStart, -3032 'end': params.itemEnd -3033 }); -3034 } -3035 } -3036 -3037 // prepare data for clustering, by filtering and sorting by type -3038 if (this.options.cluster) { -3039 this.clusterGenerator.updateData(); -3040 } -3041 -3042 this.render(); -3043 } -3044 } -3045 else { -3046 if (!params.moved && !params.zoomed) { -3047 // mouse did not move -> user has selected an item -3048 -3049 if (params.target === this.dom.items.deleteButton) { -3050 // delete item -3051 if (this.selection && this.selection.index !== undefined) { -3052 this.confirmDeleteItem(this.selection.index); -3053 } -3054 } -3055 else if (options.selectable) { -3056 // select/unselect item -3057 if (params.itemIndex != undefined) { -3058 if (!this.isSelected(params.itemIndex)) { -3059 this.selectItem(params.itemIndex); -3060 this.trigger('select'); -3061 } -3062 } -3063 else if(params.clusterIndex != undefined) { -3064 this.selectCluster(params.clusterIndex); -3065 this.trigger('select'); -3066 } -3067 else { -3068 if (options.unselectable) { -3069 this.unselectItem(); -3070 this.trigger('select'); -3071 } -3072 } -3073 } -3074 } -3075 else { -3076 // timeline is moved -3077 // TODO: optimize: no need to reflow and cluster again? -3078 this.render(); -3079 -3080 if ((params.moved && options.moveable) || (params.zoomed && options.zoomable) ) { -3081 // fire a rangechanged event -3082 this.trigger('rangechanged'); -3083 } -3084 } -3085 } -3086 }; -3087 -3088 /** -3089 * Double click event occurred for an item -3090 * @param {Event} event -3091 */ -3092 links.Timeline.prototype.onDblClick = function (event) { -3093 var params = this.eventParams, -3094 options = this.options, -3095 dom = this.dom, -3096 size = this.size; -3097 event = event || window.event; -3098 -3099 if (params.itemIndex != undefined) { -3100 var item = this.items[params.itemIndex]; -3101 if (item && this.isEditable(item)) { -3102 // fire the edit event -3103 this.trigger('edit'); -3104 } -3105 } -3106 else { -3107 if (options.editable) { -3108 // create a new item -3109 -3110 // get mouse position -3111 params.mouseX = links.Timeline.getPageX(event); -3112 params.mouseY = links.Timeline.getPageY(event); -3113 var x = params.mouseX - links.Timeline.getAbsoluteLeft(dom.content); -3114 var y = params.mouseY - links.Timeline.getAbsoluteTop(dom.content); -3115 -3116 // create a new event at the current mouse position -3117 var xstart = this.screenToTime(x); -3118 if (options.snapEvents) { -3119 this.step.snap(xstart); -3120 } -3121 -3122 var content = options.NEW; -3123 var group = this.getGroupFromHeight(y); // (group may be undefined) -3124 var preventRender = true; -3125 this.addItem({ -3126 'start': xstart, -3127 'content': content, -3128 'group': this.getGroupName(group) -3129 }, preventRender); -3130 params.itemIndex = (this.items.length - 1); -3131 this.selectItem(params.itemIndex); -3132 -3133 this.applyAdd = true; -3134 -3135 // fire an add event. -3136 // Note that the change can be canceled from within an event listener if -3137 // this listener calls the method cancelAdd(). -3138 this.trigger('add'); -3139 -3140 if (this.applyAdd) { -3141 // render and select the item -3142 this.render({animate: false}); -3143 this.selectItem(params.itemIndex); -3144 } -3145 else { -3146 // undo an add -3147 this.deleteItem(params.itemIndex); -3148 } -3149 } -3150 } -3151 -3152 links.Timeline.preventDefault(event); -3153 }; -3154 -3155 -3156 /** -3157 * Event handler for mouse wheel event, used to zoom the timeline -3158 * Code from http://adomas.org/javascript-mouse-wheel/ -3159 * @param {Event} event The event -3160 */ -3161 links.Timeline.prototype.onMouseWheel = function(event) { -3162 if (!this.options.zoomable) -3163 return; -3164 -3165 if (!event) { /* For IE. */ -3166 event = window.event; -3167 } -3168 -3169 // retrieve delta -3170 var delta = 0; -3171 if (event.wheelDelta) { /* IE/Opera. */ -3172 delta = event.wheelDelta/120; -3173 } else if (event.detail) { /* Mozilla case. */ -3174 // In Mozilla, sign of delta is different than in IE. -3175 // Also, delta is multiple of 3. -3176 delta = -event.detail/3; -3177 } -3178 -3179 // If delta is nonzero, handle it. -3180 // Basically, delta is now positive if wheel was scrolled up, -3181 // and negative, if wheel was scrolled down. -3182 if (delta) { -3183 // TODO: on FireFox, the window is not redrawn within repeated scroll-events -3184 // -> use a delayed redraw? Make a zoom queue? -3185 -3186 var timeline = this; -3187 var zoom = function () { -3188 // perform the zoom action. Delta is normally 1 or -1 -3189 var zoomFactor = delta / 5.0; -3190 var frameLeft = links.Timeline.getAbsoluteLeft(timeline.dom.content); -3191 var mouseX = links.Timeline.getPageX(event); -3192 var zoomAroundDate = -3193 (mouseX != undefined && frameLeft != undefined) ? -3194 timeline.screenToTime(mouseX - frameLeft) : -3195 undefined; -3196 -3197 timeline.zoom(zoomFactor, zoomAroundDate); -3198 -3199 // fire a rangechange and a rangechanged event -3200 timeline.trigger("rangechange"); -3201 timeline.trigger("rangechanged"); -3202 }; -3203 -3204 var scroll = function () { -3205 // Scroll the timeline -3206 timeline.move(delta * -0.2); -3207 timeline.trigger("rangechange"); -3208 timeline.trigger("rangechanged"); -3209 }; -3210 -3211 if (event.shiftKey) { -3212 scroll(); -3213 } -3214 else { -3215 zoom(); -3216 } -3217 } -3218 -3219 // Prevent default actions caused by mouse wheel. -3220 // That might be ugly, but we handle scrolls somehow -3221 // anyway, so don't bother here... -3222 links.Timeline.preventDefault(event); -3223 }; -3224 -3225 -3226 /** -3227 * Zoom the timeline the given zoomfactor in or out. Start and end date will -3228 * be adjusted, and the timeline will be redrawn. You can optionally give a -3229 * date around which to zoom. -3230 * For example, try zoomfactor = 0.1 or -0.1 -3231 * @param {Number} zoomFactor Zooming amount. Positive value will zoom in, -3232 * negative value will zoom out -3233 * @param {Date} zoomAroundDate Date around which will be zoomed. Optional -3234 */ -3235 links.Timeline.prototype.zoom = function(zoomFactor, zoomAroundDate) { -3236 // if zoomAroundDate is not provided, take it half between start Date and end Date -3237 if (zoomAroundDate == undefined) { -3238 zoomAroundDate = new Date((this.start.valueOf() + this.end.valueOf()) / 2); -3239 } -3240 -3241 // prevent zoom factor larger than 1 or smaller than -1 (larger than 1 will -3242 // result in a start>=end ) -3243 if (zoomFactor >= 1) { -3244 zoomFactor = 0.9; -3245 } -3246 if (zoomFactor <= -1) { -3247 zoomFactor = -0.9; -3248 } -3249 -3250 // adjust a negative factor such that zooming in with 0.1 equals zooming -3251 // out with a factor -0.1 -3252 if (zoomFactor < 0) { -3253 zoomFactor = zoomFactor / (1 + zoomFactor); -3254 } -3255 -3256 // zoom start Date and end Date relative to the zoomAroundDate -3257 var startDiff = (this.start.valueOf() - zoomAroundDate); -3258 var endDiff = (this.end.valueOf() - zoomAroundDate); -3259 -3260 // calculate new dates -3261 var newStart = new Date(this.start.valueOf() - startDiff * zoomFactor); -3262 var newEnd = new Date(this.end.valueOf() - endDiff * zoomFactor); -3263 -3264 // only zoom in when interval is larger than minimum interval (to prevent -3265 // sliding to left/right when having reached the minimum zoom level) -3266 var interval = (newEnd.valueOf() - newStart.valueOf()); -3267 var zoomMin = Number(this.options.zoomMin) || 10; -3268 if (zoomMin < 10) { -3269 zoomMin = 10; -3270 } -3271 if (interval >= zoomMin) { -3272 this.applyRange(newStart, newEnd, zoomAroundDate); -3273 this.render({ -3274 animate: this.options.animate && this.options.animateZoom -3275 }); -3276 } -3277 }; -3278 -3279 /** -3280 * Move the timeline the given movefactor to the left or right. Start and end -3281 * date will be adjusted, and the timeline will be redrawn. -3282 * For example, try moveFactor = 0.1 or -0.1 -3283 * @param {Number} moveFactor Moving amount. Positive value will move right, -3284 * negative value will move left -3285 */ -3286 links.Timeline.prototype.move = function(moveFactor) { -3287 // zoom start Date and end Date relative to the zoomAroundDate -3288 var diff = (this.end.valueOf() - this.start.valueOf()); -3289 -3290 // apply new dates -3291 var newStart = new Date(this.start.valueOf() + diff * moveFactor); -3292 var newEnd = new Date(this.end.valueOf() + diff * moveFactor); -3293 this.applyRange(newStart, newEnd); -3294 -3295 this.render(); // TODO: optimize, no need to reflow, only to recalc conversion and repaint -3296 }; -3297 -3298 /** -3299 * Apply a visible range. The range is limited to feasible maximum and minimum -3300 * range. -3301 * @param {Date} start -3302 * @param {Date} end -3303 * @param {Date} zoomAroundDate Optional. Date around which will be zoomed. -3304 */ -3305 links.Timeline.prototype.applyRange = function (start, end, zoomAroundDate) { -3306 // calculate new start and end value -3307 var startValue = start.valueOf(); // number -3308 var endValue = end.valueOf(); // number -3309 var interval = (endValue - startValue); -3310 -3311 // determine maximum and minimum interval -3312 var options = this.options; -3313 var year = 1000 * 60 * 60 * 24 * 365; -3314 var zoomMin = Number(options.zoomMin) || 10; -3315 if (zoomMin < 10) { -3316 zoomMin = 10; -3317 } -3318 var zoomMax = Number(options.zoomMax) || 10000 * year; -3319 if (zoomMax > 10000 * year) { -3320 zoomMax = 10000 * year; -3321 } -3322 if (zoomMax < zoomMin) { -3323 zoomMax = zoomMin; -3324 } -3325 -3326 // determine min and max date value -3327 var min = options.min ? options.min.valueOf() : undefined; // number -3328 var max = options.max ? options.max.valueOf() : undefined; // number -3329 if (min != undefined && max != undefined) { -3330 if (min >= max) { -3331 // empty range -3332 var day = 1000 * 60 * 60 * 24; -3333 max = min + day; -3334 } -3335 if (zoomMax > (max - min)) { -3336 zoomMax = (max - min); -3337 } -3338 if (zoomMin > (max - min)) { -3339 zoomMin = (max - min); -3340 } -3341 } -3342 -3343 // prevent empty interval -3344 if (startValue >= endValue) { -3345 endValue += 1000 * 60 * 60 * 24; -3346 } -3347 -3348 // prevent too small scale -3349 // TODO: IE has problems with milliseconds -3350 if (interval < zoomMin) { -3351 var diff = (zoomMin - interval); -3352 var f = zoomAroundDate ? (zoomAroundDate.valueOf() - startValue) / interval : 0.5; -3353 startValue -= Math.round(diff * f); -3354 endValue += Math.round(diff * (1 - f)); -3355 } -3356 -3357 // prevent too large scale -3358 if (interval > zoomMax) { -3359 var diff = (interval - zoomMax); -3360 var f = zoomAroundDate ? (zoomAroundDate.valueOf() - startValue) / interval : 0.5; -3361 startValue += Math.round(diff * f); -3362 endValue -= Math.round(diff * (1 - f)); -3363 } -3364 -3365 // prevent to small start date -3366 if (min != undefined) { -3367 var diff = (startValue - min); -3368 if (diff < 0) { -3369 startValue -= diff; -3370 endValue -= diff; -3371 } -3372 } -3373 -3374 // prevent to large end date -3375 if (max != undefined) { -3376 var diff = (max - endValue); -3377 if (diff < 0) { -3378 startValue += diff; -3379 endValue += diff; -3380 } -3381 } -3382 -3383 // apply new dates -3384 this.start = new Date(startValue); -3385 this.end = new Date(endValue); -3386 }; -3387 -3388 /** -3389 * Delete an item after a confirmation. -3390 * The deletion can be cancelled by executing .cancelDelete() during the -3391 * triggered event 'delete'. -3392 * @param {int} index Index of the item to be deleted -3393 */ -3394 links.Timeline.prototype.confirmDeleteItem = function(index) { -3395 this.applyDelete = true; -3396 -3397 // select the event to be deleted -3398 if (!this.isSelected(index)) { -3399 this.selectItem(index); -3400 } -3401 -3402 // fire a delete event trigger. -3403 // Note that the delete event can be canceled from within an event listener if -3404 // this listener calls the method cancelChange(). -3405 this.trigger('delete'); -3406 -3407 if (this.applyDelete) { -3408 this.deleteItem(index); -3409 } -3410 -3411 delete this.applyDelete; -3412 }; -3413 -3414 /** -3415 * Delete an item -3416 * @param {int} index Index of the item to be deleted -3417 * @param {boolean} [preventRender=false] Do not re-render timeline if true -3418 * (optimization for multiple delete) -3419 */ -3420 links.Timeline.prototype.deleteItem = function(index, preventRender) { -3421 if (index >= this.items.length) { -3422 throw "Cannot delete row, index out of range"; -3423 } -3424 -3425 if (this.selection && this.selection.index !== undefined) { -3426 // adjust the selection -3427 if (this.selection.index == index) { -3428 // item to be deleted is selected -3429 this.unselectItem(); -3430 } -3431 else if (this.selection.index > index) { -3432 // update selection index -3433 this.selection.index--; -3434 } -3435 } -3436 -3437 // actually delete the item and remove it from the DOM -3438 var item = this.items.splice(index, 1)[0]; -3439 this.renderQueue.hide.push(item); -3440 -3441 // delete the row in the original data table -3442 if (this.data) { -3443 if (google && google.visualization && -3444 this.data instanceof google.visualization.DataTable) { -3445 this.data.removeRow(index); -3446 } -3447 else if (links.Timeline.isArray(this.data)) { -3448 this.data.splice(index, 1); -3449 } -3450 else { -3451 throw "Cannot delete row from data, unknown data type"; -3452 } -3453 } -3454 -3455 // prepare data for clustering, by filtering and sorting by type -3456 if (this.options.cluster) { -3457 this.clusterGenerator.updateData(); -3458 } -3459 -3460 if (!preventRender) { -3461 this.render(); -3462 } -3463 }; -3464 -3465 -3466 /** -3467 * Delete all items -3468 */ -3469 links.Timeline.prototype.deleteAllItems = function() { -3470 this.unselectItem(); -3471 -3472 // delete the loaded items -3473 this.clearItems(); -3474 -3475 // delete the groups -3476 this.deleteGroups(); -3477 -3478 // empty original data table -3479 if (this.data) { -3480 if (google && google.visualization && -3481 this.data instanceof google.visualization.DataTable) { -3482 this.data.removeRows(0, this.data.getNumberOfRows()); -3483 } -3484 else if (links.Timeline.isArray(this.data)) { -3485 this.data.splice(0, this.data.length); -3486 } -3487 else { -3488 throw "Cannot delete row from data, unknown data type"; -3489 } -3490 } -3491 -3492 // prepare data for clustering, by filtering and sorting by type -3493 if (this.options.cluster) { -3494 this.clusterGenerator.updateData(); -3495 } -3496 -3497 this.render(); -3498 }; -3499 -3500 -3501 /** -3502 * Find the group from a given height in the timeline -3503 * @param {Number} height Height in the timeline -3504 * @return {Object | undefined} group The group object, or undefined if out -3505 * of range -3506 */ -3507 links.Timeline.prototype.getGroupFromHeight = function(height) { -3508 var i, -3509 group, -3510 groups = this.groups; -3511 -3512 if (groups.length) { -3513 if (this.options.axisOnTop) { -3514 for (i = groups.length - 1; i >= 0; i--) { -3515 group = groups[i]; -3516 if (height > group.top) { -3517 return group; -3518 } -3519 } -3520 } -3521 else { -3522 for (i = 0; i < groups.length; i++) { -3523 group = groups[i]; -3524 if (height > group.top) { -3525 return group; -3526 } -3527 } -3528 } -3529 -3530 return group; // return the last group -3531 } -3532 -3533 return undefined; -3534 }; -3535 -3536 /** -3537 * @constructor links.Timeline.Item -3538 * @param {Object} data Object containing parameters start, end -3539 * content, group, type, editable. -3540 * @param {Object} [options] Options to set initial property values -3541 * {Number} top -3542 * {Number} left -3543 * {Number} width -3544 * {Number} height -3545 */ -3546 links.Timeline.Item = function (data, options) { -3547 if (data) { -3548 /* TODO: use parseJSONDate as soon as it is tested and working (in two directions) -3549 this.start = links.Timeline.parseJSONDate(data.start); -3550 this.end = links.Timeline.parseJSONDate(data.end); -3551 */ -3552 this.start = data.start; -3553 this.end = data.end; -3554 this.content = data.content; -3555 this.className = data.className; -3556 this.editable = data.editable; -3557 this.group = data.group; -3558 this.type = data.type; -3559 } -3560 this.top = 0; -3561 this.left = 0; -3562 this.width = 0; -3563 this.height = 0; -3564 this.lineWidth = 0; -3565 this.dotWidth = 0; -3566 this.dotHeight = 0; -3567 -3568 this.rendered = false; // true when the item is draw in the Timeline DOM -3569 -3570 if (options) { -3571 // override the default properties -3572 for (var option in options) { -3573 if (options.hasOwnProperty(option)) { -3574 this[option] = options[option]; -3575 } -3576 } -3577 } -3578 -3579 }; -3580 -3581 -3582 -3583 /** -3584 * Reflow the Item: retrieve its actual size from the DOM -3585 * @return {boolean} resized returns true if the axis is resized -3586 */ -3587 links.Timeline.Item.prototype.reflow = function () { -3588 // Should be implemented by sub-prototype -3589 return false; -3590 }; -3591 -3592 /** -3593 * Append all image urls present in the items DOM to the provided array -3594 * @param {String[]} imageUrls -3595 */ -3596 links.Timeline.Item.prototype.getImageUrls = function (imageUrls) { -3597 if (this.dom) { -3598 links.imageloader.filterImageUrls(this.dom, imageUrls); -3599 } -3600 }; -3601 -3602 /** -3603 * Select the item -3604 */ -3605 links.Timeline.Item.prototype.select = function () { -3606 // Should be implemented by sub-prototype -3607 }; -3608 -3609 /** -3610 * Unselect the item -3611 */ -3612 links.Timeline.Item.prototype.unselect = function () { -3613 // Should be implemented by sub-prototype -3614 }; -3615 -3616 /** -3617 * Creates the DOM for the item, depending on its type -3618 * @return {Element | undefined} -3619 */ -3620 links.Timeline.Item.prototype.createDOM = function () { -3621 // Should be implemented by sub-prototype -3622 }; -3623 -3624 /** -3625 * Append the items DOM to the given HTML container. If items DOM does not yet -3626 * exist, it will be created first. -3627 * @param {Element} container -3628 */ -3629 links.Timeline.Item.prototype.showDOM = function (container) { -3630 // Should be implemented by sub-prototype -3631 }; -3632 -3633 /** -3634 * Remove the items DOM from the current HTML container -3635 * @param {Element} container -3636 */ -3637 links.Timeline.Item.prototype.hideDOM = function (container) { -3638 // Should be implemented by sub-prototype -3639 }; -3640 -3641 /** -3642 * Update the DOM of the item. This will update the content and the classes -3643 * of the item -3644 */ -3645 links.Timeline.Item.prototype.updateDOM = function () { -3646 // Should be implemented by sub-prototype -3647 }; -3648 -3649 /** -3650 * Reposition the item, recalculate its left, top, and width, using the current -3651 * range of the timeline and the timeline options. -3652 * @param {links.Timeline} timeline -3653 */ -3654 links.Timeline.Item.prototype.updatePosition = function (timeline) { -3655 // Should be implemented by sub-prototype -3656 }; -3657 -3658 /** -3659 * Check if the item is drawn in the timeline (i.e. the DOM of the item is -3660 * attached to the frame. You may also just request the parameter item.rendered -3661 * @return {boolean} rendered -3662 */ -3663 links.Timeline.Item.prototype.isRendered = function () { -3664 return this.rendered; -3665 }; -3666 -3667 /** -3668 * Check if the item is located in the visible area of the timeline, and -3669 * not part of a cluster -3670 * @param {Date} start -3671 * @param {Date} end -3672 * @return {boolean} visible -3673 */ -3674 links.Timeline.Item.prototype.isVisible = function (start, end) { -3675 // Should be implemented by sub-prototype -3676 return false; -3677 }; -3678 -3679 /** -3680 * Reposition the item -3681 * @param {Number} left -3682 * @param {Number} right -3683 */ -3684 links.Timeline.Item.prototype.setPosition = function (left, right) { -3685 // Should be implemented by sub-prototype -3686 }; -3687 -3688 /** -3689 * Calculate the left position of the item -3690 * @param {links.Timeline} timeline -3691 * @return {Number} left -3692 */ -3693 links.Timeline.Item.prototype.getLeft = function (timeline) { -3694 // Should be implemented by sub-prototype -3695 return 0; -3696 }; -3697 -3698 /** -3699 * Calculate the right position of the item -3700 * @param {links.Timeline} timeline -3701 * @return {Number} right -3702 */ -3703 links.Timeline.Item.prototype.getRight = function (timeline) { -3704 // Should be implemented by sub-prototype -3705 return 0; -3706 }; -3707 -3708 /** -3709 * Calculate the width of the item -3710 * @param {links.Timeline} timeline -3711 * @return {Number} width -3712 */ -3713 links.Timeline.Item.prototype.getWidth = function (timeline) { -3714 // Should be implemented by sub-prototype -3715 return this.width || 0; // last rendered width -3716 }; -3717 -3718 -3719 /** -3720 * @constructor links.Timeline.ItemBox -3721 * @extends links.Timeline.Item -3722 * @param {Object} data Object containing parameters start, end -3723 * content, group, type, className, editable. -3724 * @param {Object} [options] Options to set initial property values -3725 * {Number} top -3726 * {Number} left -3727 * {Number} width -3728 * {Number} height -3729 */ -3730 links.Timeline.ItemBox = function (data, options) { -3731 links.Timeline.Item.call(this, data, options); -3732 }; -3733 -3734 links.Timeline.ItemBox.prototype = new links.Timeline.Item(); -3735 -3736 /** -3737 * Reflow the Item: retrieve its actual size from the DOM -3738 * @return {boolean} resized returns true if the axis is resized -3739 * @override -3740 */ -3741 links.Timeline.ItemBox.prototype.reflow = function () { -3742 var dom = this.dom, -3743 dotHeight = dom.dot.offsetHeight, -3744 dotWidth = dom.dot.offsetWidth, -3745 lineWidth = dom.line.offsetWidth, -3746 resized = ( -3747 (this.dotHeight != dotHeight) || -3748 (this.dotWidth != dotWidth) || -3749 (this.lineWidth != lineWidth) -3750 ); -3751 -3752 this.dotHeight = dotHeight; -3753 this.dotWidth = dotWidth; -3754 this.lineWidth = lineWidth; -3755 -3756 return resized; -3757 }; -3758 -3759 /** -3760 * Select the item -3761 * @override -3762 */ -3763 links.Timeline.ItemBox.prototype.select = function () { -3764 var dom = this.dom; -3765 links.Timeline.addClassName(dom, 'timeline-event-selected ui-state-active'); -3766 links.Timeline.addClassName(dom.line, 'timeline-event-selected ui-state-active'); -3767 links.Timeline.addClassName(dom.dot, 'timeline-event-selected ui-state-active'); -3768 }; -3769 -3770 /** -3771 * Unselect the item -3772 * @override -3773 */ -3774 links.Timeline.ItemBox.prototype.unselect = function () { -3775 var dom = this.dom; -3776 links.Timeline.removeClassName(dom, 'timeline-event-selected ui-state-active'); -3777 links.Timeline.removeClassName(dom.line, 'timeline-event-selected ui-state-active'); -3778 links.Timeline.removeClassName(dom.dot, 'timeline-event-selected ui-state-active'); -3779 }; -3780 -3781 /** -3782 * Creates the DOM for the item, depending on its type -3783 * @return {Element | undefined} -3784 * @override -3785 */ -3786 links.Timeline.ItemBox.prototype.createDOM = function () { -3787 // background box -3788 var divBox = document.createElement("DIV"); -3789 divBox.style.position = "absolute"; -3790 divBox.style.left = this.left + "px"; -3791 divBox.style.top = this.top + "px"; -3792 -3793 // contents box (inside the background box). used for making margins -3794 var divContent = document.createElement("DIV"); -3795 divContent.className = "timeline-event-content"; -3796 divContent.innerHTML = this.content; -3797 divBox.appendChild(divContent); -3798 -3799 // line to axis -3800 var divLine = document.createElement("DIV"); -3801 divLine.style.position = "absolute"; -3802 divLine.style.width = "0px"; -3803 // important: the vertical line is added at the front of the list of elements, -3804 // so it will be drawn behind all boxes and ranges -3805 divBox.line = divLine; -3806 -3807 // dot on axis -3808 var divDot = document.createElement("DIV"); -3809 divDot.style.position = "absolute"; -3810 divDot.style.width = "0px"; -3811 divDot.style.height = "0px"; -3812 divBox.dot = divDot; -3813 -3814 this.dom = divBox; -3815 this.updateDOM(); -3816 -3817 return divBox; -3818 }; -3819 -3820 /** -3821 * Append the items DOM to the given HTML container. If items DOM does not yet -3822 * exist, it will be created first. -3823 * @param {Element} container -3824 * @override -3825 */ -3826 links.Timeline.ItemBox.prototype.showDOM = function (container) { -3827 var dom = this.dom; -3828 if (!dom) { -3829 dom = this.createDOM(); -3830 } -3831 -3832 if (dom.parentNode != container) { -3833 if (dom.parentNode) { -3834 // container is changed. remove from old container -3835 this.hideDOM(); -3836 } -3837 -3838 // append to this container -3839 container.appendChild(dom); -3840 container.insertBefore(dom.line, container.firstChild); -3841 // Note: line must be added in front of the this, -3842 // such that it stays below all this -3843 container.appendChild(dom.dot); -3844 this.rendered = true; -3845 } -3846 }; -3847 -3848 /** -3849 * Remove the items DOM from the current HTML container, but keep the DOM in -3850 * memory -3851 * @override -3852 */ -3853 links.Timeline.ItemBox.prototype.hideDOM = function () { -3854 var dom = this.dom; -3855 if (dom) { -3856 if (dom.parentNode) { -3857 dom.parentNode.removeChild(dom); -3858 } -3859 if (dom.line && dom.line.parentNode) { -3860 dom.line.parentNode.removeChild(dom.line); -3861 } -3862 if (dom.dot && dom.dot.parentNode) { -3863 dom.dot.parentNode.removeChild(dom.dot); -3864 } -3865 this.rendered = false; -3866 } -3867 }; -3868 -3869 /** -3870 * Update the DOM of the item. This will update the content and the classes -3871 * of the item -3872 * @override -3873 */ -3874 links.Timeline.ItemBox.prototype.updateDOM = function () { -3875 var divBox = this.dom; -3876 if (divBox) { -3877 var divLine = divBox.line; -3878 var divDot = divBox.dot; -3879 -3880 // update contents -3881 divBox.firstChild.innerHTML = this.content; -3882 -3883 // update class -3884 divBox.className = "timeline-event timeline-event-box ui-widget ui-state-default"; -3885 divLine.className = "timeline-event timeline-event-line ui-widget ui-state-default"; -3886 divDot.className = "timeline-event timeline-event-dot ui-widget ui-state-default"; -3887 -3888 if (this.isCluster) { -3889 links.Timeline.addClassName(divBox, 'timeline-event-cluster ui-widget-header'); -3890 links.Timeline.addClassName(divLine, 'timeline-event-cluster ui-widget-header'); -3891 links.Timeline.addClassName(divDot, 'timeline-event-cluster ui-widget-header'); -3892 } -3893 -3894 // add item specific class name when provided -3895 if (this.className) { -3896 links.Timeline.addClassName(divBox, this.className); -3897 links.Timeline.addClassName(divLine, this.className); -3898 links.Timeline.addClassName(divDot, this.className); -3899 } -3900 -3901 // TODO: apply selected className? -3902 } -3903 }; -3904 -3905 /** -3906 * Reposition the item, recalculate its left, top, and width, using the current -3907 * range of the timeline and the timeline options. -3908 * @param {links.Timeline} timeline -3909 * @override -3910 */ -3911 links.Timeline.ItemBox.prototype.updatePosition = function (timeline) { -3912 var dom = this.dom; -3913 if (dom) { -3914 var left = timeline.timeToScreen(this.start), -3915 axisOnTop = timeline.options.axisOnTop, -3916 axisTop = timeline.size.axis.top, -3917 axisHeight = timeline.size.axis.height, -3918 boxAlign = (timeline.options.box && timeline.options.box.align) ? -3919 timeline.options.box.align : undefined; -3920 -3921 dom.style.top = this.top + "px"; -3922 if (boxAlign == 'right') { -3923 dom.style.left = (left - this.width) + "px"; -3924 } -3925 else if (boxAlign == 'left') { -3926 dom.style.left = (left) + "px"; -3927 } -3928 else { // default or 'center' -3929 dom.style.left = (left - this.width/2) + "px"; -3930 } -3931 -3932 var line = dom.line; -3933 var dot = dom.dot; -3934 line.style.left = (left - this.lineWidth/2) + "px"; -3935 dot.style.left = (left - this.dotWidth/2) + "px"; -3936 if (axisOnTop) { -3937 line.style.top = axisHeight + "px"; -3938 line.style.height = Math.max(this.top - axisHeight, 0) + "px"; -3939 dot.style.top = (axisHeight - this.dotHeight/2) + "px"; -3940 } -3941 else { -3942 line.style.top = (this.top + this.height) + "px"; -3943 line.style.height = Math.max(axisTop - this.top - this.height, 0) + "px"; -3944 dot.style.top = (axisTop - this.dotHeight/2) + "px"; -3945 } -3946 } -3947 }; -3948 -3949 /** -3950 * Check if the item is visible in the timeline, and not part of a cluster -3951 * @param {Date} start -3952 * @param {Date} end -3953 * @return {Boolean} visible -3954 * @override -3955 */ -3956 links.Timeline.ItemBox.prototype.isVisible = function (start, end) { -3957 if (this.cluster) { -3958 return false; -3959 } -3960 -3961 return (this.start > start) && (this.start < end); -3962 }; -3963 -3964 /** -3965 * Reposition the item -3966 * @param {Number} left -3967 * @param {Number} right -3968 * @override -3969 */ -3970 links.Timeline.ItemBox.prototype.setPosition = function (left, right) { -3971 var dom = this.dom; -3972 -3973 dom.style.left = (left - this.width / 2) + "px"; -3974 dom.line.style.left = (left - this.lineWidth / 2) + "px"; -3975 dom.dot.style.left = (left - this.dotWidth / 2) + "px"; -3976 -3977 if (this.group) { -3978 this.top = this.group.top; -3979 dom.style.top = this.top + 'px'; -3980 } -3981 }; -3982 -3983 /** -3984 * Calculate the left position of the item -3985 * @param {links.Timeline} timeline -3986 * @return {Number} left -3987 * @override -3988 */ -3989 links.Timeline.ItemBox.prototype.getLeft = function (timeline) { -3990 var boxAlign = (timeline.options.box && timeline.options.box.align) ? -3991 timeline.options.box.align : undefined; -3992 -3993 var left = timeline.timeToScreen(this.start); -3994 if (boxAlign == 'right') { -3995 left = left - width; -3996 } -3997 else { // default or 'center' -3998 left = (left - this.width / 2); -3999 } -4000 -4001 return left; -4002 }; -4003 -4004 /** -4005 * Calculate the right position of the item -4006 * @param {links.Timeline} timeline -4007 * @return {Number} right -4008 * @override -4009 */ -4010 links.Timeline.ItemBox.prototype.getRight = function (timeline) { -4011 var boxAlign = (timeline.options.box && timeline.options.box.align) ? -4012 timeline.options.box.align : undefined; -4013 -4014 var left = timeline.timeToScreen(this.start); -4015 var right; -4016 if (boxAlign == 'right') { -4017 right = left; -4018 } -4019 else if (boxAlign == 'left') { -4020 right = (left + this.width); -4021 } -4022 else { // default or 'center' -4023 right = (left + this.width / 2); -4024 } -4025 -4026 return right; -4027 }; -4028 -4029 /** -4030 * @constructor links.Timeline.ItemRange -4031 * @extends links.Timeline.Item -4032 * @param {Object} data Object containing parameters start, end -4033 * content, group, type, className, editable. -4034 * @param {Object} [options] Options to set initial property values -4035 * {Number} top -4036 * {Number} left -4037 * {Number} width -4038 * {Number} height -4039 */ -4040 links.Timeline.ItemRange = function (data, options) { -4041 links.Timeline.Item.call(this, data, options); -4042 }; -4043 -4044 links.Timeline.ItemRange.prototype = new links.Timeline.Item(); -4045 -4046 /** -4047 * Select the item -4048 * @override -4049 */ -4050 links.Timeline.ItemRange.prototype.select = function () { -4051 var dom = this.dom; -4052 links.Timeline.addClassName(dom, 'timeline-event-selected ui-state-active'); -4053 }; -4054 -4055 /** -4056 * Unselect the item -4057 * @override -4058 */ -4059 links.Timeline.ItemRange.prototype.unselect = function () { -4060 var dom = this.dom; -4061 links.Timeline.removeClassName(dom, 'timeline-event-selected ui-state-active'); -4062 }; -4063 -4064 /** -4065 * Creates the DOM for the item, depending on its type -4066 * @return {Element | undefined} -4067 * @override -4068 */ -4069 links.Timeline.ItemRange.prototype.createDOM = function () { -4070 // background box -4071 var divBox = document.createElement("DIV"); -4072 divBox.style.position = "absolute"; -4073 -4074 // contents box -4075 var divContent = document.createElement("DIV"); -4076 divContent.className = "timeline-event-content"; -4077 divBox.appendChild(divContent); -4078 -4079 this.dom = divBox; -4080 this.updateDOM(); -4081 -4082 return divBox; -4083 }; -4084 -4085 /** -4086 * Append the items DOM to the given HTML container. If items DOM does not yet -4087 * exist, it will be created first. -4088 * @param {Element} container -4089 * @override -4090 */ -4091 links.Timeline.ItemRange.prototype.showDOM = function (container) { -4092 var dom = this.dom; -4093 if (!dom) { -4094 dom = this.createDOM(); -4095 } -4096 -4097 if (dom.parentNode != container) { -4098 if (dom.parentNode) { -4099 // container changed. remove the item from the old container -4100 this.hideDOM(); -4101 } -4102 -4103 // append to the new container -4104 container.appendChild(dom); -4105 this.rendered = true; -4106 } -4107 }; -4108 -4109 /** -4110 * Remove the items DOM from the current HTML container -4111 * The DOM will be kept in memory -4112 * @override -4113 */ -4114 links.Timeline.ItemRange.prototype.hideDOM = function () { -4115 var dom = this.dom; -4116 if (dom) { -4117 if (dom.parentNode) { -4118 dom.parentNode.removeChild(dom); -4119 } -4120 this.rendered = false; -4121 } -4122 }; -4123 -4124 /** -4125 * Update the DOM of the item. This will update the content and the classes -4126 * of the item -4127 * @override -4128 */ -4129 links.Timeline.ItemRange.prototype.updateDOM = function () { -4130 var divBox = this.dom; -4131 if (divBox) { -4132 // update contents -4133 divBox.firstChild.innerHTML = this.content; -4134 -4135 // update class -4136 divBox.className = "timeline-event timeline-event-range ui-widget ui-state-default"; -4137 -4138 if (this.isCluster) { -4139 links.Timeline.addClassName(divBox, 'timeline-event-cluster ui-widget-header'); -4140 } -4141 -4142 // add item specific class name when provided -4143 if (this.className) { -4144 links.Timeline.addClassName(divBox, this.className); -4145 } -4146 -4147 // TODO: apply selected className? -4148 } -4149 }; -4150 -4151 /** -4152 * Reposition the item, recalculate its left, top, and width, using the current -4153 * range of the timeline and the timeline options. * -4154 * @param {links.Timeline} timeline -4155 * @override -4156 */ -4157 links.Timeline.ItemRange.prototype.updatePosition = function (timeline) { -4158 var dom = this.dom; -4159 if (dom) { -4160 var contentWidth = timeline.size.contentWidth, -4161 left = timeline.timeToScreen(this.start), -4162 right = timeline.timeToScreen(this.end); -4163 -4164 // limit the width of the this, as browsers cannot draw very wide divs -4165 if (left < -contentWidth) { -4166 left = -contentWidth; -4167 } -4168 if (right > 2 * contentWidth) { -4169 right = 2 * contentWidth; -4170 } -4171 -4172 dom.style.top = this.top + "px"; -4173 dom.style.left = left + "px"; -4174 //dom.style.width = Math.max(right - left - 2 * this.borderWidth, 1) + "px"; // TODO: borderWidth -4175 dom.style.width = Math.max(right - left, 1) + "px"; -4176 } -4177 }; -4178 -4179 /** -4180 * Check if the item is visible in the timeline, and not part of a cluster -4181 * @param {Number} start -4182 * @param {Number} end -4183 * @return {boolean} visible -4184 * @override -4185 */ -4186 links.Timeline.ItemRange.prototype.isVisible = function (start, end) { -4187 if (this.cluster) { -4188 return false; -4189 } -4190 -4191 return (this.end > start) -4192 && (this.start < end); -4193 }; -4194 -4195 /** -4196 * Reposition the item -4197 * @param {Number} left -4198 * @param {Number} right -4199 * @override -4200 */ -4201 links.Timeline.ItemRange.prototype.setPosition = function (left, right) { -4202 var dom = this.dom; -4203 -4204 dom.style.left = left + 'px'; -4205 dom.style.width = (right - left) + 'px'; -4206 -4207 if (this.group) { -4208 this.top = this.group.top; -4209 dom.style.top = this.top + 'px'; -4210 } -4211 }; -4212 -4213 /** -4214 * Calculate the left position of the item -4215 * @param {links.Timeline} timeline -4216 * @return {Number} left -4217 * @override -4218 */ -4219 links.Timeline.ItemRange.prototype.getLeft = function (timeline) { -4220 return timeline.timeToScreen(this.start); -4221 }; -4222 -4223 /** -4224 * Calculate the right position of the item -4225 * @param {links.Timeline} timeline -4226 * @return {Number} right -4227 * @override -4228 */ -4229 links.Timeline.ItemRange.prototype.getRight = function (timeline) { -4230 return timeline.timeToScreen(this.end); -4231 }; -4232 -4233 /** -4234 * Calculate the width of the item -4235 * @param {links.Timeline} timeline -4236 * @return {Number} width -4237 * @override -4238 */ -4239 links.Timeline.ItemRange.prototype.getWidth = function (timeline) { -4240 return timeline.timeToScreen(this.end) - timeline.timeToScreen(this.start); -4241 }; -4242 -4243 /** -4244 * @constructor links.Timeline.ItemFloatingRange -4245 * @extends links.Timeline.Item -4246 * @param {Object} data Object containing parameters start, end -4247 * content, group, type, className, editable. -4248 * @param {Object} [options] Options to set initial property values -4249 * {Number} top -4250 * {Number} left -4251 * {Number} width -4252 * {Number} height -4253 */ -4254 links.Timeline.ItemFloatingRange = function (data, options) { -4255 links.Timeline.Item.call(this, data, options); -4256 }; -4257 -4258 links.Timeline.ItemFloatingRange.prototype = new links.Timeline.Item(); -4259 -4260 /** -4261 * Select the item -4262 * @override -4263 */ -4264 links.Timeline.ItemFloatingRange.prototype.select = function () { -4265 var dom = this.dom; -4266 links.Timeline.addClassName(dom, 'timeline-event-selected ui-state-active'); -4267 }; -4268 -4269 /** -4270 * Unselect the item -4271 * @override -4272 */ -4273 links.Timeline.ItemFloatingRange.prototype.unselect = function () { -4274 var dom = this.dom; -4275 links.Timeline.removeClassName(dom, 'timeline-event-selected ui-state-active'); -4276 }; -4277 -4278 /** -4279 * Creates the DOM for the item, depending on its type -4280 * @return {Element | undefined} -4281 * @override -4282 */ -4283 links.Timeline.ItemFloatingRange.prototype.createDOM = function () { -4284 // background box -4285 var divBox = document.createElement("DIV"); -4286 divBox.style.position = "absolute"; -4287 -4288 // contents box -4289 var divContent = document.createElement("DIV"); -4290 divContent.className = "timeline-event-content"; -4291 divBox.appendChild(divContent); -4292 -4293 this.dom = divBox; -4294 this.updateDOM(); -4295 -4296 return divBox; -4297 }; -4298 -4299 /** -4300 * Append the items DOM to the given HTML container. If items DOM does not yet -4301 * exist, it will be created first. -4302 * @param {Element} container -4303 * @override -4304 */ -4305 links.Timeline.ItemFloatingRange.prototype.showDOM = function (container) { -4306 var dom = this.dom; -4307 if (!dom) { -4308 dom = this.createDOM(); -4309 } -4310 -4311 if (dom.parentNode != container) { -4312 if (dom.parentNode) { -4313 // container changed. remove the item from the old container -4314 this.hideDOM(); -4315 } -4316 -4317 // append to the new container -4318 container.appendChild(dom); -4319 this.rendered = true; -4320 } -4321 }; -4322 -4323 /** -4324 * Remove the items DOM from the current HTML container -4325 * The DOM will be kept in memory -4326 * @override -4327 */ -4328 links.Timeline.ItemFloatingRange.prototype.hideDOM = function () { -4329 var dom = this.dom; -4330 if (dom) { -4331 if (dom.parentNode) { -4332 dom.parentNode.removeChild(dom); -4333 } -4334 this.rendered = false; -4335 } -4336 }; -4337 -4338 /** -4339 * Update the DOM of the item. This will update the content and the classes -4340 * of the item -4341 * @override -4342 */ -4343 links.Timeline.ItemFloatingRange.prototype.updateDOM = function () { -4344 var divBox = this.dom; -4345 if (divBox) { -4346 // update contents -4347 divBox.firstChild.innerHTML = this.content; -4348 -4349 // update class -4350 divBox.className = "timeline-event timeline-event-range ui-widget ui-state-default"; -4351 -4352 if (this.isCluster) { -4353 links.Timeline.addClassName(divBox, 'timeline-event-cluster ui-widget-header'); -4354 } -4355 -4356 // add item specific class name when provided -4357 if (this.className) { -4358 links.Timeline.addClassName(divBox, this.className); -4359 } -4360 -4361 // TODO: apply selected className? -4362 } -4363 }; -4364 -4365 /** -4366 * Reposition the item, recalculate its left, top, and width, using the current -4367 * range of the timeline and the timeline options. * -4368 * @param {links.Timeline} timeline -4369 * @override -4370 */ -4371 links.Timeline.ItemFloatingRange.prototype.updatePosition = function (timeline) { -4372 var dom = this.dom; -4373 if (dom) { -4374 var contentWidth = timeline.size.contentWidth, -4375 left = this.getLeft(timeline), // NH use getLeft -4376 right = this.getRight(timeline); // NH use getRight; -4377 -4378 // limit the width of the this, as browsers cannot draw very wide divs -4379 if (left < -contentWidth) { -4380 left = -contentWidth; -4381 } -4382 if (right > 2 * contentWidth) { -4383 right = 2 * contentWidth; -4384 } -4385 -4386 dom.style.top = this.top + "px"; -4387 dom.style.left = left + "px"; -4388 //dom.style.width = Math.max(right - left - 2 * this.borderWidth, 1) + "px"; // TODO: borderWidth -4389 dom.style.width = Math.max(right - left, 1) + "px"; -4390 } -4391 }; -4392 -4393 /** -4394 * Check if the item is visible in the timeline, and not part of a cluster -4395 * @param {Number} start -4396 * @param {Number} end -4397 * @return {boolean} visible -4398 * @override -4399 */ -4400 links.Timeline.ItemFloatingRange.prototype.isVisible = function (start, end) { -4401 if (this.cluster) { -4402 return false; -4403 } -4404 -4405 // NH check for no end value -4406 if (this.end && this.start) { -4407 return (this.end > start) -4408 && (this.start < end); -4409 } else if (this.start) { -4410 return (this.start < end); -4411 } else if (this.end) { -4412 return (this.end > start); -4413 } else {return true;} -4414 }; -4415 -4416 /** -4417 * Reposition the item -4418 * @param {Number} left -4419 * @param {Number} right -4420 * @override -4421 */ -4422 links.Timeline.ItemFloatingRange.prototype.setPosition = function (left, right) { -4423 var dom = this.dom; -4424 -4425 dom.style.left = left + 'px'; -4426 dom.style.width = (right - left) + 'px'; -4427 -4428 if (this.group) { -4429 this.top = this.group.top; -4430 dom.style.top = this.top + 'px'; -4431 } -4432 }; -4433 -4434 /** -4435 * Calculate the left position of the item -4436 * @param {links.Timeline} timeline -4437 * @return {Number} left -4438 * @override -4439 */ -4440 links.Timeline.ItemFloatingRange.prototype.getLeft = function (timeline) { -4441 // NH check for no start value -4442 if (this.start) { -4443 return timeline.timeToScreen(this.start); -4444 } else { -4445 return 0; -4446 } -4447 }; -4448 -4449 /** -4450 * Calculate the right position of the item -4451 * @param {links.Timeline} timeline -4452 * @return {Number} right -4453 * @override -4454 */ -4455 links.Timeline.ItemFloatingRange.prototype.getRight = function (timeline) { -4456 // NH check for no end value -4457 if (this.end) { -4458 return timeline.timeToScreen(this.end); -4459 } else { -4460 return timeline.size.contentWidth; -4461 } -4462 }; -4463 -4464 /** -4465 * Calculate the width of the item -4466 * @param {links.Timeline} timeline -4467 * @return {Number} width -4468 * @override -4469 */ -4470 links.Timeline.ItemFloatingRange.prototype.getWidth = function (timeline) { -4471 return this.getRight(timeline) - this.getLeft(timeline); -4472 }; -4473 -4474 /** -4475 * @constructor links.Timeline.ItemDot -4476 * @extends links.Timeline.Item -4477 * @param {Object} data Object containing parameters start, end -4478 * content, group, type, className, editable. -4479 * @param {Object} [options] Options to set initial property values -4480 * {Number} top -4481 * {Number} left -4482 * {Number} width -4483 * {Number} height -4484 */ -4485 links.Timeline.ItemDot = function (data, options) { -4486 links.Timeline.Item.call(this, data, options); -4487 }; -4488 -4489 links.Timeline.ItemDot.prototype = new links.Timeline.Item(); -4490 -4491 /** -4492 * Reflow the Item: retrieve its actual size from the DOM -4493 * @return {boolean} resized returns true if the axis is resized -4494 * @override -4495 */ -4496 links.Timeline.ItemDot.prototype.reflow = function () { -4497 var dom = this.dom, -4498 dotHeight = dom.dot.offsetHeight, -4499 dotWidth = dom.dot.offsetWidth, -4500 contentHeight = dom.content.offsetHeight, -4501 resized = ( -4502 (this.dotHeight != dotHeight) || -4503 (this.dotWidth != dotWidth) || -4504 (this.contentHeight != contentHeight) -4505 ); -4506 -4507 this.dotHeight = dotHeight; -4508 this.dotWidth = dotWidth; -4509 this.contentHeight = contentHeight; -4510 -4511 return resized; -4512 }; -4513 -4514 /** -4515 * Select the item -4516 * @override -4517 */ -4518 links.Timeline.ItemDot.prototype.select = function () { -4519 var dom = this.dom; -4520 links.Timeline.addClassName(dom, 'timeline-event-selected ui-state-active'); -4521 }; -4522 -4523 /** -4524 * Unselect the item -4525 * @override -4526 */ -4527 links.Timeline.ItemDot.prototype.unselect = function () { -4528 var dom = this.dom; -4529 links.Timeline.removeClassName(dom, 'timeline-event-selected ui-state-active'); -4530 }; -4531 -4532 /** -4533 * Creates the DOM for the item, depending on its type -4534 * @return {Element | undefined} -4535 * @override -4536 */ -4537 links.Timeline.ItemDot.prototype.createDOM = function () { -4538 // background box -4539 var divBox = document.createElement("DIV"); -4540 divBox.style.position = "absolute"; -4541 -4542 // contents box, right from the dot -4543 var divContent = document.createElement("DIV"); -4544 divContent.className = "timeline-event-content"; -4545 divBox.appendChild(divContent); -4546 -4547 // dot at start -4548 var divDot = document.createElement("DIV"); -4549 divDot.style.position = "absolute"; -4550 divDot.style.width = "0px"; -4551 divDot.style.height = "0px"; -4552 divBox.appendChild(divDot); -4553 -4554 divBox.content = divContent; -4555 divBox.dot = divDot; -4556 -4557 this.dom = divBox; -4558 this.updateDOM(); -4559 -4560 return divBox; -4561 }; -4562 -4563 /** -4564 * Append the items DOM to the given HTML container. If items DOM does not yet -4565 * exist, it will be created first. -4566 * @param {Element} container -4567 * @override -4568 */ -4569 links.Timeline.ItemDot.prototype.showDOM = function (container) { -4570 var dom = this.dom; -4571 if (!dom) { -4572 dom = this.createDOM(); -4573 } -4574 -4575 if (dom.parentNode != container) { -4576 if (dom.parentNode) { -4577 // container changed. remove it from old container first -4578 this.hideDOM(); -4579 } -4580 -4581 // append to container -4582 container.appendChild(dom); -4583 this.rendered = true; -4584 } -4585 }; -4586 -4587 /** -4588 * Remove the items DOM from the current HTML container -4589 * @override -4590 */ -4591 links.Timeline.ItemDot.prototype.hideDOM = function () { -4592 var dom = this.dom; -4593 if (dom) { -4594 if (dom.parentNode) { -4595 dom.parentNode.removeChild(dom); -4596 } -4597 this.rendered = false; -4598 } -4599 }; -4600 -4601 /** -4602 * Update the DOM of the item. This will update the content and the classes -4603 * of the item -4604 * @override -4605 */ -4606 links.Timeline.ItemDot.prototype.updateDOM = function () { -4607 if (this.dom) { -4608 var divBox = this.dom; -4609 var divDot = divBox.dot; -4610 -4611 // update contents -4612 divBox.firstChild.innerHTML = this.content; -4613 -4614 // update classes -4615 divBox.className = "timeline-event-dot-container"; -4616 divDot.className = "timeline-event timeline-event-dot ui-widget ui-state-default"; -4617 -4618 if (this.isCluster) { -4619 links.Timeline.addClassName(divBox, 'timeline-event-cluster ui-widget-header'); -4620 links.Timeline.addClassName(divDot, 'timeline-event-cluster ui-widget-header'); -4621 } -4622 -4623 // add item specific class name when provided -4624 if (this.className) { -4625 links.Timeline.addClassName(divBox, this.className); -4626 links.Timeline.addClassName(divDot, this.className); -4627 } -4628 -4629 // TODO: apply selected className? -4630 } -4631 }; -4632 -4633 /** -4634 * Reposition the item, recalculate its left, top, and width, using the current -4635 * range of the timeline and the timeline options. * -4636 * @param {links.Timeline} timeline -4637 * @override -4638 */ -4639 links.Timeline.ItemDot.prototype.updatePosition = function (timeline) { -4640 var dom = this.dom; -4641 if (dom) { -4642 var left = timeline.timeToScreen(this.start); -4643 -4644 dom.style.top = this.top + "px"; -4645 dom.style.left = (left - this.dotWidth / 2) + "px"; -4646 -4647 dom.content.style.marginLeft = (1.5 * this.dotWidth) + "px"; -4648 //dom.content.style.marginRight = (0.5 * this.dotWidth) + "px"; // TODO -4649 dom.dot.style.top = ((this.height - this.dotHeight) / 2) + "px"; -4650 } -4651 }; -4652 -4653 /** -4654 * Check if the item is visible in the timeline, and not part of a cluster. -4655 * @param {Date} start -4656 * @param {Date} end -4657 * @return {boolean} visible -4658 * @override -4659 */ -4660 links.Timeline.ItemDot.prototype.isVisible = function (start, end) { -4661 if (this.cluster) { -4662 return false; -4663 } -4664 -4665 return (this.start > start) -4666 && (this.start < end); -4667 }; -4668 -4669 /** -4670 * Reposition the item -4671 * @param {Number} left -4672 * @param {Number} right -4673 * @override -4674 */ -4675 links.Timeline.ItemDot.prototype.setPosition = function (left, right) { -4676 var dom = this.dom; -4677 -4678 dom.style.left = (left - this.dotWidth / 2) + "px"; -4679 -4680 if (this.group) { -4681 this.top = this.group.top; -4682 dom.style.top = this.top + 'px'; -4683 } -4684 }; -4685 -4686 /** -4687 * Calculate the left position of the item -4688 * @param {links.Timeline} timeline -4689 * @return {Number} left -4690 * @override -4691 */ -4692 links.Timeline.ItemDot.prototype.getLeft = function (timeline) { -4693 return timeline.timeToScreen(this.start); -4694 }; -4695 -4696 /** -4697 * Calculate the right position of the item -4698 * @param {links.Timeline} timeline -4699 * @return {Number} right -4700 * @override -4701 */ -4702 links.Timeline.ItemDot.prototype.getRight = function (timeline) { -4703 return timeline.timeToScreen(this.start) + this.width; -4704 }; -4705 -4706 /** -4707 * Retrieve the properties of an item. -4708 * @param {Number} index -4709 * @return {Object} itemData Object containing item properties:<br> -4710 * {Date} start (required), -4711 * {Date} end (optional), -4712 * {String} content (required), -4713 * {String} group (optional), -4714 * {String} className (optional) -4715 * {boolean} editable (optional) -4716 * {String} type (optional) -4717 */ -4718 links.Timeline.prototype.getItem = function (index) { -4719 if (index >= this.items.length) { -4720 throw "Cannot get item, index out of range"; -4721 } -4722 -4723 // take the original data as start, includes foreign fields -4724 var data = this.data, -4725 itemData; -4726 if (google && google.visualization && -4727 data instanceof google.visualization.DataTable) { -4728 // map the datatable columns -4729 var cols = links.Timeline.mapColumnIds(data); -4730 -4731 itemData = {}; -4732 for (var col in cols) { -4733 if (cols.hasOwnProperty(col)) { -4734 itemData[col] = this.data.getValue(index, cols[col]); -4735 } -4736 } -4737 } -4738 else if (links.Timeline.isArray(this.data)) { -4739 // read JSON array -4740 itemData = links.Timeline.clone(this.data[index]); -4741 } -4742 else { -4743 throw "Unknown data type. DataTable or Array expected."; -4744 } -4745 -4746 // override the data with current settings of the item (should be the same) -4747 var item = this.items[index]; -4748 -4749 itemData.start = new Date(item.start.valueOf()); -4750 if (item.end) { -4751 itemData.end = new Date(item.end.valueOf()); -4752 } -4753 itemData.content = item.content; -4754 if (item.group) { -4755 itemData.group = this.getGroupName(item.group); -4756 } -4757 if (item.className) { -4758 itemData.className = item.className; -4759 } -4760 if (typeof item.editable !== 'undefined') { -4761 itemData.editable = item.editable; -4762 } -4763 if (item.type) { -4764 itemData.type = item.type; -4765 } -4766 -4767 return itemData; -4768 }; -4769 -4770 -4771 /** -4772 * Retrieve the properties of a cluster. -4773 * @param {Number} index -4774 * @return {Object} clusterdata Object containing cluster properties:<br> -4775 * {Date} start (required), -4776 * {String} type (optional) -4777 * {Array} array with item data as is in getItem() -4778 */ -4779 links.Timeline.prototype.getCluster = function (index) { -4780 if (index >= this.clusters.length) { -4781 throw "Cannot get cluster, index out of range"; -4782 } -4783 -4784 var clusterData = {}, -4785 cluster = this.clusters[index], -4786 clusterItems = cluster.items; -4787 -4788 clusterData.start = new Date(cluster.start.valueOf()); -4789 if (cluster.type) { -4790 clusterData.type = cluster.type; -4791 } -4792 -4793 // push cluster item data -4794 clusterData.items = []; -4795 for(var i = 0; i < clusterItems.length; i++){ -4796 for(var j = 0; j < this.items.length; j++){ -4797 // TODO could be nicer to be able to have the item index into the cluster -4798 if(this.items[j] == clusterItems[i]) -4799 { -4800 clusterData.items.push(this.getItem(j)); -4801 break; -4802 } -4803 -4804 } -4805 } -4806 -4807 return clusterData; -4808 }; -4809 -4810 /** -4811 * Add a new item. -4812 * @param {Object} itemData Object containing item properties:<br> -4813 * {Date} start (required), -4814 * {Date} end (optional), -4815 * {String} content (required), -4816 * {String} group (optional) -4817 * {String} className (optional) -4818 * {Boolean} editable (optional) -4819 * {String} type (optional) -4820 * @param {boolean} [preventRender=false] Do not re-render timeline if true -4821 */ -4822 links.Timeline.prototype.addItem = function (itemData, preventRender) { -4823 var itemsData = [ -4824 itemData -4825 ]; -4826 -4827 this.addItems(itemsData, preventRender); -4828 }; -4829 -4830 /** -4831 * Add new items. -4832 * @param {Array} itemsData An array containing Objects. -4833 * The objects must have the following parameters: -4834 * {Date} start, -4835 * {Date} end, -4836 * {String} content with text or HTML code, -4837 * {String} group (optional) -4838 * {String} className (optional) -4839 * {String} editable (optional) -4840 * {String} type (optional) -4841 * @param {boolean} [preventRender=false] Do not re-render timeline if true -4842 */ -4843 links.Timeline.prototype.addItems = function (itemsData, preventRender) { -4844 var timeline = this, -4845 items = this.items; -4846 -4847 // append the items -4848 itemsData.forEach(function (itemData) { -4849 var index = items.length; -4850 items.push(timeline.createItem(itemData)); -4851 timeline.updateData(index, itemData); -4852 -4853 // note: there is no need to add the item to the renderQueue, that -4854 // will be done when this.render() is executed and all items are -4855 // filtered again. -4856 }); -4857 -4858 // prepare data for clustering, by filtering and sorting by type -4859 if (this.options.cluster) { -4860 this.clusterGenerator.updateData(); -4861 } -4862 -4863 if (!preventRender) { -4864 this.render({ -4865 animate: false -4866 }); -4867 } -4868 }; -4869 -4870 /** -4871 * Create an item object, containing all needed parameters -4872 * @param {Object} itemData Object containing parameters start, end -4873 * content, group. -4874 * @return {Object} item -4875 */ -4876 links.Timeline.prototype.createItem = function(itemData) { -4877 var type = itemData.type || (itemData.end ? 'range' : this.options.style); -4878 var data = links.Timeline.clone(itemData); -4879 data.type = type; -4880 data.group = this.getGroup(itemData.group); -4881 // TODO: optimize this, when creating an item, all data is copied twice... -4882 -4883 // TODO: is initialTop needed? -4884 var initialTop, -4885 options = this.options; -4886 if (options.axisOnTop) { -4887 initialTop = this.size.axis.height + options.eventMarginAxis + options.eventMargin / 2; -4888 } -4889 else { -4890 initialTop = this.size.contentHeight - options.eventMarginAxis - options.eventMargin / 2; -4891 } -4892 -4893 if (type in this.itemTypes) { -4894 return new this.itemTypes[type](data, {'top': initialTop}) -4895 } -4896 -4897 console.log('ERROR: Unknown event type "' + type + '"'); -4898 return new links.Timeline.Item(data, { -4899 'top': initialTop -4900 }); -4901 }; -4902 -4903 /** -4904 * Edit an item -4905 * @param {Number} index -4906 * @param {Object} itemData Object containing item properties:<br> -4907 * {Date} start (required), -4908 * {Date} end (optional), -4909 * {String} content (required), -4910 * {String} group (optional) -4911 * @param {boolean} [preventRender=false] Do not re-render timeline if true -4912 */ -4913 links.Timeline.prototype.changeItem = function (index, itemData, preventRender) { -4914 var oldItem = this.items[index]; -4915 if (!oldItem) { -4916 throw "Cannot change item, index out of range"; -4917 } -4918 -4919 // replace item, merge the changes -4920 var newItem = this.createItem({ -4921 'start': itemData.hasOwnProperty('start') ? itemData.start : oldItem.start, -4922 'end': itemData.hasOwnProperty('end') ? itemData.end : oldItem.end, -4923 'content': itemData.hasOwnProperty('content') ? itemData.content : oldItem.content, -4924 'group': itemData.hasOwnProperty('group') ? itemData.group : this.getGroupName(oldItem.group), -4925 'className': itemData.hasOwnProperty('className') ? itemData.className : oldItem.className, -4926 'editable': itemData.hasOwnProperty('editable') ? itemData.editable : oldItem.editable, -4927 'type': itemData.hasOwnProperty('type') ? itemData.type : oldItem.type -4928 }); -4929 this.items[index] = newItem; -4930 -4931 // append the changes to the render queue -4932 this.renderQueue.hide.push(oldItem); -4933 this.renderQueue.show.push(newItem); -4934 -4935 // update the original data table -4936 this.updateData(index, itemData); -4937 -4938 // prepare data for clustering, by filtering and sorting by type -4939 if (this.options.cluster) { -4940 this.clusterGenerator.updateData(); -4941 } -4942 -4943 if (!preventRender) { -4944 // redraw timeline -4945 this.render({ -4946 animate: false -4947 }); -4948 -4949 if (this.selection && this.selection.index == index) { -4950 newItem.select(); -4951 } -4952 } -4953 }; -4954 -4955 /** -4956 * Delete all groups -4957 */ -4958 links.Timeline.prototype.deleteGroups = function () { -4959 this.groups = []; -4960 this.groupIndexes = {}; -4961 }; -4962 -4963 -4964 /** -4965 * Get a group by the group name. When the group does not exist, -4966 * it will be created. -4967 * @param {String} groupName the name of the group -4968 * @return {Object} groupObject -4969 */ -4970 links.Timeline.prototype.getGroup = function (groupName) { -4971 var groups = this.groups, -4972 groupIndexes = this.groupIndexes, -4973 groupObj = undefined; -4974 -4975 var groupIndex = groupIndexes[groupName]; -4976 if (groupIndex == undefined && groupName != undefined) { // not null or undefined -4977 groupObj = { -4978 'content': groupName, -4979 'labelTop': 0, -4980 'lineTop': 0 -4981 // note: this object will lateron get addition information, -4982 // such as height and width of the group -4983 }; -4984 groups.push(groupObj); -4985 // sort the groups -4986 if (this.options.groupsOrder == true) { -4987 groups = groups.sort(function (a, b) { -4988 if (a.content > b.content) { -4989 return 1; -4990 } -4991 if (a.content < b.content) { -4992 return -1; -4993 } -4994 return 0; -4995 }); -4996 } else if (typeof(this.options.groupsOrder) == "function") { -4997 groups = groups.sort(this.options.groupsOrder) -4998 } -4999 -5000 // rebuilt the groupIndexes -5001 for (var i = 0, iMax = groups.length; i < iMax; i++) { -5002 groupIndexes[groups[i].content] = i; -5003 } -5004 } -5005 else { -5006 groupObj = groups[groupIndex]; -5007 } -5008 -5009 return groupObj; -5010 }; -5011 -5012 /** -5013 * Get the group name from a group object. -5014 * @param {Object} groupObj -5015 * @return {String} groupName the name of the group, or undefined when group -5016 * was not provided -5017 */ -5018 links.Timeline.prototype.getGroupName = function (groupObj) { -5019 return groupObj ? groupObj.content : undefined; -5020 }; -5021 -5022 /** -5023 * Cancel a change item -5024 * This method can be called insed an event listener which catches the "change" -5025 * event. The changed event position will be undone. -5026 */ -5027 links.Timeline.prototype.cancelChange = function () { -5028 this.applyChange = false; -5029 }; -5030 -5031 /** -5032 * Cancel deletion of an item -5033 * This method can be called insed an event listener which catches the "delete" -5034 * event. Deletion of the event will be undone. -5035 */ -5036 links.Timeline.prototype.cancelDelete = function () { -5037 this.applyDelete = false; -5038 }; -5039 -5040 -5041 /** -5042 * Cancel creation of a new item -5043 * This method can be called insed an event listener which catches the "new" -5044 * event. Creation of the new the event will be undone. -5045 */ -5046 links.Timeline.prototype.cancelAdd = function () { -5047 this.applyAdd = false; -5048 }; -5049 -5050 -5051 /** -5052 * Select an event. The visible chart range will be moved such that the selected -5053 * event is placed in the middle. -5054 * For example selection = [{row: 5}]; -5055 * @param {Array} selection An array with a column row, containing the row -5056 * number (the id) of the event to be selected. -5057 * @return {boolean} true if selection is succesfully set, else false. -5058 */ -5059 links.Timeline.prototype.setSelection = function(selection) { -5060 if (selection != undefined && selection.length > 0) { -5061 if (selection[0].row != undefined) { -5062 var index = selection[0].row; -5063 if (this.items[index]) { -5064 var item = this.items[index]; -5065 this.selectItem(index); -5066 -5067 // move the visible chart range to the selected event. -5068 var start = item.start; -5069 var end = item.end; -5070 var middle; // number -5071 if (end != undefined) { -5072 middle = (end.valueOf() + start.valueOf()) / 2; -5073 } else { -5074 middle = start.valueOf(); -5075 } -5076 var diff = (this.end.valueOf() - this.start.valueOf()), -5077 newStart = new Date(middle - diff/2), -5078 newEnd = new Date(middle + diff/2); -5079 -5080 this.setVisibleChartRange(newStart, newEnd); -5081 -5082 return true; -5083 } -5084 } -5085 } -5086 else { -5087 // unselect current selection -5088 this.unselectItem(); -5089 } -5090 return false; -5091 }; -5092 -5093 /** -5094 * Retrieve the currently selected event -5095 * @return {Array} sel An array with a column row, containing the row number -5096 * of the selected event. If there is no selection, an -5097 * empty array is returned. -5098 */ -5099 links.Timeline.prototype.getSelection = function() { -5100 var sel = []; -5101 if (this.selection) { -5102 if(this.selection.index !== undefined) -5103 { -5104 sel.push({"row": this.selection.index}); -5105 } else { -5106 sel.push({"cluster": this.selection.cluster}); -5107 } -5108 } -5109 return sel; -5110 }; -5111 -5112 -5113 /** -5114 * Select an item by its index -5115 * @param {Number} index -5116 */ -5117 links.Timeline.prototype.selectItem = function(index) { -5118 this.unselectItem(); -5119 -5120 this.selection = undefined; -5121 -5122 if (this.items[index] != undefined) { -5123 var item = this.items[index], -5124 domItem = item.dom; -5125 -5126 this.selection = { -5127 'index': index -5128 }; -5129 -5130 if (item && item.dom) { -5131 // TODO: move adjusting the domItem to the item itself -5132 if (this.isEditable(item)) { -5133 item.dom.style.cursor = 'move'; -5134 } -5135 item.select(); -5136 } -5137 this.repaintDeleteButton(); -5138 this.repaintDragAreas(); -5139 } -5140 }; -5141 -5142 /** -5143 * Select an cluster by its index -5144 * @param {Number} index -5145 */ -5146 links.Timeline.prototype.selectCluster = function(index) { -5147 this.unselectItem(); -5148 -5149 this.selection = undefined; -5150 -5151 if (this.clusters[index] != undefined) { -5152 this.selection = { -5153 'cluster': index -5154 }; -5155 this.repaintDeleteButton(); -5156 this.repaintDragAreas(); -5157 } -5158 }; -5159 -5160 /** -5161 * Check if an item is currently selected -5162 * @param {Number} index -5163 * @return {boolean} true if row is selected, else false -5164 */ -5165 links.Timeline.prototype.isSelected = function (index) { -5166 return (this.selection && this.selection.index == index); -5167 }; -5168 -5169 /** -5170 * Unselect the currently selected event (if any) -5171 */ -5172 links.Timeline.prototype.unselectItem = function() { -5173 if (this.selection && this.selection.index !== undefined) { -5174 var item = this.items[this.selection.index]; -5175 -5176 if (item && item.dom) { -5177 var domItem = item.dom; -5178 domItem.style.cursor = ''; -5179 item.unselect(); -5180 } -5181 -5182 this.selection = undefined; -5183 this.repaintDeleteButton(); -5184 this.repaintDragAreas(); -5185 } -5186 }; -5187 -5188 -5189 /** -5190 * Stack the items such that they don't overlap. The items will have a minimal -5191 * distance equal to options.eventMargin. -5192 * @param {boolean | undefined} animate if animate is true, the items are -5193 * moved to their new position animated -5194 * defaults to false. -5195 */ -5196 links.Timeline.prototype.stackItems = function(animate) { -5197 if (animate == undefined) { -5198 animate = false; -5199 } -5200 -5201 // calculate the order and final stack position of the items -5202 var stack = this.stack; -5203 if (!stack) { -5204 stack = {}; -5205 this.stack = stack; -5206 } -5207 stack.sortedItems = this.stackOrder(this.renderedItems); -5208 stack.finalItems = this.stackCalculateFinal(stack.sortedItems); -5209 -5210 if (animate || stack.timer) { -5211 // move animated to the final positions -5212 var timeline = this; -5213 var step = function () { -5214 var arrived = timeline.stackMoveOneStep(stack.sortedItems, -5215 stack.finalItems); -5216 -5217 timeline.repaint(); -5218 -5219 if (!arrived) { -5220 stack.timer = setTimeout(step, 30); -5221 } -5222 else { -5223 delete stack.timer; -5224 } -5225 }; -5226 -5227 if (!stack.timer) { -5228 stack.timer = setTimeout(step, 30); -5229 } -5230 } -5231 else { -5232 // move immediately to the final positions -5233 this.stackMoveToFinal(stack.sortedItems, stack.finalItems); -5234 } -5235 }; -5236 -5237 /** -5238 * Cancel any running animation -5239 */ -5240 links.Timeline.prototype.stackCancelAnimation = function() { -5241 if (this.stack && this.stack.timer) { -5242 clearTimeout(this.stack.timer); -5243 delete this.stack.timer; -5244 } -5245 }; -5246 -5247 links.Timeline.prototype.getItemsByGroup = function(items) { -5248 var itemsByGroup = {}; -5249 for (var i = 0; i < items.length; ++i) { -5250 var item = items[i]; -5251 var group = "undefined"; -5252 -5253 if (item.group) { -5254 if (item.group.content) { -5255 group = item.group.content; -5256 } else { -5257 group = item.group; -5258 } -5259 } -5260 -5261 if (!itemsByGroup[group]) { -5262 itemsByGroup[group] = []; -5263 } -5264 -5265 itemsByGroup[group].push(item); -5266 } -5267 -5268 return itemsByGroup; -5269 }; -5270 -5271 /** -5272 * Order the items in the array this.items. The default order is determined via: -5273 * - Ranges go before boxes and dots. -5274 * - The item with the oldest start time goes first -5275 * If a custom function has been provided via the stackorder option, then this will be used. -5276 * @param {Array} items Array with items -5277 * @return {Array} sortedItems Array with sorted items -5278 */ -5279 links.Timeline.prototype.stackOrder = function(items) { -5280 // TODO: store the sorted items, to have less work later on -5281 var sortedItems = items.concat([]); -5282 -5283 //if a customer stack order function exists, use it. -5284 var f = this.options.customStackOrder && (typeof this.options.customStackOrder === 'function') ? this.options.customStackOrder : function (a, b) -5285 { -5286 if ((a instanceof links.Timeline.ItemRange || a instanceof links.Timeline.ItemFloatingRange) && -5287 !(b instanceof links.Timeline.ItemRange || b instanceof links.Timeline.ItemFloatingRange)) { -5288 return -1; -5289 } -5290 -5291 if (!(a instanceof links.Timeline.ItemRange || a instanceof links.Timeline.ItemFloatingRange) && -5292 (b instanceof links.Timeline.ItemRange || b instanceof links.Timeline.ItemFloatingRange)) { -5293 return 1; -5294 } -5295 -5296 return (a.left - b.left); -5297 }; -5298 -5299 sortedItems.sort(f); -5300 -5301 return sortedItems; -5302 }; -5303 -5304 /** -5305 * Adjust vertical positions of the events such that they don't overlap each -5306 * other. -5307 * @param {timeline.Item[]} items -5308 * @return {Object[]} finalItems -5309 */ -5310 links.Timeline.prototype.stackCalculateFinal = function(items) { -5311 var size = this.size, -5312 options = this.options, -5313 axisOnTop = options.axisOnTop, -5314 eventMargin = options.eventMargin, -5315 eventMarginAxis = options.eventMarginAxis, -5316 groupBase = (axisOnTop) -5317 ? size.axis.height + eventMarginAxis + eventMargin/2 -5318 : size.contentHeight - eventMarginAxis - eventMargin/2, -5319 groupedItems, groupFinalItems, finalItems = []; -5320 -5321 groupedItems = this.getItemsByGroup(items); -5322 -5323 // -5324 // groupedItems contains all items by group, plus it may contain an -5325 // additional "undefined" group which contains all items with no group. We -5326 // first process the grouped items, and then the ungrouped -5327 // -5328 for (j = 0; j<this.groups.length; ++j) { -5329 var group = this.groups[j]; -5330 -5331 if (!groupedItems[group.content]) { -5332 if (axisOnTop) { -5333 groupBase += options.groupMinHeight + eventMargin; -5334 } else { -5335 groupBase -= (options.groupMinHeight + eventMargin); -5336 } -5337 continue; -5338 } -5339 -5340 // initialize final positions and fill finalItems -5341 groupFinalItems = this.finalItemsPosition(groupedItems[group.content], groupBase, group); -5342 groupFinalItems.forEach(function(item) { -5343 finalItems.push(item); -5344 }); -5345 -5346 if (axisOnTop) { -5347 groupBase += group.itemsHeight + eventMargin; -5348 } else { -5349 groupBase -= (group.itemsHeight + eventMargin); -5350 } -5351 } -5352 -5353 // -5354 // Ungrouped items' turn now! -5355 // -5356 if (groupedItems["undefined"]) { -5357 // initialize final positions and fill finalItems -5358 groupFinalItems = this.finalItemsPosition(groupedItems["undefined"], groupBase); -5359 groupFinalItems.forEach(function(item) { -5360 finalItems.push(item); -5361 }); -5362 } -5363 -5364 return finalItems; -5365 }; -5366 -5367 links.Timeline.prototype.finalItemsPosition = function(items, groupBase, group) { -5368 var i, -5369 iMax, -5370 options = this.options, -5371 axisOnTop = options.axisOnTop, -5372 eventMargin = options.eventMargin, -5373 groupFinalItems; -5374 -5375 // initialize final positions and fill finalItems -5376 groupFinalItems = this.initialItemsPosition(items, groupBase); -5377 -5378 // calculate new, non-overlapping positions -5379 for (i = 0, iMax = groupFinalItems.length; i < iMax; i++) { -5380 var finalItem = groupFinalItems[i]; -5381 var collidingItem = null; -5382 -5383 if (this.options.stackEvents) { -5384 do { -5385 // TODO: optimize checking for overlap. when there is a gap without items, -5386 // you only need to check for items from the next item on, not from zero -5387 collidingItem = this.stackItemsCheckOverlap(groupFinalItems, i, 0, i-1); -5388 if (collidingItem != null) { -5389 // There is a collision. Reposition the event above the colliding element -5390 if (axisOnTop) { -5391 finalItem.top = collidingItem.top + collidingItem.height + eventMargin; -5392 } -5393 else { -5394 finalItem.top = collidingItem.top - finalItem.height - eventMargin; -5395 } -5396 finalItem.bottom = finalItem.top + finalItem.height; -5397 } -5398 } while (collidingItem); -5399 } -5400 -5401 if (group) { -5402 if (axisOnTop) { -5403 group.itemsHeight = (group.itemsHeight) -5404 ? Math.max(group.itemsHeight, finalItem.bottom - groupBase) -5405 : finalItem.height + eventMargin; -5406 } else { -5407 group.itemsHeight = (group.itemsHeight) -5408 ? Math.max(group.itemsHeight, groupBase - finalItem.top) -5409 : finalItem.height + eventMargin; -5410 } -5411 } -5412 } -5413 -5414 return groupFinalItems; -5415 }; -5416 -5417 links.Timeline.prototype.initialItemsPosition = function(items, groupBase) { -5418 var options = this.options, -5419 axisOnTop = options.axisOnTop, -5420 finalItems = []; -5421 -5422 for (var i = 0, iMax = items.length; i < iMax; ++i) { -5423 var item = items[i], -5424 top, -5425 bottom, -5426 height = item.height, -5427 width = item.getWidth(this), -5428 right = item.getRight(this), -5429 left = right - width; -5430 -5431 top = (axisOnTop) ? groupBase -5432 : groupBase - height; -5433 -5434 bottom = top + height; -5435 -5436 finalItems.push({ -5437 'left': left, -5438 'top': top, -5439 'right': right, -5440 'bottom': bottom, -5441 'height': height, -5442 'item': item -5443 }); -5444 } -5445 -5446 return finalItems; -5447 }; -5448 -5449 /** -5450 * Move the events one step in the direction of their final positions -5451 * @param {Array} currentItems Array with the real items and their current -5452 * positions -5453 * @param {Array} finalItems Array with objects containing the final -5454 * positions of the items -5455 * @return {boolean} arrived True if all items have reached their final -5456 * location, else false -5457 */ -5458 links.Timeline.prototype.stackMoveOneStep = function(currentItems, finalItems) { -5459 var arrived = true; -5460 -5461 // apply new positions animated -5462 for (var i = 0, iMax = finalItems.length; i < iMax; i++) { -5463 var finalItem = finalItems[i], -5464 item = finalItem.item; -5465 -5466 var topNow = parseInt(item.top); -5467 var topFinal = parseInt(finalItem.top); -5468 var diff = (topFinal - topNow); -5469 if (diff) { -5470 var step = (topFinal == topNow) ? 0 : ((topFinal > topNow) ? 1 : -1); -5471 if (Math.abs(diff) > 4) step = diff / 4; -5472 var topNew = parseInt(topNow + step); -5473 -5474 if (topNew != topFinal) { -5475 arrived = false; -5476 } -5477 -5478 item.top = topNew; -5479 item.bottom = item.top + item.height; -5480 } -5481 else { -5482 item.top = finalItem.top; -5483 item.bottom = finalItem.bottom; -5484 } -5485 -5486 item.left = finalItem.left; -5487 item.right = finalItem.right; -5488 } -5489 -5490 return arrived; -5491 }; -5492 -5493 -5494 -5495 /** -5496 * Move the events from their current position to the final position -5497 * @param {Array} currentItems Array with the real items and their current -5498 * positions -5499 * @param {Array} finalItems Array with objects containing the final -5500 * positions of the items -5501 */ -5502 links.Timeline.prototype.stackMoveToFinal = function(currentItems, finalItems) { -5503 // Put the events directly at there final position -5504 for (var i = 0, iMax = finalItems.length; i < iMax; i++) { -5505 var finalItem = finalItems[i], -5506 current = finalItem.item; -5507 -5508 current.left = finalItem.left; -5509 current.top = finalItem.top; -5510 current.right = finalItem.right; -5511 current.bottom = finalItem.bottom; -5512 } -5513 }; -5514 -5515 -5516 -5517 /** -5518 * Check if the destiny position of given item overlaps with any -5519 * of the other items from index itemStart to itemEnd. -5520 * @param {Array} items Array with items -5521 * @param {int} itemIndex Number of the item to be checked for overlap -5522 * @param {int} itemStart First item to be checked. -5523 * @param {int} itemEnd Last item to be checked. -5524 * @return {Object} colliding item, or undefined when no collisions -5525 */ -5526 links.Timeline.prototype.stackItemsCheckOverlap = function(items, itemIndex, -5527 itemStart, itemEnd) { -5528 var eventMargin = this.options.eventMargin, -5529 collision = this.collision; -5530 -5531 // we loop from end to start, as we suppose that the chance of a -5532 // collision is larger for items at the end, so check these first. -5533 var item1 = items[itemIndex]; -5534 for (var i = itemEnd; i >= itemStart; i--) { -5535 var item2 = items[i]; -5536 if (collision(item1, item2, eventMargin)) { -5537 if (i != itemIndex) { -5538 return item2; -5539 } -5540 } -5541 } -5542 -5543 return undefined; -5544 }; -5545 -5546 /** -5547 * Test if the two provided items collide -5548 * The items must have parameters left, right, top, and bottom. -5549 * @param {Element} item1 The first item -5550 * @param {Element} item2 The second item -5551 * @param {Number} margin A minimum required margin. Optional. -5552 * If margin is provided, the two items will be -5553 * marked colliding when they overlap or -5554 * when the margin between the two is smaller than -5555 * the requested margin. -5556 * @return {boolean} true if item1 and item2 collide, else false -5557 */ -5558 links.Timeline.prototype.collision = function(item1, item2, margin) { -5559 // set margin if not specified -5560 if (margin == undefined) { -5561 margin = 0; -5562 } -5563 -5564 // calculate if there is overlap (collision) -5565 return (item1.left - margin < item2.right && -5566 item1.right + margin > item2.left && -5567 item1.top - margin < item2.bottom && -5568 item1.bottom + margin > item2.top); -5569 }; -5570 -5571 -5572 /** -5573 * fire an event -5574 * @param {String} event The name of an event, for example "rangechange" or "edit" -5575 */ -5576 links.Timeline.prototype.trigger = function (event) { -5577 // built up properties -5578 var properties = null; -5579 switch (event) { -5580 case 'rangechange': -5581 case 'rangechanged': -5582 properties = { -5583 'start': new Date(this.start.valueOf()), -5584 'end': new Date(this.end.valueOf()) -5585 }; -5586 break; -5587 -5588 case 'timechange': -5589 case 'timechanged': -5590 properties = { -5591 'time': new Date(this.customTime.valueOf()) -5592 }; -5593 break; -5594 } -5595 -5596 // trigger the links event bus -5597 links.events.trigger(this, event, properties); -5598 -5599 // trigger the google event bus -5600 if (google && google.visualization) { -5601 google.visualization.events.trigger(this, event, properties); -5602 } -5603 }; -5604 -5605 -5606 /** -5607 * Cluster the events -5608 */ -5609 links.Timeline.prototype.clusterItems = function () { -5610 if (!this.options.cluster) { -5611 return; -5612 } -5613 -5614 var clusters = this.clusterGenerator.getClusters(this.conversion.factor, this.options.clusterMaxItems); -5615 if (this.clusters != clusters) { -5616 // cluster level changed -5617 var queue = this.renderQueue; -5618 -5619 // remove the old clusters from the scene -5620 if (this.clusters) { -5621 this.clusters.forEach(function (cluster) { -5622 queue.hide.push(cluster); -5623 -5624 // unlink the items -5625 cluster.items.forEach(function (item) { -5626 item.cluster = undefined; -5627 }); -5628 }); -5629 } -5630 -5631 // append the new clusters -5632 clusters.forEach(function (cluster) { -5633 // don't add to the queue.show here, will be done in .filterItems() -5634 -5635 // link all items to the cluster -5636 cluster.items.forEach(function (item) { -5637 item.cluster = cluster; -5638 }); -5639 }); -5640 -5641 this.clusters = clusters; -5642 } -5643 }; -5644 -5645 /** -5646 * Filter the visible events -5647 */ -5648 links.Timeline.prototype.filterItems = function () { -5649 var queue = this.renderQueue, -5650 window = (this.end - this.start), -5651 start = new Date(this.start.valueOf() - window), -5652 end = new Date(this.end.valueOf() + window); -5653 -5654 function filter (arr) { -5655 arr.forEach(function (item) { -5656 var rendered = item.rendered; -5657 var visible = item.isVisible(start, end); -5658 if (rendered != visible) { -5659 if (rendered) { -5660 queue.hide.push(item); // item is rendered but no longer visible -5661 } -5662 if (visible && (queue.show.indexOf(item) == -1)) { -5663 queue.show.push(item); // item is visible but neither rendered nor queued up to be rendered -5664 } -5665 } -5666 }); -5667 } -5668 -5669 // filter all items and all clusters -5670 filter(this.items); -5671 if (this.clusters) { -5672 filter(this.clusters); -5673 } -5674 }; -5675 -5676 /** ------------------------------------------------------------------------ **/ -5677 -5678 /** -5679 * @constructor links.Timeline.ClusterGenerator -5680 * Generator which creates clusters of items, based on the visible range in -5681 * the Timeline. There is a set of cluster levels which is cached. -5682 * @param {links.Timeline} timeline -5683 */ -5684 links.Timeline.ClusterGenerator = function (timeline) { -5685 this.timeline = timeline; -5686 this.clear(); -5687 }; -5688 -5689 /** -5690 * Clear all cached clusters and data, and initialize all variables -5691 */ -5692 links.Timeline.ClusterGenerator.prototype.clear = function () { -5693 // cache containing created clusters for each cluster level -5694 this.items = []; -5695 this.groups = {}; -5696 this.clearCache(); -5697 }; -5698 -5699 /** -5700 * Clear the cached clusters -5701 */ -5702 links.Timeline.ClusterGenerator.prototype.clearCache = function () { -5703 // cache containing created clusters for each cluster level -5704 this.cache = {}; -5705 this.cacheLevel = -1; -5706 this.cache[this.cacheLevel] = []; -5707 }; -5708 -5709 /** -5710 * Set the items to be clustered. -5711 * This will clear cached clusters. -5712 * @param {Item[]} items -5713 * @param {Object} [options] Available options: -5714 * {boolean} applyOnChangedLevel -5715 * If true (default), the changed data is applied -5716 * as soon the cluster level changes. If false, -5717 * The changed data is applied immediately -5718 */ -5719 links.Timeline.ClusterGenerator.prototype.setData = function (items, options) { -5720 this.items = items || []; -5721 this.dataChanged = true; -5722 this.applyOnChangedLevel = true; -5723 if (options && options.applyOnChangedLevel) { -5724 this.applyOnChangedLevel = options.applyOnChangedLevel; -5725 } -5726 // console.log('clustergenerator setData applyOnChangedLevel=' + this.applyOnChangedLevel); // TODO: cleanup -5727 }; -5728 -5729 /** -5730 * Update the current data set: clear cache, and recalculate the clustering for -5731 * the current level -5732 */ -5733 links.Timeline.ClusterGenerator.prototype.updateData = function () { -5734 this.dataChanged = true; -5735 this.applyOnChangedLevel = false; -5736 }; -5737 -5738 /** -5739 * Filter the items per group. -5740 * @private -5741 */ -5742 links.Timeline.ClusterGenerator.prototype.filterData = function () { -5743 // filter per group -5744 var items = this.items || []; -5745 var groups = {}; -5746 this.groups = groups; -5747 -5748 // split the items per group -5749 items.forEach(function (item) { -5750 // put the item in the correct group -5751 var groupName = item.group ? item.group.content : ''; -5752 var group = groups[groupName]; -5753 if (!group) { -5754 group = []; -5755 groups[groupName] = group; -5756 } -5757 group.push(item); -5758 -5759 // calculate the center of the item -5760 if (item.start) { -5761 if (item.end) { -5762 // range -5763 item.center = (item.start.valueOf() + item.end.valueOf()) / 2; -5764 } -5765 else { -5766 // box, dot -5767 item.center = item.start.valueOf(); -5768 } -5769 } -5770 }); -5771 -5772 // sort the items per group -5773 for (var groupName in groups) { -5774 if (groups.hasOwnProperty(groupName)) { -5775 groups[groupName].sort(function (a, b) { -5776 return (a.center - b.center); -5777 }); -5778 } -5779 } -5780 -5781 this.dataChanged = false; -5782 }; -5783 -5784 /** -5785 * Cluster the events which are too close together -5786 * @param {Number} scale The scale of the current window, -5787 * defined as (windowWidth / (endDate - startDate)) -5788 * @return {Item[]} clusters -5789 */ -5790 links.Timeline.ClusterGenerator.prototype.getClusters = function (scale, maxItems) { -5791 var level = -1, -5792 granularity = 2, // TODO: what granularity is needed for the cluster levels? -5793 timeWindow = 0; // milliseconds -5794 -5795 if (scale > 0) { -5796 level = Math.round(Math.log(100 / scale) / Math.log(granularity)); -5797 timeWindow = Math.pow(granularity, level); -5798 } -5799 -5800 // clear the cache when and re-filter the data when needed. -5801 if (this.dataChanged) { -5802 var levelChanged = (level != this.cacheLevel); -5803 var applyDataNow = this.applyOnChangedLevel ? levelChanged : true; -5804 if (applyDataNow) { -5805 // TODO: currently drawn clusters should be removed! mark them as invisible? -5806 this.clearCache(); -5807 this.filterData(); -5808 // console.log('clustergenerator: cache cleared...'); // TODO: cleanup -5809 } -5810 } -5811 -5812 this.cacheLevel = level; -5813 var clusters = this.cache[level]; -5814 if (!clusters) { -5815 // console.log('clustergenerator: create cluster level ' + level); // TODO: cleanup -5816 clusters = []; -5817 -5818 // TODO: spit this method, it is too large -5819 for (var groupName in this.groups) { -5820 if (this.groups.hasOwnProperty(groupName)) { -5821 var items = this.groups[groupName]; -5822 var iMax = items.length; -5823 var i = 0; -5824 while (i < iMax) { -5825 // find all items around current item, within the timeWindow -5826 var item = items[i]; -5827 var neighbors = 1; // start at 1, to include itself) -5828 -5829 // loop through items left from the current item -5830 var j = i - 1; -5831 while (j >= 0 && (item.center - items[j].center) < timeWindow / 2) { -5832 if (!items[j].cluster) { -5833 neighbors++; -5834 } -5835 j--; -5836 } -5837 -5838 // loop through items right from the current item -5839 var k = i + 1; -5840 while (k < items.length && (items[k].center - item.center) < timeWindow / 2) { -5841 neighbors++; -5842 k++; -5843 } -5844 -5845 // loop through the created clusters -5846 var l = clusters.length - 1; -5847 while (l >= 0 && (item.center - clusters[l].center) < timeWindow / 2) { -5848 if (item.group == clusters[l].group) { -5849 neighbors++; -5850 } -5851 l--; -5852 } -5853 -5854 // aggregate until the number of items is within maxItems -5855 if (neighbors > maxItems) { -5856 // too busy in this window. -5857 var num = neighbors - maxItems + 1; -5858 var clusterItems = []; -5859 -5860 // append the items to the cluster, -5861 // and calculate the average start for the cluster -5862 var avg = undefined; // number. average of all start dates -5863 var min = undefined; // number. minimum of all start dates -5864 var max = undefined; // number. maximum of all start and end dates -5865 var containsRanges = false; -5866 var count = 0; -5867 var m = i; -5868 while (clusterItems.length < num && m < items.length) { -5869 var p = items[m]; -5870 var start = p.start.valueOf(); -5871 var end = p.end ? p.end.valueOf() : p.start.valueOf(); -5872 clusterItems.push(p); -5873 if (count) { -5874 // calculate new average (use fractions to prevent overflow) -5875 avg = (count / (count + 1)) * avg + (1 / (count + 1)) * p.center; -5876 } -5877 else { -5878 avg = p.center; -5879 } -5880 min = (min != undefined) ? Math.min(min, start) : start; -5881 max = (max != undefined) ? Math.max(max, end) : end; -5882 containsRanges = containsRanges || (p instanceof links.Timeline.ItemRange || p instanceof links.Timeline.ItemFloatingRange); -5883 count++; -5884 m++; -5885 } -5886 -5887 var cluster; -5888 var title = 'Cluster containing ' + count + -5889 ' events. Zoom in to see the individual events.'; -5890 var content = '<div title="' + title + '">' + count + ' events</div>'; -5891 var group = item.group ? item.group.content : undefined; -5892 if (containsRanges) { -5893 // boxes and/or ranges -5894 cluster = this.timeline.createItem({ -5895 'start': new Date(min), -5896 'end': new Date(max), -5897 'content': content, -5898 'group': group -5899 }); -5900 } -5901 else { -5902 // boxes only -5903 cluster = this.timeline.createItem({ -5904 'start': new Date(avg), -5905 'content': content, -5906 'group': group -5907 }); -5908 } -5909 cluster.isCluster = true; -5910 cluster.items = clusterItems; -5911 cluster.items.forEach(function (item) { -5912 item.cluster = cluster; -5913 }); -5914 -5915 clusters.push(cluster); -5916 i += num; -5917 } -5918 else { -5919 delete item.cluster; -5920 i += 1; -5921 } -5922 } -5923 } -5924 } -5925 -5926 this.cache[level] = clusters; -5927 } -5928 -5929 return clusters; -5930 }; -5931 -5932 -5933 /** ------------------------------------------------------------------------ **/ -5934 -5935 -5936 /** -5937 * Event listener (singleton) -5938 */ -5939 links.events = links.events || { -5940 'listeners': [], -5941 -5942 /** -5943 * Find a single listener by its object -5944 * @param {Object} object -5945 * @return {Number} index -1 when not found -5946 */ -5947 'indexOf': function (object) { -5948 var listeners = this.listeners; -5949 for (var i = 0, iMax = this.listeners.length; i < iMax; i++) { -5950 var listener = listeners[i]; -5951 if (listener && listener.object == object) { -5952 return i; -5953 } -5954 } -5955 return -1; -5956 }, -5957 -5958 /** -5959 * Add an event listener -5960 * @param {Object} object -5961 * @param {String} event The name of an event, for example 'select' -5962 * @param {function} callback The callback method, called when the -5963 * event takes place -5964 */ -5965 'addListener': function (object, event, callback) { -5966 var index = this.indexOf(object); -5967 var listener = this.listeners[index]; -5968 if (!listener) { -5969 listener = { -5970 'object': object, -5971 'events': {} -5972 }; -5973 this.listeners.push(listener); -5974 } -5975 -5976 var callbacks = listener.events[event]; -5977 if (!callbacks) { -5978 callbacks = []; -5979 listener.events[event] = callbacks; -5980 } -5981 -5982 // add the callback if it does not yet exist -5983 if (callbacks.indexOf(callback) == -1) { -5984 callbacks.push(callback); -5985 } -5986 }, -5987 -5988 /** -5989 * Remove an event listener -5990 * @param {Object} object -5991 * @param {String} event The name of an event, for example 'select' -5992 * @param {function} callback The registered callback method -5993 */ -5994 'removeListener': function (object, event, callback) { -5995 var index = this.indexOf(object); -5996 var listener = this.listeners[index]; -5997 if (listener) { -5998 var callbacks = listener.events[event]; -5999 if (callbacks) { -6000 var index = callbacks.indexOf(callback); -6001 if (index != -1) { -6002 callbacks.splice(index, 1); -6003 } -6004 -6005 // remove the array when empty -6006 if (callbacks.length == 0) { -6007 delete listener.events[event]; -6008 } -6009 } -6010 -6011 // count the number of registered events. remove listener when empty -6012 var count = 0; -6013 var events = listener.events; -6014 for (var e in events) { -6015 if (events.hasOwnProperty(e)) { -6016 count++; -6017 } -6018 } -6019 if (count == 0) { -6020 delete this.listeners[index]; -6021 } -6022 } -6023 }, -6024 -6025 /** -6026 * Remove all registered event listeners -6027 */ -6028 'removeAllListeners': function () { -6029 this.listeners = []; -6030 }, -6031 -6032 /** -6033 * Trigger an event. All registered event handlers will be called -6034 * @param {Object} object -6035 * @param {String} event -6036 * @param {Object} properties (optional) -6037 */ -6038 'trigger': function (object, event, properties) { -6039 var index = this.indexOf(object); -6040 var listener = this.listeners[index]; -6041 if (listener) { -6042 var callbacks = listener.events[event]; -6043 if (callbacks) { -6044 for (var i = 0, iMax = callbacks.length; i < iMax; i++) { -6045 callbacks[i](properties); -6046 } -6047 } -6048 } -6049 } -6050 }; -6051 -6052 -6053 /** ------------------------------------------------------------------------ **/ -6054 -6055 /** -6056 * @constructor links.Timeline.StepDate -6057 * The class StepDate is an iterator for dates. You provide a start date and an -6058 * end date. The class itself determines the best scale (step size) based on the -6059 * provided start Date, end Date, and minimumStep. -6060 * -6061 * If minimumStep is provided, the step size is chosen as close as possible -6062 * to the minimumStep but larger than minimumStep. If minimumStep is not -6063 * provided, the scale is set to 1 DAY. -6064 * The minimumStep should correspond with the onscreen size of about 6 characters -6065 * -6066 * Alternatively, you can set a scale by hand. -6067 * After creation, you can initialize the class by executing start(). Then you -6068 * can iterate from the start date to the end date via next(). You can check if -6069 * the end date is reached with the function end(). After each step, you can -6070 * retrieve the current date via get(). -6071 * The class step has scales ranging from milliseconds, seconds, minutes, hours, -6072 * days, to years. -6073 * -6074 * Version: 1.2 -6075 * -6076 * @param {Date} start The start date, for example new Date(2010, 9, 21) -6077 * or new Date(2010, 9, 21, 23, 45, 00) -6078 * @param {Date} end The end date -6079 * @param {Number} minimumStep Optional. Minimum step size in milliseconds -6080 */ -6081 links.Timeline.StepDate = function(start, end, minimumStep) { -6082 -6083 // variables -6084 this.current = new Date(); -6085 this._start = new Date(); -6086 this._end = new Date(); -6087 -6088 this.autoScale = true; -6089 this.scale = links.Timeline.StepDate.SCALE.DAY; -6090 this.step = 1; -6091 -6092 // initialize the range -6093 this.setRange(start, end, minimumStep); -6094 }; -6095 -6096 /// enum scale -6097 links.Timeline.StepDate.SCALE = { -6098 MILLISECOND: 1, -6099 SECOND: 2, -6100 MINUTE: 3, -6101 HOUR: 4, -6102 DAY: 5, -6103 WEEKDAY: 6, -6104 MONTH: 7, -6105 YEAR: 8 -6106 }; -6107 -6108 -6109 /** -6110 * Set a new range -6111 * If minimumStep is provided, the step size is chosen as close as possible -6112 * to the minimumStep but larger than minimumStep. If minimumStep is not -6113 * provided, the scale is set to 1 DAY. -6114 * The minimumStep should correspond with the onscreen size of about 6 characters -6115 * @param {Date} start The start date and time. -6116 * @param {Date} end The end date and time. -6117 * @param {int} minimumStep Optional. Minimum step size in milliseconds -6118 */ -6119 links.Timeline.StepDate.prototype.setRange = function(start, end, minimumStep) { -6120 if (!(start instanceof Date) || !(end instanceof Date)) { -6121 //throw "No legal start or end date in method setRange"; -6122 return; -6123 } -6124 -6125 this._start = (start != undefined) ? new Date(start.valueOf()) : new Date(); -6126 this._end = (end != undefined) ? new Date(end.valueOf()) : new Date(); -6127 -6128 if (this.autoScale) { -6129 this.setMinimumStep(minimumStep); -6130 } -6131 }; -6132 -6133 /** -6134 * Set the step iterator to the start date. -6135 */ -6136 links.Timeline.StepDate.prototype.start = function() { -6137 this.current = new Date(this._start.valueOf()); -6138 this.roundToMinor(); -6139 }; -6140 -6141 /** -6142 * Round the current date to the first minor date value -6143 * This must be executed once when the current date is set to start Date -6144 */ -6145 links.Timeline.StepDate.prototype.roundToMinor = function() { -6146 // round to floor -6147 // IMPORTANT: we have no breaks in this switch! (this is no bug) -6148 //noinspection FallthroughInSwitchStatementJS -6149 switch (this.scale) { -6150 case links.Timeline.StepDate.SCALE.YEAR: -6151 this.current.setFullYear(this.step * Math.floor(this.current.getFullYear() / this.step)); -6152 this.current.setMonth(0); -6153 case links.Timeline.StepDate.SCALE.MONTH: this.current.setDate(1); -6154 case links.Timeline.StepDate.SCALE.DAY: // intentional fall through -6155 case links.Timeline.StepDate.SCALE.WEEKDAY: this.current.setHours(0); -6156 case links.Timeline.StepDate.SCALE.HOUR: this.current.setMinutes(0); -6157 case links.Timeline.StepDate.SCALE.MINUTE: this.current.setSeconds(0); -6158 case links.Timeline.StepDate.SCALE.SECOND: this.current.setMilliseconds(0); -6159 //case links.Timeline.StepDate.SCALE.MILLISECOND: // nothing to do for milliseconds -6160 } -6161 -6162 if (this.step != 1) { -6163 // round down to the first minor value that is a multiple of the current step size -6164 switch (this.scale) { -6165 case links.Timeline.StepDate.SCALE.MILLISECOND: this.current.setMilliseconds(this.current.getMilliseconds() - this.current.getMilliseconds() % this.step); break; -6166 case links.Timeline.StepDate.SCALE.SECOND: this.current.setSeconds(this.current.getSeconds() - this.current.getSeconds() % this.step); break; -6167 case links.Timeline.StepDate.SCALE.MINUTE: this.current.setMinutes(this.current.getMinutes() - this.current.getMinutes() % this.step); break; -6168 case links.Timeline.StepDate.SCALE.HOUR: this.current.setHours(this.current.getHours() - this.current.getHours() % this.step); break; -6169 case links.Timeline.StepDate.SCALE.WEEKDAY: // intentional fall through -6170 case links.Timeline.StepDate.SCALE.DAY: this.current.setDate((this.current.getDate()-1) - (this.current.getDate()-1) % this.step + 1); break; -6171 case links.Timeline.StepDate.SCALE.MONTH: this.current.setMonth(this.current.getMonth() - this.current.getMonth() % this.step); break; -6172 case links.Timeline.StepDate.SCALE.YEAR: this.current.setFullYear(this.current.getFullYear() - this.current.getFullYear() % this.step); break; -6173 default: break; -6174 } -6175 } -6176 }; -6177 -6178 /** -6179 * Check if the end date is reached -6180 * @return {boolean} true if the current date has passed the end date -6181 */ -6182 links.Timeline.StepDate.prototype.end = function () { -6183 return (this.current.valueOf() > this._end.valueOf()); -6184 }; -6185 -6186 /** -6187 * Do the next step -6188 */ -6189 links.Timeline.StepDate.prototype.next = function() { -6190 var prev = this.current.valueOf(); -6191 -6192 // Two cases, needed to prevent issues with switching daylight savings -6193 // (end of March and end of October) -6194 if (this.current.getMonth() < 6) { -6195 switch (this.scale) { -6196 case links.Timeline.StepDate.SCALE.MILLISECOND: -6197 -6198 this.current = new Date(this.current.valueOf() + this.step); break; -6199 case links.Timeline.StepDate.SCALE.SECOND: this.current = new Date(this.current.valueOf() + this.step * 1000); break; -6200 case links.Timeline.StepDate.SCALE.MINUTE: this.current = new Date(this.current.valueOf() + this.step * 1000 * 60); break; -6201 case links.Timeline.StepDate.SCALE.HOUR: -6202 this.current = new Date(this.current.valueOf() + this.step * 1000 * 60 * 60); -6203 // in case of skipping an hour for daylight savings, adjust the hour again (else you get: 0h 5h 9h ... instead of 0h 4h 8h ...) -6204 var h = this.current.getHours(); -6205 this.current.setHours(h - (h % this.step)); -6206 break; -6207 case links.Timeline.StepDate.SCALE.WEEKDAY: // intentional fall through -6208 case links.Timeline.StepDate.SCALE.DAY: this.current.setDate(this.current.getDate() + this.step); break; -6209 case links.Timeline.StepDate.SCALE.MONTH: this.current.setMonth(this.current.getMonth() + this.step); break; -6210 case links.Timeline.StepDate.SCALE.YEAR: this.current.setFullYear(this.current.getFullYear() + this.step); break; -6211 default: break; -6212 } -6213 } -6214 else { -6215 switch (this.scale) { -6216 case links.Timeline.StepDate.SCALE.MILLISECOND: this.current = new Date(this.current.valueOf() + this.step); break; -6217 case links.Timeline.StepDate.SCALE.SECOND: this.current.setSeconds(this.current.getSeconds() + this.step); break; -6218 case links.Timeline.StepDate.SCALE.MINUTE: this.current.setMinutes(this.current.getMinutes() + this.step); break; -6219 case links.Timeline.StepDate.SCALE.HOUR: this.current.setHours(this.current.getHours() + this.step); break; -6220 case links.Timeline.StepDate.SCALE.WEEKDAY: // intentional fall through -6221 case links.Timeline.StepDate.SCALE.DAY: this.current.setDate(this.current.getDate() + this.step); break; -6222 case links.Timeline.StepDate.SCALE.MONTH: this.current.setMonth(this.current.getMonth() + this.step); break; -6223 case links.Timeline.StepDate.SCALE.YEAR: this.current.setFullYear(this.current.getFullYear() + this.step); break; -6224 default: break; -6225 } -6226 } -6227 -6228 if (this.step != 1) { -6229 // round down to the correct major value -6230 switch (this.scale) { -6231 case links.Timeline.StepDate.SCALE.MILLISECOND: if(this.current.getMilliseconds() < this.step) this.current.setMilliseconds(0); break; -6232 case links.Timeline.StepDate.SCALE.SECOND: if(this.current.getSeconds() < this.step) this.current.setSeconds(0); break; -6233 case links.Timeline.StepDate.SCALE.MINUTE: if(this.current.getMinutes() < this.step) this.current.setMinutes(0); break; -6234 case links.Timeline.StepDate.SCALE.HOUR: if(this.current.getHours() < this.step) this.current.setHours(0); break; -6235 case links.Timeline.StepDate.SCALE.WEEKDAY: // intentional fall through -6236 case links.Timeline.StepDate.SCALE.DAY: if(this.current.getDate() < this.step+1) this.current.setDate(1); break; -6237 case links.Timeline.StepDate.SCALE.MONTH: if(this.current.getMonth() < this.step) this.current.setMonth(0); break; -6238 case links.Timeline.StepDate.SCALE.YEAR: break; // nothing to do for year -6239 default: break; -6240 } -6241 } -6242 -6243 // safety mechanism: if current time is still unchanged, move to the end -6244 if (this.current.valueOf() == prev) { -6245 this.current = new Date(this._end.valueOf()); -6246 } -6247 }; -6248 -6249 -6250 /** -6251 * Get the current datetime -6252 * @return {Date} current The current date -6253 */ -6254 links.Timeline.StepDate.prototype.getCurrent = function() { -6255 return this.current; -6256 }; -6257 -6258 /** -6259 * Set a custom scale. Autoscaling will be disabled. -6260 * For example setScale(SCALE.MINUTES, 5) will result -6261 * in minor steps of 5 minutes, and major steps of an hour. -6262 * -6263 * @param {links.Timeline.StepDate.SCALE} newScale -6264 * A scale. Choose from SCALE.MILLISECOND, -6265 * SCALE.SECOND, SCALE.MINUTE, SCALE.HOUR, -6266 * SCALE.WEEKDAY, SCALE.DAY, SCALE.MONTH, -6267 * SCALE.YEAR. -6268 * @param {Number} newStep A step size, by default 1. Choose for -6269 * example 1, 2, 5, or 10. -6270 */ -6271 links.Timeline.StepDate.prototype.setScale = function(newScale, newStep) { -6272 this.scale = newScale; -6273 -6274 if (newStep > 0) { -6275 this.step = newStep; -6276 } -6277 -6278 this.autoScale = false; -6279 }; -6280 -6281 /** -6282 * Enable or disable autoscaling -6283 * @param {boolean} enable If true, autoascaling is set true -6284 */ -6285 links.Timeline.StepDate.prototype.setAutoScale = function (enable) { -6286 this.autoScale = enable; -6287 }; -6288 -6289 -6290 /** -6291 * Automatically determine the scale that bests fits the provided minimum step -6292 * @param {Number} minimumStep The minimum step size in milliseconds -6293 */ -6294 links.Timeline.StepDate.prototype.setMinimumStep = function(minimumStep) { -6295 if (minimumStep == undefined) { -6296 return; -6297 } -6298 -6299 var stepYear = (1000 * 60 * 60 * 24 * 30 * 12); -6300 var stepMonth = (1000 * 60 * 60 * 24 * 30); -6301 var stepDay = (1000 * 60 * 60 * 24); -6302 var stepHour = (1000 * 60 * 60); -6303 var stepMinute = (1000 * 60); -6304 var stepSecond = (1000); -6305 var stepMillisecond= (1); -6306 -6307 // find the smallest step that is larger than the provided minimumStep -6308 if (stepYear*1000 > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.YEAR; this.step = 1000;} -6309 if (stepYear*500 > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.YEAR; this.step = 500;} -6310 if (stepYear*100 > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.YEAR; this.step = 100;} -6311 if (stepYear*50 > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.YEAR; this.step = 50;} -6312 if (stepYear*10 > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.YEAR; this.step = 10;} -6313 if (stepYear*5 > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.YEAR; this.step = 5;} -6314 if (stepYear > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.YEAR; this.step = 1;} -6315 if (stepMonth*3 > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.MONTH; this.step = 3;} -6316 if (stepMonth > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.MONTH; this.step = 1;} -6317 if (stepDay*5 > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.DAY; this.step = 5;} -6318 if (stepDay*2 > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.DAY; this.step = 2;} -6319 if (stepDay > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.DAY; this.step = 1;} -6320 if (stepDay/2 > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.WEEKDAY; this.step = 1;} -6321 if (stepHour*4 > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.HOUR; this.step = 4;} -6322 if (stepHour > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.HOUR; this.step = 1;} -6323 if (stepMinute*15 > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.MINUTE; this.step = 15;} -6324 if (stepMinute*10 > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.MINUTE; this.step = 10;} -6325 if (stepMinute*5 > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.MINUTE; this.step = 5;} -6326 if (stepMinute > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.MINUTE; this.step = 1;} -6327 if (stepSecond*15 > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.SECOND; this.step = 15;} -6328 if (stepSecond*10 > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.SECOND; this.step = 10;} -6329 if (stepSecond*5 > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.SECOND; this.step = 5;} -6330 if (stepSecond > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.SECOND; this.step = 1;} -6331 if (stepMillisecond*200 > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.MILLISECOND; this.step = 200;} -6332 if (stepMillisecond*100 > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.MILLISECOND; this.step = 100;} -6333 if (stepMillisecond*50 > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.MILLISECOND; this.step = 50;} -6334 if (stepMillisecond*10 > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.MILLISECOND; this.step = 10;} -6335 if (stepMillisecond*5 > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.MILLISECOND; this.step = 5;} -6336 if (stepMillisecond > minimumStep) {this.scale = links.Timeline.StepDate.SCALE.MILLISECOND; this.step = 1;} -6337 }; -6338 -6339 /** -6340 * Snap a date to a rounded value. The snap intervals are dependent on the -6341 * current scale and step. -6342 * @param {Date} date the date to be snapped -6343 */ -6344 links.Timeline.StepDate.prototype.snap = function(date) { -6345 if (this.scale == links.Timeline.StepDate.SCALE.YEAR) { -6346 var year = date.getFullYear() + Math.round(date.getMonth() / 12); -6347 date.setFullYear(Math.round(year / this.step) * this.step); -6348 date.setMonth(0); -6349 date.setDate(0); -6350 date.setHours(0); -6351 date.setMinutes(0); -6352 date.setSeconds(0); -6353 date.setMilliseconds(0); -6354 } -6355 else if (this.scale == links.Timeline.StepDate.SCALE.MONTH) { -6356 if (date.getDate() > 15) { -6357 date.setDate(1); -6358 date.setMonth(date.getMonth() + 1); -6359 // important: first set Date to 1, after that change the month. -6360 } -6361 else { -6362 date.setDate(1); -6363 } -6364 -6365 date.setHours(0); -6366 date.setMinutes(0); -6367 date.setSeconds(0); -6368 date.setMilliseconds(0); -6369 } -6370 else if (this.scale == links.Timeline.StepDate.SCALE.DAY || -6371 this.scale == links.Timeline.StepDate.SCALE.WEEKDAY) { -6372 switch (this.step) { -6373 case 5: -6374 case 2: -6375 date.setHours(Math.round(date.getHours() / 24) * 24); break; -6376 default: -6377 date.setHours(Math.round(date.getHours() / 12) * 12); break; -6378 } -6379 date.setMinutes(0); -6380 date.setSeconds(0); -6381 date.setMilliseconds(0); -6382 } -6383 else if (this.scale == links.Timeline.StepDate.SCALE.HOUR) { -6384 switch (this.step) { -6385 case 4: -6386 date.setMinutes(Math.round(date.getMinutes() / 60) * 60); break; -6387 default: -6388 date.setMinutes(Math.round(date.getMinutes() / 30) * 30); break; -6389 } -6390 date.setSeconds(0); -6391 date.setMilliseconds(0); -6392 } else if (this.scale == links.Timeline.StepDate.SCALE.MINUTE) { -6393 switch (this.step) { -6394 case 15: -6395 case 10: -6396 date.setMinutes(Math.round(date.getMinutes() / 5) * 5); -6397 date.setSeconds(0); -6398 break; -6399 case 5: -6400 date.setSeconds(Math.round(date.getSeconds() / 60) * 60); break; -6401 default: -6402 date.setSeconds(Math.round(date.getSeconds() / 30) * 30); break; -6403 } -6404 date.setMilliseconds(0); -6405 } -6406 else if (this.scale == links.Timeline.StepDate.SCALE.SECOND) { -6407 switch (this.step) { -6408 case 15: -6409 case 10: -6410 date.setSeconds(Math.round(date.getSeconds() / 5) * 5); -6411 date.setMilliseconds(0); -6412 break; -6413 case 5: -6414 date.setMilliseconds(Math.round(date.getMilliseconds() / 1000) * 1000); break; -6415 default: -6416 date.setMilliseconds(Math.round(date.getMilliseconds() / 500) * 500); break; -6417 } -6418 } -6419 else if (this.scale == links.Timeline.StepDate.SCALE.MILLISECOND) { -6420 var step = this.step > 5 ? this.step / 2 : 1; -6421 date.setMilliseconds(Math.round(date.getMilliseconds() / step) * step); -6422 } -6423 }; -6424 -6425 /** -6426 * Check if the current step is a major step (for example when the step -6427 * is DAY, a major step is each first day of the MONTH) -6428 * @return {boolean} true if current date is major, else false. -6429 */ -6430 links.Timeline.StepDate.prototype.isMajor = function() { -6431 switch (this.scale) { -6432 case links.Timeline.StepDate.SCALE.MILLISECOND: -6433 return (this.current.getMilliseconds() == 0); -6434 case links.Timeline.StepDate.SCALE.SECOND: -6435 return (this.current.getSeconds() == 0); -6436 case links.Timeline.StepDate.SCALE.MINUTE: -6437 return (this.current.getHours() == 0) && (this.current.getMinutes() == 0); -6438 // Note: this is no bug. Major label is equal for both minute and hour scale -6439 case links.Timeline.StepDate.SCALE.HOUR: -6440 return (this.current.getHours() == 0); -6441 case links.Timeline.StepDate.SCALE.WEEKDAY: // intentional fall through -6442 case links.Timeline.StepDate.SCALE.DAY: -6443 return (this.current.getDate() == 1); -6444 case links.Timeline.StepDate.SCALE.MONTH: -6445 return (this.current.getMonth() == 0); -6446 case links.Timeline.StepDate.SCALE.YEAR: -6447 return false; -6448 default: -6449 return false; -6450 } -6451 }; -6452 -6453 -6454 /** -6455 * Returns formatted text for the minor axislabel, depending on the current -6456 * date and the scale. For example when scale is MINUTE, the current time is -6457 * formatted as "hh:mm". -6458 * @param {Object} options -6459 * @param {Date} [date] custom date. if not provided, current date is taken -6460 */ -6461 links.Timeline.StepDate.prototype.getLabelMinor = function(options, date) { -6462 if (date == undefined) { -6463 date = this.current; -6464 } -6465 -6466 switch (this.scale) { -6467 case links.Timeline.StepDate.SCALE.MILLISECOND: return String(date.getMilliseconds()); -6468 case links.Timeline.StepDate.SCALE.SECOND: return String(date.getSeconds()); -6469 case links.Timeline.StepDate.SCALE.MINUTE: -6470 return this.addZeros(date.getHours(), 2) + ":" + this.addZeros(date.getMinutes(), 2); -6471 case links.Timeline.StepDate.SCALE.HOUR: -6472 return this.addZeros(date.getHours(), 2) + ":" + this.addZeros(date.getMinutes(), 2); -6473 case links.Timeline.StepDate.SCALE.WEEKDAY: return options.DAYS_SHORT[date.getDay()] + ' ' + date.getDate(); -6474 case links.Timeline.StepDate.SCALE.DAY: return String(date.getDate()); -6475 case links.Timeline.StepDate.SCALE.MONTH: return options.MONTHS_SHORT[date.getMonth()]; // month is zero based -6476 case links.Timeline.StepDate.SCALE.YEAR: return String(date.getFullYear()); -6477 default: return ""; -6478 } -6479 }; -6480 -6481 -6482 /** -6483 * Returns formatted text for the major axislabel, depending on the current -6484 * date and the scale. For example when scale is MINUTE, the major scale is -6485 * hours, and the hour will be formatted as "hh". -6486 * @param {Object} options -6487 * @param {Date} [date] custom date. if not provided, current date is taken -6488 */ -6489 links.Timeline.StepDate.prototype.getLabelMajor = function(options, date) { -6490 if (date == undefined) { -6491 date = this.current; -6492 } -6493 -6494 switch (this.scale) { -6495 case links.Timeline.StepDate.SCALE.MILLISECOND: -6496 return this.addZeros(date.getHours(), 2) + ":" + -6497 this.addZeros(date.getMinutes(), 2) + ":" + -6498 this.addZeros(date.getSeconds(), 2); -6499 case links.Timeline.StepDate.SCALE.SECOND: -6500 return date.getDate() + " " + -6501 options.MONTHS[date.getMonth()] + " " + -6502 this.addZeros(date.getHours(), 2) + ":" + -6503 this.addZeros(date.getMinutes(), 2); -6504 case links.Timeline.StepDate.SCALE.MINUTE: -6505 return options.DAYS[date.getDay()] + " " + -6506 date.getDate() + " " + -6507 options.MONTHS[date.getMonth()] + " " + -6508 date.getFullYear(); -6509 case links.Timeline.StepDate.SCALE.HOUR: -6510 return options.DAYS[date.getDay()] + " " + -6511 date.getDate() + " " + -6512 options.MONTHS[date.getMonth()] + " " + -6513 date.getFullYear(); -6514 case links.Timeline.StepDate.SCALE.WEEKDAY: -6515 case links.Timeline.StepDate.SCALE.DAY: -6516 return options.MONTHS[date.getMonth()] + " " + -6517 date.getFullYear(); -6518 case links.Timeline.StepDate.SCALE.MONTH: -6519 return String(date.getFullYear()); -6520 default: -6521 return ""; -6522 } -6523 }; -6524 -6525 /** -6526 * Add leading zeros to the given value to match the desired length. -6527 * For example addZeros(123, 5) returns "00123" -6528 * @param {int} value A value -6529 * @param {int} len Desired final length -6530 * @return {string} value with leading zeros -6531 */ -6532 links.Timeline.StepDate.prototype.addZeros = function(value, len) { -6533 var str = "" + value; -6534 while (str.length < len) { -6535 str = "0" + str; -6536 } -6537 return str; -6538 }; -6539 -6540 -6541 -6542 /** ------------------------------------------------------------------------ **/ -6543 -6544 /** -6545 * Image Loader service. -6546 * can be used to get a callback when a certain image is loaded -6547 * -6548 */ -6549 links.imageloader = (function () { -6550 var urls = {}; // the loaded urls -6551 var callbacks = {}; // the urls currently being loaded. Each key contains -6552 // an array with callbacks -6553 -6554 /** -6555 * Check if an image url is loaded -6556 * @param {String} url -6557 * @return {boolean} loaded True when loaded, false when not loaded -6558 * or when being loaded -6559 */ -6560 function isLoaded (url) { -6561 if (urls[url] == true) { -6562 return true; -6563 } -6564 -6565 var image = new Image(); -6566 image.src = url; -6567 if (image.complete) { -6568 return true; -6569 } -6570 -6571 return false; -6572 } -6573 -6574 /** -6575 * Check if an image url is being loaded -6576 * @param {String} url -6577 * @return {boolean} loading True when being loaded, false when not loading -6578 * or when already loaded -6579 */ -6580 function isLoading (url) { -6581 return (callbacks[url] != undefined); -6582 } -6583 -6584 /** -6585 * Load given image url -6586 * @param {String} url -6587 * @param {function} callback -6588 * @param {boolean} sendCallbackWhenAlreadyLoaded optional -6589 */ -6590 function load (url, callback, sendCallbackWhenAlreadyLoaded) { -6591 if (sendCallbackWhenAlreadyLoaded == undefined) { -6592 sendCallbackWhenAlreadyLoaded = true; -6593 } -6594 -6595 if (isLoaded(url)) { -6596 if (sendCallbackWhenAlreadyLoaded) { -6597 callback(url); -6598 } -6599 return; -6600 } -6601 -6602 if (isLoading(url) && !sendCallbackWhenAlreadyLoaded) { -6603 return; -6604 } -6605 -6606 var c = callbacks[url]; -6607 if (!c) { -6608 var image = new Image(); -6609 image.src = url; -6610 -6611 c = []; -6612 callbacks[url] = c; -6613 -6614 image.onload = function (event) { -6615 urls[url] = true; -6616 delete callbacks[url]; -6617 -6618 for (var i = 0; i < c.length; i++) { -6619 c[i](url); -6620 } -6621 } -6622 } -6623 -6624 if (c.indexOf(callback) == -1) { -6625 c.push(callback); -6626 } -6627 } -6628 -6629 /** -6630 * Load a set of images, and send a callback as soon as all images are -6631 * loaded -6632 * @param {String[]} urls -6633 * @param {function } callback -6634 * @param {boolean} sendCallbackWhenAlreadyLoaded -6635 */ -6636 function loadAll (urls, callback, sendCallbackWhenAlreadyLoaded) { -6637 // list all urls which are not yet loaded -6638 var urlsLeft = []; -6639 urls.forEach(function (url) { -6640 if (!isLoaded(url)) { -6641 urlsLeft.push(url); -6642 } -6643 }); -6644 -6645 if (urlsLeft.length) { -6646 // there are unloaded images -6647 var countLeft = urlsLeft.length; -6648 urlsLeft.forEach(function (url) { -6649 load(url, function () { -6650 countLeft--; -6651 if (countLeft == 0) { -6652 // done! -6653 callback(); -6654 } -6655 }, sendCallbackWhenAlreadyLoaded); -6656 }); -6657 } -6658 else { -6659 // we are already done! -6660 if (sendCallbackWhenAlreadyLoaded) { -6661 callback(); -6662 } -6663 } -6664 } -6665 -6666 /** -6667 * Recursively retrieve all image urls from the images located inside a given -6668 * HTML element -6669 * @param {Node} elem -6670 * @param {String[]} urls Urls will be added here (no duplicates) -6671 */ -6672 function filterImageUrls (elem, urls) { -6673 var child = elem.firstChild; -6674 while (child) { -6675 if (child.tagName == 'IMG') { -6676 var url = child.src; -6677 if (urls.indexOf(url) == -1) { -6678 urls.push(url); -6679 } -6680 } -6681 -6682 filterImageUrls(child, urls); -6683 -6684 child = child.nextSibling; -6685 } -6686 } -6687 -6688 return { -6689 'isLoaded': isLoaded, -6690 'isLoading': isLoading, -6691 'load': load, -6692 'loadAll': loadAll, -6693 'filterImageUrls': filterImageUrls -6694 }; -6695 })(); -6696 -6697 -6698 /** ------------------------------------------------------------------------ **/ -6699 -6700 -6701 /** -6702 * Add and event listener. Works for all browsers -6703 * @param {Element} element An html element -6704 * @param {string} action The action, for example "click", -6705 * without the prefix "on" -6706 * @param {function} listener The callback function to be executed -6707 * @param {boolean} useCapture -6708 */ -6709 links.Timeline.addEventListener = function (element, action, listener, useCapture) { -6710 if (element.addEventListener) { -6711 if (useCapture === undefined) -6712 useCapture = false; -6713 -6714 if (action === "mousewheel" && navigator.userAgent.indexOf("Firefox") >= 0) { -6715 action = "DOMMouseScroll"; // For Firefox -6716 } -6717 -6718 element.addEventListener(action, listener, useCapture); -6719 } else { -6720 element.attachEvent("on" + action, listener); // IE browsers -6721 } -6722 }; -6723 -6724 /** -6725 * Remove an event listener from an element -6726 * @param {Element} element An html dom element -6727 * @param {string} action The name of the event, for example "mousedown" -6728 * @param {function} listener The listener function -6729 * @param {boolean} useCapture -6730 */ -6731 links.Timeline.removeEventListener = function(element, action, listener, useCapture) { -6732 if (element.removeEventListener) { -6733 // non-IE browsers -6734 if (useCapture === undefined) -6735 useCapture = false; -6736 -6737 if (action === "mousewheel" && navigator.userAgent.indexOf("Firefox") >= 0) { -6738 action = "DOMMouseScroll"; // For Firefox -6739 } -6740 -6741 element.removeEventListener(action, listener, useCapture); -6742 } else { -6743 // IE browsers -6744 element.detachEvent("on" + action, listener); -6745 } -6746 }; -6747 -6748 -6749 /** -6750 * Get HTML element which is the target of the event -6751 * @param {Event} event -6752 * @return {Element} target element -6753 */ -6754 links.Timeline.getTarget = function (event) { -6755 // code from http://www.quirksmode.org/js/events_properties.html -6756 if (!event) { -6757 event = window.event; -6758 } -6759 -6760 var target; -6761 -6762 if (event.target) { -6763 target = event.target; -6764 } -6765 else if (event.srcElement) { -6766 target = event.srcElement; -6767 } -6768 -6769 if (target.nodeType != undefined && target.nodeType == 3) { -6770 // defeat Safari bug -6771 target = target.parentNode; -6772 } -6773 -6774 return target; -6775 }; -6776 -6777 /** -6778 * Stop event propagation -6779 */ -6780 links.Timeline.stopPropagation = function (event) { -6781 if (!event) -6782 event = window.event; -6783 -6784 if (event.stopPropagation) { -6785 event.stopPropagation(); // non-IE browsers -6786 } -6787 else { -6788 event.cancelBubble = true; // IE browsers -6789 } -6790 }; -6791 -6792 -6793 /** -6794 * Cancels the event if it is cancelable, without stopping further propagation of the event. -6795 */ -6796 links.Timeline.preventDefault = function (event) { -6797 if (!event) -6798 event = window.event; -6799 -6800 if (event.preventDefault) { -6801 event.preventDefault(); // non-IE browsers -6802 } -6803 else { -6804 event.returnValue = false; // IE browsers -6805 } -6806 }; -6807 -6808 -6809 /** -6810 * Retrieve the absolute left value of a DOM element -6811 * @param {Element} elem A dom element, for example a div -6812 * @return {number} left The absolute left position of this element -6813 * in the browser page. -6814 */ -6815 links.Timeline.getAbsoluteLeft = function(elem) { -6816 var doc = document.documentElement; -6817 var body = document.body; -6818 -6819 var left = elem.offsetLeft; -6820 var e = elem.offsetParent; -6821 while (e != null && e != body && e != doc) { -6822 left += e.offsetLeft; -6823 left -= e.scrollLeft; -6824 e = e.offsetParent; -6825 } -6826 return left; -6827 }; -6828 -6829 /** -6830 * Retrieve the absolute top value of a DOM element -6831 * @param {Element} elem A dom element, for example a div -6832 * @return {number} top The absolute top position of this element -6833 * in the browser page. -6834 */ -6835 links.Timeline.getAbsoluteTop = function(elem) { -6836 var doc = document.documentElement; -6837 var body = document.body; -6838 -6839 var top = elem.offsetTop; -6840 var e = elem.offsetParent; -6841 while (e != null && e != body && e != doc) { -6842 top += e.offsetTop; -6843 top -= e.scrollTop; -6844 e = e.offsetParent; -6845 } -6846 return top; -6847 }; -6848 -6849 /** -6850 * Get the absolute, vertical mouse position from an event. -6851 * @param {Event} event -6852 * @return {Number} pageY -6853 */ -6854 links.Timeline.getPageY = function (event) { -6855 if (('targetTouches' in event) && event.targetTouches.length) { -6856 event = event.targetTouches[0]; -6857 } -6858 -6859 if ('pageY' in event) { -6860 return event.pageY; -6861 } -6862 -6863 // calculate pageY from clientY -6864 var clientY = event.clientY; -6865 var doc = document.documentElement; -6866 var body = document.body; -6867 return clientY + -6868 ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - -6869 ( doc && doc.clientTop || body && body.clientTop || 0 ); -6870 }; -6871 -6872 /** -6873 * Get the absolute, horizontal mouse position from an event. -6874 * @param {Event} event -6875 * @return {Number} pageX -6876 */ -6877 links.Timeline.getPageX = function (event) { -6878 if (('targetTouches' in event) && event.targetTouches.length) { -6879 event = event.targetTouches[0]; -6880 } -6881 -6882 if ('pageX' in event) { -6883 return event.pageX; -6884 } -6885 -6886 // calculate pageX from clientX -6887 var clientX = event.clientX; -6888 var doc = document.documentElement; -6889 var body = document.body; -6890 return clientX + -6891 ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - -6892 ( doc && doc.clientLeft || body && body.clientLeft || 0 ); -6893 }; -6894 -6895 /** -6896 * Adds one or more className's to the given elements style -6897 * @param {Element} elem -6898 * @param {String} className -6899 */ -6900 links.Timeline.addClassName = function(elem, className) { -6901 var classes = elem.className.split(' '); -6902 var classesToAdd = className.split(' '); -6903 -6904 var added = false; -6905 for (var i=0; i<classesToAdd.length; i++) { -6906 if (classes.indexOf(classesToAdd[i]) == -1) { -6907 classes.push(classesToAdd[i]); // add the class to the array -6908 added = true; -6909 } -6910 } -6911 -6912 if (added) { -6913 elem.className = classes.join(' '); -6914 } -6915 }; -6916 -6917 /** -6918 * Removes one or more className's from the given elements style -6919 * @param {Element} elem -6920 * @param {String} className -6921 */ -6922 links.Timeline.removeClassName = function(elem, className) { -6923 var classes = elem.className.split(' '); -6924 var classesToRemove = className.split(' '); -6925 -6926 var removed = false; -6927 for (var i=0; i<classesToRemove.length; i++) { -6928 var index = classes.indexOf(classesToRemove[i]); -6929 if (index != -1) { -6930 classes.splice(index, 1); // remove the class from the array -6931 removed = true; -6932 } -6933 } -6934 -6935 if (removed) { -6936 elem.className = classes.join(' '); -6937 } -6938 }; -6939 -6940 /** -6941 * Check if given object is a Javascript Array -6942 * @param {*} obj -6943 * @return {Boolean} isArray true if the given object is an array -6944 */ -6945 // See http://stackoverflow.com/questions/2943805/javascript-instanceof-typeof-in-gwt-jsni -6946 links.Timeline.isArray = function (obj) { -6947 if (obj instanceof Array) { -6948 return true; -6949 } -6950 return (Object.prototype.toString.call(obj) === '[object Array]'); -6951 }; -6952 -6953 /** -6954 * Shallow clone an object -6955 * @param {Object} object -6956 * @return {Object} clone -6957 */ -6958 links.Timeline.clone = function (object) { -6959 var clone = {}; -6960 for (var prop in object) { -6961 if (object.hasOwnProperty(prop)) { -6962 clone[prop] = object[prop]; -6963 } -6964 } -6965 return clone; -6966 }; -6967 -6968 /** -6969 * parse a JSON date -6970 * @param {Date | String | Number} date Date object to be parsed. Can be: -6971 * - a Date object like new Date(), -6972 * - a long like 1356970529389, -6973 * an ISO String like "2012-12-31T16:16:07.213Z", -6974 * or a .Net Date string like -6975 * "\/Date(1356970529389)\/" -6976 * @return {Date} parsedDate -6977 */ -6978 links.Timeline.parseJSONDate = function (date) { -6979 if (date == undefined) { -6980 return undefined; -6981 } -6982 -6983 //test for date -6984 if (date instanceof Date) { -6985 return date; -6986 } -6987 -6988 // test for MS format. -6989 // FIXME: will fail on a Number -6990 var m = date.match(/\/Date\((-?\d+)([-\+]?\d{2})?(\d{2})?\)\//i); -6991 if (m) { -6992 var offset = m[2] -6993 ? (3600000 * m[2]) // hrs offset -6994 + (60000 * m[3] * (m[2] / Math.abs(m[2]))) // mins offset -6995 : 0; -6996 -6997 return new Date( -6998 (1 * m[1]) // ticks -6999 + offset -7000 ); -7001 } -7002 -7003 // failing that, try to parse whatever we've got. -7004 return Date.parse(date); -7005 }; -7006\ No newline at end of file diff --git a/static/lib/timeline/doc/prettify/lang-apollo.js b/static/lib/timeline/doc/prettify/lang-apollo.js deleted file mode 100644 index bfc0014c..00000000 --- a/static/lib/timeline/doc/prettify/lang-apollo.js +++ /dev/null @@ -1,2 +0,0 @@ -PR.registerLangHandler(PR.createSimpleLexer([["com",/^#[^\r\n]*/,null,"#"],["pln",/^[\t\n\r \xA0]+/,null,"\t\n\r \u00a0"],["str",/^\"(?:[^\"\\]|\\[\s\S])*(?:\"|$)/,null,'"']],[["kwd",/^(?:ADS|AD|AUG|BZF|BZMF|CAE|CAF|CA|CCS|COM|CS|DAS|DCA|DCOM|DCS|DDOUBL|DIM|DOUBLE|DTCB|DTCF|DV|DXCH|EDRUPT|EXTEND|INCR|INDEX|NDX|INHINT|LXCH|MASK|MSK|MP|MSU|NOOP|OVSK|QXCH|RAND|READ|RELINT|RESUME|RETURN|ROR|RXOR|SQUARE|SU|TCR|TCAA|OVSK|TCF|TC|TS|WAND|WOR|WRITE|XCH|XLQ|XXALQ|ZL|ZQ|ADD|ADZ|SUB|SUZ|MPY|MPR|MPZ|DVP|COM|ABS|CLA|CLZ|LDQ|STO|STQ|ALS|LLS|LRS|TRA|TSQ|TMI|TOV|AXT|TIX|DLY|INP|OUT)\s/, -null],["typ",/^(?:-?GENADR|=MINUS|2BCADR|VN|BOF|MM|-?2CADR|-?[1-6]DNADR|ADRES|BBCON|[SE]?BANK\=?|BLOCK|BNKSUM|E?CADR|COUNT\*?|2?DEC\*?|-?DNCHAN|-?DNPTR|EQUALS|ERASE|MEMORY|2?OCT|REMADR|SETLOC|SUBRO|ORG|BSS|BES|SYN|EQU|DEFINE|END)\s/,null],["lit",/^\'(?:-*(?:\w|\\[\x21-\x7e])(?:[\w-]*|\\[\x21-\x7e])[=!?]?)?/],["pln",/^-*(?:[!-z_]|\\[\x21-\x7e])(?:[\w-]*|\\[\x21-\x7e])[=!?]?/i],["pun",/^[^\w\t\n\r \xA0()\"\\\';]+/]]),["apollo","agc","aea"]) \ No newline at end of file diff --git a/static/lib/timeline/doc/prettify/lang-css.js b/static/lib/timeline/doc/prettify/lang-css.js deleted file mode 100644 index 61157f38..00000000 --- a/static/lib/timeline/doc/prettify/lang-css.js +++ /dev/null @@ -1,2 +0,0 @@ -PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[ \t\r\n\f]+/,null," \t\r\n\u000c"]],[["str",/^\"(?:[^\n\r\f\\\"]|\\(?:\r\n?|\n|\f)|\\[\s\S])*\"/,null],["str",/^\'(?:[^\n\r\f\\\']|\\(?:\r\n?|\n|\f)|\\[\s\S])*\'/,null],["lang-css-str",/^url\(([^\)\"\']*)\)/i],["kwd",/^(?:url|rgb|\!important|@import|@page|@media|@charset|inherit)(?=[^\-\w]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|(?:\\[0-9a-f]+ ?))(?:[_a-z0-9\-]|\\(?:\\[0-9a-f]+ ?))*)\s*:/i],["com",/^\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\//], -["com",/^(?: -
- Icons by DryIcons - and TpdkDesign.net -
- -