prosperity 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (222) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Rakefile +31 -0
  4. data/app/assets/javascripts/prosperity/application.js.coffee +42 -0
  5. data/app/assets/stylesheets/prosperity/application.css.scss +19 -0
  6. data/app/controllers/prosperity/application_controller.rb +4 -0
  7. data/app/controllers/prosperity/metrics_controller.rb +9 -0
  8. data/app/helpers/prosperity/application_helper.rb +7 -0
  9. data/app/helpers/prosperity/metrics_helper.rb +11 -0
  10. data/app/views/layouts/prosperity/application.html.haml +37 -0
  11. data/app/views/prosperity/metrics/index.html.haml +7 -0
  12. data/config/routes.rb +5 -0
  13. data/db/migrate/20131026214127_test.rb +4 -0
  14. data/lib/prosperity/engine.rb +9 -0
  15. data/lib/prosperity/exception.rb +11 -0
  16. data/lib/prosperity/extractors/base.rb +11 -0
  17. data/lib/prosperity/extractors/count.rb +18 -0
  18. data/lib/prosperity/extractors/group.rb +23 -0
  19. data/lib/prosperity/metric.rb +48 -0
  20. data/lib/prosperity/metric_finder.rb +14 -0
  21. data/lib/prosperity/metrics/option.rb +9 -0
  22. data/lib/prosperity/metrics.rb +5 -0
  23. data/lib/prosperity/period.rb +11 -0
  24. data/lib/prosperity/periods.rb +6 -0
  25. data/lib/prosperity/version.rb +3 -0
  26. data/lib/prosperity.rb +4 -0
  27. data/lib/tasks/prosperity_tasks.rake +4 -0
  28. data/spec/controllers/prosperity/metrics_controller_spec.rb +14 -0
  29. data/spec/dummy/README.rdoc +28 -0
  30. data/spec/dummy/Rakefile +8 -0
  31. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  32. data/spec/dummy/app/assets/javascripts/users.js +2 -0
  33. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  34. data/spec/dummy/app/assets/stylesheets/scaffold.css +56 -0
  35. data/spec/dummy/app/assets/stylesheets/users.css +4 -0
  36. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  37. data/spec/dummy/app/controllers/users_controller.rb +58 -0
  38. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  39. data/spec/dummy/app/helpers/users_helper.rb +2 -0
  40. data/spec/dummy/app/models/user.rb +2 -0
  41. data/spec/dummy/app/prosperity/users_metric.rb +3 -0
  42. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  43. data/spec/dummy/app/views/users/_form.html.erb +25 -0
  44. data/spec/dummy/app/views/users/edit.html.erb +6 -0
  45. data/spec/dummy/app/views/users/index.html.erb +29 -0
  46. data/spec/dummy/app/views/users/new.html.erb +5 -0
  47. data/spec/dummy/app/views/users/show.html.erb +14 -0
  48. data/spec/dummy/bin/bundle +3 -0
  49. data/spec/dummy/bin/rails +4 -0
  50. data/spec/dummy/bin/rake +4 -0
  51. data/spec/dummy/config/application.rb +23 -0
  52. data/spec/dummy/config/boot.rb +5 -0
  53. data/spec/dummy/config/database.yml +24 -0
  54. data/spec/dummy/config/environment.rb +5 -0
  55. data/spec/dummy/config/environments/development.rb +29 -0
  56. data/spec/dummy/config/environments/production.rb +80 -0
  57. data/spec/dummy/config/environments/test.rb +36 -0
  58. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  59. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  60. data/spec/dummy/config/initializers/inflections.rb +16 -0
  61. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  62. data/spec/dummy/config/initializers/secret_token.rb +12 -0
  63. data/spec/dummy/config/initializers/session_store.rb +3 -0
  64. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  65. data/spec/dummy/config/locales/en.yml +23 -0
  66. data/spec/dummy/config/routes.rb +6 -0
  67. data/spec/dummy/config.ru +4 -0
  68. data/spec/dummy/db/development.sqlite3 +0 -0
  69. data/spec/dummy/db/migrate/20131026214807_create_users.rb +10 -0
  70. data/spec/dummy/db/schema.rb +26 -0
  71. data/spec/dummy/db/seeds.rb +7 -0
  72. data/spec/dummy/db/test.sqlite3 +0 -0
  73. data/spec/dummy/log/development.log +18945 -0
  74. data/spec/dummy/log/test.log +1992 -0
  75. data/spec/dummy/public/404.html +58 -0
  76. data/spec/dummy/public/422.html +58 -0
  77. data/spec/dummy/public/500.html +57 -0
  78. data/spec/dummy/public/favicon.ico +0 -0
  79. data/spec/dummy/tmp/cache/assets/development/sass/34a577735054231563e7022ea73e1468db9203ba/application.css.scssc +0 -0
  80. data/spec/dummy/tmp/cache/assets/development/sass/994e9de37e7f58fef085c22ad997b3c9ce2a4b39/_alerts.scssc +0 -0
  81. data/spec/dummy/tmp/cache/assets/development/sass/994e9de37e7f58fef085c22ad997b3c9ce2a4b39/_badges.scssc +0 -0
  82. data/spec/dummy/tmp/cache/assets/development/sass/994e9de37e7f58fef085c22ad997b3c9ce2a4b39/_bootstrap.scssc +0 -0
  83. data/spec/dummy/tmp/cache/assets/development/sass/994e9de37e7f58fef085c22ad997b3c9ce2a4b39/_breadcrumbs.scssc +0 -0
  84. data/spec/dummy/tmp/cache/assets/development/sass/994e9de37e7f58fef085c22ad997b3c9ce2a4b39/_button-groups.scssc +0 -0
  85. data/spec/dummy/tmp/cache/assets/development/sass/994e9de37e7f58fef085c22ad997b3c9ce2a4b39/_buttons.scssc +0 -0
  86. data/spec/dummy/tmp/cache/assets/development/sass/994e9de37e7f58fef085c22ad997b3c9ce2a4b39/_carousel.scssc +0 -0
  87. data/spec/dummy/tmp/cache/assets/development/sass/994e9de37e7f58fef085c22ad997b3c9ce2a4b39/_close.scssc +0 -0
  88. data/spec/dummy/tmp/cache/assets/development/sass/994e9de37e7f58fef085c22ad997b3c9ce2a4b39/_code.scssc +0 -0
  89. data/spec/dummy/tmp/cache/assets/development/sass/994e9de37e7f58fef085c22ad997b3c9ce2a4b39/_component-animations.scssc +0 -0
  90. data/spec/dummy/tmp/cache/assets/development/sass/994e9de37e7f58fef085c22ad997b3c9ce2a4b39/_dropdowns.scssc +0 -0
  91. data/spec/dummy/tmp/cache/assets/development/sass/994e9de37e7f58fef085c22ad997b3c9ce2a4b39/_forms.scssc +0 -0
  92. data/spec/dummy/tmp/cache/assets/development/sass/994e9de37e7f58fef085c22ad997b3c9ce2a4b39/_glyphicons.scssc +0 -0
  93. data/spec/dummy/tmp/cache/assets/development/sass/994e9de37e7f58fef085c22ad997b3c9ce2a4b39/_grid.scssc +0 -0
  94. data/spec/dummy/tmp/cache/assets/development/sass/994e9de37e7f58fef085c22ad997b3c9ce2a4b39/_input-groups.scssc +0 -0
  95. data/spec/dummy/tmp/cache/assets/development/sass/994e9de37e7f58fef085c22ad997b3c9ce2a4b39/_jumbotron.scssc +0 -0
  96. data/spec/dummy/tmp/cache/assets/development/sass/994e9de37e7f58fef085c22ad997b3c9ce2a4b39/_labels.scssc +0 -0
  97. data/spec/dummy/tmp/cache/assets/development/sass/994e9de37e7f58fef085c22ad997b3c9ce2a4b39/_list-group.scssc +0 -0
  98. data/spec/dummy/tmp/cache/assets/development/sass/994e9de37e7f58fef085c22ad997b3c9ce2a4b39/_media.scssc +0 -0
  99. data/spec/dummy/tmp/cache/assets/development/sass/994e9de37e7f58fef085c22ad997b3c9ce2a4b39/_mixins.scssc +0 -0
  100. data/spec/dummy/tmp/cache/assets/development/sass/994e9de37e7f58fef085c22ad997b3c9ce2a4b39/_modals.scssc +0 -0
  101. data/spec/dummy/tmp/cache/assets/development/sass/994e9de37e7f58fef085c22ad997b3c9ce2a4b39/_navbar.scssc +0 -0
  102. data/spec/dummy/tmp/cache/assets/development/sass/994e9de37e7f58fef085c22ad997b3c9ce2a4b39/_navs.scssc +0 -0
  103. data/spec/dummy/tmp/cache/assets/development/sass/994e9de37e7f58fef085c22ad997b3c9ce2a4b39/_normalize.scssc +0 -0
  104. data/spec/dummy/tmp/cache/assets/development/sass/994e9de37e7f58fef085c22ad997b3c9ce2a4b39/_pager.scssc +0 -0
  105. data/spec/dummy/tmp/cache/assets/development/sass/994e9de37e7f58fef085c22ad997b3c9ce2a4b39/_pagination.scssc +0 -0
  106. data/spec/dummy/tmp/cache/assets/development/sass/994e9de37e7f58fef085c22ad997b3c9ce2a4b39/_panels.scssc +0 -0
  107. data/spec/dummy/tmp/cache/assets/development/sass/994e9de37e7f58fef085c22ad997b3c9ce2a4b39/_popovers.scssc +0 -0
  108. data/spec/dummy/tmp/cache/assets/development/sass/994e9de37e7f58fef085c22ad997b3c9ce2a4b39/_print.scssc +0 -0
  109. data/spec/dummy/tmp/cache/assets/development/sass/994e9de37e7f58fef085c22ad997b3c9ce2a4b39/_progress-bars.scssc +0 -0
  110. data/spec/dummy/tmp/cache/assets/development/sass/994e9de37e7f58fef085c22ad997b3c9ce2a4b39/_responsive-utilities.scssc +0 -0
  111. data/spec/dummy/tmp/cache/assets/development/sass/994e9de37e7f58fef085c22ad997b3c9ce2a4b39/_scaffolding.scssc +0 -0
  112. data/spec/dummy/tmp/cache/assets/development/sass/994e9de37e7f58fef085c22ad997b3c9ce2a4b39/_tables.scssc +0 -0
  113. data/spec/dummy/tmp/cache/assets/development/sass/994e9de37e7f58fef085c22ad997b3c9ce2a4b39/_thumbnails.scssc +0 -0
  114. data/spec/dummy/tmp/cache/assets/development/sass/994e9de37e7f58fef085c22ad997b3c9ce2a4b39/_tooltip.scssc +0 -0
  115. data/spec/dummy/tmp/cache/assets/development/sass/994e9de37e7f58fef085c22ad997b3c9ce2a4b39/_type.scssc +0 -0
  116. data/spec/dummy/tmp/cache/assets/development/sass/994e9de37e7f58fef085c22ad997b3c9ce2a4b39/_utilities.scssc +0 -0
  117. data/spec/dummy/tmp/cache/assets/development/sass/994e9de37e7f58fef085c22ad997b3c9ce2a4b39/_variables.scssc +0 -0
  118. data/spec/dummy/tmp/cache/assets/development/sass/994e9de37e7f58fef085c22ad997b3c9ce2a4b39/_wells.scssc +0 -0
  119. data/spec/dummy/tmp/cache/assets/development/sass/be34d2a735e9e523a3c8b9414be4508380964813/bootstrap.scssc +0 -0
  120. data/spec/dummy/tmp/cache/assets/development/sprockets/043337ddb7f545a88004b2eb2a069b4d +0 -0
  121. data/spec/dummy/tmp/cache/assets/development/sprockets/05ee6e5d8777628542d4ed27eab34c5a +0 -0
  122. data/spec/dummy/tmp/cache/assets/development/sprockets/07080bfb6d1d7b60d61990c547c0f47a +0 -0
  123. data/spec/dummy/tmp/cache/assets/development/sprockets/073948f1a0c619142ecfcb14a5806302 +0 -0
  124. data/spec/dummy/tmp/cache/assets/development/sprockets/07e8fb2db7d85e29590a6b05160ca825 +0 -0
  125. data/spec/dummy/tmp/cache/assets/development/sprockets/0800f54ee19cf3cee10b956fa9786799 +0 -0
  126. data/spec/dummy/tmp/cache/assets/development/sprockets/12eb9637e2c654371ecf7033d5a5cb36 +0 -0
  127. data/spec/dummy/tmp/cache/assets/development/sprockets/13fe41fee1fe35b49d145bcc06610705 +0 -0
  128. data/spec/dummy/tmp/cache/assets/development/sprockets/1502b34f91cda1f4222490f78c0fea39 +0 -0
  129. data/spec/dummy/tmp/cache/assets/development/sprockets/1533bcf4a8f5a745dfda80cebd88d217 +0 -0
  130. data/spec/dummy/tmp/cache/assets/development/sprockets/177ca1f3e92db4f8a8f6e7ad9786ebfc +0 -0
  131. data/spec/dummy/tmp/cache/assets/development/sprockets/18d7d771937643bc7e9210b1a80e5763 +0 -0
  132. data/spec/dummy/tmp/cache/assets/development/sprockets/197492ed379339e17a0f5d01d3b01b8c +0 -0
  133. data/spec/dummy/tmp/cache/assets/development/sprockets/1c55cf24465f311353197ce336df0178 +0 -0
  134. data/spec/dummy/tmp/cache/assets/development/sprockets/1cb94582c835e73b3fd3c276f9a7de56 +0 -0
  135. data/spec/dummy/tmp/cache/assets/development/sprockets/203047a4141cf08162077e7290421bbb +0 -0
  136. data/spec/dummy/tmp/cache/assets/development/sprockets/210050da208fb75a75b701bfa4e8470f +0 -0
  137. data/spec/dummy/tmp/cache/assets/development/sprockets/29a6c93aeb91d07a5533b3adba7697d0 +0 -0
  138. data/spec/dummy/tmp/cache/assets/development/sprockets/29ac2be474969468157245a7f8737fa4 +0 -0
  139. data/spec/dummy/tmp/cache/assets/development/sprockets/2f5173deea6c795b8fdde723bb4b63af +0 -0
  140. data/spec/dummy/tmp/cache/assets/development/sprockets/314e44d29ecc7449c9ca3b754329c245 +0 -0
  141. data/spec/dummy/tmp/cache/assets/development/sprockets/3162d1398d6940ee5e933b7ab4d2273d +0 -0
  142. data/spec/dummy/tmp/cache/assets/development/sprockets/357970feca3ac29060c1e3861e2c0953 +0 -0
  143. data/spec/dummy/tmp/cache/assets/development/sprockets/371bf96e99717688ed7313a0c53f4212 +0 -0
  144. data/spec/dummy/tmp/cache/assets/development/sprockets/3d80e2a5faaf2bd987d804c17aead745 +0 -0
  145. data/spec/dummy/tmp/cache/assets/development/sprockets/4050a4e5062ab95c9f32e9b6940821ea +0 -0
  146. data/spec/dummy/tmp/cache/assets/development/sprockets/4155a156cc6ffb880b56dbc34346fbc0 +0 -0
  147. data/spec/dummy/tmp/cache/assets/development/sprockets/4166fe47cddf4ba18c53b843d69b4c5f +0 -0
  148. data/spec/dummy/tmp/cache/assets/development/sprockets/4432a4b71ce9551df8d4b3860f4b9be3 +0 -0
  149. data/spec/dummy/tmp/cache/assets/development/sprockets/510da110ae528e2d22533be39ff696c5 +0 -0
  150. data/spec/dummy/tmp/cache/assets/development/sprockets/5479c7b4fba9179d4d1dc59ae64964c1 +0 -0
  151. data/spec/dummy/tmp/cache/assets/development/sprockets/5a3ab9afd151c265cf11c621c0ddbe00 +0 -0
  152. data/spec/dummy/tmp/cache/assets/development/sprockets/5f1a0d05e77ca8b9a1fc2a47e17a8174 +0 -0
  153. data/spec/dummy/tmp/cache/assets/development/sprockets/647038c58f26163a9217646e1e5f09ae +0 -0
  154. data/spec/dummy/tmp/cache/assets/development/sprockets/64e0150fafbd9aaa3822efa1abb0211d +0 -0
  155. data/spec/dummy/tmp/cache/assets/development/sprockets/6536a3eb4b5d05368d260a46f2485ead +0 -0
  156. data/spec/dummy/tmp/cache/assets/development/sprockets/65c2d908dbca1bc833ae0b552f6c27fa +0 -0
  157. data/spec/dummy/tmp/cache/assets/development/sprockets/6882b260ae69f1594eff540d803e5ac3 +0 -0
  158. data/spec/dummy/tmp/cache/assets/development/sprockets/6fc757c2c8329244ca95d6909865bbc2 +0 -0
  159. data/spec/dummy/tmp/cache/assets/development/sprockets/726d3f6850549a59954f0bf5584cfd9f +0 -0
  160. data/spec/dummy/tmp/cache/assets/development/sprockets/75f2f1e32219e0b89c069e43b49e0e49 +0 -0
  161. data/spec/dummy/tmp/cache/assets/development/sprockets/7ab9ac7cc2529219a4c68fd7854d5c0f +0 -0
  162. data/spec/dummy/tmp/cache/assets/development/sprockets/7abbc975e6786a54d660e0cf19aab1f0 +0 -0
  163. data/spec/dummy/tmp/cache/assets/development/sprockets/7bac3aca131c870ad5b07d8ccdc86e06 +0 -0
  164. data/spec/dummy/tmp/cache/assets/development/sprockets/7ffb7c821c008cd2a390dc7aba74361f +0 -0
  165. data/spec/dummy/tmp/cache/assets/development/sprockets/80392355fc6e00cf69b2cd46978ebcbe +0 -0
  166. data/spec/dummy/tmp/cache/assets/development/sprockets/8084620ce7f95d2b2aeb65c977c62af1 +0 -0
  167. data/spec/dummy/tmp/cache/assets/development/sprockets/874b42463fefbb0bce47775722ebb4ae +0 -0
  168. data/spec/dummy/tmp/cache/assets/development/sprockets/87b209c0c9da28094a8d5581a21262c6 +0 -0
  169. data/spec/dummy/tmp/cache/assets/development/sprockets/87ef8ddd2847d19f22a16d726b88b433 +0 -0
  170. data/spec/dummy/tmp/cache/assets/development/sprockets/91ae0bd43f98b4f1f111e0bdc178302c +0 -0
  171. data/spec/dummy/tmp/cache/assets/development/sprockets/9a7970620504bfe92af14fd57ee08365 +0 -0
  172. data/spec/dummy/tmp/cache/assets/development/sprockets/9c8440c4efec99d4a380eeda42f089c7 +0 -0
  173. data/spec/dummy/tmp/cache/assets/development/sprockets/9e4cf41800946fd32bcfa3591b304eb6 +0 -0
  174. data/spec/dummy/tmp/cache/assets/development/sprockets/a2b14d5c46db32da9183354fb3fcd0bf +0 -0
  175. data/spec/dummy/tmp/cache/assets/development/sprockets/a6d6196cfd275dea0718553a95f997a5 +0 -0
  176. data/spec/dummy/tmp/cache/assets/development/sprockets/a7a8652a3fb42ef81fac3ab77949f82d +0 -0
  177. data/spec/dummy/tmp/cache/assets/development/sprockets/a9d839f632f9ce73fc221b79b39b0924 +0 -0
  178. data/spec/dummy/tmp/cache/assets/development/sprockets/bae0ea1201e2ec9420712ff54b3254aa +0 -0
  179. data/spec/dummy/tmp/cache/assets/development/sprockets/c05c4a514cf0b61ad7b223ee732cd6df +0 -0
  180. data/spec/dummy/tmp/cache/assets/development/sprockets/c29c465933e7e86d6f6224f5e78c5baf +0 -0
  181. data/spec/dummy/tmp/cache/assets/development/sprockets/c975834a35907ecd2d314bbd6844ee88 +0 -0
  182. data/spec/dummy/tmp/cache/assets/development/sprockets/cebc6db0bbb8120f430da3970b173d2f +0 -0
  183. data/spec/dummy/tmp/cache/assets/development/sprockets/cf4affc50c6303e717d6514380d1f417 +0 -0
  184. data/spec/dummy/tmp/cache/assets/development/sprockets/cffd775d018f68ce5dba1ee0d951a994 +0 -0
  185. data/spec/dummy/tmp/cache/assets/development/sprockets/d0194b0fba4290072cfff0be06b348fd +0 -0
  186. data/spec/dummy/tmp/cache/assets/development/sprockets/d771ace226fc8215a3572e0aa35bb0d6 +0 -0
  187. data/spec/dummy/tmp/cache/assets/development/sprockets/d8487b3a1dc72a1361b96fbbc93255c6 +0 -0
  188. data/spec/dummy/tmp/cache/assets/development/sprockets/d8b357d5741711da2dd710af0e15d268 +0 -0
  189. data/spec/dummy/tmp/cache/assets/development/sprockets/e02b5049cf477b43a72b4a00e2fceb46 +0 -0
  190. data/spec/dummy/tmp/cache/assets/development/sprockets/e0be9bf6ab486a790bda60b51224b404 +0 -0
  191. data/spec/dummy/tmp/cache/assets/development/sprockets/e22a1b318499e7f06ea9274e18ab004f +0 -0
  192. data/spec/dummy/tmp/cache/assets/development/sprockets/e298cc3a8203ab1fb6978a90254f8926 +0 -0
  193. data/spec/dummy/tmp/cache/assets/development/sprockets/e2a9bdd87a2d19256837fce6544a122c +0 -0
  194. data/spec/dummy/tmp/cache/assets/development/sprockets/e868f358fe87542c483951a2d9f532b6 +0 -0
  195. data/spec/dummy/tmp/cache/assets/development/sprockets/e8a6adf580ebd1942f4aabe6738678c2 +0 -0
  196. data/spec/dummy/tmp/cache/assets/development/sprockets/ea081ed20ad78a3e8f41a529b6baa2e1 +0 -0
  197. data/spec/dummy/tmp/cache/assets/development/sprockets/ec956e1f28c818063bb61958fe5ae855 +0 -0
  198. data/spec/dummy/tmp/cache/assets/development/sprockets/ed71f99e0a9653296abd5d6f501dd898 +0 -0
  199. data/spec/dummy/tmp/cache/assets/development/sprockets/eee3cb53f216eaa12dd927f144640d0d +0 -0
  200. data/spec/dummy/tmp/cache/assets/development/sprockets/efe6f2d16d3a1819817efb8734daf8af +0 -0
  201. data/spec/dummy/tmp/cache/assets/development/sprockets/f03e99bdb4159ae3485ca2858716e19b +0 -0
  202. data/spec/dummy/tmp/cache/assets/development/sprockets/f29bc513eb7c7d4b79d53d51363f7f1f +0 -0
  203. data/spec/dummy/tmp/cache/assets/development/sprockets/f56253b5f374fff1a33fbbc9881c9124 +0 -0
  204. data/spec/dummy/tmp/cache/assets/development/sprockets/f7cbd26ba1d28d48de824f0e94586655 +0 -0
  205. data/spec/dummy/tmp/cache/assets/development/sprockets/fc2c22310cbb7abb26ae64d4aafa37cb +0 -0
  206. data/spec/dummy/tmp/restart.txt +0 -0
  207. data/spec/helpers/prosperity/metrics_helper_spec.rb +6 -0
  208. data/spec/lib/prosperity/extractors/base_spec.rb +8 -0
  209. data/spec/lib/prosperity/extractors/count_spec.rb +35 -0
  210. data/spec/lib/prosperity/extractors/group_spec.rb +34 -0
  211. data/spec/lib/prosperity/metric_finder_spec.rb +20 -0
  212. data/spec/lib/prosperity/metric_spec.rb +49 -0
  213. data/spec/lib/prosperity/metrics/option_spec.rb +7 -0
  214. data/spec/lib/prosperity/period_spec.rb +7 -0
  215. data/spec/lib/prosperity/periods_spec.rb +7 -0
  216. data/spec/spec_helper.rb +17 -0
  217. data/spec/support/test_database.rb +18 -0
  218. data/spec/test_files/metrics/users_metric.rb +4 -0
  219. data/vendor/assets/javascripts/chart.js +1426 -0
  220. data/vendor/assets/stylesheets/bootstrap-theme.min.css +9 -0
  221. data/vendor/assets/stylesheets/bootstrap.min.css +9 -0
  222. metadata +526 -0
