sufia 3.7.2 → 4.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (275) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -1
  3. data/Gemfile +3 -0
  4. data/README.md +88 -11
  5. data/SUFIA_VERSION +1 -1
  6. data/app/assets/images/old_main_tulips.jpg +0 -0
  7. data/app/assets/javascripts/contact_form.js +0 -16
  8. data/app/assets/javascripts/jquery.blacklightTagCloud.js +44 -0
  9. data/app/assets/javascripts/jquery.tagcloud.js +116 -0
  10. data/app/assets/javascripts/jquery.tinysort.min.js +7 -0
  11. data/app/assets/javascripts/sufia.js +41 -44
  12. data/app/assets/javascripts/sufia/edit_metadata.js +1 -1
  13. data/app/assets/javascripts/sufia/editor.js +12 -0
  14. data/app/assets/javascripts/sufia/featured_works.js +64 -0
  15. data/app/assets/javascripts/sufia/fileupload.js +1 -1
  16. data/app/assets/javascripts/sufia/multiForm.js +1 -1
  17. data/app/assets/javascripts/sufia/permissions.js +4 -8
  18. data/app/assets/javascripts/sufia/single_use_link.js +1 -2
  19. data/app/assets/javascripts/sufia/trophy.js +42 -27
  20. data/app/assets/javascripts/terms_of_service.js +1 -1
  21. data/app/assets/stylesheets/dashboard.css.scss +81 -40
  22. data/app/assets/stylesheets/featured.css.scss +16 -0
  23. data/app/assets/stylesheets/fileupload/{jquery.fileupload-ui.css → jquery.fileupload-ui.css.erb} +2 -2
  24. data/app/assets/stylesheets/{generic_files.css → generic_files.css.erb} +6 -7
  25. data/app/assets/stylesheets/header.css.scss +93 -0
  26. data/app/assets/stylesheets/home-page.css.scss +50 -0
  27. data/app/assets/stylesheets/nestable.css.scss +111 -0
  28. data/app/assets/stylesheets/styles.css.scss +106 -0
  29. data/app/assets/stylesheets/sufia.css.scss +13 -8
  30. data/app/assets/stylesheets/tagcloud.css +83 -0
  31. data/app/assets/stylesheets/usage-stats.css +30 -0
  32. data/app/controllers/batch_controller.rb +0 -1
  33. data/app/controllers/concerns/sufia/catalog.rb +64 -0
  34. data/app/controllers/concerns/sufia/dashboard_controller_behavior.rb +16 -13
  35. data/app/controllers/content_blocks_controller.rb +8 -0
  36. data/app/controllers/featured_work_lists_controller.rb +16 -0
  37. data/app/controllers/featured_works_controller.rb +26 -0
  38. data/app/controllers/mailbox_controller.rb +1 -1
  39. data/app/controllers/pages_controller.rb +7 -0
  40. data/app/controllers/single_use_links_viewer_controller.rb +2 -5
  41. data/app/controllers/tinymce_assets_controller.rb +12 -0
  42. data/app/helpers/content_block_helper.rb +21 -0
  43. data/app/helpers/generic_file_helper.rb +2 -2
  44. data/app/helpers/sufia_helper.rb +8 -4
  45. data/app/helpers/trophy_helper.rb +15 -7
  46. data/app/models/content_block.rb +2 -0
  47. data/app/models/featured_work_list.rb +42 -0
  48. data/app/models/tinymce_asset.rb +3 -0
  49. data/app/uploaders/tinymce_asset_uploader.rb +51 -0
  50. data/app/views/_add_assets_links.html.erb +0 -16
  51. data/app/views/_controls.html.erb +17 -0
  52. data/app/views/_flash_msg.html.erb +2 -18
  53. data/app/views/_footer.html.erb +10 -15
  54. data/app/views/_ga.html.erb +0 -16
  55. data/app/views/_logo.html.erb +2 -2
  56. data/app/views/_masthead.html.erb +9 -36
  57. data/app/views/_user_util_links.html.erb +16 -11
  58. data/app/views/advanced/_advanced_search_facets.html.erb +0 -16
  59. data/app/views/advanced/_advanced_search_fields.html.erb +0 -16
  60. data/app/views/advanced/_advanced_search_help.html.erb +0 -16
  61. data/app/views/advanced/_facet_layout.html.erb +0 -16
  62. data/app/views/advanced/_facet_limit.html.erb +0 -16
  63. data/app/views/advanced/index.html.erb +2 -18
  64. data/app/views/batch/_metadata.html.erb +3 -3
  65. data/app/views/batch/edit.html.erb +4 -21
  66. data/app/views/catalog/_facet_limit.html.erb +11 -12
  67. data/app/views/catalog/_featured_researcher.html.erb +2 -0
  68. data/app/views/catalog/_home.html.erb +6 -2
  69. data/app/views/catalog/_home_content.html.erb +36 -0
  70. data/app/views/catalog/_home_header.html.erb +15 -0
  71. data/app/views/catalog/_recent_document.html.erb +1 -1
  72. data/app/views/catalog/_recents.html.erb +3 -4
  73. data/app/views/catalog/_search_form.html.erb +13 -9
  74. data/app/views/catalog/_tagcloud.html.erb +7 -0
  75. data/app/views/catalog/index.html.erb +4 -10
  76. data/app/views/contact_form/create.html.erb +0 -16
  77. data/app/views/contact_form/new.html.erb +0 -16
  78. data/app/views/dashboard/_constraints.html.erb +3 -19
  79. data/app/views/dashboard/_did_you_mean.html.erb +0 -16
  80. data/app/views/dashboard/_document_list.html.erb +0 -16
  81. data/app/views/dashboard/_facet_layout.html.erb +1 -17
  82. data/app/views/dashboard/_facet_limit.html.erb +5 -5
  83. data/app/views/dashboard/_facet_selected.html.erb +1 -17
  84. data/app/views/dashboard/_facets.html.erb +0 -18
  85. data/app/views/dashboard/_heading.html.erb +1 -1
  86. data/app/views/dashboard/_index_partials/_default_group.html.erb +7 -23
  87. data/app/views/dashboard/_index_partials/_list_files.html.erb +62 -57
  88. data/app/views/dashboard/_search_form.html.erb +10 -7
  89. data/app/views/dashboard/_show_partials/_default.html.erb +0 -18
  90. data/app/views/dashboard/_show_partials/_default_details.html.erb +0 -16
  91. data/app/views/dashboard/_show_partials/_facets.html.erb +0 -16
  92. data/app/views/dashboard/_sort_and_per_page.html.erb +10 -9
  93. data/app/views/dashboard/facet.html.erb +4 -19
  94. data/app/views/dashboard/index.html.erb +27 -49
  95. data/app/views/error/401.html.erb +1 -17
  96. data/app/views/error/404.html.erb +0 -16
  97. data/app/views/error/500.html.erb +0 -16
  98. data/app/views/error/single_use_error.html.erb +1 -17
  99. data/app/views/generic_files/_asset_deleted_flash.html.erb +0 -16
  100. data/app/views/generic_files/_asset_permissions_denial_flash.html.erb +0 -16
  101. data/app/views/generic_files/_asset_saved_flash.html.erb +0 -16
  102. data/app/views/generic_files/_asset_updated_flash.html.erb +0 -16
  103. data/app/views/generic_files/_breadcrumbs.html.erb +0 -16
  104. data/app/views/generic_files/_browse_everything.html.erb +24 -0
  105. data/app/views/generic_files/_descriptions.html.erb +1 -1
  106. data/app/views/generic_files/_field_form.html.erb +5 -5
  107. data/app/views/generic_files/_media_display.html.erb +3 -3
  108. data/app/views/generic_files/_permission_form.html.erb +5 -5
  109. data/app/views/generic_files/_rights_modal.html.erb +13 -29
  110. data/app/views/generic_files/_show_actions.html.erb +13 -1
  111. data/app/views/generic_files/_versioning.html.erb +14 -10
  112. data/app/views/generic_files/citation.html.erb +0 -16
  113. data/app/views/generic_files/edit.html.erb +10 -12
  114. data/app/views/generic_files/edit_fields/_default.html.erb +1 -1
  115. data/app/views/generic_files/edit_fields/_description.html.erb +1 -1
  116. data/app/views/generic_files/edit_fields/_resource_type.html.erb +2 -3
  117. data/app/views/generic_files/edit_fields/_rights.html.erb +1 -1
  118. data/app/views/generic_files/new.html.erb +11 -26
  119. data/app/views/generic_files/show.html.erb +7 -25
  120. data/app/views/generic_files/stats.html.erb +141 -0
  121. data/app/views/homepage/_featured.html.erb +11 -0
  122. data/app/views/homepage/_featured_fields.html.erb +18 -0
  123. data/app/views/homepage/_featured_works.html.erb +17 -0
  124. data/app/views/homepage/_sortable_featured.html.erb +26 -0
  125. data/app/views/layouts/_head_tag_content.html.erb +1 -1
  126. data/app/views/layouts/_homepage_sidebar2.html.erb +14 -0
  127. data/app/views/layouts/error.html.erb +3 -21
  128. data/app/views/layouts/homepage.html.erb +8 -37
  129. data/app/views/layouts/sufia-dashboard.html.erb +46 -0
  130. data/app/views/layouts/sufia-one-column.html.erb +8 -24
  131. data/app/views/layouts/sufia-two-column.html.erb +9 -8
  132. data/app/views/pages/show.html.erb +4 -0
  133. data/app/views/single_use_links_viewer/show.html.erb +2 -2
  134. data/app/views/static/agreement.html.erb +0 -17
  135. data/app/views/static/help.html.erb +0 -17
  136. data/app/views/static/mendeley.html.erb +0 -16
  137. data/app/views/static/subject_libraries.html.erb +0 -17
  138. data/app/views/static/terms.html.erb +0 -16
  139. data/app/views/static/zotero.html.erb +0 -16
  140. data/app/views/users/_activity_log.html.erb +2 -2
  141. data/app/views/users/_contributions.html.erb +6 -2
  142. data/app/views/users/_follower_modal.html.erb +2 -2
  143. data/app/views/users/_following_modal.html.erb +2 -2
  144. data/app/views/users/_left_sidebar.html.erb +1 -1
  145. data/app/views/users/_notify_link.html.erb +1 -1
  146. data/app/views/users/_notify_number.html.erb +1 -1
  147. data/app/views/users/_profile_actions.html.erb +3 -3
  148. data/app/views/users/_profile_tabs.html.erb +1 -1
  149. data/app/views/users/_search_form.html.erb +3 -2
  150. data/app/views/users/_social_media_info.html.erb +3 -3
  151. data/app/views/users/_trophy_edit.html.erb +2 -2
  152. data/app/views/users/_user_info.html.erb +27 -27
  153. data/app/views/users/_vitals.html.erb +2 -2
  154. data/app/views/users/edit.html.erb +39 -39
  155. data/app/views/users/index.html.erb +3 -3
  156. data/app/views/users/show.html.erb +7 -9
  157. data/config/locales/sufia.en.yml +15 -7
  158. data/config/routes.rb +12 -9
  159. data/lib/generators/sufia/sufia_generator.rb +18 -15
  160. data/lib/generators/sufia/templates/catalog_controller.rb +11 -73
  161. data/lib/generators/sufia/templates/config/tinymce.yml +7 -0
  162. data/lib/sufia.rb +7 -0
  163. data/lib/sufia/downloads_controller_behavior.rb +3 -4
  164. data/lib/sufia/files_controller_behavior.rb +38 -27
  165. data/lib/sufia/version.rb +1 -1
  166. data/spec/controllers/batch_controller_spec.rb +1 -1
  167. data/spec/controllers/batch_edits_controller_spec.rb +5 -5
  168. data/spec/controllers/catalog_controller_spec.rb +37 -58
  169. data/spec/controllers/content_blocks_controller_spec.rb +37 -0
  170. data/spec/controllers/downloads_controller_spec.rb +3 -11
  171. data/spec/controllers/featured_work_lists_controller_spec.rb +20 -0
  172. data/spec/controllers/featured_works_controller_spec.rb +50 -0
  173. data/spec/controllers/generic_files_controller_spec.rb +74 -9
  174. data/spec/controllers/mailbox_controller_spec.rb +1 -1
  175. data/spec/controllers/pages_controller_spec.rb +25 -0
  176. data/spec/controllers/single_use_links_controller_spec.rb +1 -1
  177. data/spec/controllers/single_use_links_viewer_controller_spec.rb +1 -1
  178. data/spec/controllers/tinymce_assets_controller_spec.rb +39 -0
  179. data/spec/controllers/users_controller_spec.rb +3 -8
  180. data/spec/factories/content_blocks.rb +4 -0
  181. data/spec/factories/featured_works.rb +4 -0
  182. data/spec/factories/generic_files.rb +46 -39
  183. data/spec/factories/users.rb +18 -18
  184. data/spec/features/browse_dashboard_files_spec.rb +6 -7
  185. data/spec/features/browse_files_spec.rb +9 -6
  186. data/spec/features/cloud_upload_spec.rb +18 -0
  187. data/spec/features/single_use_links_spec.rb +2 -3
  188. data/spec/helpers/content_block_helper_spec.rb +31 -0
  189. data/spec/helpers/generic_file_helper_spec.rb +3 -3
  190. data/spec/helpers/sufia_helper_spec.rb +13 -81
  191. data/spec/helpers/trophy_helper_spec.rb +39 -0
  192. data/spec/jobs/import_url_job_spec.rb +1 -1
  193. data/spec/jobs/ingest_local_file_job_spec.rb +1 -1
  194. data/spec/lib/sufia/usage_statistics_spec.rb +75 -0
  195. data/spec/models/ability_spec.rb +31 -0
  196. data/spec/models/active_fedora_pid_based_job_spec.rb +1 -1
  197. data/spec/models/audit_job_spec.rb +1 -1
  198. data/spec/models/batch_spec.rb +1 -1
  199. data/spec/models/batch_update_job_spec.rb +1 -1
  200. data/spec/models/event_jobs_spec.rb +1 -1
  201. data/spec/models/featured_work_list_spec.rb +23 -0
  202. data/spec/models/featured_work_spec.rb +42 -0
  203. data/spec/models/fits_datastream_spec.rb +2 -2
  204. data/spec/models/generic_file/reload_on_save_spec.rb +1 -1
  205. data/spec/models/generic_file/web_form_spec.rb +4 -4
  206. data/spec/models/generic_file_spec.rb +41 -40
  207. data/spec/models/pageview.rb +19 -0
  208. data/spec/models/properties_datastream_spec.rb +2 -2
  209. data/spec/models/solr_document_spec.rb +9 -0
  210. data/spec/models/user_spec.rb +2 -2
  211. data/spec/routing/featured_works_route_spec.rb +16 -0
  212. data/spec/routing/route_spec.rb +10 -15
  213. data/spec/spec_helper.rb +3 -0
  214. data/spec/support/lib/generators/test_app_generator.rb +19 -8
  215. data/spec/views/dashboard/facet_limit.html.erb_spec.rb +2 -2
  216. data/spec/views/generic_file/show.html.erb_spec.rb +44 -0
  217. data/spec/views/generic_file/stats.html.erb_spec.rb +47 -0
  218. data/spec/views/users/_follower_modal.html.erb_spec.rb +18 -0
  219. data/spec/views/users/_following_modal.html.erb_spec.rb +18 -0
  220. data/spec/views/users/index.html.erb_spec.rb +1 -1
  221. data/sufia-models/app/models/batch.rb +1 -1
  222. data/sufia-models/app/models/concerns/sufia/properties_datastream_behavior.rb +30 -0
  223. data/sufia-models/app/models/concerns/sufia/user.rb +1 -1
  224. data/sufia-models/app/models/datastreams/batch_rdf_datastream.rb +4 -7
  225. data/sufia-models/app/models/datastreams/fits_datastream.rb +3 -0
  226. data/sufia-models/app/models/datastreams/generic_file_rdf_datastream.rb +53 -51
  227. data/sufia-models/app/models/datastreams/properties_datastream.rb +1 -15
  228. data/sufia-models/app/models/featured_work.rb +22 -0
  229. data/sufia-models/app/models/sufia/pageview.rb +9 -0
  230. data/sufia-models/{lib/sufia/models → app/services/sufia}/id_service.rb +0 -0
  231. data/sufia-models/{lib/sufia/models → app/services/sufia}/noid.rb +0 -0
  232. data/sufia-models/app/services/sufia/usage_statistics.rb +73 -0
  233. data/sufia-models/config/locales/sufia.en.yml +3 -4
  234. data/sufia-models/lib/generators/sufia/models/install_generator.rb +23 -8
  235. data/sufia-models/lib/generators/sufia/models/templates/config/analytics.yml +9 -0
  236. data/sufia-models/lib/generators/sufia/models/templates/config/mime_types.rb +6 -0
  237. data/sufia-models/lib/generators/sufia/models/templates/config/sufia.rb +11 -2
  238. data/sufia-models/lib/generators/sufia/models/templates/migrations/create_content_blocks.rb +10 -0
  239. data/sufia-models/lib/generators/sufia/models/templates/migrations/create_featured_works.rb +12 -0
  240. data/sufia-models/lib/generators/sufia/models/templates/migrations/create_tinymce_assets.rb +8 -0
  241. data/sufia-models/lib/generators/sufia/models/usagestats_generator.rb +19 -0
  242. data/sufia-models/lib/sufia/ability.rb +34 -0
  243. data/sufia-models/lib/sufia/models/engine.rb +5 -3
  244. data/sufia-models/lib/sufia/models/generic_file.rb +4 -2
  245. data/sufia-models/lib/sufia/models/generic_file/export.rb +3 -3
  246. data/sufia-models/lib/sufia/models/generic_file/featured.rb +14 -0
  247. data/sufia-models/lib/sufia/models/solr_document_behavior.rb +11 -4
  248. data/sufia-models/lib/sufia/models/version.rb +1 -1
  249. data/sufia-models/sufia-models.gemspec +6 -5
  250. data/sufia.gemspec +6 -4
  251. data/tasks/release.rake +1 -0
  252. data/tasks/sufia-dev.rake +5 -1
  253. data/vendor/assets/javascripts/flot/excanvas.js +1428 -0
  254. data/vendor/assets/javascripts/flot/jquery.flot.js +3137 -0
  255. data/vendor/assets/javascripts/flot/jquery.flot.selection.js +360 -0
  256. data/vendor/assets/javascripts/flot/jquery.flot.time.js +431 -0
  257. data/vendor/assets/javascripts/nestable.js +647 -0
  258. data/vendor/assets/javascripts/video.js +135 -126
  259. data/{app → vendor}/assets/stylesheets/video-js.css.erb +7 -6
  260. metadata +155 -33
  261. data/app/assets/stylesheets/application-bootstrap.css.erb +0 -295
  262. data/app/assets/stylesheets/audio-js.css +0 -3
  263. data/app/assets/stylesheets/blacklight_more_facets.css +0 -427
  264. data/app/assets/stylesheets/bootstrap.min.css.erb +0 -1433
  265. data/app/assets/stylesheets/contact_form.css +0 -4
  266. data/app/assets/stylesheets/reset_body.css +0 -4
  267. data/app/assets/stylesheets/trophy.css +0 -7
  268. data/app/helpers/blacklight/render_constraints_helper_behavior.rb +0 -84
  269. data/app/helpers/facets_helper.rb +0 -3
  270. data/app/helpers/sufia/facets_helper_behavior.rb +0 -23
  271. data/app/views/catalog/_home_text.html.erb +0 -10
  272. data/app/views/generic_files/_dropbox_import.html.erb +0 -3
  273. data/app/views/generic_files/upload/_dropbox_chooser.html.erb +0 -39
  274. data/app/views/static/about.html.erb +0 -21
  275. data/spec/helpers/render_constraints_helper_behavior_spec.rb +0 -42
