logstash-lite 0.2.20101118134500

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. data/bin/logstash +56 -0
  2. data/bin/logstash-web +6 -0
  3. data/etc/logstash-elasticsearch-rabbitmq-river.yaml +41 -0
  4. data/etc/logstash-mongodb-storage.yaml +5 -0
  5. data/etc/logstash-parser.yaml +20 -0
  6. data/etc/logstash-reader.yaml +8 -0
  7. data/etc/logstash-shipper.yaml +18 -0
  8. data/etc/logstash-standalone.yaml +47 -0
  9. data/etc/prod.yaml +38 -0
  10. data/etc/redhat/logstash +92 -0
  11. data/etc/redhat/logstash-agent +83 -0
  12. data/etc/redhat/logstash-agent.sysconfig +7 -0
  13. data/etc/redhat/logstash.spec +171 -0
  14. data/etc/redhat/logstash.sysconfig +18 -0
  15. data/etc/tograylog.yaml +37 -0
  16. data/examples/test.rb +38 -0
  17. data/lib/logstash.rb +3 -0
  18. data/lib/logstash/agent.rb +116 -0
  19. data/lib/logstash/event.rb +70 -0
  20. data/lib/logstash/filters.rb +17 -0
  21. data/lib/logstash/filters/base.rb +17 -0
  22. data/lib/logstash/filters/date.rb +59 -0
  23. data/lib/logstash/filters/field.rb +29 -0
  24. data/lib/logstash/filters/grok.rb +74 -0
  25. data/lib/logstash/filters/grokdiscovery.rb +60 -0
  26. data/lib/logstash/inputs.rb +18 -0
  27. data/lib/logstash/inputs/amqp.rb +48 -0
  28. data/lib/logstash/inputs/base.rb +32 -0
  29. data/lib/logstash/inputs/file.rb +47 -0
  30. data/lib/logstash/inputs/syslog.rb +123 -0
  31. data/lib/logstash/inputs/tcp.rb +51 -0
  32. data/lib/logstash/logging.rb +82 -0
  33. data/lib/logstash/namespace.rb +6 -0
  34. data/lib/logstash/outputs.rb +15 -0
  35. data/lib/logstash/outputs/amqp.rb +48 -0
  36. data/lib/logstash/outputs/base.rb +29 -0
  37. data/lib/logstash/outputs/elasticsearch.rb +71 -0
  38. data/lib/logstash/outputs/gelf.rb +35 -0
  39. data/lib/logstash/outputs/mongodb.rb +19 -0
  40. data/lib/logstash/outputs/stdout.rb +15 -0
  41. data/lib/logstash/outputs/websocket.rb +35 -0
  42. data/lib/logstash/time.rb +27 -0
  43. data/lib/logstash/web/lib/elasticsearch.rb +79 -0
  44. data/lib/logstash/web/public/css/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  45. data/lib/logstash/web/public/css/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  46. data/lib/logstash/web/public/css/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  47. data/lib/logstash/web/public/css/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  48. data/lib/logstash/web/public/css/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  49. data/lib/logstash/web/public/css/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  50. data/lib/logstash/web/public/css/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  51. data/lib/logstash/web/public/css/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  52. data/lib/logstash/web/public/css/smoothness/images/ui-icons_222222_256x240.png +0 -0
  53. data/lib/logstash/web/public/css/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
  54. data/lib/logstash/web/public/css/smoothness/images/ui-icons_454545_256x240.png +0 -0
  55. data/lib/logstash/web/public/css/smoothness/images/ui-icons_888888_256x240.png +0 -0
  56. data/lib/logstash/web/public/css/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
  57. data/lib/logstash/web/public/css/smoothness/jquery-ui-1.8.5.custom.css +572 -0
  58. data/lib/logstash/web/public/js/flot/API.txt +1024 -0
  59. data/lib/logstash/web/public/js/flot/FAQ.txt +71 -0
  60. data/lib/logstash/web/public/js/flot/LICENSE.txt +22 -0
  61. data/lib/logstash/web/public/js/flot/Makefile +15 -0
  62. data/lib/logstash/web/public/js/flot/NEWS.txt +340 -0
  63. data/lib/logstash/web/public/js/flot/PLUGINS.txt +105 -0
  64. data/lib/logstash/web/public/js/flot/README.txt +81 -0
  65. data/lib/logstash/web/public/js/flot/examples/ajax.html +143 -0
  66. data/lib/logstash/web/public/js/flot/examples/annotating.html +75 -0
  67. data/lib/logstash/web/public/js/flot/examples/arrow-down.gif +0 -0
  68. data/lib/logstash/web/public/js/flot/examples/arrow-left.gif +0 -0
  69. data/lib/logstash/web/public/js/flot/examples/arrow-right.gif +0 -0
  70. data/lib/logstash/web/public/js/flot/examples/arrow-up.gif +0 -0
  71. data/lib/logstash/web/public/js/flot/examples/basic.html +38 -0
  72. data/lib/logstash/web/public/js/flot/examples/data-eu-gdp-growth-1.json +4 -0
  73. data/lib/logstash/web/public/js/flot/examples/data-eu-gdp-growth-2.json +4 -0
  74. data/lib/logstash/web/public/js/flot/examples/data-eu-gdp-growth-3.json +4 -0
  75. data/lib/logstash/web/public/js/flot/examples/data-eu-gdp-growth-4.json +4 -0
  76. data/lib/logstash/web/public/js/flot/examples/data-eu-gdp-growth-5.json +4 -0
  77. data/lib/logstash/web/public/js/flot/examples/data-eu-gdp-growth.json +4 -0
  78. data/lib/logstash/web/public/js/flot/examples/data-japan-gdp-growth.json +4 -0
  79. data/lib/logstash/web/public/js/flot/examples/data-usa-gdp-growth.json +4 -0
  80. data/lib/logstash/web/public/js/flot/examples/dual-axis.html +39 -0
  81. data/lib/logstash/web/public/js/flot/examples/graph-types.html +75 -0
  82. data/lib/logstash/web/public/js/flot/examples/hs-2004-27-a-large_web.jpg +0 -0
  83. data/lib/logstash/web/public/js/flot/examples/image.html +45 -0
  84. data/lib/logstash/web/public/js/flot/examples/index.html +43 -0
  85. data/lib/logstash/web/public/js/flot/examples/interacting.html +93 -0
  86. data/lib/logstash/web/public/js/flot/examples/layout.css +6 -0
  87. data/lib/logstash/web/public/js/flot/examples/navigate.html +118 -0
  88. data/lib/logstash/web/public/js/flot/examples/selection.html +114 -0
  89. data/lib/logstash/web/public/js/flot/examples/setting-options.html +65 -0
  90. data/lib/logstash/web/public/js/flot/examples/stacking.html +77 -0
  91. data/lib/logstash/web/public/js/flot/examples/thresholding.html +54 -0
  92. data/lib/logstash/web/public/js/flot/examples/time.html +71 -0
  93. data/lib/logstash/web/public/js/flot/examples/tracking.html +95 -0
  94. data/lib/logstash/web/public/js/flot/examples/turning-series.html +98 -0
  95. data/lib/logstash/web/public/js/flot/examples/visitors.html +90 -0
  96. data/lib/logstash/web/public/js/flot/examples/zooming.html +98 -0
  97. data/lib/logstash/web/public/js/flot/excanvas.js +1427 -0
  98. data/lib/logstash/web/public/js/flot/excanvas.min.js +1 -0
  99. data/lib/logstash/web/public/js/flot/jquery.colorhelpers.js +174 -0
  100. data/lib/logstash/web/public/js/flot/jquery.colorhelpers.min.js +1 -0
  101. data/lib/logstash/web/public/js/flot/jquery.flot.crosshair.js +156 -0
  102. data/lib/logstash/web/public/js/flot/jquery.flot.crosshair.min.js +1 -0
  103. data/lib/logstash/web/public/js/flot/jquery.flot.image.js +237 -0
  104. data/lib/logstash/web/public/js/flot/jquery.flot.image.min.js +1 -0
  105. data/lib/logstash/web/public/js/flot/jquery.flot.js +2119 -0
  106. data/lib/logstash/web/public/js/flot/jquery.flot.min.js +1 -0
  107. data/lib/logstash/web/public/js/flot/jquery.flot.navigate.js +272 -0
  108. data/lib/logstash/web/public/js/flot/jquery.flot.navigate.min.js +1 -0
  109. data/lib/logstash/web/public/js/flot/jquery.flot.selection.js +299 -0
  110. data/lib/logstash/web/public/js/flot/jquery.flot.selection.min.js +1 -0
  111. data/lib/logstash/web/public/js/flot/jquery.flot.stack.js +152 -0
  112. data/lib/logstash/web/public/js/flot/jquery.flot.stack.min.js +1 -0
  113. data/lib/logstash/web/public/js/flot/jquery.flot.threshold.js +103 -0
  114. data/lib/logstash/web/public/js/flot/jquery.flot.threshold.min.js +1 -0
  115. data/lib/logstash/web/public/js/flot/jquery.js +4376 -0
  116. data/lib/logstash/web/public/js/flot/jquery.min.js +19 -0
  117. data/lib/logstash/web/public/js/jquery-hashchange-1.0.0.js +121 -0
  118. data/lib/logstash/web/public/js/jquery.livequery.js +250 -0
  119. data/lib/logstash/web/public/js/jquery.tmpl.min.js +1 -0
  120. data/lib/logstash/web/public/js/logstash.js +202 -0
  121. data/lib/logstash/web/server.rb +90 -0
  122. data/lib/logstash/web/views/header.haml +8 -0
  123. data/lib/logstash/web/views/layout.haml +21 -0
  124. data/lib/logstash/web/views/main/index.haml +5 -0
  125. data/lib/logstash/web/views/search/ajax.haml +32 -0
  126. data/lib/logstash/web/views/search/results.haml +17 -0
  127. data/lib/logstash/web/views/style.sass +50 -0
  128. data/patterns/firewalls +2 -0
  129. data/patterns/grok-patterns +90 -0
  130. data/patterns/haproxy +5 -0
  131. data/patterns/linux-syslog +7 -0
  132. data/patterns/nagios +7 -0
  133. data/patterns/ruby +2 -0
  134. metadata +228 -0