@@ -0,0 +1,1426 @@
1
+ /*!
2
+ * Chart.js
3
+ * http://chartjs.org/
4
+ *
5
+ * Copyright 2013 Nick Downie
6
+ * Released under the MIT license
7
+ * https://github.com/nnnick/Chart.js/blob/master/LICENSE.md
8
+ */
9
+
10
+ //Define the global Chart Variable as a class.
11
+ window.Chart = function(context){
12
+
13
+ var chart = this;
14
+
15
+
16
+ //Easing functions adapted from Robert Penner's easing equations
17
+ //http://www.robertpenner.com/easing/
18
+
19
+ var animationOptions = {
20
+ linear : function (t){
21
+ return t;
22
+ },
23
+ easeInQuad: function (t) {
24
+ return t*t;
25
+ },
26
+ easeOutQuad: function (t) {
27
+ return -1 *t*(t-2);
28
+ },
29
+ easeInOutQuad: function (t) {
30
+ if ((t/=1/2) < 1) return 1/2*t*t;
31
+ return -1/2 * ((--t)*(t-2) - 1);
32
+ },
33
+ easeInCubic: function (t) {
34
+ return t*t*t;
35
+ },
36
+ easeOutCubic: function (t) {
37
+ return 1*((t=t/1-1)*t*t + 1);
38
+ },
39
+ easeInOutCubic: function (t) {
40
+ if ((t/=1/2) < 1) return 1/2*t*t*t;
41
+ return 1/2*((t-=2)*t*t + 2);
42
+ },
43
+ easeInQuart: function (t) {
44
+ return t*t*t*t;
45
+ },
46
+ easeOutQuart: function (t) {
47
+ return -1 * ((t=t/1-1)*t*t*t - 1);
48
+ },
49
+ easeInOutQuart: function (t) {
50
+ if ((t/=1/2) < 1) return 1/2*t*t*t*t;
51
+ return -1/2 * ((t-=2)*t*t*t - 2);
52
+ },
53
+ easeInQuint: function (t) {
54
+ return 1*(t/=1)*t*t*t*t;
55
+ },
56
+ easeOutQuint: function (t) {
57
+ return 1*((t=t/1-1)*t*t*t*t + 1);
58
+ },
59
+ easeInOutQuint: function (t) {
60
+ if ((t/=1/2) < 1) return 1/2*t*t*t*t*t;
61
+ return 1/2*((t-=2)*t*t*t*t + 2);
62
+ },
63
+ easeInSine: function (t) {
64
+ return -1 * Math.cos(t/1 * (Math.PI/2)) + 1;
65
+ },
66
+ easeOutSine: function (t) {
67
+ return 1 * Math.sin(t/1 * (Math.PI/2));
68
+ },
69
+ easeInOutSine: function (t) {
70
+ return -1/2 * (Math.cos(Math.PI*t/1) - 1);
71
+ },
72
+ easeInExpo: function (t) {
73
+ return (t==0) ? 1 : 1 * Math.pow(2, 10 * (t/1 - 1));
74
+ },
75
+ easeOutExpo: function (t) {
76
+ return (t==1) ? 1 : 1 * (-Math.pow(2, -10 * t/1) + 1);
77
+ },
78
+ easeInOutExpo: function (t) {
79
+ if (t==0) return 0;
80
+ if (t==1) return 1;
81
+ if ((t/=1/2) < 1) return 1/2 * Math.pow(2, 10 * (t - 1));
82
+ return 1/2 * (-Math.pow(2, -10 * --t) + 2);
83
+ },
84
+ easeInCirc: function (t) {
85
+ if (t>=1) return t;
86
+ return -1 * (Math.sqrt(1 - (t/=1)*t) - 1);
87
+ },
88
+ easeOutCirc: function (t) {
89
+ return 1 * Math.sqrt(1 - (t=t/1-1)*t);
90
+ },
91
+ easeInOutCirc: function (t) {
92
+ if ((t/=1/2) < 1) return -1/2 * (Math.sqrt(1 - t*t) - 1);
93
+ return 1/2 * (Math.sqrt(1 - (t-=2)*t) + 1);
94
+ },
95
+ easeInElastic: function (t) {
96
+ var s=1.70158;var p=0;var a=1;
97
+ if (t==0) return 0; if ((t/=1)==1) return 1; if (!p) p=1*.3;
98
+ if (a < Math.abs(1)) { a=1; var s=p/4; }
99
+ else var s = p/(2*Math.PI) * Math.asin (1/a);
100
+ return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*1-s)*(2*Math.PI)/p ));
101
+ },
102
+ easeOutElastic: function (t) {
103
+ var s=1.70158;var p=0;var a=1;
104
+ if (t==0) return 0; if ((t/=1)==1) return 1; if (!p) p=1*.3;
105
+ if (a < Math.abs(1)) { a=1; var s=p/4; }
106
+ else var s = p/(2*Math.PI) * Math.asin (1/a);
107
+ return a*Math.pow(2,-10*t) * Math.sin( (t*1-s)*(2*Math.PI)/p ) + 1;
108
+ },
109
+ easeInOutElastic: function (t) {
110
+ var s=1.70158;var p=0;var a=1;
111
+ if (t==0) return 0; if ((t/=1/2)==2) return 1; if (!p) p=1*(.3*1.5);
112
+ if (a < Math.abs(1)) { a=1; var s=p/4; }
113
+ else var s = p/(2*Math.PI) * Math.asin (1/a);
114
+ if (t < 1) return -.5*(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*1-s)*(2*Math.PI)/p ));
115
+ return a*Math.pow(2,-10*(t-=1)) * Math.sin( (t*1-s)*(2*Math.PI)/p )*.5 + 1;
116
+ },
117
+ easeInBack: function (t) {
118
+ var s = 1.70158;
119
+ return 1*(t/=1)*t*((s+1)*t - s);
120
+ },
121
+ easeOutBack: function (t) {
122
+ var s = 1.70158;
123
+ return 1*((t=t/1-1)*t*((s+1)*t + s) + 1);
124
+ },
125
+ easeInOutBack: function (t) {
126
+ var s = 1.70158;
127
+ if ((t/=1/2) < 1) return 1/2*(t*t*(((s*=(1.525))+1)*t - s));
128
+ return 1/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2);
129
+ },
130
+ easeInBounce: function (t) {
131
+ return 1 - animationOptions.easeOutBounce (1-t);
132
+ },
133
+ easeOutBounce: function (t) {
134
+ if ((t/=1) < (1/2.75)) {
135
+ return 1*(7.5625*t*t);
136
+ } else if (t < (2/2.75)) {
137
+ return 1*(7.5625*(t-=(1.5/2.75))*t + .75);
138
+ } else if (t < (2.5/2.75)) {
139
+ return 1*(7.5625*(t-=(2.25/2.75))*t + .9375);
140
+ } else {
141
+ return 1*(7.5625*(t-=(2.625/2.75))*t + .984375);
142
+ }
143
+ },
144
+ easeInOutBounce: function (t) {
145
+ if (t < 1/2) return animationOptions.easeInBounce (t*2) * .5;
146
+ return animationOptions.easeOutBounce (t*2-1) * .5 + 1*.5;
147
+ }
148
+ };
149
+
150
+ //Variables global to the chart
151
+ var width = context.canvas.width;
152
+ var height = context.canvas.height;
153
+
154
+
155
+ //High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale.
156
+ if (window.devicePixelRatio) {
157
+ context.canvas.style.width = width + "px";
158
+ context.canvas.style.height = height + "px";
159
+ context.canvas.height = height * window.devicePixelRatio;
160
+ context.canvas.width = width * window.devicePixelRatio;
161
+ context.scale(window.devicePixelRatio, window.devicePixelRatio);
162
+ }
163
+
164
+ this.PolarArea = function(data,options){
165
+
166
+ chart.PolarArea.defaults = {
167
+ scaleOverlay : true,
168
+ scaleOverride : false,
169
+ scaleSteps : null,
170
+ scaleStepWidth : null,
171
+ scaleStartValue : null,
172
+ scaleShowLine : true,
173
+ scaleLineColor : "rgba(0,0,0,.1)",
174
+ scaleLineWidth : 1,
175
+ scaleShowLabels : true,
176
+ scaleLabel : "<%=value%>",
177
+ scaleFontFamily : "'Arial'",
178
+ scaleFontSize : 12,
179
+ scaleFontStyle : "normal",
180
+ scaleFontColor : "#666",
181
+ scaleShowLabelBackdrop : true,
182
+ scaleBackdropColor : "rgba(255,255,255,0.75)",
183
+ scaleBackdropPaddingY : 2,
184
+ scaleBackdropPaddingX : 2,
185
+ segmentShowStroke : true,
186
+ segmentStrokeColor : "#fff",
187
+ segmentStrokeWidth : 2,
188
+ animation : true,
189
+ animationSteps : 100,
190
+ animationEasing : "easeOutBounce",
191
+ animateRotate : true,
192
+ animateScale : false,
193
+ onAnimationComplete : null
194
+ };
195
+
196
+ var config = (options)? mergeChartConfig(chart.PolarArea.defaults,options) : chart.PolarArea.defaults;
197
+
198
+ return new PolarArea(data,config,context);
199
+ };
200
+
201
+ this.Radar = function(data,options){
202
+
203
+ chart.Radar.defaults = {
204
+ scaleOverlay : false,
205
+ scaleOverride : false,
206
+ scaleSteps : null,
207
+ scaleStepWidth : null,
208
+ scaleStartValue : null,
209
+ scaleShowLine : true,
210
+ scaleLineColor : "rgba(0,0,0,.1)",
211
+ scaleLineWidth : 1,
212
+ scaleShowLabels : false,
213
+ scaleLabel : "<%=value%>",
214
+ scaleFontFamily : "'Arial'",
215
+ scaleFontSize : 12,
216
+ scaleFontStyle : "normal",
217
+ scaleFontColor : "#666",
218
+ scaleShowLabelBackdrop : true,
219
+ scaleBackdropColor : "rgba(255,255,255,0.75)",
220
+ scaleBackdropPaddingY : 2,
221
+ scaleBackdropPaddingX : 2,
222
+ angleShowLineOut : true,
223
+ angleLineColor : "rgba(0,0,0,.1)",
224
+ angleLineWidth : 1,
225
+ pointLabelFontFamily : "'Arial'",
226
+ pointLabelFontStyle : "normal",
227
+ pointLabelFontSize : 12,
228
+ pointLabelFontColor : "#666",
229
+ pointDot : true,
230
+ pointDotRadius : 3,
231
+ pointDotStrokeWidth : 1,
232
+ datasetStroke : true,
233
+ datasetStrokeWidth : 2,
234
+ datasetFill : true,
235
+ animation : true,
236
+ animationSteps : 60,
237
+ animationEasing : "easeOutQuart",
238
+ onAnimationComplete : null
239
+ };
240
+
241
+ var config = (options)? mergeChartConfig(chart.Radar.defaults,options) : chart.Radar.defaults;
242
+
243
+ return new Radar(data,config,context);
244
+ };
245
+
246
+ this.Pie = function(data,options){
247
+ chart.Pie.defaults = {
248
+ segmentShowStroke : true,
249
+ segmentStrokeColor : "#fff",
250
+ segmentStrokeWidth : 2,
251
+ animation : true,
252
+ animationSteps : 100,
253
+ animationEasing : "easeOutBounce",
254
+ animateRotate : true,
255
+ animateScale : false,
256
+ onAnimationComplete : null
257
+ };
258
+
259
+ var config = (options)? mergeChartConfig(chart.Pie.defaults,options) : chart.Pie.defaults;
260
+
261
+ return new Pie(data,config,context);
262
+ };
263
+
264
+ this.Doughnut = function(data,options){
265
+
266
+ chart.Doughnut.defaults = {
267
+ segmentShowStroke : true,
268
+ segmentStrokeColor : "#fff",
269
+ segmentStrokeWidth : 2,
270
+ percentageInnerCutout : 50,
271
+ animation : true,
272
+ animationSteps : 100,
273
+ animationEasing : "easeOutBounce",
274
+ animateRotate : true,
275
+ animateScale : false,
276
+ onAnimationComplete : null
277
+ };
278
+
279
+ var config = (options)? mergeChartConfig(chart.Doughnut.defaults,options) : chart.Doughnut.defaults;
280
+
281
+ return new Doughnut(data,config,context);
282
+
283
+ };
284
+
285
+ this.Line = function(data,options){
286
+
287
+ chart.Line.defaults = {
288
+ scaleOverlay : false,
289
+ scaleOverride : false,
290
+ scaleSteps : null,
291
+ scaleStepWidth : null,
292
+ scaleStartValue : null,
293
+ scaleLineColor : "rgba(0,0,0,.1)",
294
+ scaleLineWidth : 1,
295
+ scaleShowLabels : true,
296
+ scaleLabel : "<%=value%>",
297
+ scaleFontFamily : "'Arial'",
298
+ scaleFontSize : 12,
299
+ scaleFontStyle : "normal",
300
+ scaleFontColor : "#666",
301
+ scaleShowGridLines : true,
302
+ scaleGridLineColor : "rgba(0,0,0,.05)",
303
+ scaleGridLineWidth : 1,
304
+ bezierCurve : true,
305
+ pointDot : true,
306
+ pointDotRadius : 4,
307
+ pointDotStrokeWidth : 2,
308
+ datasetStroke : true,
309
+ datasetStrokeWidth : 2,
310
+ datasetFill : true,
311
+ animation : true,
312
+ animationSteps : 60,
313
+ animationEasing : "easeOutQuart",
314
+ onAnimationComplete : null
315
+ };
316
+ var config = (options) ? mergeChartConfig(chart.Line.defaults,options) : chart.Line.defaults;
317
+
318
+ return new Line(data,config,context);
319
+ }
320
+
321
+ this.Bar = function(data,options){
322
+ chart.Bar.defaults = {
323
+ scaleOverlay : false,
324
+ scaleOverride : false,
325
+ scaleSteps : null,
326
+ scaleStepWidth : null,
327
+ scaleStartValue : null,
328
+ scaleLineColor : "rgba(0,0,0,.1)",
329
+ scaleLineWidth : 1,
330
+ scaleShowLabels : true,
331
+ scaleLabel : "<%=value%>",
332
+ scaleFontFamily : "'Arial'",
333
+ scaleFontSize : 12,
334
+ scaleFontStyle : "normal",
335
+ scaleFontColor : "#666",
336
+ scaleShowGridLines : true,
337
+ scaleGridLineColor : "rgba(0,0,0,.05)",
338
+ scaleGridLineWidth : 1,
339
+ barShowStroke : true,
340
+ barStrokeWidth : 2,
341
+ barValueSpacing : 5,
342
+ barDatasetSpacing : 1,
343
+ animation : true,
344
+ animationSteps : 60,
345
+ animationEasing : "easeOutQuart",
346
+ onAnimationComplete : null
347
+ };
348
+ var config = (options) ? mergeChartConfig(chart.Bar.defaults,options) : chart.Bar.defaults;
349
+
350
+ return new Bar(data,config,context);
351
+ }
352
+
353
+ var clear = function(c){
354
+ c.clearRect(0, 0, width, height);
355
+ };
356
+
357
+ var PolarArea = function(data,config,ctx){
358
+ var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString;
359
+
360
+
361
+ calculateDrawingSizes();
362
+
363
+ valueBounds = getValueBounds();
364
+
365
+ labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : null;
366
+
367
+ //Check and set the scale
368
+ if (!config.scaleOverride){
369
+
370
+ calculatedScale = calculateScale(scaleHeight,valueBounds.maxSteps,valueBounds.minSteps,valueBounds.maxValue,valueBounds.minValue,labelTemplateString);
371
+ }
372
+ else {
373
+ calculatedScale = {
374
+ steps : config.scaleSteps,
375
+ stepValue : config.scaleStepWidth,
376
+ graphMin : config.scaleStartValue,
377
+ labels : []
378
+ }
379
+ populateLabels(labelTemplateString, calculatedScale.labels,calculatedScale.steps,config.scaleStartValue,config.scaleStepWidth);
380
+ }
381
+
382
+ scaleHop = maxSize/(calculatedScale.steps);
383
+
384
+ //Wrap in an animation loop wrapper
385
+ animationLoop(config,drawScale,drawAllSegments,ctx);
386
+
387
+ function calculateDrawingSizes(){
388
+ maxSize = (Min([width,height])/2);
389
+ //Remove whatever is larger - the font size or line width.
390
+
391
+ maxSize -= Max([config.scaleFontSize*0.5,config.scaleLineWidth*0.5]);
392
+
393
+ labelHeight = config.scaleFontSize*2;
394
+ //If we're drawing the backdrop - add the Y padding to the label height and remove from drawing region.
395
+ if (config.scaleShowLabelBackdrop){
396
+ labelHeight += (2 * config.scaleBackdropPaddingY);
397
+ maxSize -= config.scaleBackdropPaddingY*1.5;
398
+ }
399
+
400
+ scaleHeight = maxSize;
401
+ //If the label height is less than 5, set it to 5 so we don't have lines on top of each other.
402
+ labelHeight = Default(labelHeight,5);
403
+ }
404
+ function drawScale(){
405
+ for (var i=0; i<calculatedScale.steps; i++){
406
+ //If the line object is there
407
+ if (config.scaleShowLine){
408
+ ctx.beginPath();
409
+ ctx.arc(width/2, height/2, scaleHop * (i + 1), 0, (Math.PI * 2), true);
410
+ ctx.strokeStyle = config.scaleLineColor;
411
+ ctx.lineWidth = config.scaleLineWidth;
412
+ ctx.stroke();
413
+ }
414
+
415
+ if (config.scaleShowLabels){
416
+ ctx.textAlign = "center";
417
+ ctx.font = config.scaleFontStyle + " " + config.scaleFontSize + "px " + config.scaleFontFamily;
418
+ var label = calculatedScale.labels[i];
419
+ //If the backdrop object is within the font object
420
+ if (config.scaleShowLabelBackdrop){
421
+ var textWidth = ctx.measureText(label).width;
422
+ ctx.fillStyle = config.scaleBackdropColor;
423
+ ctx.beginPath();
424
+ ctx.rect(
425
+ Math.round(width/2 - textWidth/2 - config.scaleBackdropPaddingX), //X
426
+ Math.round(height/2 - (scaleHop * (i + 1)) - config.scaleFontSize*0.5 - config.scaleBackdropPaddingY),//Y
427
+ Math.round(textWidth + (config.scaleBackdropPaddingX*2)), //Width
428
+ Math.round(config.scaleFontSize + (config.scaleBackdropPaddingY*2)) //Height
429
+ );
430
+ ctx.fill();
431
+ }
432
+ ctx.textBaseline = "middle";
433
+ ctx.fillStyle = config.scaleFontColor;
434
+ ctx.fillText(label,width/2,height/2 - (scaleHop * (i + 1)));
435
+ }
436
+ }
437
+ }
438
+ function drawAllSegments(animationDecimal){
439
+ var startAngle = -Math.PI/2,
440
+ angleStep = (Math.PI*2)/data.length,
441
+ scaleAnimation = 1,
442
+ rotateAnimation = 1;
443
+ if (config.animation) {
444
+ if (config.animateScale) {
445
+ scaleAnimation = animationDecimal;
446
+ }
447
+ if (config.animateRotate){
448
+ rotateAnimation = animationDecimal;
449
+ }
450
+ }
451
+
452
+ for (var i=0; i<data.length; i++){
453
+
454
+ ctx.beginPath();
455
+ ctx.arc(width/2,height/2,scaleAnimation * calculateOffset(data[i].value,calculatedScale,scaleHop),startAngle, startAngle + rotateAnimation*angleStep, false);
456
+ ctx.lineTo(width/2,height/2);
457
+ ctx.closePath();
458
+ ctx.fillStyle = data[i].color;
459
+ ctx.fill();
460
+
461
+ if(config.segmentShowStroke){
462
+ ctx.strokeStyle = config.segmentStrokeColor;
463
+ ctx.lineWidth = config.segmentStrokeWidth;
464
+ ctx.stroke();
465
+ }
466
+ startAngle += rotateAnimation*angleStep;
467
+ }
468
+ }
469
+ function getValueBounds() {
470
+ var upperValue = Number.MIN_VALUE;
471
+ var lowerValue = Number.MAX_VALUE;
472
+ for (var i=0; i<data.length; i++){
473
+ if (data[i].value > upperValue) {upperValue = data[i].value;}
474
+ if (data[i].value < lowerValue) {lowerValue = data[i].value;}
475
+ };
476
+
477
+ var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66)));
478
+ var minSteps = Math.floor((scaleHeight / labelHeight*0.5));
479
+
480
+ return {
481
+ maxValue : upperValue,
482
+ minValue : lowerValue,
483
+ maxSteps : maxSteps,
484
+ minSteps : minSteps
485
+ };
486
+
487
+
488
+ }
489
+ }
490
+
491
+ var Radar = function (data,config,ctx) {
492
+ var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString;
493
+
494
+ //If no labels are defined set to an empty array, so referencing length for looping doesn't blow up.
495
+ if (!data.labels) data.labels = [];
496
+
497
+ calculateDrawingSizes();
498
+
499
+ var valueBounds = getValueBounds();
500
+
501
+ labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : null;
502
+
503
+ //Check and set the scale
504
+ if (!config.scaleOverride){
505
+
506
+ calculatedScale = calculateScale(scaleHeight,valueBounds.maxSteps,valueBounds.minSteps,valueBounds.maxValue,valueBounds.minValue,labelTemplateString);
507
+ }
508
+ else {
509
+ calculatedScale = {
510
+ steps : config.scaleSteps,
511
+ stepValue : config.scaleStepWidth,
512
+ graphMin : config.scaleStartValue,
513
+ labels : []
514
+ }
515
+ populateLabels(labelTemplateString, calculatedScale.labels,calculatedScale.steps,config.scaleStartValue,config.scaleStepWidth);
516
+ }
517
+
518
+ scaleHop = maxSize/(calculatedScale.steps);
519
+
520
+ animationLoop(config,drawScale,drawAllDataPoints,ctx);
521
+
522
+ //Radar specific functions.
523
+ function drawAllDataPoints(animationDecimal){
524
+ var rotationDegree = (2*Math.PI)/data.datasets[0].data.length;
525
+
526
+ ctx.save();
527
+ //translate to the centre of the canvas.
528
+ ctx.translate(width/2,height/2);
529
+
530
+ //We accept multiple data sets for radar charts, so show loop through each set
531
+ for (var i=0; i<data.datasets.length; i++){
532
+ ctx.beginPath();
533
+
534
+ ctx.moveTo(0,animationDecimal*(-1*calculateOffset(data.datasets[i].data[0],calculatedScale,scaleHop)));
535
+ for (var j=1; j<data.datasets[i].data.length; j++){
536
+ ctx.rotate(rotationDegree);
537
+ ctx.lineTo(0,animationDecimal*(-1*calculateOffset(data.datasets[i].data[j],calculatedScale,scaleHop)));
538
+
539
+ }
540
+ ctx.closePath();
541
+
542
+
543
+ ctx.fillStyle = data.datasets[i].fillColor;
544
+ ctx.strokeStyle = data.datasets[i].strokeColor;
545
+ ctx.lineWidth = config.datasetStrokeWidth;
546
+ ctx.fill();
547
+ ctx.stroke();
548
+
549
+
550
+ if (config.pointDot){
551
+ ctx.fillStyle = data.datasets[i].pointColor;
552
+ ctx.strokeStyle = data.datasets[i].pointStrokeColor;
553
+ ctx.lineWidth = config.pointDotStrokeWidth;
554
+ for (var k=0; k<data.datasets[i].data.length; k++){
555
+ ctx.rotate(rotationDegree);
556
+ ctx.beginPath();
557
+ ctx.arc(0,animationDecimal*(-1*calculateOffset(data.datasets[i].data[k],calculatedScale,scaleHop)),config.pointDotRadius,2*Math.PI,false);
558
+ ctx.fill();
559
+ ctx.stroke();
560
+ }
561
+
562
+ }
563
+ ctx.rotate(rotationDegree);
564
+
565
+ }
566
+ ctx.restore();
567
+
568
+
569
+ }
570
+ function drawScale(){
571
+ var rotationDegree = (2*Math.PI)/data.datasets[0].data.length;
572
+ ctx.save();
573
+ ctx.translate(width / 2, height / 2);
574
+
575
+ if (config.angleShowLineOut){
576
+ ctx.strokeStyle = config.angleLineColor;
577
+ ctx.lineWidth = config.angleLineWidth;
578
+ for (var h=0; h<data.datasets[0].data.length; h++){
579
+
580
+ ctx.rotate(rotationDegree);
581
+ ctx.beginPath();
582
+ ctx.moveTo(0,0);
583
+ ctx.lineTo(0,-maxSize);
584
+ ctx.stroke();
585
+ }
586
+ }
587
+
588
+ for (var i=0; i<calculatedScale.steps; i++){
589
+ ctx.beginPath();
590
+
591
+ if(config.scaleShowLine){
592
+ ctx.strokeStyle = config.scaleLineColor;
593
+ ctx.lineWidth = config.scaleLineWidth;
594
+ ctx.moveTo(0,-scaleHop * (i+1));
595
+ for (var j=0; j<data.datasets[0].data.length; j++){
596
+ ctx.rotate(rotationDegree);
597
+ ctx.lineTo(0,-scaleHop * (i+1));
598
+ }
599
+ ctx.closePath();
600
+ ctx.stroke();
601
+
602
+ }
603
+
604
+ if (config.scaleShowLabels){
605
+ ctx.textAlign = 'center';
606
+ ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily;
607
+ ctx.textBaseline = "middle";
608
+
609
+ if (config.scaleShowLabelBackdrop){
610
+ var textWidth = ctx.measureText(calculatedScale.labels[i]).width;
611
+ ctx.fillStyle = config.scaleBackdropColor;
612
+ ctx.beginPath();
613
+ ctx.rect(
614
+ Math.round(- textWidth/2 - config.scaleBackdropPaddingX), //X
615
+ Math.round((-scaleHop * (i + 1)) - config.scaleFontSize*0.5 - config.scaleBackdropPaddingY),//Y
616
+ Math.round(textWidth + (config.scaleBackdropPaddingX*2)), //Width
617
+ Math.round(config.scaleFontSize + (config.scaleBackdropPaddingY*2)) //Height
618
+ );
619
+ ctx.fill();
620
+ }
621
+ ctx.fillStyle = config.scaleFontColor;
622
+ ctx.fillText(calculatedScale.labels[i],0,-scaleHop*(i+1));
623
+ }
624
+
625
+ }
626
+ for (var k=0; k<data.labels.length; k++){
627
+ ctx.font = config.pointLabelFontStyle + " " + config.pointLabelFontSize+"px " + config.pointLabelFontFamily;
628
+ ctx.fillStyle = config.pointLabelFontColor;
629
+ var opposite = Math.sin(rotationDegree*k) * (maxSize + config.pointLabelFontSize);
630
+ var adjacent = Math.cos(rotationDegree*k) * (maxSize + config.pointLabelFontSize);
631
+
632
+ if(rotationDegree*k == Math.PI || rotationDegree*k == 0){
633
+ ctx.textAlign = "center";
634
+ }
635
+ else if(rotationDegree*k > Math.PI){
636
+ ctx.textAlign = "right";
637
+ }
638
+ else{
639
+ ctx.textAlign = "left";
640
+ }
641
+
642
+ ctx.textBaseline = "middle";
643
+
644
+ ctx.fillText(data.labels[k],opposite,-adjacent);
645
+
646
+ }
647
+ ctx.restore();
648
+ };
649
+ function calculateDrawingSizes(){
650
+ maxSize = (Min([width,height])/2);
651
+
652
+ labelHeight = config.scaleFontSize*2;
653
+
654
+ var labelLength = 0;
655
+ for (var i=0; i<data.labels.length; i++){
656
+ ctx.font = config.pointLabelFontStyle + " " + config.pointLabelFontSize+"px " + config.pointLabelFontFamily;
657
+ var textMeasurement = ctx.measureText(data.labels[i]).width;
658
+ if(textMeasurement>labelLength) labelLength = textMeasurement;
659
+ }
660
+
661
+ //Figure out whats the largest - the height of the text or the width of what's there, and minus it from the maximum usable size.
662
+ maxSize -= Max([labelLength,((config.pointLabelFontSize/2)*1.5)]);
663
+
664
+ maxSize -= config.pointLabelFontSize;
665
+ maxSize = CapValue(maxSize, null, 0);
666
+ scaleHeight = maxSize;
667
+ //If the label height is less than 5, set it to 5 so we don't have lines on top of each other.
668
+ labelHeight = Default(labelHeight,5);
669
+ };
670
+ function getValueBounds() {
671
+ var upperValue = Number.MIN_VALUE;
672
+ var lowerValue = Number.MAX_VALUE;
673
+
674
+ for (var i=0; i<data.datasets.length; i++){
675
+ for (var j=0; j<data.datasets[i].data.length; j++){
676
+ if (data.datasets[i].data[j] > upperValue){upperValue = data.datasets[i].data[j]}
677
+ if (data.datasets[i].data[j] < lowerValue){lowerValue = data.datasets[i].data[j]}
678
+ }
679
+ }
680
+
681
+ var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66)));
682
+ var minSteps = Math.floor((scaleHeight / labelHeight*0.5));
683
+
684
+ return {
685
+ maxValue : upperValue,
686
+ minValue : lowerValue,
687
+ maxSteps : maxSteps,
688
+ minSteps : minSteps
689
+ };
690
+
691
+
692
+ }
693
+ }
694
+
695
+ var Pie = function(data,config,ctx){
696
+ var segmentTotal = 0;
697
+
698
+ //In case we have a canvas that is not a square. Minus 5 pixels as padding round the edge.
699
+ var pieRadius = Min([height/2,width/2]) - 5;
700
+
701
+ for (var i=0; i<data.length; i++){
702
+ segmentTotal += data[i].value;
703
+ }
704
+
705
+
706
+ animationLoop(config,null,drawPieSegments,ctx);
707
+
708
+ function drawPieSegments (animationDecimal){
709
+ var cumulativeAngle = -Math.PI/2,
710
+ scaleAnimation = 1,
711
+ rotateAnimation = 1;
712
+ if (config.animation) {
713
+ if (config.animateScale) {
714
+ scaleAnimation = animationDecimal;
715
+ }
716
+ if (config.animateRotate){
717
+ rotateAnimation = animationDecimal;
718
+ }
719
+ }
720
+ for (var i=0; i<data.length; i++){
721
+ var segmentAngle = rotateAnimation * ((data[i].value/segmentTotal) * (Math.PI*2));
722
+ ctx.beginPath();
723
+ ctx.arc(width/2,height/2,scaleAnimation * pieRadius,cumulativeAngle,cumulativeAngle + segmentAngle);
724
+ ctx.lineTo(width/2,height/2);
725
+ ctx.closePath();
726
+ ctx.fillStyle = data[i].color;
727
+ ctx.fill();
728
+
729
+ if(config.segmentShowStroke){
730
+ ctx.lineWidth = config.segmentStrokeWidth;
731
+ ctx.strokeStyle = config.segmentStrokeColor;
732
+ ctx.stroke();
733
+ }
734
+ cumulativeAngle += segmentAngle;
735
+ }
736
+ }
737
+ }
738
+
739
+ var Doughnut = function(data,config,ctx){
740
+ var segmentTotal = 0;
741
+
742
+ //In case we have a canvas that is not a square. Minus 5 pixels as padding round the edge.
743
+ var doughnutRadius = Min([height/2,width/2]) - 5;
744
+
745
+ var cutoutRadius = doughnutRadius * (config.percentageInnerCutout/100);
746
+
747
+ for (var i=0; i<data.length; i++){
748
+ segmentTotal += data[i].value;
749
+ }
750
+
751
+
752
+ animationLoop(config,null,drawPieSegments,ctx);
753
+
754
+
755
+ function drawPieSegments (animationDecimal){
756
+ var cumulativeAngle = -Math.PI/2,
757
+ scaleAnimation = 1,
758
+ rotateAnimation = 1;
759
+ if (config.animation) {
760
+ if (config.animateScale) {
761
+ scaleAnimation = animationDecimal;
762
+ }
763
+ if (config.animateRotate){
764
+ rotateAnimation = animationDecimal;
765
+ }
766
+ }
767
+ for (var i=0; i<data.length; i++){
768
+ var segmentAngle = rotateAnimation * ((data[i].value/segmentTotal) * (Math.PI*2));
769
+ ctx.beginPath();
770
+ ctx.arc(width/2,height/2,scaleAnimation * doughnutRadius,cumulativeAngle,cumulativeAngle + segmentAngle,false);
771
+ ctx.arc(width/2,height/2,scaleAnimation * cutoutRadius,cumulativeAngle + segmentAngle,cumulativeAngle,true);
772
+ ctx.closePath();
773
+ ctx.fillStyle = data[i].color;
774
+ ctx.fill();
775
+
776
+ if(config.segmentShowStroke){
777
+ ctx.lineWidth = config.segmentStrokeWidth;
778
+ ctx.strokeStyle = config.segmentStrokeColor;
779
+ ctx.stroke();
780
+ }
781
+ cumulativeAngle += segmentAngle;
782
+ }
783
+ }
784
+
785
+
786
+
787
+ }
788
+
789
+ var Line = function(data,config,ctx){
790
+ var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString, valueHop,widestXLabel, xAxisLength,yAxisPosX,xAxisPosY, rotateLabels = 0;
791
+
792
+ calculateDrawingSizes();
793
+
794
+ valueBounds = getValueBounds();
795
+ //Check and set the scale
796
+ labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : "";
797
+ if (!config.scaleOverride){
798
+
799
+ calculatedScale = calculateScale(scaleHeight,valueBounds.maxSteps,valueBounds.minSteps,valueBounds.maxValue,valueBounds.minValue,labelTemplateString);
800
+ }
801
+ else {
802
+ calculatedScale = {
803
+ steps : config.scaleSteps,
804
+ stepValue : config.scaleStepWidth,
805
+ graphMin : config.scaleStartValue,
806
+ labels : []
807
+ }
808
+ populateLabels(labelTemplateString, calculatedScale.labels,calculatedScale.steps,config.scaleStartValue,config.scaleStepWidth);
809
+ }
810
+
811
+ scaleHop = Math.floor(scaleHeight/calculatedScale.steps);
812
+ calculateXAxisSize();
813
+ animationLoop(config,drawScale,drawLines,ctx);
814
+
815
+ function drawLines(animPc){
816
+ for (var i=0; i<data.datasets.length; i++){
817
+ ctx.strokeStyle = data.datasets[i].strokeColor;
818
+ ctx.lineWidth = config.datasetStrokeWidth;
819
+ ctx.beginPath();
820
+ ctx.moveTo(yAxisPosX, xAxisPosY - animPc*(calculateOffset(data.datasets[i].data[0],calculatedScale,scaleHop)))
821
+
822
+ for (var j=1; j<data.datasets[i].data.length; j++){
823
+ if (config.bezierCurve){
824
+ ctx.bezierCurveTo(xPos(j-0.5),yPos(i,j-1),xPos(j-0.5),yPos(i,j),xPos(j),yPos(i,j));
825
+ }
826
+ else{
827
+ ctx.lineTo(xPos(j),yPos(i,j));
828
+ }
829
+ }
830
+ ctx.stroke();
831
+ if (config.datasetFill){
832
+ ctx.lineTo(yAxisPosX + (valueHop*(data.datasets[i].data.length-1)),xAxisPosY);
833
+ ctx.lineTo(yAxisPosX,xAxisPosY);
834
+ ctx.closePath();
835
+ ctx.fillStyle = data.datasets[i].fillColor;
836
+ ctx.fill();
837
+ }
838
+ else{
839
+ ctx.closePath();
840
+ }
841
+ if(config.pointDot){
842
+ ctx.fillStyle = data.datasets[i].pointColor;
843
+ ctx.strokeStyle = data.datasets[i].pointStrokeColor;
844
+ ctx.lineWidth = config.pointDotStrokeWidth;
845
+ for (var k=0; k<data.datasets[i].data.length; k++){
846
+ ctx.beginPath();
847
+ ctx.arc(yAxisPosX + (valueHop *k),xAxisPosY - animPc*(calculateOffset(data.datasets[i].data[k],calculatedScale,scaleHop)),config.pointDotRadius,0,Math.PI*2,true);
848
+ ctx.fill();
849
+ ctx.stroke();
850
+ }
851
+ }
852
+ }
853
+
854
+ function yPos(dataSet,iteration){
855
+ return xAxisPosY - animPc*(calculateOffset(data.datasets[dataSet].data[iteration],calculatedScale,scaleHop));
856
+ }
857
+ function xPos(iteration){
858
+ return yAxisPosX + (valueHop * iteration);
859
+ }
860
+ }
861
+ function drawScale(){
862
+ //X axis line
863
+ ctx.lineWidth = config.scaleLineWidth;
864
+ ctx.strokeStyle = config.scaleLineColor;
865
+ ctx.beginPath();
866
+ ctx.moveTo(width-widestXLabel/2+5,xAxisPosY);
867
+ ctx.lineTo(width-(widestXLabel/2)-xAxisLength-5,xAxisPosY);
868
+ ctx.stroke();
869
+
870
+
871
+ if (rotateLabels > 0){
872
+ ctx.save();
873
+ ctx.textAlign = "right";
874
+ }
875
+ else{
876
+ ctx.textAlign = "center";
877
+ }
878
+ ctx.fillStyle = config.scaleFontColor;
879
+ for (var i=0; i<data.labels.length; i++){
880
+ ctx.save();
881
+ if (rotateLabels > 0){
882
+ ctx.translate(yAxisPosX + i*valueHop,xAxisPosY + config.scaleFontSize);
883
+ ctx.rotate(-(rotateLabels * (Math.PI/180)));
884
+ ctx.fillText(data.labels[i], 0,0);
885
+ ctx.restore();
886
+ }
887
+
888
+ else{
889
+ ctx.fillText(data.labels[i], yAxisPosX + i*valueHop,xAxisPosY + config.scaleFontSize+3);
890
+ }
891
+
892
+ ctx.beginPath();
893
+ ctx.moveTo(yAxisPosX + i * valueHop, xAxisPosY+3);
894
+
895
+ //Check i isnt 0, so we dont go over the Y axis twice.
896
+ if(config.scaleShowGridLines && i>0){
897
+ ctx.lineWidth = config.scaleGridLineWidth;
898
+ ctx.strokeStyle = config.scaleGridLineColor;
899
+ ctx.lineTo(yAxisPosX + i * valueHop, 5);
900
+ }
901
+ else{
902
+ ctx.lineTo(yAxisPosX + i * valueHop, xAxisPosY+3);
903
+ }
904
+ ctx.stroke();
905
+ }
906
+
907
+ //Y axis
908
+ ctx.lineWidth = config.scaleLineWidth;
909
+ ctx.strokeStyle = config.scaleLineColor;
910
+ ctx.beginPath();
911
+ ctx.moveTo(yAxisPosX,xAxisPosY+5);
912
+ ctx.lineTo(yAxisPosX,5);
913
+ ctx.stroke();
914
+
915
+ ctx.textAlign = "right";
916
+ ctx.textBaseline = "middle";
917
+ for (var j=0; j<calculatedScale.steps; j++){
918
+ ctx.beginPath();
919
+ ctx.moveTo(yAxisPosX-3,xAxisPosY - ((j+1) * scaleHop));
920
+ if (config.scaleShowGridLines){
921
+ ctx.lineWidth = config.scaleGridLineWidth;
922
+ ctx.strokeStyle = config.scaleGridLineColor;
923
+ ctx.lineTo(yAxisPosX + xAxisLength + 5,xAxisPosY - ((j+1) * scaleHop));
924
+ }
925
+ else{
926
+ ctx.lineTo(yAxisPosX-0.5,xAxisPosY - ((j+1) * scaleHop));
927
+ }
928
+
929
+ ctx.stroke();
930
+
931
+ if (config.scaleShowLabels){
932
+ ctx.fillText(calculatedScale.labels[j],yAxisPosX-8,xAxisPosY - ((j+1) * scaleHop));
933
+ }
934
+ }
935
+
936
+
937
+ }
938
+ function calculateXAxisSize(){
939
+ var longestText = 1;
940
+ //if we are showing the labels
941
+ if (config.scaleShowLabels){
942
+ ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily;
943
+ for (var i=0; i<calculatedScale.labels.length; i++){
944
+ var measuredText = ctx.measureText(calculatedScale.labels[i]).width;
945
+ longestText = (measuredText > longestText)? measuredText : longestText;
946
+ }
947
+ //Add a little extra padding from the y axis
948
+ longestText +=10;
949
+ }
950
+ xAxisLength = width - longestText - widestXLabel;
951
+ valueHop = Math.floor(xAxisLength/(data.labels.length-1));
952
+
953
+ yAxisPosX = width-widestXLabel/2-xAxisLength;
954
+ xAxisPosY = scaleHeight + config.scaleFontSize/2;
955
+ }
956
+ function calculateDrawingSizes(){
957
+ maxSize = height;
958
+
959
+ //Need to check the X axis first - measure the length of each text metric, and figure out if we need to rotate by 45 degrees.
960
+ ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily;
961
+ widestXLabel = 1;
962
+ for (var i=0; i<data.labels.length; i++){
963
+ var textLength = ctx.measureText(data.labels[i]).width;
964
+ //If the text length is longer - make that equal to longest text!
965
+ widestXLabel = (textLength > widestXLabel)? textLength : widestXLabel;
966
+ }
967
+ if (width/data.labels.length < widestXLabel){
968
+ rotateLabels = 45;
969
+ if (width/data.labels.length < Math.cos(rotateLabels) * widestXLabel){
970
+ rotateLabels = 90;
971
+ maxSize -= widestXLabel;
972
+ }
973
+ else{
974
+ maxSize -= Math.sin(rotateLabels) * widestXLabel;
975
+ }
976
+ }
977
+ else{
978
+ maxSize -= config.scaleFontSize;
979
+ }
980
+
981
+ //Add a little padding between the x line and the text
982
+ maxSize -= 5;
983
+
984
+
985
+ labelHeight = config.scaleFontSize;
986
+
987
+ maxSize -= labelHeight;
988
+ //Set 5 pixels greater than the font size to allow for a little padding from the X axis.
989
+
990
+ scaleHeight = maxSize;
991
+
992
+ //Then get the area above we can safely draw on.
993
+
994
+ }
995
+ function getValueBounds() {
996
+ var upperValue = Number.MIN_VALUE;
997
+ var lowerValue = Number.MAX_VALUE;
998
+ for (var i=0; i<data.datasets.length; i++){
999
+ for (var j=0; j<data.datasets[i].data.length; j++){
1000
+ if ( data.datasets[i].data[j] > upperValue) { upperValue = data.datasets[i].data[j] };
1001
+ if ( data.datasets[i].data[j] < lowerValue) { lowerValue = data.datasets[i].data[j] };
1002
+ }
1003
+ };
1004
+
1005
+ var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66)));
1006
+ var minSteps = Math.floor((scaleHeight / labelHeight*0.5));
1007
+
1008
+ return {
1009
+ maxValue : upperValue,
1010
+ minValue : lowerValue,
1011
+ maxSteps : maxSteps,
1012
+ minSteps : minSteps
1013
+ };
1014
+
1015
+
1016
+ }
1017
+
1018
+
1019
+ }
1020
+
1021
+ var Bar = function(data,config,ctx){
1022
+ var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString, valueHop,widestXLabel, xAxisLength,yAxisPosX,xAxisPosY,barWidth, rotateLabels = 0;
1023
+
1024
+ calculateDrawingSizes();
1025
+
1026
+ valueBounds = getValueBounds();
1027
+ //Check and set the scale
1028
+ labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : "";
1029
+ if (!config.scaleOverride){
1030
+
1031
+ calculatedScale = calculateScale(scaleHeight,valueBounds.maxSteps,valueBounds.minSteps,valueBounds.maxValue,valueBounds.minValue,labelTemplateString);
1032
+ }
1033
+ else {
1034
+ calculatedScale = {
1035
+ steps : config.scaleSteps,
1036
+ stepValue : config.scaleStepWidth,
1037
+ graphMin : config.scaleStartValue,
1038
+ labels : []
1039
+ }
1040
+ populateLabels(labelTemplateString, calculatedScale.labels,calculatedScale.steps,config.scaleStartValue,config.scaleStepWidth);
1041
+ }
1042
+
1043
+ scaleHop = Math.floor(scaleHeight/calculatedScale.steps);
1044
+ calculateXAxisSize();
1045
+ animationLoop(config,drawScale,drawBars,ctx);
1046
+
1047
+ function drawBars(animPc){
1048
+ ctx.lineWidth = config.barStrokeWidth;
1049
+ for (var i=0; i<data.datasets.length; i++){
1050
+ ctx.fillStyle = data.datasets[i].fillColor;
1051
+ ctx.strokeStyle = data.datasets[i].strokeColor;
1052
+ for (var j=0; j<data.datasets[i].data.length; j++){
1053
+ var barOffset = yAxisPosX + config.barValueSpacing + valueHop*j + barWidth*i + config.barDatasetSpacing*i + config.barStrokeWidth*i;
1054
+
1055
+ ctx.beginPath();
1056
+ ctx.moveTo(barOffset, xAxisPosY);
1057
+ ctx.lineTo(barOffset, xAxisPosY - animPc*calculateOffset(data.datasets[i].data[j],calculatedScale,scaleHop)+(config.barStrokeWidth/2));
1058
+ ctx.lineTo(barOffset + barWidth, xAxisPosY - animPc*calculateOffset(data.datasets[i].data[j],calculatedScale,scaleHop)+(config.barStrokeWidth/2));
1059
+ ctx.lineTo(barOffset + barWidth, xAxisPosY);
1060
+ if(config.barShowStroke){
1061
+ ctx.stroke();
1062
+ }
1063
+ ctx.closePath();
1064
+ ctx.fill();
1065
+ }
1066
+ }
1067
+
1068
+ }
1069
+ function drawScale(){
1070
+ //X axis line
1071
+ ctx.lineWidth = config.scaleLineWidth;
1072
+ ctx.strokeStyle = config.scaleLineColor;
1073
+ ctx.beginPath();
1074
+ ctx.moveTo(width-widestXLabel/2+5,xAxisPosY);
1075
+ ctx.lineTo(width-(widestXLabel/2)-xAxisLength-5,xAxisPosY);
1076
+ ctx.stroke();
1077
+
1078
+
1079
+ if (rotateLabels > 0){
1080
+ ctx.save();
1081
+ ctx.textAlign = "right";
1082
+ }
1083
+ else{
1084
+ ctx.textAlign = "center";
1085
+ }
1086
+ ctx.fillStyle = config.scaleFontColor;
1087
+ for (var i=0; i<data.labels.length; i++){
1088
+ ctx.save();
1089
+ if (rotateLabels > 0){
1090
+ ctx.translate(yAxisPosX + i*valueHop,xAxisPosY + config.scaleFontSize);
1091
+ ctx.rotate(-(rotateLabels * (Math.PI/180)));
1092
+ ctx.fillText(data.labels[i], 0,0);
1093
+ ctx.restore();
1094
+ }
1095
+
1096
+ else{
1097
+ ctx.fillText(data.labels[i], yAxisPosX + i*valueHop + valueHop/2,xAxisPosY + config.scaleFontSize+3);
1098
+ }
1099
+
1100
+ ctx.beginPath();
1101
+ ctx.moveTo(yAxisPosX + (i+1) * valueHop, xAxisPosY+3);
1102
+
1103
+ //Check i isnt 0, so we dont go over the Y axis twice.
1104
+ ctx.lineWidth = config.scaleGridLineWidth;
1105
+ ctx.strokeStyle = config.scaleGridLineColor;
1106
+ ctx.lineTo(yAxisPosX + (i+1) * valueHop, 5);
1107
+ ctx.stroke();
1108
+ }
1109
+
1110
+ //Y axis
1111
+ ctx.lineWidth = config.scaleLineWidth;
1112
+ ctx.strokeStyle = config.scaleLineColor;
1113
+ ctx.beginPath();
1114
+ ctx.moveTo(yAxisPosX,xAxisPosY+5);
1115
+ ctx.lineTo(yAxisPosX,5);
1116
+ ctx.stroke();
1117
+
1118
+ ctx.textAlign = "right";
1119
+ ctx.textBaseline = "middle";
1120
+ for (var j=0; j<calculatedScale.steps; j++){
1121
+ ctx.beginPath();
1122
+ ctx.moveTo(yAxisPosX-3,xAxisPosY - ((j+1) * scaleHop));
1123
+ if (config.scaleShowGridLines){
1124
+ ctx.lineWidth = config.scaleGridLineWidth;
1125
+ ctx.strokeStyle = config.scaleGridLineColor;
1126
+ ctx.lineTo(yAxisPosX + xAxisLength + 5,xAxisPosY - ((j+1) * scaleHop));
1127
+ }
1128
+ else{
1129
+ ctx.lineTo(yAxisPosX-0.5,xAxisPosY - ((j+1) * scaleHop));
1130
+ }
1131
+
1132
+ ctx.stroke();
1133
+ if (config.scaleShowLabels){
1134
+ ctx.fillText(calculatedScale.labels[j],yAxisPosX-8,xAxisPosY - ((j+1) * scaleHop));
1135
+ }
1136
+ }
1137
+
1138
+
1139
+ }
1140
+ function calculateXAxisSize(){
1141
+ var longestText = 1;
1142
+ //if we are showing the labels
1143
+ if (config.scaleShowLabels){
1144
+ ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily;
1145
+ for (var i=0; i<calculatedScale.labels.length; i++){
1146
+ var measuredText = ctx.measureText(calculatedScale.labels[i]).width;
1147
+ longestText = (measuredText > longestText)? measuredText : longestText;
1148
+ }
1149
+ //Add a little extra padding from the y axis
1150
+ longestText +=10;
1151
+ }
1152
+ xAxisLength = width - longestText - widestXLabel;
1153
+ valueHop = Math.floor(xAxisLength/(data.labels.length));
1154
+
1155
+ barWidth = (valueHop - config.scaleGridLineWidth*2 - (config.barValueSpacing*2) - (config.barDatasetSpacing*data.datasets.length-1) - ((config.barStrokeWidth/2)*data.datasets.length-1))/data.datasets.length;
1156
+
1157
+ yAxisPosX = width-widestXLabel/2-xAxisLength;
1158
+ xAxisPosY = scaleHeight + config.scaleFontSize/2;
1159
+ }
1160
+ function calculateDrawingSizes(){
1161
+ maxSize = height;
1162
+
1163
+ //Need to check the X axis first - measure the length of each text metric, and figure out if we need to rotate by 45 degrees.
1164
+ ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily;
1165
+ widestXLabel = 1;
1166
+ for (var i=0; i<data.labels.length; i++){
1167
+ var textLength = ctx.measureText(data.labels[i]).width;
1168
+ //If the text length is longer - make that equal to longest text!
1169
+ widestXLabel = (textLength > widestXLabel)? textLength : widestXLabel;
1170
+ }
1171
+ if (width/data.labels.length < widestXLabel){
1172
+ rotateLabels = 45;
1173
+ if (width/data.labels.length < Math.cos(rotateLabels) * widestXLabel){
1174
+ rotateLabels = 90;
1175
+ maxSize -= widestXLabel;
1176
+ }
1177
+ else{
1178
+ maxSize -= Math.sin(rotateLabels) * widestXLabel;
1179
+ }
1180
+ }
1181
+ else{
1182
+ maxSize -= config.scaleFontSize;
1183
+ }
1184
+
1185
+ //Add a little padding between the x line and the text
1186
+ maxSize -= 5;
1187
+
1188
+
1189
+ labelHeight = config.scaleFontSize;
1190
+
1191
+ maxSize -= labelHeight;
1192
+ //Set 5 pixels greater than the font size to allow for a little padding from the X axis.
1193
+
1194
+ scaleHeight = maxSize;
1195
+
1196
+ //Then get the area above we can safely draw on.
1197
+
1198
+ }
1199
+ function getValueBounds() {
1200
+ var upperValue = Number.MIN_VALUE;
1201
+ var lowerValue = Number.MAX_VALUE;
1202
+ for (var i=0; i<data.datasets.length; i++){
1203
+ for (var j=0; j<data.datasets[i].data.length; j++){
1204
+ if ( data.datasets[i].data[j] > upperValue) { upperValue = data.datasets[i].data[j] };
1205
+ if ( data.datasets[i].data[j] < lowerValue) { lowerValue = data.datasets[i].data[j] };
1206
+ }
1207
+ };
1208
+
1209
+ var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66)));
1210
+ var minSteps = Math.floor((scaleHeight / labelHeight*0.5));
1211
+
1212
+ return {
1213
+ maxValue : upperValue,
1214
+ minValue : lowerValue,
1215
+ maxSteps : maxSteps,
1216
+ minSteps : minSteps
1217
+ };
1218
+
1219
+
1220
+ }
1221
+ }
1222
+
1223
+ function calculateOffset(val,calculatedScale,scaleHop){
1224
+ var outerValue = calculatedScale.steps * calculatedScale.stepValue;
1225
+ var adjustedValue = val - calculatedScale.graphMin;
1226
+ var scalingFactor = CapValue(adjustedValue/outerValue,1,0);
1227
+ return (scaleHop*calculatedScale.steps) * scalingFactor;
1228
+ }
1229
+
1230
+ function animationLoop(config,drawScale,drawData,ctx){
1231
+ var animFrameAmount = (config.animation)? 1/CapValue(config.animationSteps,Number.MAX_VALUE,1) : 1,
1232
+ easingFunction = animationOptions[config.animationEasing],
1233
+ percentAnimComplete =(config.animation)? 0 : 1;
1234
+
1235
+
1236
+
1237
+ if (typeof drawScale !== "function") drawScale = function(){};
1238
+
1239
+ requestAnimFrame(animLoop);
1240
+
1241
+ function animateFrame(){
1242
+ var easeAdjustedAnimationPercent =(config.animation)? CapValue(easingFunction(percentAnimComplete),null,0) : 1;
1243
+ clear(ctx);
1244
+ if(config.scaleOverlay){
1245
+ drawData(easeAdjustedAnimationPercent);
1246
+ drawScale();
1247
+ } else {
1248
+ drawScale();
1249
+ drawData(easeAdjustedAnimationPercent);
1250
+ }
1251
+ }
1252
+ function animLoop(){
1253
+ //We need to check if the animation is incomplete (less than 1), or complete (1).
1254
+ percentAnimComplete += animFrameAmount;
1255
+ animateFrame();
1256
+ //Stop the loop continuing forever
1257
+ if (percentAnimComplete <= 1){
1258
+ requestAnimFrame(animLoop);
1259
+ }
1260
+ else{
1261
+ if (typeof config.onAnimationComplete == "function") config.onAnimationComplete();
1262
+ }
1263
+
1264
+ }
1265
+
1266
+ }
1267
+
1268
+ //Declare global functions to be called within this namespace here.
1269
+
1270
+
1271
+ // shim layer with setTimeout fallback
1272
+ var requestAnimFrame = (function(){
1273
+ return window.requestAnimationFrame ||
1274
+ window.webkitRequestAnimationFrame ||
1275
+ window.mozRequestAnimationFrame ||
1276
+ window.oRequestAnimationFrame ||
1277
+ window.msRequestAnimationFrame ||
1278
+ function(callback) {
1279
+ window.setTimeout(callback, 1000 / 60);
1280
+ };
1281
+ })();
1282
+
1283
+ function calculateScale(drawingHeight,maxSteps,minSteps,maxValue,minValue,labelTemplateString){
1284
+ var graphMin,graphMax,graphRange,stepValue,numberOfSteps,valueRange,rangeOrderOfMagnitude,decimalNum;
1285
+
1286
+ valueRange = maxValue - minValue;
1287
+
1288
+ rangeOrderOfMagnitude = calculateOrderOfMagnitude(valueRange);
1289
+
1290
+ graphMin = Math.floor(minValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude);
1291
+
1292
+ graphMax = Math.ceil(maxValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude);
1293
+
1294
+ graphRange = graphMax - graphMin;
1295
+
1296
+ stepValue = Math.pow(10, rangeOrderOfMagnitude);
1297
+
1298
+ numberOfSteps = Math.round(graphRange / stepValue);
1299
+
1300
+ //Compare number of steps to the max and min for that size graph, and add in half steps if need be.
1301
+ while(numberOfSteps < minSteps || numberOfSteps > maxSteps) {
1302
+ if (numberOfSteps < minSteps){
1303
+ stepValue /= 2;
1304
+ numberOfSteps = Math.round(graphRange/stepValue);
1305
+ }
1306
+ else{
1307
+ stepValue *=2;
1308
+ numberOfSteps = Math.round(graphRange/stepValue);
1309
+ }
1310
+ };
1311
+
1312
+ var labels = [];
1313
+ populateLabels(labelTemplateString, labels, numberOfSteps, graphMin, stepValue);
1314
+
1315
+ return {
1316
+ steps : numberOfSteps,
1317
+ stepValue : stepValue,
1318
+ graphMin : graphMin,
1319
+ labels : labels
1320
+
1321
+ }
1322
+
1323
+ function calculateOrderOfMagnitude(val){
1324
+ return Math.floor(Math.log(val) / Math.LN10);
1325
+ }
1326
+
1327
+
1328
+ }
1329
+
1330
+ //Populate an array of all the labels by interpolating the string.
1331
+ function populateLabels(labelTemplateString, labels, numberOfSteps, graphMin, stepValue) {
1332
+ if (labelTemplateString) {
1333
+ //Fix floating point errors by setting to fixed the on the same decimal as the stepValue.
1334
+ for (var i = 1; i < numberOfSteps + 1; i++) {
1335
+ labels.push(tmpl(labelTemplateString, {value: (graphMin + (stepValue * i)).toFixed(getDecimalPlaces(stepValue))}));
1336
+ }
1337
+ }
1338
+ }
1339
+
1340
+ //Max value from array
1341
+ function Max( array ){
1342
+ return Math.max.apply( Math, array );
1343
+ };
1344
+ //Min value from array
1345
+ function Min( array ){
1346
+ return Math.min.apply( Math, array );
1347
+ };
1348
+ //Default if undefined
1349
+ function Default(userDeclared,valueIfFalse){
1350
+ if(!userDeclared){
1351
+ return valueIfFalse;
1352
+ } else {
1353
+ return userDeclared;
1354
+ }
1355
+ };
1356
+ //Is a number function
1357
+ function isNumber(n) {
1358
+ return !isNaN(parseFloat(n)) && isFinite(n);
1359
+ }
1360
+ //Apply cap a value at a high or low number
1361
+ function CapValue(valueToCap, maxValue, minValue){
1362
+ if(isNumber(maxValue)) {
1363
+ if( valueToCap > maxValue ) {
1364
+ return maxValue;
1365
+ }
1366
+ }
1367
+ if(isNumber(minValue)){
1368
+ if ( valueToCap < minValue ){
1369
+ return minValue;
1370
+ }
1371
+ }
1372
+ return valueToCap;
1373
+ }
1374
+ function getDecimalPlaces (num){
1375
+ var numberOfDecimalPlaces;
1376
+ if (num%1!=0){
1377
+ return num.toString().split(".")[1].length
1378
+ }
1379
+ else{
1380
+ return 0;
1381
+ }
1382
+
1383
+ }
1384
+
1385
+ function mergeChartConfig(defaults,userDefined){
1386
+ var returnObj = {};
1387
+ for (var attrname in defaults) { returnObj[attrname] = defaults[attrname]; }
1388
+ for (var attrname in userDefined) { returnObj[attrname] = userDefined[attrname]; }
1389
+ return returnObj;
1390
+ }
1391
+
1392
+ //Javascript micro templating by John Resig - source at http://ejohn.org/blog/javascript-micro-templating/
1393
+ var cache = {};
1394
+
1395
+ function tmpl(str, data){
1396
+ // Figure out if we're getting a template, or if we need to
1397
+ // load the template - and be sure to cache the result.
1398
+ var fn = !/\W/.test(str) ?
1399
+ cache[str] = cache[str] ||
1400
+ tmpl(document.getElementById(str).innerHTML) :
1401
+
1402
+ // Generate a reusable function that will serve as a template
1403
+ // generator (and which will be cached).
1404
+ new Function("obj",
1405
+ "var p=[],print=function(){p.push.apply(p,arguments);};" +
1406
+
1407
+ // Introduce the data as local variables using with(){}
1408
+ "with(obj){p.push('" +
1409
+
1410
+ // Convert the template into pure JavaScript
1411
+ str
1412
+ .replace(/[\r\t\n]/g, " ")
1413
+ .split("<%").join("\t")
1414
+ .replace(/((^|%>)[^\t]*)'/g, "$1\r")
1415
+ .replace(/\t=(.*?)%>/g, "',$1,'")
1416
+ .split("\t").join("');")
1417
+ .split("%>").join("p.push('")
1418
+ .split("\r").join("\\'")
1419
+ + "');}return p.join('');");
1420
+
1421
+ // Provide some basic currying to the user
1422
+ return data ? fn( data ) : fn;
1423
+ };
1424
+ }
1425
+
1426
+