pcapr-local 0.1.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (203) hide show
  1. data/.document +5 -0
  2. data/LICENSE.txt +20 -0
  3. data/README.md +64 -0
  4. data/Rakefile +57 -0
  5. data/VERSION +1 -0
  6. data/bin/pcap2par +49 -0
  7. data/bin/startpcapr +40 -0
  8. data/bin/stoppcapr +33 -0
  9. data/bin/xtractr +5 -0
  10. data/lib/environment.rb +106 -0
  11. data/lib/exe/xtractr +0 -0
  12. data/lib/mu/pcap.rb +110 -0
  13. data/lib/mu/pcap/ethernet.rb +148 -0
  14. data/lib/mu/pcap/header.rb +75 -0
  15. data/lib/mu/pcap/io_pair.rb +67 -0
  16. data/lib/mu/pcap/io_wrapper.rb +76 -0
  17. data/lib/mu/pcap/ip.rb +61 -0
  18. data/lib/mu/pcap/ipv4.rb +257 -0
  19. data/lib/mu/pcap/ipv6.rb +148 -0
  20. data/lib/mu/pcap/packet.rb +104 -0
  21. data/lib/mu/pcap/pkthdr.rb +155 -0
  22. data/lib/mu/pcap/reader.rb +61 -0
  23. data/lib/mu/pcap/reader/http_family.rb +170 -0
  24. data/lib/mu/pcap/sctp.rb +367 -0
  25. data/lib/mu/pcap/sctp/chunk.rb +123 -0
  26. data/lib/mu/pcap/sctp/chunk/data.rb +134 -0
  27. data/lib/mu/pcap/sctp/chunk/init.rb +100 -0
  28. data/lib/mu/pcap/sctp/chunk/init_ack.rb +68 -0
  29. data/lib/mu/pcap/sctp/parameter.rb +110 -0
  30. data/lib/mu/pcap/sctp/parameter/ip_address.rb +48 -0
  31. data/lib/mu/pcap/stream_packetizer.rb +72 -0
  32. data/lib/mu/pcap/tcp.rb +505 -0
  33. data/lib/mu/pcap/udp.rb +69 -0
  34. data/lib/mu/scenario/pcap.rb +164 -0
  35. data/lib/mu/scenario/pcap/fields.rb +50 -0
  36. data/lib/mu/scenario/pcap/rtp.rb +71 -0
  37. data/lib/pcapr_local.rb +159 -0
  38. data/lib/pcapr_local/config.rb +336 -0
  39. data/lib/pcapr_local/db.rb +197 -0
  40. data/lib/pcapr_local/scanner.rb +250 -0
  41. data/lib/pcapr_local/server.rb +178 -0
  42. data/lib/pcapr_local/www/favicon.ico +0 -0
  43. data/lib/pcapr_local/www/favicon.png +0 -0
  44. data/lib/pcapr_local/www/home/index.html +138 -0
  45. data/lib/pcapr_local/www/static/image/16x16/Cancel.png +0 -0
  46. data/lib/pcapr_local/www/static/image/16x16/Cancel.png.1 +0 -0
  47. data/lib/pcapr_local/www/static/image/16x16/Download.png +0 -0
  48. data/lib/pcapr_local/www/static/image/16x16/Folder3.png +0 -0
  49. data/lib/pcapr_local/www/static/image/16x16/Full Size.png +0 -0
  50. data/lib/pcapr_local/www/static/image/16x16/Minus.png +0 -0
  51. data/lib/pcapr_local/www/static/image/16x16/Plus.png +0 -0
  52. data/lib/pcapr_local/www/static/image/16x16/Search.png +0 -0
  53. data/lib/pcapr_local/www/static/image/16x16/User.png +0 -0
  54. data/lib/pcapr_local/www/static/image/48x48/Phone.png +0 -0
  55. data/lib/pcapr_local/www/static/image/48x48/Video.png +0 -0
  56. data/lib/pcapr_local/www/static/image/bar-orange.gif +0 -0
  57. data/lib/pcapr_local/www/static/image/beta.png +0 -0
  58. data/lib/pcapr_local/www/static/image/bg.png +0 -0
  59. data/lib/pcapr_local/www/static/image/blockquote.png +0 -0
  60. data/lib/pcapr_local/www/static/image/body-bg.png +0 -0
  61. data/lib/pcapr_local/www/static/image/body-h3.png +0 -0
  62. data/lib/pcapr_local/www/static/image/body-hl1-bg.png +0 -0
  63. data/lib/pcapr_local/www/static/image/body-hl1-h3.png +0 -0
  64. data/lib/pcapr_local/www/static/image/body-hl1-readmore.png +0 -0
  65. data/lib/pcapr_local/www/static/image/body-hl2-bg.png +0 -0
  66. data/lib/pcapr_local/www/static/image/body-hl2-h3.png +0 -0
  67. data/lib/pcapr_local/www/static/image/body-hl2-readmore.png +0 -0
  68. data/lib/pcapr_local/www/static/image/body-hl3-bg.png +0 -0
  69. data/lib/pcapr_local/www/static/image/body-hl3-h3.png +0 -0
  70. data/lib/pcapr_local/www/static/image/body-hl3-readmore.png +0 -0
  71. data/lib/pcapr_local/www/static/image/body-hl4-bg.png +0 -0
  72. data/lib/pcapr_local/www/static/image/body-hl4-h3.png +0 -0
  73. data/lib/pcapr_local/www/static/image/body-hl4-readmore.png +0 -0
  74. data/lib/pcapr_local/www/static/image/body-hl5-h3.png +0 -0
  75. data/lib/pcapr_local/www/static/image/body-hl6-h3.png +0 -0
  76. data/lib/pcapr_local/www/static/image/body-hl7-h3.png +0 -0
  77. data/lib/pcapr_local/www/static/image/body-hl8-h3.png +0 -0
  78. data/lib/pcapr_local/www/static/image/body-readmore.png +0 -0
  79. data/lib/pcapr_local/www/static/image/bottom-bg.png +0 -0
  80. data/lib/pcapr_local/www/static/image/bottom-l.png +0 -0
  81. data/lib/pcapr_local/www/static/image/bottom-r.png +0 -0
  82. data/lib/pcapr_local/www/static/image/btn-search.png +0 -0
  83. data/lib/pcapr_local/www/static/image/bullet-1.png +0 -0
  84. data/lib/pcapr_local/www/static/image/bullet-2.png +0 -0
  85. data/lib/pcapr_local/www/static/image/bullet-3.png +0 -0
  86. data/lib/pcapr_local/www/static/image/bullet-4.png +0 -0
  87. data/lib/pcapr_local/www/static/image/bullet-5.png +0 -0
  88. data/lib/pcapr_local/www/static/image/bullet-6.png +0 -0
  89. data/lib/pcapr_local/www/static/image/bullet-7.png +0 -0
  90. data/lib/pcapr_local/www/static/image/bullet-hl1.png +0 -0
  91. data/lib/pcapr_local/www/static/image/bullet-hl2.png +0 -0
  92. data/lib/pcapr_local/www/static/image/bullet-hl3.png +0 -0
  93. data/lib/pcapr_local/www/static/image/bullet-hl4.png +0 -0
  94. data/lib/pcapr_local/www/static/image/bullet-pathway.png +0 -0
  95. data/lib/pcapr_local/www/static/image/bullet-section1.png +0 -0
  96. data/lib/pcapr_local/www/static/image/bullet-section2.png +0 -0
  97. data/lib/pcapr_local/www/static/image/collapsed.gif +0 -0
  98. data/lib/pcapr_local/www/static/image/crosslink.png +0 -0
  99. data/lib/pcapr_local/www/static/image/expanded.gif +0 -0
  100. data/lib/pcapr_local/www/static/image/favicon.ico +0 -0
  101. data/lib/pcapr_local/www/static/image/favicon.png +0 -0
  102. data/lib/pcapr_local/www/static/image/icon-author.png +0 -0
  103. data/lib/pcapr_local/www/static/image/icon-created.png +0 -0
  104. data/lib/pcapr_local/www/static/image/p-expand.gif +0 -0
  105. data/lib/pcapr_local/www/static/image/pcapr-logo.png +0 -0
  106. data/lib/pcapr_local/www/static/image/powered-by.png +0 -0
  107. data/lib/pcapr_local/www/static/image/section1-bg.png +0 -0
  108. data/lib/pcapr_local/www/static/image/section1-h3.png +0 -0
  109. data/lib/pcapr_local/www/static/image/section1-readmore.png +0 -0
  110. data/lib/pcapr_local/www/static/image/section2-bg.png +0 -0
  111. data/lib/pcapr_local/www/static/image/section2-h3.png +0 -0
  112. data/lib/pcapr_local/www/static/image/section2-readmore.png +0 -0
  113. data/lib/pcapr_local/www/static/image/status-alert.png +0 -0
  114. data/lib/pcapr_local/www/static/image/status-download.png +0 -0
  115. data/lib/pcapr_local/www/static/image/status-info.png +0 -0
  116. data/lib/pcapr_local/www/static/image/status-note.png +0 -0
  117. data/lib/pcapr_local/www/static/image/tab-round.png +0 -0
  118. data/lib/pcapr_local/www/static/image/throbber.gif +0 -0
  119. data/lib/pcapr_local/www/static/image/user.jpg +0 -0
  120. data/lib/pcapr_local/www/static/script/closet/async.js +421 -0
  121. data/lib/pcapr_local/www/static/script/closet/closet.api.js +241 -0
  122. data/lib/pcapr_local/www/static/script/closet/closet.folders.js +94 -0
  123. data/lib/pcapr_local/www/static/script/closet/closet.js +187 -0
  124. data/lib/pcapr_local/www/static/script/closet/closet.mr.js +219 -0
  125. data/lib/pcapr_local/www/static/script/closet/closet.options.js +359 -0
  126. data/lib/pcapr_local/www/static/script/closet/closet.quantity.js +73 -0
  127. data/lib/pcapr_local/www/static/script/closet/closet.render.js +205 -0
  128. data/lib/pcapr_local/www/static/script/closet/closet.report.js +86 -0
  129. data/lib/pcapr_local/www/static/script/closet/closet.reports.http.js +135 -0
  130. data/lib/pcapr_local/www/static/script/closet/closet.reports.overview.js +163 -0
  131. data/lib/pcapr_local/www/static/script/closet/closet.reports.sip.js +159 -0
  132. data/lib/pcapr_local/www/static/script/closet/closet.reports.tcp.js +72 -0
  133. data/lib/pcapr_local/www/static/script/closet/closet.reports.visualize.js +263 -0
  134. data/lib/pcapr_local/www/static/script/closet/closet.util.js +40 -0
  135. data/lib/pcapr_local/www/static/script/jquery/jquery-1.4.2.min.js +154 -0
  136. data/lib/pcapr_local/www/static/script/jquery/jquery-ui.js +10921 -0
  137. data/lib/pcapr_local/www/static/script/jquery/jquery.flot.js +2123 -0
  138. data/lib/pcapr_local/www/static/script/jquery/jquery.flot.selection.js +184 -0
  139. data/lib/pcapr_local/www/static/script/jquery/jquery.flot.stack.js +184 -0
  140. data/lib/pcapr_local/www/static/script/jquery/jquery.form.js +643 -0
  141. data/lib/pcapr_local/www/static/script/jquery/jquery.jsonp.min.js +3 -0
  142. data/lib/pcapr_local/www/static/script/jquery/jquery.menu.js +142 -0
  143. data/lib/pcapr_local/www/static/script/jquery/jquery.suggest.js +308 -0
  144. data/lib/pcapr_local/www/static/script/jquery/jquery.ui.core.js +203 -0
  145. data/lib/pcapr_local/www/static/script/jquery/jquery.ui.slider.js +629 -0
  146. data/lib/pcapr_local/www/static/script/jquery/jquery.ui.sortable.js +1055 -0
  147. data/lib/pcapr_local/www/static/script/jquery/jquery.ui.widget.js +236 -0
  148. data/lib/pcapr_local/www/static/script/json2.js +481 -0
  149. data/lib/pcapr_local/www/static/script/sammy/plugins/sammy.cache.js +115 -0
  150. data/lib/pcapr_local/www/static/script/sammy/plugins/sammy.template.js +117 -0
  151. data/lib/pcapr_local/www/static/script/sammy/sammy.js +1696 -0
  152. data/lib/pcapr_local/www/static/script/tipsy/jquery.tipsy.js +104 -0
  153. data/lib/pcapr_local/www/static/style/c3p0.css +116 -0
  154. data/lib/pcapr_local/www/static/style/jquery.suggest.css +27 -0
  155. data/lib/pcapr_local/www/static/style/page.css +1113 -0
  156. data/lib/pcapr_local/www/static/style/tipsy.css +7 -0
  157. data/lib/pcapr_local/www/templates/browse.services.template +10 -0
  158. data/lib/pcapr_local/www/templates/browse.template +77 -0
  159. data/lib/pcapr_local/www/templates/flows.template +38 -0
  160. data/lib/pcapr_local/www/templates/pcap.template +63 -0
  161. data/lib/pcapr_local/www/templates/sip.calls.template +35 -0
  162. data/lib/pcapr_local/www/templates/statistics.template +6 -0
  163. data/lib/pcapr_local/xtractr.rb +179 -0
  164. data/lib/pcapr_local/xtractr/instance.rb +172 -0
  165. data/pcapr-local.gemspec +297 -0
  166. data/test/mu/pcap/reader/tc_http_family.rb +251 -0
  167. data/test/mu/pcap/tc_ethernet.rb +71 -0
  168. data/test/mu/pcap/tc_header.rb +56 -0
  169. data/test/mu/pcap/tc_ipv4.rb +103 -0
  170. data/test/mu/pcap/tc_ipv6.rb +83 -0
  171. data/test/mu/pcap/tc_packet.rb +44 -0
  172. data/test/mu/pcap/tc_pair.rb +58 -0
  173. data/test/mu/pcap/tc_pkthdr.rb +33 -0
  174. data/test/mu/pcap/tc_reader.rb +76 -0
  175. data/test/mu/pcap/tc_tcp.rb +426 -0
  176. data/test/mu/pcap/tc_udp.rb +33 -0
  177. data/test/mu/pcap/tc_wrapper.rb +80 -0
  178. data/test/mu/scenario/pcap/tc_fields.rb +67 -0
  179. data/test/mu/scenario/pcap/tc_rtp.rb +135 -0
  180. data/test/mu/scenario/sip_signalled_call_1.pcap +0 -0
  181. data/test/mu/scenario/tc_pcap.rb +190 -0
  182. data/test/mu/scenario/test_data/arp.pcap +0 -0
  183. data/test/mu/scenario/test_data/dns.pcap +0 -0
  184. data/test/mu/scenario/test_data/http-v6.pcap +0 -0
  185. data/test/mu/scenario/test_data/http.pcap +0 -0
  186. data/test/mu/scenario/test_data/http_chunked.pcap +0 -0
  187. data/test/mu/scenario/test_data/http_deflate.pcap +0 -0
  188. data/test/mu/scenario/test_data/httpauth3.pcap +0 -0
  189. data/test/mu/scenario/test_data/icmp.pcap +0 -0
  190. data/test/mu/scenario/test_data/sip_signalled_call_1.pcap +0 -0
  191. data/test/mu/tc_pcap.rb +39 -0
  192. data/test/mu/testcase.rb +86 -0
  193. data/test/pcapr_local/arp.pcap +0 -0
  194. data/test/pcapr_local/data.js +3 -0
  195. data/test/pcapr_local/http_chunked.pcap +0 -0
  196. data/test/pcapr_local/tc_api.rb +181 -0
  197. data/test/pcapr_local/test.tgz +0 -0
  198. data/test/pcapr_local/test_scanner.rb +241 -0
  199. data/test/pcapr_local/test_xtractr.rb +219 -0
  200. data/test/pcapr_local/testcase.rb +107 -0
  201. data/test/test_export_to_scenario.sh +25 -0
  202. data/test/test_pcapr_local.rb +29 -0
  203. metadata +450 -0
