mountain-goat 0.0.4 → 0.0.5

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