midas-g_flot 1.0.0 → 1.0.1

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