@@ -0,0 +1,2123 @@
1
+ /* Javascript plotting library for jQuery, v. 0.6.
2
+ *
3
+ * Released under the MIT license by IOLA, December 2007.
4
+ *
5
+ */
6
+
7
+ // first an inline dependency, jquery.colorhelpers.js, we inline it here
8
+ // for convenience
9
+
10
+ /* Plugin for jQuery for working with colors.
11
+ *
12
+ * Version 1.0.
13
+ *
14
+ * Inspiration from jQuery color animation plugin by John Resig.
15
+ *
16
+ * Released under the MIT license by Ole Laursen, October 2009.
17
+ *
18
+ * Examples:
19
+ *
20
+ * $.color.parse("#fff").scale('rgb', 0.25).add('a', -0.5).toString()
21
+ * var c = $.color.extract($("#mydiv"), 'background-color');
22
+ * console.log(c.r, c.g, c.b, c.a);
23
+ * $.color.make(100, 50, 25, 0.4).toString() // returns "rgba(100,50,25,0.4)"
24
+ *
25
+ * Note that .scale() and .add() work in-place instead of returning
26
+ * new objects.
27
+ */
28
+ (function(){jQuery.color={};jQuery.color.make=function(E,D,B,C){var F={};F.r=E||0;F.g=D||0;F.b=B||0;F.a=C!=null?C:1;F.add=function(I,H){for(var G=0;G<I.length;++G){F[I.charAt(G)]+=H}return F.normalize()};F.scale=function(I,H){for(var G=0;G<I.length;++G){F[I.charAt(G)]*=H}return F.normalize()};F.toString=function(){if(F.a>=1){return"rgb("+[F.r,F.g,F.b].join(",")+")"}else{return"rgba("+[F.r,F.g,F.b,F.a].join(",")+")"}};F.normalize=function(){function G(I,J,H){return J<I?I:(J>H?H:J)}F.r=G(0,parseInt(F.r),255);F.g=G(0,parseInt(F.g),255);F.b=G(0,parseInt(F.b),255);F.a=G(0,F.a,1);return F};F.clone=function(){return jQuery.color.make(F.r,F.b,F.g,F.a)};return F.normalize()};jQuery.color.extract=function(C,B){var D;do{D=C.css(B).toLowerCase();if(D!=""&&D!="transparent"){break}C=C.parent()}while(!jQuery.nodeName(C.get(0),"body"));if(D=="rgba(0, 0, 0, 0)"){D="transparent"}return jQuery.color.parse(D)};jQuery.color.parse=function(E){var D,B=jQuery.color.make;if(D=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(E)){return B(parseInt(D[1],10),parseInt(D[2],10),parseInt(D[3],10))}if(D=/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(E)){return B(parseInt(D[1],10),parseInt(D[2],10),parseInt(D[3],10),parseFloat(D[4]))}if(D=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(E)){return B(parseFloat(D[1])*2.55,parseFloat(D[2])*2.55,parseFloat(D[3])*2.55)}if(D=/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(E)){return B(parseFloat(D[1])*2.55,parseFloat(D[2])*2.55,parseFloat(D[3])*2.55,parseFloat(D[4]))}if(D=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(E)){return B(parseInt(D[1],16),parseInt(D[2],16),parseInt(D[3],16))}if(D=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(E)){return B(parseInt(D[1]+D[1],16),parseInt(D[2]+D[2],16),parseInt(D[3]+D[3],16))}var C=jQuery.trim(E).toLowerCase();if(C=="transparent"){return B(255,255,255,0)}else{D=A[C];return B(D[0],D[1],D[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]}})();
29
+
30
+ // the actual Flot code
31
+ (function($) {
32
+ function Plot(placeholder, data_, options_, plugins) {
33
+ // data is on the form:
34
+ // [ series1, series2 ... ]
35
+ // where series is either just the data as [ [x1, y1], [x2, y2], ... ]
36
+ // or { data: [ [x1, y1], [x2, y2], ... ], label: "some label", ... }
37
+
38
+ var series = [],
39
+ options = {
40
+ // the color theme used for graphs
41
+ colors: ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"],
42
+ legend: {
43
+ show: true,
44
+ noColumns: 1, // number of colums in legend table
45
+ labelFormatter: null, // fn: string -> string
46
+ labelBoxBorderColor: "#ccc", // border color for the little label boxes
47
+ container: null, // container (as jQuery object) to put legend in, null means default on top of graph
48
+ position: "ne", // position of default legend container within plot
49
+ margin: 5, // distance from grid edge to default legend container within plot
50
+ backgroundColor: null, // null means auto-detect
51
+ backgroundOpacity: 0.85 // set to 0 to avoid background
52
+ },
53
+ xaxis: {
54
+ mode: null, // null or "time"
55
+ transform: null, // null or f: number -> number to transform axis
56
+ inverseTransform: null, // if transform is set, this should be the inverse function
57
+ min: null, // min. value to show, null means set automatically
58
+ max: null, // max. value to show, null means set automatically
59
+ autoscaleMargin: null, // margin in % to add if auto-setting min/max
60
+ ticks: null, // either [1, 3] or [[1, "a"], 3] or (fn: axis info -> ticks) or app. number of ticks for auto-ticks
61
+ tickFormatter: null, // fn: number -> string
62
+ labelWidth: null, // size of tick labels in pixels
63
+ labelHeight: null,
64
+
65
+ // mode specific options
66
+ tickDecimals: null, // no. of decimals, null means auto
67
+ tickSize: null, // number or [number, "unit"]
68
+ minTickSize: null, // number or [number, "unit"]
69
+ monthNames: null, // list of names of months
70
+ timeformat: null, // format string to use
71
+ twelveHourClock: false // 12 or 24 time in time mode
72
+ },
73
+ yaxis: {
74
+ autoscaleMargin: 0.02
75
+ },
76
+ x2axis: {
77
+ autoscaleMargin: null
78
+ },
79
+ y2axis: {
80
+ autoscaleMargin: 0.02
81
+ },
82
+ series: {
83
+ points: {
84
+ show: false,
85
+ radius: 3,
86
+ lineWidth: 2, // in pixels
87
+ fill: true,
88
+ fillColor: "#ffffff"
89
+ },
90
+ lines: {
91
+ // we don't put in show: false so we can see
92
+ // whether lines were actively disabled
93
+ lineWidth: 2, // in pixels
94
+ fill: false,
95
+ fillColor: null,
96
+ steps: false
97
+ },
98
+ bars: {
99
+ show: false,
100
+ lineWidth: 2, // in pixels
101
+ barWidth: 1, // in units of the x axis
102
+ fill: true,
103
+ fillColor: null,
104
+ align: "left", // or "center"
105
+ horizontal: false // when horizontal, left is now top
106
+ },
107
+ shadowSize: 3
108
+ },
109
+ grid: {
110
+ show: true,
111
+ aboveData: false,
112
+ color: "#545454", // primary color used for outline and labels
113
+ backgroundColor: null, // null for transparent, else color
114
+ tickColor: "rgba(0,0,0,0.15)", // color used for the ticks
115
+ labelMargin: 5, // in pixels
116
+ borderWidth: 2, // in pixels
117
+ borderColor: null, // set if different from the grid color
118
+ markings: null, // array of ranges or fn: axes -> array of ranges
119
+ markingsColor: "#f4f4f4",
120
+ markingsLineWidth: 2,
121
+ // interactive stuff
122
+ clickable: false,
123
+ hoverable: false,
124
+ autoHighlight: true, // highlight in case mouse is near
125
+ mouseActiveRadius: 10 // how far the mouse can be away to activate an item
126
+ },
127
+ hooks: {}
128
+ },
129
+ canvas = null, // the canvas for the plot itself
130
+ overlay = null, // canvas for interactive stuff on top of plot
131
+ eventHolder = null, // jQuery object that events should be bound to
132
+ ctx = null, octx = null,
133
+ axes = { xaxis: {}, yaxis: {}, x2axis: {}, y2axis: {} },
134
+ plotOffset = { left: 0, right: 0, top: 0, bottom: 0},
135
+ canvasWidth = 0, canvasHeight = 0,
136
+ plotWidth = 0, plotHeight = 0,
137
+ hooks = {
138
+ processOptions: [],
139
+ processRawData: [],
140
+ processDatapoints: [],
141
+ draw: [],
142
+ bindEvents: [],
143
+ drawOverlay: []
144
+ },
145
+ plot = this;
146
+
147
+ // public functions
148
+ plot.setData = setData;
149
+ plot.setupGrid = setupGrid;
150
+ plot.draw = draw;
151
+ plot.getPlaceholder = function() { return placeholder; };
152
+ plot.getCanvas = function() { return canvas; };
153
+ plot.getPlotOffset = function() { return plotOffset; };
154
+ plot.width = function () { return plotWidth; };
155
+ plot.height = function () { return plotHeight; };
156
+ plot.offset = function () {
157
+ var o = eventHolder.offset();
158
+ o.left += plotOffset.left;
159
+ o.top += plotOffset.top;
160
+ return o;
161
+ };
162
+ plot.getData = function() { return series; };
163
+ plot.getAxes = function() { return axes; };
164
+ plot.getOptions = function() { return options; };
165
+ plot.highlight = highlight;
166
+ plot.unhighlight = unhighlight;
167
+ plot.triggerRedrawOverlay = triggerRedrawOverlay;
168
+ plot.pointOffset = function(point) {
169
+ return { left: parseInt(axisSpecToRealAxis(point, "xaxis").p2c(+point.x) + plotOffset.left),
170
+ top: parseInt(axisSpecToRealAxis(point, "yaxis").p2c(+point.y) + plotOffset.top) };
171
+ };
172
+
173
+
174
+ // public attributes
175
+ plot.hooks = hooks;
176
+
177
+ // initialize
178
+ initPlugins(plot);
179
+ parseOptions(options_);
180
+ constructCanvas();
181
+ setData(data_);
182
+ setupGrid();
183
+ draw();
184
+ bindEvents();
185
+
186
+
187
+ function executeHooks(hook, args) {
188
+ args = [plot].concat(args);
189
+ for (var i = 0; i < hook.length; ++i)
190
+ hook[i].apply(this, args);
191
+ }
192
+
193
+ function initPlugins() {
194
+ for (var i = 0; i < plugins.length; ++i) {
195
+ var p = plugins[i];
196
+ p.init(plot);
197
+ if (p.options)
198
+ $.extend(true, options, p.options);
199
+ }
200
+ }
201
+
202
+ function parseOptions(opts) {
203
+ $.extend(true, options, opts);
204
+ if (options.grid.borderColor == null)
205
+ options.grid.borderColor = options.grid.color;
206
+ // backwards compatibility, to be removed in future
207
+ if (options.xaxis.noTicks && options.xaxis.ticks == null)
208
+ options.xaxis.ticks = options.xaxis.noTicks;
209
+ if (options.yaxis.noTicks && options.yaxis.ticks == null)
210
+ options.yaxis.ticks = options.yaxis.noTicks;
211
+ if (options.grid.coloredAreas)
212
+ options.grid.markings = options.grid.coloredAreas;
213
+ if (options.grid.coloredAreasColor)
214
+ options.grid.markingsColor = options.grid.coloredAreasColor;
215
+ if (options.lines)
216
+ $.extend(true, options.series.lines, options.lines);
217
+ if (options.points)
218
+ $.extend(true, options.series.points, options.points);
219
+ if (options.bars)
220
+ $.extend(true, options.series.bars, options.bars);
221
+ if (options.shadowSize)
222
+ options.series.shadowSize = options.shadowSize;
223
+
224
+ for (var n in hooks)
225
+ if (options.hooks[n] && options.hooks[n].length)
226
+ hooks[n] = hooks[n].concat(options.hooks[n]);
227
+
228
+ executeHooks(hooks.processOptions, [options]);
229
+ }
230
+
231
+ function setData(d) {
232
+ series = parseData(d);
233
+ fillInSeriesOptions();
234
+ processData();
235
+ }
236
+
237
+ function parseData(d) {
238
+ var res = [];
239
+ for (var i = 0; i < d.length; ++i) {
240
+ var s = $.extend(true, {}, options.series);
241
+
242
+ if (d[i].data) {
243
+ s.data = d[i].data; // move the data instead of deep-copy
244
+ delete d[i].data;
245
+
246
+ $.extend(true, s, d[i]);
247
+
248
+ d[i].data = s.data;
249
+ }
250
+ else
251
+ s.data = d[i];
252
+ res.push(s);
253
+ }
254
+
255
+ return res;
256
+ }
257
+
258
+ function axisSpecToRealAxis(obj, attr) {
259
+ var a = obj[attr];
260
+ if (!a || a == 1)
261
+ return axes[attr];
262
+ if (typeof a == "number")
263
+ return axes[attr.charAt(0) + a + attr.slice(1)];
264
+ return a; // assume it's OK
265
+ }
266
+
267
+ function fillInSeriesOptions() {
268
+ var i;
269
+
270
+ // collect what we already got of colors
271
+ var neededColors = series.length,
272
+ usedColors = [],
273
+ assignedColors = [];
274
+ for (i = 0; i < series.length; ++i) {
275
+ var sc = series[i].color;
276
+ if (sc != null) {
277
+ --neededColors;
278
+ if (typeof sc == "number")
279
+ assignedColors.push(sc);
280
+ else
281
+ usedColors.push($.color.parse(series[i].color));
282
+ }
283
+ }
284
+
285
+ // we might need to generate more colors if higher indices
286
+ // are assigned
287
+ for (i = 0; i < assignedColors.length; ++i) {
288
+ neededColors = Math.max(neededColors, assignedColors[i] + 1);
289
+ }
290
+
291
+ // produce colors as needed
292
+ var colors = [], variation = 0;
293
+ i = 0;
294
+ while (colors.length < neededColors) {
295
+ var c;
296
+ if (options.colors.length == i) // check degenerate case
297
+ c = $.color.make(100, 100, 100);
298
+ else
299
+ c = $.color.parse(options.colors[i]);
300
+
301
+ // vary color if needed
302
+ var sign = variation % 2 == 1 ? -1 : 1;
303
+ c.scale('rgb', 1 + sign * Math.ceil(variation / 2) * 0.2)
304
+
305
+ // FIXME: if we're getting to close to something else,
306
+ // we should probably skip this one
307
+ colors.push(c);
308
+
309
+ ++i;
310
+ if (i >= options.colors.length) {
311
+ i = 0;
312
+ ++variation;
313
+ }
314
+ }
315
+
316
+ // fill in the options
317
+ var colori = 0, s;
318
+ for (i = 0; i < series.length; ++i) {
319
+ s = series[i];
320
+
321
+ // assign colors
322
+ if (s.color == null) {
323
+ s.color = colors[colori].toString();
324
+ ++colori;
325
+ }
326
+ else if (typeof s.color == "number")
327
+ s.color = colors[s.color].toString();
328
+
329
+ // turn on lines automatically in case nothing is set
330
+ if (s.lines.show == null) {
331
+ var v, show = true;
332
+ for (v in s)
333
+ if (s[v].show) {
334
+ show = false;
335
+ break;
336
+ }
337
+ if (show)
338
+ s.lines.show = true;
339
+ }
340
+
341
+ // setup axes
342
+ s.xaxis = axisSpecToRealAxis(s, "xaxis");
343
+ s.yaxis = axisSpecToRealAxis(s, "yaxis");
344
+ }
345
+ }
346
+
347
+ function processData() {
348
+ var topSentry = Number.POSITIVE_INFINITY,
349
+ bottomSentry = Number.NEGATIVE_INFINITY,
350
+ i, j, k, m, length,
351
+ s, points, ps, x, y, axis, val, f, p;
352
+
353
+ for (axis in axes) {
354
+ axes[axis].datamin = topSentry;
355
+ axes[axis].datamax = bottomSentry;
356
+ axes[axis].used = false;
357
+ }
358
+
359
+ function updateAxis(axis, min, max) {
360
+ if (min < axis.datamin)
361
+ axis.datamin = min;
362
+ if (max > axis.datamax)
363
+ axis.datamax = max;
364
+ }
365
+
366
+ for (i = 0; i < series.length; ++i) {
367
+ s = series[i];
368
+ s.datapoints = { points: [] };
369
+
370
+ executeHooks(hooks.processRawData, [ s, s.data, s.datapoints ]);
371
+ }
372
+
373
+ // first pass: clean and copy data
374
+ for (i = 0; i < series.length; ++i) {
375
+ s = series[i];
376
+
377
+ var data = s.data, format = s.datapoints.format;
378
+
379
+ if (!format) {
380
+ format = [];
381
+ // find out how to copy
382
+ format.push({ x: true, number: true, required: true });
383
+ format.push({ y: true, number: true, required: true });
384
+
385
+ if (s.bars.show)
386
+ format.push({ y: true, number: true, required: false, defaultValue: 0 });
387
+
388
+ s.datapoints.format = format;
389
+ }
390
+
391
+ if (s.datapoints.pointsize != null)
392
+ continue; // already filled in
393
+
394
+ if (s.datapoints.pointsize == null)
395
+ s.datapoints.pointsize = format.length;
396
+
397
+ ps = s.datapoints.pointsize;
398
+ points = s.datapoints.points;
399
+
400
+ insertSteps = s.lines.show && s.lines.steps;
401
+ s.xaxis.used = s.yaxis.used = true;
402
+
403
+ for (j = k = 0; j < data.length; ++j, k += ps) {
404
+ p = data[j];
405
+
406
+ var nullify = p == null;
407
+ if (!nullify) {
408
+ for (m = 0; m < ps; ++m) {
409
+ val = p[m];
410
+ f = format[m];
411
+
412
+ if (f) {
413
+ if (f.number && val != null) {
414
+ val = +val; // convert to number
415
+ if (isNaN(val))
416
+ val = null;
417
+ }
418
+
419
+ if (val == null) {
420
+ if (f.required)
421
+ nullify = true;
422
+
423
+ if (f.defaultValue != null)
424
+ val = f.defaultValue;
425
+ }
426
+ }
427
+
428
+ points[k + m] = val;
429
+ }
430
+ }
431
+
432
+ if (nullify) {
433
+ for (m = 0; m < ps; ++m) {
434
+ val = points[k + m];
435
+ if (val != null) {
436
+ f = format[m];
437
+ // extract min/max info
438
+ if (f.x)
439
+ updateAxis(s.xaxis, val, val);
440
+ if (f.y)
441
+ updateAxis(s.yaxis, val, val);
442
+ }
443
+ points[k + m] = null;
444
+ }
445
+ }
446
+ else {
447
+ // a little bit of line specific stuff that
448
+ // perhaps shouldn't be here, but lacking
449
+ // better means...
450
+ if (insertSteps && k > 0
451
+ && points[k - ps] != null
452
+ && points[k - ps] != points[k]
453
+ && points[k - ps + 1] != points[k + 1]) {
454
+ // copy the point to make room for a middle point
455
+ for (m = 0; m < ps; ++m)
456
+ points[k + ps + m] = points[k + m];
457
+
458
+ // middle point has same y
459
+ points[k + 1] = points[k - ps + 1];
460
+
461
+ // we've added a point, better reflect that
462
+ k += ps;
463
+ }
464
+ }
465
+ }
466
+ }
467
+
468
+ // give the hooks a chance to run
469
+ for (i = 0; i < series.length; ++i) {
470
+ s = series[i];
471
+
472
+ executeHooks(hooks.processDatapoints, [ s, s.datapoints]);
473
+ }
474
+
475
+ // second pass: find datamax/datamin for auto-scaling
476
+ for (i = 0; i < series.length; ++i) {
477
+ s = series[i];
478
+ points = s.datapoints.points,
479
+ ps = s.datapoints.pointsize;
480
+
481
+ var xmin = topSentry, ymin = topSentry,
482
+ xmax = bottomSentry, ymax = bottomSentry;
483
+
484
+ for (j = 0; j < points.length; j += ps) {
485
+ if (points[j] == null)
486
+ continue;
487
+
488
+ for (m = 0; m < ps; ++m) {
489
+ val = points[j + m];
490
+ f = format[m];
491
+ if (!f)
492
+ continue;
493
+
494
+ if (f.x) {
495
+ if (val < xmin)
496
+ xmin = val;
497
+ if (val > xmax)
498
+ xmax = val;
499
+ }
500
+ if (f.y) {
501
+ if (val < ymin)
502
+ ymin = val;
503
+ if (val > ymax)
504
+ ymax = val;
505
+ }
506
+ }
507
+ }
508
+
509
+ if (s.bars.show) {
510
+ // make sure we got room for the bar on the dancing floor
511
+ var delta = s.bars.align == "left" ? 0 : -s.bars.barWidth/2;
512
+ if (s.bars.horizontal) {
513
+ ymin += delta;
514
+ ymax += delta + s.bars.barWidth;
515
+ }
516
+ else {
517
+ xmin += delta;
518
+ xmax += delta + s.bars.barWidth;
519
+ }
520
+ }
521
+
522
+ updateAxis(s.xaxis, xmin, xmax);
523
+ updateAxis(s.yaxis, ymin, ymax);
524
+ }
525
+
526
+ for (axis in axes) {
527
+ if (axes[axis].datamin == topSentry)
528
+ axes[axis].datamin = null;
529
+ if (axes[axis].datamax == bottomSentry)
530
+ axes[axis].datamax = null;
531
+ }
532
+ }
533
+
534
+ function constructCanvas() {
535
+ function makeCanvas(width, height) {
536
+ var c = document.createElement('canvas');
537
+ c.width = width;
538
+ c.height = height;
539
+ if ($.browser.msie) // excanvas hack
540
+ c = window.G_vmlCanvasManager.initElement(c);
541
+ return c;
542
+ }
543
+
544
+ canvasWidth = parseInt(placeholder.width(), 10);
545
+ canvasHeight = parseInt(placeholder.height(), 10);
546
+ placeholder.html(""); // clear placeholder
547
+ if (placeholder.css("position") == 'static')
548
+ placeholder.css("position", "relative"); // for positioning labels and overlay
549
+
550
+ if (canvasWidth <= 0 || canvasHeight <= 0)
551
+ throw "Invalid dimensions for plot, width = " + canvasWidth + ", height = " + canvasHeight;
552
+
553
+ if ($.browser.msie) // excanvas hack
554
+ window.G_vmlCanvasManager.init_(document); // make sure everything is setup
555
+
556
+ // the canvas
557
+ canvas = $(makeCanvas(canvasWidth, canvasHeight)).appendTo(placeholder).get(0);
558
+ ctx = canvas.getContext("2d");
559
+
560
+ // overlay canvas for interactive features
561
+ overlay = $(makeCanvas(canvasWidth, canvasHeight)).css({ position: 'absolute', left: 0, top: 0 }).appendTo(placeholder).get(0);
562
+ octx = overlay.getContext("2d");
563
+ octx.stroke();
564
+ }
565
+
566
+ function bindEvents() {
567
+ // we include the canvas in the event holder too, because IE 7
568
+ // sometimes has trouble with the stacking order
569
+ eventHolder = $([overlay, canvas]);
570
+
571
+ // bind events
572
+ if (options.grid.hoverable)
573
+ eventHolder.mousemove(onMouseMove);
574
+
575
+ if (options.grid.clickable)
576
+ eventHolder.click(onClick);
577
+
578
+ executeHooks(hooks.bindEvents, [eventHolder]);
579
+ }
580
+
581
+ function setupGrid() {
582
+ function setTransformationHelpers(axis, o) {
583
+ function identity(x) { return x; }
584
+
585
+ var s, m, t = o.transform || identity,
586
+ it = o.inverseTransform;
587
+
588
+ // add transformation helpers
589
+ if (axis == axes.xaxis || axis == axes.x2axis) {
590
+ // precompute how much the axis is scaling a point
591
+ // in canvas space
592
+ s = axis.scale = plotWidth / (t(axis.max) - t(axis.min));
593
+ m = t(axis.min);
594
+
595
+ // data point to canvas coordinate
596
+ if (t == identity) // slight optimization
597
+ axis.p2c = function (p) { return (p - m) * s; };
598
+ else
599
+ axis.p2c = function (p) { return (t(p) - m) * s; };
600
+ // canvas coordinate to data point
601
+ if (!it)
602
+ axis.c2p = function (c) { return m + c / s; };
603
+ else
604
+ axis.c2p = function (c) { return it(m + c / s); };
605
+ }
606
+ else {
607
+ s = axis.scale = plotHeight / (t(axis.max) - t(axis.min));
608
+ m = t(axis.max);
609
+
610
+ if (t == identity)
611
+ axis.p2c = function (p) { return (m - p) * s; };
612
+ else
613
+ axis.p2c = function (p) { return (m - t(p)) * s; };
614
+ if (!it)
615
+ axis.c2p = function (c) { return m - c / s; };
616
+ else
617
+ axis.c2p = function (c) { return it(m - c / s); };
618
+ }
619
+ }
620
+
621
+ function measureLabels(axis, axisOptions) {
622
+ var i, labels = [], l;
623
+
624
+ axis.labelWidth = axisOptions.labelWidth;
625
+ axis.labelHeight = axisOptions.labelHeight;
626
+
627
+ if (axis == axes.xaxis || axis == axes.x2axis) {
628
+ // to avoid measuring the widths of the labels, we
629
+ // construct fixed-size boxes and put the labels inside
630
+ // them, we don't need the exact figures and the
631
+ // fixed-size box content is easy to center
632
+ if (axis.labelWidth == null)
633
+ axis.labelWidth = canvasWidth / (axis.ticks.length > 0 ? axis.ticks.length : 1);
634
+
635
+ // measure x label heights
636
+ if (axis.labelHeight == null) {
637
+ labels = [];
638
+ for (i = 0; i < axis.ticks.length; ++i) {
639
+ l = axis.ticks[i].label;
640
+ if (l)
641
+ labels.push('<div class="tickLabel" style="float:left;width:' + axis.labelWidth + 'px">' + l + '</div>');
642
+ }
643
+
644
+ if (labels.length > 0) {
645
+ var dummyDiv = $('<div style="position:absolute;top:-10000px;width:10000px;font-size:smaller">'
646
+ + labels.join("") + '<div style="clear:left"></div></div>').appendTo(placeholder);
647
+ axis.labelHeight = dummyDiv.height();
648
+ dummyDiv.remove();
649
+ }
650
+ }
651
+ }
652
+ else if (axis.labelWidth == null || axis.labelHeight == null) {
653
+ // calculate y label dimensions
654
+ for (i = 0; i < axis.ticks.length; ++i) {
655
+ l = axis.ticks[i].label;
656
+ if (l)
657
+ labels.push('<div class="tickLabel">' + l + '</div>');
658
+ }
659
+
660
+ if (labels.length > 0) {
661
+ var dummyDiv = $('<div style="position:absolute;top:-10000px;font-size:smaller">'
662
+ + labels.join("") + '</div>').appendTo(placeholder);
663
+ if (axis.labelWidth == null)
664
+ axis.labelWidth = dummyDiv.width();
665
+ if (axis.labelHeight == null)
666
+ axis.labelHeight = dummyDiv.find("div").height();
667
+ dummyDiv.remove();
668
+ }
669
+
670
+ }
671
+
672
+ if (axis.labelWidth == null)
673
+ axis.labelWidth = 0;
674
+ if (axis.labelHeight == null)
675
+ axis.labelHeight = 0;
676
+ }
677
+
678
+ function setGridSpacing() {
679
+ // get the most space needed around the grid for things
680
+ // that may stick out
681
+ var maxOutset = options.grid.borderWidth;
682
+ for (i = 0; i < series.length; ++i)
683
+ maxOutset = Math.max(maxOutset, 2 * (series[i].points.radius + series[i].points.lineWidth/2));
684
+
685
+ plotOffset.left = plotOffset.right = plotOffset.top = plotOffset.bottom = maxOutset;
686
+
687
+ var margin = options.grid.labelMargin + options.grid.borderWidth;
688
+
689
+ if (axes.xaxis.labelHeight > 0)
690
+ plotOffset.bottom = Math.max(maxOutset, axes.xaxis.labelHeight + margin);
691
+ if (axes.yaxis.labelWidth > 0)
692
+ plotOffset.left = Math.max(maxOutset, axes.yaxis.labelWidth + margin);
693
+ if (axes.x2axis.labelHeight > 0)
694
+ plotOffset.top = Math.max(maxOutset, axes.x2axis.labelHeight + margin);
695
+ if (axes.y2axis.labelWidth > 0)
696
+ plotOffset.right = Math.max(maxOutset, axes.y2axis.labelWidth + margin);
697
+
698
+ plotWidth = canvasWidth - plotOffset.left - plotOffset.right;
699
+ plotHeight = canvasHeight - plotOffset.bottom - plotOffset.top;
700
+ }
701
+
702
+ var axis;
703
+ for (axis in axes)
704
+ setRange(axes[axis], options[axis]);
705
+
706
+ if (options.grid.show) {
707
+ for (axis in axes) {
708
+ prepareTickGeneration(axes[axis], options[axis]);
709
+ setTicks(axes[axis], options[axis]);
710
+ measureLabels(axes[axis], options[axis]);
711
+ }
712
+
713
+ setGridSpacing();
714
+ }
715
+ else {
716
+ plotOffset.left = plotOffset.right = plotOffset.top = plotOffset.bottom = 0;
717
+ plotWidth = canvasWidth;
718
+ plotHeight = canvasHeight;
719
+ }
720
+
721
+ for (axis in axes)
722
+ setTransformationHelpers(axes[axis], options[axis]);
723
+
724
+ if (options.grid.show)
725
+ insertLabels();
726
+
727
+ insertLegend();
728
+ }
729
+
730
+ function setRange(axis, axisOptions) {
731
+ var min = +(axisOptions.min != null ? axisOptions.min : axis.datamin),
732
+ max = +(axisOptions.max != null ? axisOptions.max : axis.datamax),
733
+ delta = max - min;
734
+
735
+ if (delta == 0.0) {
736
+ // degenerate case
737
+ var widen = max == 0 ? 1 : 0.01;
738
+
739
+ if (axisOptions.min == null)
740
+ min -= widen;
741
+ // alway widen max if we couldn't widen min to ensure we
742
+ // don't fall into min == max which doesn't work
743
+ if (axisOptions.max == null || axisOptions.min != null)
744
+ max += widen;
745
+ }
746
+ else {
747
+ // consider autoscaling
748
+ var margin = axisOptions.autoscaleMargin;
749
+ if (margin != null) {
750
+ if (axisOptions.min == null) {
751
+ min -= delta * margin;
752
+ // make sure we don't go below zero if all values
753
+ // are positive
754
+ if (min < 0 && axis.datamin != null && axis.datamin >= 0)
755
+ min = 0;
756
+ }
757
+ if (axisOptions.max == null) {
758
+ max += delta * margin;
759
+ if (max > 0 && axis.datamax != null && axis.datamax <= 0)
760
+ max = 0;
761
+ }
762
+ }
763
+ }
764
+ axis.min = min;
765
+ axis.max = max;
766
+ }
767
+
768
+ function prepareTickGeneration(axis, axisOptions) {
769
+ // estimate number of ticks
770
+ var noTicks;
771
+ if (typeof axisOptions.ticks == "number" && axisOptions.ticks > 0)
772
+ noTicks = axisOptions.ticks;
773
+ else if (axis == axes.xaxis || axis == axes.x2axis)
774
+ // heuristic based on the model a*sqrt(x) fitted to
775
+ // some reasonable data points
776
+ noTicks = 0.3 * Math.sqrt(canvasWidth);
777
+ else
778
+ noTicks = 0.3 * Math.sqrt(canvasHeight);
779
+
780
+ var delta = (axis.max - axis.min) / noTicks,
781
+ size, generator, unit, formatter, i, magn, norm;
782
+
783
+ if (axisOptions.mode == "time") {
784
+ // pretty handling of time
785
+
786
+ // map of app. size of time units in milliseconds
787
+ var timeUnitSize = {
788
+ "second": 1000,
789
+ "minute": 60 * 1000,
790
+ "hour": 60 * 60 * 1000,
791
+ "day": 24 * 60 * 60 * 1000,
792
+ "month": 30 * 24 * 60 * 60 * 1000,
793
+ "year": 365.2425 * 24 * 60 * 60 * 1000
794
+ };
795
+
796
+
797
+ // the allowed tick sizes, after 1 year we use
798
+ // an integer algorithm
799
+ var spec = [
800
+ [1, "second"], [2, "second"], [5, "second"], [10, "second"],
801
+ [30, "second"],
802
+ [1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"],
803
+ [30, "minute"],
804
+ [1, "hour"], [2, "hour"], [4, "hour"],
805
+ [8, "hour"], [12, "hour"],
806
+ [1, "day"], [2, "day"], [3, "day"],
807
+ [0.25, "month"], [0.5, "month"], [1, "month"],
808
+ [2, "month"], [3, "month"], [6, "month"],
809
+ [1, "year"]
810
+ ];
811
+
812
+ var minSize = 0;
813
+ if (axisOptions.minTickSize != null) {
814
+ if (typeof axisOptions.tickSize == "number")
815
+ minSize = axisOptions.tickSize;
816
+ else
817
+ minSize = axisOptions.minTickSize[0] * timeUnitSize[axisOptions.minTickSize[1]];
818
+ }
819
+
820
+ for (i = 0; i < spec.length - 1; ++i)
821
+ if (delta < (spec[i][0] * timeUnitSize[spec[i][1]]
822
+ + spec[i + 1][0] * timeUnitSize[spec[i + 1][1]]) / 2
823
+ && spec[i][0] * timeUnitSize[spec[i][1]] >= minSize)
824
+ break;
825
+ size = spec[i][0];
826
+ unit = spec[i][1];
827
+
828
+ // special-case the possibility of several years
829
+ if (unit == "year") {
830
+ magn = Math.pow(10, Math.floor(Math.log(delta / timeUnitSize.year) / Math.LN10));
831
+ norm = (delta / timeUnitSize.year) / magn;
832
+ if (norm < 1.5)
833
+ size = 1;
834
+ else if (norm < 3)
835
+ size = 2;
836
+ else if (norm < 7.5)
837
+ size = 5;
838
+ else
839
+ size = 10;
840
+
841
+ size *= magn;
842
+ }
843
+
844
+ if (axisOptions.tickSize) {
845
+ size = axisOptions.tickSize[0];
846
+ unit = axisOptions.tickSize[1];
847
+ }
848
+
849
+ generator = function(axis) {
850
+ var ticks = [],
851
+ tickSize = axis.tickSize[0], unit = axis.tickSize[1],
852
+ d = new Date(axis.min);
853
+
854
+ var step = tickSize * timeUnitSize[unit];
855
+
856
+ if (unit == "second")
857
+ d.setUTCSeconds(floorInBase(d.getUTCSeconds(), tickSize));
858
+ if (unit == "minute")
859
+ d.setUTCMinutes(floorInBase(d.getUTCMinutes(), tickSize));
860
+ if (unit == "hour")
861
+ d.setUTCHours(floorInBase(d.getUTCHours(), tickSize));
862
+ if (unit == "month")
863
+ d.setUTCMonth(floorInBase(d.getUTCMonth(), tickSize));
864
+ if (unit == "year")
865
+ d.setUTCFullYear(floorInBase(d.getUTCFullYear(), tickSize));
866
+
867
+ // reset smaller components
868
+ d.setUTCMilliseconds(0);
869
+ if (step >= timeUnitSize.minute)
870
+ d.setUTCSeconds(0);
871
+ if (step >= timeUnitSize.hour)
872
+ d.setUTCMinutes(0);
873
+ if (step >= timeUnitSize.day)
874
+ d.setUTCHours(0);
875
+ if (step >= timeUnitSize.day * 4)
876
+ d.setUTCDate(1);
877
+ if (step >= timeUnitSize.year)
878
+ d.setUTCMonth(0);
879
+
880
+
881
+ var carry = 0, v = Number.NaN, prev;
882
+ do {
883
+ prev = v;
884
+ v = d.getTime();
885
+ ticks.push({ v: v, label: axis.tickFormatter(v, axis) });
886
+ if (unit == "month") {
887
+ if (tickSize < 1) {
888
+ // a bit complicated - we'll divide the month
889
+ // up but we need to take care of fractions
890
+ // so we don't end up in the middle of a day
891
+ d.setUTCDate(1);
892
+ var start = d.getTime();
893
+ d.setUTCMonth(d.getUTCMonth() + 1);
894
+ var end = d.getTime();
895
+ d.setTime(v + carry * timeUnitSize.hour + (end - start) * tickSize);
896
+ carry = d.getUTCHours();
897
+ d.setUTCHours(0);
898
+ }
899
+ else
900
+ d.setUTCMonth(d.getUTCMonth() + tickSize);
901
+ }
902
+ else if (unit == "year") {
903
+ d.setUTCFullYear(d.getUTCFullYear() + tickSize);
904
+ }
905
+ else
906
+ d.setTime(v + step);
907
+ } while (v < axis.max && v != prev);
908
+
909
+ return ticks;
910
+ };
911
+
912
+ formatter = function (v, axis) {
913
+ var d = new Date(v);
914
+
915
+ // first check global format
916
+ if (axisOptions.timeformat != null)
917
+ return $.plot.formatDate(d, axisOptions.timeformat, axisOptions.monthNames);
918
+
919
+ var t = axis.tickSize[0] * timeUnitSize[axis.tickSize[1]];
920
+ var span = axis.max - axis.min;
921
+ var suffix = (axisOptions.twelveHourClock) ? " %p" : "";
922
+
923
+ if (t < timeUnitSize.minute)
924
+ fmt = "%h:%M:%S" + suffix;
925
+ else if (t < timeUnitSize.day) {
926
+ if (span < 2 * timeUnitSize.day)
927
+ fmt = "%h:%M" + suffix;
928
+ else
929
+ fmt = "%b %d %h:%M" + suffix;
930
+ }
931
+ else if (t < timeUnitSize.month)
932
+ fmt = "%b %d";
933
+ else if (t < timeUnitSize.year) {
934
+ if (span < timeUnitSize.year)
935
+ fmt = "%b";
936
+ else
937
+ fmt = "%b %y";
938
+ }
939
+ else
940
+ fmt = "%y";
941
+
942
+ return $.plot.formatDate(d, fmt, axisOptions.monthNames);
943
+ };
944
+ }
945
+ else {
946
+ // pretty rounding of base-10 numbers
947
+ var maxDec = axisOptions.tickDecimals;
948
+ var dec = -Math.floor(Math.log(delta) / Math.LN10);
949
+ if (maxDec != null && dec > maxDec)
950
+ dec = maxDec;
951
+
952
+ magn = Math.pow(10, -dec);
953
+ norm = delta / magn; // norm is between 1.0 and 10.0
954
+
955
+ if (norm < 1.5)
956
+ size = 1;
957
+ else if (norm < 3) {
958
+ size = 2;
959
+ // special case for 2.5, requires an extra decimal
960
+ if (norm > 2.25 && (maxDec == null || dec + 1 <= maxDec)) {
961
+ size = 2.5;
962
+ ++dec;
963
+ }
964
+ }
965
+ else if (norm < 7.5)
966
+ size = 5;
967
+ else
968
+ size = 10;
969
+
970
+ size *= magn;
971
+
972
+ if (axisOptions.minTickSize != null && size < axisOptions.minTickSize)
973
+ size = axisOptions.minTickSize;
974
+
975
+ if (axisOptions.tickSize != null)
976
+ size = axisOptions.tickSize;
977
+
978
+ axis.tickDecimals = Math.max(0, (maxDec != null) ? maxDec : dec);
979
+
980
+ generator = function (axis) {
981
+ var ticks = [];
982
+
983
+ // spew out all possible ticks
984
+ var start = floorInBase(axis.min, axis.tickSize),
985
+ i = 0, v = Number.NaN, prev;
986
+ do {
987
+ prev = v;
988
+ v = start + i * axis.tickSize;
989
+ ticks.push({ v: v, label: axis.tickFormatter(v, axis) });
990
+ ++i;
991
+ } while (v < axis.max && v != prev);
992
+ return ticks;
993
+ };
994
+
995
+ formatter = function (v, axis) {
996
+ return v.toFixed(axis.tickDecimals);
997
+ };
998
+ }
999
+
1000
+ axis.tickSize = unit ? [size, unit] : size;
1001
+ axis.tickGenerator = generator;
1002
+ if ($.isFunction(axisOptions.tickFormatter))
1003
+ axis.tickFormatter = function (v, axis) { return "" + axisOptions.tickFormatter(v, axis); };
1004
+ else
1005
+ axis.tickFormatter = formatter;
1006
+ }
1007
+
1008
+ function setTicks(axis, axisOptions) {
1009
+ axis.ticks = [];
1010
+
1011
+ if (!axis.used)
1012
+ return;
1013
+
1014
+ if (axisOptions.ticks == null)
1015
+ axis.ticks = axis.tickGenerator(axis);
1016
+ else if (typeof axisOptions.ticks == "number") {
1017
+ if (axisOptions.ticks > 0)
1018
+ axis.ticks = axis.tickGenerator(axis);
1019
+ }
1020
+ else if (axisOptions.ticks) {
1021
+ var ticks = axisOptions.ticks;
1022
+
1023
+ if ($.isFunction(ticks))
1024
+ // generate the ticks
1025
+ ticks = ticks({ min: axis.min, max: axis.max });
1026
+
1027
+ // clean up the user-supplied ticks, copy them over
1028
+ var i, v;
1029
+ for (i = 0; i < ticks.length; ++i) {
1030
+ var label = null;
1031
+ var t = ticks[i];
1032
+ if (typeof t == "object") {
1033
+ v = t[0];
1034
+ if (t.length > 1)
1035
+ label = t[1];
1036
+ }
1037
+ else
1038
+ v = t;
1039
+ if (label == null)
1040
+ label = axis.tickFormatter(v, axis);
1041
+ axis.ticks[i] = { v: v, label: label };
1042
+ }
1043
+ }
1044
+
1045
+ if (axisOptions.autoscaleMargin != null && axis.ticks.length > 0) {
1046
+ // snap to ticks
1047
+ if (axisOptions.min == null)
1048
+ axis.min = Math.min(axis.min, axis.ticks[0].v);
1049
+ if (axisOptions.max == null && axis.ticks.length > 1)
1050
+ axis.max = Math.max(axis.max, axis.ticks[axis.ticks.length - 1].v);
1051
+ }
1052
+ }
1053
+
1054
+ function draw() {
1055
+ ctx.clearRect(0, 0, canvasWidth, canvasHeight);
1056
+
1057
+ var grid = options.grid;
1058
+
1059
+ if (grid.show && !grid.aboveData)
1060
+ drawGrid();
1061
+
1062
+ for (var i = series.length-1; i >= 0; --i)
1063
+ drawSeries(series[i]);
1064
+
1065
+ executeHooks(hooks.draw, [ctx]);
1066
+
1067
+ if (grid.show && grid.aboveData)
1068
+ drawGrid();
1069
+ }
1070
+
1071
+ function extractRange(ranges, coord) {
1072
+ var firstAxis = coord + "axis",
1073
+ secondaryAxis = coord + "2axis",
1074
+ axis, from, to, reverse;
1075
+
1076
+ if (ranges[firstAxis]) {
1077
+ axis = axes[firstAxis];
1078
+ from = ranges[firstAxis].from;
1079
+ to = ranges[firstAxis].to;
1080
+ }
1081
+ else if (ranges[secondaryAxis]) {
1082
+ axis = axes[secondaryAxis];
1083
+ from = ranges[secondaryAxis].from;
1084
+ to = ranges[secondaryAxis].to;
1085
+ }
1086
+ else {
1087
+ // backwards-compat stuff - to be removed in future
1088
+ axis = axes[firstAxis];
1089
+ from = ranges[coord + "1"];
1090
+ to = ranges[coord + "2"];
1091
+ }
1092
+
1093
+ // auto-reverse as an added bonus
1094
+ if (from != null && to != null && from > to)
1095
+ return { from: to, to: from, axis: axis };
1096
+
1097
+ return { from: from, to: to, axis: axis };
1098
+ }
1099
+
1100
+ function drawGrid() {
1101
+ var i;
1102
+
1103
+ ctx.save();
1104
+ ctx.translate(plotOffset.left, plotOffset.top);
1105
+
1106
+ // draw background, if any
1107
+ if (options.grid.backgroundColor) {
1108
+ ctx.fillStyle = getColorOrGradient(options.grid.backgroundColor, plotHeight, 0, "rgba(255, 255, 255, 0)");
1109
+ ctx.fillRect(0, 0, plotWidth, plotHeight);
1110
+ }
1111
+
1112
+ // draw markings
1113
+ var markings = options.grid.markings;
1114
+ if (markings) {
1115
+ if ($.isFunction(markings))
1116
+ // xmin etc. are backwards-compatible, to be removed in future
1117
+ markings = markings({ xmin: axes.xaxis.min, xmax: axes.xaxis.max, ymin: axes.yaxis.min, ymax: axes.yaxis.max, xaxis: axes.xaxis, yaxis: axes.yaxis, x2axis: axes.x2axis, y2axis: axes.y2axis });
1118
+
1119
+ for (i = 0; i < markings.length; ++i) {
1120
+ var m = markings[i],
1121
+ xrange = extractRange(m, "x"),
1122
+ yrange = extractRange(m, "y");
1123
+
1124
+ // fill in missing
1125
+ if (xrange.from == null)
1126
+ xrange.from = xrange.axis.min;
1127
+ if (xrange.to == null)
1128
+ xrange.to = xrange.axis.max;
1129
+ if (yrange.from == null)
1130
+ yrange.from = yrange.axis.min;
1131
+ if (yrange.to == null)
1132
+ yrange.to = yrange.axis.max;
1133
+
1134
+ // clip
1135
+ if (xrange.to < xrange.axis.min || xrange.from > xrange.axis.max ||
1136
+ yrange.to < yrange.axis.min || yrange.from > yrange.axis.max)
1137
+ continue;
1138
+
1139
+ xrange.from = Math.max(xrange.from, xrange.axis.min);
1140
+ xrange.to = Math.min(xrange.to, xrange.axis.max);
1141
+ yrange.from = Math.max(yrange.from, yrange.axis.min);
1142
+ yrange.to = Math.min(yrange.to, yrange.axis.max);
1143
+
1144
+ if (xrange.from == xrange.to && yrange.from == yrange.to)
1145
+ continue;
1146
+
1147
+ // then draw
1148
+ xrange.from = xrange.axis.p2c(xrange.from);
1149
+ xrange.to = xrange.axis.p2c(xrange.to);
1150
+ yrange.from = yrange.axis.p2c(yrange.from);
1151
+ yrange.to = yrange.axis.p2c(yrange.to);
1152
+
1153
+ if (xrange.from == xrange.to || yrange.from == yrange.to) {
1154
+ // draw line
1155
+ ctx.beginPath();
1156
+ ctx.strokeStyle = m.color || options.grid.markingsColor;
1157
+ ctx.lineWidth = m.lineWidth || options.grid.markingsLineWidth;
1158
+ //ctx.moveTo(Math.floor(xrange.from), yrange.from);
1159
+ //ctx.lineTo(Math.floor(xrange.to), yrange.to);
1160
+ ctx.moveTo(xrange.from, yrange.from);
1161
+ ctx.lineTo(xrange.to, yrange.to);
1162
+ ctx.stroke();
1163
+ }
1164
+ else {
1165
+ // fill area
1166
+ ctx.fillStyle = m.color || options.grid.markingsColor;
1167
+ ctx.fillRect(xrange.from, yrange.to,
1168
+ xrange.to - xrange.from,
1169
+ yrange.from - yrange.to);
1170
+ }
1171
+ }
1172
+ }
1173
+
1174
+ // draw the inner grid
1175
+ ctx.lineWidth = 1;
1176
+ ctx.strokeStyle = options.grid.tickColor;
1177
+ ctx.beginPath();
1178
+ var v, axis = axes.xaxis,
1179
+ bw = options.grid.borderWidth;
1180
+ for (i = 0; i < axis.ticks.length; ++i) {
1181
+ v = axis.ticks[i].v;
1182
+ if (v < axis.min || v > axis.max ||
1183
+ (bw > 0 && (v == axis.min || v == axis.max)))
1184
+ continue; // skip those lying on the axes if we got a border
1185
+
1186
+ ctx.moveTo(Math.floor(axis.p2c(v)) + ctx.lineWidth/2, 0);
1187
+ ctx.lineTo(Math.floor(axis.p2c(v)) + ctx.lineWidth/2, plotHeight);
1188
+ }
1189
+
1190
+ axis = axes.yaxis;
1191
+ for (i = 0; i < axis.ticks.length; ++i) {
1192
+ v = axis.ticks[i].v;
1193
+ if (v < axis.min || v > axis.max ||
1194
+ (bw > 0 && (v == axis.min || v == axis.max)))
1195
+ continue;
1196
+
1197
+ ctx.moveTo(0, Math.floor(axis.p2c(v)) + ctx.lineWidth/2);
1198
+ ctx.lineTo(plotWidth, Math.floor(axis.p2c(v)) + ctx.lineWidth/2);
1199
+ }
1200
+
1201
+ axis = axes.x2axis;
1202
+ for (i = 0; i < axis.ticks.length; ++i) {
1203
+ v = axis.ticks[i].v;
1204
+ if (v < axis.min || v > axis.max ||
1205
+ (bw > 0 && (v == axis.min || v == axis.max)))
1206
+ continue;
1207
+
1208
+ ctx.moveTo(Math.floor(axis.p2c(v)) + ctx.lineWidth/2, -5);
1209
+ ctx.lineTo(Math.floor(axis.p2c(v)) + ctx.lineWidth/2, 5);
1210
+ }
1211
+
1212
+ axis = axes.y2axis;
1213
+ for (i = 0; i < axis.ticks.length; ++i) {
1214
+ v = axis.ticks[i].v;
1215
+ if (v < axis.min || v > axis.max ||
1216
+ (bw > 0 && (v == axis.min || v == axis.max)))
1217
+ continue;
1218
+
1219
+ ctx.moveTo(plotWidth-5, Math.floor(axis.p2c(v)) + ctx.lineWidth/2);
1220
+ ctx.lineTo(plotWidth+5, Math.floor(axis.p2c(v)) + ctx.lineWidth/2);
1221
+ }
1222
+
1223
+ ctx.stroke();
1224
+
1225
+ if (options.grid.borderWidth) {
1226
+ // draw border
1227
+ ctx.lineWidth = bw;
1228
+ ctx.strokeStyle = options.grid.borderColor;
1229
+ ctx.strokeRect(-bw/2, -bw/2, plotWidth + bw, plotHeight + bw);
1230
+ }
1231
+
1232
+ ctx.restore();
1233
+ }
1234
+
1235
+ function insertLabels() {
1236
+ placeholder.find(".tickLabels").remove();
1237
+
1238
+ var html = ['<div class="tickLabels" style="font-size:smaller;color:' + options.grid.color + '">'];
1239
+
1240
+ function addLabels(axis, labelGenerator) {
1241
+ for (var i = 0; i < axis.ticks.length; ++i) {
1242
+ var tick = axis.ticks[i];
1243
+ if (!tick.label || tick.v < axis.min || tick.v > axis.max)
1244
+ continue;
1245
+ html.push(labelGenerator(tick, axis));
1246
+ }
1247
+ }
1248
+
1249
+ var margin = options.grid.labelMargin + options.grid.borderWidth;
1250
+
1251
+ addLabels(axes.xaxis, function (tick, axis) {
1252
+ return '<div style="position:absolute;top:' + (plotOffset.top + plotHeight + margin) + 'px;left:' + Math.round(plotOffset.left + axis.p2c(tick.v) - axis.labelWidth/2) + 'px;width:' + axis.labelWidth + 'px;text-align:center" class="tickLabel">' + tick.label + "</div>";
1253
+ });
1254
+
1255
+
1256
+ addLabels(axes.yaxis, function (tick, axis) {
1257
+ return '<div style="position:absolute;top:' + Math.round(plotOffset.top + axis.p2c(tick.v) - axis.labelHeight/2) + 'px;right:' + (plotOffset.right + plotWidth + margin) + 'px;width:' + axis.labelWidth + 'px;text-align:right" class="tickLabel">' + tick.label + "</div>";
1258
+ });
1259
+
1260
+ addLabels(axes.x2axis, function (tick, axis) {
1261
+ return '<div style="position:absolute;bottom:' + (plotOffset.bottom + plotHeight + margin) + 'px;left:' + Math.round(plotOffset.left + axis.p2c(tick.v) - axis.labelWidth/2) + 'px;width:' + axis.labelWidth + 'px;text-align:center" class="tickLabel">' + tick.label + "</div>";
1262
+ });
1263
+
1264
+ addLabels(axes.y2axis, function (tick, axis) {
1265
+ return '<div style="position:absolute;top:' + Math.round(plotOffset.top + axis.p2c(tick.v) - axis.labelHeight/2) + 'px;left:' + (plotOffset.left + plotWidth + margin) +'px;width:' + axis.labelWidth + 'px;text-align:left" class="tickLabel">' + tick.label + "</div>";
1266
+ });
1267
+
1268
+ html.push('</div>');
1269
+
1270
+ placeholder.append(html.join(""));
1271
+ }
1272
+
1273
+ function drawSeries(series) {
1274
+ if (series.lines.show)
1275
+ drawSeriesLines(series);
1276
+ if (series.bars.show)
1277
+ drawSeriesBars(series);
1278
+ if (series.points.show)
1279
+ drawSeriesPoints(series);
1280
+ }
1281
+
1282
+ function drawSeriesLines(series) {
1283
+ function plotLine(datapoints, xoffset, yoffset, axisx, axisy) {
1284
+ var points = datapoints.points,
1285
+ ps = datapoints.pointsize,
1286
+ prevx = null, prevy = null;
1287
+
1288
+ ctx.beginPath();
1289
+ for (var i = ps; i < points.length; i += ps) {
1290
+ var x1 = points[i - ps], y1 = points[i - ps + 1],
1291
+ x2 = points[i], y2 = points[i + 1];
1292
+
1293
+ if (x1 == null || x2 == null)
1294
+ continue;
1295
+
1296
+ // clip with ymin
1297
+ if (y1 <= y2 && y1 < axisy.min) {
1298
+ if (y2 < axisy.min)
1299
+ continue; // line segment is outside
1300
+ // compute new intersection point
1301
+ x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
1302
+ y1 = axisy.min;
1303
+ }
1304
+ else if (y2 <= y1 && y2 < axisy.min) {
1305
+ if (y1 < axisy.min)
1306
+ continue;
1307
+ x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
1308
+ y2 = axisy.min;
1309
+ }
1310
+
1311
+ // clip with ymax
1312
+ if (y1 >= y2 && y1 > axisy.max) {
1313
+ if (y2 > axisy.max)
1314
+ continue;
1315
+ x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
1316
+ y1 = axisy.max;
1317
+ }
1318
+ else if (y2 >= y1 && y2 > axisy.max) {
1319
+ if (y1 > axisy.max)
1320
+ continue;
1321
+ x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
1322
+ y2 = axisy.max;
1323
+ }
1324
+
1325
+ // clip with xmin
1326
+ if (x1 <= x2 && x1 < axisx.min) {
1327
+ if (x2 < axisx.min)
1328
+ continue;
1329
+ y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
1330
+ x1 = axisx.min;
1331
+ }
1332
+ else if (x2 <= x1 && x2 < axisx.min) {
1333
+ if (x1 < axisx.min)
1334
+ continue;
1335
+ y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
1336
+ x2 = axisx.min;
1337
+ }
1338
+
1339
+ // clip with xmax
1340
+ if (x1 >= x2 && x1 > axisx.max) {
1341
+ if (x2 > axisx.max)
1342
+ continue;
1343
+ y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
1344
+ x1 = axisx.max;
1345
+ }
1346
+ else if (x2 >= x1 && x2 > axisx.max) {
1347
+ if (x1 > axisx.max)
1348
+ continue;
1349
+ y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
1350
+ x2 = axisx.max;
1351
+ }
1352
+
1353
+ if (x1 != prevx || y1 != prevy)
1354
+ ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset);
1355
+
1356
+ prevx = x2;
1357
+ prevy = y2;
1358
+ ctx.lineTo(axisx.p2c(x2) + xoffset, axisy.p2c(y2) + yoffset);
1359
+ }
1360
+ ctx.stroke();
1361
+ }
1362
+
1363
+ function plotLineArea(datapoints, axisx, axisy) {
1364
+ var points = datapoints.points,
1365
+ ps = datapoints.pointsize,
1366
+ bottom = Math.min(Math.max(0, axisy.min), axisy.max),
1367
+ top, lastX = 0, areaOpen = false;
1368
+
1369
+ for (var i = ps; i < points.length; i += ps) {
1370
+ var x1 = points[i - ps], y1 = points[i - ps + 1],
1371
+ x2 = points[i], y2 = points[i + 1];
1372
+
1373
+ if (areaOpen && x1 != null && x2 == null) {
1374
+ // close area
1375
+ ctx.lineTo(axisx.p2c(lastX), axisy.p2c(bottom));
1376
+ ctx.fill();
1377
+ areaOpen = false;
1378
+ continue;
1379
+ }
1380
+
1381
+ if (x1 == null || x2 == null)
1382
+ continue;
1383
+
1384
+ // clip x values
1385
+
1386
+ // clip with xmin
1387
+ if (x1 <= x2 && x1 < axisx.min) {
1388
+ if (x2 < axisx.min)
1389
+ continue;
1390
+ y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
1391
+ x1 = axisx.min;
1392
+ }
1393
+ else if (x2 <= x1 && x2 < axisx.min) {
1394
+ if (x1 < axisx.min)
1395
+ continue;
1396
+ y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
1397
+ x2 = axisx.min;
1398
+ }
1399
+
1400
+ // clip with xmax
1401
+ if (x1 >= x2 && x1 > axisx.max) {
1402
+ if (x2 > axisx.max)
1403
+ continue;
1404
+ y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
1405
+ x1 = axisx.max;
1406
+ }
1407
+ else if (x2 >= x1 && x2 > axisx.max) {
1408
+ if (x1 > axisx.max)
1409
+ continue;
1410
+ y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
1411
+ x2 = axisx.max;
1412
+ }
1413
+
1414
+ if (!areaOpen) {
1415
+ // open area
1416
+ ctx.beginPath();
1417
+ ctx.moveTo(axisx.p2c(x1), axisy.p2c(bottom));
1418
+ areaOpen = true;
1419
+ }
1420
+
1421
+ // now first check the case where both is outside
1422
+ if (y1 >= axisy.max && y2 >= axisy.max) {
1423
+ ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.max));
1424
+ ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.max));
1425
+ lastX = x2;
1426
+ continue;
1427
+ }
1428
+ else if (y1 <= axisy.min && y2 <= axisy.min) {
1429
+ ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.min));
1430
+ ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.min));
1431
+ lastX = x2;
1432
+ continue;
1433
+ }
1434
+
1435
+ // else it's a bit more complicated, there might
1436
+ // be two rectangles and two triangles we need to fill
1437
+ // in; to find these keep track of the current x values
1438
+ var x1old = x1, x2old = x2;
1439
+
1440
+ // and clip the y values, without shortcutting
1441
+
1442
+ // clip with ymin
1443
+ if (y1 <= y2 && y1 < axisy.min && y2 >= axisy.min) {
1444
+ x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
1445
+ y1 = axisy.min;
1446
+ }
1447
+ else if (y2 <= y1 && y2 < axisy.min && y1 >= axisy.min) {
1448
+ x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
1449
+ y2 = axisy.min;
1450
+ }
1451
+
1452
+ // clip with ymax
1453
+ if (y1 >= y2 && y1 > axisy.max && y2 <= axisy.max) {
1454
+ x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
1455
+ y1 = axisy.max;
1456
+ }
1457
+ else if (y2 >= y1 && y2 > axisy.max && y1 <= axisy.max) {
1458
+ x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
1459
+ y2 = axisy.max;
1460
+ }
1461
+
1462
+
1463
+ // if the x value was changed we got a rectangle
1464
+ // to fill
1465
+ if (x1 != x1old) {
1466
+ if (y1 <= axisy.min)
1467
+ top = axisy.min;
1468
+ else
1469
+ top = axisy.max;
1470
+
1471
+ ctx.lineTo(axisx.p2c(x1old), axisy.p2c(top));
1472
+ ctx.lineTo(axisx.p2c(x1), axisy.p2c(top));
1473
+ }
1474
+
1475
+ // fill the triangles
1476
+ ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1));
1477
+ ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2));
1478
+
1479
+ // fill the other rectangle if it's there
1480
+ if (x2 != x2old) {
1481
+ if (y2 <= axisy.min)
1482
+ top = axisy.min;
1483
+ else
1484
+ top = axisy.max;
1485
+
1486
+ ctx.lineTo(axisx.p2c(x2), axisy.p2c(top));
1487
+ ctx.lineTo(axisx.p2c(x2old), axisy.p2c(top));
1488
+ }
1489
+
1490
+ lastX = Math.max(x2, x2old);
1491
+ }
1492
+
1493
+ if (areaOpen) {
1494
+ ctx.lineTo(axisx.p2c(lastX), axisy.p2c(bottom));
1495
+ ctx.fill();
1496
+ }
1497
+ }
1498
+
1499
+ ctx.save();
1500
+ ctx.translate(plotOffset.left, plotOffset.top);
1501
+ ctx.lineJoin = "round";
1502
+
1503
+ var lw = series.lines.lineWidth,
1504
+ sw = series.shadowSize;
1505
+ // FIXME: consider another form of shadow when filling is turned on
1506
+ if (lw > 0 && sw > 0) {
1507
+ // draw shadow as a thick and thin line with transparency
1508
+ ctx.lineWidth = sw;
1509
+ ctx.strokeStyle = "rgba(0,0,0,0.1)";
1510
+ // position shadow at angle from the mid of line
1511
+ var angle = Math.PI/18;
1512
+ plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/2), Math.cos(angle) * (lw/2 + sw/2), series.xaxis, series.yaxis);
1513
+ ctx.lineWidth = sw/2;
1514
+ plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/4), Math.cos(angle) * (lw/2 + sw/4), series.xaxis, series.yaxis);
1515
+ }
1516
+
1517
+ ctx.lineWidth = lw;
1518
+ ctx.strokeStyle = series.color;
1519
+ var fillStyle = getFillStyle(series.lines, series.color, 0, plotHeight);
1520
+ if (fillStyle) {
1521
+ ctx.fillStyle = fillStyle;
1522
+ plotLineArea(series.datapoints, series.xaxis, series.yaxis);
1523
+ }
1524
+
1525
+ if (lw > 0)
1526
+ plotLine(series.datapoints, 0, 0, series.xaxis, series.yaxis);
1527
+ ctx.restore();
1528
+ }
1529
+
1530
+ function drawSeriesPoints(series) {
1531
+ function plotPoints(datapoints, radius, fillStyle, offset, circumference, axisx, axisy) {
1532
+ var points = datapoints.points, ps = datapoints.pointsize;
1533
+
1534
+ for (var i = 0; i < points.length; i += ps) {
1535
+ var x = points[i], y = points[i + 1];
1536
+ if (x == null || x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max)
1537
+ continue;
1538
+
1539
+ ctx.beginPath();
1540
+ ctx.arc(axisx.p2c(x), axisy.p2c(y) + offset, radius, 0, circumference, false);
1541
+ if (fillStyle) {
1542
+ ctx.fillStyle = fillStyle;
1543
+ ctx.fill();
1544
+ }
1545
+ ctx.stroke();
1546
+ }
1547
+ }
1548
+
1549
+ ctx.save();
1550
+ ctx.translate(plotOffset.left, plotOffset.top);
1551
+
1552
+ var lw = series.lines.lineWidth,
1553
+ sw = series.shadowSize,
1554
+ radius = series.points.radius;
1555
+ if (lw > 0 && sw > 0) {
1556
+ // draw shadow in two steps
1557
+ var w = sw / 2;
1558
+ ctx.lineWidth = w;
1559
+ ctx.strokeStyle = "rgba(0,0,0,0.1)";
1560
+ plotPoints(series.datapoints, radius, null, w + w/2, Math.PI,
1561
+ series.xaxis, series.yaxis);
1562
+
1563
+ ctx.strokeStyle = "rgba(0,0,0,0.2)";
1564
+ plotPoints(series.datapoints, radius, null, w/2, Math.PI,
1565
+ series.xaxis, series.yaxis);
1566
+ }
1567
+
1568
+ ctx.lineWidth = lw;
1569
+ ctx.strokeStyle = series.color;
1570
+ plotPoints(series.datapoints, radius,
1571
+ getFillStyle(series.points, series.color), 0, 2 * Math.PI,
1572
+ series.xaxis, series.yaxis);
1573
+ ctx.restore();
1574
+ }
1575
+
1576
+ function drawBar(x, y, b, barLeft, barRight, offset, fillStyleCallback, axisx, axisy, c, horizontal) {
1577
+ var left, right, bottom, top,
1578
+ drawLeft, drawRight, drawTop, drawBottom,
1579
+ tmp;
1580
+
1581
+ if (horizontal) {
1582
+ drawBottom = drawRight = drawTop = true;
1583
+ drawLeft = false;
1584
+ left = b;
1585
+ right = x;
1586
+ top = y + barLeft;
1587
+ bottom = y + barRight;
1588
+
1589
+ // account for negative bars
1590
+ if (right < left) {
1591
+ tmp = right;
1592
+ right = left;
1593
+ left = tmp;
1594
+ drawLeft = true;
1595
+ drawRight = false;
1596
+ }
1597
+ }
1598
+ else {
1599
+ drawLeft = drawRight = drawTop = true;
1600
+ drawBottom = false;
1601
+ left = x + barLeft;
1602
+ right = x + barRight;
1603
+ bottom = b;
1604
+ top = y;
1605
+
1606
+ // account for negative bars
1607
+ if (top < bottom) {
1608
+ tmp = top;
1609
+ top = bottom;
1610
+ bottom = tmp;
1611
+ drawBottom = true;
1612
+ drawTop = false;
1613
+ }
1614
+ }
1615
+
1616
+ // clip
1617
+ if (right < axisx.min || left > axisx.max ||
1618
+ top < axisy.min || bottom > axisy.max)
1619
+ return;
1620
+
1621
+ if (left < axisx.min) {
1622
+ left = axisx.min;
1623
+ drawLeft = false;
1624
+ }
1625
+
1626
+ if (right > axisx.max) {
1627
+ right = axisx.max;
1628
+ drawRight = false;
1629
+ }
1630
+
1631
+ if (bottom < axisy.min) {
1632
+ bottom = axisy.min;
1633
+ drawBottom = false;
1634
+ }
1635
+
1636
+ if (top > axisy.max) {
1637
+ top = axisy.max;
1638
+ drawTop = false;
1639
+ }
1640
+
1641
+ left = axisx.p2c(left);
1642
+ bottom = axisy.p2c(bottom);
1643
+ right = axisx.p2c(right);
1644
+ top = axisy.p2c(top);
1645
+
1646
+ // fill the bar
1647
+ if (fillStyleCallback) {
1648
+ c.beginPath();
1649
+ c.moveTo(left, bottom);
1650
+ c.lineTo(left, top);
1651
+ c.lineTo(right, top);
1652
+ c.lineTo(right, bottom);
1653
+ c.fillStyle = fillStyleCallback(bottom, top);
1654
+ c.fill();
1655
+ }
1656
+
1657
+ // draw outline
1658
+ if (drawLeft || drawRight || drawTop || drawBottom) {
1659
+ c.beginPath();
1660
+
1661
+ // FIXME: inline moveTo is buggy with excanvas
1662
+ c.moveTo(left, bottom + offset);
1663
+ if (drawLeft)
1664
+ c.lineTo(left, top + offset);
1665
+ else
1666
+ c.moveTo(left, top + offset);
1667
+ if (drawTop)
1668
+ c.lineTo(right, top + offset);
1669
+ else
1670
+ c.moveTo(right, top + offset);
1671
+ if (drawRight)
1672
+ c.lineTo(right, bottom + offset);
1673
+ else
1674
+ c.moveTo(right, bottom + offset);
1675
+ if (drawBottom)
1676
+ c.lineTo(left, bottom + offset);
1677
+ else
1678
+ c.moveTo(left, bottom + offset);
1679
+ c.stroke();
1680
+ }
1681
+ }
1682
+
1683
+ function drawSeriesBars(series) {
1684
+ function plotBars(datapoints, barLeft, barRight, offset, fillStyleCallback, axisx, axisy) {
1685
+ var points = datapoints.points, ps = datapoints.pointsize;
1686
+
1687
+ for (var i = 0; i < points.length; i += ps) {
1688
+ if (points[i] == null)
1689
+ continue;
1690
+ drawBar(points[i], points[i + 1], points[i + 2], barLeft, barRight, offset, fillStyleCallback, axisx, axisy, ctx, series.bars.horizontal);
1691
+ }
1692
+ }
1693
+
1694
+ ctx.save();
1695
+ ctx.translate(plotOffset.left, plotOffset.top);
1696
+
1697
+ // FIXME: figure out a way to add shadows (for instance along the right edge)
1698
+ ctx.lineWidth = series.bars.lineWidth;
1699
+ ctx.strokeStyle = series.color;
1700
+ var barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth/2;
1701
+ var fillStyleCallback = series.bars.fill ? function (bottom, top) { return getFillStyle(series.bars, series.color, bottom, top); } : null;
1702
+ plotBars(series.datapoints, barLeft, barLeft + series.bars.barWidth, 0, fillStyleCallback, series.xaxis, series.yaxis);
1703
+ ctx.restore();
1704
+ }
1705
+
1706
+ function getFillStyle(filloptions, seriesColor, bottom, top) {
1707
+ var fill = filloptions.fill;
1708
+ if (!fill)
1709
+ return null;
1710
+
1711
+ if (filloptions.fillColor)
1712
+ return getColorOrGradient(filloptions.fillColor, bottom, top, seriesColor);
1713
+
1714
+ var c = $.color.parse(seriesColor);
1715
+ c.a = typeof fill == "number" ? fill : 0.4;
1716
+ c.normalize();
1717
+ return c.toString();
1718
+ }
1719
+
1720
+ function insertLegend() {
1721
+ placeholder.find(".legend").remove();
1722
+
1723
+ if (!options.legend.show)
1724
+ return;
1725
+
1726
+ var fragments = [], rowStarted = false,
1727
+ lf = options.legend.labelFormatter, s, label;
1728
+ for (i = 0; i < series.length; ++i) {
1729
+ s = series[i];
1730
+ label = s.label;
1731
+ if (!label)
1732
+ continue;
1733
+
1734
+ if (i % options.legend.noColumns == 0) {
1735
+ if (rowStarted)
1736
+ fragments.push('</tr>');
1737
+ fragments.push('<tr>');
1738
+ rowStarted = true;
1739
+ }
1740
+
1741
+ if (lf)
1742
+ label = lf(label, s);
1743
+
1744
+ fragments.push(
1745
+ '<td class="legendColorBox"><div style="border:1px solid ' + options.legend.labelBoxBorderColor + ';padding:1px"><div style="width:4px;height:0;border:5px solid ' + s.color + ';overflow:hidden"></div></div></td>' +
1746
+ '<td class="legendLabel">' + label + '</td>');
1747
+ }
1748
+ if (rowStarted)
1749
+ fragments.push('</tr>');
1750
+
1751
+ if (fragments.length == 0)
1752
+ return;
1753
+
1754
+ var table = '<table style="font-size:smaller;color:' + options.grid.color + '">' + fragments.join("") + '</table>';
1755
+ if (options.legend.container != null)
1756
+ $(options.legend.container).html(table);
1757
+ else {
1758
+ var pos = "",
1759
+ p = options.legend.position,
1760
+ m = options.legend.margin;
1761
+ if (m[0] == null)
1762
+ m = [m, m];
1763
+ if (p.charAt(0) == "n")
1764
+ pos += 'top:' + (m[1] + plotOffset.top) + 'px;';
1765
+ else if (p.charAt(0) == "s")
1766
+ pos += 'bottom:' + (m[1] + plotOffset.bottom) + 'px;';
1767
+ if (p.charAt(1) == "e")
1768
+ pos += 'right:' + (m[0] + plotOffset.right) + 'px;';
1769
+ else if (p.charAt(1) == "w")
1770
+ pos += 'left:' + (m[0] + plotOffset.left) + 'px;';
1771
+ var legend = $('<div class="legend">' + table.replace('style="', 'style="position:absolute;' + pos +';') + '</div>').appendTo(placeholder);
1772
+ if (options.legend.backgroundOpacity != 0.0) {
1773
+ // put in the transparent background
1774
+ // separately to avoid blended labels and
1775
+ // label boxes
1776
+ var c = options.legend.backgroundColor;
1777
+ if (c == null) {
1778
+ c = options.grid.backgroundColor;
1779
+ if (c && typeof c == "string")
1780
+ c = $.color.parse(c);
1781
+ else
1782
+ c = $.color.extract(legend, 'background-color');
1783
+ c.a = 1;
1784
+ c = c.toString();
1785
+ }
1786
+ var div = legend.children();
1787
+ $('<div style="position:absolute;width:' + div.width() + 'px;height:' + div.height() + 'px;' + pos +'background-color:' + c + ';"> </div>').prependTo(legend).css('opacity', options.legend.backgroundOpacity);
1788
+ }
1789
+ }
1790
+ }
1791
+
1792
+
1793
+ // interactive features
1794
+
1795
+ var highlights = [],
1796
+ redrawTimeout = null;
1797
+
1798
+ // returns the data item the mouse is over, or null if none is found
1799
+ function findNearbyItem(mouseX, mouseY, seriesFilter) {
1800
+ var maxDistance = options.grid.mouseActiveRadius,
1801
+ smallestDistance = maxDistance * maxDistance + 1,
1802
+ item = null, foundPoint = false, i, j;
1803
+
1804
+ for (i = series.length - 1; i >= 0; --i) {
1805
+ if (!seriesFilter(series[i]))
1806
+ continue;
1807
+
1808
+ var s = series[i],
1809
+ axisx = s.xaxis,
1810
+ axisy = s.yaxis,
1811
+ points = s.datapoints.points,
1812
+ ps = s.datapoints.pointsize,
1813
+ mx = axisx.c2p(mouseX), // precompute some stuff to make the loop faster
1814
+ my = axisy.c2p(mouseY),
1815
+ maxx = maxDistance / axisx.scale,
1816
+ maxy = maxDistance / axisy.scale;
1817
+
1818
+ if (s.lines.show || s.points.show) {
1819
+ for (j = 0; j < points.length; j += ps) {
1820
+ var x = points[j], y = points[j + 1];
1821
+ if (x == null)
1822
+ continue;
1823
+
1824
+ // For points and lines, the cursor must be within a
1825
+ // certain distance to the data point
1826
+ if (x - mx > maxx || x - mx < -maxx ||
1827
+ y - my > maxy || y - my < -maxy)
1828
+ continue;
1829
+
1830
+ // We have to calculate distances in pixels, not in
1831
+ // data units, because the scales of the axes may be different
1832
+ var dx = Math.abs(axisx.p2c(x) - mouseX),
1833
+ dy = Math.abs(axisy.p2c(y) - mouseY),
1834
+ dist = dx * dx + dy * dy; // we save the sqrt
1835
+
1836
+ // use <= to ensure last point takes precedence
1837
+ // (last generally means on top of)
1838
+ if (dist < smallestDistance) {
1839
+ smallestDistance = dist;
1840
+ item = [i, j / ps];
1841
+ }
1842
+ }
1843
+ }
1844
+
1845
+ if (s.bars.show && !item) { // no other point can be nearby
1846
+ var barLeft = s.bars.align == "left" ? 0 : -s.bars.barWidth/2,
1847
+ barRight = barLeft + s.bars.barWidth;
1848
+
1849
+ for (j = 0; j < points.length; j += ps) {
1850
+ var x = points[j], y = points[j + 1], b = points[j + 2];
1851
+ if (x == null)
1852
+ continue;
1853
+
1854
+ // for a bar graph, the cursor must be inside the bar
1855
+ if (series[i].bars.horizontal ?
1856
+ (mx <= Math.max(b, x) && mx >= Math.min(b, x) &&
1857
+ my >= y + barLeft && my <= y + barRight) :
1858
+ (mx >= x + barLeft && mx <= x + barRight &&
1859
+ my >= Math.min(b, y) && my <= Math.max(b, y)))
1860
+ item = [i, j / ps];
1861
+ }
1862
+ }
1863
+ }
1864
+
1865
+ if (item) {
1866
+ i = item[0];
1867
+ j = item[1];
1868
+ ps = series[i].datapoints.pointsize;
1869
+
1870
+ return { datapoint: series[i].datapoints.points.slice(j * ps, (j + 1) * ps),
1871
+ dataIndex: j,
1872
+ series: series[i],
1873
+ seriesIndex: i };
1874
+ }
1875
+
1876
+ return null;
1877
+ }
1878
+
1879
+ function onMouseMove(e) {
1880
+ if (options.grid.hoverable)
1881
+ triggerClickHoverEvent("plothover", e,
1882
+ function (s) { return s["hoverable"] != false; });
1883
+ }
1884
+
1885
+ function onClick(e) {
1886
+ triggerClickHoverEvent("plotclick", e,
1887
+ function (s) { return s["clickable"] != false; });
1888
+ }
1889
+
1890
+ // trigger click or hover event (they send the same parameters
1891
+ // so we share their code)
1892
+ function triggerClickHoverEvent(eventname, event, seriesFilter) {
1893
+ var offset = eventHolder.offset(),
1894
+ pos = { pageX: event.pageX, pageY: event.pageY },
1895
+ canvasX = event.pageX - offset.left - plotOffset.left,
1896
+ canvasY = event.pageY - offset.top - plotOffset.top;
1897
+
1898
+ if (axes.xaxis.used)
1899
+ pos.x = axes.xaxis.c2p(canvasX);
1900
+ if (axes.yaxis.used)
1901
+ pos.y = axes.yaxis.c2p(canvasY);
1902
+ if (axes.x2axis.used)
1903
+ pos.x2 = axes.x2axis.c2p(canvasX);
1904
+ if (axes.y2axis.used)
1905
+ pos.y2 = axes.y2axis.c2p(canvasY);
1906
+
1907
+ var item = findNearbyItem(canvasX, canvasY, seriesFilter);
1908
+
1909
+ if (item) {
1910
+ // fill in mouse pos for any listeners out there
1911
+ item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint[0]) + offset.left + plotOffset.left);
1912
+ item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top + plotOffset.top);
1913
+ }
1914
+
1915
+ if (options.grid.autoHighlight) {
1916
+ // clear auto-highlights
1917
+ for (var i = 0; i < highlights.length; ++i) {
1918
+ var h = highlights[i];
1919
+ if (h.auto == eventname &&
1920
+ !(item && h.series == item.series && h.point == item.datapoint))
1921
+ unhighlight(h.series, h.point);
1922
+ }
1923
+
1924
+ if (item)
1925
+ highlight(item.series, item.datapoint, eventname);
1926
+ }
1927
+
1928
+ placeholder.trigger(eventname, [ pos, item ]);
1929
+ }
1930
+
1931
+ function triggerRedrawOverlay() {
1932
+ if (!redrawTimeout)
1933
+ redrawTimeout = setTimeout(drawOverlay, 30);
1934
+ }
1935
+
1936
+ function drawOverlay() {
1937
+ redrawTimeout = null;
1938
+
1939
+ // draw highlights
1940
+ octx.save();
1941
+ octx.clearRect(0, 0, canvasWidth, canvasHeight);
1942
+ octx.translate(plotOffset.left, plotOffset.top);
1943
+
1944
+ var i, hi;
1945
+ for (i = 0; i < highlights.length; ++i) {
1946
+ hi = highlights[i];
1947
+
1948
+ if (hi.series.bars.show)
1949
+ drawBarHighlight(hi.series, hi.point);
1950
+ else
1951
+ drawPointHighlight(hi.series, hi.point);
1952
+ }
1953
+ octx.restore();
1954
+
1955
+ executeHooks(hooks.drawOverlay, [octx]);
1956
+ }
1957
+
1958
+ function highlight(s, point, auto) {
1959
+ if (typeof s == "number")
1960
+ s = series[s];
1961
+
1962
+ if (typeof point == "number")
1963
+ point = s.data[point];
1964
+
1965
+ var i = indexOfHighlight(s, point);
1966
+ if (i == -1) {
1967
+ highlights.push({ series: s, point: point, auto: auto });
1968
+
1969
+ triggerRedrawOverlay();
1970
+ }
1971
+ else if (!auto)
1972
+ highlights[i].auto = false;
1973
+ }
1974
+
1975
+ function unhighlight(s, point) {
1976
+ if (s == null && point == null) {
1977
+ highlights = [];
1978
+ triggerRedrawOverlay();
1979
+ }
1980
+
1981
+ if (typeof s == "number")
1982
+ s = series[s];
1983
+
1984
+ if (typeof point == "number")
1985
+ point = s.data[point];
1986
+
1987
+ var i = indexOfHighlight(s, point);
1988
+ if (i != -1) {
1989
+ highlights.splice(i, 1);
1990
+
1991
+ triggerRedrawOverlay();
1992
+ }
1993
+ }
1994
+
1995
+ function indexOfHighlight(s, p) {
1996
+ for (var i = 0; i < highlights.length; ++i) {
1997
+ var h = highlights[i];
1998
+ if (h.series == s && h.point[0] == p[0]
1999
+ && h.point[1] == p[1])
2000
+ return i;
2001
+ }
2002
+ return -1;
2003
+ }
2004
+
2005
+ function drawPointHighlight(series, point) {
2006
+ var x = point[0], y = point[1],
2007
+ axisx = series.xaxis, axisy = series.yaxis;
2008
+
2009
+ if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max)
2010
+ return;
2011
+
2012
+ var pointRadius = series.points.radius + series.points.lineWidth / 2;
2013
+ octx.lineWidth = pointRadius;
2014
+ octx.strokeStyle = $.color.parse(series.color).scale('a', 0.5).toString();
2015
+ var radius = 1.5 * pointRadius;
2016
+ octx.beginPath();
2017
+ octx.arc(axisx.p2c(x), axisy.p2c(y), radius, 0, 2 * Math.PI, false);
2018
+ octx.stroke();
2019
+ }
2020
+
2021
+ function drawBarHighlight(series, point) {
2022
+ octx.lineWidth = series.bars.lineWidth;
2023
+ octx.strokeStyle = $.color.parse(series.color).scale('a', 0.5).toString();
2024
+ var fillStyle = $.color.parse(series.color).scale('a', 0.5).toString();
2025
+ var barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth/2;
2026
+ drawBar(point[0], point[1], point[2] || 0, barLeft, barLeft + series.bars.barWidth,
2027
+ 0, function () { return fillStyle; }, series.xaxis, series.yaxis, octx, series.bars.horizontal);
2028
+ }
2029
+
2030
+ function getColorOrGradient(spec, bottom, top, defaultColor) {
2031
+ if (typeof spec == "string")
2032
+ return spec;
2033
+ else {
2034
+ // assume this is a gradient spec; IE currently only
2035
+ // supports a simple vertical gradient properly, so that's
2036
+ // what we support too
2037
+ var gradient = ctx.createLinearGradient(0, top, 0, bottom);
2038
+
2039
+ for (var i = 0, l = spec.colors.length; i < l; ++i) {
2040
+ var c = spec.colors[i];
2041
+ if (typeof c != "string") {
2042
+ c = $.color.parse(defaultColor).scale('rgb', c.brightness);
2043
+ c.a *= c.opacity;
2044
+ c = c.toString();
2045
+ }
2046
+ gradient.addColorStop(i / (l - 1), c);
2047
+ }
2048
+
2049
+ return gradient;
2050
+ }
2051
+ }
2052
+ }
2053
+
2054
+ $.plot = function(placeholder, data, options) {
2055
+ var plot = new Plot($(placeholder), data, options, $.plot.plugins);
2056
+ /*var t0 = new Date();
2057
+ var t1 = new Date();
2058
+ var tstr = "time used (msecs): " + (t1.getTime() - t0.getTime())
2059
+ if (window.console)
2060
+ console.log(tstr);
2061
+ else
2062
+ alert(tstr);*/
2063
+ return plot;
2064
+ };
2065
+
2066
+ $.plot.plugins = [];
2067
+
2068
+ // returns a string with the date d formatted according to fmt
2069
+ $.plot.formatDate = function(d, fmt, monthNames) {
2070
+ var leftPad = function(n) {
2071
+ n = "" + n;
2072
+ return n.length == 1 ? "0" + n : n;
2073
+ };
2074
+
2075
+ var r = [];
2076
+ var escape = false;
2077
+ var hours = d.getUTCHours();
2078
+ var isAM = hours < 12;
2079
+ if (monthNames == null)
2080
+ monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
2081
+
2082
+ if (fmt.search(/%p|%P/) != -1) {
2083
+ if (hours > 12) {
2084
+ hours = hours - 12;
2085
+ } else if (hours == 0) {
2086
+ hours = 12;
2087
+ }
2088
+ }
2089
+ for (var i = 0; i < fmt.length; ++i) {
2090
+ var c = fmt.charAt(i);
2091
+
2092
+ if (escape) {
2093
+ switch (c) {
2094
+ case 'h': c = "" + hours; break;
2095
+ case 'H': c = leftPad(hours); break;
2096
+ case 'M': c = leftPad(d.getUTCMinutes()); break;
2097
+ case 'S': c = leftPad(d.getUTCSeconds()); break;
2098
+ case 'd': c = "" + d.getUTCDate(); break;
2099
+ case 'm': c = "" + (d.getUTCMonth() + 1); break;
2100
+ case 'y': c = "" + d.getUTCFullYear(); break;
2101
+ case 'b': c = "" + monthNames[d.getUTCMonth()]; break;
2102
+ case 'p': c = (isAM) ? ("" + "am") : ("" + "pm"); break;
2103
+ case 'P': c = (isAM) ? ("" + "AM") : ("" + "PM"); break;
2104
+ }
2105
+ r.push(c);
2106
+ escape = false;
2107
+ }
2108
+ else {
2109
+ if (c == "%")
2110
+ escape = true;
2111
+ else
2112
+ r.push(c);
2113
+ }
2114
+ }
2115
+ return r.join("");
2116
+ };
2117
+
2118
+ // round to nearby lower multiple of base
2119
+ function floorInBase(n, base) {
2120
+ return base * Math.floor(n / base);
2121
+ }
2122
+
2123
+ })(jQuery);