rademade_admin 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (270) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Rakefile +34 -0
  4. data/app/assets/fonts/rademade_admin/glyphicons-halflings-regular.eot +0 -0
  5. data/app/assets/fonts/rademade_admin/glyphicons-halflings-regular.svg +228 -0
  6. data/app/assets/fonts/rademade_admin/glyphicons-halflings-regular.ttf +0 -0
  7. data/app/assets/fonts/rademade_admin/glyphicons-halflings-regular.woff +0 -0
  8. data/app/assets/images/rademade_admin/backgrounds/back-blue.jpg +0 -0
  9. data/app/assets/images/rademade_admin/backgrounds/back-green.jpg +0 -0
  10. data/app/assets/images/rademade_admin/backgrounds/back-orange.png +0 -0
  11. data/app/assets/images/rademade_admin/backgrounds/blueish.jpg +0 -0
  12. data/app/assets/images/rademade_admin/backgrounds/select-bg.png +0 -0
  13. data/app/assets/images/rademade_admin/datatables/back_disabled.png +0 -0
  14. data/app/assets/images/rademade_admin/datatables/back_enabled.png +0 -0
  15. data/app/assets/images/rademade_admin/datatables/back_enabled_hover.png +0 -0
  16. data/app/assets/images/rademade_admin/datatables/forward_disabled.png +0 -0
  17. data/app/assets/images/rademade_admin/datatables/forward_enabled.png +0 -0
  18. data/app/assets/images/rademade_admin/datatables/forward_enabled_hover.png +0 -0
  19. data/app/assets/images/rademade_admin/datatables/sort_asc.png +0 -0
  20. data/app/assets/images/rademade_admin/datatables/sort_asc_disabled.png +0 -0
  21. data/app/assets/images/rademade_admin/datatables/sort_both.png +0 -0
  22. data/app/assets/images/rademade_admin/datatables/sort_desc.png +0 -0
  23. data/app/assets/images/rademade_admin/datatables/sort_desc_disabled.png +0 -0
  24. data/app/assets/images/rademade_admin/icons/btn-attach.png +0 -0
  25. data/app/assets/images/rademade_admin/icons/btn-setting.png +0 -0
  26. data/app/assets/images/rademade_admin/icons/btn-shuffle.png +0 -0
  27. data/app/assets/images/rademade_admin/icons/btn-tool.png +0 -0
  28. data/app/assets/images/rademade_admin/icons/buble.png +0 -0
  29. data/app/assets/images/rademade_admin/icons/glyphicons-halflings-white.png +0 -0
  30. data/app/assets/images/rademade_admin/icons/glyphicons-halflings.png +0 -0
  31. data/app/assets/images/rademade_admin/icons/ico-arrow-black.png +0 -0
  32. data/app/assets/images/rademade_admin/icons/ico-gallery-edit.png +0 -0
  33. data/app/assets/images/rademade_admin/icons/ico-gallery-trash.png +0 -0
  34. data/app/assets/images/rademade_admin/icons/ico-mail.png +0 -0
  35. data/app/assets/images/rademade_admin/icons/ico-phone.png +0 -0
  36. data/app/assets/images/rademade_admin/icons/ico-table-delete.png +0 -0
  37. data/app/assets/images/rademade_admin/icons/ico-table-edit.png +0 -0
  38. data/app/assets/images/rademade_admin/icons/ico-table-new.png +0 -0
  39. data/app/assets/images/rademade_admin/icons/lens.png +0 -0
  40. data/app/assets/images/rademade_admin/icons/skin-nav-bullets.png +0 -0
  41. data/app/assets/images/rademade_admin/icons/table-icons.png +0 -0
  42. data/app/assets/images/rademade_admin/icons/table-img.png +0 -0
  43. data/app/assets/images/rademade_admin/jquery-ui/animated-overlay.gif +0 -0
  44. data/app/assets/images/rademade_admin/jquery-ui/slider-handler.png +0 -0
  45. data/app/assets/images/rademade_admin/jquery-ui/slider-handler2.png +0 -0
  46. data/app/assets/images/rademade_admin/jquery-ui/ui-bg_diagonals-thick_18_b81900_40x40.png +0 -0
  47. data/app/assets/images/rademade_admin/jquery-ui/ui-bg_diagonals-thick_20_666666_40x40.png +0 -0
  48. data/app/assets/images/rademade_admin/jquery-ui/ui-bg_flat_10_000000_40x100.png +0 -0
  49. data/app/assets/images/rademade_admin/jquery-ui/ui-bg_glass_100_f6f6f6_1x400.png +0 -0
  50. data/app/assets/images/rademade_admin/jquery-ui/ui-bg_glass_100_fdf5ce_1x400.png +0 -0
  51. data/app/assets/images/rademade_admin/jquery-ui/ui-bg_glass_65_ffffff_1x400.png +0 -0
  52. data/app/assets/images/rademade_admin/jquery-ui/ui-bg_gloss-wave_35_f6a828_500x100.png +0 -0
  53. data/app/assets/images/rademade_admin/jquery-ui/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
  54. data/app/assets/images/rademade_admin/jquery-ui/ui-bg_highlight-soft_75_ffe45c_1x100.png +0 -0
  55. data/app/assets/images/rademade_admin/jquery-ui/ui-icons_222222_256x240.png +0 -0
  56. data/app/assets/images/rademade_admin/jquery-ui/ui-icons_228ef1_256x240.png +0 -0
  57. data/app/assets/images/rademade_admin/jquery-ui/ui-icons_ef8c08_256x240.png +0 -0
  58. data/app/assets/images/rademade_admin/jquery-ui/ui-icons_ffd27a_256x240.png +0 -0
  59. data/app/assets/images/rademade_admin/jquery-ui/ui-icons_ffffff_256x240.png +0 -0
  60. data/app/assets/images/rademade_admin/logos/logo-white.png +0 -0
  61. data/app/assets/images/rademade_admin/logos/logo.png +0 -0
  62. data/app/assets/images/rademade_admin/notifier-loader-clean.gif +0 -0
  63. data/app/assets/images/rademade_admin/notifier-loader-plastic.gif +0 -0
  64. data/app/assets/images/rademade_admin/placeholders/new-gallery-img.png +0 -0
  65. data/app/assets/images/rademade_admin/placeholders/no-img-gallery.png +0 -0
  66. data/app/assets/images/rademade_admin/select2/select2.png +0 -0
  67. data/app/assets/images/rademade_admin/select2/select2x2.png +0 -0
  68. data/app/assets/images/rademade_admin/select2/spinner.gif +0 -0
  69. data/app/assets/images/rademade_admin/uniform/bg-input-focus.png +0 -0
  70. data/app/assets/images/rademade_admin/uniform/bg-input.png +0 -0
  71. data/app/assets/images/rademade_admin/uniform/sprite.png +0 -0
  72. data/app/assets/javascripts/rademade_admin.coffee +14 -0
  73. data/app/assets/javascripts/rademade_admin/app/app.coffee +1 -0
  74. data/app/assets/javascripts/rademade_admin/app/common/dnd-sort.coffee +42 -0
  75. data/app/assets/javascripts/rademade_admin/app/common/pagination.coffee +6 -0
  76. data/app/assets/javascripts/rademade_admin/app/common/related-index.coffee +54 -0
  77. data/app/assets/javascripts/rademade_admin/app/common/relation-list.coffee +109 -0
  78. data/app/assets/javascripts/rademade_admin/app/common/sort-list.coffee +47 -0
  79. data/app/assets/javascripts/rademade_admin/app/common/uploader.coffee +53 -0
  80. data/app/assets/javascripts/rademade_admin/app/form-popup/collection.coffee +20 -0
  81. data/app/assets/javascripts/rademade_admin/app/form-popup/initializer.coffee +43 -0
  82. data/app/assets/javascripts/rademade_admin/app/form-popup/model.coffee +4 -0
  83. data/app/assets/javascripts/rademade_admin/app/form-popup/view.coffee +57 -0
  84. data/app/assets/javascripts/rademade_admin/app/forms/insert.coffee +7 -0
  85. data/app/assets/javascripts/rademade_admin/app/forms/login.coffee +4 -0
  86. data/app/assets/javascripts/rademade_admin/app/forms/remove.coffee +6 -0
  87. data/app/assets/javascripts/rademade_admin/app/notifier/initialize.js +31 -0
  88. data/app/assets/javascripts/rademade_admin/detail_admin/theme.js +56 -0
  89. data/app/assets/javascripts/rademade_admin/form/form-ajax-submit.coffee +153 -0
  90. data/app/assets/javascripts/rademade_admin/form/library/jquery.form-serialize.js +54 -0
  91. data/app/assets/javascripts/rademade_admin/form/library/jquery.form.js +1121 -0
  92. data/app/assets/javascripts/rademade_admin/form/library/jquery.formrestrict.js +27 -0
  93. data/app/assets/javascripts/rademade_admin/form/library/jquery.validate.js +1231 -0
  94. data/app/assets/javascripts/rademade_admin/form/library/jquery.validate.messages_ru.js +27 -0
  95. data/app/assets/javascripts/rademade_admin/library/backbone.js +1581 -0
  96. data/app/assets/javascripts/rademade_admin/library/bootstrap/bootstrap.datepicker.js +989 -0
  97. data/app/assets/javascripts/rademade_admin/library/bootstrap/bootstrap.js +1992 -0
  98. data/app/assets/javascripts/rademade_admin/library/ckeditor/config.js +12 -0
  99. data/app/assets/javascripts/rademade_admin/library/jquery/jquery-ui-1.10.4.custom.min.js +6 -0
  100. data/app/assets/javascripts/rademade_admin/library/jquery/jquery.dataTables.js +12099 -0
  101. data/app/assets/javascripts/rademade_admin/library/jquery/jquery.flot.js +2960 -0
  102. data/app/assets/javascripts/rademade_admin/library/jquery/jquery.flot.resize.js +60 -0
  103. data/app/assets/javascripts/rademade_admin/library/jquery/jquery.flot.stack.js +188 -0
  104. data/app/assets/javascripts/rademade_admin/library/jquery/jquery.knob.js +661 -0
  105. data/app/assets/javascripts/rademade_admin/library/jquery/jquery.tablednd.js +1 -0
  106. data/app/assets/javascripts/rademade_admin/library/jquery/jquery.uniform.min.js +1 -0
  107. data/app/assets/javascripts/rademade_admin/library/notifier.custom.js +431 -0
  108. data/app/assets/javascripts/rademade_admin/library/notifier.custom.min.js +15 -0
  109. data/app/assets/javascripts/rademade_admin/library/underscore.js +1276 -0
  110. data/app/assets/javascripts/rademade_admin/library/wysihtml5-0.3.0.js +9463 -0
  111. data/app/assets/stylesheets/rademade_admin.css +14 -0
  112. data/app/assets/stylesheets/rademade_admin/base.scss.erb +23 -0
  113. data/app/assets/stylesheets/rademade_admin/bootstrap/bootstrap-overrides.css.erb +341 -0
  114. data/app/assets/stylesheets/rademade_admin/bootstrap/bootstrap.css.erb +5911 -0
  115. data/app/assets/stylesheets/rademade_admin/bootstrap/main.scss.erb +78 -0
  116. data/app/assets/stylesheets/rademade_admin/detail_admin/chart-showcase.scss.erb +33 -0
  117. data/app/assets/stylesheets/rademade_admin/detail_admin/datatables.scss.erb +3 -0
  118. data/app/assets/stylesheets/rademade_admin/detail_admin/elements.scss.erb +665 -0
  119. data/app/assets/stylesheets/rademade_admin/detail_admin/error.scss.erb +19 -0
  120. data/app/assets/stylesheets/rademade_admin/detail_admin/form-showcase.scss.erb +87 -0
  121. data/app/assets/stylesheets/rademade_admin/detail_admin/form-wizard.scss.erb +261 -0
  122. data/app/assets/stylesheets/rademade_admin/detail_admin/gallery.scss.erb +106 -0
  123. data/app/assets/stylesheets/rademade_admin/detail_admin/grids.scss.erb +21 -0
  124. data/app/assets/stylesheets/rademade_admin/detail_admin/icons.scss.erb +115 -0
  125. data/app/assets/stylesheets/rademade_admin/detail_admin/index.scss.erb +290 -0
  126. data/app/assets/stylesheets/rademade_admin/detail_admin/layout.scss.erb +523 -0
  127. data/app/assets/stylesheets/rademade_admin/detail_admin/new-user.scss.erb +49 -0
  128. data/app/assets/stylesheets/rademade_admin/detail_admin/personal-info.scss.erb +144 -0
  129. data/app/assets/stylesheets/rademade_admin/detail_admin/popup.scss.erb +3 -0
  130. data/app/assets/stylesheets/rademade_admin/detail_admin/signin.scss.erb +128 -0
  131. data/app/assets/stylesheets/rademade_admin/detail_admin/tables.scss.erb +195 -0
  132. data/app/assets/stylesheets/rademade_admin/detail_admin/ui-elements.scss.erb +177 -0
  133. data/app/assets/stylesheets/rademade_admin/detail_admin/user-list.scss.erb +112 -0
  134. data/app/assets/stylesheets/rademade_admin/detail_admin/user-profile.scss.erb +169 -0
  135. data/app/assets/stylesheets/rademade_admin/detail_admin/web-app-icons.scss.erb +25 -0
  136. data/app/assets/stylesheets/rademade_admin/edit.css +7 -0
  137. data/app/assets/stylesheets/rademade_admin/file-upload.scss.erb +12 -0
  138. data/app/assets/stylesheets/rademade_admin/lib/bootstrap.datepicker.css.erb +302 -0
  139. data/app/assets/stylesheets/rademade_admin/lib/jquery-ui-1.10.2.custom.css.erb +544 -0
  140. data/app/assets/stylesheets/rademade_admin/lib/morris.css.erb +2 -0
  141. data/app/assets/stylesheets/rademade_admin/lib/select2.css.erb +513 -0
  142. data/app/assets/stylesheets/rademade_admin/lib/uniform.default.css.erb +366 -0
  143. data/app/assets/stylesheets/rademade_admin/notifier.custom.css.erb +661 -0
  144. data/app/assets/stylesheets/rademade_admin/scaffolds.scss.erb +32 -0
  145. data/app/controllers/rademade_admin/abstract_controller.rb +24 -0
  146. data/app/controllers/rademade_admin/admin_users_controller.rb +16 -0
  147. data/app/controllers/rademade_admin/application_controller.rb +7 -0
  148. data/app/controllers/rademade_admin/dashboard_controller.rb +16 -0
  149. data/app/controllers/rademade_admin/model_controller.rb +122 -0
  150. data/app/controllers/rademade_admin/sessions_controller.rb +22 -0
  151. data/app/helpers/rademade_admin/field_helper.rb +27 -0
  152. data/app/helpers/rademade_admin/form_helper.rb +63 -0
  153. data/app/helpers/rademade_admin/menu_helper.rb +45 -0
  154. data/app/helpers/rademade_admin/name_helper.rb +17 -0
  155. data/app/helpers/rademade_admin/upload_preview_helper.rb +59 -0
  156. data/app/helpers/rademade_admin/uri_helper.rb +104 -0
  157. data/app/inputs/rademade_admin/admin_file_input.rb +70 -0
  158. data/app/inputs/rademade_admin/admin_select_input.rb +81 -0
  159. data/app/inputs/rademade_admin/admin_textarea_input.rb +5 -0
  160. data/app/inputs/rademade_admin/boolean_input.rb +13 -0
  161. data/app/models/rademade_admin/ability.rb +39 -0
  162. data/app/models/rademade_admin/user.rb +6 -0
  163. data/app/serializers/autocomplete_serializer.rb +46 -0
  164. data/app/services/form_builder.rb +3 -0
  165. data/app/services/loader_service.rb +7 -0
  166. data/app/services/login.rb +18 -0
  167. data/app/services/login/error.rb +17 -0
  168. data/app/services/model_controller/instance_options.rb +38 -0
  169. data/app/services/model_controller/linker.rb +49 -0
  170. data/app/services/model_controller/model_options.rb +39 -0
  171. data/app/services/model_controller/notifier.rb +40 -0
  172. data/app/services/model_controller/templates.rb +27 -0
  173. data/app/services/saver.rb +69 -0
  174. data/app/services/search/conditions/abstract.rb +53 -0
  175. data/app/services/search/conditions/autocomplete.rb +44 -0
  176. data/app/services/search/conditions/list.rb +35 -0
  177. data/app/services/search/query_adapter/abstract.rb +54 -0
  178. data/app/services/search/query_adapter/active_record.rb +33 -0
  179. data/app/services/search/query_adapter/mongoid.rb +33 -0
  180. data/app/services/search/searcher.rb +19 -0
  181. data/app/services/sortable_service.rb +37 -0
  182. data/app/services/video_service.rb +22 -0
  183. data/app/views/layouts/rademade_admin.html.erb +13 -0
  184. data/app/views/rademade_admin/_blocks/_form_control.html.erb +5 -0
  185. data/app/views/rademade_admin/_blocks/_header.html.erb +30 -0
  186. data/app/views/rademade_admin/_blocks/_menu.html.erb +41 -0
  187. data/app/views/rademade_admin/_blocks/_pagination.html.erb +18 -0
  188. data/app/views/rademade_admin/_blocks/_sub_menu.html.erb +19 -0
  189. data/app/views/rademade_admin/_blocks/sub_menu/_link.html.erb +5 -0
  190. data/app/views/rademade_admin/_layouts/inner.html.erb +37 -0
  191. data/app/views/rademade_admin/_layouts/inner/form.html.erb +15 -0
  192. data/app/views/rademade_admin/_layouts/inner/index_table.html.erb +59 -0
  193. data/app/views/rademade_admin/_layouts/inner/related_index_table.html.erb +55 -0
  194. data/app/views/rademade_admin/abstract/_form.html.erb +10 -0
  195. data/app/views/rademade_admin/abstract/edit.html.erb +9 -0
  196. data/app/views/rademade_admin/abstract/form.html.erb +1 -0
  197. data/app/views/rademade_admin/abstract/index.html.erb +77 -0
  198. data/app/views/rademade_admin/abstract/new.html.erb +9 -0
  199. data/app/views/rademade_admin/abstract/related_index.html.erb +78 -0
  200. data/app/views/rademade_admin/dashboard/index.html.erb +9 -0
  201. data/app/views/rademade_admin/dashboard/login.html.erb +31 -0
  202. data/config/initializers/devise.rb +250 -0
  203. data/config/initializers/routes.rb +3 -0
  204. data/config/locales/devise.en.yml +59 -0
  205. data/config/routes.rb +17 -0
  206. data/lib/rademade_admin.rb +26 -0
  207. data/lib/rademade_admin/configuration.rb +17 -0
  208. data/lib/rademade_admin/engine.rb +22 -0
  209. data/lib/rademade_admin/file_info_formatter.rb +12 -0
  210. data/lib/rademade_admin/hrml_buffer.rb +14 -0
  211. data/lib/rademade_admin/model/configuration.rb +52 -0
  212. data/lib/rademade_admin/model/configuration/fields.rb +33 -0
  213. data/lib/rademade_admin/model/data_adapter.rb +59 -0
  214. data/lib/rademade_admin/model/data_adapter/active_record.rb +53 -0
  215. data/lib/rademade_admin/model/data_adapter/mongoid.rb +49 -0
  216. data/lib/rademade_admin/model/graph.rb +36 -0
  217. data/lib/rademade_admin/model/info.rb +106 -0
  218. data/lib/rademade_admin/model/reflection.rb +31 -0
  219. data/lib/rademade_admin/model/reflection/data.rb +47 -0
  220. data/lib/rademade_admin/model/reflection/uploader.rb +18 -0
  221. data/lib/rademade_admin/routing.rb +3 -0
  222. data/lib/rademade_admin/routing/mapper.rb +42 -0
  223. data/lib/rademade_admin/routing/resource.rb +6 -0
  224. data/lib/rademade_admin/sortable.rb +12 -0
  225. data/lib/rademade_admin/uploader/photo.rb +13 -0
  226. data/lib/rademade_admin/uploader/video.rb +11 -0
  227. data/lib/rademade_admin/user.rb +16 -0
  228. data/lib/rademade_admin/version.rb +3 -0
  229. data/lib/tasks/rademade_admin_tasks.rake +4 -0
  230. data/test/dummy/README.rdoc +28 -0
  231. data/test/dummy/Rakefile +6 -0
  232. data/test/dummy/app/assets/javascripts/application.js +13 -0
  233. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  234. data/test/dummy/app/controllers/application_controller.rb +5 -0
  235. data/test/dummy/app/controllers/rademade_admin/posts_controller.rb +9 -0
  236. data/test/dummy/app/controllers/rademade_admin/users_controller.rb +14 -0
  237. data/test/dummy/app/helpers/application_helper.rb +2 -0
  238. data/test/dummy/app/models/post.rb +9 -0
  239. data/test/dummy/app/models/user.rb +7 -0
  240. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  241. data/test/dummy/bin/bundle +3 -0
  242. data/test/dummy/bin/rails +4 -0
  243. data/test/dummy/bin/rake +4 -0
  244. data/test/dummy/config.ru +4 -0
  245. data/test/dummy/config/application.rb +29 -0
  246. data/test/dummy/config/boot.rb +5 -0
  247. data/test/dummy/config/environment.rb +5 -0
  248. data/test/dummy/config/environments/development.rb +29 -0
  249. data/test/dummy/config/environments/production.rb +80 -0
  250. data/test/dummy/config/environments/test.rb +36 -0
  251. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  252. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  253. data/test/dummy/config/initializers/inflections.rb +16 -0
  254. data/test/dummy/config/initializers/mime_types.rb +5 -0
  255. data/test/dummy/config/initializers/rademade_admin.rb +3 -0
  256. data/test/dummy/config/initializers/secret_token.rb +12 -0
  257. data/test/dummy/config/initializers/session_store.rb +3 -0
  258. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  259. data/test/dummy/config/locales/en.yml +23 -0
  260. data/test/dummy/config/mongoid.yml +6 -0
  261. data/test/dummy/config/routes.rb +8 -0
  262. data/test/dummy/db/development.sqlite3 +0 -0
  263. data/test/dummy/public/404.html +58 -0
  264. data/test/dummy/public/422.html +58 -0
  265. data/test/dummy/public/500.html +57 -0
  266. data/test/dummy/public/favicon.ico +0 -0
  267. data/test/integration/navigation_test.rb +10 -0
  268. data/test/rademade_admin_test.rb +7 -0
  269. data/test/test_helper.rb +15 -0
  270. metadata +691 -0
