radiant-race_results-extension 1.4.3 → 1.4.5

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 (107) hide show
  1. data/app/controllers/admin/race_instances_controller.rb +1 -1
  2. data/app/controllers/race_instances_controller.rb +7 -1
  3. data/app/controllers/race_performances_controller.rb +8 -0
  4. data/app/models/race.rb +7 -0
  5. data/app/models/race_checkpoint.rb +27 -1
  6. data/app/models/race_checkpoint_time.rb +33 -5
  7. data/app/models/race_instance.rb +38 -9
  8. data/app/models/race_performance.rb +81 -3
  9. data/app/views/admin/races/_form.html.haml +1 -2
  10. data/app/views/race_clubs/show.html.haml +3 -3
  11. data/app/views/race_instances/_results_header.html.haml +10 -0
  12. data/app/views/race_instances/_splits_header.html.haml +9 -0
  13. data/app/views/race_instances/show.html.haml +17 -8
  14. data/app/views/race_instances/splits.html.haml +19 -14
  15. data/app/views/race_performances/_performance.html.haml +12 -2
  16. data/app/views/race_performances/_splits.html.haml +30 -8
  17. data/app/views/race_performances/show.html.haml +18 -76
  18. data/app/views/races/show.html.haml +2 -1
  19. data/config/locales/en.yml +20 -0
  20. data/config/routes.rb +1 -1
  21. data/db/migrate/20111103150827_mapping_routes.rb +22 -0
  22. data/db/migrate/20111115150827_finish_checkpoint.rb +8 -0
  23. data/lib/race_tags.rb +3 -3
  24. data/lib/radiant-race_results-extension.rb +1 -1
  25. data/public/images/race_results/sorts.png +0 -0
  26. data/public/javascripts/flot/API.txt +1201 -0
  27. data/public/javascripts/flot/FAQ.txt +76 -0
  28. data/public/javascripts/flot/LICENSE.txt +22 -0
  29. data/public/javascripts/flot/Makefile +9 -0
  30. data/public/javascripts/flot/NEWS.txt +508 -0
  31. data/public/javascripts/flot/PLUGINS.txt +137 -0
  32. data/public/javascripts/flot/README.txt +90 -0
  33. data/public/javascripts/flot/examples/ajax.html +143 -0
  34. data/public/javascripts/flot/examples/annotating.html +75 -0
  35. data/public/javascripts/flot/examples/arrow-down.gif +0 -0
  36. data/public/javascripts/flot/examples/arrow-left.gif +0 -0
  37. data/public/javascripts/flot/examples/arrow-right.gif +0 -0
  38. data/public/javascripts/flot/examples/arrow-up.gif +0 -0
  39. data/public/javascripts/flot/examples/basic.html +38 -0
  40. data/public/javascripts/flot/examples/data-eu-gdp-growth-1.json +4 -0
  41. data/public/javascripts/flot/examples/data-eu-gdp-growth-2.json +4 -0
  42. data/public/javascripts/flot/examples/data-eu-gdp-growth-3.json +4 -0
  43. data/public/javascripts/flot/examples/data-eu-gdp-growth-4.json +4 -0
  44. data/public/javascripts/flot/examples/data-eu-gdp-growth-5.json +4 -0
  45. data/public/javascripts/flot/examples/data-eu-gdp-growth.json +4 -0
  46. data/public/javascripts/flot/examples/data-japan-gdp-growth.json +4 -0
  47. data/public/javascripts/flot/examples/data-usa-gdp-growth.json +4 -0
  48. data/public/javascripts/flot/examples/graph-types.html +75 -0
  49. data/public/javascripts/flot/examples/hs-2004-27-a-large_web.jpg +0 -0
  50. data/public/javascripts/flot/examples/image.html +45 -0
  51. data/public/javascripts/flot/examples/index.html +44 -0
  52. data/public/javascripts/flot/examples/interacting-axes.html +97 -0
  53. data/public/javascripts/flot/examples/interacting.html +93 -0
  54. data/public/javascripts/flot/examples/layout.css +6 -0
  55. data/public/javascripts/flot/examples/multiple-axes.html +60 -0
  56. data/public/javascripts/flot/examples/navigate.html +118 -0
  57. data/public/javascripts/flot/examples/percentiles.html +57 -0
  58. data/public/javascripts/flot/examples/pie.html +756 -0
  59. data/public/javascripts/flot/examples/realtime.html +83 -0
  60. data/public/javascripts/flot/examples/resize.html +61 -0
  61. data/public/javascripts/flot/examples/selection.html +114 -0
  62. data/public/javascripts/flot/examples/setting-options.html +61 -0
  63. data/public/javascripts/flot/examples/stacking.html +77 -0
  64. data/public/javascripts/flot/examples/symbols.html +49 -0
  65. data/public/javascripts/flot/examples/thresholding.html +54 -0
  66. data/public/javascripts/flot/examples/time.html +71 -0
  67. data/public/javascripts/flot/examples/tracking.html +95 -0
  68. data/public/javascripts/flot/examples/turning-series.html +98 -0
  69. data/public/javascripts/flot/examples/visitors.html +90 -0
  70. data/public/javascripts/flot/examples/zooming.html +98 -0
  71. data/public/javascripts/flot/excanvas.js +1427 -0
  72. data/public/javascripts/flot/excanvas.min.js +1 -0
  73. data/public/javascripts/flot/jquery.colorhelpers.js +179 -0
  74. data/public/javascripts/flot/jquery.colorhelpers.min.js +1 -0
  75. data/public/javascripts/flot/jquery.flot.crosshair.js +167 -0
  76. data/public/javascripts/flot/jquery.flot.crosshair.min.js +1 -0
  77. data/public/javascripts/flot/jquery.flot.fillbetween.js +183 -0
  78. data/public/javascripts/flot/jquery.flot.fillbetween.min.js +1 -0
  79. data/public/javascripts/flot/jquery.flot.image.js +238 -0
  80. data/public/javascripts/flot/jquery.flot.image.min.js +1 -0
  81. data/public/javascripts/flot/jquery.flot.js +2599 -0
  82. data/public/javascripts/flot/jquery.flot.min.js +6 -0
  83. data/public/javascripts/flot/jquery.flot.navigate.js +336 -0
  84. data/public/javascripts/flot/jquery.flot.navigate.min.js +1 -0
  85. data/public/javascripts/flot/jquery.flot.pie.js +750 -0
  86. data/public/javascripts/flot/jquery.flot.pie.min.js +1 -0
  87. data/public/javascripts/flot/jquery.flot.resize.js +60 -0
  88. data/public/javascripts/flot/jquery.flot.resize.min.js +1 -0
  89. data/public/javascripts/flot/jquery.flot.selection.js +344 -0
  90. data/public/javascripts/flot/jquery.flot.selection.min.js +1 -0
  91. data/public/javascripts/flot/jquery.flot.stack.js +184 -0
  92. data/public/javascripts/flot/jquery.flot.stack.min.js +1 -0
  93. data/public/javascripts/flot/jquery.flot.symbol.js +70 -0
  94. data/public/javascripts/flot/jquery.flot.symbol.min.js +1 -0
  95. data/public/javascripts/flot/jquery.flot.threshold.js +103 -0
  96. data/public/javascripts/flot/jquery.flot.threshold.min.js +1 -0
  97. data/public/javascripts/flot/jquery.js +8316 -0
  98. data/public/javascripts/flot/jquery.min.js +23 -0
  99. data/public/javascripts/jquery.qtip.js +2675 -0
  100. data/public/javascripts/jquery.sparkline.js +1271 -0
  101. data/public/javascripts/races.js +245 -0
  102. data/public/stylesheets/sass/admin/races.sass +65 -70
  103. data/public/stylesheets/sass/jquery.flot.sass +416 -0
  104. data/public/stylesheets/sass/race_results.sass +38 -2
  105. data/radiant-race_results-extension.gemspec +1 -1
  106. metadata +95 -11
  107. data/public/javascripts/tablesorter.js +0 -3
