statsd 0.0.4

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