statsd 0.0.4 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,2119 +0,0 @@
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);