pbosetti-flotr 1.3 → 1.3.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.
- data/README.markdown +60 -0
- metadata +2 -9
- data/lib/excanvas.js +0 -785
- data/lib/excanvas.pack.js +0 -1
- data/lib/flotr.rb +0 -99
- data/lib/interacting.rhtml +0 -77
- data/lib/jquery.flot.js +0 -2187
- data/lib/jquery.min.js +0 -32
- data/lib/layout.css +0 -5
- data/lib/zooming.rhtml +0 -123
data/lib/jquery.flot.js
DELETED
@@ -1,2187 +0,0 @@
|
|
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 );
|