@@ -0,0 +1,2960 @@
1
+ /* Javascript plotting library for jQuery, version 0.8.0-beta.
2
+
3
+ Copyright (c) 2007-2013 IOLA and Ole Laursen.
4
+ Licensed under the MIT license.
5
+
6
+ */
7
+
8
+ // first an inline dependency, jquery.colorhelpers.js, we inline it here
9
+ // for convenience
10
+
11
+ /* Plugin for jQuery for working with colors.
12
+ *
13
+ * Version 1.1.
14
+ *
15
+ * Inspiration from jQuery color animation plugin by John Resig.
16
+ *
17
+ * Released under the MIT license by Ole Laursen, October 2009.
18
+ *
19
+ * Examples:
20
+ *
21
+ * $.color.parse("#fff").scale('rgb', 0.25).add('a', -0.5).toString()
22
+ * var c = $.color.extract($("#mydiv"), 'background-color');
23
+ * console.log(c.r, c.g, c.b, c.a);
24
+ * $.color.make(100, 50, 25, 0.4).toString() // returns "rgba(100,50,25,0.4)"
25
+ *
26
+ * Note that .scale() and .add() return the same modified object
27
+ * instead of making a new one.
28
+ *
29
+ * V. 1.1: Fix error handling so e.g. parsing an empty string does
30
+ * produce a color rather than just crashing.
31
+ */
32
+ (function(B){B.color={};B.color.make=function(F,E,C,D){var G={};G.r=F||0;G.g=E||0;G.b=C||0;G.a=D!=null?D:1;G.add=function(J,I){for(var H=0;H<J.length;++H){G[J.charAt(H)]+=I}return G.normalize()};G.scale=function(J,I){for(var H=0;H<J.length;++H){G[J.charAt(H)]*=I}return G.normalize()};G.toString=function(){if(G.a>=1){return"rgb("+[G.r,G.g,G.b].join(",")+")"}else{return"rgba("+[G.r,G.g,G.b,G.a].join(",")+")"}};G.normalize=function(){function H(J,K,I){return K<J?J:(K>I?I:K)}G.r=H(0,parseInt(G.r),255);G.g=H(0,parseInt(G.g),255);G.b=H(0,parseInt(G.b),255);G.a=H(0,G.a,1);return G};G.clone=function(){return B.color.make(G.r,G.b,G.g,G.a)};return G.normalize()};B.color.extract=function(D,C){var E;do{E=D.css(C).toLowerCase();if(E!=""&&E!="transparent"){break}D=D.parent()}while(!B.nodeName(D.get(0),"body"));if(E=="rgba(0, 0, 0, 0)"){E="transparent"}return B.color.parse(E)};B.color.parse=function(F){var E,C=B.color.make;if(E=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(F)){return C(parseInt(E[1],10),parseInt(E[2],10),parseInt(E[3],10))}if(E=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(F)){return C(parseInt(E[1],10),parseInt(E[2],10),parseInt(E[3],10),parseFloat(E[4]))}if(E=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(F)){return C(parseFloat(E[1])*2.55,parseFloat(E[2])*2.55,parseFloat(E[3])*2.55)}if(E=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(F)){return C(parseFloat(E[1])*2.55,parseFloat(E[2])*2.55,parseFloat(E[3])*2.55,parseFloat(E[4]))}if(E=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(F)){return C(parseInt(E[1],16),parseInt(E[2],16),parseInt(E[3],16))}if(E=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(F)){return C(parseInt(E[1]+E[1],16),parseInt(E[2]+E[2],16),parseInt(E[3]+E[3],16))}var D=B.trim(F).toLowerCase();if(D=="transparent"){return C(255,255,255,0)}else{E=A[D]||[0,0,0];return C(E[0],E[1],E[2])}};var A={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery);
33
+
34
+ // the actual Flot code
35
+ (function($) {
36
+
37
+ // Cache the prototype hasOwnProperty for faster access
38
+
39
+ var hasOwnProperty = Object.prototype.hasOwnProperty;
40
+
41
+ // Add default styles for tick labels and other text
42
+
43
+ $(function() {
44
+ $("head").prepend([
45
+ "<style id='flot-default-styles'>",
46
+ ".flot-tick-label {font-size:smaller;color:#545454;}",
47
+ "</style>"
48
+ ].join(""));
49
+ });
50
+
51
+ ///////////////////////////////////////////////////////////////////////////
52
+ // The Canvas object is a wrapper around an HTML5 <canvas> tag.
53
+ //
54
+ // @constructor
55
+ // @param {string} cls List of classes to apply to the canvas.
56
+ // @param {element} container Element onto which to append the canvas.
57
+ //
58
+ // Requiring a container is a little iffy, but unfortunately canvas
59
+ // operations don't work unless the canvas is attached to the DOM.
60
+
61
+ function Canvas(cls, container) {
62
+
63
+ var element = container.children("." + cls)[0];
64
+
65
+ if (element == null) {
66
+
67
+ element = document.createElement("canvas");
68
+ element.className = cls;
69
+
70
+ $(element).css({ direction: "ltr", position: "absolute", left: 0, top: 0 })
71
+ .appendTo(container);
72
+
73
+ // If HTML5 Canvas isn't available, fall back to [Ex|Flash]canvas
74
+
75
+ if (!element.getContext) {
76
+ if (window.G_vmlCanvasManager) {
77
+ element = window.G_vmlCanvasManager.initElement(element);
78
+ } else {
79
+ throw new Error("Canvas is not available. If you're using IE with a fall-back such as Excanvas, then there's either a mistake in your conditional include, or the page has no DOCTYPE and is rendering in Quirks Mode.");
80
+ }
81
+ }
82
+ }
83
+
84
+ this.element = element;
85
+
86
+ var context = this.context = element.getContext("2d");
87
+
88
+ // Determine the screen's ratio of physical to device-independent
89
+ // pixels. This is the ratio between the canvas width that the browser
90
+ // advertises and the number of pixels actually present in that space.
91
+
92
+ // The iPhone 4, for example, has a device-independent width of 320px,
93
+ // but its screen is actually 640px wide. It therefore has a pixel
94
+ // ratio of 2, while most normal devices have a ratio of 1.
95
+
96
+ var devicePixelRatio = window.devicePixelRatio || 1,
97
+ backingStoreRatio =
98
+ context.webkitBackingStorePixelRatio ||
99
+ context.mozBackingStorePixelRatio ||
100
+ context.msBackingStorePixelRatio ||
101
+ context.oBackingStorePixelRatio ||
102
+ context.backingStorePixelRatio || 1;
103
+
104
+ this.pixelRatio = devicePixelRatio / backingStoreRatio;
105
+
106
+ // Size the canvas to match the internal dimensions of its container
107
+
108
+ this.resize(container.width(), container.height());
109
+
110
+ // Collection of HTML div layers for text overlaid onto the canvas
111
+
112
+ this.text = {};
113
+
114
+ // Cache of text fragments and metrics, so we can avoid expensively
115
+ // re-calculating them when the plot is re-rendered in a loop.
116
+
117
+ this._textCache = {};
118
+ }
119
+
120
+ // Resizes the canvas to the given dimensions.
121
+ //
122
+ // @param {number} width New width of the canvas, in pixels.
123
+ // @param {number} width New height of the canvas, in pixels.
124
+
125
+ Canvas.prototype.resize = function(width, height) {
126
+
127
+ if (width <= 0 || height <= 0) {
128
+ throw new Error("Invalid dimensions for plot, width = " + width + ", height = " + height);
129
+ }
130
+
131
+ var element = this.element,
132
+ context = this.context,
133
+ pixelRatio = this.pixelRatio;
134
+
135
+ // Resize the canvas, increasing its density based on the display's
136
+ // pixel ratio; basically giving it more pixels without increasing the
137
+ // size of its element, to take advantage of the fact that retina
138
+ // displays have that many more pixels in the same advertised space.
139
+
140
+ // Resizing should reset the state (excanvas seems to be buggy though)
141
+
142
+ if (this.width != width) {
143
+ element.width = width * pixelRatio;
144
+ element.style.width = width + "px";
145
+ this.width = width;
146
+ }
147
+
148
+ if (this.height != height) {
149
+ element.height = height * pixelRatio;
150
+ element.style.height = height + "px";
151
+ this.height = height;
152
+ }
153
+
154
+ // Save the context, so we can reset in case we get replotted. The
155
+ // restore ensure that we're really back at the initial state, and
156
+ // should be safe even if we haven't saved the initial state yet.
157
+
158
+ context.restore();
159
+ context.save();
160
+
161
+ // Scale the coordinate space to match the display density; so even though we
162
+ // may have twice as many pixels, we still want lines and other drawing to
163
+ // appear at the same size; the extra pixels will just make them crisper.
164
+
165
+ context.scale(pixelRatio, pixelRatio);
166
+ };
167
+
168
+ // Clears the entire canvas area, not including any overlaid HTML text
169
+
170
+ Canvas.prototype.clear = function() {
171
+ this.context.clearRect(0, 0, this.width, this.height);
172
+ };
173
+
174
+ // Finishes rendering the canvas, including managing the text overlay.
175
+
176
+ Canvas.prototype.render = function() {
177
+
178
+ var cache = this._textCache;
179
+
180
+ // For each text layer, add elements marked as active that haven't
181
+ // already been rendered, and remove those that are no longer active.
182
+
183
+ for (var layerKey in cache) {
184
+ if (hasOwnProperty.call(cache, layerKey)) {
185
+
186
+ var layer = this.getTextLayer(layerKey),
187
+ layerCache = cache[layerKey];
188
+
189
+ layer.hide();
190
+
191
+ for (var styleKey in layerCache) {
192
+ if (hasOwnProperty.call(layerCache, styleKey)) {
193
+ var styleCache = layerCache[styleKey];
194
+ for (var key in styleCache) {
195
+ if (hasOwnProperty.call(styleCache, key)) {
196
+ var info = styleCache[key];
197
+ if (info.active) {
198
+ if (!info.rendered) {
199
+ layer.append(info.element);
200
+ info.rendered = true;
201
+ }
202
+ } else {
203
+ delete styleCache[key];
204
+ if (info.rendered) {
205
+ info.element.detach();
206
+ }
207
+ }
208
+ }
209
+ }
210
+ }
211
+ }
212
+
213
+ layer.show();
214
+ }
215
+ }
216
+ };
217
+
218
+ // Creates (if necessary) and returns the text overlay container.
219
+ //
220
+ // @param {string} classes String of space-separated CSS classes used to
221
+ // uniquely identify the text layer.
222
+ // @return {object} The jQuery-wrapped text-layer div.
223
+
224
+ Canvas.prototype.getTextLayer = function(classes) {
225
+
226
+ var layer = this.text[classes];
227
+
228
+ // Create the text layer if it doesn't exist
229
+
230
+ if (layer == null) {
231
+ layer = this.text[classes] = $("<div></div>")
232
+ .addClass("flot-text " + classes)
233
+ .css({
234
+ position: "absolute",
235
+ top: 0,
236
+ left: 0,
237
+ bottom: 0,
238
+ right: 0
239
+ })
240
+ .insertAfter(this.element);
241
+ }
242
+
243
+ return layer;
244
+ };
245
+
246
+ // Creates (if necessary) and returns a text info object.
247
+ //
248
+ // The object looks like this:
249
+ //
250
+ // {
251
+ // width: Width of the text's wrapper div.
252
+ // height: Height of the text's wrapper div.
253
+ // active: Flag indicating whether the text should be visible.
254
+ // rendered: Flag indicating whether the text is currently visible.
255
+ // element: The jQuery-wrapped HTML div containing the text.
256
+ // }
257
+ //
258
+ // Canvas maintains a cache of recently-used text info objects; getTextInfo
259
+ // either returns the cached element or creates a new entry.
260
+ //
261
+ // @param {string} layer A string of space-separated CSS classes uniquely
262
+ // identifying the layer containing this text.
263
+ // @param {string} text Text string to retrieve info for.
264
+ // @param {(string|object)=} font Either a string of space-separated CSS
265
+ // classes or a font-spec object, defining the text's font and style.
266
+ // @param {number=} angle Angle at which to rotate the text, in degrees.
267
+ // Angle is currently unused, it will be implemented in the future.
268
+ // @return {object} a text info object.
269
+
270
+ Canvas.prototype.getTextInfo = function(layer, text, font, angle) {
271
+
272
+ var textStyle, layerCache, styleCache, info;
273
+
274
+ // Cast the value to a string, in case we were given a number or such
275
+
276
+ text = "" + text;
277
+
278
+ // If the font is a font-spec object, generate a CSS font definition
279
+
280
+ if (typeof font === "object") {
281
+ textStyle = font.style + " " + font.variant + " " + font.weight + " " + font.size + "px " + font.family;
282
+ } else {
283
+ textStyle = font;
284
+ }
285
+
286
+ // Retrieve (or create) the cache for the text's layer and styles
287
+
288
+ layerCache = this._textCache[layer];
289
+
290
+ if (layerCache == null) {
291
+ layerCache = this._textCache[layer] = {};
292
+ }
293
+
294
+ styleCache = layerCache[textStyle];
295
+
296
+ if (styleCache == null) {
297
+ styleCache = layerCache[textStyle] = {};
298
+ }
299
+
300
+ info = styleCache[text];
301
+
302
+ // If we can't find a matching element in our cache, create a new one
303
+
304
+ if (info == null) {
305
+
306
+ var element = $("<div></div>").html(text)
307
+ .css({
308
+ position: "absolute",
309
+ top: -9999
310
+ })
311
+ .appendTo(this.getTextLayer(layer));
312
+
313
+ if (typeof font === "object") {
314
+ element.css({
315
+ font: textStyle,
316
+ color: font.color
317
+ });
318
+ } else if (typeof font === "string") {
319
+ element.addClass(font);
320
+ }
321
+
322
+ info = styleCache[text] = {
323
+ active: false,
324
+ rendered: false,
325
+ element: element,
326
+ width: element.outerWidth(true),
327
+ height: element.outerHeight(true)
328
+ };
329
+
330
+ element.detach();
331
+ }
332
+
333
+ return info;
334
+ };
335
+
336
+ // Adds a text string to the canvas text overlay.
337
+ //
338
+ // The text isn't drawn immediately; it is marked as rendering, which will
339
+ // result in its addition to the canvas on the next render pass.
340
+ //
341
+ // @param {string} layer A string of space-separated CSS classes uniquely
342
+ // identifying the layer containing this text.
343
+ // @param {number} x X coordinate at which to draw the text.
344
+ // @param {number} y Y coordinate at which to draw the text.
345
+ // @param {string} text Text string to draw.
346
+ // @param {(string|object)=} font Either a string of space-separated CSS
347
+ // classes or a font-spec object, defining the text's font and style.
348
+ // @param {number=} angle Angle at which to rotate the text, in degrees.
349
+ // Angle is currently unused, it will be implemented in the future.
350
+ // @param {string=} halign Horizontal alignment of the text; either "left",
351
+ // "center" or "right".
352
+ // @param {string=} valign Vertical alignment of the text; either "top",
353
+ // "middle" or "bottom".
354
+
355
+ Canvas.prototype.addText = function(layer, x, y, text, font, angle, halign, valign) {
356
+
357
+ var info = this.getTextInfo(layer, text, font, angle);
358
+
359
+ // Mark the div for inclusion in the next render pass
360
+
361
+ info.active = true;
362
+
363
+ // Tweak the div's position to match the text's alignment
364
+
365
+ if (halign == "center") {
366
+ x -= info.width / 2;
367
+ } else if (halign == "right") {
368
+ x -= info.width;
369
+ }
370
+
371
+ if (valign == "middle") {
372
+ y -= info.height / 2;
373
+ } else if (valign == "bottom") {
374
+ y -= info.height;
375
+ }
376
+
377
+ // Move the element to its final position within the container
378
+
379
+ info.element.css({
380
+ top: parseInt(y, 10),
381
+ left: parseInt(x, 10)
382
+ });
383
+ };
384
+
385
+ // Removes one or more text strings from the canvas text overlay.
386
+ //
387
+ // If no parameters are given, all text within the layer is removed.
388
+ // The text is not actually removed; it is simply marked as inactive, which
389
+ // will result in its removal on the next render pass.
390
+ //
391
+ // @param {string} layer A string of space-separated CSS classes uniquely
392
+ // identifying the layer containing this text.
393
+ // @param {string} text Text string to remove.
394
+ // @param {(string|object)=} font Either a string of space-separated CSS
395
+ // classes or a font-spec object, defining the text's font and style.
396
+ // @param {number=} angle Angle at which the text is rotated, in degrees.
397
+ // Angle is currently unused, it will be implemented in the future.
398
+
399
+ Canvas.prototype.removeText = function(layer, text, font, angle) {
400
+ if (text == null) {
401
+ var layerCache = this._textCache[layer];
402
+ if (layerCache != null) {
403
+ for (var styleKey in layerCache) {
404
+ if (hasOwnProperty.call(layerCache, styleKey)) {
405
+ var styleCache = layerCache[styleKey]
406
+ for (var key in styleCache) {
407
+ if (hasOwnProperty.call(styleCache, key)) {
408
+ styleCache[key].active = false;
409
+ }
410
+ }
411
+ }
412
+ }
413
+ }
414
+ } else {
415
+ this.getTextInfo(layer, text, font, angle).active = false;
416
+ }
417
+ };
418
+
419
+ ///////////////////////////////////////////////////////////////////////////
420
+ // The top-level container for the entire plot.
421
+
422
+ function Plot(placeholder, data_, options_, plugins) {
423
+ // data is on the form:
424
+ // [ series1, series2 ... ]
425
+ // where series is either just the data as [ [x1, y1], [x2, y2], ... ]
426
+ // or { data: [ [x1, y1], [x2, y2], ... ], label: "some label", ... }
427
+
428
+ var series = [],
429
+ options = {
430
+ // the color theme used for graphs
431
+ colors: ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"],
432
+ legend: {
433
+ show: true,
434
+ noColumns: 1, // number of colums in legend table
435
+ labelFormatter: null, // fn: string -> string
436
+ labelBoxBorderColor: "#ccc", // border color for the little label boxes
437
+ container: null, // container (as jQuery object) to put legend in, null means default on top of graph
438
+ position: "ne", // position of default legend container within plot
439
+ margin: 5, // distance from grid edge to default legend container within plot
440
+ backgroundColor: null, // null means auto-detect
441
+ backgroundOpacity: 0.85, // set to 0 to avoid background
442
+ sorted: null // default to no legend sorting
443
+ },
444
+ xaxis: {
445
+ show: null, // null = auto-detect, true = always, false = never
446
+ position: "bottom", // or "top"
447
+ mode: null, // null or "time"
448
+ timezone: null, // "browser" for local to the client or timezone for timezone-js
449
+ font: null, // null (derived from CSS in placeholder) or object like { size: 11, style: "italic", weight: "bold", family: "sans-serif", variant: "small-caps" }
450
+ color: null, // base color, labels, ticks
451
+ tickColor: null, // possibly different color of ticks, e.g. "rgba(0,0,0,0.15)"
452
+ transform: null, // null or f: number -> number to transform axis
453
+ inverseTransform: null, // if transform is set, this should be the inverse function
454
+ min: null, // min. value to show, null means set automatically
455
+ max: null, // max. value to show, null means set automatically
456
+ autoscaleMargin: null, // margin in % to add if auto-setting min/max
457
+ ticks: null, // either [1, 3] or [[1, "a"], 3] or (fn: axis info -> ticks) or app. number of ticks for auto-ticks
458
+ tickFormatter: null, // fn: number -> string
459
+ labelWidth: null, // size of tick labels in pixels
460
+ labelHeight: null,
461
+ reserveSpace: null, // whether to reserve space even if axis isn't shown
462
+ tickLength: null, // size in pixels of ticks, or "full" for whole line
463
+ alignTicksWithAxis: null, // axis number or null for no sync
464
+
465
+ // mode specific options
466
+ tickDecimals: null, // no. of decimals, null means auto
467
+ tickSize: null, // number or [number, "unit"]
468
+ minTickSize: null, // number or [number, "unit"]
469
+ monthNames: null, // list of names of months
470
+ timeformat: null, // format string to use
471
+ twelveHourClock: false // 12 or 24 time in time mode
472
+ },
473
+ yaxis: {
474
+ autoscaleMargin: 0.02,
475
+ position: "left" // or "right"
476
+ },
477
+ xaxes: [],
478
+ yaxes: [],
479
+ series: {
480
+ points: {
481
+ show: false,
482
+ radius: 3,
483
+ lineWidth: 2, // in pixels
484
+ fill: true,
485
+ fillColor: "#ffffff",
486
+ symbol: "circle" // or callback
487
+ },
488
+ lines: {
489
+ // we don't put in show: false so we can see
490
+ // whether lines were actively disabled
491
+ lineWidth: 2, // in pixels
492
+ fill: false,
493
+ fillColor: null,
494
+ steps: false
495
+ // Omit 'zero', so we can later default its value to
496
+ // match that of the 'fill' option.
497
+ },
498
+ bars: {
499
+ show: false,
500
+ lineWidth: 2, // in pixels
501
+ barWidth: 1, // in units of the x axis
502
+ fill: true,
503
+ fillColor: null,
504
+ align: "left", // "left", "right", or "center"
505
+ horizontal: false,
506
+ zero: true
507
+ },
508
+ shadowSize: 3,
509
+ highlightColor: null
510
+ },
511
+ grid: {
512
+ show: true,
513
+ aboveData: false,
514
+ color: "#545454", // primary color used for outline and labels
515
+ backgroundColor: null, // null for transparent, else color
516
+ borderColor: null, // set if different from the grid color
517
+ tickColor: null, // color for the ticks, e.g. "rgba(0,0,0,0.15)"
518
+ margin: 0, // distance from the canvas edge to the grid
519
+ labelMargin: 5, // in pixels
520
+ axisMargin: 8, // in pixels
521
+ borderWidth: 2, // in pixels
522
+ minBorderMargin: null, // in pixels, null means taken from points radius
523
+ markings: null, // array of ranges or fn: axes -> array of ranges
524
+ markingsColor: "#f4f4f4",
525
+ markingsLineWidth: 2,
526
+ // interactive stuff
527
+ clickable: false,
528
+ hoverable: false,
529
+ autoHighlight: true, // highlight in case mouse is near
530
+ mouseActiveRadius: 10 // how far the mouse can be away to activate an item
531
+ },
532
+ interaction: {
533
+ redrawOverlayInterval: 1000/60 // time between updates, -1 means in same flow
534
+ },
535
+ hooks: {}
536
+ },
537
+ surface = null, // the canvas for the plot itself
538
+ overlay = null, // canvas for interactive stuff on top of plot
539
+ eventHolder = null, // jQuery object that events should be bound to
540
+ ctx = null, octx = null,
541
+ xaxes = [], yaxes = [],
542
+ plotOffset = { left: 0, right: 0, top: 0, bottom: 0},
543
+ plotWidth = 0, plotHeight = 0,
544
+ hooks = {
545
+ processOptions: [],
546
+ processRawData: [],
547
+ processDatapoints: [],
548
+ processOffset: [],
549
+ drawBackground: [],
550
+ drawSeries: [],
551
+ draw: [],
552
+ bindEvents: [],
553
+ drawOverlay: [],
554
+ shutdown: []
555
+ },
556
+ plot = this;
557
+
558
+ // public functions
559
+ plot.setData = setData;
560
+ plot.setupGrid = setupGrid;
561
+ plot.draw = draw;
562
+ plot.getPlaceholder = function() { return placeholder; };
563
+ plot.getCanvas = function() { return surface.element; };
564
+ plot.getPlotOffset = function() { return plotOffset; };
565
+ plot.width = function () { return plotWidth; };
566
+ plot.height = function () { return plotHeight; };
567
+ plot.offset = function () {
568
+ var o = eventHolder.offset();
569
+ o.left += plotOffset.left;
570
+ o.top += plotOffset.top;
571
+ return o;
572
+ };
573
+ plot.getData = function () { return series; };
574
+ plot.getAxes = function () {
575
+ var res = {}, i;
576
+ $.each(xaxes.concat(yaxes), function (_, axis) {
577
+ if (axis)
578
+ res[axis.direction + (axis.n != 1 ? axis.n : "") + "axis"] = axis;
579
+ });
580
+ return res;
581
+ };
582
+ plot.getXAxes = function () { return xaxes; };
583
+ plot.getYAxes = function () { return yaxes; };
584
+ plot.c2p = canvasToAxisCoords;
585
+ plot.p2c = axisToCanvasCoords;
586
+ plot.getOptions = function () { return options; };
587
+ plot.highlight = highlight;
588
+ plot.unhighlight = unhighlight;
589
+ plot.triggerRedrawOverlay = triggerRedrawOverlay;
590
+ plot.pointOffset = function(point) {
591
+ return {
592
+ left: parseInt(xaxes[axisNumber(point, "x") - 1].p2c(+point.x) + plotOffset.left, 10),
593
+ top: parseInt(yaxes[axisNumber(point, "y") - 1].p2c(+point.y) + plotOffset.top, 10)
594
+ };
595
+ };
596
+ plot.shutdown = shutdown;
597
+ plot.resize = function () {
598
+ var width = placeholder.width(),
599
+ height = placeholder.height();
600
+ surface.resize(width, height);
601
+ overlay.resize(width, height);
602
+ };
603
+
604
+ // public attributes
605
+ plot.hooks = hooks;
606
+
607
+ // initialize
608
+ initPlugins(plot);
609
+ parseOptions(options_);
610
+ setupCanvases();
611
+ setData(data_);
612
+ setupGrid();
613
+ draw();
614
+ bindEvents();
615
+
616
+
617
+ function executeHooks(hook, args) {
618
+ args = [plot].concat(args);
619
+ for (var i = 0; i < hook.length; ++i)
620
+ hook[i].apply(this, args);
621
+ }
622
+
623
+ function initPlugins() {
624
+
625
+ // References to key classes, allowing plugins to modify them
626
+
627
+ var classes = {
628
+ Canvas: Canvas
629
+ };
630
+
631
+ for (var i = 0; i < plugins.length; ++i) {
632
+ var p = plugins[i];
633
+ p.init(plot, classes);
634
+ if (p.options)
635
+ $.extend(true, options, p.options);
636
+ }
637
+ }
638
+
639
+ function parseOptions(opts) {
640
+
641
+ $.extend(true, options, opts);
642
+
643
+ if (options.xaxis.color == null)
644
+ options.xaxis.color = $.color.parse(options.grid.color).scale('a', 0.22).toString();
645
+ if (options.yaxis.color == null)
646
+ options.yaxis.color = $.color.parse(options.grid.color).scale('a', 0.22).toString();
647
+
648
+ if (options.xaxis.tickColor == null) // grid.tickColor for back-compatibility
649
+ options.xaxis.tickColor = options.grid.tickColor || options.xaxis.color;
650
+ if (options.yaxis.tickColor == null) // grid.tickColor for back-compatibility
651
+ options.yaxis.tickColor = options.grid.tickColor || options.yaxis.color;
652
+
653
+ if (options.grid.borderColor == null)
654
+ options.grid.borderColor = options.grid.color;
655
+ if (options.grid.tickColor == null)
656
+ options.grid.tickColor = $.color.parse(options.grid.color).scale('a', 0.22).toString();
657
+
658
+ // Fill in defaults for axis options, including any unspecified
659
+ // font-spec fields, if a font-spec was provided.
660
+
661
+ // If no x/y axis options were provided, create one of each anyway,
662
+ // since the rest of the code assumes that they exist.
663
+
664
+ var i, axisOptions, axisCount,
665
+ fontDefaults = {
666
+ style: placeholder.css("font-style"),
667
+ size: Math.round(0.8 * (+placeholder.css("font-size").replace("px", "") || 13)),
668
+ variant: placeholder.css("font-variant"),
669
+ weight: placeholder.css("font-weight"),
670
+ family: placeholder.css("font-family")
671
+ };
672
+
673
+ axisCount = options.xaxes.length || 1;
674
+ for (i = 0; i < axisCount; ++i) {
675
+ axisOptions = $.extend(true, {}, options.xaxis, options.xaxes[i]);
676
+ options.xaxes[i] = axisOptions;
677
+ if (axisOptions.font) {
678
+ axisOptions.font = $.extend({}, fontDefaults, axisOptions.font);
679
+ if (!axisOptions.font.color) {
680
+ axisOptions.font.color = axisOptions.color;
681
+ }
682
+ }
683
+ }
684
+
685
+ axisCount = options.yaxes.length || 1;
686
+ for (i = 0; i < axisCount; ++i) {
687
+ axisOptions = $.extend(true, {}, options.yaxis, options.yaxes[i]);
688
+ options.yaxes[i] = axisOptions;
689
+ if (axisOptions.font) {
690
+ axisOptions.font = $.extend({}, fontDefaults, axisOptions.font);
691
+ if (!axisOptions.font.color) {
692
+ axisOptions.font.color = axisOptions.color;
693
+ }
694
+ }
695
+ }
696
+
697
+ // backwards compatibility, to be removed in future
698
+ if (options.xaxis.noTicks && options.xaxis.ticks == null)
699
+ options.xaxis.ticks = options.xaxis.noTicks;
700
+ if (options.yaxis.noTicks && options.yaxis.ticks == null)
701
+ options.yaxis.ticks = options.yaxis.noTicks;
702
+ if (options.x2axis) {
703
+ options.xaxes[1] = $.extend(true, {}, options.xaxis, options.x2axis);
704
+ options.xaxes[1].position = "top";
705
+ }
706
+ if (options.y2axis) {
707
+ options.yaxes[1] = $.extend(true, {}, options.yaxis, options.y2axis);
708
+ options.yaxes[1].position = "right";
709
+ }
710
+ if (options.grid.coloredAreas)
711
+ options.grid.markings = options.grid.coloredAreas;
712
+ if (options.grid.coloredAreasColor)
713
+ options.grid.markingsColor = options.grid.coloredAreasColor;
714
+ if (options.lines)
715
+ $.extend(true, options.series.lines, options.lines);
716
+ if (options.points)
717
+ $.extend(true, options.series.points, options.points);
718
+ if (options.bars)
719
+ $.extend(true, options.series.bars, options.bars);
720
+ if (options.shadowSize != null)
721
+ options.series.shadowSize = options.shadowSize;
722
+ if (options.highlightColor != null)
723
+ options.series.highlightColor = options.highlightColor;
724
+
725
+ // save options on axes for future reference
726
+ for (i = 0; i < options.xaxes.length; ++i)
727
+ getOrCreateAxis(xaxes, i + 1).options = options.xaxes[i];
728
+ for (i = 0; i < options.yaxes.length; ++i)
729
+ getOrCreateAxis(yaxes, i + 1).options = options.yaxes[i];
730
+
731
+ // add hooks from options
732
+ for (var n in hooks)
733
+ if (options.hooks[n] && options.hooks[n].length)
734
+ hooks[n] = hooks[n].concat(options.hooks[n]);
735
+
736
+ executeHooks(hooks.processOptions, [options]);
737
+ }
738
+
739
+ function setData(d) {
740
+ series = parseData(d);
741
+ fillInSeriesOptions();
742
+ processData();
743
+ }
744
+
745
+ function parseData(d) {
746
+ var res = [];
747
+ for (var i = 0; i < d.length; ++i) {
748
+ var s = $.extend(true, {}, options.series);
749
+
750
+ if (d[i].data != null) {
751
+ s.data = d[i].data; // move the data instead of deep-copy
752
+ delete d[i].data;
753
+
754
+ $.extend(true, s, d[i]);
755
+
756
+ d[i].data = s.data;
757
+ }
758
+ else
759
+ s.data = d[i];
760
+ res.push(s);
761
+ }
762
+
763
+ return res;
764
+ }
765
+
766
+ function axisNumber(obj, coord) {
767
+ var a = obj[coord + "axis"];
768
+ if (typeof a == "object") // if we got a real axis, extract number
769
+ a = a.n;
770
+ if (typeof a != "number")
771
+ a = 1; // default to first axis
772
+ return a;
773
+ }
774
+
775
+ function allAxes() {
776
+ // return flat array without annoying null entries
777
+ return $.grep(xaxes.concat(yaxes), function (a) { return a; });
778
+ }
779
+
780
+ function canvasToAxisCoords(pos) {
781
+ // return an object with x/y corresponding to all used axes
782
+ var res = {}, i, axis;
783
+ for (i = 0; i < xaxes.length; ++i) {
784
+ axis = xaxes[i];
785
+ if (axis && axis.used)
786
+ res["x" + axis.n] = axis.c2p(pos.left);
787
+ }
788
+
789
+ for (i = 0; i < yaxes.length; ++i) {
790
+ axis = yaxes[i];
791
+ if (axis && axis.used)
792
+ res["y" + axis.n] = axis.c2p(pos.top);
793
+ }
794
+
795
+ if (res.x1 !== undefined)
796
+ res.x = res.x1;
797
+ if (res.y1 !== undefined)
798
+ res.y = res.y1;
799
+
800
+ return res;
801
+ }
802
+
803
+ function axisToCanvasCoords(pos) {
804
+ // get canvas coords from the first pair of x/y found in pos
805
+ var res = {}, i, axis, key;
806
+
807
+ for (i = 0; i < xaxes.length; ++i) {
808
+ axis = xaxes[i];
809
+ if (axis && axis.used) {
810
+ key = "x" + axis.n;
811
+ if (pos[key] == null && axis.n == 1)
812
+ key = "x";
813
+
814
+ if (pos[key] != null) {
815
+ res.left = axis.p2c(pos[key]);
816
+ break;
817
+ }
818
+ }
819
+ }
820
+
821
+ for (i = 0; i < yaxes.length; ++i) {
822
+ axis = yaxes[i];
823
+ if (axis && axis.used) {
824
+ key = "y" + axis.n;
825
+ if (pos[key] == null && axis.n == 1)
826
+ key = "y";
827
+
828
+ if (pos[key] != null) {
829
+ res.top = axis.p2c(pos[key]);
830
+ break;
831
+ }
832
+ }
833
+ }
834
+
835
+ return res;
836
+ }
837
+
838
+ function getOrCreateAxis(axes, number) {
839
+ if (!axes[number - 1])
840
+ axes[number - 1] = {
841
+ n: number, // save the number for future reference
842
+ direction: axes == xaxes ? "x" : "y",
843
+ options: $.extend(true, {}, axes == xaxes ? options.xaxis : options.yaxis)
844
+ };
845
+
846
+ return axes[number - 1];
847
+ }
848
+
849
+ function fillInSeriesOptions() {
850
+
851
+ var neededColors = series.length, maxIndex = -1, i;
852
+
853
+ // Subtract the number of series that already have fixed colors or
854
+ // color indexes from the number that we still need to generate.
855
+
856
+ for (i = 0; i < series.length; ++i) {
857
+ var sc = series[i].color;
858
+ if (sc != null) {
859
+ neededColors--;
860
+ if (typeof sc == "number" && sc > maxIndex) {
861
+ maxIndex = sc;
862
+ }
863
+ }
864
+ }
865
+
866
+ // If any of the series have fixed color indexes, then we need to
867
+ // generate at least as many colors as the highest index.
868
+
869
+ if (neededColors <= maxIndex) {
870
+ neededColors = maxIndex + 1;
871
+ }
872
+
873
+ // Generate all the colors, using first the option colors and then
874
+ // variations on those colors once they're exhausted.
875
+
876
+ var c, colors = [], colorPool = options.colors,
877
+ colorPoolSize = colorPool.length, variation = 0;
878
+
879
+ for (i = 0; i < neededColors; i++) {
880
+
881
+ c = $.color.parse(colorPool[i % colorPoolSize] || "#666");
882
+
883
+ // Each time we exhaust the colors in the pool we adjust
884
+ // a scaling factor used to produce more variations on
885
+ // those colors. The factor alternates negative/positive
886
+ // to produce lighter/darker colors.
887
+
888
+ // Reset the variation after every few cycles, or else
889
+ // it will end up producing only white or black colors.
890
+
891
+ if (i % colorPoolSize == 0 && i) {
892
+ if (variation >= 0) {
893
+ if (variation < 0.5) {
894
+ variation = -variation - 0.2;
895
+ } else variation = 0;
896
+ } else variation = -variation;
897
+ }
898
+
899
+ colors[i] = c.scale('rgb', 1 + variation);
900
+ }
901
+
902
+ // Finalize the series options, filling in their colors
903
+
904
+ var colori = 0, s;
905
+ for (i = 0; i < series.length; ++i) {
906
+ s = series[i];
907
+
908
+ // assign colors
909
+ if (s.color == null) {
910
+ s.color = colors[colori].toString();
911
+ ++colori;
912
+ }
913
+ else if (typeof s.color == "number")
914
+ s.color = colors[s.color].toString();
915
+
916
+ // turn on lines automatically in case nothing is set
917
+ if (s.lines.show == null) {
918
+ var v, show = true;
919
+ for (v in s)
920
+ if (s[v] && s[v].show) {
921
+ show = false;
922
+ break;
923
+ }
924
+ if (show)
925
+ s.lines.show = true;
926
+ }
927
+
928
+ // If nothing was provided for lines.zero, default it to match
929
+ // lines.fill, since areas by default should extend to zero.
930
+
931
+ if (s.lines.zero == null) {
932
+ s.lines.zero = !!s.lines.fill;
933
+ }
934
+
935
+ // setup axes
936
+ s.xaxis = getOrCreateAxis(xaxes, axisNumber(s, "x"));
937
+ s.yaxis = getOrCreateAxis(yaxes, axisNumber(s, "y"));
938
+ }
939
+ }
940
+
941
+ function processData() {
942
+ var topSentry = Number.POSITIVE_INFINITY,
943
+ bottomSentry = Number.NEGATIVE_INFINITY,
944
+ fakeInfinity = Number.MAX_VALUE,
945
+ i, j, k, m, length,
946
+ s, points, ps, x, y, axis, val, f, p,
947
+ data, format;
948
+
949
+ function updateAxis(axis, min, max) {
950
+ if (min < axis.datamin && min != -fakeInfinity)
951
+ axis.datamin = min;
952
+ if (max > axis.datamax && max != fakeInfinity)
953
+ axis.datamax = max;
954
+ }
955
+
956
+ $.each(allAxes(), function (_, axis) {
957
+ // init axis
958
+ axis.datamin = topSentry;
959
+ axis.datamax = bottomSentry;
960
+ axis.used = false;
961
+ });
962
+
963
+ for (i = 0; i < series.length; ++i) {
964
+ s = series[i];
965
+ s.datapoints = { points: [] };
966
+
967
+ executeHooks(hooks.processRawData, [ s, s.data, s.datapoints ]);
968
+ }
969
+
970
+ // first pass: clean and copy data
971
+ for (i = 0; i < series.length; ++i) {
972
+ s = series[i];
973
+
974
+ data = s.data;
975
+ format = s.datapoints.format;
976
+
977
+ if (!format) {
978
+ format = [];
979
+ // find out how to copy
980
+ format.push({ x: true, number: true, required: true });
981
+ format.push({ y: true, number: true, required: true });
982
+
983
+ if (s.bars.show || (s.lines.show && s.lines.fill)) {
984
+ var autoscale = !!((s.bars.show && s.bars.zero) || (s.lines.show && s.lines.zero));
985
+ format.push({ y: true, number: true, required: false, defaultValue: 0, autoscale: autoscale });
986
+ if (s.bars.horizontal) {
987
+ delete format[format.length - 1].y;
988
+ format[format.length - 1].x = true;
989
+ }
990
+ }
991
+
992
+ s.datapoints.format = format;
993
+ }
994
+
995
+ if (s.datapoints.pointsize != null)
996
+ continue; // already filled in
997
+
998
+ s.datapoints.pointsize = format.length;
999
+
1000
+ ps = s.datapoints.pointsize;
1001
+ points = s.datapoints.points;
1002
+
1003
+ var insertSteps = s.lines.show && s.lines.steps;
1004
+ s.xaxis.used = s.yaxis.used = true;
1005
+
1006
+ for (j = k = 0; j < data.length; ++j, k += ps) {
1007
+ p = data[j];
1008
+
1009
+ var nullify = p == null;
1010
+ if (!nullify) {
1011
+ for (m = 0; m < ps; ++m) {
1012
+ val = p[m];
1013
+ f = format[m];
1014
+
1015
+ if (f) {
1016
+ if (f.number && val != null) {
1017
+ val = +val; // convert to number
1018
+ if (isNaN(val))
1019
+ val = null;
1020
+ else if (val == Infinity)
1021
+ val = fakeInfinity;
1022
+ else if (val == -Infinity)
1023
+ val = -fakeInfinity;
1024
+ }
1025
+
1026
+ if (val == null) {
1027
+ if (f.required)
1028
+ nullify = true;
1029
+
1030
+ if (f.defaultValue != null)
1031
+ val = f.defaultValue;
1032
+ }
1033
+ }
1034
+
1035
+ points[k + m] = val;
1036
+ }
1037
+ }
1038
+
1039
+ if (nullify) {
1040
+ for (m = 0; m < ps; ++m) {
1041
+ val = points[k + m];
1042
+ if (val != null) {
1043
+ f = format[m];
1044
+ // extract min/max info
1045
+ if (f.x)
1046
+ updateAxis(s.xaxis, val, val);
1047
+ if (f.y)
1048
+ updateAxis(s.yaxis, val, val);
1049
+ }
1050
+ points[k + m] = null;
1051
+ }
1052
+ }
1053
+ else {
1054
+ // a little bit of line specific stuff that
1055
+ // perhaps shouldn't be here, but lacking
1056
+ // better means...
1057
+ if (insertSteps && k > 0
1058
+ && points[k - ps] != null
1059
+ && points[k - ps] != points[k]
1060
+ && points[k - ps + 1] != points[k + 1]) {
1061
+ // copy the point to make room for a middle point
1062
+ for (m = 0; m < ps; ++m)
1063
+ points[k + ps + m] = points[k + m];
1064
+
1065
+ // middle point has same y
1066
+ points[k + 1] = points[k - ps + 1];
1067
+
1068
+ // we've added a point, better reflect that
1069
+ k += ps;
1070
+ }
1071
+ }
1072
+ }
1073
+ }
1074
+
1075
+ // give the hooks a chance to run
1076
+ for (i = 0; i < series.length; ++i) {
1077
+ s = series[i];
1078
+
1079
+ executeHooks(hooks.processDatapoints, [ s, s.datapoints]);
1080
+ }
1081
+
1082
+ // second pass: find datamax/datamin for auto-scaling
1083
+ for (i = 0; i < series.length; ++i) {
1084
+ s = series[i];
1085
+ points = s.datapoints.points,
1086
+ ps = s.datapoints.pointsize;
1087
+ format = s.datapoints.format;
1088
+
1089
+ var xmin = topSentry, ymin = topSentry,
1090
+ xmax = bottomSentry, ymax = bottomSentry;
1091
+
1092
+ for (j = 0; j < points.length; j += ps) {
1093
+ if (points[j] == null)
1094
+ continue;
1095
+
1096
+ for (m = 0; m < ps; ++m) {
1097
+ val = points[j + m];
1098
+ f = format[m];
1099
+ if (!f || f.autoscale === false || val == fakeInfinity || val == -fakeInfinity)
1100
+ continue;
1101
+
1102
+ if (f.x) {
1103
+ if (val < xmin)
1104
+ xmin = val;
1105
+ if (val > xmax)
1106
+ xmax = val;
1107
+ }
1108
+ if (f.y) {
1109
+ if (val < ymin)
1110
+ ymin = val;
1111
+ if (val > ymax)
1112
+ ymax = val;
1113
+ }
1114
+ }
1115
+ }
1116
+
1117
+ if (s.bars.show) {
1118
+ // make sure we got room for the bar on the dancing floor
1119
+ var delta;
1120
+
1121
+ switch (s.bars.align) {
1122
+ case "left":
1123
+ delta = 0;
1124
+ break;
1125
+ case "right":
1126
+ delta = -s.bars.barWidth;
1127
+ break;
1128
+ case "center":
1129
+ delta = -s.bars.barWidth / 2;
1130
+ break;
1131
+ default:
1132
+ throw new Error("Invalid bar alignment: " + s.bars.align);
1133
+ }
1134
+
1135
+ if (s.bars.horizontal) {
1136
+ ymin += delta;
1137
+ ymax += delta + s.bars.barWidth;
1138
+ }
1139
+ else {
1140
+ xmin += delta;
1141
+ xmax += delta + s.bars.barWidth;
1142
+ }
1143
+ }
1144
+
1145
+ updateAxis(s.xaxis, xmin, xmax);
1146
+ updateAxis(s.yaxis, ymin, ymax);
1147
+ }
1148
+
1149
+ $.each(allAxes(), function (_, axis) {
1150
+ if (axis.datamin == topSentry)
1151
+ axis.datamin = null;
1152
+ if (axis.datamax == bottomSentry)
1153
+ axis.datamax = null;
1154
+ });
1155
+ }
1156
+
1157
+ function setupCanvases() {
1158
+
1159
+ // Make sure the placeholder is clear of everything except canvases
1160
+ // from a previous plot in this container that we'll try to re-use.
1161
+
1162
+ placeholder.css("padding", 0) // padding messes up the positioning
1163
+ .children(":not(.flot-base,.flot-overlay)").remove();
1164
+
1165
+ if (placeholder.css("position") == 'static')
1166
+ placeholder.css("position", "relative"); // for positioning labels and overlay
1167
+
1168
+ surface = new Canvas("flot-base", placeholder);
1169
+ overlay = new Canvas("flot-overlay", placeholder); // overlay canvas for interactive features
1170
+
1171
+ ctx = surface.context;
1172
+ octx = overlay.context;
1173
+
1174
+ // define which element we're listening for events on
1175
+ eventHolder = $(overlay.element).unbind();
1176
+
1177
+ // If we're re-using a plot object, shut down the old one
1178
+
1179
+ var existing = placeholder.data("plot");
1180
+
1181
+ if (existing) {
1182
+ existing.shutdown();
1183
+ overlay.clear();
1184
+ }
1185
+
1186
+ // save in case we get replotted
1187
+ placeholder.data("plot", plot);
1188
+ }
1189
+
1190
+ function bindEvents() {
1191
+ // bind events
1192
+ if (options.grid.hoverable) {
1193
+ eventHolder.mousemove(onMouseMove);
1194
+
1195
+ // Use bind, rather than .mouseleave, because we officially
1196
+ // still support jQuery 1.2.6, which doesn't define a shortcut
1197
+ // for mouseenter or mouseleave. This was a bug/oversight that
1198
+ // was fixed somewhere around 1.3.x. We can return to using
1199
+ // .mouseleave when we drop support for 1.2.6.
1200
+
1201
+ eventHolder.bind("mouseleave", onMouseLeave);
1202
+ }
1203
+
1204
+ if (options.grid.clickable)
1205
+ eventHolder.click(onClick);
1206
+
1207
+ executeHooks(hooks.bindEvents, [eventHolder]);
1208
+ }
1209
+
1210
+ function shutdown() {
1211
+ if (redrawTimeout)
1212
+ clearTimeout(redrawTimeout);
1213
+
1214
+ eventHolder.unbind("mousemove", onMouseMove);
1215
+ eventHolder.unbind("mouseleave", onMouseLeave);
1216
+ eventHolder.unbind("click", onClick);
1217
+
1218
+ executeHooks(hooks.shutdown, [eventHolder]);
1219
+ }
1220
+
1221
+ function setTransformationHelpers(axis) {
1222
+ // set helper functions on the axis, assumes plot area
1223
+ // has been computed already
1224
+
1225
+ function identity(x) { return x; }
1226
+
1227
+ var s, m, t = axis.options.transform || identity,
1228
+ it = axis.options.inverseTransform;
1229
+
1230
+ // precompute how much the axis is scaling a point
1231
+ // in canvas space
1232
+ if (axis.direction == "x") {
1233
+ s = axis.scale = plotWidth / Math.abs(t(axis.max) - t(axis.min));
1234
+ m = Math.min(t(axis.max), t(axis.min));
1235
+ }
1236
+ else {
1237
+ s = axis.scale = plotHeight / Math.abs(t(axis.max) - t(axis.min));
1238
+ s = -s;
1239
+ m = Math.max(t(axis.max), t(axis.min));
1240
+ }
1241
+
1242
+ // data point to canvas coordinate
1243
+ if (t == identity) // slight optimization
1244
+ axis.p2c = function (p) { return (p - m) * s; };
1245
+ else
1246
+ axis.p2c = function (p) { return (t(p) - m) * s; };
1247
+ // canvas coordinate to data point
1248
+ if (!it)
1249
+ axis.c2p = function (c) { return m + c / s; };
1250
+ else
1251
+ axis.c2p = function (c) { return it(m + c / s); };
1252
+ }
1253
+
1254
+ function measureTickLabels(axis) {
1255
+
1256
+ var opts = axis.options, ticks = axis.ticks || [],
1257
+ axisw = opts.labelWidth || 0, axish = opts.labelHeight || 0,
1258
+ legacyStyles = axis.direction + "Axis " + axis.direction + axis.n + "Axis",
1259
+ layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + legacyStyles,
1260
+ font = opts.font || "flot-tick-label tickLabel";
1261
+
1262
+ for (var i = 0; i < ticks.length; ++i) {
1263
+
1264
+ var t = ticks[i];
1265
+
1266
+ if (!t.label)
1267
+ continue;
1268
+
1269
+ var info = surface.getTextInfo(layer, t.label, font);
1270
+
1271
+ if (opts.labelWidth == null)
1272
+ axisw = Math.max(axisw, info.width);
1273
+ if (opts.labelHeight == null)
1274
+ axish = Math.max(axish, info.height);
1275
+ }
1276
+
1277
+ axis.labelWidth = Math.ceil(axisw);
1278
+ axis.labelHeight = Math.ceil(axish);
1279
+ }
1280
+
1281
+ function allocateAxisBoxFirstPhase(axis) {
1282
+ // find the bounding box of the axis by looking at label
1283
+ // widths/heights and ticks, make room by diminishing the
1284
+ // plotOffset; this first phase only looks at one
1285
+ // dimension per axis, the other dimension depends on the
1286
+ // other axes so will have to wait
1287
+
1288
+ var lw = axis.labelWidth,
1289
+ lh = axis.labelHeight,
1290
+ pos = axis.options.position,
1291
+ tickLength = axis.options.tickLength,
1292
+ axisMargin = options.grid.axisMargin,
1293
+ padding = options.grid.labelMargin,
1294
+ all = axis.direction == "x" ? xaxes : yaxes,
1295
+ index, innermost;
1296
+
1297
+ // determine axis margin
1298
+ var samePosition = $.grep(all, function (a) {
1299
+ return a && a.options.position == pos && a.reserveSpace;
1300
+ });
1301
+ if ($.inArray(axis, samePosition) == samePosition.length - 1)
1302
+ axisMargin = 0; // outermost
1303
+
1304
+ // determine tick length - if we're innermost, we can use "full"
1305
+ if (tickLength == null) {
1306
+ var sameDirection = $.grep(all, function (a) {
1307
+ return a && a.reserveSpace;
1308
+ });
1309
+
1310
+ innermost = $.inArray(axis, sameDirection) == 0;
1311
+ if (innermost)
1312
+ tickLength = "full";
1313
+ else
1314
+ tickLength = 5;
1315
+ }
1316
+
1317
+ if (!isNaN(+tickLength))
1318
+ padding += +tickLength;
1319
+
1320
+ // compute box
1321
+ if (axis.direction == "x") {
1322
+ lh += padding;
1323
+
1324
+ if (pos == "bottom") {
1325
+ plotOffset.bottom += lh + axisMargin;
1326
+ axis.box = { top: surface.height - plotOffset.bottom, height: lh };
1327
+ }
1328
+ else {
1329
+ axis.box = { top: plotOffset.top + axisMargin, height: lh };
1330
+ plotOffset.top += lh + axisMargin;
1331
+ }
1332
+ }
1333
+ else {
1334
+ lw += padding;
1335
+
1336
+ if (pos == "left") {
1337
+ axis.box = { left: plotOffset.left + axisMargin, width: lw };
1338
+ plotOffset.left += lw + axisMargin;
1339
+ }
1340
+ else {
1341
+ plotOffset.right += lw + axisMargin;
1342
+ axis.box = { left: surface.width - plotOffset.right, width: lw };
1343
+ }
1344
+ }
1345
+
1346
+ // save for future reference
1347
+ axis.position = pos;
1348
+ axis.tickLength = tickLength;
1349
+ axis.box.padding = padding;
1350
+ axis.innermost = innermost;
1351
+ }
1352
+
1353
+ function allocateAxisBoxSecondPhase(axis) {
1354
+ // now that all axis boxes have been placed in one
1355
+ // dimension, we can set the remaining dimension coordinates
1356
+ if (axis.direction == "x") {
1357
+ axis.box.left = plotOffset.left - axis.labelWidth / 2;
1358
+ axis.box.width = surface.width - plotOffset.left - plotOffset.right + axis.labelWidth;
1359
+ }
1360
+ else {
1361
+ axis.box.top = plotOffset.top - axis.labelHeight / 2;
1362
+ axis.box.height = surface.height - plotOffset.bottom - plotOffset.top + axis.labelHeight;
1363
+ }
1364
+ }
1365
+
1366
+ function adjustLayoutForThingsStickingOut() {
1367
+ // possibly adjust plot offset to ensure everything stays
1368
+ // inside the canvas and isn't clipped off
1369
+
1370
+ var minMargin = options.grid.minBorderMargin,
1371
+ margins = { x: 0, y: 0 }, i, axis;
1372
+
1373
+ // check stuff from the plot (FIXME: this should just read
1374
+ // a value from the series, otherwise it's impossible to
1375
+ // customize)
1376
+ if (minMargin == null) {
1377
+ minMargin = 0;
1378
+ for (i = 0; i < series.length; ++i)
1379
+ minMargin = Math.max(minMargin, 2 * (series[i].points.radius + series[i].points.lineWidth/2));
1380
+ }
1381
+
1382
+ margins.x = margins.y = Math.ceil(minMargin);
1383
+
1384
+ // check axis labels, note we don't check the actual
1385
+ // labels but instead use the overall width/height to not
1386
+ // jump as much around with replots
1387
+ $.each(allAxes(), function (_, axis) {
1388
+ var dir = axis.direction;
1389
+ if (axis.reserveSpace)
1390
+ margins[dir] = Math.ceil(Math.max(margins[dir], (dir == "x" ? axis.labelWidth : axis.labelHeight) / 2));
1391
+ });
1392
+
1393
+ plotOffset.left = Math.max(margins.x, plotOffset.left);
1394
+ plotOffset.right = Math.max(margins.x, plotOffset.right);
1395
+ plotOffset.top = Math.max(margins.y, plotOffset.top);
1396
+ plotOffset.bottom = Math.max(margins.y, plotOffset.bottom);
1397
+ }
1398
+
1399
+ function setupGrid() {
1400
+ var i, axes = allAxes(), showGrid = options.grid.show;
1401
+
1402
+ // Initialize the plot's offset from the edge of the canvas
1403
+
1404
+ for (var a in plotOffset) {
1405
+ var margin = options.grid.margin || 0;
1406
+ plotOffset[a] = typeof margin == "number" ? margin : margin[a] || 0;
1407
+ }
1408
+
1409
+ executeHooks(hooks.processOffset, [plotOffset]);
1410
+
1411
+ // If the grid is visible, add its border width to the offset
1412
+
1413
+ for (var a in plotOffset) {
1414
+ if(typeof(options.grid.borderWidth) == "object") {
1415
+ plotOffset[a] += showGrid ? options.grid.borderWidth[a] : 0;
1416
+ }
1417
+ else {
1418
+ plotOffset[a] += showGrid ? options.grid.borderWidth : 0;
1419
+ }
1420
+ }
1421
+
1422
+ // init axes
1423
+ $.each(axes, function (_, axis) {
1424
+ axis.show = axis.options.show;
1425
+ if (axis.show == null)
1426
+ axis.show = axis.used; // by default an axis is visible if it's got data
1427
+
1428
+ axis.reserveSpace = axis.show || axis.options.reserveSpace;
1429
+
1430
+ setRange(axis);
1431
+ });
1432
+
1433
+ if (showGrid) {
1434
+
1435
+ var allocatedAxes = $.grep(axes, function (axis) { return axis.reserveSpace; });
1436
+
1437
+ $.each(allocatedAxes, function (_, axis) {
1438
+ // make the ticks
1439
+ setupTickGeneration(axis);
1440
+ setTicks(axis);
1441
+ snapRangeToTicks(axis, axis.ticks);
1442
+ // find labelWidth/Height for axis
1443
+ measureTickLabels(axis);
1444
+ });
1445
+
1446
+ // with all dimensions calculated, we can compute the
1447
+ // axis bounding boxes, start from the outside
1448
+ // (reverse order)
1449
+ for (i = allocatedAxes.length - 1; i >= 0; --i)
1450
+ allocateAxisBoxFirstPhase(allocatedAxes[i]);
1451
+
1452
+ // make sure we've got enough space for things that
1453
+ // might stick out
1454
+ adjustLayoutForThingsStickingOut();
1455
+
1456
+ $.each(allocatedAxes, function (_, axis) {
1457
+ allocateAxisBoxSecondPhase(axis);
1458
+ });
1459
+ }
1460
+
1461
+ plotWidth = surface.width - plotOffset.left - plotOffset.right;
1462
+ plotHeight = surface.height - plotOffset.bottom - plotOffset.top;
1463
+
1464
+ // now we got the proper plot dimensions, we can compute the scaling
1465
+ $.each(axes, function (_, axis) {
1466
+ setTransformationHelpers(axis);
1467
+ });
1468
+
1469
+ if (showGrid) {
1470
+ drawAxisLabels();
1471
+ }
1472
+
1473
+ insertLegend();
1474
+ }
1475
+
1476
+ function setRange(axis) {
1477
+ var opts = axis.options,
1478
+ min = +(opts.min != null ? opts.min : axis.datamin),
1479
+ max = +(opts.max != null ? opts.max : axis.datamax),
1480
+ delta = max - min;
1481
+
1482
+ if (delta == 0.0) {
1483
+ // degenerate case
1484
+ var widen = max == 0 ? 1 : 0.01;
1485
+
1486
+ if (opts.min == null)
1487
+ min -= widen;
1488
+ // always widen max if we couldn't widen min to ensure we
1489
+ // don't fall into min == max which doesn't work
1490
+ if (opts.max == null || opts.min != null)
1491
+ max += widen;
1492
+ }
1493
+ else {
1494
+ // consider autoscaling
1495
+ var margin = opts.autoscaleMargin;
1496
+ if (margin != null) {
1497
+ if (opts.min == null) {
1498
+ min -= delta * margin;
1499
+ // make sure we don't go below zero if all values
1500
+ // are positive
1501
+ if (min < 0 && axis.datamin != null && axis.datamin >= 0)
1502
+ min = 0;
1503
+ }
1504
+ if (opts.max == null) {
1505
+ max += delta * margin;
1506
+ if (max > 0 && axis.datamax != null && axis.datamax <= 0)
1507
+ max = 0;
1508
+ }
1509
+ }
1510
+ }
1511
+ axis.min = min;
1512
+ axis.max = max;
1513
+ }
1514
+
1515
+ function setupTickGeneration(axis) {
1516
+ var opts = axis.options;
1517
+
1518
+ // estimate number of ticks
1519
+ var noTicks;
1520
+ if (typeof opts.ticks == "number" && opts.ticks > 0)
1521
+ noTicks = opts.ticks;
1522
+ else
1523
+ // heuristic based on the model a*sqrt(x) fitted to
1524
+ // some data points that seemed reasonable
1525
+ noTicks = 0.3 * Math.sqrt(axis.direction == "x" ? surface.width : surface.height);
1526
+
1527
+ axis.delta = (axis.max - axis.min) / noTicks;
1528
+
1529
+ // Time mode was moved to a plug-in in 0.8, but since so many people use this
1530
+ // we'll add an especially friendly make sure they remembered to include it.
1531
+
1532
+ if (opts.mode == "time" && !axis.tickGenerator) {
1533
+ throw new Error("Time mode requires the flot.time plugin.");
1534
+ }
1535
+
1536
+ // Flot supports base-10 axes; any other mode else is handled by a plug-in,
1537
+ // like flot.time.js.
1538
+
1539
+ if (!axis.tickGenerator) {
1540
+
1541
+ axis.tickGenerator = function (axis) {
1542
+ var maxDec = opts.tickDecimals,
1543
+ dec = -Math.floor(Math.log(axis.delta) / Math.LN10);
1544
+
1545
+ if (maxDec != null && dec > maxDec)
1546
+ dec = maxDec;
1547
+
1548
+ var magn = Math.pow(10, -dec),
1549
+ norm = axis.delta / magn, // norm is between 1.0 and 10.0
1550
+ size,
1551
+
1552
+ ticks = [],
1553
+ start,
1554
+ i = 0,
1555
+ v = Number.NaN,
1556
+ prev;
1557
+
1558
+ if (norm < 1.5)
1559
+ size = 1;
1560
+ else if (norm < 3) {
1561
+ size = 2;
1562
+ // special case for 2.5, requires an extra decimal
1563
+ if (norm > 2.25 && (maxDec == null || dec + 1 <= maxDec)) {
1564
+ size = 2.5;
1565
+ ++dec;
1566
+ }
1567
+ }
1568
+ else if (norm < 7.5)
1569
+ size = 5;
1570
+ else size = 10;
1571
+
1572
+ size *= magn;
1573
+
1574
+ if (opts.minTickSize != null && size < opts.minTickSize)
1575
+ size = opts.minTickSize;
1576
+
1577
+ axis.tickDecimals = Math.max(0, maxDec != null ? maxDec : dec);
1578
+ axis.tickSize = opts.tickSize || size;
1579
+
1580
+ start = floorInBase(axis.min, axis.tickSize);
1581
+
1582
+ do {
1583
+ prev = v;
1584
+ v = start + i * axis.tickSize;
1585
+ ticks.push(v);
1586
+ ++i;
1587
+ } while (v < axis.max && v != prev);
1588
+ return ticks;
1589
+ };
1590
+
1591
+ axis.tickFormatter = function (value, axis) {
1592
+
1593
+ var factor = axis.tickDecimals ? Math.pow(10, axis.tickDecimals) : 1;
1594
+ var formatted = "" + Math.round(value * factor) / factor;
1595
+
1596
+ // If tickDecimals was specified, ensure that we have exactly that
1597
+ // much precision; otherwise default to the value's own precision.
1598
+
1599
+ if (axis.tickDecimals != null) {
1600
+ var decimal = formatted.indexOf(".");
1601
+ var precision = decimal == -1 ? 0 : formatted.length - decimal - 1;
1602
+ if (precision < axis.tickDecimals) {
1603
+ return (precision ? formatted : formatted + ".") + ("" + factor).substr(1, axis.tickDecimals - precision);
1604
+ }
1605
+ }
1606
+
1607
+ return formatted;
1608
+ };
1609
+ }
1610
+
1611
+ if ($.isFunction(opts.tickFormatter))
1612
+ axis.tickFormatter = function (v, axis) { return "" + opts.tickFormatter(v, axis); };
1613
+
1614
+ if (opts.alignTicksWithAxis != null) {
1615
+ var otherAxis = (axis.direction == "x" ? xaxes : yaxes)[opts.alignTicksWithAxis - 1];
1616
+ if (otherAxis && otherAxis.used && otherAxis != axis) {
1617
+ // consider snapping min/max to outermost nice ticks
1618
+ var niceTicks = axis.tickGenerator(axis);
1619
+ if (niceTicks.length > 0) {
1620
+ if (opts.min == null)
1621
+ axis.min = Math.min(axis.min, niceTicks[0]);
1622
+ if (opts.max == null && niceTicks.length > 1)
1623
+ axis.max = Math.max(axis.max, niceTicks[niceTicks.length - 1]);
1624
+ }
1625
+
1626
+ axis.tickGenerator = function (axis) {
1627
+ // copy ticks, scaled to this axis
1628
+ var ticks = [], v, i;
1629
+ for (i = 0; i < otherAxis.ticks.length; ++i) {
1630
+ v = (otherAxis.ticks[i].v - otherAxis.min) / (otherAxis.max - otherAxis.min);
1631
+ v = axis.min + v * (axis.max - axis.min);
1632
+ ticks.push(v);
1633
+ }
1634
+ return ticks;
1635
+ };
1636
+
1637
+ // we might need an extra decimal since forced
1638
+ // ticks don't necessarily fit naturally
1639
+ if (!axis.mode && opts.tickDecimals == null) {
1640
+ var extraDec = Math.max(0, -Math.floor(Math.log(axis.delta) / Math.LN10) + 1),
1641
+ ts = axis.tickGenerator(axis);
1642
+
1643
+ // only proceed if the tick interval rounded
1644
+ // with an extra decimal doesn't give us a
1645
+ // zero at end
1646
+ if (!(ts.length > 1 && /\..*0$/.test((ts[1] - ts[0]).toFixed(extraDec))))
1647
+ axis.tickDecimals = extraDec;
1648
+ }
1649
+ }
1650
+ }
1651
+ }
1652
+
1653
+ function setTicks(axis) {
1654
+ var oticks = axis.options.ticks, ticks = [];
1655
+ if (oticks == null || (typeof oticks == "number" && oticks > 0))
1656
+ ticks = axis.tickGenerator(axis);
1657
+ else if (oticks) {
1658
+ if ($.isFunction(oticks))
1659
+ // generate the ticks
1660
+ ticks = oticks(axis);
1661
+ else
1662
+ ticks = oticks;
1663
+ }
1664
+
1665
+ // clean up/labelify the supplied ticks, copy them over
1666
+ var i, v;
1667
+ axis.ticks = [];
1668
+ for (i = 0; i < ticks.length; ++i) {
1669
+ var label = null;
1670
+ var t = ticks[i];
1671
+ if (typeof t == "object") {
1672
+ v = +t[0];
1673
+ if (t.length > 1)
1674
+ label = t[1];
1675
+ }
1676
+ else
1677
+ v = +t;
1678
+ if (label == null)
1679
+ label = axis.tickFormatter(v, axis);
1680
+ if (!isNaN(v))
1681
+ axis.ticks.push({ v: v, label: label });
1682
+ }
1683
+ }
1684
+
1685
+ function snapRangeToTicks(axis, ticks) {
1686
+ if (axis.options.autoscaleMargin && ticks.length > 0) {
1687
+ // snap to ticks
1688
+ if (axis.options.min == null)
1689
+ axis.min = Math.min(axis.min, ticks[0].v);
1690
+ if (axis.options.max == null && ticks.length > 1)
1691
+ axis.max = Math.max(axis.max, ticks[ticks.length - 1].v);
1692
+ }
1693
+ }
1694
+
1695
+ function draw() {
1696
+
1697
+ surface.clear();
1698
+
1699
+ executeHooks(hooks.drawBackground, [ctx]);
1700
+
1701
+ var grid = options.grid;
1702
+
1703
+ // draw background, if any
1704
+ if (grid.show && grid.backgroundColor)
1705
+ drawBackground();
1706
+
1707
+ if (grid.show && !grid.aboveData) {
1708
+ drawGrid();
1709
+ }
1710
+
1711
+ for (var i = 0; i < series.length; ++i) {
1712
+ executeHooks(hooks.drawSeries, [ctx, series[i]]);
1713
+ drawSeries(series[i]);
1714
+ }
1715
+
1716
+ executeHooks(hooks.draw, [ctx]);
1717
+
1718
+ if (grid.show && grid.aboveData) {
1719
+ drawGrid();
1720
+ }
1721
+
1722
+ surface.render();
1723
+ }
1724
+
1725
+ function extractRange(ranges, coord) {
1726
+ var axis, from, to, key, axes = allAxes();
1727
+
1728
+ for (var i = 0; i < axes.length; ++i) {
1729
+ axis = axes[i];
1730
+ if (axis.direction == coord) {
1731
+ key = coord + axis.n + "axis";
1732
+ if (!ranges[key] && axis.n == 1)
1733
+ key = coord + "axis"; // support x1axis as xaxis
1734
+ if (ranges[key]) {
1735
+ from = ranges[key].from;
1736
+ to = ranges[key].to;
1737
+ break;
1738
+ }
1739
+ }
1740
+ }
1741
+
1742
+ // backwards-compat stuff - to be removed in future
1743
+ if (!ranges[key]) {
1744
+ axis = coord == "x" ? xaxes[0] : yaxes[0];
1745
+ from = ranges[coord + "1"];
1746
+ to = ranges[coord + "2"];
1747
+ }
1748
+
1749
+ // auto-reverse as an added bonus
1750
+ if (from != null && to != null && from > to) {
1751
+ var tmp = from;
1752
+ from = to;
1753
+ to = tmp;
1754
+ }
1755
+
1756
+ return { from: from, to: to, axis: axis };
1757
+ }
1758
+
1759
+ function drawBackground() {
1760
+ ctx.save();
1761
+ ctx.translate(plotOffset.left, plotOffset.top);
1762
+
1763
+ ctx.fillStyle = getColorOrGradient(options.grid.backgroundColor, plotHeight, 0, "rgba(255, 255, 255, 0)");
1764
+ ctx.fillRect(0, 0, plotWidth, plotHeight);
1765
+ ctx.restore();
1766
+ }
1767
+
1768
+ function drawGrid() {
1769
+ var i, axes, bw, bc;
1770
+
1771
+ ctx.save();
1772
+ ctx.translate(plotOffset.left, plotOffset.top);
1773
+
1774
+ // draw markings
1775
+ var markings = options.grid.markings;
1776
+ if (markings) {
1777
+ if ($.isFunction(markings)) {
1778
+ axes = plot.getAxes();
1779
+ // xmin etc. is backwards compatibility, to be
1780
+ // removed in the future
1781
+ axes.xmin = axes.xaxis.min;
1782
+ axes.xmax = axes.xaxis.max;
1783
+ axes.ymin = axes.yaxis.min;
1784
+ axes.ymax = axes.yaxis.max;
1785
+
1786
+ markings = markings(axes);
1787
+ }
1788
+
1789
+ for (i = 0; i < markings.length; ++i) {
1790
+ var m = markings[i],
1791
+ xrange = extractRange(m, "x"),
1792
+ yrange = extractRange(m, "y");
1793
+
1794
+ // fill in missing
1795
+ if (xrange.from == null)
1796
+ xrange.from = xrange.axis.min;
1797
+ if (xrange.to == null)
1798
+ xrange.to = xrange.axis.max;
1799
+ if (yrange.from == null)
1800
+ yrange.from = yrange.axis.min;
1801
+ if (yrange.to == null)
1802
+ yrange.to = yrange.axis.max;
1803
+
1804
+ // clip
1805
+ if (xrange.to < xrange.axis.min || xrange.from > xrange.axis.max ||
1806
+ yrange.to < yrange.axis.min || yrange.from > yrange.axis.max)
1807
+ continue;
1808
+
1809
+ xrange.from = Math.max(xrange.from, xrange.axis.min);
1810
+ xrange.to = Math.min(xrange.to, xrange.axis.max);
1811
+ yrange.from = Math.max(yrange.from, yrange.axis.min);
1812
+ yrange.to = Math.min(yrange.to, yrange.axis.max);
1813
+
1814
+ if (xrange.from == xrange.to && yrange.from == yrange.to)
1815
+ continue;
1816
+
1817
+ // then draw
1818
+ xrange.from = xrange.axis.p2c(xrange.from);
1819
+ xrange.to = xrange.axis.p2c(xrange.to);
1820
+ yrange.from = yrange.axis.p2c(yrange.from);
1821
+ yrange.to = yrange.axis.p2c(yrange.to);
1822
+
1823
+ if (xrange.from == xrange.to || yrange.from == yrange.to) {
1824
+ // draw line
1825
+ ctx.beginPath();
1826
+ ctx.strokeStyle = m.color || options.grid.markingsColor;
1827
+ ctx.lineWidth = m.lineWidth || options.grid.markingsLineWidth;
1828
+ ctx.moveTo(xrange.from, yrange.from);
1829
+ ctx.lineTo(xrange.to, yrange.to);
1830
+ ctx.stroke();
1831
+ }
1832
+ else {
1833
+ // fill area
1834
+ ctx.fillStyle = m.color || options.grid.markingsColor;
1835
+ ctx.fillRect(xrange.from, yrange.to,
1836
+ xrange.to - xrange.from,
1837
+ yrange.from - yrange.to);
1838
+ }
1839
+ }
1840
+ }
1841
+
1842
+ // draw the ticks
1843
+ axes = allAxes();
1844
+ bw = options.grid.borderWidth;
1845
+
1846
+ for (var j = 0; j < axes.length; ++j) {
1847
+ var axis = axes[j], box = axis.box,
1848
+ t = axis.tickLength, x, y, xoff, yoff;
1849
+ if (!axis.show || axis.ticks.length == 0)
1850
+ continue;
1851
+
1852
+ ctx.lineWidth = 1;
1853
+
1854
+ // find the edges
1855
+ if (axis.direction == "x") {
1856
+ x = 0;
1857
+ if (t == "full")
1858
+ y = (axis.position == "top" ? 0 : plotHeight);
1859
+ else
1860
+ y = box.top - plotOffset.top + (axis.position == "top" ? box.height : 0);
1861
+ }
1862
+ else {
1863
+ y = 0;
1864
+ if (t == "full")
1865
+ x = (axis.position == "left" ? 0 : plotWidth);
1866
+ else
1867
+ x = box.left - plotOffset.left + (axis.position == "left" ? box.width : 0);
1868
+ }
1869
+
1870
+ // draw tick bar
1871
+ if (!axis.innermost) {
1872
+ ctx.strokeStyle = axis.options.color;
1873
+ ctx.beginPath();
1874
+ xoff = yoff = 0;
1875
+ if (axis.direction == "x")
1876
+ xoff = plotWidth + 1;
1877
+ else
1878
+ yoff = plotHeight + 1;
1879
+
1880
+ if (ctx.lineWidth == 1) {
1881
+ if (axis.direction == "x") {
1882
+ y = Math.floor(y) + 0.5;
1883
+ } else {
1884
+ x = Math.floor(x) + 0.5;
1885
+ }
1886
+ }
1887
+
1888
+ ctx.moveTo(x, y);
1889
+ ctx.lineTo(x + xoff, y + yoff);
1890
+ ctx.stroke();
1891
+ }
1892
+
1893
+ // draw ticks
1894
+
1895
+ ctx.strokeStyle = axis.options.tickColor;
1896
+
1897
+ ctx.beginPath();
1898
+ for (i = 0; i < axis.ticks.length; ++i) {
1899
+ var v = axis.ticks[i].v;
1900
+
1901
+ xoff = yoff = 0;
1902
+
1903
+ if (isNaN(v) || v < axis.min || v > axis.max
1904
+ // skip those lying on the axes if we got a border
1905
+ || (t == "full"
1906
+ && ((typeof bw == "object" && bw[axis.position] > 0) || bw > 0)
1907
+ && (v == axis.min || v == axis.max)))
1908
+ continue;
1909
+
1910
+ if (axis.direction == "x") {
1911
+ x = axis.p2c(v);
1912
+ yoff = t == "full" ? -plotHeight : t;
1913
+
1914
+ if (axis.position == "top")
1915
+ yoff = -yoff;
1916
+ }
1917
+ else {
1918
+ y = axis.p2c(v);
1919
+ xoff = t == "full" ? -plotWidth : t;
1920
+
1921
+ if (axis.position == "left")
1922
+ xoff = -xoff;
1923
+ }
1924
+
1925
+ if (ctx.lineWidth == 1) {
1926
+ if (axis.direction == "x")
1927
+ x = Math.floor(x) + 0.5;
1928
+ else
1929
+ y = Math.floor(y) + 0.5;
1930
+ }
1931
+
1932
+ ctx.moveTo(x, y);
1933
+ ctx.lineTo(x + xoff, y + yoff);
1934
+ }
1935
+
1936
+ ctx.stroke();
1937
+ }
1938
+
1939
+
1940
+ // draw border
1941
+ if (bw) {
1942
+ // If either borderWidth or borderColor is an object, then draw the border
1943
+ // line by line instead of as one rectangle
1944
+ bc = options.grid.borderColor;
1945
+ if(typeof bw == "object" || typeof bc == "object") {
1946
+ if (typeof bw !== "object") {
1947
+ bw = {top: bw, right: bw, bottom: bw, left: bw};
1948
+ }
1949
+ if (typeof bc !== "object") {
1950
+ bc = {top: bc, right: bc, bottom: bc, left: bc};
1951
+ }
1952
+
1953
+ if (bw.top > 0) {
1954
+ ctx.strokeStyle = bc.top;
1955
+ ctx.lineWidth = bw.top;
1956
+ ctx.beginPath();
1957
+ ctx.moveTo(0 - bw.left, 0 - bw.top/2);
1958
+ ctx.lineTo(plotWidth, 0 - bw.top/2);
1959
+ ctx.stroke();
1960
+ }
1961
+
1962
+ if (bw.right > 0) {
1963
+ ctx.strokeStyle = bc.right;
1964
+ ctx.lineWidth = bw.right;
1965
+ ctx.beginPath();
1966
+ ctx.moveTo(plotWidth + bw.right / 2, 0 - bw.top);
1967
+ ctx.lineTo(plotWidth + bw.right / 2, plotHeight);
1968
+ ctx.stroke();
1969
+ }
1970
+
1971
+ if (bw.bottom > 0) {
1972
+ ctx.strokeStyle = bc.bottom;
1973
+ ctx.lineWidth = bw.bottom;
1974
+ ctx.beginPath();
1975
+ ctx.moveTo(plotWidth + bw.right, plotHeight + bw.bottom / 2);
1976
+ ctx.lineTo(0, plotHeight + bw.bottom / 2);
1977
+ ctx.stroke();
1978
+ }
1979
+
1980
+ if (bw.left > 0) {
1981
+ ctx.strokeStyle = bc.left;
1982
+ ctx.lineWidth = bw.left;
1983
+ ctx.beginPath();
1984
+ ctx.moveTo(0 - bw.left/2, plotHeight + bw.bottom);
1985
+ ctx.lineTo(0- bw.left/2, 0);
1986
+ ctx.stroke();
1987
+ }
1988
+ }
1989
+ else {
1990
+ ctx.lineWidth = bw;
1991
+ ctx.strokeStyle = options.grid.borderColor;
1992
+ ctx.strokeRect(-bw/2, -bw/2, plotWidth + bw, plotHeight + bw);
1993
+ }
1994
+ }
1995
+
1996
+ ctx.restore();
1997
+ }
1998
+
1999
+ function drawAxisLabels() {
2000
+
2001
+ $.each(allAxes(), function (_, axis) {
2002
+ if (!axis.show || axis.ticks.length == 0)
2003
+ return;
2004
+
2005
+ var box = axis.box,
2006
+ legacyStyles = axis.direction + "Axis " + axis.direction + axis.n + "Axis",
2007
+ layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + legacyStyles,
2008
+ font = axis.options.font || "flot-tick-label tickLabel",
2009
+ tick, x, y, halign, valign;
2010
+
2011
+ surface.removeText(layer);
2012
+
2013
+ for (var i = 0; i < axis.ticks.length; ++i) {
2014
+
2015
+ tick = axis.ticks[i];
2016
+ if (!tick.label || tick.v < axis.min || tick.v > axis.max)
2017
+ continue;
2018
+
2019
+ if (axis.direction == "x") {
2020
+ halign = "center";
2021
+ x = plotOffset.left + axis.p2c(tick.v);
2022
+ if (axis.position == "bottom") {
2023
+ y = box.top + box.padding;
2024
+ } else {
2025
+ y = box.top + box.height - box.padding;
2026
+ valign = "bottom";
2027
+ }
2028
+ } else {
2029
+ valign = "middle";
2030
+ y = plotOffset.top + axis.p2c(tick.v);
2031
+ if (axis.position == "left") {
2032
+ x = box.left + box.width - box.padding;
2033
+ halign = "right";
2034
+ } else {
2035
+ x = box.left + box.padding;
2036
+ }
2037
+ }
2038
+
2039
+ surface.addText(layer, x, y, tick.label, font, null, halign, valign);
2040
+ }
2041
+ });
2042
+ }
2043
+
2044
+ function drawSeries(series) {
2045
+ if (series.lines.show)
2046
+ drawSeriesLines(series);
2047
+ if (series.bars.show)
2048
+ drawSeriesBars(series);
2049
+ if (series.points.show)
2050
+ drawSeriesPoints(series);
2051
+ }
2052
+
2053
+ function drawSeriesLines(series) {
2054
+ function plotLine(datapoints, xoffset, yoffset, axisx, axisy) {
2055
+ var points = datapoints.points,
2056
+ ps = datapoints.pointsize,
2057
+ prevx = null, prevy = null;
2058
+
2059
+ ctx.beginPath();
2060
+ for (var i = ps; i < points.length; i += ps) {
2061
+ var x1 = points[i - ps], y1 = points[i - ps + 1],
2062
+ x2 = points[i], y2 = points[i + 1];
2063
+
2064
+ if (x1 == null || x2 == null)
2065
+ continue;
2066
+
2067
+ // clip with ymin
2068
+ if (y1 <= y2 && y1 < axisy.min) {
2069
+ if (y2 < axisy.min)
2070
+ continue; // line segment is outside
2071
+ // compute new intersection point
2072
+ x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
2073
+ y1 = axisy.min;
2074
+ }
2075
+ else if (y2 <= y1 && y2 < axisy.min) {
2076
+ if (y1 < axisy.min)
2077
+ continue;
2078
+ x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
2079
+ y2 = axisy.min;
2080
+ }
2081
+
2082
+ // clip with ymax
2083
+ if (y1 >= y2 && y1 > axisy.max) {
2084
+ if (y2 > axisy.max)
2085
+ continue;
2086
+ x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
2087
+ y1 = axisy.max;
2088
+ }
2089
+ else if (y2 >= y1 && y2 > axisy.max) {
2090
+ if (y1 > axisy.max)
2091
+ continue;
2092
+ x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
2093
+ y2 = axisy.max;
2094
+ }
2095
+
2096
+ // clip with xmin
2097
+ if (x1 <= x2 && x1 < axisx.min) {
2098
+ if (x2 < axisx.min)
2099
+ continue;
2100
+ y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
2101
+ x1 = axisx.min;
2102
+ }
2103
+ else if (x2 <= x1 && x2 < axisx.min) {
2104
+ if (x1 < axisx.min)
2105
+ continue;
2106
+ y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
2107
+ x2 = axisx.min;
2108
+ }
2109
+
2110
+ // clip with xmax
2111
+ if (x1 >= x2 && x1 > axisx.max) {
2112
+ if (x2 > axisx.max)
2113
+ continue;
2114
+ y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
2115
+ x1 = axisx.max;
2116
+ }
2117
+ else if (x2 >= x1 && x2 > axisx.max) {
2118
+ if (x1 > axisx.max)
2119
+ continue;
2120
+ y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
2121
+ x2 = axisx.max;
2122
+ }
2123
+
2124
+ if (x1 != prevx || y1 != prevy)
2125
+ ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset);
2126
+
2127
+ prevx = x2;
2128
+ prevy = y2;
2129
+ ctx.lineTo(axisx.p2c(x2) + xoffset, axisy.p2c(y2) + yoffset);
2130
+ }
2131
+ ctx.stroke();
2132
+ }
2133
+
2134
+ function plotLineArea(datapoints, axisx, axisy) {
2135
+ var points = datapoints.points,
2136
+ ps = datapoints.pointsize,
2137
+ bottom = Math.min(Math.max(0, axisy.min), axisy.max),
2138
+ i = 0, top, areaOpen = false,
2139
+ ypos = 1, segmentStart = 0, segmentEnd = 0;
2140
+
2141
+ // we process each segment in two turns, first forward
2142
+ // direction to sketch out top, then once we hit the
2143
+ // end we go backwards to sketch the bottom
2144
+ while (true) {
2145
+ if (ps > 0 && i > points.length + ps)
2146
+ break;
2147
+
2148
+ i += ps; // ps is negative if going backwards
2149
+
2150
+ var x1 = points[i - ps],
2151
+ y1 = points[i - ps + ypos],
2152
+ x2 = points[i], y2 = points[i + ypos];
2153
+
2154
+ if (areaOpen) {
2155
+ if (ps > 0 && x1 != null && x2 == null) {
2156
+ // at turning point
2157
+ segmentEnd = i;
2158
+ ps = -ps;
2159
+ ypos = 2;
2160
+ continue;
2161
+ }
2162
+
2163
+ if (ps < 0 && i == segmentStart + ps) {
2164
+ // done with the reverse sweep
2165
+ ctx.fill();
2166
+ areaOpen = false;
2167
+ ps = -ps;
2168
+ ypos = 1;
2169
+ i = segmentStart = segmentEnd + ps;
2170
+ continue;
2171
+ }
2172
+ }
2173
+
2174
+ if (x1 == null || x2 == null)
2175
+ continue;
2176
+
2177
+ // clip x values
2178
+
2179
+ // clip with xmin
2180
+ if (x1 <= x2 && x1 < axisx.min) {
2181
+ if (x2 < axisx.min)
2182
+ continue;
2183
+ y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
2184
+ x1 = axisx.min;
2185
+ }
2186
+ else if (x2 <= x1 && x2 < axisx.min) {
2187
+ if (x1 < axisx.min)
2188
+ continue;
2189
+ y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
2190
+ x2 = axisx.min;
2191
+ }
2192
+
2193
+ // clip with xmax
2194
+ if (x1 >= x2 && x1 > axisx.max) {
2195
+ if (x2 > axisx.max)
2196
+ continue;
2197
+ y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
2198
+ x1 = axisx.max;
2199
+ }
2200
+ else if (x2 >= x1 && x2 > axisx.max) {
2201
+ if (x1 > axisx.max)
2202
+ continue;
2203
+ y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
2204
+ x2 = axisx.max;
2205
+ }
2206
+
2207
+ if (!areaOpen) {
2208
+ // open area
2209
+ ctx.beginPath();
2210
+ ctx.moveTo(axisx.p2c(x1), axisy.p2c(bottom));
2211
+ areaOpen = true;
2212
+ }
2213
+
2214
+ // now first check the case where both is outside
2215
+ if (y1 >= axisy.max && y2 >= axisy.max) {
2216
+ ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.max));
2217
+ ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.max));
2218
+ continue;
2219
+ }
2220
+ else if (y1 <= axisy.min && y2 <= axisy.min) {
2221
+ ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.min));
2222
+ ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.min));
2223
+ continue;
2224
+ }
2225
+
2226
+ // else it's a bit more complicated, there might
2227
+ // be a flat maxed out rectangle first, then a
2228
+ // triangular cutout or reverse; to find these
2229
+ // keep track of the current x values
2230
+ var x1old = x1, x2old = x2;
2231
+
2232
+ // clip the y values, without shortcutting, we
2233
+ // go through all cases in turn
2234
+
2235
+ // clip with ymin
2236
+ if (y1 <= y2 && y1 < axisy.min && y2 >= axisy.min) {
2237
+ x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
2238
+ y1 = axisy.min;
2239
+ }
2240
+ else if (y2 <= y1 && y2 < axisy.min && y1 >= axisy.min) {
2241
+ x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
2242
+ y2 = axisy.min;
2243
+ }
2244
+
2245
+ // clip with ymax
2246
+ if (y1 >= y2 && y1 > axisy.max && y2 <= axisy.max) {
2247
+ x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
2248
+ y1 = axisy.max;
2249
+ }
2250
+ else if (y2 >= y1 && y2 > axisy.max && y1 <= axisy.max) {
2251
+ x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
2252
+ y2 = axisy.max;
2253
+ }
2254
+
2255
+ // if the x value was changed we got a rectangle
2256
+ // to fill
2257
+ if (x1 != x1old) {
2258
+ ctx.lineTo(axisx.p2c(x1old), axisy.p2c(y1));
2259
+ // it goes to (x1, y1), but we fill that below
2260
+ }
2261
+
2262
+ // fill triangular section, this sometimes result
2263
+ // in redundant points if (x1, y1) hasn't changed
2264
+ // from previous line to, but we just ignore that
2265
+ ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1));
2266
+ ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2));
2267
+
2268
+ // fill the other rectangle if it's there
2269
+ if (x2 != x2old) {
2270
+ ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2));
2271
+ ctx.lineTo(axisx.p2c(x2old), axisy.p2c(y2));
2272
+ }
2273
+ }
2274
+ }
2275
+
2276
+ ctx.save();
2277
+ ctx.translate(plotOffset.left, plotOffset.top);
2278
+ ctx.lineJoin = "round";
2279
+
2280
+ var lw = series.lines.lineWidth,
2281
+ sw = series.shadowSize;
2282
+ // FIXME: consider another form of shadow when filling is turned on
2283
+ if (lw > 0 && sw > 0) {
2284
+ // draw shadow as a thick and thin line with transparency
2285
+ ctx.lineWidth = sw;
2286
+ ctx.strokeStyle = "rgba(0,0,0,0.1)";
2287
+ // position shadow at angle from the mid of line
2288
+ var angle = Math.PI/18;
2289
+ plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/2), Math.cos(angle) * (lw/2 + sw/2), series.xaxis, series.yaxis);
2290
+ ctx.lineWidth = sw/2;
2291
+ plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/4), Math.cos(angle) * (lw/2 + sw/4), series.xaxis, series.yaxis);
2292
+ }
2293
+
2294
+ ctx.lineWidth = lw;
2295
+ ctx.strokeStyle = series.color;
2296
+ var fillStyle = getFillStyle(series.lines, series.color, 0, plotHeight);
2297
+ if (fillStyle) {
2298
+ ctx.fillStyle = fillStyle;
2299
+ plotLineArea(series.datapoints, series.xaxis, series.yaxis);
2300
+ }
2301
+
2302
+ if (lw > 0)
2303
+ plotLine(series.datapoints, 0, 0, series.xaxis, series.yaxis);
2304
+ ctx.restore();
2305
+ }
2306
+
2307
+ function drawSeriesPoints(series) {
2308
+ function plotPoints(datapoints, radius, fillStyle, offset, shadow, axisx, axisy, symbol) {
2309
+ var points = datapoints.points, ps = datapoints.pointsize;
2310
+
2311
+ for (var i = 0; i < points.length; i += ps) {
2312
+ var x = points[i], y = points[i + 1];
2313
+ if (x == null || x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max)
2314
+ continue;
2315
+
2316
+ ctx.beginPath();
2317
+ x = axisx.p2c(x);
2318
+ y = axisy.p2c(y) + offset;
2319
+ if (symbol == "circle")
2320
+ ctx.arc(x, y, radius, 0, shadow ? Math.PI : Math.PI * 2, false);
2321
+ else
2322
+ symbol(ctx, x, y, radius, shadow);
2323
+ ctx.closePath();
2324
+
2325
+ if (fillStyle) {
2326
+ ctx.fillStyle = fillStyle;
2327
+ ctx.fill();
2328
+ }
2329
+ ctx.stroke();
2330
+ }
2331
+ }
2332
+
2333
+ ctx.save();
2334
+ ctx.translate(plotOffset.left, plotOffset.top);
2335
+
2336
+ var lw = series.points.lineWidth,
2337
+ sw = series.shadowSize,
2338
+ radius = series.points.radius,
2339
+ symbol = series.points.symbol;
2340
+
2341
+ // If the user sets the line width to 0, we change it to a very
2342
+ // small value. A line width of 0 seems to force the default of 1.
2343
+ // Doing the conditional here allows the shadow setting to still be
2344
+ // optional even with a lineWidth of 0.
2345
+
2346
+ if( lw == 0 )
2347
+ lw = 0.0001;
2348
+
2349
+ if (lw > 0 && sw > 0) {
2350
+ // draw shadow in two steps
2351
+ var w = sw / 2;
2352
+ ctx.lineWidth = w;
2353
+ ctx.strokeStyle = "rgba(0,0,0,0.1)";
2354
+ plotPoints(series.datapoints, radius, null, w + w/2, true,
2355
+ series.xaxis, series.yaxis, symbol);
2356
+
2357
+ ctx.strokeStyle = "rgba(0,0,0,0.2)";
2358
+ plotPoints(series.datapoints, radius, null, w/2, true,
2359
+ series.xaxis, series.yaxis, symbol);
2360
+ }
2361
+
2362
+ ctx.lineWidth = lw;
2363
+ ctx.strokeStyle = series.color;
2364
+ plotPoints(series.datapoints, radius,
2365
+ getFillStyle(series.points, series.color), 0, false,
2366
+ series.xaxis, series.yaxis, symbol);
2367
+ ctx.restore();
2368
+ }
2369
+
2370
+ function drawBar(x, y, b, barLeft, barRight, offset, fillStyleCallback, axisx, axisy, c, horizontal, lineWidth) {
2371
+ var left, right, bottom, top,
2372
+ drawLeft, drawRight, drawTop, drawBottom,
2373
+ tmp;
2374
+
2375
+ // in horizontal mode, we start the bar from the left
2376
+ // instead of from the bottom so it appears to be
2377
+ // horizontal rather than vertical
2378
+ if (horizontal) {
2379
+ drawBottom = drawRight = drawTop = true;
2380
+ drawLeft = false;
2381
+ left = b;
2382
+ right = x;
2383
+ top = y + barLeft;
2384
+ bottom = y + barRight;
2385
+
2386
+ // account for negative bars
2387
+ if (right < left) {
2388
+ tmp = right;
2389
+ right = left;
2390
+ left = tmp;
2391
+ drawLeft = true;
2392
+ drawRight = false;
2393
+ }
2394
+ }
2395
+ else {
2396
+ drawLeft = drawRight = drawTop = true;
2397
+ drawBottom = false;
2398
+ left = x + barLeft;
2399
+ right = x + barRight;
2400
+ bottom = b;
2401
+ top = y;
2402
+
2403
+ // account for negative bars
2404
+ if (top < bottom) {
2405
+ tmp = top;
2406
+ top = bottom;
2407
+ bottom = tmp;
2408
+ drawBottom = true;
2409
+ drawTop = false;
2410
+ }
2411
+ }
2412
+
2413
+ // clip
2414
+ if (right < axisx.min || left > axisx.max ||
2415
+ top < axisy.min || bottom > axisy.max)
2416
+ return;
2417
+
2418
+ if (left < axisx.min) {
2419
+ left = axisx.min;
2420
+ drawLeft = false;
2421
+ }
2422
+
2423
+ if (right > axisx.max) {
2424
+ right = axisx.max;
2425
+ drawRight = false;
2426
+ }
2427
+
2428
+ if (bottom < axisy.min) {
2429
+ bottom = axisy.min;
2430
+ drawBottom = false;
2431
+ }
2432
+
2433
+ if (top > axisy.max) {
2434
+ top = axisy.max;
2435
+ drawTop = false;
2436
+ }
2437
+
2438
+ left = axisx.p2c(left);
2439
+ bottom = axisy.p2c(bottom);
2440
+ right = axisx.p2c(right);
2441
+ top = axisy.p2c(top);
2442
+
2443
+ // fill the bar
2444
+ if (fillStyleCallback) {
2445
+ c.beginPath();
2446
+ c.moveTo(left, bottom);
2447
+ c.lineTo(left, top);
2448
+ c.lineTo(right, top);
2449
+ c.lineTo(right, bottom);
2450
+ c.fillStyle = fillStyleCallback(bottom, top);
2451
+ c.fill();
2452
+ }
2453
+
2454
+ // draw outline
2455
+ if (lineWidth > 0 && (drawLeft || drawRight || drawTop || drawBottom)) {
2456
+ c.beginPath();
2457
+
2458
+ // FIXME: inline moveTo is buggy with excanvas
2459
+ c.moveTo(left, bottom + offset);
2460
+ if (drawLeft)
2461
+ c.lineTo(left, top + offset);
2462
+ else
2463
+ c.moveTo(left, top + offset);
2464
+ if (drawTop)
2465
+ c.lineTo(right, top + offset);
2466
+ else
2467
+ c.moveTo(right, top + offset);
2468
+ if (drawRight)
2469
+ c.lineTo(right, bottom + offset);
2470
+ else
2471
+ c.moveTo(right, bottom + offset);
2472
+ if (drawBottom)
2473
+ c.lineTo(left, bottom + offset);
2474
+ else
2475
+ c.moveTo(left, bottom + offset);
2476
+ c.stroke();
2477
+ }
2478
+ }
2479
+
2480
+ function drawSeriesBars(series) {
2481
+ function plotBars(datapoints, barLeft, barRight, offset, fillStyleCallback, axisx, axisy) {
2482
+ var points = datapoints.points, ps = datapoints.pointsize;
2483
+
2484
+ for (var i = 0; i < points.length; i += ps) {
2485
+ if (points[i] == null)
2486
+ continue;
2487
+ drawBar(points[i], points[i + 1], points[i + 2], barLeft, barRight, offset, fillStyleCallback, axisx, axisy, ctx, series.bars.horizontal, series.bars.lineWidth);
2488
+ }
2489
+ }
2490
+
2491
+ ctx.save();
2492
+ ctx.translate(plotOffset.left, plotOffset.top);
2493
+
2494
+ // FIXME: figure out a way to add shadows (for instance along the right edge)
2495
+ ctx.lineWidth = series.bars.lineWidth;
2496
+ ctx.strokeStyle = series.color;
2497
+
2498
+ var barLeft;
2499
+
2500
+ switch (series.bars.align) {
2501
+ case "left":
2502
+ barLeft = 0;
2503
+ break;
2504
+ case "right":
2505
+ barLeft = -series.bars.barWidth;
2506
+ break;
2507
+ case "center":
2508
+ barLeft = -series.bars.barWidth / 2;
2509
+ break;
2510
+ default:
2511
+ throw new Error("Invalid bar alignment: " + series.bars.align);
2512
+ }
2513
+
2514
+ var fillStyleCallback = series.bars.fill ? function (bottom, top) { return getFillStyle(series.bars, series.color, bottom, top); } : null;
2515
+ plotBars(series.datapoints, barLeft, barLeft + series.bars.barWidth, 0, fillStyleCallback, series.xaxis, series.yaxis);
2516
+ ctx.restore();
2517
+ }
2518
+
2519
+ function getFillStyle(filloptions, seriesColor, bottom, top) {
2520
+ var fill = filloptions.fill;
2521
+ if (!fill)
2522
+ return null;
2523
+
2524
+ if (filloptions.fillColor)
2525
+ return getColorOrGradient(filloptions.fillColor, bottom, top, seriesColor);
2526
+
2527
+ var c = $.color.parse(seriesColor);
2528
+ c.a = typeof fill == "number" ? fill : 0.4;
2529
+ c.normalize();
2530
+ return c.toString();
2531
+ }
2532
+
2533
+ function insertLegend() {
2534
+
2535
+ placeholder.find(".legend").remove();
2536
+
2537
+ if (!options.legend.show)
2538
+ return;
2539
+
2540
+ var fragments = [], entries = [], rowStarted = false,
2541
+ lf = options.legend.labelFormatter, s, label;
2542
+
2543
+ // Build a list of legend entries, with each having a label and a color
2544
+
2545
+ for (var i = 0; i < series.length; ++i) {
2546
+ s = series[i];
2547
+ if (s.label) {
2548
+ label = lf ? lf(s.label, s) : s.label;
2549
+ if (label) {
2550
+ entries.push({
2551
+ label: label,
2552
+ color: s.color
2553
+ });
2554
+ }
2555
+ }
2556
+ }
2557
+
2558
+ // Sort the legend using either the default or a custom comparator
2559
+
2560
+ if (options.legend.sorted) {
2561
+ if ($.isFunction(options.legend.sorted)) {
2562
+ entries.sort(options.legend.sorted);
2563
+ } else if (options.legend.sorted == "reverse") {
2564
+ entries.reverse();
2565
+ } else {
2566
+ var ascending = options.legend.sorted != "descending";
2567
+ entries.sort(function(a, b) {
2568
+ return a.label == b.label ? 0 : (
2569
+ (a.label < b.label) != ascending ? 1 : -1 // Logical XOR
2570
+ );
2571
+ });
2572
+ }
2573
+ }
2574
+
2575
+ // Generate markup for the list of entries, in their final order
2576
+
2577
+ for (var i = 0; i < entries.length; ++i) {
2578
+
2579
+ var entry = entries[i];
2580
+
2581
+ if (i % options.legend.noColumns == 0) {
2582
+ if (rowStarted)
2583
+ fragments.push('</tr>');
2584
+ fragments.push('<tr>');
2585
+ rowStarted = true;
2586
+ }
2587
+
2588
+ fragments.push(
2589
+ '<td class="legendColorBox"><div style="border:1px solid ' + options.legend.labelBoxBorderColor + ';padding:1px"><div style="width:4px;height:0;border:5px solid ' + entry.color + ';overflow:hidden"></div></div></td>' +
2590
+ '<td class="legendLabel">' + entry.label + '</td>'
2591
+ );
2592
+ }
2593
+
2594
+ if (rowStarted)
2595
+ fragments.push('</tr>');
2596
+
2597
+ if (fragments.length == 0)
2598
+ return;
2599
+
2600
+ var table = '<table style="font-size:smaller;color:' + options.grid.color + '">' + fragments.join("") + '</table>';
2601
+ if (options.legend.container != null)
2602
+ $(options.legend.container).html(table);
2603
+ else {
2604
+ var pos = "",
2605
+ p = options.legend.position,
2606
+ m = options.legend.margin;
2607
+ if (m[0] == null)
2608
+ m = [m, m];
2609
+ if (p.charAt(0) == "n")
2610
+ pos += 'top:' + (m[1] + plotOffset.top) + 'px;';
2611
+ else if (p.charAt(0) == "s")
2612
+ pos += 'bottom:' + (m[1] + plotOffset.bottom) + 'px;';
2613
+ if (p.charAt(1) == "e")
2614
+ pos += 'right:' + (m[0] + plotOffset.right) + 'px;';
2615
+ else if (p.charAt(1) == "w")
2616
+ pos += 'left:' + (m[0] + plotOffset.left) + 'px;';
2617
+ var legend = $('<div class="legend">' + table.replace('style="', 'style="position:absolute;' + pos +';') + '</div>').appendTo(placeholder);
2618
+ if (options.legend.backgroundOpacity != 0.0) {
2619
+ // put in the transparent background
2620
+ // separately to avoid blended labels and
2621
+ // label boxes
2622
+ var c = options.legend.backgroundColor;
2623
+ if (c == null) {
2624
+ c = options.grid.backgroundColor;
2625
+ if (c && typeof c == "string")
2626
+ c = $.color.parse(c);
2627
+ else
2628
+ c = $.color.extract(legend, 'background-color');
2629
+ c.a = 1;
2630
+ c = c.toString();
2631
+ }
2632
+ var div = legend.children();
2633
+ $('<div style="position:absolute;width:' + div.width() + 'px;height:' + div.height() + 'px;' + pos +'background-color:' + c + ';"> </div>').prependTo(legend).css('opacity', options.legend.backgroundOpacity);
2634
+ }
2635
+ }
2636
+ }
2637
+
2638
+
2639
+ // interactive features
2640
+
2641
+ var highlights = [],
2642
+ redrawTimeout = null;
2643
+
2644
+ // returns the data item the mouse is over, or null if none is found
2645
+ function findNearbyItem(mouseX, mouseY, seriesFilter) {
2646
+ var maxDistance = options.grid.mouseActiveRadius,
2647
+ smallestDistance = maxDistance * maxDistance + 1,
2648
+ item = null, foundPoint = false, i, j, ps;
2649
+
2650
+ for (i = series.length - 1; i >= 0; --i) {
2651
+ if (!seriesFilter(series[i]))
2652
+ continue;
2653
+
2654
+ var s = series[i],
2655
+ axisx = s.xaxis,
2656
+ axisy = s.yaxis,
2657
+ points = s.datapoints.points,
2658
+ mx = axisx.c2p(mouseX), // precompute some stuff to make the loop faster
2659
+ my = axisy.c2p(mouseY),
2660
+ maxx = maxDistance / axisx.scale,
2661
+ maxy = maxDistance / axisy.scale;
2662
+
2663
+ ps = s.datapoints.pointsize;
2664
+ // with inverse transforms, we can't use the maxx/maxy
2665
+ // optimization, sadly
2666
+ if (axisx.options.inverseTransform)
2667
+ maxx = Number.MAX_VALUE;
2668
+ if (axisy.options.inverseTransform)
2669
+ maxy = Number.MAX_VALUE;
2670
+
2671
+ if (s.lines.show || s.points.show) {
2672
+ for (j = 0; j < points.length; j += ps) {
2673
+ var x = points[j], y = points[j + 1];
2674
+ if (x == null)
2675
+ continue;
2676
+
2677
+ // For points and lines, the cursor must be within a
2678
+ // certain distance to the data point
2679
+ if (x - mx > maxx || x - mx < -maxx ||
2680
+ y - my > maxy || y - my < -maxy)
2681
+ continue;
2682
+
2683
+ // We have to calculate distances in pixels, not in
2684
+ // data units, because the scales of the axes may be different
2685
+ var dx = Math.abs(axisx.p2c(x) - mouseX),
2686
+ dy = Math.abs(axisy.p2c(y) - mouseY),
2687
+ dist = dx * dx + dy * dy; // we save the sqrt
2688
+
2689
+ // use <= to ensure last point takes precedence
2690
+ // (last generally means on top of)
2691
+ if (dist < smallestDistance) {
2692
+ smallestDistance = dist;
2693
+ item = [i, j / ps];
2694
+ }
2695
+ }
2696
+ }
2697
+
2698
+ if (s.bars.show && !item) { // no other point can be nearby
2699
+ var barLeft = s.bars.align == "left" ? 0 : -s.bars.barWidth/2,
2700
+ barRight = barLeft + s.bars.barWidth;
2701
+
2702
+ for (j = 0; j < points.length; j += ps) {
2703
+ var x = points[j], y = points[j + 1], b = points[j + 2];
2704
+ if (x == null)
2705
+ continue;
2706
+
2707
+ // for a bar graph, the cursor must be inside the bar
2708
+ if (series[i].bars.horizontal ?
2709
+ (mx <= Math.max(b, x) && mx >= Math.min(b, x) &&
2710
+ my >= y + barLeft && my <= y + barRight) :
2711
+ (mx >= x + barLeft && mx <= x + barRight &&
2712
+ my >= Math.min(b, y) && my <= Math.max(b, y)))
2713
+ item = [i, j / ps];
2714
+ }
2715
+ }
2716
+ }
2717
+
2718
+ if (item) {
2719
+ i = item[0];
2720
+ j = item[1];
2721
+ ps = series[i].datapoints.pointsize;
2722
+
2723
+ return { datapoint: series[i].datapoints.points.slice(j * ps, (j + 1) * ps),
2724
+ dataIndex: j,
2725
+ series: series[i],
2726
+ seriesIndex: i };
2727
+ }
2728
+
2729
+ return null;
2730
+ }
2731
+
2732
+ function onMouseMove(e) {
2733
+ if (options.grid.hoverable)
2734
+ triggerClickHoverEvent("plothover", e,
2735
+ function (s) { return s["hoverable"] != false; });
2736
+ }
2737
+
2738
+ function onMouseLeave(e) {
2739
+ if (options.grid.hoverable)
2740
+ triggerClickHoverEvent("plothover", e,
2741
+ function (s) { return false; });
2742
+ }
2743
+
2744
+ function onClick(e) {
2745
+ triggerClickHoverEvent("plotclick", e,
2746
+ function (s) { return s["clickable"] != false; });
2747
+ }
2748
+
2749
+ // trigger click or hover event (they send the same parameters
2750
+ // so we share their code)
2751
+ function triggerClickHoverEvent(eventname, event, seriesFilter) {
2752
+ var offset = eventHolder.offset(),
2753
+ canvasX = event.pageX - offset.left - plotOffset.left,
2754
+ canvasY = event.pageY - offset.top - plotOffset.top,
2755
+ pos = canvasToAxisCoords({ left: canvasX, top: canvasY });
2756
+
2757
+ pos.pageX = event.pageX;
2758
+ pos.pageY = event.pageY;
2759
+
2760
+ var item = findNearbyItem(canvasX, canvasY, seriesFilter);
2761
+
2762
+ if (item) {
2763
+ // fill in mouse pos for any listeners out there
2764
+ item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint[0]) + offset.left + plotOffset.left, 10);
2765
+ item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top + plotOffset.top, 10);
2766
+ }
2767
+
2768
+ if (options.grid.autoHighlight) {
2769
+ // clear auto-highlights
2770
+ for (var i = 0; i < highlights.length; ++i) {
2771
+ var h = highlights[i];
2772
+ if (h.auto == eventname &&
2773
+ !(item && h.series == item.series &&
2774
+ h.point[0] == item.datapoint[0] &&
2775
+ h.point[1] == item.datapoint[1]))
2776
+ unhighlight(h.series, h.point);
2777
+ }
2778
+
2779
+ if (item)
2780
+ highlight(item.series, item.datapoint, eventname);
2781
+ }
2782
+
2783
+ placeholder.trigger(eventname, [ pos, item ]);
2784
+ }
2785
+
2786
+ function triggerRedrawOverlay() {
2787
+ var t = options.interaction.redrawOverlayInterval;
2788
+ if (t == -1) { // skip event queue
2789
+ drawOverlay();
2790
+ return;
2791
+ }
2792
+
2793
+ if (!redrawTimeout)
2794
+ redrawTimeout = setTimeout(drawOverlay, t);
2795
+ }
2796
+
2797
+ function drawOverlay() {
2798
+ redrawTimeout = null;
2799
+
2800
+ // draw highlights
2801
+ octx.save();
2802
+ overlay.clear();
2803
+ octx.translate(plotOffset.left, plotOffset.top);
2804
+
2805
+ var i, hi;
2806
+ for (i = 0; i < highlights.length; ++i) {
2807
+ hi = highlights[i];
2808
+
2809
+ if (hi.series.bars.show)
2810
+ drawBarHighlight(hi.series, hi.point);
2811
+ else
2812
+ drawPointHighlight(hi.series, hi.point);
2813
+ }
2814
+ octx.restore();
2815
+
2816
+ executeHooks(hooks.drawOverlay, [octx]);
2817
+ }
2818
+
2819
+ function highlight(s, point, auto) {
2820
+ if (typeof s == "number")
2821
+ s = series[s];
2822
+
2823
+ if (typeof point == "number") {
2824
+ var ps = s.datapoints.pointsize;
2825
+ point = s.datapoints.points.slice(ps * point, ps * (point + 1));
2826
+ }
2827
+
2828
+ var i = indexOfHighlight(s, point);
2829
+ if (i == -1) {
2830
+ highlights.push({ series: s, point: point, auto: auto });
2831
+
2832
+ triggerRedrawOverlay();
2833
+ }
2834
+ else if (!auto)
2835
+ highlights[i].auto = false;
2836
+ }
2837
+
2838
+ function unhighlight(s, point) {
2839
+ if (s == null && point == null) {
2840
+ highlights = [];
2841
+ triggerRedrawOverlay();
2842
+ return;
2843
+ }
2844
+
2845
+ if (typeof s == "number")
2846
+ s = series[s];
2847
+
2848
+ if (typeof point == "number") {
2849
+ var ps = s.datapoints.pointsize;
2850
+ point = s.datapoints.points.slice(ps * point, ps * (point + 1));
2851
+ }
2852
+
2853
+ var i = indexOfHighlight(s, point);
2854
+ if (i != -1) {
2855
+ highlights.splice(i, 1);
2856
+
2857
+ triggerRedrawOverlay();
2858
+ }
2859
+ }
2860
+
2861
+ function indexOfHighlight(s, p) {
2862
+ for (var i = 0; i < highlights.length; ++i) {
2863
+ var h = highlights[i];
2864
+ if (h.series == s && h.point[0] == p[0]
2865
+ && h.point[1] == p[1])
2866
+ return i;
2867
+ }
2868
+ return -1;
2869
+ }
2870
+
2871
+ function drawPointHighlight(series, point) {
2872
+ var x = point[0], y = point[1],
2873
+ axisx = series.xaxis, axisy = series.yaxis,
2874
+ highlightColor = (typeof series.highlightColor === "string") ? series.highlightColor : $.color.parse(series.color).scale('a', 0.5).toString();
2875
+
2876
+ if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max)
2877
+ return;
2878
+
2879
+ var pointRadius = series.points.radius + series.points.lineWidth / 2;
2880
+ octx.lineWidth = pointRadius;
2881
+ octx.strokeStyle = highlightColor;
2882
+ var radius = 1.5 * pointRadius;
2883
+ x = axisx.p2c(x);
2884
+ y = axisy.p2c(y);
2885
+
2886
+ octx.beginPath();
2887
+ if (series.points.symbol == "circle")
2888
+ octx.arc(x, y, radius, 0, 2 * Math.PI, false);
2889
+ else
2890
+ series.points.symbol(octx, x, y, radius, false);
2891
+ octx.closePath();
2892
+ octx.stroke();
2893
+ }
2894
+
2895
+ function drawBarHighlight(series, point) {
2896
+ var highlightColor = (typeof series.highlightColor === "string") ? series.highlightColor : $.color.parse(series.color).scale('a', 0.5).toString(),
2897
+ fillStyle = highlightColor,
2898
+ barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth/2;
2899
+
2900
+ octx.lineWidth = series.bars.lineWidth;
2901
+ octx.strokeStyle = highlightColor;
2902
+
2903
+ drawBar(point[0], point[1], point[2] || 0, barLeft, barLeft + series.bars.barWidth,
2904
+ 0, function () { return fillStyle; }, series.xaxis, series.yaxis, octx, series.bars.horizontal, series.bars.lineWidth);
2905
+ }
2906
+
2907
+ function getColorOrGradient(spec, bottom, top, defaultColor) {
2908
+ if (typeof spec == "string")
2909
+ return spec;
2910
+ else {
2911
+ // assume this is a gradient spec; IE currently only
2912
+ // supports a simple vertical gradient properly, so that's
2913
+ // what we support too
2914
+ var gradient = ctx.createLinearGradient(0, top, 0, bottom);
2915
+
2916
+ for (var i = 0, l = spec.colors.length; i < l; ++i) {
2917
+ var c = spec.colors[i];
2918
+ if (typeof c != "string") {
2919
+ var co = $.color.parse(defaultColor);
2920
+ if (c.brightness != null)
2921
+ co = co.scale('rgb', c.brightness);
2922
+ if (c.opacity != null)
2923
+ co.a *= c.opacity;
2924
+ c = co.toString();
2925
+ }
2926
+ gradient.addColorStop(i / (l - 1), c);
2927
+ }
2928
+
2929
+ return gradient;
2930
+ }
2931
+ }
2932
+ }
2933
+
2934
+ // Add the plot function to the top level of the jQuery object
2935
+
2936
+ $.plot = function(placeholder, data, options) {
2937
+ //var t0 = new Date();
2938
+ var plot = new Plot($(placeholder), data, options, $.plot.plugins);
2939
+ //(window.console ? console.log : alert)("time used (msecs): " + ((new Date()).getTime() - t0.getTime()));
2940
+ return plot;
2941
+ };
2942
+
2943
+ $.plot.version = "0.8.0-beta";
2944
+
2945
+ $.plot.plugins = [];
2946
+
2947
+ // Also add the plot function as a chainable property
2948
+
2949
+ $.fn.plot = function(data, options) {
2950
+ return this.each(function() {
2951
+ $.plot(this, data, options);
2952
+ });
2953
+ }
2954
+
2955
+ // round to nearby lower multiple of base
2956
+ function floorInBase(n, base) {
2957
+ return base * Math.floor(n / base);
2958
+ }
2959
+
2960
+ })(jQuery);