@@ -0,0 +1 @@
1
+ (function(D){var B={series:{images:{show:false,alpha:1,anchor:"corner"}}};D.plot.image={};D.plot.image.loadDataImages=function(G,F,K){var J=[],H=[];var I=F.series.images.show;D.each(G,function(L,M){if(!(I||M.images.show)){return }if(M.data){M=M.data}D.each(M,function(N,O){if(typeof O[0]=="string"){J.push(O[0]);H.push(O)}})});D.plot.image.load(J,function(L){D.each(H,function(N,O){var M=O[0];if(L[M]){O[0]=L[M]}});K()})};D.plot.image.load=function(H,I){var G=H.length,F={};if(G==0){I({})}D.each(H,function(K,J){var L=function(){--G;F[J]=this;if(G==0){I(F)}};D("<img />").load(L).error(L).attr("src",J)})};function A(H,F){var G=H.getPlotOffset();D.each(H.getData(),function(O,P){var X=P.datapoints.points,I=P.datapoints.pointsize;for(var O=0;O<X.length;O+=I){var Q=X[O],M=X[O+1],V=X[O+2],K=X[O+3],T=X[O+4],W=P.xaxis,S=P.yaxis,N;if(!Q||Q.width<=0||Q.height<=0){continue}if(M>K){N=K;K=M;M=N}if(V>T){N=T;T=V;V=N}if(P.images.anchor=="center"){N=0.5*(K-M)/(Q.width-1);M-=N;K+=N;N=0.5*(T-V)/(Q.height-1);V-=N;T+=N}if(M==K||V==T||M>=W.max||K<=W.min||V>=S.max||T<=S.min){continue}var L=0,U=0,J=Q.width,R=Q.height;if(M<W.min){L+=(J-L)*(W.min-M)/(K-M);M=W.min}if(K>W.max){J+=(J-L)*(W.max-K)/(K-M);K=W.max}if(V<S.min){R+=(U-R)*(S.min-V)/(T-V);V=S.min}if(T>S.max){U+=(U-R)*(S.max-T)/(T-V);T=S.max}M=W.p2c(M);K=W.p2c(K);V=S.p2c(V);T=S.p2c(T);if(M>K){N=K;K=M;M=N}if(V>T){N=T;T=V;V=N}N=F.globalAlpha;F.globalAlpha*=P.images.alpha;F.drawImage(Q,L,U,J-L,R-U,M+G.left,V+G.top,K-M,T-V);F.globalAlpha=N}})}function C(I,F,G,H){if(!F.images.show){return }H.format=[{required:true},{x:true,number:true,required:true},{y:true,number:true,required:true},{x:true,number:true,required:true},{y:true,number:true,required:true}]}function E(F){F.hooks.processRawData.push(C);F.hooks.draw.push(A)}D.plot.plugins.push({init:E,options:B,name:"image",version:"1.1"})})(jQuery);
@@ -0,0 +1,2119 @@
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 = placeholder.width();
545
+ canvasHeight = placeholder.height();
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 = 0; i < series.length; ++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
+ for (i = 0; i < axis.ticks.length; ++i) {
1180
+ v = axis.ticks[i].v;
1181
+ if (v <= axis.min || v >= axes.xaxis.max)
1182
+ continue; // skip those lying on the axes
1183
+
1184
+ ctx.moveTo(Math.floor(axis.p2c(v)) + ctx.lineWidth/2, 0);
1185
+ ctx.lineTo(Math.floor(axis.p2c(v)) + ctx.lineWidth/2, plotHeight);
1186
+ }
1187
+
1188
+ axis = axes.yaxis;
1189
+ for (i = 0; i < axis.ticks.length; ++i) {
1190
+ v = axis.ticks[i].v;
1191
+ if (v <= axis.min || v >= axis.max)
1192
+ continue;
1193
+
1194
+ ctx.moveTo(0, Math.floor(axis.p2c(v)) + ctx.lineWidth/2);
1195
+ ctx.lineTo(plotWidth, Math.floor(axis.p2c(v)) + ctx.lineWidth/2);
1196
+ }
1197
+
1198
+ axis = axes.x2axis;
1199
+ for (i = 0; i < axis.ticks.length; ++i) {
1200
+ v = axis.ticks[i].v;
1201
+ if (v <= axis.min || v >= axis.max)
1202
+ continue;
1203
+
1204
+ ctx.moveTo(Math.floor(axis.p2c(v)) + ctx.lineWidth/2, -5);
1205
+ ctx.lineTo(Math.floor(axis.p2c(v)) + ctx.lineWidth/2, 5);
1206
+ }
1207
+
1208
+ axis = axes.y2axis;
1209
+ for (i = 0; i < axis.ticks.length; ++i) {
1210
+ v = axis.ticks[i].v;
1211
+ if (v <= axis.min || v >= axis.max)
1212
+ continue;
1213
+
1214
+ ctx.moveTo(plotWidth-5, Math.floor(axis.p2c(v)) + ctx.lineWidth/2);
1215
+ ctx.lineTo(plotWidth+5, Math.floor(axis.p2c(v)) + ctx.lineWidth/2);
1216
+ }
1217
+
1218
+ ctx.stroke();
1219
+
1220
+ if (options.grid.borderWidth) {
1221
+ // draw border
1222
+ var bw = options.grid.borderWidth;
1223
+ ctx.lineWidth = bw;
1224
+ ctx.strokeStyle = options.grid.borderColor;
1225
+ ctx.strokeRect(-bw/2, -bw/2, plotWidth + bw, plotHeight + bw);
1226
+ }
1227
+
1228
+ ctx.restore();
1229
+ }
1230
+
1231
+ function insertLabels() {
1232
+ placeholder.find(".tickLabels").remove();
1233
+
1234
+ var html = ['<div class="tickLabels" style="font-size:smaller;color:' + options.grid.color + '">'];
1235
+
1236
+ function addLabels(axis, labelGenerator) {
1237
+ for (var i = 0; i < axis.ticks.length; ++i) {
1238
+ var tick = axis.ticks[i];
1239
+ if (!tick.label || tick.v < axis.min || tick.v > axis.max)
1240
+ continue;
1241
+ html.push(labelGenerator(tick, axis));
1242
+ }
1243
+ }
1244
+
1245
+ var margin = options.grid.labelMargin + options.grid.borderWidth;
1246
+
1247
+ addLabels(axes.xaxis, function (tick, axis) {
1248
+ 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>";
1249
+ });
1250
+
1251
+
1252
+ addLabels(axes.yaxis, function (tick, axis) {
1253
+ 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>";
1254
+ });
1255
+
1256
+ addLabels(axes.x2axis, function (tick, axis) {
1257
+ 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>";
1258
+ });
1259
+
1260
+ addLabels(axes.y2axis, function (tick, axis) {
1261
+ 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>";
1262
+ });
1263
+
1264
+ html.push('</div>');
1265
+
1266
+ placeholder.append(html.join(""));
1267
+ }
1268
+
1269
+ function drawSeries(series) {
1270
+ if (series.lines.show)
1271
+ drawSeriesLines(series);
1272
+ if (series.bars.show)
1273
+ drawSeriesBars(series);
1274
+ if (series.points.show)
1275
+ drawSeriesPoints(series);
1276
+ }
1277
+
1278
+ function drawSeriesLines(series) {
1279
+ function plotLine(datapoints, xoffset, yoffset, axisx, axisy) {
1280
+ var points = datapoints.points,
1281
+ ps = datapoints.pointsize,
1282
+ prevx = null, prevy = null;
1283
+
1284
+ ctx.beginPath();
1285
+ for (var i = ps; i < points.length; i += ps) {
1286
+ var x1 = points[i - ps], y1 = points[i - ps + 1],
1287
+ x2 = points[i], y2 = points[i + 1];
1288
+
1289
+ if (x1 == null || x2 == null)
1290
+ continue;
1291
+
1292
+ // clip with ymin
1293
+ if (y1 <= y2 && y1 < axisy.min) {
1294
+ if (y2 < axisy.min)
1295
+ continue; // line segment is outside
1296
+ // compute new intersection point
1297
+ x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
1298
+ y1 = axisy.min;
1299
+ }
1300
+ else if (y2 <= y1 && y2 < axisy.min) {
1301
+ if (y1 < axisy.min)
1302
+ continue;
1303
+ x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
1304
+ y2 = axisy.min;
1305
+ }
1306
+
1307
+ // clip with ymax
1308
+ if (y1 >= y2 && y1 > axisy.max) {
1309
+ if (y2 > axisy.max)
1310
+ continue;
1311
+ x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
1312
+ y1 = axisy.max;
1313
+ }
1314
+ else if (y2 >= y1 && y2 > axisy.max) {
1315
+ if (y1 > axisy.max)
1316
+ continue;
1317
+ x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
1318
+ y2 = axisy.max;
1319
+ }
1320
+
1321
+ // clip with xmin
1322
+ if (x1 <= x2 && x1 < axisx.min) {
1323
+ if (x2 < axisx.min)
1324
+ continue;
1325
+ y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
1326
+ x1 = axisx.min;
1327
+ }
1328
+ else if (x2 <= x1 && x2 < axisx.min) {
1329
+ if (x1 < axisx.min)
1330
+ continue;
1331
+ y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
1332
+ x2 = axisx.min;
1333
+ }
1334
+
1335
+ // clip with xmax
1336
+ if (x1 >= x2 && x1 > axisx.max) {
1337
+ if (x2 > axisx.max)
1338
+ continue;
1339
+ y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
1340
+ x1 = axisx.max;
1341
+ }
1342
+ else if (x2 >= x1 && x2 > axisx.max) {
1343
+ if (x1 > axisx.max)
1344
+ continue;
1345
+ y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
1346
+ x2 = axisx.max;
1347
+ }
1348
+
1349
+ if (x1 != prevx || y1 != prevy)
1350
+ ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset);
1351
+
1352
+ prevx = x2;
1353
+ prevy = y2;
1354
+ ctx.lineTo(axisx.p2c(x2) + xoffset, axisy.p2c(y2) + yoffset);
1355
+ }
1356
+ ctx.stroke();
1357
+ }
1358
+
1359
+ function plotLineArea(datapoints, axisx, axisy) {
1360
+ var points = datapoints.points,
1361
+ ps = datapoints.pointsize,
1362
+ bottom = Math.min(Math.max(0, axisy.min), axisy.max),
1363
+ top, lastX = 0, areaOpen = false;
1364
+
1365
+ for (var i = ps; i < points.length; i += ps) {
1366
+ var x1 = points[i - ps], y1 = points[i - ps + 1],
1367
+ x2 = points[i], y2 = points[i + 1];
1368
+
1369
+ if (areaOpen && x1 != null && x2 == null) {
1370
+ // close area
1371
+ ctx.lineTo(axisx.p2c(lastX), axisy.p2c(bottom));
1372
+ ctx.fill();
1373
+ areaOpen = false;
1374
+ continue;
1375
+ }
1376
+
1377
+ if (x1 == null || x2 == null)
1378
+ continue;
1379
+
1380
+ // clip x values
1381
+
1382
+ // clip with xmin
1383
+ if (x1 <= x2 && x1 < axisx.min) {
1384
+ if (x2 < axisx.min)
1385
+ continue;
1386
+ y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
1387
+ x1 = axisx.min;
1388
+ }
1389
+ else if (x2 <= x1 && x2 < axisx.min) {
1390
+ if (x1 < axisx.min)
1391
+ continue;
1392
+ y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
1393
+ x2 = axisx.min;
1394
+ }
1395
+
1396
+ // clip with xmax
1397
+ if (x1 >= x2 && x1 > axisx.max) {
1398
+ if (x2 > axisx.max)
1399
+ continue;
1400
+ y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
1401
+ x1 = axisx.max;
1402
+ }
1403
+ else if (x2 >= x1 && x2 > axisx.max) {
1404
+ if (x1 > axisx.max)
1405
+ continue;
1406
+ y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
1407
+ x2 = axisx.max;
1408
+ }
1409
+
1410
+ if (!areaOpen) {
1411
+ // open area
1412
+ ctx.beginPath();
1413
+ ctx.moveTo(axisx.p2c(x1), axisy.p2c(bottom));
1414
+ areaOpen = true;
1415
+ }
1416
+
1417
+ // now first check the case where both is outside
1418
+ if (y1 >= axisy.max && y2 >= axisy.max) {
1419
+ ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.max));
1420
+ ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.max));
1421
+ lastX = x2;
1422
+ continue;
1423
+ }
1424
+ else if (y1 <= axisy.min && y2 <= axisy.min) {
1425
+ ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.min));
1426
+ ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.min));
1427
+ lastX = x2;
1428
+ continue;
1429
+ }
1430
+
1431
+ // else it's a bit more complicated, there might
1432
+ // be two rectangles and two triangles we need to fill
1433
+ // in; to find these keep track of the current x values
1434
+ var x1old = x1, x2old = x2;
1435
+
1436
+ // and clip the y values, without shortcutting
1437
+
1438
+ // clip with ymin
1439
+ if (y1 <= y2 && y1 < axisy.min && y2 >= axisy.min) {
1440
+ x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
1441
+ y1 = axisy.min;
1442
+ }
1443
+ else if (y2 <= y1 && y2 < axisy.min && y1 >= axisy.min) {
1444
+ x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
1445
+ y2 = axisy.min;
1446
+ }
1447
+
1448
+ // clip with ymax
1449
+ if (y1 >= y2 && y1 > axisy.max && y2 <= axisy.max) {
1450
+ x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
1451
+ y1 = axisy.max;
1452
+ }
1453
+ else if (y2 >= y1 && y2 > axisy.max && y1 <= axisy.max) {
1454
+ x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
1455
+ y2 = axisy.max;
1456
+ }
1457
+
1458
+
1459
+ // if the x value was changed we got a rectangle
1460
+ // to fill
1461
+ if (x1 != x1old) {
1462
+ if (y1 <= axisy.min)
1463
+ top = axisy.min;
1464
+ else
1465
+ top = axisy.max;
1466
+
1467
+ ctx.lineTo(axisx.p2c(x1old), axisy.p2c(top));
1468
+ ctx.lineTo(axisx.p2c(x1), axisy.p2c(top));
1469
+ }
1470
+
1471
+ // fill the triangles
1472
+ ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1));
1473
+ ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2));
1474
+
1475
+ // fill the other rectangle if it's there
1476
+ if (x2 != x2old) {
1477
+ if (y2 <= axisy.min)
1478
+ top = axisy.min;
1479
+ else
1480
+ top = axisy.max;
1481
+
1482
+ ctx.lineTo(axisx.p2c(x2), axisy.p2c(top));
1483
+ ctx.lineTo(axisx.p2c(x2old), axisy.p2c(top));
1484
+ }
1485
+
1486
+ lastX = Math.max(x2, x2old);
1487
+ }
1488
+
1489
+ if (areaOpen) {
1490
+ ctx.lineTo(axisx.p2c(lastX), axisy.p2c(bottom));
1491
+ ctx.fill();
1492
+ }
1493
+ }
1494
+
1495
+ ctx.save();
1496
+ ctx.translate(plotOffset.left, plotOffset.top);
1497
+ ctx.lineJoin = "round";
1498
+
1499
+ var lw = series.lines.lineWidth,
1500
+ sw = series.shadowSize;
1501
+ // FIXME: consider another form of shadow when filling is turned on
1502
+ if (lw > 0 && sw > 0) {
1503
+ // draw shadow as a thick and thin line with transparency
1504
+ ctx.lineWidth = sw;
1505
+ ctx.strokeStyle = "rgba(0,0,0,0.1)";
1506
+ // position shadow at angle from the mid of line
1507
+ var angle = Math.PI/18;
1508
+ plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/2), Math.cos(angle) * (lw/2 + sw/2), series.xaxis, series.yaxis);
1509
+ ctx.lineWidth = sw/2;
1510
+ plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/4), Math.cos(angle) * (lw/2 + sw/4), series.xaxis, series.yaxis);
1511
+ }
1512
+
1513
+ ctx.lineWidth = lw;
1514
+ ctx.strokeStyle = series.color;
1515
+ var fillStyle = getFillStyle(series.lines, series.color, 0, plotHeight);
1516
+ if (fillStyle) {
1517
+ ctx.fillStyle = fillStyle;
1518
+ plotLineArea(series.datapoints, series.xaxis, series.yaxis);
1519
+ }
1520
+
1521
+ if (lw > 0)
1522
+ plotLine(series.datapoints, 0, 0, series.xaxis, series.yaxis);
1523
+ ctx.restore();
1524
+ }
1525
+
1526
+ function drawSeriesPoints(series) {
1527
+ function plotPoints(datapoints, radius, fillStyle, offset, circumference, axisx, axisy) {
1528
+ var points = datapoints.points, ps = datapoints.pointsize;
1529
+
1530
+ for (var i = 0; i < points.length; i += ps) {
1531
+ var x = points[i], y = points[i + 1];
1532
+ if (x == null || x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max)
1533
+ continue;
1534
+
1535
+ ctx.beginPath();
1536
+ ctx.arc(axisx.p2c(x), axisy.p2c(y) + offset, radius, 0, circumference, false);
1537
+ if (fillStyle) {
1538
+ ctx.fillStyle = fillStyle;
1539
+ ctx.fill();
1540
+ }
1541
+ ctx.stroke();
1542
+ }
1543
+ }
1544
+
1545
+ ctx.save();
1546
+ ctx.translate(plotOffset.left, plotOffset.top);
1547
+
1548
+ var lw = series.lines.lineWidth,
1549
+ sw = series.shadowSize,
1550
+ radius = series.points.radius;
1551
+ if (lw > 0 && sw > 0) {
1552
+ // draw shadow in two steps
1553
+ var w = sw / 2;
1554
+ ctx.lineWidth = w;
1555
+ ctx.strokeStyle = "rgba(0,0,0,0.1)";
1556
+ plotPoints(series.datapoints, radius, null, w + w/2, Math.PI,
1557
+ series.xaxis, series.yaxis);
1558
+
1559
+ ctx.strokeStyle = "rgba(0,0,0,0.2)";
1560
+ plotPoints(series.datapoints, radius, null, w/2, Math.PI,
1561
+ series.xaxis, series.yaxis);
1562
+ }
1563
+
1564
+ ctx.lineWidth = lw;
1565
+ ctx.strokeStyle = series.color;
1566
+ plotPoints(series.datapoints, radius,
1567
+ getFillStyle(series.points, series.color), 0, 2 * Math.PI,
1568
+ series.xaxis, series.yaxis);
1569
+ ctx.restore();
1570
+ }
1571
+
1572
+ function drawBar(x, y, b, barLeft, barRight, offset, fillStyleCallback, axisx, axisy, c, horizontal) {
1573
+ var left, right, bottom, top,
1574
+ drawLeft, drawRight, drawTop, drawBottom,
1575
+ tmp;
1576
+
1577
+ if (horizontal) {
1578
+ drawBottom = drawRight = drawTop = true;
1579
+ drawLeft = false;
1580
+ left = b;
1581
+ right = x;
1582
+ top = y + barLeft;
1583
+ bottom = y + barRight;
1584
+
1585
+ // account for negative bars
1586
+ if (right < left) {
1587
+ tmp = right;
1588
+ right = left;
1589
+ left = tmp;
1590
+ drawLeft = true;
1591
+ drawRight = false;
1592
+ }
1593
+ }
1594
+ else {
1595
+ drawLeft = drawRight = drawTop = true;
1596
+ drawBottom = false;
1597
+ left = x + barLeft;
1598
+ right = x + barRight;
1599
+ bottom = b;
1600
+ top = y;
1601
+
1602
+ // account for negative bars
1603
+ if (top < bottom) {
1604
+ tmp = top;
1605
+ top = bottom;
1606
+ bottom = tmp;
1607
+ drawBottom = true;
1608
+ drawTop = false;
1609
+ }
1610
+ }
1611
+
1612
+ // clip
1613
+ if (right < axisx.min || left > axisx.max ||
1614
+ top < axisy.min || bottom > axisy.max)
1615
+ return;
1616
+
1617
+ if (left < axisx.min) {
1618
+ left = axisx.min;
1619
+ drawLeft = false;
1620
+ }
1621
+
1622
+ if (right > axisx.max) {
1623
+ right = axisx.max;
1624
+ drawRight = false;
1625
+ }
1626
+
1627
+ if (bottom < axisy.min) {
1628
+ bottom = axisy.min;
1629
+ drawBottom = false;
1630
+ }
1631
+
1632
+ if (top > axisy.max) {
1633
+ top = axisy.max;
1634
+ drawTop = false;
1635
+ }
1636
+
1637
+ left = axisx.p2c(left);
1638
+ bottom = axisy.p2c(bottom);
1639
+ right = axisx.p2c(right);
1640
+ top = axisy.p2c(top);
1641
+
1642
+ // fill the bar
1643
+ if (fillStyleCallback) {
1644
+ c.beginPath();
1645
+ c.moveTo(left, bottom);
1646
+ c.lineTo(left, top);
1647
+ c.lineTo(right, top);
1648
+ c.lineTo(right, bottom);
1649
+ c.fillStyle = fillStyleCallback(bottom, top);
1650
+ c.fill();
1651
+ }
1652
+
1653
+ // draw outline
1654
+ if (drawLeft || drawRight || drawTop || drawBottom) {
1655
+ c.beginPath();
1656
+
1657
+ // FIXME: inline moveTo is buggy with excanvas
1658
+ c.moveTo(left, bottom + offset);
1659
+ if (drawLeft)
1660
+ c.lineTo(left, top + offset);
1661
+ else
1662
+ c.moveTo(left, top + offset);
1663
+ if (drawTop)
1664
+ c.lineTo(right, top + offset);
1665
+ else
1666
+ c.moveTo(right, top + offset);
1667
+ if (drawRight)
1668
+ c.lineTo(right, bottom + offset);
1669
+ else
1670
+ c.moveTo(right, bottom + offset);
1671
+ if (drawBottom)
1672
+ c.lineTo(left, bottom + offset);
1673
+ else
1674
+ c.moveTo(left, bottom + offset);
1675
+ c.stroke();
1676
+ }
1677
+ }
1678
+
1679
+ function drawSeriesBars(series) {
1680
+ function plotBars(datapoints, barLeft, barRight, offset, fillStyleCallback, axisx, axisy) {
1681
+ var points = datapoints.points, ps = datapoints.pointsize;
1682
+
1683
+ for (var i = 0; i < points.length; i += ps) {
1684
+ if (points[i] == null)
1685
+ continue;
1686
+ drawBar(points[i], points[i + 1], points[i + 2], barLeft, barRight, offset, fillStyleCallback, axisx, axisy, ctx, series.bars.horizontal);
1687
+ }
1688
+ }
1689
+
1690
+ ctx.save();
1691
+ ctx.translate(plotOffset.left, plotOffset.top);
1692
+
1693
+ // FIXME: figure out a way to add shadows (for instance along the right edge)
1694
+ ctx.lineWidth = series.bars.lineWidth;
1695
+ ctx.strokeStyle = series.color;
1696
+ var barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth/2;
1697
+ var fillStyleCallback = series.bars.fill ? function (bottom, top) { return getFillStyle(series.bars, series.color, bottom, top); } : null;
1698
+ plotBars(series.datapoints, barLeft, barLeft + series.bars.barWidth, 0, fillStyleCallback, series.xaxis, series.yaxis);
1699
+ ctx.restore();
1700
+ }
1701
+
1702
+ function getFillStyle(filloptions, seriesColor, bottom, top) {
1703
+ var fill = filloptions.fill;
1704
+ if (!fill)
1705
+ return null;
1706
+
1707
+ if (filloptions.fillColor)
1708
+ return getColorOrGradient(filloptions.fillColor, bottom, top, seriesColor);
1709
+
1710
+ var c = $.color.parse(seriesColor);
1711
+ c.a = typeof fill == "number" ? fill : 0.4;
1712
+ c.normalize();
1713
+ return c.toString();
1714
+ }
1715
+
1716
+ function insertLegend() {
1717
+ placeholder.find(".legend").remove();
1718
+
1719
+ if (!options.legend.show)
1720
+ return;
1721
+
1722
+ var fragments = [], rowStarted = false,
1723
+ lf = options.legend.labelFormatter, s, label;
1724
+ for (i = 0; i < series.length; ++i) {
1725
+ s = series[i];
1726
+ label = s.label;
1727
+ if (!label)
1728
+ continue;
1729
+
1730
+ if (i % options.legend.noColumns == 0) {
1731
+ if (rowStarted)
1732
+ fragments.push('</tr>');
1733
+ fragments.push('<tr>');
1734
+ rowStarted = true;
1735
+ }
1736
+
1737
+ if (lf)
1738
+ label = lf(label, s);
1739
+
1740
+ fragments.push(
1741
+ '<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>' +
1742
+ '<td class="legendLabel">' + label + '</td>');
1743
+ }
1744
+ if (rowStarted)
1745
+ fragments.push('</tr>');
1746
+
1747
+ if (fragments.length == 0)
1748
+ return;
1749
+
1750
+ var table = '<table style="font-size:smaller;color:' + options.grid.color + '">' + fragments.join("") + '</table>';
1751
+ if (options.legend.container != null)
1752
+ $(options.legend.container).html(table);
1753
+ else {
1754
+ var pos = "",
1755
+ p = options.legend.position,
1756
+ m = options.legend.margin;
1757
+ if (m[0] == null)
1758
+ m = [m, m];
1759
+ if (p.charAt(0) == "n")
1760
+ pos += 'top:' + (m[1] + plotOffset.top) + 'px;';
1761
+ else if (p.charAt(0) == "s")
1762
+ pos += 'bottom:' + (m[1] + plotOffset.bottom) + 'px;';
1763
+ if (p.charAt(1) == "e")
1764
+ pos += 'right:' + (m[0] + plotOffset.right) + 'px;';
1765
+ else if (p.charAt(1) == "w")
1766
+ pos += 'left:' + (m[0] + plotOffset.left) + 'px;';
1767
+ var legend = $('<div class="legend">' + table.replace('style="', 'style="position:absolute;' + pos +';') + '</div>').appendTo(placeholder);
1768
+ if (options.legend.backgroundOpacity != 0.0) {
1769
+ // put in the transparent background
1770
+ // separately to avoid blended labels and
1771
+ // label boxes
1772
+ var c = options.legend.backgroundColor;
1773
+ if (c == null) {
1774
+ c = options.grid.backgroundColor;
1775
+ if (c && typeof c == "string")
1776
+ c = $.color.parse(c);
1777
+ else
1778
+ c = $.color.extract(legend, 'background-color');
1779
+ c.a = 1;
1780
+ c = c.toString();
1781
+ }
1782
+ var div = legend.children();
1783
+ $('<div style="position:absolute;width:' + div.width() + 'px;height:' + div.height() + 'px;' + pos +'background-color:' + c + ';"> </div>').prependTo(legend).css('opacity', options.legend.backgroundOpacity);
1784
+ }
1785
+ }
1786
+ }
1787
+
1788
+
1789
+ // interactive features
1790
+
1791
+ var highlights = [],
1792
+ redrawTimeout = null;
1793
+
1794
+ // returns the data item the mouse is over, or null if none is found
1795
+ function findNearbyItem(mouseX, mouseY, seriesFilter) {
1796
+ var maxDistance = options.grid.mouseActiveRadius,
1797
+ smallestDistance = maxDistance * maxDistance + 1,
1798
+ item = null, foundPoint = false, i, j;
1799
+
1800
+ for (i = 0; i < series.length; ++i) {
1801
+ if (!seriesFilter(series[i]))
1802
+ continue;
1803
+
1804
+ var s = series[i],
1805
+ axisx = s.xaxis,
1806
+ axisy = s.yaxis,
1807
+ points = s.datapoints.points,
1808
+ ps = s.datapoints.pointsize,
1809
+ mx = axisx.c2p(mouseX), // precompute some stuff to make the loop faster
1810
+ my = axisy.c2p(mouseY),
1811
+ maxx = maxDistance / axisx.scale,
1812
+ maxy = maxDistance / axisy.scale;
1813
+
1814
+ if (s.lines.show || s.points.show) {
1815
+ for (j = 0; j < points.length; j += ps) {
1816
+ var x = points[j], y = points[j + 1];
1817
+ if (x == null)
1818
+ continue;
1819
+
1820
+ // For points and lines, the cursor must be within a
1821
+ // certain distance to the data point
1822
+ if (x - mx > maxx || x - mx < -maxx ||
1823
+ y - my > maxy || y - my < -maxy)
1824
+ continue;
1825
+
1826
+ // We have to calculate distances in pixels, not in
1827
+ // data units, because the scales of the axes may be different
1828
+ var dx = Math.abs(axisx.p2c(x) - mouseX),
1829
+ dy = Math.abs(axisy.p2c(y) - mouseY),
1830
+ dist = dx * dx + dy * dy; // we save the sqrt
1831
+
1832
+ // use <= to ensure last point takes precedence
1833
+ // (last generally means on top of)
1834
+ if (dist <= smallestDistance) {
1835
+ smallestDistance = dist;
1836
+ item = [i, j / ps];
1837
+ }
1838
+ }
1839
+ }
1840
+
1841
+ if (s.bars.show && !item) { // no other point can be nearby
1842
+ var barLeft = s.bars.align == "left" ? 0 : -s.bars.barWidth/2,
1843
+ barRight = barLeft + s.bars.barWidth;
1844
+
1845
+ for (j = 0; j < points.length; j += ps) {
1846
+ var x = points[j], y = points[j + 1], b = points[j + 2];
1847
+ if (x == null)
1848
+ continue;
1849
+
1850
+ // for a bar graph, the cursor must be inside the bar
1851
+ if (series[i].bars.horizontal ?
1852
+ (mx <= Math.max(b, x) && mx >= Math.min(b, x) &&
1853
+ my >= y + barLeft && my <= y + barRight) :
1854
+ (mx >= x + barLeft && mx <= x + barRight &&
1855
+ my >= Math.min(b, y) && my <= Math.max(b, y)))
1856
+ item = [i, j / ps];
1857
+ }
1858
+ }
1859
+ }
1860
+
1861
+ if (item) {
1862
+ i = item[0];
1863
+ j = item[1];
1864
+ ps = series[i].datapoints.pointsize;
1865
+
1866
+ return { datapoint: series[i].datapoints.points.slice(j * ps, (j + 1) * ps),
1867
+ dataIndex: j,
1868
+ series: series[i],
1869
+ seriesIndex: i };
1870
+ }
1871
+
1872
+ return null;
1873
+ }
1874
+
1875
+ function onMouseMove(e) {
1876
+ if (options.grid.hoverable)
1877
+ triggerClickHoverEvent("plothover", e,
1878
+ function (s) { return s["hoverable"] != false; });
1879
+ }
1880
+
1881
+ function onClick(e) {
1882
+ triggerClickHoverEvent("plotclick", e,
1883
+ function (s) { return s["clickable"] != false; });
1884
+ }
1885
+
1886
+ // trigger click or hover event (they send the same parameters
1887
+ // so we share their code)
1888
+ function triggerClickHoverEvent(eventname, event, seriesFilter) {
1889
+ var offset = eventHolder.offset(),
1890
+ pos = { pageX: event.pageX, pageY: event.pageY },
1891
+ canvasX = event.pageX - offset.left - plotOffset.left,
1892
+ canvasY = event.pageY - offset.top - plotOffset.top;
1893
+
1894
+ if (axes.xaxis.used)
1895
+ pos.x = axes.xaxis.c2p(canvasX);
1896
+ if (axes.yaxis.used)
1897
+ pos.y = axes.yaxis.c2p(canvasY);
1898
+ if (axes.x2axis.used)
1899
+ pos.x2 = axes.x2axis.c2p(canvasX);
1900
+ if (axes.y2axis.used)
1901
+ pos.y2 = axes.y2axis.c2p(canvasY);
1902
+
1903
+ var item = findNearbyItem(canvasX, canvasY, seriesFilter);
1904
+
1905
+ if (item) {
1906
+ // fill in mouse pos for any listeners out there
1907
+ item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint[0]) + offset.left + plotOffset.left);
1908
+ item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top + plotOffset.top);
1909
+ }
1910
+
1911
+ if (options.grid.autoHighlight) {
1912
+ // clear auto-highlights
1913
+ for (var i = 0; i < highlights.length; ++i) {
1914
+ var h = highlights[i];
1915
+ if (h.auto == eventname &&
1916
+ !(item && h.series == item.series && h.point == item.datapoint))
1917
+ unhighlight(h.series, h.point);
1918
+ }
1919
+
1920
+ if (item)
1921
+ highlight(item.series, item.datapoint, eventname);
1922
+ }
1923
+
1924
+ placeholder.trigger(eventname, [ pos, item ]);
1925
+ }
1926
+
1927
+ function triggerRedrawOverlay() {
1928
+ if (!redrawTimeout)
1929
+ redrawTimeout = setTimeout(drawOverlay, 30);
1930
+ }
1931
+
1932
+ function drawOverlay() {
1933
+ redrawTimeout = null;
1934
+
1935
+ // draw highlights
1936
+ octx.save();
1937
+ octx.clearRect(0, 0, canvasWidth, canvasHeight);
1938
+ octx.translate(plotOffset.left, plotOffset.top);
1939
+
1940
+ var i, hi;
1941
+ for (i = 0; i < highlights.length; ++i) {
1942
+ hi = highlights[i];
1943
+
1944
+ if (hi.series.bars.show)
1945
+ drawBarHighlight(hi.series, hi.point);
1946
+ else
1947
+ drawPointHighlight(hi.series, hi.point);
1948
+ }
1949
+ octx.restore();
1950
+
1951
+ executeHooks(hooks.drawOverlay, [octx]);
1952
+ }
1953
+
1954
+ function highlight(s, point, auto) {
1955
+ if (typeof s == "number")
1956
+ s = series[s];
1957
+
1958
+ if (typeof point == "number")
1959
+ point = s.data[point];
1960
+
1961
+ var i = indexOfHighlight(s, point);
1962
+ if (i == -1) {
1963
+ highlights.push({ series: s, point: point, auto: auto });
1964
+
1965
+ triggerRedrawOverlay();
1966
+ }
1967
+ else if (!auto)
1968
+ highlights[i].auto = false;
1969
+ }
1970
+
1971
+ function unhighlight(s, point) {
1972
+ if (s == null && point == null) {
1973
+ highlights = [];
1974
+ triggerRedrawOverlay();
1975
+ }
1976
+
1977
+ if (typeof s == "number")
1978
+ s = series[s];
1979
+
1980
+ if (typeof point == "number")
1981
+ point = s.data[point];
1982
+
1983
+ var i = indexOfHighlight(s, point);
1984
+ if (i != -1) {
1985
+ highlights.splice(i, 1);
1986
+
1987
+ triggerRedrawOverlay();
1988
+ }
1989
+ }
1990
+
1991
+ function indexOfHighlight(s, p) {
1992
+ for (var i = 0; i < highlights.length; ++i) {
1993
+ var h = highlights[i];
1994
+ if (h.series == s && h.point[0] == p[0]
1995
+ && h.point[1] == p[1])
1996
+ return i;
1997
+ }
1998
+ return -1;
1999
+ }
2000
+
2001
+ function drawPointHighlight(series, point) {
2002
+ var x = point[0], y = point[1],
2003
+ axisx = series.xaxis, axisy = series.yaxis;
2004
+
2005
+ if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max)
2006
+ return;
2007
+
2008
+ var pointRadius = series.points.radius + series.points.lineWidth / 2;
2009
+ octx.lineWidth = pointRadius;
2010
+ octx.strokeStyle = $.color.parse(series.color).scale('a', 0.5).toString();
2011
+ var radius = 1.5 * pointRadius;
2012
+ octx.beginPath();
2013
+ octx.arc(axisx.p2c(x), axisy.p2c(y), radius, 0, 2 * Math.PI, false);
2014
+ octx.stroke();
2015
+ }
2016
+
2017
+ function drawBarHighlight(series, point) {
2018
+ octx.lineWidth = series.bars.lineWidth;
2019
+ octx.strokeStyle = $.color.parse(series.color).scale('a', 0.5).toString();
2020
+ var fillStyle = $.color.parse(series.color).scale('a', 0.5).toString();
2021
+ var barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth/2;
2022
+ drawBar(point[0], point[1], point[2] || 0, barLeft, barLeft + series.bars.barWidth,
2023
+ 0, function () { return fillStyle; }, series.xaxis, series.yaxis, octx, series.bars.horizontal);
2024
+ }
2025
+
2026
+ function getColorOrGradient(spec, bottom, top, defaultColor) {
2027
+ if (typeof spec == "string")
2028
+ return spec;
2029
+ else {
2030
+ // assume this is a gradient spec; IE currently only
2031
+ // supports a simple vertical gradient properly, so that's
2032
+ // what we support too
2033
+ var gradient = ctx.createLinearGradient(0, top, 0, bottom);
2034
+
2035
+ for (var i = 0, l = spec.colors.length; i < l; ++i) {
2036
+ var c = spec.colors[i];
2037
+ if (typeof c != "string") {
2038
+ c = $.color.parse(defaultColor).scale('rgb', c.brightness);
2039
+ c.a *= c.opacity;
2040
+ c = c.toString();
2041
+ }
2042
+ gradient.addColorStop(i / (l - 1), c);
2043
+ }
2044
+
2045
+ return gradient;
2046
+ }
2047
+ }
2048
+ }
2049
+
2050
+ $.plot = function(placeholder, data, options) {
2051
+ var plot = new Plot($(placeholder), data, options, $.plot.plugins);
2052
+ /*var t0 = new Date();
2053
+ var t1 = new Date();
2054
+ var tstr = "time used (msecs): " + (t1.getTime() - t0.getTime())
2055
+ if (window.console)
2056
+ console.log(tstr);
2057
+ else
2058
+ alert(tstr);*/
2059
+ return plot;
2060
+ };
2061
+
2062
+ $.plot.plugins = [];
2063
+
2064
+ // returns a string with the date d formatted according to fmt
2065
+ $.plot.formatDate = function(d, fmt, monthNames) {
2066
+ var leftPad = function(n) {
2067
+ n = "" + n;
2068
+ return n.length == 1 ? "0" + n : n;
2069
+ };
2070
+
2071
+ var r = [];
2072
+ var escape = false;
2073
+ var hours = d.getUTCHours();
2074
+ var isAM = hours < 12;
2075
+ if (monthNames == null)
2076
+ monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
2077
+
2078
+ if (fmt.search(/%p|%P/) != -1) {
2079
+ if (hours > 12) {
2080
+ hours = hours - 12;
2081
+ } else if (hours == 0) {
2082
+ hours = 12;
2083
+ }
2084
+ }
2085
+ for (var i = 0; i < fmt.length; ++i) {
2086
+ var c = fmt.charAt(i);
2087
+
2088
+ if (escape) {
2089
+ switch (c) {
2090
+ case 'h': c = "" + hours; break;
2091
+ case 'H': c = leftPad(hours); break;
2092
+ case 'M': c = leftPad(d.getUTCMinutes()); break;
2093
+ case 'S': c = leftPad(d.getUTCSeconds()); break;
2094
+ case 'd': c = "" + d.getUTCDate(); break;
2095
+ case 'm': c = "" + (d.getUTCMonth() + 1); break;
2096
+ case 'y': c = "" + d.getUTCFullYear(); break;
2097
+ case 'b': c = "" + monthNames[d.getUTCMonth()]; break;
2098
+ case 'p': c = (isAM) ? ("" + "am") : ("" + "pm"); break;
2099
+ case 'P': c = (isAM) ? ("" + "AM") : ("" + "PM"); break;
2100
+ }
2101
+ r.push(c);
2102
+ escape = false;
2103
+ }
2104
+ else {
2105
+ if (c == "%")
2106
+ escape = true;
2107
+ else
2108
+ r.push(c);
2109
+ }
2110
+ }
2111
+ return r.join("");
2112
+ };
2113
+
2114
+ // round to nearby lower multiple of base
2115
+ function floorInBase(n, base) {
2116
+ return base * Math.floor(n / base);
2117
+ }
2118
+
2119
+ })(jQuery);