@@ -0,0 +1 @@
1
+ (function(b){var a={series:{fillBetween:null}};function c(f){function d(j,h){var g;for(g=0;g<h.length;++g){if(h[g].id==j.fillBetween){return h[g]}}if(typeof j.fillBetween=="number"){g=j.fillBetween;if(g<0||g>=h.length){return null}return h[g]}return null}function e(B,u,g){if(u.fillBetween==null){return}var p=d(u,B.getData());if(!p){return}var y=g.pointsize,E=g.points,h=p.datapoints.pointsize,x=p.datapoints.points,r=[],w,v,k,G,F,q,t=u.lines.show,o=y>2&&g.format[2].y,n=t&&u.lines.steps,D=true,C=0,A=0,z;while(true){if(C>=E.length){break}z=r.length;if(E[C]==null){for(m=0;m<y;++m){r.push(E[C+m])}C+=y}else{if(A>=x.length){if(!t){for(m=0;m<y;++m){r.push(E[C+m])}}C+=y}else{if(x[A]==null){for(m=0;m<y;++m){r.push(null)}D=true;A+=h}else{w=E[C];v=E[C+1];G=x[A];F=x[A+1];q=0;if(w==G){for(m=0;m<y;++m){r.push(E[C+m])}q=F;C+=y;A+=h}else{if(w>G){if(t&&C>0&&E[C-y]!=null){k=v+(E[C-y+1]-v)*(G-w)/(E[C-y]-w);r.push(G);r.push(k);for(m=2;m<y;++m){r.push(E[C+m])}q=F}A+=h}else{if(D&&t){C+=y;continue}for(m=0;m<y;++m){r.push(E[C+m])}if(t&&A>0&&x[A-h]!=null){q=F+(x[A-h+1]-F)*(w-G)/(x[A-h]-G)}C+=y}}D=false;if(z!=r.length&&o){r[z+2]=q}}}}if(n&&z!=r.length&&z>0&&r[z]!=null&&r[z]!=r[z-y]&&r[z+1]!=r[z-y+1]){for(m=0;m<y;++m){r[z+y+m]=r[z+m]}r[z+1]=r[z-y+1]}}g.points=r}f.hooks.processDatapoints.push(e)}b.plot.plugins.push({init:c,options:a,name:"fillbetween",version:"1.0"})})(jQuery);
@@ -0,0 +1,238 @@
1
+ /*
2
+ Flot plugin for plotting images, e.g. useful for putting ticks on a
3
+ prerendered complex visualization.
4
+
5
+ The data syntax is [[image, x1, y1, x2, y2], ...] where (x1, y1) and
6
+ (x2, y2) are where you intend the two opposite corners of the image to
7
+ end up in the plot. Image must be a fully loaded Javascript image (you
8
+ can make one with new Image()). If the image is not complete, it's
9
+ skipped when plotting.
10
+
11
+ There are two helpers included for retrieving images. The easiest work
12
+ the way that you put in URLs instead of images in the data (like
13
+ ["myimage.png", 0, 0, 10, 10]), then call $.plot.image.loadData(data,
14
+ options, callback) where data and options are the same as you pass in
15
+ to $.plot. This loads the images, replaces the URLs in the data with
16
+ the corresponding images and calls "callback" when all images are
17
+ loaded (or failed loading). In the callback, you can then call $.plot
18
+ with the data set. See the included example.
19
+
20
+ A more low-level helper, $.plot.image.load(urls, callback) is also
21
+ included. Given a list of URLs, it calls callback with an object
22
+ mapping from URL to Image object when all images are loaded or have
23
+ failed loading.
24
+
25
+ Options for the plugin are
26
+
27
+ series: {
28
+ images: {
29
+ show: boolean
30
+ anchor: "corner" or "center"
31
+ alpha: [0,1]
32
+ }
33
+ }
34
+
35
+ which can be specified for a specific series
36
+
37
+ $.plot($("#placeholder"), [{ data: [ ... ], images: { ... } ])
38
+
39
+ Note that because the data format is different from usual data points,
40
+ you can't use images with anything else in a specific data series.
41
+
42
+ Setting "anchor" to "center" causes the pixels in the image to be
43
+ anchored at the corner pixel centers inside of at the pixel corners,
44
+ effectively letting half a pixel stick out to each side in the plot.
45
+
46
+
47
+ A possible future direction could be support for tiling for large
48
+ images (like Google Maps).
49
+
50
+ */
51
+
52
+ (function ($) {
53
+ var options = {
54
+ series: {
55
+ images: {
56
+ show: false,
57
+ alpha: 1,
58
+ anchor: "corner" // or "center"
59
+ }
60
+ }
61
+ };
62
+
63
+ $.plot.image = {};
64
+
65
+ $.plot.image.loadDataImages = function (series, options, callback) {
66
+ var urls = [], points = [];
67
+
68
+ var defaultShow = options.series.images.show;
69
+
70
+ $.each(series, function (i, s) {
71
+ if (!(defaultShow || s.images.show))
72
+ return;
73
+
74
+ if (s.data)
75
+ s = s.data;
76
+
77
+ $.each(s, function (i, p) {
78
+ if (typeof p[0] == "string") {
79
+ urls.push(p[0]);
80
+ points.push(p);
81
+ }
82
+ });
83
+ });
84
+
85
+ $.plot.image.load(urls, function (loadedImages) {
86
+ $.each(points, function (i, p) {
87
+ var url = p[0];
88
+ if (loadedImages[url])
89
+ p[0] = loadedImages[url];
90
+ });
91
+
92
+ callback();
93
+ });
94
+ }
95
+
96
+ $.plot.image.load = function (urls, callback) {
97
+ var missing = urls.length, loaded = {};
98
+ if (missing == 0)
99
+ callback({});
100
+
101
+ $.each(urls, function (i, url) {
102
+ var handler = function () {
103
+ --missing;
104
+
105
+ loaded[url] = this;
106
+
107
+ if (missing == 0)
108
+ callback(loaded);
109
+ };
110
+
111
+ $('<img />').load(handler).error(handler).attr('src', url);
112
+ });
113
+ }
114
+
115
+ function drawSeries(plot, ctx, series) {
116
+ var plotOffset = plot.getPlotOffset();
117
+
118
+ if (!series.images || !series.images.show)
119
+ return;
120
+
121
+ var points = series.datapoints.points,
122
+ ps = series.datapoints.pointsize;
123
+
124
+ for (var i = 0; i < points.length; i += ps) {
125
+ var img = points[i],
126
+ x1 = points[i + 1], y1 = points[i + 2],
127
+ x2 = points[i + 3], y2 = points[i + 4],
128
+ xaxis = series.xaxis, yaxis = series.yaxis,
129
+ tmp;
130
+
131
+ // actually we should check img.complete, but it
132
+ // appears to be a somewhat unreliable indicator in
133
+ // IE6 (false even after load event)
134
+ if (!img || img.width <= 0 || img.height <= 0)
135
+ continue;
136
+
137
+ if (x1 > x2) {
138
+ tmp = x2;
139
+ x2 = x1;
140
+ x1 = tmp;
141
+ }
142
+ if (y1 > y2) {
143
+ tmp = y2;
144
+ y2 = y1;
145
+ y1 = tmp;
146
+ }
147
+
148
+ // if the anchor is at the center of the pixel, expand the
149
+ // image by 1/2 pixel in each direction
150
+ if (series.images.anchor == "center") {
151
+ tmp = 0.5 * (x2-x1) / (img.width - 1);
152
+ x1 -= tmp;
153
+ x2 += tmp;
154
+ tmp = 0.5 * (y2-y1) / (img.height - 1);
155
+ y1 -= tmp;
156
+ y2 += tmp;
157
+ }
158
+
159
+ // clip
160
+ if (x1 == x2 || y1 == y2 ||
161
+ x1 >= xaxis.max || x2 <= xaxis.min ||
162
+ y1 >= yaxis.max || y2 <= yaxis.min)
163
+ continue;
164
+
165
+ var sx1 = 0, sy1 = 0, sx2 = img.width, sy2 = img.height;
166
+ if (x1 < xaxis.min) {
167
+ sx1 += (sx2 - sx1) * (xaxis.min - x1) / (x2 - x1);
168
+ x1 = xaxis.min;
169
+ }
170
+
171
+ if (x2 > xaxis.max) {
172
+ sx2 += (sx2 - sx1) * (xaxis.max - x2) / (x2 - x1);
173
+ x2 = xaxis.max;
174
+ }
175
+
176
+ if (y1 < yaxis.min) {
177
+ sy2 += (sy1 - sy2) * (yaxis.min - y1) / (y2 - y1);
178
+ y1 = yaxis.min;
179
+ }
180
+
181
+ if (y2 > yaxis.max) {
182
+ sy1 += (sy1 - sy2) * (yaxis.max - y2) / (y2 - y1);
183
+ y2 = yaxis.max;
184
+ }
185
+
186
+ x1 = xaxis.p2c(x1);
187
+ x2 = xaxis.p2c(x2);
188
+ y1 = yaxis.p2c(y1);
189
+ y2 = yaxis.p2c(y2);
190
+
191
+ // the transformation may have swapped us
192
+ if (x1 > x2) {
193
+ tmp = x2;
194
+ x2 = x1;
195
+ x1 = tmp;
196
+ }
197
+ if (y1 > y2) {
198
+ tmp = y2;
199
+ y2 = y1;
200
+ y1 = tmp;
201
+ }
202
+
203
+ tmp = ctx.globalAlpha;
204
+ ctx.globalAlpha *= series.images.alpha;
205
+ ctx.drawImage(img,
206
+ sx1, sy1, sx2 - sx1, sy2 - sy1,
207
+ x1 + plotOffset.left, y1 + plotOffset.top,
208
+ x2 - x1, y2 - y1);
209
+ ctx.globalAlpha = tmp;
210
+ }
211
+ }
212
+
213
+ function processRawData(plot, series, data, datapoints) {
214
+ if (!series.images.show)
215
+ return;
216
+
217
+ // format is Image, x1, y1, x2, y2 (opposite corners)
218
+ datapoints.format = [
219
+ { required: true },
220
+ { x: true, number: true, required: true },
221
+ { y: true, number: true, required: true },
222
+ { x: true, number: true, required: true },
223
+ { y: true, number: true, required: true }
224
+ ];
225
+ }
226
+
227
+ function init(plot) {
228
+ plot.hooks.processRawData.push(processRawData);
229
+ plot.hooks.drawSeries.push(drawSeries);
230
+ }
231
+
232
+ $.plot.plugins.push({
233
+ init: init,
234
+ options: options,
235
+ name: 'image',
236
+ version: '1.1'
237
+ });
238
+ })(jQuery);
@@ -0,0 +1 @@
1
+ (function(c){var a={series:{images:{show:false,alpha:1,anchor:"corner"}}};c.plot.image={};c.plot.image.loadDataImages=function(g,f,k){var j=[],h=[];var i=f.series.images.show;c.each(g,function(l,m){if(!(i||m.images.show)){return}if(m.data){m=m.data}c.each(m,function(n,o){if(typeof o[0]=="string"){j.push(o[0]);h.push(o)}})});c.plot.image.load(j,function(l){c.each(h,function(n,o){var m=o[0];if(l[m]){o[0]=l[m]}});k()})};c.plot.image.load=function(h,i){var g=h.length,f={};if(g==0){i({})}c.each(h,function(k,j){var l=function(){--g;f[j]=this;if(g==0){i(f)}};c("<img />").load(l).error(l).attr("src",j)})};function d(q,o,l){var m=q.getPlotOffset();if(!l.images||!l.images.show){return}var r=l.datapoints.points,n=l.datapoints.pointsize;for(var t=0;t<r.length;t+=n){var y=r[t],w=r[t+1],g=r[t+2],v=r[t+3],f=r[t+4],h=l.xaxis,u=l.yaxis,x;if(!y||y.width<=0||y.height<=0){continue}if(w>v){x=v;v=w;w=x}if(g>f){x=f;f=g;g=x}if(l.images.anchor=="center"){x=0.5*(v-w)/(y.width-1);w-=x;v+=x;x=0.5*(f-g)/(y.height-1);g-=x;f+=x}if(w==v||g==f||w>=h.max||v<=h.min||g>=u.max||f<=u.min){continue}var k=0,s=0,j=y.width,p=y.height;if(w<h.min){k+=(j-k)*(h.min-w)/(v-w);w=h.min}if(v>h.max){j+=(j-k)*(h.max-v)/(v-w);v=h.max}if(g<u.min){p+=(s-p)*(u.min-g)/(f-g);g=u.min}if(f>u.max){s+=(s-p)*(u.max-f)/(f-g);f=u.max}w=h.p2c(w);v=h.p2c(v);g=u.p2c(g);f=u.p2c(f);if(w>v){x=v;v=w;w=x}if(g>f){x=f;f=g;g=x}x=o.globalAlpha;o.globalAlpha*=l.images.alpha;o.drawImage(y,k,s,j-k,p-s,w+m.left,g+m.top,v-w,f-g);o.globalAlpha=x}}function b(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(b);f.hooks.drawSeries.push(d)}c.plot.plugins.push({init:e,options:a,name:"image",version:"1.1"})})(jQuery);
@@ -0,0 +1,2599 @@
1
+ /*! Javascript plotting library for jQuery, v. 0.7.
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.1.
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() return the same modified object
26
+ * instead of making a new one.
27
+ *
28
+ * V. 1.1: Fix error handling so e.g. parsing an empty string does
29
+ * produce a color rather than just crashing.
30
+ */
31
+ (function(B){B.color={};B.color.make=function(F,E,C,D){var G={};G.r=F||0;G.g=E||0;G.b=C||0;G.a=D!=null?D:1;G.add=function(J,I){for(var H=0;H<J.length;++H){G[J.charAt(H)]+=I}return G.normalize()};G.scale=function(J,I){for(var H=0;H<J.length;++H){G[J.charAt(H)]*=I}return G.normalize()};G.toString=function(){if(G.a>=1){return"rgb("+[G.r,G.g,G.b].join(",")+")"}else{return"rgba("+[G.r,G.g,G.b,G.a].join(",")+")"}};G.normalize=function(){function H(J,K,I){return K<J?J:(K>I?I:K)}G.r=H(0,parseInt(G.r),255);G.g=H(0,parseInt(G.g),255);G.b=H(0,parseInt(G.b),255);G.a=H(0,G.a,1);return G};G.clone=function(){return B.color.make(G.r,G.b,G.g,G.a)};return G.normalize()};B.color.extract=function(D,C){var E;do{E=D.css(C).toLowerCase();if(E!=""&&E!="transparent"){break}D=D.parent()}while(!B.nodeName(D.get(0),"body"));if(E=="rgba(0, 0, 0, 0)"){E="transparent"}return B.color.parse(E)};B.color.parse=function(F){var E,C=B.color.make;if(E=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(F)){return C(parseInt(E[1],10),parseInt(E[2],10),parseInt(E[3],10))}if(E=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(F)){return C(parseInt(E[1],10),parseInt(E[2],10),parseInt(E[3],10),parseFloat(E[4]))}if(E=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(F)){return C(parseFloat(E[1])*2.55,parseFloat(E[2])*2.55,parseFloat(E[3])*2.55)}if(E=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(F)){return C(parseFloat(E[1])*2.55,parseFloat(E[2])*2.55,parseFloat(E[3])*2.55,parseFloat(E[4]))}if(E=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(F)){return C(parseInt(E[1],16),parseInt(E[2],16),parseInt(E[3],16))}if(E=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(F)){return C(parseInt(E[1]+E[1],16),parseInt(E[2]+E[2],16),parseInt(E[3]+E[3],16))}var D=B.trim(F).toLowerCase();if(D=="transparent"){return C(255,255,255,0)}else{E=A[D]||[0,0,0];return C(E[0],E[1],E[2])}};var A={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery);
32
+
33
+ // the actual Flot code
34
+ (function($) {
35
+ function Plot(placeholder, data_, options_, plugins) {
36
+ // data is on the form:
37
+ // [ series1, series2 ... ]
38
+ // where series is either just the data as [ [x1, y1], [x2, y2], ... ]
39
+ // or { data: [ [x1, y1], [x2, y2], ... ], label: "some label", ... }
40
+
41
+ var series = [],
42
+ options = {
43
+ // the color theme used for graphs
44
+ colors: ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"],
45
+ legend: {
46
+ show: true,
47
+ noColumns: 1, // number of colums in legend table
48
+ labelFormatter: null, // fn: string -> string
49
+ labelBoxBorderColor: "#ccc", // border color for the little label boxes
50
+ container: null, // container (as jQuery object) to put legend in, null means default on top of graph
51
+ position: "ne", // position of default legend container within plot
52
+ margin: 5, // distance from grid edge to default legend container within plot
53
+ backgroundColor: null, // null means auto-detect
54
+ backgroundOpacity: 0.85 // set to 0 to avoid background
55
+ },
56
+ xaxis: {
57
+ show: null, // null = auto-detect, true = always, false = never
58
+ position: "bottom", // or "top"
59
+ mode: null, // null or "time"
60
+ color: null, // base color, labels, ticks
61
+ tickColor: null, // possibly different color of ticks, e.g. "rgba(0,0,0,0.15)"
62
+ transform: null, // null or f: number -> number to transform axis
63
+ inverseTransform: null, // if transform is set, this should be the inverse function
64
+ min: null, // min. value to show, null means set automatically
65
+ max: null, // max. value to show, null means set automatically
66
+ autoscaleMargin: null, // margin in % to add if auto-setting min/max
67
+ ticks: null, // either [1, 3] or [[1, "a"], 3] or (fn: axis info -> ticks) or app. number of ticks for auto-ticks
68
+ tickFormatter: null, // fn: number -> string
69
+ labelWidth: null, // size of tick labels in pixels
70
+ labelHeight: null,
71
+ reserveSpace: null, // whether to reserve space even if axis isn't shown
72
+ tickLength: null, // size in pixels of ticks, or "full" for whole line
73
+ alignTicksWithAxis: null, // axis number or null for no sync
74
+
75
+ // mode specific options
76
+ tickDecimals: null, // no. of decimals, null means auto
77
+ tickSize: null, // number or [number, "unit"]
78
+ minTickSize: null, // number or [number, "unit"]
79
+ monthNames: null, // list of names of months
80
+ timeformat: null, // format string to use
81
+ twelveHourClock: false // 12 or 24 time in time mode
82
+ },
83
+ yaxis: {
84
+ autoscaleMargin: 0.02,
85
+ position: "left" // or "right"
86
+ },
87
+ xaxes: [],
88
+ yaxes: [],
89
+ series: {
90
+ points: {
91
+ show: false,
92
+ radius: 3,
93
+ lineWidth: 2, // in pixels
94
+ fill: true,
95
+ fillColor: "#ffffff",
96
+ symbol: "circle" // or callback
97
+ },
98
+ lines: {
99
+ // we don't put in show: false so we can see
100
+ // whether lines were actively disabled
101
+ lineWidth: 2, // in pixels
102
+ fill: false,
103
+ fillColor: null,
104
+ steps: false
105
+ },
106
+ bars: {
107
+ show: false,
108
+ lineWidth: 2, // in pixels
109
+ barWidth: 1, // in units of the x axis
110
+ fill: true,
111
+ fillColor: null,
112
+ align: "left", // or "center"
113
+ horizontal: false
114
+ },
115
+ shadowSize: 3
116
+ },
117
+ grid: {
118
+ show: true,
119
+ aboveData: false,
120
+ color: "#545454", // primary color used for outline and labels
121
+ backgroundColor: null, // null for transparent, else color
122
+ borderColor: null, // set if different from the grid color
123
+ tickColor: null, // color for the ticks, e.g. "rgba(0,0,0,0.15)"
124
+ labelMargin: 5, // in pixels
125
+ axisMargin: 8, // in pixels
126
+ borderWidth: 2, // in pixels
127
+ minBorderMargin: null, // in pixels, null means taken from points radius
128
+ markings: null, // array of ranges or fn: axes -> array of ranges
129
+ markingsColor: "#f4f4f4",
130
+ markingsLineWidth: 2,
131
+ // interactive stuff
132
+ clickable: false,
133
+ hoverable: false,
134
+ autoHighlight: true, // highlight in case mouse is near
135
+ mouseActiveRadius: 10 // how far the mouse can be away to activate an item
136
+ },
137
+ hooks: {}
138
+ },
139
+ canvas = null, // the canvas for the plot itself
140
+ overlay = null, // canvas for interactive stuff on top of plot
141
+ eventHolder = null, // jQuery object that events should be bound to
142
+ ctx = null, octx = null,
143
+ xaxes = [], yaxes = [],
144
+ plotOffset = { left: 0, right: 0, top: 0, bottom: 0},
145
+ canvasWidth = 0, canvasHeight = 0,
146
+ plotWidth = 0, plotHeight = 0,
147
+ hooks = {
148
+ processOptions: [],
149
+ processRawData: [],
150
+ processDatapoints: [],
151
+ drawSeries: [],
152
+ draw: [],
153
+ bindEvents: [],
154
+ drawOverlay: [],
155
+ shutdown: []
156
+ },
157
+ plot = this;
158
+
159
+ // public functions
160
+ plot.setData = setData;
161
+ plot.setupGrid = setupGrid;
162
+ plot.draw = draw;
163
+ plot.getPlaceholder = function() { return placeholder; };
164
+ plot.getCanvas = function() { return canvas; };
165
+ plot.getPlotOffset = function() { return plotOffset; };
166
+ plot.width = function () { return plotWidth; };
167
+ plot.height = function () { return plotHeight; };
168
+ plot.offset = function () {
169
+ var o = eventHolder.offset();
170
+ o.left += plotOffset.left;
171
+ o.top += plotOffset.top;
172
+ return o;
173
+ };
174
+ plot.getData = function () { return series; };
175
+ plot.getAxes = function () {
176
+ var res = {}, i;
177
+ $.each(xaxes.concat(yaxes), function (_, axis) {
178
+ if (axis)
179
+ res[axis.direction + (axis.n != 1 ? axis.n : "") + "axis"] = axis;
180
+ });
181
+ return res;
182
+ };
183
+ plot.getXAxes = function () { return xaxes; };
184
+ plot.getYAxes = function () { return yaxes; };
185
+ plot.c2p = canvasToAxisCoords;
186
+ plot.p2c = axisToCanvasCoords;
187
+ plot.getOptions = function () { return options; };
188
+ plot.highlight = highlight;
189
+ plot.unhighlight = unhighlight;
190
+ plot.triggerRedrawOverlay = triggerRedrawOverlay;
191
+ plot.pointOffset = function(point) {
192
+ return {
193
+ left: parseInt(xaxes[axisNumber(point, "x") - 1].p2c(+point.x) + plotOffset.left),
194
+ top: parseInt(yaxes[axisNumber(point, "y") - 1].p2c(+point.y) + plotOffset.top)
195
+ };
196
+ };
197
+ plot.shutdown = shutdown;
198
+ plot.resize = function () {
199
+ getCanvasDimensions();
200
+ resizeCanvas(canvas);
201
+ resizeCanvas(overlay);
202
+ };
203
+
204
+ // public attributes
205
+ plot.hooks = hooks;
206
+
207
+ // initialize
208
+ initPlugins(plot);
209
+ parseOptions(options_);
210
+ setupCanvases();
211
+ setData(data_);
212
+ setupGrid();
213
+ draw();
214
+ bindEvents();
215
+
216
+
217
+ function executeHooks(hook, args) {
218
+ args = [plot].concat(args);
219
+ for (var i = 0; i < hook.length; ++i)
220
+ hook[i].apply(this, args);
221
+ }
222
+
223
+ function initPlugins() {
224
+ for (var i = 0; i < plugins.length; ++i) {
225
+ var p = plugins[i];
226
+ p.init(plot);
227
+ if (p.options)
228
+ $.extend(true, options, p.options);
229
+ }
230
+ }
231
+
232
+ function parseOptions(opts) {
233
+ var i;
234
+
235
+ $.extend(true, options, opts);
236
+
237
+ if (options.xaxis.color == null)
238
+ options.xaxis.color = options.grid.color;
239
+ if (options.yaxis.color == null)
240
+ options.yaxis.color = options.grid.color;
241
+
242
+ if (options.xaxis.tickColor == null) // backwards-compatibility
243
+ options.xaxis.tickColor = options.grid.tickColor;
244
+ if (options.yaxis.tickColor == null) // backwards-compatibility
245
+ options.yaxis.tickColor = options.grid.tickColor;
246
+
247
+ if (options.grid.borderColor == null)
248
+ options.grid.borderColor = options.grid.color;
249
+ if (options.grid.tickColor == null)
250
+ options.grid.tickColor = $.color.parse(options.grid.color).scale('a', 0.22).toString();
251
+
252
+ // fill in defaults in axes, copy at least always the
253
+ // first as the rest of the code assumes it'll be there
254
+ for (i = 0; i < Math.max(1, options.xaxes.length); ++i)
255
+ options.xaxes[i] = $.extend(true, {}, options.xaxis, options.xaxes[i]);
256
+ for (i = 0; i < Math.max(1, options.yaxes.length); ++i)
257
+ options.yaxes[i] = $.extend(true, {}, options.yaxis, options.yaxes[i]);
258
+
259
+ // backwards compatibility, to be removed in future
260
+ if (options.xaxis.noTicks && options.xaxis.ticks == null)
261
+ options.xaxis.ticks = options.xaxis.noTicks;
262
+ if (options.yaxis.noTicks && options.yaxis.ticks == null)
263
+ options.yaxis.ticks = options.yaxis.noTicks;
264
+ if (options.x2axis) {
265
+ options.xaxes[1] = $.extend(true, {}, options.xaxis, options.x2axis);
266
+ options.xaxes[1].position = "top";
267
+ }
268
+ if (options.y2axis) {
269
+ options.yaxes[1] = $.extend(true, {}, options.yaxis, options.y2axis);
270
+ options.yaxes[1].position = "right";
271
+ }
272
+ if (options.grid.coloredAreas)
273
+ options.grid.markings = options.grid.coloredAreas;
274
+ if (options.grid.coloredAreasColor)
275
+ options.grid.markingsColor = options.grid.coloredAreasColor;
276
+ if (options.lines)
277
+ $.extend(true, options.series.lines, options.lines);
278
+ if (options.points)
279
+ $.extend(true, options.series.points, options.points);
280
+ if (options.bars)
281
+ $.extend(true, options.series.bars, options.bars);
282
+ if (options.shadowSize != null)
283
+ options.series.shadowSize = options.shadowSize;
284
+
285
+ // save options on axes for future reference
286
+ for (i = 0; i < options.xaxes.length; ++i)
287
+ getOrCreateAxis(xaxes, i + 1).options = options.xaxes[i];
288
+ for (i = 0; i < options.yaxes.length; ++i)
289
+ getOrCreateAxis(yaxes, i + 1).options = options.yaxes[i];
290
+
291
+ // add hooks from options
292
+ for (var n in hooks)
293
+ if (options.hooks[n] && options.hooks[n].length)
294
+ hooks[n] = hooks[n].concat(options.hooks[n]);
295
+
296
+ executeHooks(hooks.processOptions, [options]);
297
+ }
298
+
299
+ function setData(d) {
300
+ series = parseData(d);
301
+ fillInSeriesOptions();
302
+ processData();
303
+ }
304
+
305
+ function parseData(d) {
306
+ var res = [];
307
+ for (var i = 0; i < d.length; ++i) {
308
+ var s = $.extend(true, {}, options.series);
309
+
310
+ if (d[i].data != null) {
311
+ s.data = d[i].data; // move the data instead of deep-copy
312
+ delete d[i].data;
313
+
314
+ $.extend(true, s, d[i]);
315
+
316
+ d[i].data = s.data;
317
+ }
318
+ else
319
+ s.data = d[i];
320
+ res.push(s);
321
+ }
322
+
323
+ return res;
324
+ }
325
+
326
+ function axisNumber(obj, coord) {
327
+ var a = obj[coord + "axis"];
328
+ if (typeof a == "object") // if we got a real axis, extract number
329
+ a = a.n;
330
+ if (typeof a != "number")
331
+ a = 1; // default to first axis
332
+ return a;
333
+ }
334
+
335
+ function allAxes() {
336
+ // return flat array without annoying null entries
337
+ return $.grep(xaxes.concat(yaxes), function (a) { return a; });
338
+ }
339
+
340
+ function canvasToAxisCoords(pos) {
341
+ // return an object with x/y corresponding to all used axes
342
+ var res = {}, i, axis;
343
+ for (i = 0; i < xaxes.length; ++i) {
344
+ axis = xaxes[i];
345
+ if (axis && axis.used)
346
+ res["x" + axis.n] = axis.c2p(pos.left);
347
+ }
348
+
349
+ for (i = 0; i < yaxes.length; ++i) {
350
+ axis = yaxes[i];
351
+ if (axis && axis.used)
352
+ res["y" + axis.n] = axis.c2p(pos.top);
353
+ }
354
+
355
+ if (res.x1 !== undefined)
356
+ res.x = res.x1;
357
+ if (res.y1 !== undefined)
358
+ res.y = res.y1;
359
+
360
+ return res;
361
+ }
362
+
363
+ function axisToCanvasCoords(pos) {
364
+ // get canvas coords from the first pair of x/y found in pos
365
+ var res = {}, i, axis, key;
366
+
367
+ for (i = 0; i < xaxes.length; ++i) {
368
+ axis = xaxes[i];
369
+ if (axis && axis.used) {
370
+ key = "x" + axis.n;
371
+ if (pos[key] == null && axis.n == 1)
372
+ key = "x";
373
+
374
+ if (pos[key] != null) {
375
+ res.left = axis.p2c(pos[key]);
376
+ break;
377
+ }
378
+ }
379
+ }
380
+
381
+ for (i = 0; i < yaxes.length; ++i) {
382
+ axis = yaxes[i];
383
+ if (axis && axis.used) {
384
+ key = "y" + axis.n;
385
+ if (pos[key] == null && axis.n == 1)
386
+ key = "y";
387
+
388
+ if (pos[key] != null) {
389
+ res.top = axis.p2c(pos[key]);
390
+ break;
391
+ }
392
+ }
393
+ }
394
+
395
+ return res;
396
+ }
397
+
398
+ function getOrCreateAxis(axes, number) {
399
+ if (!axes[number - 1])
400
+ axes[number - 1] = {
401
+ n: number, // save the number for future reference
402
+ direction: axes == xaxes ? "x" : "y",
403
+ options: $.extend(true, {}, axes == xaxes ? options.xaxis : options.yaxis)
404
+ };
405
+
406
+ return axes[number - 1];
407
+ }
408
+
409
+ function fillInSeriesOptions() {
410
+ var i;
411
+
412
+ // collect what we already got of colors
413
+ var neededColors = series.length,
414
+ usedColors = [],
415
+ assignedColors = [];
416
+ for (i = 0; i < series.length; ++i) {
417
+ var sc = series[i].color;
418
+ if (sc != null) {
419
+ --neededColors;
420
+ if (typeof sc == "number")
421
+ assignedColors.push(sc);
422
+ else
423
+ usedColors.push($.color.parse(series[i].color));
424
+ }
425
+ }
426
+
427
+ // we might need to generate more colors if higher indices
428
+ // are assigned
429
+ for (i = 0; i < assignedColors.length; ++i) {
430
+ neededColors = Math.max(neededColors, assignedColors[i] + 1);
431
+ }
432
+
433
+ // produce colors as needed
434
+ var colors = [], variation = 0;
435
+ i = 0;
436
+ while (colors.length < neededColors) {
437
+ var c;
438
+ if (options.colors.length == i) // check degenerate case
439
+ c = $.color.make(100, 100, 100);
440
+ else
441
+ c = $.color.parse(options.colors[i]);
442
+
443
+ // vary color if needed
444
+ var sign = variation % 2 == 1 ? -1 : 1;
445
+ c.scale('rgb', 1 + sign * Math.ceil(variation / 2) * 0.2)
446
+
447
+ // FIXME: if we're getting to close to something else,
448
+ // we should probably skip this one
449
+ colors.push(c);
450
+
451
+ ++i;
452
+ if (i >= options.colors.length) {
453
+ i = 0;
454
+ ++variation;
455
+ }
456
+ }
457
+
458
+ // fill in the options
459
+ var colori = 0, s;
460
+ for (i = 0; i < series.length; ++i) {
461
+ s = series[i];
462
+
463
+ // assign colors
464
+ if (s.color == null) {
465
+ s.color = colors[colori].toString();
466
+ ++colori;
467
+ }
468
+ else if (typeof s.color == "number")
469
+ s.color = colors[s.color].toString();
470
+
471
+ // turn on lines automatically in case nothing is set
472
+ if (s.lines.show == null) {
473
+ var v, show = true;
474
+ for (v in s)
475
+ if (s[v] && s[v].show) {
476
+ show = false;
477
+ break;
478
+ }
479
+ if (show)
480
+ s.lines.show = true;
481
+ }
482
+
483
+ // setup axes
484
+ s.xaxis = getOrCreateAxis(xaxes, axisNumber(s, "x"));
485
+ s.yaxis = getOrCreateAxis(yaxes, axisNumber(s, "y"));
486
+ }
487
+ }
488
+
489
+ function processData() {
490
+ var topSentry = Number.POSITIVE_INFINITY,
491
+ bottomSentry = Number.NEGATIVE_INFINITY,
492
+ fakeInfinity = Number.MAX_VALUE,
493
+ i, j, k, m, length,
494
+ s, points, ps, x, y, axis, val, f, p;
495
+
496
+ function updateAxis(axis, min, max) {
497
+ if (min < axis.datamin && min != -fakeInfinity)
498
+ axis.datamin = min;
499
+ if (max > axis.datamax && max != fakeInfinity)
500
+ axis.datamax = max;
501
+ }
502
+
503
+ $.each(allAxes(), function (_, axis) {
504
+ // init axis
505
+ axis.datamin = topSentry;
506
+ axis.datamax = bottomSentry;
507
+ axis.used = false;
508
+ });
509
+
510
+ for (i = 0; i < series.length; ++i) {
511
+ s = series[i];
512
+ s.datapoints = { points: [] };
513
+
514
+ executeHooks(hooks.processRawData, [ s, s.data, s.datapoints ]);
515
+ }
516
+
517
+ // first pass: clean and copy data
518
+ for (i = 0; i < series.length; ++i) {
519
+ s = series[i];
520
+
521
+ var data = s.data, format = s.datapoints.format;
522
+
523
+ if (!format) {
524
+ format = [];
525
+ // find out how to copy
526
+ format.push({ x: true, number: true, required: true });
527
+ format.push({ y: true, number: true, required: true });
528
+
529
+ if (s.bars.show || (s.lines.show && s.lines.fill)) {
530
+ format.push({ y: true, number: true, required: false, defaultValue: 0 });
531
+ if (s.bars.horizontal) {
532
+ delete format[format.length - 1].y;
533
+ format[format.length - 1].x = true;
534
+ }
535
+ }
536
+
537
+ s.datapoints.format = format;
538
+ }
539
+
540
+ if (s.datapoints.pointsize != null)
541
+ continue; // already filled in
542
+
543
+ s.datapoints.pointsize = format.length;
544
+
545
+ ps = s.datapoints.pointsize;
546
+ points = s.datapoints.points;
547
+
548
+ insertSteps = s.lines.show && s.lines.steps;
549
+ s.xaxis.used = s.yaxis.used = true;
550
+
551
+ for (j = k = 0; j < data.length; ++j, k += ps) {
552
+ p = data[j];
553
+
554
+ var nullify = p == null;
555
+ if (!nullify) {
556
+ for (m = 0; m < ps; ++m) {
557
+ val = p[m];
558
+ f = format[m];
559
+
560
+ if (f) {
561
+ if (f.number && val != null) {
562
+ val = +val; // convert to number
563
+ if (isNaN(val))
564
+ val = null;
565
+ else if (val == Infinity)
566
+ val = fakeInfinity;
567
+ else if (val == -Infinity)
568
+ val = -fakeInfinity;
569
+ }
570
+
571
+ if (val == null) {
572
+ if (f.required)
573
+ nullify = true;
574
+
575
+ if (f.defaultValue != null)
576
+ val = f.defaultValue;
577
+ }
578
+ }
579
+
580
+ points[k + m] = val;
581
+ }
582
+ }
583
+
584
+ if (nullify) {
585
+ for (m = 0; m < ps; ++m) {
586
+ val = points[k + m];
587
+ if (val != null) {
588
+ f = format[m];
589
+ // extract min/max info
590
+ if (f.x)
591
+ updateAxis(s.xaxis, val, val);
592
+ if (f.y)
593
+ updateAxis(s.yaxis, val, val);
594
+ }
595
+ points[k + m] = null;
596
+ }
597
+ }
598
+ else {
599
+ // a little bit of line specific stuff that
600
+ // perhaps shouldn't be here, but lacking
601
+ // better means...
602
+ if (insertSteps && k > 0
603
+ && points[k - ps] != null
604
+ && points[k - ps] != points[k]
605
+ && points[k - ps + 1] != points[k + 1]) {
606
+ // copy the point to make room for a middle point
607
+ for (m = 0; m < ps; ++m)
608
+ points[k + ps + m] = points[k + m];
609
+
610
+ // middle point has same y
611
+ points[k + 1] = points[k - ps + 1];
612
+
613
+ // we've added a point, better reflect that
614
+ k += ps;
615
+ }
616
+ }
617
+ }
618
+ }
619
+
620
+ // give the hooks a chance to run
621
+ for (i = 0; i < series.length; ++i) {
622
+ s = series[i];
623
+
624
+ executeHooks(hooks.processDatapoints, [ s, s.datapoints]);
625
+ }
626
+
627
+ // second pass: find datamax/datamin for auto-scaling
628
+ for (i = 0; i < series.length; ++i) {
629
+ s = series[i];
630
+ points = s.datapoints.points,
631
+ ps = s.datapoints.pointsize;
632
+
633
+ var xmin = topSentry, ymin = topSentry,
634
+ xmax = bottomSentry, ymax = bottomSentry;
635
+
636
+ for (j = 0; j < points.length; j += ps) {
637
+ if (points[j] == null)
638
+ continue;
639
+
640
+ for (m = 0; m < ps; ++m) {
641
+ val = points[j + m];
642
+ f = format[m];
643
+ if (!f || val == fakeInfinity || val == -fakeInfinity)
644
+ continue;
645
+
646
+ if (f.x) {
647
+ if (val < xmin)
648
+ xmin = val;
649
+ if (val > xmax)
650
+ xmax = val;
651
+ }
652
+ if (f.y) {
653
+ if (val < ymin)
654
+ ymin = val;
655
+ if (val > ymax)
656
+ ymax = val;
657
+ }
658
+ }
659
+ }
660
+
661
+ if (s.bars.show) {
662
+ // make sure we got room for the bar on the dancing floor
663
+ var delta = s.bars.align == "left" ? 0 : -s.bars.barWidth/2;
664
+ if (s.bars.horizontal) {
665
+ ymin += delta;
666
+ ymax += delta + s.bars.barWidth;
667
+ }
668
+ else {
669
+ xmin += delta;
670
+ xmax += delta + s.bars.barWidth;
671
+ }
672
+ }
673
+
674
+ updateAxis(s.xaxis, xmin, xmax);
675
+ updateAxis(s.yaxis, ymin, ymax);
676
+ }
677
+
678
+ $.each(allAxes(), function (_, axis) {
679
+ if (axis.datamin == topSentry)
680
+ axis.datamin = null;
681
+ if (axis.datamax == bottomSentry)
682
+ axis.datamax = null;
683
+ });
684
+ }
685
+
686
+ function makeCanvas(skipPositioning, cls) {
687
+ var c = document.createElement('canvas');
688
+ c.className = cls;
689
+ c.width = canvasWidth;
690
+ c.height = canvasHeight;
691
+
692
+ if (!skipPositioning)
693
+ $(c).css({ position: 'absolute', left: 0, top: 0 });
694
+
695
+ $(c).appendTo(placeholder);
696
+
697
+ if (!c.getContext) // excanvas hack
698
+ c = window.G_vmlCanvasManager.initElement(c);
699
+
700
+ // used for resetting in case we get replotted
701
+ c.getContext("2d").save();
702
+
703
+ return c;
704
+ }
705
+
706
+ function getCanvasDimensions() {
707
+ canvasWidth = placeholder.width();
708
+ canvasHeight = placeholder.height();
709
+
710
+ if (canvasWidth <= 0 || canvasHeight <= 0)
711
+ throw "Invalid dimensions for plot, width = " + canvasWidth + ", height = " + canvasHeight;
712
+ }
713
+
714
+ function resizeCanvas(c) {
715
+ // resizing should reset the state (excanvas seems to be
716
+ // buggy though)
717
+ if (c.width != canvasWidth)
718
+ c.width = canvasWidth;
719
+
720
+ if (c.height != canvasHeight)
721
+ c.height = canvasHeight;
722
+
723
+ // so try to get back to the initial state (even if it's
724
+ // gone now, this should be safe according to the spec)
725
+ var cctx = c.getContext("2d");
726
+ cctx.restore();
727
+
728
+ // and save again
729
+ cctx.save();
730
+ }
731
+
732
+ function setupCanvases() {
733
+ var reused,
734
+ existingCanvas = placeholder.children("canvas.base"),
735
+ existingOverlay = placeholder.children("canvas.overlay");
736
+
737
+ if (existingCanvas.length == 0 || existingOverlay == 0) {
738
+ // init everything
739
+
740
+ placeholder.html(""); // make sure placeholder is clear
741
+
742
+ placeholder.css({ padding: 0 }); // padding messes up the positioning
743
+
744
+ if (placeholder.css("position") == 'static')
745
+ placeholder.css("position", "relative"); // for positioning labels and overlay
746
+
747
+ getCanvasDimensions();
748
+
749
+ canvas = makeCanvas(true, "base");
750
+ overlay = makeCanvas(false, "overlay"); // overlay canvas for interactive features
751
+
752
+ reused = false;
753
+ }
754
+ else {
755
+ // reuse existing elements
756
+
757
+ canvas = existingCanvas.get(0);
758
+ overlay = existingOverlay.get(0);
759
+
760
+ reused = true;
761
+ }
762
+
763
+ ctx = canvas.getContext("2d");
764
+ octx = overlay.getContext("2d");
765
+
766
+ // we include the canvas in the event holder too, because IE 7
767
+ // sometimes has trouble with the stacking order
768
+ eventHolder = $([overlay, canvas]);
769
+
770
+ if (reused) {
771
+ // run shutdown in the old plot object
772
+ placeholder.data("plot").shutdown();
773
+
774
+ // reset reused canvases
775
+ plot.resize();
776
+
777
+ // make sure overlay pixels are cleared (canvas is cleared when we redraw)
778
+ octx.clearRect(0, 0, canvasWidth, canvasHeight);
779
+
780
+ // then whack any remaining obvious garbage left
781
+ eventHolder.unbind();
782
+ placeholder.children().not([canvas, overlay]).remove();
783
+ }
784
+
785
+ // save in case we get replotted
786
+ placeholder.data("plot", plot);
787
+ }
788
+
789
+ function bindEvents() {
790
+ // bind events
791
+ if (options.grid.hoverable) {
792
+ eventHolder.mousemove(onMouseMove);
793
+ eventHolder.mouseleave(onMouseLeave);
794
+ }
795
+
796
+ if (options.grid.clickable)
797
+ eventHolder.click(onClick);
798
+
799
+ executeHooks(hooks.bindEvents, [eventHolder]);
800
+ }
801
+
802
+ function shutdown() {
803
+ if (redrawTimeout)
804
+ clearTimeout(redrawTimeout);
805
+
806
+ eventHolder.unbind("mousemove", onMouseMove);
807
+ eventHolder.unbind("mouseleave", onMouseLeave);
808
+ eventHolder.unbind("click", onClick);
809
+
810
+ executeHooks(hooks.shutdown, [eventHolder]);
811
+ }
812
+
813
+ function setTransformationHelpers(axis) {
814
+ // set helper functions on the axis, assumes plot area
815
+ // has been computed already
816
+
817
+ function identity(x) { return x; }
818
+
819
+ var s, m, t = axis.options.transform || identity,
820
+ it = axis.options.inverseTransform;
821
+
822
+ // precompute how much the axis is scaling a point
823
+ // in canvas space
824
+ if (axis.direction == "x") {
825
+ s = axis.scale = plotWidth / Math.abs(t(axis.max) - t(axis.min));
826
+ m = Math.min(t(axis.max), t(axis.min));
827
+ }
828
+ else {
829
+ s = axis.scale = plotHeight / Math.abs(t(axis.max) - t(axis.min));
830
+ s = -s;
831
+ m = Math.max(t(axis.max), t(axis.min));
832
+ }
833
+
834
+ // data point to canvas coordinate
835
+ if (t == identity) // slight optimization
836
+ axis.p2c = function (p) { return (p - m) * s; };
837
+ else
838
+ axis.p2c = function (p) { return (t(p) - m) * s; };
839
+ // canvas coordinate to data point
840
+ if (!it)
841
+ axis.c2p = function (c) { return m + c / s; };
842
+ else
843
+ axis.c2p = function (c) { return it(m + c / s); };
844
+ }
845
+
846
+ function measureTickLabels(axis) {
847
+ var opts = axis.options, i, ticks = axis.ticks || [], labels = [],
848
+ l, w = opts.labelWidth, h = opts.labelHeight, dummyDiv;
849
+
850
+ function makeDummyDiv(labels, width) {
851
+ return $('<div style="position:absolute;top:-10000px;' + width + 'font-size:smaller">' +
852
+ '<div class="' + axis.direction + 'Axis ' + axis.direction + axis.n + 'Axis">'
853
+ + labels.join("") + '</div></div>')
854
+ .appendTo(placeholder);
855
+ }
856
+
857
+ if (axis.direction == "x") {
858
+ // to avoid measuring the widths of the labels (it's slow), we
859
+ // construct fixed-size boxes and put the labels inside
860
+ // them, we don't need the exact figures and the
861
+ // fixed-size box content is easy to center
862
+ if (w == null)
863
+ w = Math.floor(canvasWidth / (ticks.length > 0 ? ticks.length : 1));
864
+
865
+ // measure x label heights
866
+ if (h == null) {
867
+ labels = [];
868
+ for (i = 0; i < ticks.length; ++i) {
869
+ l = ticks[i].label;
870
+ if (l)
871
+ labels.push('<div class="tickLabel" style="float:left;width:' + w + 'px">' + l + '</div>');
872
+ }
873
+
874
+ if (labels.length > 0) {
875
+ // stick them all in the same div and measure
876
+ // collective height
877
+ labels.push('<div style="clear:left"></div>');
878
+ dummyDiv = makeDummyDiv(labels, "width:10000px;");
879
+ h = dummyDiv.height();
880
+ dummyDiv.remove();
881
+ }
882
+ }
883
+ }
884
+ else if (w == null || h == null) {
885
+ // calculate y label dimensions
886
+ for (i = 0; i < ticks.length; ++i) {
887
+ l = ticks[i].label;
888
+ if (l)
889
+ labels.push('<div class="tickLabel">' + l + '</div>');
890
+ }
891
+
892
+ if (labels.length > 0) {
893
+ dummyDiv = makeDummyDiv(labels, "");
894
+ if (w == null)
895
+ w = dummyDiv.children().width();
896
+ if (h == null)
897
+ h = dummyDiv.find("div.tickLabel").height();
898
+ dummyDiv.remove();
899
+ }
900
+ }
901
+
902
+ if (w == null)
903
+ w = 0;
904
+ if (h == null)
905
+ h = 0;
906
+
907
+ axis.labelWidth = w;
908
+ axis.labelHeight = h;
909
+ }
910
+
911
+ function allocateAxisBoxFirstPhase(axis) {
912
+ // find the bounding box of the axis by looking at label
913
+ // widths/heights and ticks, make room by diminishing the
914
+ // plotOffset
915
+
916
+ var lw = axis.labelWidth,
917
+ lh = axis.labelHeight,
918
+ pos = axis.options.position,
919
+ tickLength = axis.options.tickLength,
920
+ axismargin = options.grid.axisMargin,
921
+ padding = options.grid.labelMargin,
922
+ all = axis.direction == "x" ? xaxes : yaxes,
923
+ index;
924
+
925
+ // determine axis margin
926
+ var samePosition = $.grep(all, function (a) {
927
+ return a && a.options.position == pos && a.reserveSpace;
928
+ });
929
+ if ($.inArray(axis, samePosition) == samePosition.length - 1)
930
+ axismargin = 0; // outermost
931
+
932
+ // determine tick length - if we're innermost, we can use "full"
933
+ if (tickLength == null)
934
+ tickLength = "full";
935
+
936
+ var sameDirection = $.grep(all, function (a) {
937
+ return a && a.reserveSpace;
938
+ });
939
+
940
+ var innermost = $.inArray(axis, sameDirection) == 0;
941
+ if (!innermost && tickLength == "full")
942
+ tickLength = 5;
943
+
944
+ if (!isNaN(+tickLength))
945
+ padding += +tickLength;
946
+
947
+ // compute box
948
+ if (axis.direction == "x") {
949
+ lh += padding;
950
+
951
+ if (pos == "bottom") {
952
+ plotOffset.bottom += lh + axismargin;
953
+ axis.box = { top: canvasHeight - plotOffset.bottom, height: lh };
954
+ }
955
+ else {
956
+ axis.box = { top: plotOffset.top + axismargin, height: lh };
957
+ plotOffset.top += lh + axismargin;
958
+ }
959
+ }
960
+ else {
961
+ lw += padding;
962
+
963
+ if (pos == "left") {
964
+ axis.box = { left: plotOffset.left + axismargin, width: lw };
965
+ plotOffset.left += lw + axismargin;
966
+ }
967
+ else {
968
+ plotOffset.right += lw + axismargin;
969
+ axis.box = { left: canvasWidth - plotOffset.right, width: lw };
970
+ }
971
+ }
972
+
973
+ // save for future reference
974
+ axis.position = pos;
975
+ axis.tickLength = tickLength;
976
+ axis.box.padding = padding;
977
+ axis.innermost = innermost;
978
+ }
979
+
980
+ function allocateAxisBoxSecondPhase(axis) {
981
+ // set remaining bounding box coordinates
982
+ if (axis.direction == "x") {
983
+ axis.box.left = plotOffset.left;
984
+ axis.box.width = plotWidth;
985
+ }
986
+ else {
987
+ axis.box.top = plotOffset.top;
988
+ axis.box.height = plotHeight;
989
+ }
990
+ }
991
+
992
+ function setupGrid() {
993
+ var i, axes = allAxes();
994
+
995
+ // first calculate the plot and axis box dimensions
996
+
997
+ $.each(axes, function (_, axis) {
998
+ axis.show = axis.options.show;
999
+ if (axis.show == null)
1000
+ axis.show = axis.used; // by default an axis is visible if it's got data
1001
+
1002
+ axis.reserveSpace = axis.show || axis.options.reserveSpace;
1003
+
1004
+ setRange(axis);
1005
+ });
1006
+
1007
+ allocatedAxes = $.grep(axes, function (axis) { return axis.reserveSpace; });
1008
+
1009
+ plotOffset.left = plotOffset.right = plotOffset.top = plotOffset.bottom = 0;
1010
+ if (options.grid.show) {
1011
+ $.each(allocatedAxes, function (_, axis) {
1012
+ // make the ticks
1013
+ setupTickGeneration(axis);
1014
+ setTicks(axis);
1015
+ snapRangeToTicks(axis, axis.ticks);
1016
+
1017
+ // find labelWidth/Height for axis
1018
+ measureTickLabels(axis);
1019
+ });
1020
+
1021
+ // with all dimensions in house, we can compute the
1022
+ // axis boxes, start from the outside (reverse order)
1023
+ for (i = allocatedAxes.length - 1; i >= 0; --i)
1024
+ allocateAxisBoxFirstPhase(allocatedAxes[i]);
1025
+
1026
+ // make sure we've got enough space for things that
1027
+ // might stick out
1028
+ var minMargin = options.grid.minBorderMargin;
1029
+ if (minMargin == null) {
1030
+ minMargin = 0;
1031
+ for (i = 0; i < series.length; ++i)
1032
+ minMargin = Math.max(minMargin, series[i].points.radius + series[i].points.lineWidth/2);
1033
+ }
1034
+
1035
+ for (var a in plotOffset) {
1036
+ plotOffset[a] += options.grid.borderWidth;
1037
+ plotOffset[a] = Math.max(minMargin, plotOffset[a]);
1038
+ }
1039
+ }
1040
+
1041
+ plotWidth = canvasWidth - plotOffset.left - plotOffset.right;
1042
+ plotHeight = canvasHeight - plotOffset.bottom - plotOffset.top;
1043
+
1044
+ // now we got the proper plotWidth/Height, we can compute the scaling
1045
+ $.each(axes, function (_, axis) {
1046
+ setTransformationHelpers(axis);
1047
+ });
1048
+
1049
+ if (options.grid.show) {
1050
+ $.each(allocatedAxes, function (_, axis) {
1051
+ allocateAxisBoxSecondPhase(axis);
1052
+ });
1053
+
1054
+ insertAxisLabels();
1055
+ }
1056
+
1057
+ insertLegend();
1058
+ }
1059
+
1060
+ function setRange(axis) {
1061
+ var opts = axis.options,
1062
+ min = +(opts.min != null ? opts.min : axis.datamin),
1063
+ max = +(opts.max != null ? opts.max : axis.datamax),
1064
+ delta = max - min;
1065
+
1066
+ if (delta == 0.0) {
1067
+ // degenerate case
1068
+ var widen = max == 0 ? 1 : 0.01;
1069
+
1070
+ if (opts.min == null)
1071
+ min -= widen;
1072
+ // always widen max if we couldn't widen min to ensure we
1073
+ // don't fall into min == max which doesn't work
1074
+ if (opts.max == null || opts.min != null)
1075
+ max += widen;
1076
+ }
1077
+ else {
1078
+ // consider autoscaling
1079
+ var margin = opts.autoscaleMargin;
1080
+ if (margin != null) {
1081
+ if (opts.min == null) {
1082
+ min -= delta * margin;
1083
+ // make sure we don't go below zero if all values
1084
+ // are positive
1085
+ if (min < 0 && axis.datamin != null && axis.datamin >= 0)
1086
+ min = 0;
1087
+ }
1088
+ if (opts.max == null) {
1089
+ max += delta * margin;
1090
+ if (max > 0 && axis.datamax != null && axis.datamax <= 0)
1091
+ max = 0;
1092
+ }
1093
+ }
1094
+ }
1095
+ axis.min = min;
1096
+ axis.max = max;
1097
+ }
1098
+
1099
+ function setupTickGeneration(axis) {
1100
+ var opts = axis.options;
1101
+
1102
+ // estimate number of ticks
1103
+ var noTicks;
1104
+ if (typeof opts.ticks == "number" && opts.ticks > 0)
1105
+ noTicks = opts.ticks;
1106
+ else
1107
+ // heuristic based on the model a*sqrt(x) fitted to
1108
+ // some data points that seemed reasonable
1109
+ noTicks = 0.3 * Math.sqrt(axis.direction == "x" ? canvasWidth : canvasHeight);
1110
+
1111
+ var delta = (axis.max - axis.min) / noTicks,
1112
+ size, generator, unit, formatter, i, magn, norm;
1113
+
1114
+ if (opts.mode == "time") {
1115
+ // pretty handling of time
1116
+
1117
+ // map of app. size of time units in milliseconds
1118
+ var timeUnitSize = {
1119
+ "second": 1000,
1120
+ "minute": 60 * 1000,
1121
+ "hour": 60 * 60 * 1000,
1122
+ "day": 24 * 60 * 60 * 1000,
1123
+ "month": 30 * 24 * 60 * 60 * 1000,
1124
+ "year": 365.2425 * 24 * 60 * 60 * 1000
1125
+ };
1126
+
1127
+
1128
+ // the allowed tick sizes, after 1 year we use
1129
+ // an integer algorithm
1130
+ var spec = [
1131
+ [1, "second"], [2, "second"], [5, "second"], [10, "second"],
1132
+ [30, "second"],
1133
+ [1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"],
1134
+ [30, "minute"],
1135
+ [1, "hour"], [2, "hour"], [4, "hour"],
1136
+ [8, "hour"], [12, "hour"],
1137
+ [1, "day"], [2, "day"], [3, "day"],
1138
+ [0.25, "month"], [0.5, "month"], [1, "month"],
1139
+ [2, "month"], [3, "month"], [6, "month"],
1140
+ [1, "year"]
1141
+ ];
1142
+
1143
+ var minSize = 0;
1144
+ if (opts.minTickSize != null) {
1145
+ if (typeof opts.tickSize == "number")
1146
+ minSize = opts.tickSize;
1147
+ else
1148
+ minSize = opts.minTickSize[0] * timeUnitSize[opts.minTickSize[1]];
1149
+ }
1150
+
1151
+ for (var i = 0; i < spec.length - 1; ++i)
1152
+ if (delta < (spec[i][0] * timeUnitSize[spec[i][1]]
1153
+ + spec[i + 1][0] * timeUnitSize[spec[i + 1][1]]) / 2
1154
+ && spec[i][0] * timeUnitSize[spec[i][1]] >= minSize)
1155
+ break;
1156
+ size = spec[i][0];
1157
+ unit = spec[i][1];
1158
+
1159
+ // special-case the possibility of several years
1160
+ if (unit == "year") {
1161
+ magn = Math.pow(10, Math.floor(Math.log(delta / timeUnitSize.year) / Math.LN10));
1162
+ norm = (delta / timeUnitSize.year) / magn;
1163
+ if (norm < 1.5)
1164
+ size = 1;
1165
+ else if (norm < 3)
1166
+ size = 2;
1167
+ else if (norm < 7.5)
1168
+ size = 5;
1169
+ else
1170
+ size = 10;
1171
+
1172
+ size *= magn;
1173
+ }
1174
+
1175
+ axis.tickSize = opts.tickSize || [size, unit];
1176
+
1177
+ generator = function(axis) {
1178
+ var ticks = [],
1179
+ tickSize = axis.tickSize[0], unit = axis.tickSize[1],
1180
+ d = new Date(axis.min);
1181
+
1182
+ var step = tickSize * timeUnitSize[unit];
1183
+
1184
+ if (unit == "second")
1185
+ d.setUTCSeconds(floorInBase(d.getUTCSeconds(), tickSize));
1186
+ if (unit == "minute")
1187
+ d.setUTCMinutes(floorInBase(d.getUTCMinutes(), tickSize));
1188
+ if (unit == "hour")
1189
+ d.setUTCHours(floorInBase(d.getUTCHours(), tickSize));
1190
+ if (unit == "month")
1191
+ d.setUTCMonth(floorInBase(d.getUTCMonth(), tickSize));
1192
+ if (unit == "year")
1193
+ d.setUTCFullYear(floorInBase(d.getUTCFullYear(), tickSize));
1194
+
1195
+ // reset smaller components
1196
+ d.setUTCMilliseconds(0);
1197
+ if (step >= timeUnitSize.minute)
1198
+ d.setUTCSeconds(0);
1199
+ if (step >= timeUnitSize.hour)
1200
+ d.setUTCMinutes(0);
1201
+ if (step >= timeUnitSize.day)
1202
+ d.setUTCHours(0);
1203
+ if (step >= timeUnitSize.day * 4)
1204
+ d.setUTCDate(1);
1205
+ if (step >= timeUnitSize.year)
1206
+ d.setUTCMonth(0);
1207
+
1208
+
1209
+ var carry = 0, v = Number.NaN, prev;
1210
+ do {
1211
+ prev = v;
1212
+ v = d.getTime();
1213
+ ticks.push(v);
1214
+ if (unit == "month") {
1215
+ if (tickSize < 1) {
1216
+ // a bit complicated - we'll divide the month
1217
+ // up but we need to take care of fractions
1218
+ // so we don't end up in the middle of a day
1219
+ d.setUTCDate(1);
1220
+ var start = d.getTime();
1221
+ d.setUTCMonth(d.getUTCMonth() + 1);
1222
+ var end = d.getTime();
1223
+ d.setTime(v + carry * timeUnitSize.hour + (end - start) * tickSize);
1224
+ carry = d.getUTCHours();
1225
+ d.setUTCHours(0);
1226
+ }
1227
+ else
1228
+ d.setUTCMonth(d.getUTCMonth() + tickSize);
1229
+ }
1230
+ else if (unit == "year") {
1231
+ d.setUTCFullYear(d.getUTCFullYear() + tickSize);
1232
+ }
1233
+ else
1234
+ d.setTime(v + step);
1235
+ } while (v < axis.max && v != prev);
1236
+
1237
+ return ticks;
1238
+ };
1239
+
1240
+ formatter = function (v, axis) {
1241
+ var d = new Date(v);
1242
+
1243
+ // first check global format
1244
+ if (opts.timeformat != null)
1245
+ return $.plot.formatDate(d, opts.timeformat, opts.monthNames);
1246
+
1247
+ var t = axis.tickSize[0] * timeUnitSize[axis.tickSize[1]];
1248
+ var span = axis.max - axis.min;
1249
+ var suffix = (opts.twelveHourClock) ? " %p" : "";
1250
+
1251
+ if (t < timeUnitSize.minute)
1252
+ fmt = "%h:%M:%S" + suffix;
1253
+ else if (t < timeUnitSize.day) {
1254
+ if (span < 2 * timeUnitSize.day)
1255
+ fmt = "%h:%M" + suffix;
1256
+ else
1257
+ fmt = "%b %d %h:%M" + suffix;
1258
+ }
1259
+ else if (t < timeUnitSize.month)
1260
+ fmt = "%b %d";
1261
+ else if (t < timeUnitSize.year) {
1262
+ if (span < timeUnitSize.year)
1263
+ fmt = "%b";
1264
+ else
1265
+ fmt = "%b %y";
1266
+ }
1267
+ else
1268
+ fmt = "%y";
1269
+
1270
+ return $.plot.formatDate(d, fmt, opts.monthNames);
1271
+ };
1272
+ }
1273
+ else {
1274
+ // pretty rounding of base-10 numbers
1275
+ var maxDec = opts.tickDecimals;
1276
+ var dec = -Math.floor(Math.log(delta) / Math.LN10);
1277
+ if (maxDec != null && dec > maxDec)
1278
+ dec = maxDec;
1279
+
1280
+ magn = Math.pow(10, -dec);
1281
+ norm = delta / magn; // norm is between 1.0 and 10.0
1282
+
1283
+ if (norm < 1.5)
1284
+ size = 1;
1285
+ else if (norm < 3) {
1286
+ size = 2;
1287
+ // special case for 2.5, requires an extra decimal
1288
+ if (norm > 2.25 && (maxDec == null || dec + 1 <= maxDec)) {
1289
+ size = 2.5;
1290
+ ++dec;
1291
+ }
1292
+ }
1293
+ else if (norm < 7.5)
1294
+ size = 5;
1295
+ else
1296
+ size = 10;
1297
+
1298
+ size *= magn;
1299
+
1300
+ if (opts.minTickSize != null && size < opts.minTickSize)
1301
+ size = opts.minTickSize;
1302
+
1303
+ axis.tickDecimals = Math.max(0, maxDec != null ? maxDec : dec);
1304
+ axis.tickSize = opts.tickSize || size;
1305
+
1306
+ generator = function (axis) {
1307
+ var ticks = [];
1308
+
1309
+ // spew out all possible ticks
1310
+ var start = floorInBase(axis.min, axis.tickSize),
1311
+ i = 0, v = Number.NaN, prev;
1312
+ do {
1313
+ prev = v;
1314
+ v = start + i * axis.tickSize;
1315
+ ticks.push(v);
1316
+ ++i;
1317
+ } while (v < axis.max && v != prev);
1318
+ return ticks;
1319
+ };
1320
+
1321
+ formatter = function (v, axis) {
1322
+ return v.toFixed(axis.tickDecimals);
1323
+ };
1324
+ }
1325
+
1326
+ if (opts.alignTicksWithAxis != null) {
1327
+ var otherAxis = (axis.direction == "x" ? xaxes : yaxes)[opts.alignTicksWithAxis - 1];
1328
+ if (otherAxis && otherAxis.used && otherAxis != axis) {
1329
+ // consider snapping min/max to outermost nice ticks
1330
+ var niceTicks = generator(axis);
1331
+ if (niceTicks.length > 0) {
1332
+ if (opts.min == null)
1333
+ axis.min = Math.min(axis.min, niceTicks[0]);
1334
+ if (opts.max == null && niceTicks.length > 1)
1335
+ axis.max = Math.max(axis.max, niceTicks[niceTicks.length - 1]);
1336
+ }
1337
+
1338
+ generator = function (axis) {
1339
+ // copy ticks, scaled to this axis
1340
+ var ticks = [], v, i;
1341
+ for (i = 0; i < otherAxis.ticks.length; ++i) {
1342
+ v = (otherAxis.ticks[i].v - otherAxis.min) / (otherAxis.max - otherAxis.min);
1343
+ v = axis.min + v * (axis.max - axis.min);
1344
+ ticks.push(v);
1345
+ }
1346
+ return ticks;
1347
+ };
1348
+
1349
+ // we might need an extra decimal since forced
1350
+ // ticks don't necessarily fit naturally
1351
+ if (axis.mode != "time" && opts.tickDecimals == null) {
1352
+ var extraDec = Math.max(0, -Math.floor(Math.log(delta) / Math.LN10) + 1),
1353
+ ts = generator(axis);
1354
+
1355
+ // only proceed if the tick interval rounded
1356
+ // with an extra decimal doesn't give us a
1357
+ // zero at end
1358
+ if (!(ts.length > 1 && /\..*0$/.test((ts[1] - ts[0]).toFixed(extraDec))))
1359
+ axis.tickDecimals = extraDec;
1360
+ }
1361
+ }
1362
+ }
1363
+
1364
+ axis.tickGenerator = generator;
1365
+ if ($.isFunction(opts.tickFormatter))
1366
+ axis.tickFormatter = function (v, axis) { return "" + opts.tickFormatter(v, axis); };
1367
+ else
1368
+ axis.tickFormatter = formatter;
1369
+ }
1370
+
1371
+ function setTicks(axis) {
1372
+ var oticks = axis.options.ticks, ticks = [];
1373
+ if (oticks == null || (typeof oticks == "number" && oticks > 0))
1374
+ ticks = axis.tickGenerator(axis);
1375
+ else if (oticks) {
1376
+ if ($.isFunction(oticks))
1377
+ // generate the ticks
1378
+ ticks = oticks({ min: axis.min, max: axis.max });
1379
+ else
1380
+ ticks = oticks;
1381
+ }
1382
+
1383
+ // clean up/labelify the supplied ticks, copy them over
1384
+ var i, v;
1385
+ axis.ticks = [];
1386
+ for (i = 0; i < ticks.length; ++i) {
1387
+ var label = null;
1388
+ var t = ticks[i];
1389
+ if (typeof t == "object") {
1390
+ v = +t[0];
1391
+ if (t.length > 1)
1392
+ label = t[1];
1393
+ }
1394
+ else
1395
+ v = +t;
1396
+ if (label == null)
1397
+ label = axis.tickFormatter(v, axis);
1398
+ if (!isNaN(v))
1399
+ axis.ticks.push({ v: v, label: label });
1400
+ }
1401
+ }
1402
+
1403
+ function snapRangeToTicks(axis, ticks) {
1404
+ if (axis.options.autoscaleMargin && ticks.length > 0) {
1405
+ // snap to ticks
1406
+ if (axis.options.min == null)
1407
+ axis.min = Math.min(axis.min, ticks[0].v);
1408
+ if (axis.options.max == null && ticks.length > 1)
1409
+ axis.max = Math.max(axis.max, ticks[ticks.length - 1].v);
1410
+ }
1411
+ }
1412
+
1413
+ function draw() {
1414
+ ctx.clearRect(0, 0, canvasWidth, canvasHeight);
1415
+
1416
+ var grid = options.grid;
1417
+
1418
+ // draw background, if any
1419
+ if (grid.show && grid.backgroundColor)
1420
+ drawBackground();
1421
+
1422
+ if (grid.show && !grid.aboveData)
1423
+ drawGrid();
1424
+
1425
+ for (var i = 0; i < series.length; ++i) {
1426
+ executeHooks(hooks.drawSeries, [ctx, series[i]]);
1427
+ drawSeries(series[i]);
1428
+ }
1429
+
1430
+ executeHooks(hooks.draw, [ctx]);
1431
+
1432
+ if (grid.show && grid.aboveData)
1433
+ drawGrid();
1434
+ }
1435
+
1436
+ function extractRange(ranges, coord) {
1437
+ var axis, from, to, key, axes = allAxes();
1438
+
1439
+ for (i = 0; i < axes.length; ++i) {
1440
+ axis = axes[i];
1441
+ if (axis.direction == coord) {
1442
+ key = coord + axis.n + "axis";
1443
+ if (!ranges[key] && axis.n == 1)
1444
+ key = coord + "axis"; // support x1axis as xaxis
1445
+ if (ranges[key]) {
1446
+ from = ranges[key].from;
1447
+ to = ranges[key].to;
1448
+ break;
1449
+ }
1450
+ }
1451
+ }
1452
+
1453
+ // backwards-compat stuff - to be removed in future
1454
+ if (!ranges[key]) {
1455
+ axis = coord == "x" ? xaxes[0] : yaxes[0];
1456
+ from = ranges[coord + "1"];
1457
+ to = ranges[coord + "2"];
1458
+ }
1459
+
1460
+ // auto-reverse as an added bonus
1461
+ if (from != null && to != null && from > to) {
1462
+ var tmp = from;
1463
+ from = to;
1464
+ to = tmp;
1465
+ }
1466
+
1467
+ return { from: from, to: to, axis: axis };
1468
+ }
1469
+
1470
+ function drawBackground() {
1471
+ ctx.save();
1472
+ ctx.translate(plotOffset.left, plotOffset.top);
1473
+
1474
+ ctx.fillStyle = getColorOrGradient(options.grid.backgroundColor, plotHeight, 0, "rgba(255, 255, 255, 0)");
1475
+ ctx.fillRect(0, 0, plotWidth, plotHeight);
1476
+ ctx.restore();
1477
+ }
1478
+
1479
+ function drawGrid() {
1480
+ var i;
1481
+
1482
+ ctx.save();
1483
+ ctx.translate(plotOffset.left, plotOffset.top);
1484
+
1485
+ // draw markings
1486
+ var markings = options.grid.markings;
1487
+ if (markings) {
1488
+ if ($.isFunction(markings)) {
1489
+ var axes = plot.getAxes();
1490
+ // xmin etc. is backwards compatibility, to be
1491
+ // removed in the future
1492
+ axes.xmin = axes.xaxis.min;
1493
+ axes.xmax = axes.xaxis.max;
1494
+ axes.ymin = axes.yaxis.min;
1495
+ axes.ymax = axes.yaxis.max;
1496
+
1497
+ markings = markings(axes);
1498
+ }
1499
+
1500
+ for (i = 0; i < markings.length; ++i) {
1501
+ var m = markings[i],
1502
+ xrange = extractRange(m, "x"),
1503
+ yrange = extractRange(m, "y");
1504
+
1505
+ // fill in missing
1506
+ if (xrange.from == null)
1507
+ xrange.from = xrange.axis.min;
1508
+ if (xrange.to == null)
1509
+ xrange.to = xrange.axis.max;
1510
+ if (yrange.from == null)
1511
+ yrange.from = yrange.axis.min;
1512
+ if (yrange.to == null)
1513
+ yrange.to = yrange.axis.max;
1514
+
1515
+ // clip
1516
+ if (xrange.to < xrange.axis.min || xrange.from > xrange.axis.max ||
1517
+ yrange.to < yrange.axis.min || yrange.from > yrange.axis.max)
1518
+ continue;
1519
+
1520
+ xrange.from = Math.max(xrange.from, xrange.axis.min);
1521
+ xrange.to = Math.min(xrange.to, xrange.axis.max);
1522
+ yrange.from = Math.max(yrange.from, yrange.axis.min);
1523
+ yrange.to = Math.min(yrange.to, yrange.axis.max);
1524
+
1525
+ if (xrange.from == xrange.to && yrange.from == yrange.to)
1526
+ continue;
1527
+
1528
+ // then draw
1529
+ xrange.from = xrange.axis.p2c(xrange.from);
1530
+ xrange.to = xrange.axis.p2c(xrange.to);
1531
+ yrange.from = yrange.axis.p2c(yrange.from);
1532
+ yrange.to = yrange.axis.p2c(yrange.to);
1533
+
1534
+ if (xrange.from == xrange.to || yrange.from == yrange.to) {
1535
+ // draw line
1536
+ ctx.beginPath();
1537
+ ctx.strokeStyle = m.color || options.grid.markingsColor;
1538
+ ctx.lineWidth = m.lineWidth || options.grid.markingsLineWidth;
1539
+ ctx.moveTo(xrange.from, yrange.from);
1540
+ ctx.lineTo(xrange.to, yrange.to);
1541
+ ctx.stroke();
1542
+ }
1543
+ else {
1544
+ // fill area
1545
+ ctx.fillStyle = m.color || options.grid.markingsColor;
1546
+ ctx.fillRect(xrange.from, yrange.to,
1547
+ xrange.to - xrange.from,
1548
+ yrange.from - yrange.to);
1549
+ }
1550
+ }
1551
+ }
1552
+
1553
+ // draw the ticks
1554
+ var axes = allAxes(), bw = options.grid.borderWidth;
1555
+
1556
+ for (var j = 0; j < axes.length; ++j) {
1557
+ var axis = axes[j], box = axis.box,
1558
+ t = axis.tickLength, x, y, xoff, yoff;
1559
+ if (!axis.show || axis.ticks.length == 0)
1560
+ continue
1561
+
1562
+ ctx.strokeStyle = axis.options.tickColor || $.color.parse(axis.options.color).scale('a', 0.22).toString();
1563
+ ctx.lineWidth = 1;
1564
+
1565
+ // find the edges
1566
+ if (axis.direction == "x") {
1567
+ x = 0;
1568
+ if (t == "full")
1569
+ y = (axis.position == "top" ? 0 : plotHeight);
1570
+ else
1571
+ y = box.top - plotOffset.top + (axis.position == "top" ? box.height : 0);
1572
+ }
1573
+ else {
1574
+ y = 0;
1575
+ if (t == "full")
1576
+ x = (axis.position == "left" ? 0 : plotWidth);
1577
+ else
1578
+ x = box.left - plotOffset.left + (axis.position == "left" ? box.width : 0);
1579
+ }
1580
+
1581
+ // draw tick bar
1582
+ if (!axis.innermost) {
1583
+ ctx.beginPath();
1584
+ xoff = yoff = 0;
1585
+ if (axis.direction == "x")
1586
+ xoff = plotWidth;
1587
+ else
1588
+ yoff = plotHeight;
1589
+
1590
+ if (ctx.lineWidth == 1) {
1591
+ x = Math.floor(x) + 0.5;
1592
+ y = Math.floor(y) + 0.5;
1593
+ }
1594
+
1595
+ ctx.moveTo(x, y);
1596
+ ctx.lineTo(x + xoff, y + yoff);
1597
+ ctx.stroke();
1598
+ }
1599
+
1600
+ // draw ticks
1601
+ ctx.beginPath();
1602
+ for (i = 0; i < axis.ticks.length; ++i) {
1603
+ var v = axis.ticks[i].v;
1604
+
1605
+ xoff = yoff = 0;
1606
+
1607
+ if (v < axis.min || v > axis.max
1608
+ // skip those lying on the axes if we got a border
1609
+ || (t == "full" && bw > 0
1610
+ && (v == axis.min || v == axis.max)))
1611
+ continue;
1612
+
1613
+ if (axis.direction == "x") {
1614
+ x = axis.p2c(v);
1615
+ yoff = t == "full" ? -plotHeight : t;
1616
+
1617
+ if (axis.position == "top")
1618
+ yoff = -yoff;
1619
+ }
1620
+ else {
1621
+ y = axis.p2c(v);
1622
+ xoff = t == "full" ? -plotWidth : t;
1623
+
1624
+ if (axis.position == "left")
1625
+ xoff = -xoff;
1626
+ }
1627
+
1628
+ if (ctx.lineWidth == 1) {
1629
+ if (axis.direction == "x")
1630
+ x = Math.floor(x) + 0.5;
1631
+ else
1632
+ y = Math.floor(y) + 0.5;
1633
+ }
1634
+
1635
+ ctx.moveTo(x, y);
1636
+ ctx.lineTo(x + xoff, y + yoff);
1637
+ }
1638
+
1639
+ ctx.stroke();
1640
+ }
1641
+
1642
+
1643
+ // draw border
1644
+ if (bw) {
1645
+ ctx.lineWidth = bw;
1646
+ ctx.strokeStyle = options.grid.borderColor;
1647
+ ctx.strokeRect(-bw/2, -bw/2, plotWidth + bw, plotHeight + bw);
1648
+ }
1649
+
1650
+ ctx.restore();
1651
+ }
1652
+
1653
+ function insertAxisLabels() {
1654
+ placeholder.find(".tickLabels").remove();
1655
+
1656
+ var html = ['<div class="tickLabels" style="font-size:smaller">'];
1657
+
1658
+ var axes = allAxes();
1659
+ for (var j = 0; j < axes.length; ++j) {
1660
+ var axis = axes[j], box = axis.box;
1661
+ if (!axis.show)
1662
+ continue;
1663
+ //debug: html.push('<div style="position:absolute;opacity:0.10;background-color:red;left:' + box.left + 'px;top:' + box.top + 'px;width:' + box.width + 'px;height:' + box.height + 'px"></div>')
1664
+ html.push('<div class="' + axis.direction + 'Axis ' + axis.direction + axis.n + 'Axis" style="color:' + axis.options.color + '">');
1665
+ for (var i = 0; i < axis.ticks.length; ++i) {
1666
+ var tick = axis.ticks[i];
1667
+ if (!tick.label || tick.v < axis.min || tick.v > axis.max)
1668
+ continue;
1669
+
1670
+ var pos = {}, align;
1671
+
1672
+ if (axis.direction == "x") {
1673
+ align = "center";
1674
+ pos.left = Math.round(plotOffset.left + axis.p2c(tick.v) - axis.labelWidth/2);
1675
+ if (axis.position == "bottom")
1676
+ pos.top = box.top + box.padding;
1677
+ else
1678
+ pos.bottom = canvasHeight - (box.top + box.height - box.padding);
1679
+ }
1680
+ else {
1681
+ pos.top = Math.round(plotOffset.top + axis.p2c(tick.v) - axis.labelHeight/2);
1682
+ if (axis.position == "left") {
1683
+ pos.right = canvasWidth - (box.left + box.width - box.padding)
1684
+ align = "right";
1685
+ }
1686
+ else {
1687
+ pos.left = box.left + box.padding;
1688
+ align = "left";
1689
+ }
1690
+ }
1691
+
1692
+ pos.width = axis.labelWidth;
1693
+
1694
+ var style = ["position:absolute", "text-align:" + align ];
1695
+ for (var a in pos)
1696
+ style.push(a + ":" + pos[a] + "px")
1697
+
1698
+ html.push('<div class="tickLabel" style="' + style.join(';') + '">' + tick.label + '</div>');
1699
+ }
1700
+ html.push('</div>');
1701
+ }
1702
+
1703
+ html.push('</div>');
1704
+
1705
+ placeholder.append(html.join(""));
1706
+ }
1707
+
1708
+ function drawSeries(series) {
1709
+ if (series.lines.show)
1710
+ drawSeriesLines(series);
1711
+ if (series.bars.show)
1712
+ drawSeriesBars(series);
1713
+ if (series.points.show)
1714
+ drawSeriesPoints(series);
1715
+ }
1716
+
1717
+ function drawSeriesLines(series) {
1718
+ function plotLine(datapoints, xoffset, yoffset, axisx, axisy) {
1719
+ var points = datapoints.points,
1720
+ ps = datapoints.pointsize,
1721
+ prevx = null, prevy = null;
1722
+
1723
+ ctx.beginPath();
1724
+ for (var i = ps; i < points.length; i += ps) {
1725
+ var x1 = points[i - ps], y1 = points[i - ps + 1],
1726
+ x2 = points[i], y2 = points[i + 1];
1727
+
1728
+ if (x1 == null || x2 == null)
1729
+ continue;
1730
+
1731
+ // clip with ymin
1732
+ if (y1 <= y2 && y1 < axisy.min) {
1733
+ if (y2 < axisy.min)
1734
+ continue; // line segment is outside
1735
+ // compute new intersection point
1736
+ x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
1737
+ y1 = axisy.min;
1738
+ }
1739
+ else if (y2 <= y1 && y2 < axisy.min) {
1740
+ if (y1 < axisy.min)
1741
+ continue;
1742
+ x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
1743
+ y2 = axisy.min;
1744
+ }
1745
+
1746
+ // clip with ymax
1747
+ if (y1 >= y2 && y1 > axisy.max) {
1748
+ if (y2 > axisy.max)
1749
+ continue;
1750
+ x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
1751
+ y1 = axisy.max;
1752
+ }
1753
+ else if (y2 >= y1 && y2 > axisy.max) {
1754
+ if (y1 > axisy.max)
1755
+ continue;
1756
+ x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
1757
+ y2 = axisy.max;
1758
+ }
1759
+
1760
+ // clip with xmin
1761
+ if (x1 <= x2 && x1 < axisx.min) {
1762
+ if (x2 < axisx.min)
1763
+ continue;
1764
+ y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
1765
+ x1 = axisx.min;
1766
+ }
1767
+ else if (x2 <= x1 && x2 < axisx.min) {
1768
+ if (x1 < axisx.min)
1769
+ continue;
1770
+ y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
1771
+ x2 = axisx.min;
1772
+ }
1773
+
1774
+ // clip with xmax
1775
+ if (x1 >= x2 && x1 > axisx.max) {
1776
+ if (x2 > axisx.max)
1777
+ continue;
1778
+ y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
1779
+ x1 = axisx.max;
1780
+ }
1781
+ else if (x2 >= x1 && x2 > axisx.max) {
1782
+ if (x1 > axisx.max)
1783
+ continue;
1784
+ y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
1785
+ x2 = axisx.max;
1786
+ }
1787
+
1788
+ if (x1 != prevx || y1 != prevy)
1789
+ ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset);
1790
+
1791
+ prevx = x2;
1792
+ prevy = y2;
1793
+ ctx.lineTo(axisx.p2c(x2) + xoffset, axisy.p2c(y2) + yoffset);
1794
+ }
1795
+ ctx.stroke();
1796
+ }
1797
+
1798
+ function plotLineArea(datapoints, axisx, axisy) {
1799
+ var points = datapoints.points,
1800
+ ps = datapoints.pointsize,
1801
+ bottom = Math.min(Math.max(0, axisy.min), axisy.max),
1802
+ i = 0, top, areaOpen = false,
1803
+ ypos = 1, segmentStart = 0, segmentEnd = 0;
1804
+
1805
+ // we process each segment in two turns, first forward
1806
+ // direction to sketch out top, then once we hit the
1807
+ // end we go backwards to sketch the bottom
1808
+ while (true) {
1809
+ if (ps > 0 && i > points.length + ps)
1810
+ break;
1811
+
1812
+ i += ps; // ps is negative if going backwards
1813
+
1814
+ var x1 = points[i - ps],
1815
+ y1 = points[i - ps + ypos],
1816
+ x2 = points[i], y2 = points[i + ypos];
1817
+
1818
+ if (areaOpen) {
1819
+ if (ps > 0 && x1 != null && x2 == null) {
1820
+ // at turning point
1821
+ segmentEnd = i;
1822
+ ps = -ps;
1823
+ ypos = 2;
1824
+ continue;
1825
+ }
1826
+
1827
+ if (ps < 0 && i == segmentStart + ps) {
1828
+ // done with the reverse sweep
1829
+ ctx.fill();
1830
+ areaOpen = false;
1831
+ ps = -ps;
1832
+ ypos = 1;
1833
+ i = segmentStart = segmentEnd + ps;
1834
+ continue;
1835
+ }
1836
+ }
1837
+
1838
+ if (x1 == null || x2 == null)
1839
+ continue;
1840
+
1841
+ // clip x values
1842
+
1843
+ // clip with xmin
1844
+ if (x1 <= x2 && x1 < axisx.min) {
1845
+ if (x2 < axisx.min)
1846
+ continue;
1847
+ y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
1848
+ x1 = axisx.min;
1849
+ }
1850
+ else if (x2 <= x1 && x2 < axisx.min) {
1851
+ if (x1 < axisx.min)
1852
+ continue;
1853
+ y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
1854
+ x2 = axisx.min;
1855
+ }
1856
+
1857
+ // clip with xmax
1858
+ if (x1 >= x2 && x1 > axisx.max) {
1859
+ if (x2 > axisx.max)
1860
+ continue;
1861
+ y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
1862
+ x1 = axisx.max;
1863
+ }
1864
+ else if (x2 >= x1 && x2 > axisx.max) {
1865
+ if (x1 > axisx.max)
1866
+ continue;
1867
+ y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
1868
+ x2 = axisx.max;
1869
+ }
1870
+
1871
+ if (!areaOpen) {
1872
+ // open area
1873
+ ctx.beginPath();
1874
+ ctx.moveTo(axisx.p2c(x1), axisy.p2c(bottom));
1875
+ areaOpen = true;
1876
+ }
1877
+
1878
+ // now first check the case where both is outside
1879
+ if (y1 >= axisy.max && y2 >= axisy.max) {
1880
+ ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.max));
1881
+ ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.max));
1882
+ continue;
1883
+ }
1884
+ else if (y1 <= axisy.min && y2 <= axisy.min) {
1885
+ ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.min));
1886
+ ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.min));
1887
+ continue;
1888
+ }
1889
+
1890
+ // else it's a bit more complicated, there might
1891
+ // be a flat maxed out rectangle first, then a
1892
+ // triangular cutout or reverse; to find these
1893
+ // keep track of the current x values
1894
+ var x1old = x1, x2old = x2;
1895
+
1896
+ // clip the y values, without shortcutting, we
1897
+ // go through all cases in turn
1898
+
1899
+ // clip with ymin
1900
+ if (y1 <= y2 && y1 < axisy.min && y2 >= axisy.min) {
1901
+ x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
1902
+ y1 = axisy.min;
1903
+ }
1904
+ else if (y2 <= y1 && y2 < axisy.min && y1 >= axisy.min) {
1905
+ x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
1906
+ y2 = axisy.min;
1907
+ }
1908
+
1909
+ // clip with ymax
1910
+ if (y1 >= y2 && y1 > axisy.max && y2 <= axisy.max) {
1911
+ x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
1912
+ y1 = axisy.max;
1913
+ }
1914
+ else if (y2 >= y1 && y2 > axisy.max && y1 <= axisy.max) {
1915
+ x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
1916
+ y2 = axisy.max;
1917
+ }
1918
+
1919
+ // if the x value was changed we got a rectangle
1920
+ // to fill
1921
+ if (x1 != x1old) {
1922
+ ctx.lineTo(axisx.p2c(x1old), axisy.p2c(y1));
1923
+ // it goes to (x1, y1), but we fill that below
1924
+ }
1925
+
1926
+ // fill triangular section, this sometimes result
1927
+ // in redundant points if (x1, y1) hasn't changed
1928
+ // from previous line to, but we just ignore that
1929
+ ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1));
1930
+ ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2));
1931
+
1932
+ // fill the other rectangle if it's there
1933
+ if (x2 != x2old) {
1934
+ ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2));
1935
+ ctx.lineTo(axisx.p2c(x2old), axisy.p2c(y2));
1936
+ }
1937
+ }
1938
+ }
1939
+
1940
+ ctx.save();
1941
+ ctx.translate(plotOffset.left, plotOffset.top);
1942
+ ctx.lineJoin = "round";
1943
+
1944
+ var lw = series.lines.lineWidth,
1945
+ sw = series.shadowSize;
1946
+ // FIXME: consider another form of shadow when filling is turned on
1947
+ if (lw > 0 && sw > 0) {
1948
+ // draw shadow as a thick and thin line with transparency
1949
+ ctx.lineWidth = sw;
1950
+ ctx.strokeStyle = "rgba(0,0,0,0.1)";
1951
+ // position shadow at angle from the mid of line
1952
+ var angle = Math.PI/18;
1953
+ plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/2), Math.cos(angle) * (lw/2 + sw/2), series.xaxis, series.yaxis);
1954
+ ctx.lineWidth = sw/2;
1955
+ plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/4), Math.cos(angle) * (lw/2 + sw/4), series.xaxis, series.yaxis);
1956
+ }
1957
+
1958
+ ctx.lineWidth = lw;
1959
+ ctx.strokeStyle = series.color;
1960
+ var fillStyle = getFillStyle(series.lines, series.color, 0, plotHeight);
1961
+ if (fillStyle) {
1962
+ ctx.fillStyle = fillStyle;
1963
+ plotLineArea(series.datapoints, series.xaxis, series.yaxis);
1964
+ }
1965
+
1966
+ if (lw > 0)
1967
+ plotLine(series.datapoints, 0, 0, series.xaxis, series.yaxis);
1968
+ ctx.restore();
1969
+ }
1970
+
1971
+ function drawSeriesPoints(series) {
1972
+ function plotPoints(datapoints, radius, fillStyle, offset, shadow, axisx, axisy, symbol) {
1973
+ var points = datapoints.points, ps = datapoints.pointsize;
1974
+
1975
+ for (var i = 0; i < points.length; i += ps) {
1976
+ var x = points[i], y = points[i + 1];
1977
+ if (x == null || x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max)
1978
+ continue;
1979
+
1980
+ ctx.beginPath();
1981
+ x = axisx.p2c(x);
1982
+ y = axisy.p2c(y) + offset;
1983
+ if (symbol == "circle")
1984
+ ctx.arc(x, y, radius, 0, shadow ? Math.PI : Math.PI * 2, false);
1985
+ else
1986
+ symbol(ctx, x, y, radius, shadow);
1987
+ ctx.closePath();
1988
+
1989
+ if (fillStyle) {
1990
+ ctx.fillStyle = fillStyle;
1991
+ ctx.fill();
1992
+ }
1993
+ ctx.stroke();
1994
+ }
1995
+ }
1996
+
1997
+ ctx.save();
1998
+ ctx.translate(plotOffset.left, plotOffset.top);
1999
+
2000
+ var lw = series.points.lineWidth,
2001
+ sw = series.shadowSize,
2002
+ radius = series.points.radius,
2003
+ symbol = series.points.symbol;
2004
+ if (lw > 0 && sw > 0) {
2005
+ // draw shadow in two steps
2006
+ var w = sw / 2;
2007
+ ctx.lineWidth = w;
2008
+ ctx.strokeStyle = "rgba(0,0,0,0.1)";
2009
+ plotPoints(series.datapoints, radius, null, w + w/2, true,
2010
+ series.xaxis, series.yaxis, symbol);
2011
+
2012
+ ctx.strokeStyle = "rgba(0,0,0,0.2)";
2013
+ plotPoints(series.datapoints, radius, null, w/2, true,
2014
+ series.xaxis, series.yaxis, symbol);
2015
+ }
2016
+
2017
+ ctx.lineWidth = lw;
2018
+ ctx.strokeStyle = series.color;
2019
+ plotPoints(series.datapoints, radius,
2020
+ getFillStyle(series.points, series.color), 0, false,
2021
+ series.xaxis, series.yaxis, symbol);
2022
+ ctx.restore();
2023
+ }
2024
+
2025
+ function drawBar(x, y, b, barLeft, barRight, offset, fillStyleCallback, axisx, axisy, c, horizontal, lineWidth) {
2026
+ var left, right, bottom, top,
2027
+ drawLeft, drawRight, drawTop, drawBottom,
2028
+ tmp;
2029
+
2030
+ // in horizontal mode, we start the bar from the left
2031
+ // instead of from the bottom so it appears to be
2032
+ // horizontal rather than vertical
2033
+ if (horizontal) {
2034
+ drawBottom = drawRight = drawTop = true;
2035
+ drawLeft = false;
2036
+ left = b;
2037
+ right = x;
2038
+ top = y + barLeft;
2039
+ bottom = y + barRight;
2040
+
2041
+ // account for negative bars
2042
+ if (right < left) {
2043
+ tmp = right;
2044
+ right = left;
2045
+ left = tmp;
2046
+ drawLeft = true;
2047
+ drawRight = false;
2048
+ }
2049
+ }
2050
+ else {
2051
+ drawLeft = drawRight = drawTop = true;
2052
+ drawBottom = false;
2053
+ left = x + barLeft;
2054
+ right = x + barRight;
2055
+ bottom = b;
2056
+ top = y;
2057
+
2058
+ // account for negative bars
2059
+ if (top < bottom) {
2060
+ tmp = top;
2061
+ top = bottom;
2062
+ bottom = tmp;
2063
+ drawBottom = true;
2064
+ drawTop = false;
2065
+ }
2066
+ }
2067
+
2068
+ // clip
2069
+ if (right < axisx.min || left > axisx.max ||
2070
+ top < axisy.min || bottom > axisy.max)
2071
+ return;
2072
+
2073
+ if (left < axisx.min) {
2074
+ left = axisx.min;
2075
+ drawLeft = false;
2076
+ }
2077
+
2078
+ if (right > axisx.max) {
2079
+ right = axisx.max;
2080
+ drawRight = false;
2081
+ }
2082
+
2083
+ if (bottom < axisy.min) {
2084
+ bottom = axisy.min;
2085
+ drawBottom = false;
2086
+ }
2087
+
2088
+ if (top > axisy.max) {
2089
+ top = axisy.max;
2090
+ drawTop = false;
2091
+ }
2092
+
2093
+ left = axisx.p2c(left);
2094
+ bottom = axisy.p2c(bottom);
2095
+ right = axisx.p2c(right);
2096
+ top = axisy.p2c(top);
2097
+
2098
+ // fill the bar
2099
+ if (fillStyleCallback) {
2100
+ c.beginPath();
2101
+ c.moveTo(left, bottom);
2102
+ c.lineTo(left, top);
2103
+ c.lineTo(right, top);
2104
+ c.lineTo(right, bottom);
2105
+ c.fillStyle = fillStyleCallback(bottom, top);
2106
+ c.fill();
2107
+ }
2108
+
2109
+ // draw outline
2110
+ if (lineWidth > 0 && (drawLeft || drawRight || drawTop || drawBottom)) {
2111
+ c.beginPath();
2112
+
2113
+ // FIXME: inline moveTo is buggy with excanvas
2114
+ c.moveTo(left, bottom + offset);
2115
+ if (drawLeft)
2116
+ c.lineTo(left, top + offset);
2117
+ else
2118
+ c.moveTo(left, top + offset);
2119
+ if (drawTop)
2120
+ c.lineTo(right, top + offset);
2121
+ else
2122
+ c.moveTo(right, top + offset);
2123
+ if (drawRight)
2124
+ c.lineTo(right, bottom + offset);
2125
+ else
2126
+ c.moveTo(right, bottom + offset);
2127
+ if (drawBottom)
2128
+ c.lineTo(left, bottom + offset);
2129
+ else
2130
+ c.moveTo(left, bottom + offset);
2131
+ c.stroke();
2132
+ }
2133
+ }
2134
+
2135
+ function drawSeriesBars(series) {
2136
+ function plotBars(datapoints, barLeft, barRight, offset, fillStyleCallback, axisx, axisy) {
2137
+ var points = datapoints.points, ps = datapoints.pointsize;
2138
+
2139
+ for (var i = 0; i < points.length; i += ps) {
2140
+ if (points[i] == null)
2141
+ continue;
2142
+ drawBar(points[i], points[i + 1], points[i + 2], barLeft, barRight, offset, fillStyleCallback, axisx, axisy, ctx, series.bars.horizontal, series.bars.lineWidth);
2143
+ }
2144
+ }
2145
+
2146
+ ctx.save();
2147
+ ctx.translate(plotOffset.left, plotOffset.top);
2148
+
2149
+ // FIXME: figure out a way to add shadows (for instance along the right edge)
2150
+ ctx.lineWidth = series.bars.lineWidth;
2151
+ ctx.strokeStyle = series.color;
2152
+ var barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth/2;
2153
+ var fillStyleCallback = series.bars.fill ? function (bottom, top) { return getFillStyle(series.bars, series.color, bottom, top); } : null;
2154
+ plotBars(series.datapoints, barLeft, barLeft + series.bars.barWidth, 0, fillStyleCallback, series.xaxis, series.yaxis);
2155
+ ctx.restore();
2156
+ }
2157
+
2158
+ function getFillStyle(filloptions, seriesColor, bottom, top) {
2159
+ var fill = filloptions.fill;
2160
+ if (!fill)
2161
+ return null;
2162
+
2163
+ if (filloptions.fillColor)
2164
+ return getColorOrGradient(filloptions.fillColor, bottom, top, seriesColor);
2165
+
2166
+ var c = $.color.parse(seriesColor);
2167
+ c.a = typeof fill == "number" ? fill : 0.4;
2168
+ c.normalize();
2169
+ return c.toString();
2170
+ }
2171
+
2172
+ function insertLegend() {
2173
+ placeholder.find(".legend").remove();
2174
+
2175
+ if (!options.legend.show)
2176
+ return;
2177
+
2178
+ var fragments = [], rowStarted = false,
2179
+ lf = options.legend.labelFormatter, s, label;
2180
+ for (var i = 0; i < series.length; ++i) {
2181
+ s = series[i];
2182
+ label = s.label;
2183
+ if (!label)
2184
+ continue;
2185
+
2186
+ if (i % options.legend.noColumns == 0) {
2187
+ if (rowStarted)
2188
+ fragments.push('</tr>');
2189
+ fragments.push('<tr>');
2190
+ rowStarted = true;
2191
+ }
2192
+
2193
+ if (lf)
2194
+ label = lf(label, s);
2195
+
2196
+ fragments.push(
2197
+ '<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>' +
2198
+ '<td class="legendLabel">' + label + '</td>');
2199
+ }
2200
+ if (rowStarted)
2201
+ fragments.push('</tr>');
2202
+
2203
+ if (fragments.length == 0)
2204
+ return;
2205
+
2206
+ var table = '<table style="font-size:smaller;color:' + options.grid.color + '">' + fragments.join("") + '</table>';
2207
+ if (options.legend.container != null)
2208
+ $(options.legend.container).html(table);
2209
+ else {
2210
+ var pos = "",
2211
+ p = options.legend.position,
2212
+ m = options.legend.margin;
2213
+ if (m[0] == null)
2214
+ m = [m, m];
2215
+ if (p.charAt(0) == "n")
2216
+ pos += 'top:' + (m[1] + plotOffset.top) + 'px;';
2217
+ else if (p.charAt(0) == "s")
2218
+ pos += 'bottom:' + (m[1] + plotOffset.bottom) + 'px;';
2219
+ if (p.charAt(1) == "e")
2220
+ pos += 'right:' + (m[0] + plotOffset.right) + 'px;';
2221
+ else if (p.charAt(1) == "w")
2222
+ pos += 'left:' + (m[0] + plotOffset.left) + 'px;';
2223
+ var legend = $('<div class="legend">' + table.replace('style="', 'style="position:absolute;' + pos +';') + '</div>').appendTo(placeholder);
2224
+ if (options.legend.backgroundOpacity != 0.0) {
2225
+ // put in the transparent background
2226
+ // separately to avoid blended labels and
2227
+ // label boxes
2228
+ var c = options.legend.backgroundColor;
2229
+ if (c == null) {
2230
+ c = options.grid.backgroundColor;
2231
+ if (c && typeof c == "string")
2232
+ c = $.color.parse(c);
2233
+ else
2234
+ c = $.color.extract(legend, 'background-color');
2235
+ c.a = 1;
2236
+ c = c.toString();
2237
+ }
2238
+ var div = legend.children();
2239
+ $('<div style="position:absolute;width:' + div.width() + 'px;height:' + div.height() + 'px;' + pos +'background-color:' + c + ';"> </div>').prependTo(legend).css('opacity', options.legend.backgroundOpacity);
2240
+ }
2241
+ }
2242
+ }
2243
+
2244
+
2245
+ // interactive features
2246
+
2247
+ var highlights = [],
2248
+ redrawTimeout = null;
2249
+
2250
+ // returns the data item the mouse is over, or null if none is found
2251
+ function findNearbyItem(mouseX, mouseY, seriesFilter) {
2252
+ var maxDistance = options.grid.mouseActiveRadius,
2253
+ smallestDistance = maxDistance * maxDistance + 1,
2254
+ item = null, foundPoint = false, i, j;
2255
+
2256
+ for (i = series.length - 1; i >= 0; --i) {
2257
+ if (!seriesFilter(series[i]))
2258
+ continue;
2259
+
2260
+ var s = series[i],
2261
+ axisx = s.xaxis,
2262
+ axisy = s.yaxis,
2263
+ points = s.datapoints.points,
2264
+ ps = s.datapoints.pointsize,
2265
+ mx = axisx.c2p(mouseX), // precompute some stuff to make the loop faster
2266
+ my = axisy.c2p(mouseY),
2267
+ maxx = maxDistance / axisx.scale,
2268
+ maxy = maxDistance / axisy.scale;
2269
+
2270
+ // with inverse transforms, we can't use the maxx/maxy
2271
+ // optimization, sadly
2272
+ if (axisx.options.inverseTransform)
2273
+ maxx = Number.MAX_VALUE;
2274
+ if (axisy.options.inverseTransform)
2275
+ maxy = Number.MAX_VALUE;
2276
+
2277
+ if (s.lines.show || s.points.show) {
2278
+ for (j = 0; j < points.length; j += ps) {
2279
+ var x = points[j], y = points[j + 1];
2280
+ if (x == null)
2281
+ continue;
2282
+
2283
+ // For points and lines, the cursor must be within a
2284
+ // certain distance to the data point
2285
+ if (x - mx > maxx || x - mx < -maxx ||
2286
+ y - my > maxy || y - my < -maxy)
2287
+ continue;
2288
+
2289
+ // We have to calculate distances in pixels, not in
2290
+ // data units, because the scales of the axes may be different
2291
+ var dx = Math.abs(axisx.p2c(x) - mouseX),
2292
+ dy = Math.abs(axisy.p2c(y) - mouseY),
2293
+ dist = dx * dx + dy * dy; // we save the sqrt
2294
+
2295
+ // use <= to ensure last point takes precedence
2296
+ // (last generally means on top of)
2297
+ if (dist < smallestDistance) {
2298
+ smallestDistance = dist;
2299
+ item = [i, j / ps];
2300
+ }
2301
+ }
2302
+ }
2303
+
2304
+ if (s.bars.show && !item) { // no other point can be nearby
2305
+ var barLeft = s.bars.align == "left" ? 0 : -s.bars.barWidth/2,
2306
+ barRight = barLeft + s.bars.barWidth;
2307
+
2308
+ for (j = 0; j < points.length; j += ps) {
2309
+ var x = points[j], y = points[j + 1], b = points[j + 2];
2310
+ if (x == null)
2311
+ continue;
2312
+
2313
+ // for a bar graph, the cursor must be inside the bar
2314
+ if (series[i].bars.horizontal ?
2315
+ (mx <= Math.max(b, x) && mx >= Math.min(b, x) &&
2316
+ my >= y + barLeft && my <= y + barRight) :
2317
+ (mx >= x + barLeft && mx <= x + barRight &&
2318
+ my >= Math.min(b, y) && my <= Math.max(b, y)))
2319
+ item = [i, j / ps];
2320
+ }
2321
+ }
2322
+ }
2323
+
2324
+ if (item) {
2325
+ i = item[0];
2326
+ j = item[1];
2327
+ ps = series[i].datapoints.pointsize;
2328
+
2329
+ return { datapoint: series[i].datapoints.points.slice(j * ps, (j + 1) * ps),
2330
+ dataIndex: j,
2331
+ series: series[i],
2332
+ seriesIndex: i };
2333
+ }
2334
+
2335
+ return null;
2336
+ }
2337
+
2338
+ function onMouseMove(e) {
2339
+ if (options.grid.hoverable)
2340
+ triggerClickHoverEvent("plothover", e,
2341
+ function (s) { return s["hoverable"] != false; });
2342
+ }
2343
+
2344
+ function onMouseLeave(e) {
2345
+ if (options.grid.hoverable)
2346
+ triggerClickHoverEvent("plothover", e,
2347
+ function (s) { return false; });
2348
+ }
2349
+
2350
+ function onClick(e) {
2351
+ triggerClickHoverEvent("plotclick", e,
2352
+ function (s) { return s["clickable"] != false; });
2353
+ }
2354
+
2355
+ // trigger click or hover event (they send the same parameters
2356
+ // so we share their code)
2357
+ function triggerClickHoverEvent(eventname, event, seriesFilter) {
2358
+ var offset = eventHolder.offset(),
2359
+ canvasX = event.pageX - offset.left - plotOffset.left,
2360
+ canvasY = event.pageY - offset.top - plotOffset.top,
2361
+ pos = canvasToAxisCoords({ left: canvasX, top: canvasY });
2362
+
2363
+ pos.pageX = event.pageX;
2364
+ pos.pageY = event.pageY;
2365
+
2366
+ var item = findNearbyItem(canvasX, canvasY, seriesFilter);
2367
+
2368
+ if (item) {
2369
+ // fill in mouse pos for any listeners out there
2370
+ item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint[0]) + offset.left + plotOffset.left);
2371
+ item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top + plotOffset.top);
2372
+ }
2373
+
2374
+ if (options.grid.autoHighlight) {
2375
+ // clear auto-highlights
2376
+ for (var i = 0; i < highlights.length; ++i) {
2377
+ var h = highlights[i];
2378
+ if (h.auto == eventname &&
2379
+ !(item && h.series == item.series &&
2380
+ h.point[0] == item.datapoint[0] &&
2381
+ h.point[1] == item.datapoint[1]))
2382
+ unhighlight(h.series, h.point);
2383
+ }
2384
+
2385
+ if (item)
2386
+ highlight(item.series, item.datapoint, eventname);
2387
+ }
2388
+
2389
+ placeholder.trigger(eventname, [ pos, item ]);
2390
+ }
2391
+
2392
+ function triggerRedrawOverlay() {
2393
+ if (!redrawTimeout)
2394
+ redrawTimeout = setTimeout(drawOverlay, 30);
2395
+ }
2396
+
2397
+ function drawOverlay() {
2398
+ redrawTimeout = null;
2399
+
2400
+ // draw highlights
2401
+ octx.save();
2402
+ octx.clearRect(0, 0, canvasWidth, canvasHeight);
2403
+ octx.translate(plotOffset.left, plotOffset.top);
2404
+
2405
+ var i, hi;
2406
+ for (i = 0; i < highlights.length; ++i) {
2407
+ hi = highlights[i];
2408
+
2409
+ if (hi.series.bars.show)
2410
+ drawBarHighlight(hi.series, hi.point);
2411
+ else
2412
+ drawPointHighlight(hi.series, hi.point);
2413
+ }
2414
+ octx.restore();
2415
+
2416
+ executeHooks(hooks.drawOverlay, [octx]);
2417
+ }
2418
+
2419
+ function highlight(s, point, auto) {
2420
+ if (typeof s == "number")
2421
+ s = series[s];
2422
+
2423
+ if (typeof point == "number") {
2424
+ var ps = s.datapoints.pointsize;
2425
+ point = s.datapoints.points.slice(ps * point, ps * (point + 1));
2426
+ }
2427
+
2428
+ var i = indexOfHighlight(s, point);
2429
+ if (i == -1) {
2430
+ highlights.push({ series: s, point: point, auto: auto });
2431
+
2432
+ triggerRedrawOverlay();
2433
+ }
2434
+ else if (!auto)
2435
+ highlights[i].auto = false;
2436
+ }
2437
+
2438
+ function unhighlight(s, point) {
2439
+ if (s == null && point == null) {
2440
+ highlights = [];
2441
+ triggerRedrawOverlay();
2442
+ }
2443
+
2444
+ if (typeof s == "number")
2445
+ s = series[s];
2446
+
2447
+ if (typeof point == "number")
2448
+ point = s.data[point];
2449
+
2450
+ var i = indexOfHighlight(s, point);
2451
+ if (i != -1) {
2452
+ highlights.splice(i, 1);
2453
+
2454
+ triggerRedrawOverlay();
2455
+ }
2456
+ }
2457
+
2458
+ function indexOfHighlight(s, p) {
2459
+ for (var i = 0; i < highlights.length; ++i) {
2460
+ var h = highlights[i];
2461
+ if (h.series == s && h.point[0] == p[0]
2462
+ && h.point[1] == p[1])
2463
+ return i;
2464
+ }
2465
+ return -1;
2466
+ }
2467
+
2468
+ function drawPointHighlight(series, point) {
2469
+ var x = point[0], y = point[1],
2470
+ axisx = series.xaxis, axisy = series.yaxis;
2471
+
2472
+ if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max)
2473
+ return;
2474
+
2475
+ var pointRadius = series.points.radius + series.points.lineWidth / 2;
2476
+ octx.lineWidth = pointRadius;
2477
+ octx.strokeStyle = $.color.parse(series.color).scale('a', 0.5).toString();
2478
+ var radius = 1.5 * pointRadius,
2479
+ x = axisx.p2c(x),
2480
+ y = axisy.p2c(y);
2481
+
2482
+ octx.beginPath();
2483
+ if (series.points.symbol == "circle")
2484
+ octx.arc(x, y, radius, 0, 2 * Math.PI, false);
2485
+ else
2486
+ series.points.symbol(octx, x, y, radius, false);
2487
+ octx.closePath();
2488
+ octx.stroke();
2489
+ }
2490
+
2491
+ function drawBarHighlight(series, point) {
2492
+ octx.lineWidth = series.bars.lineWidth;
2493
+ octx.strokeStyle = $.color.parse(series.color).scale('a', 0.5).toString();
2494
+ var fillStyle = $.color.parse(series.color).scale('a', 0.5).toString();
2495
+ var barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth/2;
2496
+ drawBar(point[0], point[1], point[2] || 0, barLeft, barLeft + series.bars.barWidth,
2497
+ 0, function () { return fillStyle; }, series.xaxis, series.yaxis, octx, series.bars.horizontal, series.bars.lineWidth);
2498
+ }
2499
+
2500
+ function getColorOrGradient(spec, bottom, top, defaultColor) {
2501
+ if (typeof spec == "string")
2502
+ return spec;
2503
+ else {
2504
+ // assume this is a gradient spec; IE currently only
2505
+ // supports a simple vertical gradient properly, so that's
2506
+ // what we support too
2507
+ var gradient = ctx.createLinearGradient(0, top, 0, bottom);
2508
+
2509
+ for (var i = 0, l = spec.colors.length; i < l; ++i) {
2510
+ var c = spec.colors[i];
2511
+ if (typeof c != "string") {
2512
+ var co = $.color.parse(defaultColor);
2513
+ if (c.brightness != null)
2514
+ co = co.scale('rgb', c.brightness)
2515
+ if (c.opacity != null)
2516
+ co.a *= c.opacity;
2517
+ c = co.toString();
2518
+ }
2519
+ gradient.addColorStop(i / (l - 1), c);
2520
+ }
2521
+
2522
+ return gradient;
2523
+ }
2524
+ }
2525
+ }
2526
+
2527
+ $.plot = function(placeholder, data, options) {
2528
+ //var t0 = new Date();
2529
+ var plot = new Plot($(placeholder), data, options, $.plot.plugins);
2530
+ //(window.console ? console.log : alert)("time used (msecs): " + ((new Date()).getTime() - t0.getTime()));
2531
+ return plot;
2532
+ };
2533
+
2534
+ $.plot.version = "0.7";
2535
+
2536
+ $.plot.plugins = [];
2537
+
2538
+ // returns a string with the date d formatted according to fmt
2539
+ $.plot.formatDate = function(d, fmt, monthNames) {
2540
+ var leftPad = function(n) {
2541
+ n = "" + n;
2542
+ return n.length == 1 ? "0" + n : n;
2543
+ };
2544
+
2545
+ var r = [];
2546
+ var escape = false, padNext = false;
2547
+ var hours = d.getUTCHours();
2548
+ var isAM = hours < 12;
2549
+ if (monthNames == null)
2550
+ monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
2551
+
2552
+ if (fmt.search(/%p|%P/) != -1) {
2553
+ if (hours > 12) {
2554
+ hours = hours - 12;
2555
+ } else if (hours == 0) {
2556
+ hours = 12;
2557
+ }
2558
+ }
2559
+ for (var i = 0; i < fmt.length; ++i) {
2560
+ var c = fmt.charAt(i);
2561
+
2562
+ if (escape) {
2563
+ switch (c) {
2564
+ case 'h': c = "" + hours; break;
2565
+ case 'H': c = leftPad(hours); break;
2566
+ case 'M': c = leftPad(d.getUTCMinutes()); break;
2567
+ case 'S': c = leftPad(d.getUTCSeconds()); break;
2568
+ case 'd': c = "" + d.getUTCDate(); break;
2569
+ case 'm': c = "" + (d.getUTCMonth() + 1); break;
2570
+ case 'y': c = "" + d.getUTCFullYear(); break;
2571
+ case 'b': c = "" + monthNames[d.getUTCMonth()]; break;
2572
+ case 'p': c = (isAM) ? ("" + "am") : ("" + "pm"); break;
2573
+ case 'P': c = (isAM) ? ("" + "AM") : ("" + "PM"); break;
2574
+ case '0': c = ""; padNext = true; break;
2575
+ }
2576
+ if (c && padNext) {
2577
+ c = leftPad(c);
2578
+ padNext = false;
2579
+ }
2580
+ r.push(c);
2581
+ if (!padNext)
2582
+ escape = false;
2583
+ }
2584
+ else {
2585
+ if (c == "%")
2586
+ escape = true;
2587
+ else
2588
+ r.push(c);
2589
+ }
2590
+ }
2591
+ return r.join("");
2592
+ };
2593
+
2594
+ // round to nearby lower multiple of base
2595
+ function floorInBase(n, base) {
2596
+ return base * Math.floor(n / base);
2597
+ }
2598
+
2599
+ })(jQuery);