@@ -0,0 +1,360 @@
1
+ /* Flot plugin for selecting regions of a plot.
2
+
3
+ Copyright (c) 2007-2013 IOLA and Ole Laursen.
4
+ Licensed under the MIT license.
5
+
6
+ The plugin supports these options:
7
+
8
+ selection: {
9
+ mode: null or "x" or "y" or "xy",
10
+ color: color,
11
+ shape: "round" or "miter" or "bevel",
12
+ minSize: number of pixels
13
+ }
14
+
15
+ Selection support is enabled by setting the mode to one of "x", "y" or "xy".
16
+ In "x" mode, the user will only be able to specify the x range, similarly for
17
+ "y" mode. For "xy", the selection becomes a rectangle where both ranges can be
18
+ specified. "color" is color of the selection (if you need to change the color
19
+ later on, you can get to it with plot.getOptions().selection.color). "shape"
20
+ is the shape of the corners of the selection.
21
+
22
+ "minSize" is the minimum size a selection can be in pixels. This value can
23
+ be customized to determine the smallest size a selection can be and still
24
+ have the selection rectangle be displayed. When customizing this value, the
25
+ fact that it refers to pixels, not axis units must be taken into account.
26
+ Thus, for example, if there is a bar graph in time mode with BarWidth set to 1
27
+ minute, setting "minSize" to 1 will not make the minimum selection size 1
28
+ minute, but rather 1 pixel. Note also that setting "minSize" to 0 will prevent
29
+ "plotunselected" events from being fired when the user clicks the mouse without
30
+ dragging.
31
+
32
+ When selection support is enabled, a "plotselected" event will be emitted on
33
+ the DOM element you passed into the plot function. The event handler gets a
34
+ parameter with the ranges selected on the axes, like this:
35
+
36
+ placeholder.bind( "plotselected", function( event, ranges ) {
37
+ alert("You selected " + ranges.xaxis.from + " to " + ranges.xaxis.to)
38
+ // similar for yaxis - with multiple axes, the extra ones are in
39
+ // x2axis, x3axis, ...
40
+ });
41
+
42
+ The "plotselected" event is only fired when the user has finished making the
43
+ selection. A "plotselecting" event is fired during the process with the same
44
+ parameters as the "plotselected" event, in case you want to know what's
45
+ happening while it's happening,
46
+
47
+ A "plotunselected" event with no arguments is emitted when the user clicks the
48
+ mouse to remove the selection. As stated above, setting "minSize" to 0 will
49
+ destroy this behavior.
50
+
51
+ The plugin allso adds the following methods to the plot object:
52
+
53
+ - setSelection( ranges, preventEvent )
54
+
55
+ Set the selection rectangle. The passed in ranges is on the same form as
56
+ returned in the "plotselected" event. If the selection mode is "x", you
57
+ should put in either an xaxis range, if the mode is "y" you need to put in
58
+ an yaxis range and both xaxis and yaxis if the selection mode is "xy", like
59
+ this:
60
+
61
+ setSelection({ xaxis: { from: 0, to: 10 }, yaxis: { from: 40, to: 60 } });
62
+
63
+ setSelection will trigger the "plotselected" event when called. If you don't
64
+ want that to happen, e.g. if you're inside a "plotselected" handler, pass
65
+ true as the second parameter. If you are using multiple axes, you can
66
+ specify the ranges on any of those, e.g. as x2axis/x3axis/... instead of
67
+ xaxis, the plugin picks the first one it sees.
68
+
69
+ - clearSelection( preventEvent )
70
+
71
+ Clear the selection rectangle. Pass in true to avoid getting a
72
+ "plotunselected" event.
73
+
74
+ - getSelection()
75
+
76
+ Returns the current selection in the same format as the "plotselected"
77
+ event. If there's currently no selection, the function returns null.
78
+
79
+ */
80
+
81
+ (function ($) {
82
+ function init(plot) {
83
+ var selection = {
84
+ first: { x: -1, y: -1}, second: { x: -1, y: -1},
85
+ show: false,
86
+ active: false
87
+ };
88
+
89
+ // FIXME: The drag handling implemented here should be
90
+ // abstracted out, there's some similar code from a library in
91
+ // the navigation plugin, this should be massaged a bit to fit
92
+ // the Flot cases here better and reused. Doing this would
93
+ // make this plugin much slimmer.
94
+ var savedhandlers = {};
95
+
96
+ var mouseUpHandler = null;
97
+
98
+ function onMouseMove(e) {
99
+ if (selection.active) {
100
+ updateSelection(e);
101
+
102
+ plot.getPlaceholder().trigger("plotselecting", [ getSelection() ]);
103
+ }
104
+ }
105
+
106
+ function onMouseDown(e) {
107
+ if (e.which != 1) // only accept left-click
108
+ return;
109
+
110
+ // cancel out any text selections
111
+ document.body.focus();
112
+
113
+ // prevent text selection and drag in old-school browsers
114
+ if (document.onselectstart !== undefined && savedhandlers.onselectstart == null) {
115
+ savedhandlers.onselectstart = document.onselectstart;
116
+ document.onselectstart = function () { return false; };
117
+ }
118
+ if (document.ondrag !== undefined && savedhandlers.ondrag == null) {
119
+ savedhandlers.ondrag = document.ondrag;
120
+ document.ondrag = function () { return false; };
121
+ }
122
+
123
+ setSelectionPos(selection.first, e);
124
+
125
+ selection.active = true;
126
+
127
+ // this is a bit silly, but we have to use a closure to be
128
+ // able to whack the same handler again
129
+ mouseUpHandler = function (e) { onMouseUp(e); };
130
+
131
+ $(document).one("mouseup", mouseUpHandler);
132
+ }
133
+
134
+ function onMouseUp(e) {
135
+ mouseUpHandler = null;
136
+
137
+ // revert drag stuff for old-school browsers
138
+ if (document.onselectstart !== undefined)
139
+ document.onselectstart = savedhandlers.onselectstart;
140
+ if (document.ondrag !== undefined)
141
+ document.ondrag = savedhandlers.ondrag;
142
+
143
+ // no more dragging
144
+ selection.active = false;
145
+ updateSelection(e);
146
+
147
+ if (selectionIsSane())
148
+ triggerSelectedEvent();
149
+ else {
150
+ // this counts as a clear
151
+ plot.getPlaceholder().trigger("plotunselected", [ ]);
152
+ plot.getPlaceholder().trigger("plotselecting", [ null ]);
153
+ }
154
+
155
+ return false;
156
+ }
157
+
158
+ function getSelection() {
159
+ if (!selectionIsSane())
160
+ return null;
161
+
162
+ if (!selection.show) return null;
163
+
164
+ var r = {}, c1 = selection.first, c2 = selection.second;
165
+ $.each(plot.getAxes(), function (name, axis) {
166
+ if (axis.used) {
167
+ var p1 = axis.c2p(c1[axis.direction]), p2 = axis.c2p(c2[axis.direction]);
168
+ r[name] = { from: Math.min(p1, p2), to: Math.max(p1, p2) };
169
+ }
170
+ });
171
+ return r;
172
+ }
173
+
174
+ function triggerSelectedEvent() {
175
+ var r = getSelection();
176
+
177
+ plot.getPlaceholder().trigger("plotselected", [ r ]);
178
+
179
+ // backwards-compat stuff, to be removed in future
180
+ if (r.xaxis && r.yaxis)
181
+ plot.getPlaceholder().trigger("selected", [ { x1: r.xaxis.from, y1: r.yaxis.from, x2: r.xaxis.to, y2: r.yaxis.to } ]);
182
+ }
183
+
184
+ function clamp(min, value, max) {
185
+ return value < min ? min: (value > max ? max: value);
186
+ }
187
+
188
+ function setSelectionPos(pos, e) {
189
+ var o = plot.getOptions();
190
+ var offset = plot.getPlaceholder().offset();
191
+ var plotOffset = plot.getPlotOffset();
192
+ pos.x = clamp(0, e.pageX - offset.left - plotOffset.left, plot.width());
193
+ pos.y = clamp(0, e.pageY - offset.top - plotOffset.top, plot.height());
194
+
195
+ if (o.selection.mode == "y")
196
+ pos.x = pos == selection.first ? 0 : plot.width();
197
+
198
+ if (o.selection.mode == "x")
199
+ pos.y = pos == selection.first ? 0 : plot.height();
200
+ }
201
+
202
+ function updateSelection(pos) {
203
+ if (pos.pageX == null)
204
+ return;
205
+
206
+ setSelectionPos(selection.second, pos);
207
+ if (selectionIsSane()) {
208
+ selection.show = true;
209
+ plot.triggerRedrawOverlay();
210
+ }
211
+ else
212
+ clearSelection(true);
213
+ }
214
+
215
+ function clearSelection(preventEvent) {
216
+ if (selection.show) {
217
+ selection.show = false;
218
+ plot.triggerRedrawOverlay();
219
+ if (!preventEvent)
220
+ plot.getPlaceholder().trigger("plotunselected", [ ]);
221
+ }
222
+ }
223
+
224
+ // function taken from markings support in Flot
225
+ function extractRange(ranges, coord) {
226
+ var axis, from, to, key, axes = plot.getAxes();
227
+
228
+ for (var k in axes) {
229
+ axis = axes[k];
230
+ if (axis.direction == coord) {
231
+ key = coord + axis.n + "axis";
232
+ if (!ranges[key] && axis.n == 1)
233
+ key = coord + "axis"; // support x1axis as xaxis
234
+ if (ranges[key]) {
235
+ from = ranges[key].from;
236
+ to = ranges[key].to;
237
+ break;
238
+ }
239
+ }
240
+ }
241
+
242
+ // backwards-compat stuff - to be removed in future
243
+ if (!ranges[key]) {
244
+ axis = coord == "x" ? plot.getXAxes()[0] : plot.getYAxes()[0];
245
+ from = ranges[coord + "1"];
246
+ to = ranges[coord + "2"];
247
+ }
248
+
249
+ // auto-reverse as an added bonus
250
+ if (from != null && to != null && from > to) {
251
+ var tmp = from;
252
+ from = to;
253
+ to = tmp;
254
+ }
255
+
256
+ return { from: from, to: to, axis: axis };
257
+ }
258
+
259
+ function setSelection(ranges, preventEvent) {
260
+ var axis, range, o = plot.getOptions();
261
+
262
+ if (o.selection.mode == "y") {
263
+ selection.first.x = 0;
264
+ selection.second.x = plot.width();
265
+ }
266
+ else {
267
+ range = extractRange(ranges, "x");
268
+
269
+ selection.first.x = range.axis.p2c(range.from);
270
+ selection.second.x = range.axis.p2c(range.to);
271
+ }
272
+
273
+ if (o.selection.mode == "x") {
274
+ selection.first.y = 0;
275
+ selection.second.y = plot.height();
276
+ }
277
+ else {
278
+ range = extractRange(ranges, "y");
279
+
280
+ selection.first.y = range.axis.p2c(range.from);
281
+ selection.second.y = range.axis.p2c(range.to);
282
+ }
283
+
284
+ selection.show = true;
285
+ plot.triggerRedrawOverlay();
286
+ if (!preventEvent && selectionIsSane())
287
+ triggerSelectedEvent();
288
+ }
289
+
290
+ function selectionIsSane() {
291
+ var minSize = plot.getOptions().selection.minSize;
292
+ return Math.abs(selection.second.x - selection.first.x) >= minSize &&
293
+ Math.abs(selection.second.y - selection.first.y) >= minSize;
294
+ }
295
+
296
+ plot.clearSelection = clearSelection;
297
+ plot.setSelection = setSelection;
298
+ plot.getSelection = getSelection;
299
+
300
+ plot.hooks.bindEvents.push(function(plot, eventHolder) {
301
+ var o = plot.getOptions();
302
+ if (o.selection.mode != null) {
303
+ eventHolder.mousemove(onMouseMove);
304
+ eventHolder.mousedown(onMouseDown);
305
+ }
306
+ });
307
+
308
+
309
+ plot.hooks.drawOverlay.push(function (plot, ctx) {
310
+ // draw selection
311
+ if (selection.show && selectionIsSane()) {
312
+ var plotOffset = plot.getPlotOffset();
313
+ var o = plot.getOptions();
314
+
315
+ ctx.save();
316
+ ctx.translate(plotOffset.left, plotOffset.top);
317
+
318
+ var c = $.color.parse(o.selection.color);
319
+
320
+ ctx.strokeStyle = c.scale('a', 0.8).toString();
321
+ ctx.lineWidth = 1;
322
+ ctx.lineJoin = o.selection.shape;
323
+ ctx.fillStyle = c.scale('a', 0.4).toString();
324
+
325
+ var x = Math.min(selection.first.x, selection.second.x) + 0.5,
326
+ y = Math.min(selection.first.y, selection.second.y) + 0.5,
327
+ w = Math.abs(selection.second.x - selection.first.x) - 1,
328
+ h = Math.abs(selection.second.y - selection.first.y) - 1;
329
+
330
+ ctx.fillRect(x, y, w, h);
331
+ ctx.strokeRect(x, y, w, h);
332
+
333
+ ctx.restore();
334
+ }
335
+ });
336
+
337
+ plot.hooks.shutdown.push(function (plot, eventHolder) {
338
+ eventHolder.unbind("mousemove", onMouseMove);
339
+ eventHolder.unbind("mousedown", onMouseDown);
340
+
341
+ if (mouseUpHandler)
342
+ $(document).unbind("mouseup", mouseUpHandler);
343
+ });
344
+
345
+ }
346
+
347
+ $.plot.plugins.push({
348
+ init: init,
349
+ options: {
350
+ selection: {
351
+ mode: null, // one of null, "x", "y" or "xy"
352
+ color: "#e8cfac",
353
+ shape: "round", // one of "round", "miter", or "bevel"
354
+ minSize: 5 // minimum number of pixels
355
+ }
356
+ },
357
+ name: 'selection',
358
+ version: '1.1'
359
+ });
360
+ })(jQuery);
@@ -0,0 +1,431 @@
1
+ /* Pretty handling of time axes.
2
+
3
+ Copyright (c) 2007-2013 IOLA and Ole Laursen.
4
+ Licensed under the MIT license.
5
+
6
+ Set axis.mode to "time" to enable. See the section "Time series data" in
7
+ API.txt for details.
8
+
9
+ */
10
+
11
+ (function($) {
12
+
13
+ var options = {
14
+ xaxis: {
15
+ timezone: null, // "browser" for local to the client or timezone for timezone-js
16
+ timeformat: null, // format string to use
17
+ twelveHourClock: false, // 12 or 24 time in time mode
18
+ monthNames: null // list of names of months
19
+ }
20
+ };
21
+
22
+ // round to nearby lower multiple of base
23
+
24
+ function floorInBase(n, base) {
25
+ return base * Math.floor(n / base);
26
+ }
27
+
28
+ // Returns a string with the date d formatted according to fmt.
29
+ // A subset of the Open Group's strftime format is supported.
30
+
31
+ function formatDate(d, fmt, monthNames, dayNames) {
32
+
33
+ if (typeof d.strftime == "function") {
34
+ return d.strftime(fmt);
35
+ }
36
+
37
+ var leftPad = function(n, pad) {
38
+ n = "" + n;
39
+ pad = "" + (pad == null ? "0" : pad);
40
+ return n.length == 1 ? pad + n : n;
41
+ };
42
+
43
+ var r = [];
44
+ var escape = false;
45
+ var hours = d.getHours();
46
+ var isAM = hours < 12;
47
+
48
+ if (monthNames == null) {
49
+ monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
50
+ }
51
+
52
+ if (dayNames == null) {
53
+ dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
54
+ }
55
+
56
+ var hours12;
57
+
58
+ if (hours > 12) {
59
+ hours12 = hours - 12;
60
+ } else if (hours == 0) {
61
+ hours12 = 12;
62
+ } else {
63
+ hours12 = hours;
64
+ }
65
+
66
+ for (var i = 0; i < fmt.length; ++i) {
67
+
68
+ var c = fmt.charAt(i);
69
+
70
+ if (escape) {
71
+ switch (c) {
72
+ case 'a': c = "" + dayNames[d.getDay()]; break;
73
+ case 'b': c = "" + monthNames[d.getMonth()]; break;
74
+ case 'd': c = leftPad(d.getDate()); break;
75
+ case 'e': c = leftPad(d.getDate(), " "); break;
76
+ case 'h': // For back-compat with 0.7; remove in 1.0
77
+ case 'H': c = leftPad(hours); break;
78
+ case 'I': c = leftPad(hours12); break;
79
+ case 'l': c = leftPad(hours12, " "); break;
80
+ case 'm': c = leftPad(d.getMonth() + 1); break;
81
+ case 'M': c = leftPad(d.getMinutes()); break;
82
+ // quarters not in Open Group's strftime specification
83
+ case 'q':
84
+ c = "" + (Math.floor(d.getMonth() / 3) + 1); break;
85
+ case 'S': c = leftPad(d.getSeconds()); break;
86
+ case 'y': c = leftPad(d.getFullYear() % 100); break;
87
+ case 'Y': c = "" + d.getFullYear(); break;
88
+ case 'p': c = (isAM) ? ("" + "am") : ("" + "pm"); break;
89
+ case 'P': c = (isAM) ? ("" + "AM") : ("" + "PM"); break;
90
+ case 'w': c = "" + d.getDay(); break;
91
+ }
92
+ r.push(c);
93
+ escape = false;
94
+ } else {
95
+ if (c == "%") {
96
+ escape = true;
97
+ } else {
98
+ r.push(c);
99
+ }
100
+ }
101
+ }
102
+
103
+ return r.join("");
104
+ }
105
+
106
+ // To have a consistent view of time-based data independent of which time
107
+ // zone the client happens to be in we need a date-like object independent
108
+ // of time zones. This is done through a wrapper that only calls the UTC
109
+ // versions of the accessor methods.
110
+
111
+ function makeUtcWrapper(d) {
112
+
113
+ function addProxyMethod(sourceObj, sourceMethod, targetObj, targetMethod) {
114
+ sourceObj[sourceMethod] = function() {
115
+ return targetObj[targetMethod].apply(targetObj, arguments);
116
+ };
117
+ };
118
+
119
+ var utc = {
120
+ date: d
121
+ };
122
+
123
+ // support strftime, if found
124
+
125
+ if (d.strftime != undefined) {
126
+ addProxyMethod(utc, "strftime", d, "strftime");
127
+ }
128
+
129
+ addProxyMethod(utc, "getTime", d, "getTime");
130
+ addProxyMethod(utc, "setTime", d, "setTime");
131
+
132
+ var props = ["Date", "Day", "FullYear", "Hours", "Milliseconds", "Minutes", "Month", "Seconds"];
133
+
134
+ for (var p = 0; p < props.length; p++) {
135
+ addProxyMethod(utc, "get" + props[p], d, "getUTC" + props[p]);
136
+ addProxyMethod(utc, "set" + props[p], d, "setUTC" + props[p]);
137
+ }
138
+
139
+ return utc;
140
+ };
141
+
142
+ // select time zone strategy. This returns a date-like object tied to the
143
+ // desired timezone
144
+
145
+ function dateGenerator(ts, opts) {
146
+ if (opts.timezone == "browser") {
147
+ return new Date(ts);
148
+ } else if (!opts.timezone || opts.timezone == "utc") {
149
+ return makeUtcWrapper(new Date(ts));
150
+ } else if (typeof timezoneJS != "undefined" && typeof timezoneJS.Date != "undefined") {
151
+ var d = new timezoneJS.Date();
152
+ // timezone-js is fickle, so be sure to set the time zone before
153
+ // setting the time.
154
+ d.setTimezone(opts.timezone);
155
+ d.setTime(ts);
156
+ return d;
157
+ } else {
158
+ return makeUtcWrapper(new Date(ts));
159
+ }
160
+ }
161
+
162
+ // map of app. size of time units in milliseconds
163
+
164
+ var timeUnitSize = {
165
+ "second": 1000,
166
+ "minute": 60 * 1000,
167
+ "hour": 60 * 60 * 1000,
168
+ "day": 24 * 60 * 60 * 1000,
169
+ "month": 30 * 24 * 60 * 60 * 1000,
170
+ "quarter": 3 * 30 * 24 * 60 * 60 * 1000,
171
+ "year": 365.2425 * 24 * 60 * 60 * 1000
172
+ };
173
+
174
+ // the allowed tick sizes, after 1 year we use
175
+ // an integer algorithm
176
+
177
+ var baseSpec = [
178
+ [1, "second"], [2, "second"], [5, "second"], [10, "second"],
179
+ [30, "second"],
180
+ [1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"],
181
+ [30, "minute"],
182
+ [1, "hour"], [2, "hour"], [4, "hour"],
183
+ [8, "hour"], [12, "hour"],
184
+ [1, "day"], [2, "day"], [3, "day"],
185
+ [0.25, "month"], [0.5, "month"], [1, "month"],
186
+ [2, "month"]
187
+ ];
188
+
189
+ // we don't know which variant(s) we'll need yet, but generating both is
190
+ // cheap
191
+
192
+ var specMonths = baseSpec.concat([[3, "month"], [6, "month"],
193
+ [1, "year"]]);
194
+ var specQuarters = baseSpec.concat([[1, "quarter"], [2, "quarter"],
195
+ [1, "year"]]);
196
+
197
+ function init(plot) {
198
+ plot.hooks.processOptions.push(function (plot, options) {
199
+ $.each(plot.getAxes(), function(axisName, axis) {
200
+
201
+ var opts = axis.options;
202
+
203
+ if (opts.mode == "time") {
204
+ axis.tickGenerator = function(axis) {
205
+
206
+ var ticks = [];
207
+ var d = dateGenerator(axis.min, opts);
208
+ var minSize = 0;
209
+
210
+ // make quarter use a possibility if quarters are
211
+ // mentioned in either of these options
212
+
213
+ var spec = (opts.tickSize && opts.tickSize[1] ===
214
+ "quarter") ||
215
+ (opts.minTickSize && opts.minTickSize[1] ===
216
+ "quarter") ? specQuarters : specMonths;
217
+
218
+ if (opts.minTickSize != null) {
219
+ if (typeof opts.tickSize == "number") {
220
+ minSize = opts.tickSize;
221
+ } else {
222
+ minSize = opts.minTickSize[0] * timeUnitSize[opts.minTickSize[1]];
223
+ }
224
+ }
225
+
226
+ for (var i = 0; i < spec.length - 1; ++i) {
227
+ if (axis.delta < (spec[i][0] * timeUnitSize[spec[i][1]]
228
+ + spec[i + 1][0] * timeUnitSize[spec[i + 1][1]]) / 2
229
+ && spec[i][0] * timeUnitSize[spec[i][1]] >= minSize) {
230
+ break;
231
+ }
232
+ }
233
+
234
+ var size = spec[i][0];
235
+ var unit = spec[i][1];
236
+
237
+ // special-case the possibility of several years
238
+
239
+ if (unit == "year") {
240
+
241
+ // if given a minTickSize in years, just use it,
242
+ // ensuring that it's an integer
243
+
244
+ if (opts.minTickSize != null && opts.minTickSize[1] == "year") {
245
+ size = Math.floor(opts.minTickSize[0]);
246
+ } else {
247
+
248
+ var magn = Math.pow(10, Math.floor(Math.log(axis.delta / timeUnitSize.year) / Math.LN10));
249
+ var norm = (axis.delta / timeUnitSize.year) / magn;
250
+
251
+ if (norm < 1.5) {
252
+ size = 1;
253
+ } else if (norm < 3) {
254
+ size = 2;
255
+ } else if (norm < 7.5) {
256
+ size = 5;
257
+ } else {
258
+ size = 10;
259
+ }
260
+
261
+ size *= magn;
262
+ }
263
+
264
+ // minimum size for years is 1
265
+
266
+ if (size < 1) {
267
+ size = 1;
268
+ }
269
+ }
270
+
271
+ axis.tickSize = opts.tickSize || [size, unit];
272
+ var tickSize = axis.tickSize[0];
273
+ unit = axis.tickSize[1];
274
+
275
+ var step = tickSize * timeUnitSize[unit];
276
+
277
+ if (unit == "second") {
278
+ d.setSeconds(floorInBase(d.getSeconds(), tickSize));
279
+ } else if (unit == "minute") {
280
+ d.setMinutes(floorInBase(d.getMinutes(), tickSize));
281
+ } else if (unit == "hour") {
282
+ d.setHours(floorInBase(d.getHours(), tickSize));
283
+ } else if (unit == "month") {
284
+ d.setMonth(floorInBase(d.getMonth(), tickSize));
285
+ } else if (unit == "quarter") {
286
+ d.setMonth(3 * floorInBase(d.getMonth() / 3,
287
+ tickSize));
288
+ } else if (unit == "year") {
289
+ d.setFullYear(floorInBase(d.getFullYear(), tickSize));
290
+ }
291
+
292
+ // reset smaller components
293
+
294
+ d.setMilliseconds(0);
295
+
296
+ if (step >= timeUnitSize.minute) {
297
+ d.setSeconds(0);
298
+ }
299
+ if (step >= timeUnitSize.hour) {
300
+ d.setMinutes(0);
301
+ }
302
+ if (step >= timeUnitSize.day) {
303
+ d.setHours(0);
304
+ }
305
+ if (step >= timeUnitSize.day * 4) {
306
+ d.setDate(1);
307
+ }
308
+ if (step >= timeUnitSize.month * 2) {
309
+ d.setMonth(floorInBase(d.getMonth(), 3));
310
+ }
311
+ if (step >= timeUnitSize.quarter * 2) {
312
+ d.setMonth(floorInBase(d.getMonth(), 6));
313
+ }
314
+ if (step >= timeUnitSize.year) {
315
+ d.setMonth(0);
316
+ }
317
+
318
+ var carry = 0;
319
+ var v = Number.NaN;
320
+ var prev;
321
+
322
+ do {
323
+
324
+ prev = v;
325
+ v = d.getTime();
326
+ ticks.push(v);
327
+
328
+ if (unit == "month" || unit == "quarter") {
329
+ if (tickSize < 1) {
330
+
331
+ // a bit complicated - we'll divide the
332
+ // month/quarter up but we need to take
333
+ // care of fractions so we don't end up in
334
+ // the middle of a day
335
+
336
+ d.setDate(1);
337
+ var start = d.getTime();
338
+ d.setMonth(d.getMonth() +
339
+ (unit == "quarter" ? 3 : 1));
340
+ var end = d.getTime();
341
+ d.setTime(v + carry * timeUnitSize.hour + (end - start) * tickSize);
342
+ carry = d.getHours();
343
+ d.setHours(0);
344
+ } else {
345
+ d.setMonth(d.getMonth() +
346
+ tickSize * (unit == "quarter" ? 3 : 1));
347
+ }
348
+ } else if (unit == "year") {
349
+ d.setFullYear(d.getFullYear() + tickSize);
350
+ } else {
351
+ d.setTime(v + step);
352
+ }
353
+ } while (v < axis.max && v != prev);
354
+
355
+ return ticks;
356
+ };
357
+
358
+ axis.tickFormatter = function (v, axis) {
359
+
360
+ var d = dateGenerator(v, axis.options);
361
+
362
+ // first check global format
363
+
364
+ if (opts.timeformat != null) {
365
+ return formatDate(d, opts.timeformat, opts.monthNames, opts.dayNames);
366
+ }
367
+
368
+ // possibly use quarters if quarters are mentioned in
369
+ // any of these places
370
+
371
+ var useQuarters = (axis.options.tickSize &&
372
+ axis.options.tickSize[1] == "quarter") ||
373
+ (axis.options.minTickSize &&
374
+ axis.options.minTickSize[1] == "quarter");
375
+
376
+ var t = axis.tickSize[0] * timeUnitSize[axis.tickSize[1]];
377
+ var span = axis.max - axis.min;
378
+ var suffix = (opts.twelveHourClock) ? " %p" : "";
379
+ var hourCode = (opts.twelveHourClock) ? "%I" : "%H";
380
+ var fmt;
381
+
382
+ if (t < timeUnitSize.minute) {
383
+ fmt = hourCode + ":%M:%S" + suffix;
384
+ } else if (t < timeUnitSize.day) {
385
+ if (span < 2 * timeUnitSize.day) {
386
+ fmt = hourCode + ":%M" + suffix;
387
+ } else {
388
+ fmt = "%b %d " + hourCode + ":%M" + suffix;
389
+ }
390
+ } else if (t < timeUnitSize.month) {
391
+ fmt = "%b %d";
392
+ } else if ((useQuarters && t < timeUnitSize.quarter) ||
393
+ (!useQuarters && t < timeUnitSize.year)) {
394
+ if (span < timeUnitSize.year) {
395
+ fmt = "%b";
396
+ } else {
397
+ fmt = "%b %Y";
398
+ }
399
+ } else if (useQuarters && t < timeUnitSize.year) {
400
+ if (span < timeUnitSize.year) {
401
+ fmt = "Q%q";
402
+ } else {
403
+ fmt = "Q%q %Y";
404
+ }
405
+ } else {
406
+ fmt = "%Y";
407
+ }
408
+
409
+ var rt = formatDate(d, fmt, opts.monthNames, opts.dayNames);
410
+
411
+ return rt;
412
+ };
413
+ }
414
+ });
415
+ });
416
+ }
417
+
418
+ $.plot.plugins.push({
419
+ init: init,
420
+ options: options,
421
+ name: 'time',
422
+ version: '1.0'
423
+ });
424
+
425
+ // Time-axis support used to be in Flot core, which exposed the
426
+ // formatDate function on the plot object. Various plugins depend
427
+ // on the function, so we need to re-expose it here.
428
+
429
+ $.plot.formatDate = formatDate;
430
+
431
+ })(jQuery);