pbosetti-flotr 1.3.3 → 1.3.4

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