mountain-goat 1.0.3 → 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (25) hide show
  1. data/README.md +4 -3
  2. data/generators/mg/mg_generator.rb +2 -2
  3. data/init.rb +0 -1
  4. data/lib/mountain-goat.rb +0 -2
  5. data/lib/mountain-goat/controllers/mg/report_items_controller.rb +1 -1
  6. data/lib/mountain-goat/controllers/mg/tests_controller.rb +0 -16
  7. data/lib/mountain-goat/m_g.rb +20 -19
  8. data/lib/mountain-goat/models/mg/choice.rb +6 -0
  9. data/lib/mountain-goat/public/g-funnel.js +1 -1
  10. data/lib/mountain-goat/version.rb +1 -1
  11. data/lib/mountain-goat/views/mountain_goat/mg/records/_records.html.erb +1 -1
  12. data/lib/mountain-goat/views/mountain_goat/mg/records/index.html.erb +0 -1
  13. data/lib/mountain-goat/views/mountain_goat/mg/reports/_report.html.erb +1 -1
  14. data/lib/mountain-goat/views/mountain_goat/mg/tests/show.html.erb +14 -11
  15. metadata +5 -15
  16. data/lib/mgflotilla.rb +0 -170
  17. data/lib/mountain-goat/public/jquery.flot.js +0 -2615
  18. data/lib/mountain-goat/views/mountain_goat/mg/goals/.tmp_show.html.erb.86489~ +0 -32
  19. data/lib/mountain-goat/views/mountain_goat/mg/report_items/.tmp__chart.html.erb.73596~ +0 -18
  20. data/lib/mountain-goat/views/mountain_goat/mg/report_items/.tmp__show.html.erb.52502~ +0 -29
  21. data/lib/mountain-goat/views/mountain_goat/mg/reports/.tmp__report_form.html.erb.42949~ +0 -26
  22. data/lib/mountain-goat/views/mountain_goat/mg/reports/.tmp__report_report_items.html.erb.96264~ +0 -5
  23. data/lib/mountain-goat/views/mountain_goat/mg/reports/.tmp_edit.html.erb.39909~ +0 -33
  24. data/lib/mountain-goat/views/mountain_goat/mg/reports/.tmp_show.html.erb.37905~ +0 -18
  25. data/lib/mountain-goat/views/mountain_goat/mg/tests/.tmp_show.html.erb.19989~ +0 -63
@@ -1,2615 +0,0 @@
1
- /*! Javascript plotting library for jQuery, v. 0.7.
2
- *
3
- * Released under the MIT license by IOLA, December 2007.
4
- *
5
- */
6
-
7
- // first an inline dependency, jquery.colorhelpers.js, we inline it here
8
- // for convenience
9
-
10
- /* Plugin for jQuery for working with colors.
11
- *
12
- * Version 1.1.
13
- *
14
- * Inspiration from jQuery color animation plugin by John Resig.
15
- *
16
- * Released under the MIT license by Ole Laursen, October 2009.
17
- *
18
- * Examples:
19
- *
20
- * $.color.parse("#fff").scale('rgb', 0.25).add('a', -0.5).toString()
21
- * var c = $.color.extract($("#mydiv"), 'background-color');
22
- * console.log(c.r, c.g, c.b, c.a);
23
- * $.color.make(100, 50, 25, 0.4).toString() // returns "rgba(100,50,25,0.4)"
24
- *
25
- * Note that .scale() and .add() return the same modified object
26
- * instead of making a new one.
27
- *
28
- * V. 1.1: Fix error handling so e.g. parsing an empty string does
29
- * produce a color rather than just crashing.
30
- */
31
- (function(B){B.color={};B.color.make=function(F,E,C,D){var G={};G.r=F||0;G.g=E||0;G.b=C||0;G.a=D!=null?D:1;G.add=function(J,I){for(var H=0;H<J.length;++H){G[J.charAt(H)]+=I}return G.normalize()};G.scale=function(J,I){for(var H=0;H<J.length;++H){G[J.charAt(H)]*=I}return G.normalize()};G.toString=function(){if(G.a>=1){return"rgb("+[G.r,G.g,G.b].join(",")+")"}else{return"rgba("+[G.r,G.g,G.b,G.a].join(",")+")"}};G.normalize=function(){function H(J,K,I){return K<J?J:(K>I?I:K)}G.r=H(0,parseInt(G.r),255);G.g=H(0,parseInt(G.g),255);G.b=H(0,parseInt(G.b),255);G.a=H(0,G.a,1);return G};G.clone=function(){return B.color.make(G.r,G.b,G.g,G.a)};return G.normalize()};B.color.extract=function(D,C){var E;do{E=D.css(C).toLowerCase();if(E!=""&&E!="transparent"){break}D=D.parent()}while(!B.nodeName(D.get(0),"body"));if(E=="rgba(0, 0, 0, 0)"){E="transparent"}return B.color.parse(E)};B.color.parse=function(F){var E,C=B.color.make;if(E=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(F)){return C(parseInt(E[1],10),parseInt(E[2],10),parseInt(E[3],10))}if(E=/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(F)){return C(parseInt(E[1],10),parseInt(E[2],10),parseInt(E[3],10),parseFloat(E[4]))}if(E=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(F)){return C(parseFloat(E[1])*2.55,parseFloat(E[2])*2.55,parseFloat(E[3])*2.55)}if(E=/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(F)){return C(parseFloat(E[1])*2.55,parseFloat(E[2])*2.55,parseFloat(E[3])*2.55,parseFloat(E[4]))}if(E=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(F)){return C(parseInt(E[1],16),parseInt(E[2],16),parseInt(E[3],16))}if(E=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(F)){return C(parseInt(E[1]+E[1],16),parseInt(E[2]+E[2],16),parseInt(E[3]+E[3],16))}var D=B.trim(F).toLowerCase();if(D=="transparent"){return C(255,255,255,0)}else{E=A[D]||[0,0,0];return C(E[0],E[1],E[2])}};var A={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery);
32
-
33
- // the actual Flot code
34
- (function($) {
35
- function Plot(placeholder, data_, options_, plugins) {
36
- // data is on the form:
37
- // [ series1, series2 ... ]
38
- // where series is either just the data as [ [x1, y1], [x2, y2], ... ]
39
- // or { data: [ [x1, y1], [x2, y2], ... ], label: "some label", ... }
40
-
41
- var series = [],
42
- options = {
43
- // the color theme used for graphs
44
- inline: true,
45
- colors: ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"],
46
- legend: {
47
- show: true,
48
- noColumns: 1, // number of colums in legend table
49
- labelFormatter: null, // fn: string -> string
50
- labelBoxBorderColor: "#ccc", // border color for the little label boxes
51
- container: null, // container (as jQuery object) to put legend in, null means default on top of graph
52
- position: "ne", // position of default legend container within plot
53
- margin: 5, // distance from grid edge to default legend container within plot
54
- backgroundColor: null, // null means auto-detect
55
- backgroundOpacity: 0.85 // set to 0 to avoid background
56
- },
57
- xaxis: {
58
- show: null, // null = auto-detect, true = always, false = never
59
- position: "bottom", // or "top"
60
- mode: null, // null or "time"
61
- color: null, // base color, labels, ticks
62
- tickColor: null, // possibly different color of ticks, e.g. "rgba(0,0,0,0.15)"
63
- transform: null, // null or f: number -> number to transform axis
64
- inverseTransform: null, // if transform is set, this should be the inverse function
65
- min: null, // min. value to show, null means set automatically
66
- max: null, // max. value to show, null means set automatically
67
- autoscaleMargin: null, // margin in % to add if auto-setting min/max
68
- ticks: null, // either [1, 3] or [[1, "a"], 3] or (fn: axis info -> ticks) or app. number of ticks for auto-ticks
69
- tickFormatter: null, // fn: number -> string
70
- labelWidth: null, // size of tick labels in pixels
71
- labelHeight: null,
72
- reserveSpace: null, // whether to reserve space even if axis isn't shown
73
- tickLength: null, // size in pixels of ticks, or "full" for whole line
74
- alignTicksWithAxis: null, // axis number or null for no sync
75
-
76
- // mode specific options
77
- tickDecimals: null, // no. of decimals, null means auto
78
- tickSize: null, // number or [number, "unit"]
79
- minTickSize: null, // number or [number, "unit"]
80
- monthNames: null, // list of names of months
81
- timeformat: null, // format string to use
82
- twelveHourClock: false // 12 or 24 time in time mode
83
- },
84
- yaxis: {
85
- autoscaleMargin: 0.02,
86
- position: "left" // or "right"
87
- },
88
- xaxes: [],
89
- yaxes: [],
90
- series: {
91
- points: {
92
- show: false,
93
- radius: 3,
94
- lineWidth: 2, // in pixels
95
- fill: true,
96
- fillColor: "#ffffff",
97
- symbol: "circle" // or callback
98
- },
99
- lines: {
100
- // we don't put in show: false so we can see
101
- // whether lines were actively disabled
102
- lineWidth: 2, // in pixels
103
- fill: false,
104
- fillColor: null,
105
- steps: false
106
- },
107
- bars: {
108
- show: false,
109
- lineWidth: 2, // in pixels
110
- barWidth: 1, // in units of the x axis
111
- fill: true,
112
- fillColor: null,
113
- align: "left", // or "center"
114
- horizontal: false
115
- },
116
- shadowSize: 3
117
- },
118
- grid: {
119
- show: true,
120
- aboveData: false,
121
- color: "#545454", // primary color used for outline and labels
122
- backgroundColor: null, // null for transparent, else color
123
- borderColor: "#333333", // set if different from the grid color
124
- tickColor: null, // color for the ticks, e.g. "rgba(0,0,0,0.15)"
125
- labelMargin: 5, // in pixels
126
- axisMargin: 8, // in pixels
127
- borderWidth: 1, // in pixels
128
- minBorderMargin: null, // in pixels, null means taken from points radius
129
- markings: null, // array of ranges or fn: axes -> array of ranges
130
- markingsColor: "#f4f4f4",
131
- markingsLineWidth: 2,
132
- // interactive stuff
133
- clickable: false,
134
- hoverable: false,
135
- autoHighlight: true, // highlight in case mouse is near
136
- mouseActiveRadius: 10 // how far the mouse can be away to activate an item
137
- },
138
- hooks: {}
139
- },
140
- canvas = null, // the canvas for the plot itself
141
- overlay = null, // canvas for interactive stuff on top of plot
142
- eventHolder = null, // jQuery object that events should be bound to
143
- ctx = null, octx = null,
144
- xaxes = [], yaxes = [],
145
- plotOffset = { left: 0, right: 0, top: 0, bottom: 0},
146
- canvasWidth = 0, canvasHeight = 0,
147
- plotWidth = 0, plotHeight = 0,
148
- hooks = {
149
- processOptions: [],
150
- processRawData: [],
151
- processDatapoints: [],
152
- drawSeries: [],
153
- draw: [],
154
- bindEvents: [],
155
- drawOverlay: [],
156
- shutdown: []
157
- },
158
- plot = this;
159
-
160
- // public functions
161
- plot.setData = setData;
162
- plot.setupGrid = setupGrid;
163
- plot.draw = draw;
164
- plot.getPlaceholder = function() { return placeholder; };
165
- plot.getCanvas = function() { return canvas; };
166
- plot.getPlotOffset = function() { return plotOffset; };
167
- plot.width = function () { return plotWidth; };
168
- plot.height = function () { return plotHeight; };
169
- plot.offset = function () {
170
- var o = eventHolder.offset();
171
- o.left += plotOffset.left;
172
- o.top += plotOffset.top;
173
- return o;
174
- };
175
- plot.getData = function () { return series; };
176
- plot.getAxes = function () {
177
- var res = {}, i;
178
- $.each(xaxes.concat(yaxes), function (_, axis) {
179
- if (axis)
180
- res[axis.direction + (axis.n != 1 ? axis.n : "") + "axis"] = axis;
181
- });
182
- return res;
183
- };
184
- plot.getXAxes = function () { return xaxes; };
185
- plot.getYAxes = function () { return yaxes; };
186
- plot.c2p = canvasToAxisCoords;
187
- plot.p2c = axisToCanvasCoords;
188
- plot.getOptions = function () { return options; };
189
- plot.highlight = highlight;
190
- plot.unhighlight = unhighlight;
191
- plot.triggerRedrawOverlay = triggerRedrawOverlay;
192
- plot.pointOffset = function(point) {
193
- return {
194
- left: parseInt(xaxes[axisNumber(point, "x") - 1].p2c(+point.x) + plotOffset.left),
195
- top: parseInt(yaxes[axisNumber(point, "y") - 1].p2c(+point.y) + plotOffset.top)
196
- };
197
- };
198
- plot.shutdown = shutdown;
199
- plot.resize = function () {
200
- getCanvasDimensions();
201
- resizeCanvas(canvas);
202
- resizeCanvas(overlay);
203
- };
204
-
205
- // public attributes
206
- plot.hooks = hooks;
207
-
208
- // initialize
209
- initPlugins(plot);
210
- parseOptions(options_);
211
- setupCanvases();
212
- setData(data_);
213
- setupGrid();
214
- draw();
215
- bindEvents();
216
-
217
-
218
- function executeHooks(hook, args) {
219
- args = [plot].concat(args);
220
- for (var i = 0; i < hook.length; ++i)
221
- hook[i].apply(this, args);
222
- }
223
-
224
- function initPlugins() {
225
- for (var i = 0; i < plugins.length; ++i) {
226
- var p = plugins[i];
227
- p.init(plot);
228
- if (p.options)
229
- $.extend(true, options, p.options);
230
- }
231
- }
232
-
233
- function parseOptions(opts) {
234
- var i;
235
-
236
- $.extend(true, options, opts);
237
-
238
- if (options.xaxis.color == null)
239
- options.xaxis.color = options.grid.color;
240
- if (options.yaxis.color == null)
241
- options.yaxis.color = options.grid.color;
242
-
243
- if (options.xaxis.tickColor == null) // backwards-compatibility
244
- options.xaxis.tickColor = options.grid.tickColor;
245
- if (options.yaxis.tickColor == null) // backwards-compatibility
246
- options.yaxis.tickColor = options.grid.tickColor;
247
-
248
- if (options.grid.borderColor == null)
249
- options.grid.borderColor = options.grid.color;
250
- if (options.grid.tickColor == null)
251
- options.grid.tickColor = $.color.parse(options.grid.color).scale('a', 0.22).toString();
252
-
253
- // fill in defaults in axes, copy at least always the
254
- // first as the rest of the code assumes it'll be there
255
- for (i = 0; i < Math.max(1, options.xaxes.length); ++i)
256
- options.xaxes[i] = $.extend(true, {}, options.xaxis, options.xaxes[i]);
257
- for (i = 0; i < Math.max(1, options.yaxes.length); ++i)
258
- options.yaxes[i] = $.extend(true, {}, options.yaxis, options.yaxes[i]);
259
-
260
- // backwards compatibility, to be removed in future
261
- if (options.xaxis.noTicks && options.xaxis.ticks == null)
262
- options.xaxis.ticks = options.xaxis.noTicks;
263
- if (options.yaxis.noTicks && options.yaxis.ticks == null)
264
- options.yaxis.ticks = options.yaxis.noTicks;
265
- if (options.x2axis) {
266
- options.xaxes[1] = $.extend(true, {}, options.xaxis, options.x2axis);
267
- options.xaxes[1].position = "top";
268
- }
269
- if (options.y2axis) {
270
- options.yaxes[1] = $.extend(true, {}, options.yaxis, options.y2axis);
271
- options.yaxes[1].position = "right";
272
- }
273
- if (options.grid.coloredAreas)
274
- options.grid.markings = options.grid.coloredAreas;
275
- if (options.grid.coloredAreasColor)
276
- options.grid.markingsColor = options.grid.coloredAreasColor;
277
- if (options.lines)
278
- $.extend(true, options.series.lines, options.lines);
279
- if (options.points)
280
- $.extend(true, options.series.points, options.points);
281
- if (options.bars)
282
- $.extend(true, options.series.bars, options.bars);
283
- if (options.shadowSize != null)
284
- options.series.shadowSize = options.shadowSize;
285
-
286
- // save options on axes for future reference
287
- for (i = 0; i < options.xaxes.length; ++i)
288
- getOrCreateAxis(xaxes, i + 1).options = options.xaxes[i];
289
- for (i = 0; i < options.yaxes.length; ++i)
290
- getOrCreateAxis(yaxes, i + 1).options = options.yaxes[i];
291
-
292
- // add hooks from options
293
- for (var n in hooks)
294
- if (options.hooks[n] && options.hooks[n].length)
295
- hooks[n] = hooks[n].concat(options.hooks[n]);
296
-
297
- executeHooks(hooks.processOptions, [options]);
298
- }
299
-
300
- function setData(d) {
301
- series = parseData(d);
302
- fillInSeriesOptions();
303
- processData();
304
- }
305
-
306
- function parseData(d) {
307
- var res = [];
308
- for (var i = 0; i < d.length; ++i) {
309
- var s = $.extend(true, {}, options.series);
310
-
311
- if (d[i].data != null) {
312
- s.data = d[i].data; // move the data instead of deep-copy
313
- delete d[i].data;
314
-
315
- $.extend(true, s, d[i]);
316
-
317
- d[i].data = s.data;
318
- }
319
- else
320
- s.data = d[i];
321
- res.push(s);
322
- }
323
-
324
- return res;
325
- }
326
-
327
- function axisNumber(obj, coord) {
328
- var a = obj[coord + "axis"];
329
- if (typeof a == "object") // if we got a real axis, extract number
330
- a = a.n;
331
- if (typeof a != "number")
332
- a = 1; // default to first axis
333
- return a;
334
- }
335
-
336
- function allAxes() {
337
- // return flat array without annoying null entries
338
- return $.grep(xaxes.concat(yaxes), function (a) { return a; });
339
- }
340
-
341
- function canvasToAxisCoords(pos) {
342
- // return an object with x/y corresponding to all used axes
343
- var res = {}, i, axis;
344
- for (i = 0; i < xaxes.length; ++i) {
345
- axis = xaxes[i];
346
- if (axis && axis.used)
347
- res["x" + axis.n] = axis.c2p(pos.left);
348
- }
349
-
350
- for (i = 0; i < yaxes.length; ++i) {
351
- axis = yaxes[i];
352
- if (axis && axis.used)
353
- res["y" + axis.n] = axis.c2p(pos.top);
354
- }
355
-
356
- if (res.x1 !== undefined)
357
- res.x = res.x1;
358
- if (res.y1 !== undefined)
359
- res.y = res.y1;
360
-
361
- return res;
362
- }
363
-
364
- function axisToCanvasCoords(pos) {
365
- // get canvas coords from the first pair of x/y found in pos
366
- var res = {}, i, axis, key;
367
-
368
- for (i = 0; i < xaxes.length; ++i) {
369
- axis = xaxes[i];
370
- if (axis && axis.used) {
371
- key = "x" + axis.n;
372
- if (pos[key] == null && axis.n == 1)
373
- key = "x";
374
-
375
- if (pos[key] != null) {
376
- res.left = axis.p2c(pos[key]);
377
- break;
378
- }
379
- }
380
- }
381
-
382
- for (i = 0; i < yaxes.length; ++i) {
383
- axis = yaxes[i];
384
- if (axis && axis.used) {
385
- key = "y" + axis.n;
386
- if (pos[key] == null && axis.n == 1)
387
- key = "y";
388
-
389
- if (pos[key] != null) {
390
- res.top = axis.p2c(pos[key]);
391
- break;
392
- }
393
- }
394
- }
395
-
396
- return res;
397
- }
398
-
399
- function getOrCreateAxis(axes, number) {
400
- if (!axes[number - 1])
401
- axes[number - 1] = {
402
- n: number, // save the number for future reference
403
- direction: axes == xaxes ? "x" : "y",
404
- options: $.extend(true, {}, axes == xaxes ? options.xaxis : options.yaxis)
405
- };
406
-
407
- return axes[number - 1];
408
- }
409
-
410
- function fillInSeriesOptions() {
411
- var i;
412
-
413
- // collect what we already got of colors
414
- var neededColors = series.length,
415
- usedColors = [],
416
- assignedColors = [];
417
- for (i = 0; i < series.length; ++i) {
418
- var sc = series[i].color;
419
- if (sc != null) {
420
- --neededColors;
421
- if (typeof sc == "number")
422
- assignedColors.push(sc);
423
- else
424
- usedColors.push($.color.parse(series[i].color));
425
- }
426
- }
427
-
428
- // we might need to generate more colors if higher indices
429
- // are assigned
430
- for (i = 0; i < assignedColors.length; ++i) {
431
- neededColors = Math.max(neededColors, assignedColors[i] + 1);
432
- }
433
-
434
- // produce colors as needed
435
- var colors = [], variation = 0;
436
- i = 0;
437
- while (colors.length < neededColors) {
438
- var c;
439
- if (options.colors.length == i) // check degenerate case
440
- c = $.color.make(100, 100, 100);
441
- else
442
- c = $.color.parse(options.colors[i]);
443
-
444
- // vary color if needed
445
- var sign = variation % 2 == 1 ? -1 : 1;
446
- c.scale('rgb', 1 + sign * Math.ceil(variation / 2) * 0.2)
447
-
448
- // FIXME: if we're getting to close to something else,
449
- // we should probably skip this one
450
- colors.push(c);
451
-
452
- ++i;
453
- if (i >= options.colors.length) {
454
- i = 0;
455
- ++variation;
456
- }
457
- }
458
-
459
- // fill in the options
460
- var colori = 0, s;
461
- for (i = 0; i < series.length; ++i) {
462
- s = series[i];
463
-
464
- // assign colors
465
- if (s.color == null) {
466
- s.color = colors[colori].toString();
467
- ++colori;
468
- }
469
- else if (typeof s.color == "number")
470
- s.color = colors[s.color].toString();
471
-
472
- // turn on lines automatically in case nothing is set
473
- if (s.lines.show == null) {
474
- var v, show = true;
475
- for (v in s)
476
- if (s[v] && s[v].show) {
477
- show = false;
478
- break;
479
- }
480
- if (show)
481
- s.lines.show = true;
482
- }
483
-
484
- // setup axes
485
- s.xaxis = getOrCreateAxis(xaxes, axisNumber(s, "x"));
486
- s.yaxis = getOrCreateAxis(yaxes, axisNumber(s, "y"));
487
- }
488
- }
489
-
490
- function processData() {
491
- var topSentry = Number.POSITIVE_INFINITY,
492
- bottomSentry = Number.NEGATIVE_INFINITY,
493
- fakeInfinity = Number.MAX_VALUE,
494
- i, j, k, m, length,
495
- s, points, ps, x, y, axis, val, f, p;
496
-
497
- function updateAxis(axis, min, max) {
498
- if (min < axis.datamin && min != -fakeInfinity)
499
- axis.datamin = min;
500
- if (max > axis.datamax && max != fakeInfinity)
501
- axis.datamax = max;
502
- }
503
-
504
- $.each(allAxes(), function (_, axis) {
505
- // init axis
506
- axis.datamin = topSentry;
507
- axis.datamax = bottomSentry;
508
- axis.used = false;
509
- });
510
-
511
- for (i = 0; i < series.length; ++i) {
512
- s = series[i];
513
- s.datapoints = { points: [] };
514
-
515
- executeHooks(hooks.processRawData, [ s, s.data, s.datapoints ]);
516
- }
517
-
518
- // first pass: clean and copy data
519
- for (i = 0; i < series.length; ++i) {
520
- s = series[i];
521
-
522
- var data = s.data, format = s.datapoints.format;
523
-
524
- if (!format) {
525
- format = [];
526
- // find out how to copy
527
- format.push({ x: true, number: true, required: true });
528
- format.push({ y: true, number: true, required: true });
529
-
530
- if (s.bars.show || (s.lines.show && s.lines.fill)) {
531
- format.push({ y: true, number: true, required: false, defaultValue: 0 });
532
- if (s.bars.horizontal) {
533
- delete format[format.length - 1].y;
534
- format[format.length - 1].x = true;
535
- }
536
- }
537
-
538
- s.datapoints.format = format;
539
- }
540
-
541
- if (s.datapoints.pointsize != null)
542
- continue; // already filled in
543
-
544
- s.datapoints.pointsize = format.length;
545
-
546
- ps = s.datapoints.pointsize;
547
- points = s.datapoints.points;
548
-
549
- insertSteps = s.lines.show && s.lines.steps;
550
- s.xaxis.used = s.yaxis.used = true;
551
-
552
- for (j = k = 0; j < data.length; ++j, k += ps) {
553
- p = data[j];
554
-
555
- var nullify = p == null;
556
- if (!nullify) {
557
- for (m = 0; m < ps; ++m) {
558
- val = p[m];
559
- f = format[m];
560
-
561
- if (f) {
562
- if (f.number && val != null) {
563
- val = +val; // convert to number
564
- if (isNaN(val))
565
- val = null;
566
- else if (val == Infinity)
567
- val = fakeInfinity;
568
- else if (val == -Infinity)
569
- val = -fakeInfinity;
570
- }
571
-
572
- if (val == null) {
573
- if (f.required)
574
- nullify = true;
575
-
576
- if (f.defaultValue != null)
577
- val = f.defaultValue;
578
- }
579
- }
580
-
581
- points[k + m] = val;
582
- }
583
- }
584
-
585
- if (nullify) {
586
- for (m = 0; m < ps; ++m) {
587
- val = points[k + m];
588
- if (val != null) {
589
- f = format[m];
590
- // extract min/max info
591
- if (f.x)
592
- updateAxis(s.xaxis, val, val);
593
- if (f.y)
594
- updateAxis(s.yaxis, val, val);
595
- }
596
- points[k + m] = null;
597
- }
598
- }
599
- else {
600
- // a little bit of line specific stuff that
601
- // perhaps shouldn't be here, but lacking
602
- // better means...
603
- if (insertSteps && k > 0
604
- && points[k - ps] != null
605
- && points[k - ps] != points[k]
606
- && points[k - ps + 1] != points[k + 1]) {
607
- // copy the point to make room for a middle point
608
- for (m = 0; m < ps; ++m)
609
- points[k + ps + m] = points[k + m];
610
-
611
- // middle point has same y
612
- points[k + 1] = points[k - ps + 1];
613
-
614
- // we've added a point, better reflect that
615
- k += ps;
616
- }
617
- }
618
- }
619
- }
620
-
621
- // give the hooks a chance to run
622
- for (i = 0; i < series.length; ++i) {
623
- s = series[i];
624
-
625
- executeHooks(hooks.processDatapoints, [ s, s.datapoints]);
626
- }
627
-
628
- // second pass: find datamax/datamin for auto-scaling
629
- for (i = 0; i < series.length; ++i) {
630
- s = series[i];
631
- points = s.datapoints.points,
632
- ps = s.datapoints.pointsize;
633
-
634
- var xmin = topSentry, ymin = topSentry,
635
- xmax = bottomSentry, ymax = bottomSentry;
636
-
637
- for (j = 0; j < points.length; j += ps) {
638
- if (points[j] == null)
639
- continue;
640
-
641
- for (m = 0; m < ps; ++m) {
642
- val = points[j + m];
643
- f = format[m];
644
- if (!f || val == fakeInfinity || val == -fakeInfinity)
645
- continue;
646
-
647
- if (f.x) {
648
- if (val < xmin)
649
- xmin = val;
650
- if (val > xmax)
651
- xmax = val;
652
- }
653
- if (f.y) {
654
- if (val < ymin)
655
- ymin = val;
656
- if (val > ymax)
657
- ymax = val;
658
- }
659
- }
660
- }
661
-
662
- if (s.bars.show) {
663
- // make sure we got room for the bar on the dancing floor
664
- var delta = s.bars.align == "left" ? 0 : -s.bars.barWidth/2;
665
- if (s.bars.horizontal) {
666
- ymin += delta;
667
- ymax += delta + s.bars.barWidth;
668
- }
669
- else {
670
- xmin += delta;
671
- xmax += delta + s.bars.barWidth;
672
- }
673
- }
674
-
675
- updateAxis(s.xaxis, xmin, xmax);
676
- updateAxis(s.yaxis, ymin, ymax);
677
- }
678
-
679
- $.each(allAxes(), function (_, axis) {
680
- if (axis.datamin == topSentry)
681
- axis.datamin = null;
682
- if (axis.datamax == bottomSentry)
683
- axis.datamax = null;
684
- });
685
- }
686
-
687
- function makeCanvas(skipPositioning, cls) {
688
- var c = document.createElement('canvas');
689
- c.className = cls;
690
- c.width = canvasWidth;
691
- c.height = canvasHeight;
692
-
693
- if (!skipPositioning)
694
- $(c).css({ position: 'absolute', left: 0, top: 0 });
695
-
696
- $(c).appendTo(placeholder);
697
-
698
- if (!c.getContext) // excanvas hack
699
- c = window.G_vmlCanvasManager.initElement(c);
700
-
701
- // used for resetting in case we get replotted
702
- c.getContext("2d").save();
703
-
704
- return c;
705
- }
706
-
707
- function getCanvasDimensions() {
708
- canvasWidth = placeholder.width();
709
- canvasHeight = placeholder.height();
710
-
711
- if (canvasWidth <= 0 || canvasHeight <= 0)
712
- throw "Invalid dimensions for plot, width = " + canvasWidth + ", height = " + canvasHeight;
713
- }
714
-
715
- function resizeCanvas(c) {
716
- // resizing should reset the state (excanvas seems to be
717
- // buggy though)
718
- if (c.width != canvasWidth)
719
- c.width = canvasWidth;
720
-
721
- if (c.height != canvasHeight)
722
- c.height = canvasHeight;
723
-
724
- // so try to get back to the initial state (even if it's
725
- // gone now, this should be safe according to the spec)
726
- var cctx = c.getContext("2d");
727
- cctx.restore();
728
-
729
- // and save again
730
- cctx.save();
731
- }
732
-
733
- function setupCanvases() {
734
- var reused,
735
- existingCanvas = placeholder.children("canvas.base"),
736
- existingOverlay = placeholder.children("canvas.overlay");
737
-
738
- if (existingCanvas.length == 0 || existingOverlay == 0) {
739
- // init everything
740
-
741
- placeholder.html(""); // make sure placeholder is clear
742
-
743
- placeholder.css({ padding: 0 }); // padding messes up the positioning
744
-
745
- if (placeholder.css("position") == 'static')
746
- placeholder.css("position", "relative"); // for positioning labels and overlay
747
-
748
- getCanvasDimensions();
749
-
750
- canvas = makeCanvas(true, "base");
751
- overlay = makeCanvas(false, "overlay"); // overlay canvas for interactive features
752
-
753
- reused = false;
754
- }
755
- else {
756
- // reuse existing elements
757
-
758
- canvas = existingCanvas.get(0);
759
- overlay = existingOverlay.get(0);
760
-
761
- reused = true;
762
- }
763
-
764
- ctx = canvas.getContext("2d");
765
- octx = overlay.getContext("2d");
766
-
767
- // we include the canvas in the event holder too, because IE 7
768
- // sometimes has trouble with the stacking order
769
- eventHolder = $([overlay, canvas]);
770
-
771
- if (reused) {
772
- // run shutdown in the old plot object
773
- placeholder.data("plot").shutdown();
774
-
775
- // reset reused canvases
776
- plot.resize();
777
-
778
- // make sure overlay pixels are cleared (canvas is cleared when we redraw)
779
- octx.clearRect(0, 0, canvasWidth, canvasHeight);
780
-
781
- // then whack any remaining obvious garbage left
782
- eventHolder.unbind();
783
- placeholder.children().not([canvas, overlay]).remove();
784
- }
785
-
786
- // save in case we get replotted
787
- placeholder.data("plot", plot);
788
- }
789
-
790
- function bindEvents() {
791
- // bind events
792
- if (options.grid.hoverable) {
793
- eventHolder.mousemove(onMouseMove);
794
- eventHolder.mouseleave(onMouseLeave);
795
- }
796
-
797
- if (options.grid.clickable)
798
- eventHolder.click(onClick);
799
-
800
- executeHooks(hooks.bindEvents, [eventHolder]);
801
- }
802
-
803
- function shutdown() {
804
- if (redrawTimeout)
805
- clearTimeout(redrawTimeout);
806
-
807
- eventHolder.unbind("mousemove", onMouseMove);
808
- eventHolder.unbind("mouseleave", onMouseLeave);
809
- eventHolder.unbind("click", onClick);
810
-
811
- executeHooks(hooks.shutdown, [eventHolder]);
812
- }
813
-
814
- function setTransformationHelpers(axis) {
815
- // set helper functions on the axis, assumes plot area
816
- // has been computed already
817
-
818
- function identity(x) { return x; }
819
-
820
- var s, m, t = axis.options.transform || identity,
821
- it = axis.options.inverseTransform;
822
-
823
- // precompute how much the axis is scaling a point
824
- // in canvas space
825
- if (axis.direction == "x") {
826
- s = axis.scale = plotWidth / Math.abs(t(axis.max) - t(axis.min));
827
- m = Math.min(t(axis.max), t(axis.min));
828
- }
829
- else {
830
- s = axis.scale = plotHeight / Math.abs(t(axis.max) - t(axis.min));
831
- s = -s;
832
- m = Math.max(t(axis.max), t(axis.min));
833
- }
834
-
835
- // data point to canvas coordinate
836
- if (t == identity) // slight optimization
837
- axis.p2c = function (p) { return (p - m) * s; };
838
- else
839
- axis.p2c = function (p) { return (t(p) - m) * s; };
840
- // canvas coordinate to data point
841
- if (!it)
842
- axis.c2p = function (c) { return m + c / s; };
843
- else
844
- axis.c2p = function (c) { return it(m + c / s); };
845
- }
846
-
847
- function measureTickLabels(axis) {
848
- var opts = axis.options, i, ticks = axis.ticks || [], labels = [],
849
- l, w = opts.labelWidth, h = opts.labelHeight, dummyDiv;
850
-
851
- function makeDummyDiv(labels, width) {
852
- return $('<div style="position:absolute;top:-10000px;' + width + 'font-size:smaller">' +
853
- '<div class="' + axis.direction + 'Axis ' + axis.direction + axis.n + 'Axis">'
854
- + labels.join("") + '</div></div>')
855
- .appendTo(placeholder);
856
- }
857
-
858
- if (axis.direction == "x") {
859
- // to avoid measuring the widths of the labels (it's slow), we
860
- // construct fixed-size boxes and put the labels inside
861
- // them, we don't need the exact figures and the
862
- // fixed-size box content is easy to center
863
- if (w == null)
864
- w = Math.floor(canvasWidth / (ticks.length > 0 ? ticks.length : 1));
865
-
866
- // measure x label heights
867
- if (h == null) {
868
- labels = [];
869
- for (i = 0; i < ticks.length; ++i) {
870
- l = ticks[i].label;
871
- if (l)
872
- labels.push('<div class="tickLabel" style="float:left;width:' + w + 'px">' + l + '</div>');
873
- }
874
-
875
- if (labels.length > 0) {
876
- // stick them all in the same div and measure
877
- // collective height
878
- labels.push('<div style="clear:left"></div>');
879
- dummyDiv = makeDummyDiv(labels, "width:10000px;");
880
- h = dummyDiv.height();
881
- dummyDiv.remove();
882
- }
883
- }
884
- }
885
- else if (w == null || h == null) {
886
- // calculate y label dimensions
887
- for (i = 0; i < ticks.length; ++i) {
888
- l = ticks[i].label;
889
- if (l)
890
- labels.push('<div class="tickLabel">' + l + '</div>');
891
- }
892
-
893
- if (labels.length > 0) {
894
- dummyDiv = makeDummyDiv(labels, "");
895
- if (w == null)
896
- w = dummyDiv.children().width();
897
- if (h == null)
898
- h = dummyDiv.find("div.tickLabel").height();
899
- dummyDiv.remove();
900
- }
901
- }
902
-
903
- if (w == null)
904
- w = 0;
905
- if (h == null)
906
- h = 0;
907
-
908
- axis.labelWidth = w;
909
- axis.labelHeight = h;
910
- }
911
-
912
- function allocateAxisBoxFirstPhase(axis) {
913
- // find the bounding box of the axis by looking at label
914
- // widths/heights and ticks, make room by diminishing the
915
- // plotOffset
916
-
917
- var lw = axis.labelWidth,
918
- lh = axis.labelHeight,
919
- pos = axis.options.position,
920
- tickLength = axis.options.tickLength,
921
- axismargin = options.grid.axisMargin,
922
- padding = options.grid.labelMargin,
923
- all = axis.direction == "x" ? xaxes : yaxes,
924
- index;
925
-
926
- // determine axis margin
927
- var samePosition = $.grep(all, function (a) {
928
- return a && a.options.position == pos && a.reserveSpace;
929
- });
930
- if ($.inArray(axis, samePosition) == samePosition.length - 1)
931
- axismargin = 0; // outermost
932
-
933
- // determine tick length - if we're innermost, we can use "full"
934
- if (tickLength == null)
935
- tickLength = "full";
936
-
937
- var sameDirection = $.grep(all, function (a) {
938
- return a && a.reserveSpace;
939
- });
940
-
941
- var innermost = $.inArray(axis, sameDirection) == 0;
942
- if (!innermost && tickLength == "full")
943
- tickLength = 5;
944
-
945
- if (!isNaN(+tickLength))
946
- padding += +tickLength;
947
-
948
- // compute box
949
- if (axis.direction == "x") {
950
- lh += padding;
951
-
952
- if (pos == "bottom") {
953
- plotOffset.bottom += lh + axismargin;
954
- axis.box = { top: canvasHeight - plotOffset.bottom, height: lh };
955
- }
956
- else {
957
- axis.box = { top: plotOffset.top + axismargin, height: lh };
958
- plotOffset.top += lh + axismargin;
959
- }
960
- }
961
- else {
962
- lw += padding;
963
-
964
- if (pos == "left") {
965
- axis.box = { left: plotOffset.left + axismargin, width: lw };
966
- plotOffset.left += lw + axismargin;
967
- }
968
- else {
969
- plotOffset.right += lw + axismargin;
970
- axis.box = { left: canvasWidth - plotOffset.right, width: lw };
971
- }
972
- }
973
-
974
- // save for future reference
975
- axis.position = pos;
976
- axis.tickLength = tickLength;
977
- axis.box.padding = padding;
978
- axis.innermost = innermost;
979
- }
980
-
981
- function allocateAxisBoxSecondPhase(axis) {
982
- // set remaining bounding box coordinates
983
- if (axis.direction == "x") {
984
- axis.box.left = plotOffset.left;
985
- axis.box.width = plotWidth;
986
- }
987
- else {
988
- axis.box.top = plotOffset.top;
989
- axis.box.height = plotHeight;
990
- }
991
- }
992
-
993
- function setupGrid() {
994
- var i, axes = allAxes();
995
-
996
- // first calculate the plot and axis box dimensions
997
-
998
- $.each(axes, function (_, axis) {
999
- axis.show = axis.options.show;
1000
- if (axis.show == null)
1001
- axis.show = axis.used; // by default an axis is visible if it's got data
1002
-
1003
- axis.reserveSpace = axis.show || axis.options.reserveSpace;
1004
-
1005
- setRange(axis);
1006
- });
1007
-
1008
- allocatedAxes = $.grep(axes, function (axis) { return axis.reserveSpace; });
1009
-
1010
- plotOffset.left = plotOffset.right = plotOffset.top = plotOffset.bottom = 0;
1011
- if (options.grid.show) {
1012
- $.each(allocatedAxes, function (_, axis) {
1013
- // make the ticks
1014
- setupTickGeneration(axis);
1015
- setTicks(axis);
1016
- snapRangeToTicks(axis, axis.ticks);
1017
-
1018
- // find labelWidth/Height for axis
1019
- measureTickLabels(axis);
1020
- });
1021
-
1022
- // with all dimensions in house, we can compute the
1023
- // axis boxes, start from the outside (reverse order)
1024
- for (i = allocatedAxes.length - 1; i >= 0; --i)
1025
- allocateAxisBoxFirstPhase(allocatedAxes[i]);
1026
-
1027
- // make sure we've got enough space for things that
1028
- // might stick out
1029
- var minMargin = options.grid.minBorderMargin;
1030
- if (minMargin == null) {
1031
- minMargin = 0;
1032
- for (i = 0; i < series.length; ++i)
1033
- minMargin = Math.max(minMargin, series[i].points.radius + series[i].points.lineWidth/2);
1034
- }
1035
-
1036
- for (var a in plotOffset) {
1037
- plotOffset[a] += options.grid.borderWidth;
1038
- plotOffset[a] = Math.max(minMargin, plotOffset[a]);
1039
- }
1040
- }
1041
-
1042
- plotWidth = canvasWidth - plotOffset.left - plotOffset.right;
1043
- plotHeight = canvasHeight - plotOffset.bottom - plotOffset.top;
1044
-
1045
- // now we got the proper plotWidth/Height, we can compute the scaling
1046
- $.each(axes, function (_, axis) {
1047
- setTransformationHelpers(axis);
1048
- });
1049
-
1050
- if (options.grid.show) {
1051
- $.each(allocatedAxes, function (_, axis) {
1052
- allocateAxisBoxSecondPhase(axis);
1053
- });
1054
-
1055
- insertAxisLabels();
1056
- }
1057
-
1058
- insertLegend();
1059
- }
1060
-
1061
- function setRange(axis) {
1062
- var opts = axis.options,
1063
- min = +(opts.min != null ? opts.min : axis.datamin),
1064
- max = +(opts.max != null ? opts.max : axis.datamax),
1065
- delta = max - min;
1066
-
1067
- if (delta == 0.0) {
1068
- // degenerate case
1069
- var widen = max == 0 ? 1 : 0.01;
1070
-
1071
- if (opts.min == null)
1072
- min -= widen;
1073
- // always widen max if we couldn't widen min to ensure we
1074
- // don't fall into min == max which doesn't work
1075
- if (opts.max == null || opts.min != null)
1076
- max += widen;
1077
- }
1078
- else {
1079
- // consider autoscaling
1080
- var margin = opts.autoscaleMargin;
1081
- if (margin != null) {
1082
- if (opts.min == null) {
1083
- min -= delta * margin;
1084
- // make sure we don't go below zero if all values
1085
- // are positive
1086
- if (min < 0 && axis.datamin != null && axis.datamin >= 0)
1087
- min = 0;
1088
- }
1089
- if (opts.max == null) {
1090
- max += delta * margin;
1091
- if (max > 0 && axis.datamax != null && axis.datamax <= 0)
1092
- max = 0;
1093
- }
1094
- }
1095
- }
1096
- axis.min = min;
1097
- axis.max = max;
1098
- }
1099
-
1100
- function setupTickGeneration(axis) {
1101
- var opts = axis.options;
1102
-
1103
- // estimate number of ticks
1104
- var noTicks;
1105
- if (typeof opts.ticks == "number" && opts.ticks > 0)
1106
- noTicks = opts.ticks;
1107
- else
1108
- // heuristic based on the model a*sqrt(x) fitted to
1109
- // some data points that seemed reasonable
1110
- noTicks = 0.3 * Math.sqrt(axis.direction == "x" ? canvasWidth : canvasHeight);
1111
-
1112
- var delta = (axis.max - axis.min) / noTicks,
1113
- size, generator, unit, formatter, i, magn, norm;
1114
-
1115
- if (opts.mode == "time") {
1116
- // pretty handling of time
1117
-
1118
- // map of app. size of time units in milliseconds
1119
- var timeUnitSize = {
1120
- "second": 1000,
1121
- "minute": 60 * 1000,
1122
- "hour": 60 * 60 * 1000,
1123
- "day": 24 * 60 * 60 * 1000,
1124
- "month": 30 * 24 * 60 * 60 * 1000,
1125
- "year": 365.2425 * 24 * 60 * 60 * 1000
1126
- };
1127
-
1128
-
1129
- // the allowed tick sizes, after 1 year we use
1130
- // an integer algorithm
1131
- var spec = [
1132
- [1, "second"], [2, "second"], [5, "second"], [10, "second"],
1133
- [30, "second"],
1134
- [1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"],
1135
- [30, "minute"],
1136
- [1, "hour"], [2, "hour"], [4, "hour"],
1137
- [8, "hour"], [12, "hour"],
1138
- [1, "day"], [2, "day"], [3, "day"],
1139
- [0.25, "month"], [0.5, "month"], [1, "month"],
1140
- [2, "month"], [3, "month"], [6, "month"],
1141
- [1, "year"]
1142
- ];
1143
-
1144
- var minSize = 0;
1145
- if (opts.minTickSize != null) {
1146
- if (typeof opts.tickSize == "number")
1147
- minSize = opts.tickSize;
1148
- else
1149
- minSize = opts.minTickSize[0] * timeUnitSize[opts.minTickSize[1]];
1150
- }
1151
-
1152
- for (var i = 0; i < spec.length - 1; ++i)
1153
- if (delta < (spec[i][0] * timeUnitSize[spec[i][1]]
1154
- + spec[i + 1][0] * timeUnitSize[spec[i + 1][1]]) / 2
1155
- && spec[i][0] * timeUnitSize[spec[i][1]] >= minSize)
1156
- break;
1157
- size = spec[i][0];
1158
- unit = spec[i][1];
1159
-
1160
- // special-case the possibility of several years
1161
- if (unit == "year") {
1162
- magn = Math.pow(10, Math.floor(Math.log(delta / timeUnitSize.year) / Math.LN10));
1163
- norm = (delta / timeUnitSize.year) / magn;
1164
- if (norm < 1.5)
1165
- size = 1;
1166
- else if (norm < 3)
1167
- size = 2;
1168
- else if (norm < 7.5)
1169
- size = 5;
1170
- else
1171
- size = 10;
1172
-
1173
- size *= magn;
1174
- }
1175
-
1176
- axis.tickSize = opts.tickSize || [size, unit];
1177
-
1178
- generator = function(axis) {
1179
- var ticks = [],
1180
- tickSize = axis.tickSize[0], unit = axis.tickSize[1],
1181
- d = new Date(axis.min);
1182
-
1183
- var step = tickSize * timeUnitSize[unit];
1184
-
1185
- if (unit == "second")
1186
- d.setUTCSeconds(floorInBase(d.getUTCSeconds(), tickSize));
1187
- if (unit == "minute")
1188
- d.setUTCMinutes(floorInBase(d.getUTCMinutes(), tickSize));
1189
- if (unit == "hour")
1190
- d.setUTCHours(floorInBase(d.getUTCHours(), tickSize));
1191
- if (unit == "month")
1192
- d.setUTCMonth(floorInBase(d.getUTCMonth(), tickSize));
1193
- if (unit == "year")
1194
- d.setUTCFullYear(floorInBase(d.getUTCFullYear(), tickSize));
1195
-
1196
- // reset smaller components
1197
- d.setUTCMilliseconds(0);
1198
- if (step >= timeUnitSize.minute)
1199
- d.setUTCSeconds(0);
1200
- if (step >= timeUnitSize.hour)
1201
- d.setUTCMinutes(0);
1202
- if (step >= timeUnitSize.day)
1203
- d.setUTCHours(0);
1204
- if (step >= timeUnitSize.day * 4)
1205
- d.setUTCDate(1);
1206
- if (step >= timeUnitSize.year)
1207
- d.setUTCMonth(0);
1208
-
1209
-
1210
- var carry = 0, v = Number.NaN, prev;
1211
- do {
1212
- prev = v;
1213
- v = d.getTime();
1214
- ticks.push(v);
1215
- if (unit == "month") {
1216
- if (tickSize < 1) {
1217
- // a bit complicated - we'll divide the month
1218
- // up but we need to take care of fractions
1219
- // so we don't end up in the middle of a day
1220
- d.setUTCDate(1);
1221
- var start = d.getTime();
1222
- d.setUTCMonth(d.getUTCMonth() + 1);
1223
- var end = d.getTime();
1224
- d.setTime(v + carry * timeUnitSize.hour + (end - start) * tickSize);
1225
- carry = d.getUTCHours();
1226
- d.setUTCHours(0);
1227
- }
1228
- else
1229
- d.setUTCMonth(d.getUTCMonth() + tickSize);
1230
- }
1231
- else if (unit == "year") {
1232
- d.setUTCFullYear(d.getUTCFullYear() + tickSize);
1233
- }
1234
- else
1235
- d.setTime(v + step);
1236
- } while (v < axis.max && v != prev);
1237
-
1238
- return ticks;
1239
- };
1240
-
1241
- formatter = function (v, axis) {
1242
- var d = new Date(v);
1243
-
1244
- // first check global format
1245
- if (opts.timeformat != null)
1246
- return $.plot.formatDate(d, opts.timeformat, opts.monthNames);
1247
-
1248
- var t = axis.tickSize[0] * timeUnitSize[axis.tickSize[1]];
1249
- var span = axis.max - axis.min;
1250
- var suffix = (opts.twelveHourClock) ? " %p" : "";
1251
-
1252
- if (t < timeUnitSize.minute)
1253
- fmt = "%h:%M:%S" + suffix;
1254
- else if (t < timeUnitSize.day) {
1255
- if (span < 2 * timeUnitSize.day)
1256
- fmt = "%h:%M" + suffix;
1257
- else
1258
- fmt = "%b %d %h:%M" + suffix;
1259
- }
1260
- else if (t < timeUnitSize.month)
1261
- fmt = "%b %d";
1262
- else if (t < timeUnitSize.year) {
1263
- if (span < timeUnitSize.year)
1264
- fmt = "%b";
1265
- else
1266
- fmt = "%b %y";
1267
- }
1268
- else
1269
- fmt = "%y";
1270
-
1271
- return $.plot.formatDate(d, fmt, opts.monthNames);
1272
- };
1273
- }
1274
- else {
1275
- // pretty rounding of base-10 numbers
1276
- var maxDec = opts.tickDecimals;
1277
- var dec = -Math.floor(Math.log(delta) / Math.LN10);
1278
- if (maxDec != null && dec > maxDec)
1279
- dec = maxDec;
1280
-
1281
- magn = Math.pow(10, -dec);
1282
- norm = delta / magn; // norm is between 1.0 and 10.0
1283
-
1284
- if (norm < 1.5)
1285
- size = 1;
1286
- else if (norm < 3) {
1287
- size = 2;
1288
- // special case for 2.5, requires an extra decimal
1289
- if (norm > 2.25 && (maxDec == null || dec + 1 <= maxDec)) {
1290
- size = 2.5;
1291
- ++dec;
1292
- }
1293
- }
1294
- else if (norm < 7.5)
1295
- size = 5;
1296
- else
1297
- size = 10;
1298
-
1299
- size *= magn;
1300
-
1301
- if (opts.minTickSize != null && size < opts.minTickSize)
1302
- size = opts.minTickSize;
1303
-
1304
- axis.tickDecimals = Math.max(0, maxDec != null ? maxDec : dec);
1305
- axis.tickSize = opts.tickSize || size;
1306
-
1307
- generator = function (axis) {
1308
- var ticks = [];
1309
-
1310
- // spew out all possible ticks
1311
- var start = floorInBase(axis.min, axis.tickSize),
1312
- i = 0, v = Number.NaN, prev;
1313
- do {
1314
- prev = v;
1315
- v = start + i * axis.tickSize;
1316
- ticks.push(v);
1317
- ++i;
1318
- } while (v < axis.max && v != prev);
1319
- return ticks;
1320
- };
1321
-
1322
- formatter = function (v, axis) {
1323
- return v.toFixed(axis.tickDecimals);
1324
- };
1325
- }
1326
-
1327
- if (opts.alignTicksWithAxis != null) {
1328
- var otherAxis = (axis.direction == "x" ? xaxes : yaxes)[opts.alignTicksWithAxis - 1];
1329
- if (otherAxis && otherAxis.used && otherAxis != axis) {
1330
- // consider snapping min/max to outermost nice ticks
1331
- var niceTicks = generator(axis);
1332
- if (niceTicks.length > 0) {
1333
- if (opts.min == null)
1334
- axis.min = Math.min(axis.min, niceTicks[0]);
1335
- if (opts.max == null && niceTicks.length > 1)
1336
- axis.max = Math.max(axis.max, niceTicks[niceTicks.length - 1]);
1337
- }
1338
-
1339
- generator = function (axis) {
1340
- // copy ticks, scaled to this axis
1341
- var ticks = [], v, i;
1342
- for (i = 0; i < otherAxis.ticks.length; ++i) {
1343
- v = (otherAxis.ticks[i].v - otherAxis.min) / (otherAxis.max - otherAxis.min);
1344
- v = axis.min + v * (axis.max - axis.min);
1345
- ticks.push(v);
1346
- }
1347
- return ticks;
1348
- };
1349
-
1350
- // we might need an extra decimal since forced
1351
- // ticks don't necessarily fit naturally
1352
- if (axis.mode != "time" && opts.tickDecimals == null) {
1353
- var extraDec = Math.max(0, -Math.floor(Math.log(delta) / Math.LN10) + 1),
1354
- ts = generator(axis);
1355
-
1356
- // only proceed if the tick interval rounded
1357
- // with an extra decimal doesn't give us a
1358
- // zero at end
1359
- if (!(ts.length > 1 && /\..*0$/.test((ts[1] - ts[0]).toFixed(extraDec))))
1360
- axis.tickDecimals = extraDec;
1361
- }
1362
- }
1363
- }
1364
-
1365
- axis.tickGenerator = generator;
1366
- if ($.isFunction(opts.tickFormatter))
1367
- axis.tickFormatter = function (v, axis) { return "" + opts.tickFormatter(v, axis); };
1368
- else
1369
- axis.tickFormatter = formatter;
1370
- }
1371
-
1372
- function setTicks(axis) {
1373
- var oticks = axis.options.ticks, ticks = [];
1374
- if (oticks == null || (typeof oticks == "number" && oticks > 0))
1375
- ticks = axis.tickGenerator(axis);
1376
- else if (oticks) {
1377
- if ($.isFunction(oticks))
1378
- // generate the ticks
1379
- ticks = oticks({ min: axis.min, max: axis.max });
1380
- else
1381
- ticks = oticks;
1382
- }
1383
-
1384
- // clean up/labelify the supplied ticks, copy them over
1385
- var i, v;
1386
- axis.ticks = [];
1387
- for (i = 0; i < ticks.length; ++i) {
1388
- var label = null;
1389
- var t = ticks[i];
1390
- if (typeof t == "object") {
1391
- v = +t[0];
1392
- if (t.length > 1)
1393
- label = t[1];
1394
- }
1395
- else
1396
- v = +t;
1397
- if (label == null)
1398
- label = axis.tickFormatter(v, axis);
1399
- if (!isNaN(v))
1400
- axis.ticks.push({ v: v, label: label });
1401
- }
1402
- }
1403
-
1404
- function snapRangeToTicks(axis, ticks) {
1405
- if (axis.options.autoscaleMargin && ticks.length > 0) {
1406
- // snap to ticks
1407
- if (axis.options.min == null)
1408
- axis.min = Math.min(axis.min, ticks[0].v);
1409
- if (axis.options.max == null && ticks.length > 1)
1410
- axis.max = Math.max(axis.max, ticks[ticks.length - 1].v);
1411
- }
1412
- }
1413
-
1414
- function draw() {
1415
- ctx.clearRect(0, 0, canvasWidth, canvasHeight);
1416
-
1417
- var grid = options.grid;
1418
-
1419
- // draw background, if any
1420
- if (grid.show && grid.backgroundColor)
1421
- drawBackground();
1422
-
1423
- if (grid.show && !grid.aboveData)
1424
- drawGrid();
1425
-
1426
- for (var i = 0; i < series.length; ++i) {
1427
- executeHooks(hooks.drawSeries, [ctx, series[i]]);
1428
- drawSeries(series[i]);
1429
- }
1430
-
1431
- executeHooks(hooks.draw, [ctx]);
1432
-
1433
- if (grid.show && grid.aboveData)
1434
- drawGrid();
1435
- }
1436
-
1437
- function extractRange(ranges, coord) {
1438
- var axis, from, to, key, axes = allAxes();
1439
-
1440
- for (i = 0; i < axes.length; ++i) {
1441
- axis = axes[i];
1442
- if (axis.direction == coord) {
1443
- key = coord + axis.n + "axis";
1444
- if (!ranges[key] && axis.n == 1)
1445
- key = coord + "axis"; // support x1axis as xaxis
1446
- if (ranges[key]) {
1447
- from = ranges[key].from;
1448
- to = ranges[key].to;
1449
- break;
1450
- }
1451
- }
1452
- }
1453
-
1454
- // backwards-compat stuff - to be removed in future
1455
- if (!ranges[key]) {
1456
- axis = coord == "x" ? xaxes[0] : yaxes[0];
1457
- from = ranges[coord + "1"];
1458
- to = ranges[coord + "2"];
1459
- }
1460
-
1461
- // auto-reverse as an added bonus
1462
- if (from != null && to != null && from > to) {
1463
- var tmp = from;
1464
- from = to;
1465
- to = tmp;
1466
- }
1467
-
1468
- return { from: from, to: to, axis: axis };
1469
- }
1470
-
1471
- function drawBackground() {
1472
- ctx.save();
1473
- ctx.translate(plotOffset.left, plotOffset.top);
1474
-
1475
- ctx.fillStyle = getColorOrGradient(options.grid.backgroundColor, plotHeight, 0, "rgba(255, 255, 255, 0)");
1476
- ctx.fillRect(0, 0, plotWidth, plotHeight);
1477
- ctx.restore();
1478
- }
1479
-
1480
- function drawGrid() {
1481
- var i;
1482
-
1483
- ctx.save();
1484
- ctx.translate(plotOffset.left, plotOffset.top);
1485
-
1486
- // draw markings
1487
- var markings = options.grid.markings;
1488
- if (markings) {
1489
- if ($.isFunction(markings)) {
1490
- var axes = plot.getAxes();
1491
- // xmin etc. is backwards compatibility, to be
1492
- // removed in the future
1493
- axes.xmin = axes.xaxis.min;
1494
- axes.xmax = axes.xaxis.max;
1495
- axes.ymin = axes.yaxis.min;
1496
- axes.ymax = axes.yaxis.max;
1497
-
1498
- markings = markings(axes);
1499
- }
1500
-
1501
- for (i = 0; i < markings.length; ++i) {
1502
- var m = markings[i],
1503
- xrange = extractRange(m, "x"),
1504
- yrange = extractRange(m, "y");
1505
-
1506
- // fill in missing
1507
- if (xrange.from == null)
1508
- xrange.from = xrange.axis.min;
1509
- if (xrange.to == null)
1510
- xrange.to = xrange.axis.max;
1511
- if (yrange.from == null)
1512
- yrange.from = yrange.axis.min;
1513
- if (yrange.to == null)
1514
- yrange.to = yrange.axis.max;
1515
-
1516
- // clip
1517
- if (xrange.to < xrange.axis.min || xrange.from > xrange.axis.max ||
1518
- yrange.to < yrange.axis.min || yrange.from > yrange.axis.max)
1519
- continue;
1520
-
1521
- xrange.from = Math.max(xrange.from, xrange.axis.min);
1522
- xrange.to = Math.min(xrange.to, xrange.axis.max);
1523
- yrange.from = Math.max(yrange.from, yrange.axis.min);
1524
- yrange.to = Math.min(yrange.to, yrange.axis.max);
1525
-
1526
- if (xrange.from == xrange.to && yrange.from == yrange.to)
1527
- continue;
1528
-
1529
- // then draw
1530
- xrange.from = xrange.axis.p2c(xrange.from);
1531
- xrange.to = xrange.axis.p2c(xrange.to);
1532
- yrange.from = yrange.axis.p2c(yrange.from);
1533
- yrange.to = yrange.axis.p2c(yrange.to);
1534
-
1535
- if (xrange.from == xrange.to || yrange.from == yrange.to) {
1536
- // draw line
1537
- ctx.beginPath();
1538
- ctx.strokeStyle = m.color || options.grid.markingsColor;
1539
- ctx.lineWidth = m.lineWidth || options.grid.markingsLineWidth;
1540
- ctx.moveTo(xrange.from, yrange.from);
1541
- ctx.lineTo(xrange.to, yrange.to);
1542
- ctx.stroke();
1543
- }
1544
- else {
1545
- // fill area
1546
- ctx.fillStyle = m.color || options.grid.markingsColor;
1547
- ctx.fillRect(xrange.from, yrange.to,
1548
- xrange.to - xrange.from,
1549
- yrange.from - yrange.to);
1550
- }
1551
- }
1552
- }
1553
-
1554
- // draw the ticks
1555
- var axes = allAxes(), bw = options.grid.borderWidth;
1556
-
1557
- for (var j = 0; j < axes.length; ++j) {
1558
- var axis = axes[j], box = axis.box,
1559
- t = axis.tickLength, x, y, xoff, yoff;
1560
- if (!axis.show || axis.ticks.length == 0)
1561
- continue
1562
-
1563
- ctx.strokeStyle = axis.options.tickColor || $.color.parse(axis.options.color).scale('a', 0.22).toString();
1564
- ctx.lineWidth = 1;
1565
-
1566
- // find the edges
1567
- if (axis.direction == "x") {
1568
- x = 0;
1569
- if (t == "full")
1570
- y = (axis.position == "top" ? 0 : plotHeight);
1571
- else
1572
- y = box.top - plotOffset.top + (axis.position == "top" ? box.height : 0);
1573
- }
1574
- else {
1575
- y = 0;
1576
- if (t == "full")
1577
- x = (axis.position == "left" ? 0 : plotWidth);
1578
- else
1579
- x = box.left - plotOffset.left + (axis.position == "left" ? box.width : 0);
1580
- }
1581
-
1582
- // draw tick bar
1583
- if (!axis.innermost) {
1584
- ctx.beginPath();
1585
- xoff = yoff = 0;
1586
- if (axis.direction == "x")
1587
- xoff = plotWidth;
1588
- else
1589
- yoff = plotHeight;
1590
-
1591
- if (ctx.lineWidth == 1) {
1592
- x = Math.floor(x) + 0.5;
1593
- y = Math.floor(y) + 0.5;
1594
- }
1595
-
1596
- ctx.moveTo(x, y);
1597
- ctx.lineTo(x + xoff, y + yoff);
1598
- ctx.stroke();
1599
- }
1600
-
1601
- // draw ticks
1602
- ctx.beginPath();
1603
- for (i = 0; i < axis.ticks.length; ++i) {
1604
- var v = axis.ticks[i].v;
1605
-
1606
- xoff = yoff = 0;
1607
-
1608
- if (v < axis.min || v > axis.max
1609
- // skip those lying on the axes if we got a border
1610
- || (t == "full" && bw > 0
1611
- && (v == axis.min || v == axis.max)))
1612
- continue;
1613
-
1614
- if (axis.direction == "x") {
1615
- x = axis.p2c(v);
1616
- yoff = t == "full" ? -plotHeight : t;
1617
-
1618
- if (axis.position == "top")
1619
- yoff = -yoff;
1620
- }
1621
- else {
1622
- y = axis.p2c(v);
1623
- xoff = t == "full" ? -plotWidth : t;
1624
-
1625
- if (axis.position == "left")
1626
- xoff = -xoff;
1627
- }
1628
-
1629
- if (ctx.lineWidth == 1) {
1630
- if (axis.direction == "x")
1631
- x = Math.floor(x) + 0.5;
1632
- else
1633
- y = Math.floor(y) + 0.5;
1634
- }
1635
-
1636
- ctx.moveTo(x, y);
1637
- ctx.lineTo(x + xoff, y + yoff);
1638
- }
1639
-
1640
- ctx.stroke();
1641
- }
1642
-
1643
-
1644
- // draw border
1645
- if (bw) {
1646
- ctx.lineWidth = bw;
1647
- ctx.strokeStyle = options.grid.borderColor;
1648
- ctx.strokeRect(-bw/2, -bw/2, plotWidth + bw, plotHeight + bw);
1649
- }
1650
-
1651
- ctx.restore();
1652
- }
1653
-
1654
- function insertAxisLabels() {
1655
- placeholder.find(".tickLabels").remove();
1656
-
1657
- var html = ['<div class="tickLabels" style="font-size:smaller">'];
1658
-
1659
- var axes = allAxes();
1660
- for (var j = 0; j < axes.length; ++j) {
1661
- var axis = axes[j], box = axis.box;
1662
- if (!axis.show)
1663
- continue;
1664
- //debug: html.push('<div style="position:absolute;opacity:0.10;background-color:red;left:' + box.left + 'px;top:' + box.top + 'px;width:' + box.width + 'px;height:' + box.height + 'px"></div>')
1665
- html.push('<div class="' + axis.direction + 'Axis ' + axis.direction + axis.n + 'Axis" style="color:' + axis.options.color + '">');
1666
- for (var i = 0; i < axis.ticks.length; ++i) {
1667
- var tick = axis.ticks[i];
1668
- if (!tick.label || tick.v < axis.min || tick.v > axis.max)
1669
- continue;
1670
-
1671
- var pos = {}, align;
1672
-
1673
- if (axis.direction == "x") {
1674
- align = "center";
1675
- pos.left = Math.round(plotOffset.left + axis.p2c(tick.v) - axis.labelWidth/2);
1676
- if (axis.position == "bottom")
1677
- pos.top = box.top + box.padding;
1678
- else
1679
- pos.bottom = canvasHeight - (box.top + box.height - box.padding);
1680
- }
1681
- else {
1682
- pos.top = Math.round(plotOffset.top + axis.p2c(tick.v) - axis.labelHeight/2);
1683
- if (axis.position == "left") {
1684
- pos.right = canvasWidth - (box.left + box.width - box.padding)
1685
- align = "right";
1686
- }
1687
- else {
1688
- pos.left = box.left + box.padding;
1689
- align = "left";
1690
- }
1691
- }
1692
-
1693
- pos.width = axis.labelWidth;
1694
-
1695
- var style = ["position:absolute", "text-align:" + align ];
1696
- for (var a in pos)
1697
- style.push(a + ":" + pos[a] + "px")
1698
-
1699
- html.push('<div class="tickLabel" style="' + style.join(';') + '">' + tick.label + '</div>');
1700
- }
1701
- html.push('</div>');
1702
- }
1703
-
1704
- html.push('</div>');
1705
-
1706
- placeholder.append(html.join(""));
1707
- }
1708
-
1709
- function drawSeries(series) {
1710
- if (series.lines.show)
1711
- drawSeriesLines(series);
1712
- if (series.bars.show)
1713
- drawSeriesBars(series);
1714
- if (series.points.show)
1715
- drawSeriesPoints(series);
1716
- }
1717
-
1718
- function drawSeriesLines(series) {
1719
- function plotLine(datapoints, xoffset, yoffset, axisx, axisy) {
1720
- var points = datapoints.points,
1721
- ps = datapoints.pointsize,
1722
- prevx = null, prevy = null;
1723
-
1724
- ctx.beginPath();
1725
- for (var i = ps; i < points.length; i += ps) {
1726
- var x1 = points[i - ps], y1 = points[i - ps + 1],
1727
- x2 = points[i], y2 = points[i + 1];
1728
-
1729
- if (x1 == null || x2 == null)
1730
- continue;
1731
-
1732
- // clip with ymin
1733
- if (y1 <= y2 && y1 < axisy.min) {
1734
- if (y2 < axisy.min)
1735
- continue; // line segment is outside
1736
- // compute new intersection point
1737
- x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
1738
- y1 = axisy.min;
1739
- }
1740
- else if (y2 <= y1 && y2 < axisy.min) {
1741
- if (y1 < axisy.min)
1742
- continue;
1743
- x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
1744
- y2 = axisy.min;
1745
- }
1746
-
1747
- // clip with ymax
1748
- if (y1 >= y2 && y1 > axisy.max) {
1749
- if (y2 > axisy.max)
1750
- continue;
1751
- x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
1752
- y1 = axisy.max;
1753
- }
1754
- else if (y2 >= y1 && y2 > axisy.max) {
1755
- if (y1 > axisy.max)
1756
- continue;
1757
- x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
1758
- y2 = axisy.max;
1759
- }
1760
-
1761
- // clip with xmin
1762
- if (x1 <= x2 && x1 < axisx.min) {
1763
- if (x2 < axisx.min)
1764
- continue;
1765
- y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
1766
- x1 = axisx.min;
1767
- }
1768
- else if (x2 <= x1 && x2 < axisx.min) {
1769
- if (x1 < axisx.min)
1770
- continue;
1771
- y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
1772
- x2 = axisx.min;
1773
- }
1774
-
1775
- // clip with xmax
1776
- if (x1 >= x2 && x1 > axisx.max) {
1777
- if (x2 > axisx.max)
1778
- continue;
1779
- y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
1780
- x1 = axisx.max;
1781
- }
1782
- else if (x2 >= x1 && x2 > axisx.max) {
1783
- if (x1 > axisx.max)
1784
- continue;
1785
- y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
1786
- x2 = axisx.max;
1787
- }
1788
-
1789
- if (x1 != prevx || y1 != prevy)
1790
- ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset);
1791
-
1792
- prevx = x2;
1793
- prevy = y2;
1794
- ctx.lineTo(axisx.p2c(x2) + xoffset, axisy.p2c(y2) + yoffset);
1795
- }
1796
- ctx.stroke();
1797
- }
1798
-
1799
- function plotLineArea(datapoints, axisx, axisy) {
1800
- var points = datapoints.points,
1801
- ps = datapoints.pointsize,
1802
- bottom = Math.min(Math.max(0, axisy.min), axisy.max),
1803
- i = 0, top, areaOpen = false,
1804
- ypos = 1, segmentStart = 0, segmentEnd = 0;
1805
-
1806
- // we process each segment in two turns, first forward
1807
- // direction to sketch out top, then once we hit the
1808
- // end we go backwards to sketch the bottom
1809
- while (true) {
1810
- if (ps > 0 && i > points.length + ps)
1811
- break;
1812
-
1813
- i += ps; // ps is negative if going backwards
1814
-
1815
- var x1 = points[i - ps],
1816
- y1 = points[i - ps + ypos],
1817
- x2 = points[i], y2 = points[i + ypos];
1818
-
1819
- if (areaOpen) {
1820
- if (ps > 0 && x1 != null && x2 == null) {
1821
- // at turning point
1822
- segmentEnd = i;
1823
- ps = -ps;
1824
- ypos = 2;
1825
- continue;
1826
- }
1827
-
1828
- if (ps < 0 && i == segmentStart + ps) {
1829
- // done with the reverse sweep
1830
- ctx.fill();
1831
- areaOpen = false;
1832
- ps = -ps;
1833
- ypos = 1;
1834
- i = segmentStart = segmentEnd + ps;
1835
- continue;
1836
- }
1837
- }
1838
-
1839
- if (x1 == null || x2 == null)
1840
- continue;
1841
-
1842
- // clip x values
1843
-
1844
- // clip with xmin
1845
- if (x1 <= x2 && x1 < axisx.min) {
1846
- if (x2 < axisx.min)
1847
- continue;
1848
- y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
1849
- x1 = axisx.min;
1850
- }
1851
- else if (x2 <= x1 && x2 < axisx.min) {
1852
- if (x1 < axisx.min)
1853
- continue;
1854
- y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
1855
- x2 = axisx.min;
1856
- }
1857
-
1858
- // clip with xmax
1859
- if (x1 >= x2 && x1 > axisx.max) {
1860
- if (x2 > axisx.max)
1861
- continue;
1862
- y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
1863
- x1 = axisx.max;
1864
- }
1865
- else if (x2 >= x1 && x2 > axisx.max) {
1866
- if (x1 > axisx.max)
1867
- continue;
1868
- y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
1869
- x2 = axisx.max;
1870
- }
1871
-
1872
- if (!areaOpen) {
1873
- // open area
1874
- ctx.beginPath();
1875
- ctx.moveTo(axisx.p2c(x1), axisy.p2c(bottom));
1876
- areaOpen = true;
1877
- }
1878
-
1879
- // now first check the case where both is outside
1880
- if (y1 >= axisy.max && y2 >= axisy.max) {
1881
- ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.max));
1882
- ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.max));
1883
- continue;
1884
- }
1885
- else if (y1 <= axisy.min && y2 <= axisy.min) {
1886
- ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.min));
1887
- ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.min));
1888
- continue;
1889
- }
1890
-
1891
- // else it's a bit more complicated, there might
1892
- // be a flat maxed out rectangle first, then a
1893
- // triangular cutout or reverse; to find these
1894
- // keep track of the current x values
1895
- var x1old = x1, x2old = x2;
1896
-
1897
- // clip the y values, without shortcutting, we
1898
- // go through all cases in turn
1899
-
1900
- // clip with ymin
1901
- if (y1 <= y2 && y1 < axisy.min && y2 >= axisy.min) {
1902
- x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
1903
- y1 = axisy.min;
1904
- }
1905
- else if (y2 <= y1 && y2 < axisy.min && y1 >= axisy.min) {
1906
- x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
1907
- y2 = axisy.min;
1908
- }
1909
-
1910
- // clip with ymax
1911
- if (y1 >= y2 && y1 > axisy.max && y2 <= axisy.max) {
1912
- x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
1913
- y1 = axisy.max;
1914
- }
1915
- else if (y2 >= y1 && y2 > axisy.max && y1 <= axisy.max) {
1916
- x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
1917
- y2 = axisy.max;
1918
- }
1919
-
1920
- // if the x value was changed we got a rectangle
1921
- // to fill
1922
- if (x1 != x1old) {
1923
- ctx.lineTo(axisx.p2c(x1old), axisy.p2c(y1));
1924
- // it goes to (x1, y1), but we fill that below
1925
- }
1926
-
1927
- // fill triangular section, this sometimes result
1928
- // in redundant points if (x1, y1) hasn't changed
1929
- // from previous line to, but we just ignore that
1930
- ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1));
1931
- ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2));
1932
-
1933
- // fill the other rectangle if it's there
1934
- if (x2 != x2old) {
1935
- ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2));
1936
- ctx.lineTo(axisx.p2c(x2old), axisy.p2c(y2));
1937
- }
1938
- }
1939
- }
1940
-
1941
- ctx.save();
1942
- ctx.translate(plotOffset.left, plotOffset.top);
1943
- ctx.lineJoin = "round";
1944
-
1945
- var lw = series.lines.lineWidth,
1946
- sw = series.shadowSize;
1947
- // FIXME: consider another form of shadow when filling is turned on
1948
- if (lw > 0 && sw > 0) {
1949
- // draw shadow as a thick and thin line with transparency
1950
- ctx.lineWidth = sw;
1951
- ctx.strokeStyle = "rgba(0,0,0,0.1)";
1952
- // position shadow at angle from the mid of line
1953
- var angle = Math.PI/18;
1954
- plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/2), Math.cos(angle) * (lw/2 + sw/2), series.xaxis, series.yaxis);
1955
- ctx.lineWidth = sw/2;
1956
- plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/4), Math.cos(angle) * (lw/2 + sw/4), series.xaxis, series.yaxis);
1957
- }
1958
-
1959
- ctx.lineWidth = lw;
1960
- ctx.strokeStyle = series.color;
1961
- var fillStyle = getFillStyle(series.lines, series.color, 0, plotHeight);
1962
- if (fillStyle) {
1963
- ctx.fillStyle = fillStyle;
1964
- plotLineArea(series.datapoints, series.xaxis, series.yaxis);
1965
- }
1966
-
1967
- if (lw > 0)
1968
- plotLine(series.datapoints, 0, 0, series.xaxis, series.yaxis);
1969
- ctx.restore();
1970
- }
1971
-
1972
- function drawSeriesPoints(series) {
1973
- function plotPoints(datapoints, radius, fillStyle, offset, shadow, axisx, axisy, symbol) {
1974
- var points = datapoints.points, ps = datapoints.pointsize;
1975
-
1976
- for (var i = 0; i < points.length; i += ps) {
1977
- var x = points[i], y = points[i + 1];
1978
- if (x == null || x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max)
1979
- continue;
1980
-
1981
- ctx.beginPath();
1982
- x = axisx.p2c(x);
1983
- y = axisy.p2c(y) + offset;
1984
- if (symbol == "circle")
1985
- ctx.arc(x, y, radius, 0, shadow ? Math.PI : Math.PI * 2, false);
1986
- else
1987
- symbol(ctx, x, y, radius, shadow);
1988
- ctx.closePath();
1989
-
1990
- if (fillStyle) {
1991
- ctx.fillStyle = fillStyle;
1992
- ctx.fill();
1993
- }
1994
- ctx.stroke();
1995
- }
1996
- }
1997
-
1998
- ctx.save();
1999
- ctx.translate(plotOffset.left, plotOffset.top);
2000
-
2001
- var lw = series.points.lineWidth,
2002
- sw = series.shadowSize,
2003
- radius = series.points.radius,
2004
- symbol = series.points.symbol;
2005
- if (lw > 0 && sw > 0) {
2006
- // draw shadow in two steps
2007
- var w = sw / 2;
2008
- ctx.lineWidth = w;
2009
- ctx.strokeStyle = "rgba(0,0,0,0.1)";
2010
- plotPoints(series.datapoints, radius, null, w + w/2, true,
2011
- series.xaxis, series.yaxis, symbol);
2012
-
2013
- ctx.strokeStyle = "rgba(0,0,0,0.2)";
2014
- plotPoints(series.datapoints, radius, null, w/2, true,
2015
- series.xaxis, series.yaxis, symbol);
2016
- }
2017
-
2018
- ctx.lineWidth = lw;
2019
- ctx.strokeStyle = series.color;
2020
- plotPoints(series.datapoints, radius,
2021
- getFillStyle(series.points, series.color), 0, false,
2022
- series.xaxis, series.yaxis, symbol);
2023
- ctx.restore();
2024
- }
2025
-
2026
- function drawBar(x, y, b, barLeft, barRight, offset, fillStyleCallback, axisx, axisy, c, horizontal, lineWidth) {
2027
- var left, right, bottom, top,
2028
- drawLeft, drawRight, drawTop, drawBottom,
2029
- tmp;
2030
-
2031
- // in horizontal mode, we start the bar from the left
2032
- // instead of from the bottom so it appears to be
2033
- // horizontal rather than vertical
2034
- if (horizontal) {
2035
- drawBottom = drawRight = drawTop = true;
2036
- drawLeft = false;
2037
- left = b;
2038
- right = x;
2039
- top = y + barLeft;
2040
- bottom = y + barRight;
2041
-
2042
- // account for negative bars
2043
- if (right < left) {
2044
- tmp = right;
2045
- right = left;
2046
- left = tmp;
2047
- drawLeft = true;
2048
- drawRight = false;
2049
- }
2050
- }
2051
- else {
2052
- drawLeft = drawRight = drawTop = true;
2053
- drawBottom = false;
2054
- left = x + barLeft;
2055
- right = x + barRight;
2056
- bottom = b;
2057
- top = y;
2058
-
2059
- // account for negative bars
2060
- if (top < bottom) {
2061
- tmp = top;
2062
- top = bottom;
2063
- bottom = tmp;
2064
- drawBottom = true;
2065
- drawTop = false;
2066
- }
2067
- }
2068
-
2069
- // clip
2070
- if (right < axisx.min || left > axisx.max ||
2071
- top < axisy.min || bottom > axisy.max)
2072
- return;
2073
-
2074
- if (left < axisx.min) {
2075
- left = axisx.min;
2076
- drawLeft = false;
2077
- }
2078
-
2079
- if (right > axisx.max) {
2080
- right = axisx.max;
2081
- drawRight = false;
2082
- }
2083
-
2084
- if (bottom < axisy.min) {
2085
- bottom = axisy.min;
2086
- drawBottom = false;
2087
- }
2088
-
2089
- if (top > axisy.max) {
2090
- top = axisy.max;
2091
- drawTop = false;
2092
- }
2093
-
2094
- left = axisx.p2c(left);
2095
- bottom = axisy.p2c(bottom);
2096
- right = axisx.p2c(right);
2097
- top = axisy.p2c(top);
2098
-
2099
- // fill the bar
2100
- if (fillStyleCallback) {
2101
- c.beginPath();
2102
- c.moveTo(left, bottom);
2103
- c.lineTo(left, top);
2104
- c.lineTo(right, top);
2105
- c.lineTo(right, bottom);
2106
- c.fillStyle = fillStyleCallback(bottom, top);
2107
- c.fill();
2108
- }
2109
-
2110
- // draw outline
2111
- if (lineWidth > 0 && (drawLeft || drawRight || drawTop || drawBottom)) {
2112
- c.beginPath();
2113
-
2114
- // FIXME: inline moveTo is buggy with excanvas
2115
- c.moveTo(left, bottom + offset);
2116
- if (drawLeft)
2117
- c.lineTo(left, top + offset);
2118
- else
2119
- c.moveTo(left, top + offset);
2120
- if (drawTop)
2121
- c.lineTo(right, top + offset);
2122
- else
2123
- c.moveTo(right, top + offset);
2124
- if (drawRight)
2125
- c.lineTo(right, bottom + offset);
2126
- else
2127
- c.moveTo(right, bottom + offset);
2128
- if (drawBottom)
2129
- c.lineTo(left, bottom + offset);
2130
- else
2131
- c.moveTo(left, bottom + offset);
2132
- c.stroke();
2133
- }
2134
- }
2135
-
2136
- function drawSeriesBars(series) {
2137
- function plotBars(datapoints, barLeft, barRight, offset, fillStyleCallback, axisx, axisy) {
2138
- var points = datapoints.points, ps = datapoints.pointsize;
2139
-
2140
- for (var i = 0; i < points.length; i += ps) {
2141
- if (points[i] == null)
2142
- continue;
2143
- drawBar(points[i], points[i + 1], points[i + 2], barLeft, barRight, offset, fillStyleCallback, axisx, axisy, ctx, series.bars.horizontal, series.bars.lineWidth);
2144
- }
2145
- }
2146
-
2147
- ctx.save();
2148
- ctx.translate(plotOffset.left, plotOffset.top);
2149
-
2150
- // FIXME: figure out a way to add shadows (for instance along the right edge)
2151
- ctx.lineWidth = series.bars.lineWidth;
2152
- ctx.strokeStyle = series.color;
2153
- var barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth/2;
2154
- var fillStyleCallback = series.bars.fill ? function (bottom, top) { return getFillStyle(series.bars, series.color, bottom, top); } : null;
2155
- plotBars(series.datapoints, barLeft, barLeft + series.bars.barWidth, 0, fillStyleCallback, series.xaxis, series.yaxis);
2156
- ctx.restore();
2157
- }
2158
-
2159
- function getFillStyle(filloptions, seriesColor, bottom, top) {
2160
- var fill = filloptions.fill;
2161
- if (!fill)
2162
- return null;
2163
-
2164
- if (filloptions.fillColor)
2165
- return getColorOrGradient(filloptions.fillColor, bottom, top, seriesColor);
2166
-
2167
- var c = $.color.parse(seriesColor);
2168
- c.a = typeof fill == "number" ? fill : 0.4;
2169
- c.normalize();
2170
- return c.toString();
2171
- }
2172
-
2173
- function insertLegend() {
2174
- placeholder.find(".legend").remove();
2175
-
2176
- if (!options.legend.show)
2177
- return;
2178
-
2179
- var fragments = [], rowStarted = false,
2180
- lf = options.legend.labelFormatter, s, label;
2181
- for (var i = 0; i < series.length; ++i) {
2182
- s = series[i];
2183
- label = s.label;
2184
- if (!label)
2185
- continue;
2186
-
2187
- if (i % options.legend.noColumns == 0) {
2188
- if (rowStarted)
2189
- fragments.push('</tr>');
2190
- fragments.push('<tr>');
2191
- rowStarted = true;
2192
- }
2193
-
2194
- if (lf)
2195
- label = lf(label, s);
2196
-
2197
- fragments.push(
2198
- '<td class="legendColorBox"><div style="width:4px;height:0;border:5px solid ' + s.color + ';overflow:hidden"></div></td>' +
2199
- '<td class="legendLabel">' + label + '</td>');
2200
- }
2201
- if (rowStarted)
2202
- fragments.push('</tr>');
2203
-
2204
- if (fragments.length == 0)
2205
- return;
2206
-
2207
- var table = $('<table ' + ( options.inline ? 'style="font-size:smaller;color:' + options.grid.color + '"' : '' ) + '>' + fragments.join("") + '</table>');
2208
- if (options.legend.container != null)
2209
- $(options.legend.container).html(table);
2210
- else {
2211
- var pos = {},
2212
- p = options.legend.position,
2213
- m = options.legend.margin;
2214
- if (m[0] == null)
2215
- m = [m, m];
2216
- if (p.charAt(0) == "n")
2217
- pos = $.extend(pos, { top: (m[1] + plotOffset.top) + 'px' });
2218
- else if (p.charAt(0) == "s")
2219
- pos = $.extend(pos, { bottom: (m[1] + plotOffset.bottom) + 'px' });
2220
- if (p.charAt(1) == "e")
2221
- pos = $.extend(pos, { right: (m[0] + plotOffset.right) + 'px' });
2222
- else if (p.charAt(1) == "w")
2223
- pos = $.extend(pos, { left: (m[0] + plotOffset.left) + 'px' });
2224
-
2225
- var legend = $('<div class="legend">').append(table.css( {
2226
- position: 'absolute',
2227
- top: pos.top,
2228
- right: pos.right,
2229
- bottom: pos.bottom,
2230
- left: pos.left
2231
- } ) ).appendTo(placeholder);
2232
-
2233
- if (options.legend.backgroundOpacity != 0.0) {
2234
- // put in the transparent background
2235
- // separately to avoid blended labels and
2236
- // label boxes
2237
- var c = options.legend.backgroundColor;
2238
- if (c == null) {
2239
- c = options.grid.backgroundColor;
2240
- if (c && typeof c == "string")
2241
- c = $.color.parse(c);
2242
- else
2243
- c = $.color.extract(legend, 'background-color');
2244
- c.a = 1;
2245
- c = c.toString();
2246
- }
2247
- var div = legend.children();
2248
- $('<div style="position:absolute;width:' + div.width() + 'px;height:' + div.height() + 'px;' + pos +'background-color:' + c + ';"> </div>').prependTo(legend).css('opacity', options.legend.backgroundOpacity);
2249
- }
2250
- }
2251
- }
2252
-
2253
-
2254
- // interactive features
2255
-
2256
- var highlights = [],
2257
- redrawTimeout = null;
2258
-
2259
- // returns the data item the mouse is over, or null if none is found
2260
- function findNearbyItem(mouseX, mouseY, seriesFilter) {
2261
- var maxDistance = options.grid.mouseActiveRadius,
2262
- smallestDistance = maxDistance * maxDistance + 1,
2263
- item = null, foundPoint = false, i, j;
2264
-
2265
- for (i = series.length - 1; i >= 0; --i) {
2266
- if (!seriesFilter(series[i]))
2267
- continue;
2268
-
2269
- var s = series[i],
2270
- axisx = s.xaxis,
2271
- axisy = s.yaxis,
2272
- points = s.datapoints.points,
2273
- ps = s.datapoints.pointsize,
2274
- mx = axisx.c2p(mouseX), // precompute some stuff to make the loop faster
2275
- my = axisy.c2p(mouseY),
2276
- maxx = maxDistance / axisx.scale,
2277
- maxy = maxDistance / axisy.scale;
2278
-
2279
- // with inverse transforms, we can't use the maxx/maxy
2280
- // optimization, sadly
2281
- if (axisx.options.inverseTransform)
2282
- maxx = Number.MAX_VALUE;
2283
- if (axisy.options.inverseTransform)
2284
- maxy = Number.MAX_VALUE;
2285
-
2286
- if (s.lines.show || s.points.show) {
2287
- for (j = 0; j < points.length; j += ps) {
2288
- var x = points[j], y = points[j + 1];
2289
- if (x == null)
2290
- continue;
2291
-
2292
- // For points and lines, the cursor must be within a
2293
- // certain distance to the data point
2294
- if (x - mx > maxx || x - mx < -maxx ||
2295
- y - my > maxy || y - my < -maxy)
2296
- continue;
2297
-
2298
- // We have to calculate distances in pixels, not in
2299
- // data units, because the scales of the axes may be different
2300
- var dx = Math.abs(axisx.p2c(x) - mouseX),
2301
- dy = Math.abs(axisy.p2c(y) - mouseY),
2302
- dist = dx * dx + dy * dy; // we save the sqrt
2303
-
2304
- // use <= to ensure last point takes precedence
2305
- // (last generally means on top of)
2306
- if (dist < smallestDistance) {
2307
- smallestDistance = dist;
2308
- item = [i, j / ps];
2309
- }
2310
- }
2311
- }
2312
-
2313
- if (s.bars.show && !item) { // no other point can be nearby
2314
- var barLeft = s.bars.align == "left" ? 0 : -s.bars.barWidth/2,
2315
- barRight = barLeft + s.bars.barWidth;
2316
-
2317
- for (j = 0; j < points.length; j += ps) {
2318
- var x = points[j], y = points[j + 1], b = points[j + 2];
2319
- if (x == null)
2320
- continue;
2321
-
2322
- // for a bar graph, the cursor must be inside the bar
2323
- if (series[i].bars.horizontal ?
2324
- (mx <= Math.max(b, x) && mx >= Math.min(b, x) &&
2325
- my >= y + barLeft && my <= y + barRight) :
2326
- (mx >= x + barLeft && mx <= x + barRight &&
2327
- my >= Math.min(b, y) && my <= Math.max(b, y)))
2328
- item = [i, j / ps];
2329
- }
2330
- }
2331
- }
2332
-
2333
- if (item) {
2334
- i = item[0];
2335
- j = item[1];
2336
- ps = series[i].datapoints.pointsize;
2337
-
2338
- return { datapoint: series[i].datapoints.points.slice(j * ps, (j + 1) * ps),
2339
- dataIndex: j,
2340
- series: series[i],
2341
- seriesIndex: i };
2342
- }
2343
-
2344
- return null;
2345
- }
2346
-
2347
- function onMouseMove(e) {
2348
- if (options.grid.hoverable)
2349
- triggerClickHoverEvent("plothover", e,
2350
- function (s) { return s["hoverable"] != false; });
2351
- }
2352
-
2353
- function onMouseLeave(e) {
2354
- if (options.grid.hoverable)
2355
- triggerClickHoverEvent("plothover", e,
2356
- function (s) { return false; });
2357
- }
2358
-
2359
- function onClick(e) {
2360
- triggerClickHoverEvent("plotclick", e,
2361
- function (s) { return s["clickable"] != false; });
2362
- }
2363
-
2364
- // trigger click or hover event (they send the same parameters
2365
- // so we share their code)
2366
- function triggerClickHoverEvent(eventname, event, seriesFilter) {
2367
- var offset = eventHolder.offset(),
2368
- canvasX = event.pageX - offset.left - plotOffset.left,
2369
- canvasY = event.pageY - offset.top - plotOffset.top,
2370
- pos = canvasToAxisCoords({ left: canvasX, top: canvasY });
2371
-
2372
- pos.pageX = event.pageX;
2373
- pos.pageY = event.pageY;
2374
-
2375
- var item = findNearbyItem(canvasX, canvasY, seriesFilter);
2376
-
2377
- if (item) {
2378
- // fill in mouse pos for any listeners out there
2379
- item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint[0]) + offset.left + plotOffset.left);
2380
- item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top + plotOffset.top);
2381
- }
2382
-
2383
- if (options.grid.autoHighlight) {
2384
- // clear auto-highlights
2385
- for (var i = 0; i < highlights.length; ++i) {
2386
- var h = highlights[i];
2387
- if (h.auto == eventname &&
2388
- !(item && h.series == item.series &&
2389
- h.point[0] == item.datapoint[0] &&
2390
- h.point[1] == item.datapoint[1]))
2391
- unhighlight(h.series, h.point);
2392
- }
2393
-
2394
- if (item)
2395
- highlight(item.series, item.datapoint, eventname);
2396
- }
2397
-
2398
- placeholder.trigger(eventname, [ pos, item ]);
2399
- }
2400
-
2401
- function triggerRedrawOverlay() {
2402
- //console.log("Trigger Highlights: " + highlights.length);
2403
-
2404
- if (redrawTimeout) {
2405
- //console.log("Clearing timeout");
2406
- clearTimeout(redrawTimeout)
2407
- }
2408
-
2409
- redrawTimeout = setTimeout(drawOverlay, 30);
2410
- }
2411
-
2412
- function drawOverlay() {
2413
- //console.log("Highlights: " + highlights.length);
2414
- redrawTimeout = null;
2415
-
2416
- // draw highlights
2417
- octx.save();
2418
- octx.clearRect(0, 0, canvasWidth, canvasHeight);
2419
- octx.translate(plotOffset.left, plotOffset.top);
2420
-
2421
- var i, hi;
2422
- for (i = 0; i < highlights.length; ++i) {
2423
- hi = highlights[i];
2424
-
2425
- if (hi.series.bars.show)
2426
- drawBarHighlight(hi.series, hi.point);
2427
- else
2428
- drawPointHighlight(hi.series, hi.point);
2429
- }
2430
- octx.restore();
2431
-
2432
- executeHooks(hooks.drawOverlay, [octx]);
2433
- }
2434
-
2435
- function highlight(s, point, auto) {
2436
- if (typeof s == "number")
2437
- s = series[s];
2438
-
2439
- if (typeof point == "number") {
2440
- var ps = s.datapoints.pointsize;
2441
- point = s.datapoints.points.slice(ps * point, ps * (point + 1));
2442
- }
2443
-
2444
- var i = indexOfHighlight(s, point);
2445
- if (i == -1) {
2446
- highlights.push({ series: s, point: point, auto: auto });
2447
-
2448
- triggerRedrawOverlay();
2449
- }
2450
- else if (!auto)
2451
- highlights[i].auto = false;
2452
- }
2453
-
2454
- function unhighlight(s, point) {
2455
- if (s == null && point == null) {
2456
- highlights = [];
2457
- triggerRedrawOverlay();
2458
- }
2459
-
2460
- if (typeof s == "number")
2461
- s = series[s];
2462
-
2463
- if (typeof point == "number")
2464
- point = s.data[point];
2465
-
2466
- var i = indexOfHighlight(s, point);
2467
- if (i != -1) {
2468
- highlights.splice(i, 1);
2469
-
2470
- triggerRedrawOverlay();
2471
- }
2472
- }
2473
-
2474
- function indexOfHighlight(s, p) {
2475
- for (var i = 0; i < highlights.length; ++i) {
2476
- var h = highlights[i];
2477
- if (h.series == s && h.point[0] == p[0]
2478
- && h.point[1] == p[1])
2479
- return i;
2480
- }
2481
- return -1;
2482
- }
2483
-
2484
- function drawPointHighlight(series, point) {
2485
- var x = point[0], y = point[1],
2486
- axisx = series.xaxis, axisy = series.yaxis;
2487
-
2488
- if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max)
2489
- return;
2490
-
2491
- var pointRadius = series.points.radius + series.points.lineWidth / 2;
2492
- octx.lineWidth = pointRadius;
2493
- octx.strokeStyle = $.color.parse(series.color).scale('a', 0.5).toString();
2494
- var radius = 1.5 * pointRadius,
2495
- x = axisx.p2c(x),
2496
- y = axisy.p2c(y);
2497
-
2498
- octx.beginPath();
2499
- if (series.points.symbol == "circle")
2500
- octx.arc(x, y, radius, 0, 2 * Math.PI, false);
2501
- else
2502
- series.points.symbol(octx, x, y, radius, false);
2503
- octx.closePath();
2504
- octx.stroke();
2505
- }
2506
-
2507
- function drawBarHighlight(series, point) {
2508
- octx.lineWidth = series.bars.lineWidth;
2509
- octx.strokeStyle = $.color.parse(series.color).scale('a', 0.5).toString();
2510
- var fillStyle = $.color.parse(series.color).scale('a', 0.5).toString();
2511
- var barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth/2;
2512
- drawBar(point[0], point[1], point[2] || 0, barLeft, barLeft + series.bars.barWidth,
2513
- 0, function () { return fillStyle; }, series.xaxis, series.yaxis, octx, series.bars.horizontal, series.bars.lineWidth);
2514
- }
2515
-
2516
- function getColorOrGradient(spec, bottom, top, defaultColor) {
2517
- if (typeof spec == "string")
2518
- return spec;
2519
- else {
2520
- // assume this is a gradient spec; IE currently only
2521
- // supports a simple vertical gradient properly, so that's
2522
- // what we support too
2523
- var gradient = ctx.createLinearGradient(0, top, 0, bottom);
2524
-
2525
- for (var i = 0, l = spec.colors.length; i < l; ++i) {
2526
- var c = spec.colors[i];
2527
- if (typeof c != "string") {
2528
- var co = $.color.parse(defaultColor);
2529
- if (c.brightness != null)
2530
- co = co.scale('rgb', c.brightness)
2531
- if (c.opacity != null)
2532
- co.a *= c.opacity;
2533
- c = co.toString();
2534
- }
2535
- gradient.addColorStop(i / (l - 1), c);
2536
- }
2537
-
2538
- return gradient;
2539
- }
2540
- }
2541
- }
2542
-
2543
- $.plot = function(placeholder, data, options) {
2544
- //var t0 = new Date();
2545
- var plot = new Plot($(placeholder), data, options, $.plot.plugins);
2546
- //(window.console ? console.log : alert)("time used (msecs): " + ((new Date()).getTime() - t0.getTime()));
2547
- return plot;
2548
- };
2549
-
2550
- $.plot.version = "0.7";
2551
-
2552
- $.plot.plugins = [];
2553
-
2554
- // returns a string with the date d formatted according to fmt
2555
- $.plot.formatDate = function(d, fmt, monthNames) {
2556
- var leftPad = function(n) {
2557
- n = "" + n;
2558
- return n.length == 1 ? "0" + n : n;
2559
- };
2560
-
2561
- var r = [];
2562
- var escape = false, padNext = false;
2563
- var hours = d.getUTCHours();
2564
- var isAM = hours < 12;
2565
- if (monthNames == null)
2566
- monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
2567
-
2568
- if (fmt.search(/%p|%P/) != -1) {
2569
- if (hours > 12) {
2570
- hours = hours - 12;
2571
- } else if (hours == 0) {
2572
- hours = 12;
2573
- }
2574
- }
2575
- for (var i = 0; i < fmt.length; ++i) {
2576
- var c = fmt.charAt(i);
2577
-
2578
- if (escape) {
2579
- switch (c) {
2580
- case 'h': c = "" + hours; break;
2581
- case 'H': c = leftPad(hours); break;
2582
- case 'M': c = leftPad(d.getUTCMinutes()); break;
2583
- case 'S': c = leftPad(d.getUTCSeconds()); break;
2584
- case 'd': c = "" + d.getUTCDate(); break;
2585
- case 'm': c = "" + (d.getUTCMonth() + 1); break;
2586
- case 'y': c = "" + d.getUTCFullYear(); break;
2587
- case 'b': c = "" + monthNames[d.getUTCMonth()]; break;
2588
- case 'p': c = (isAM) ? ("" + "am") : ("" + "pm"); break;
2589
- case 'P': c = (isAM) ? ("" + "AM") : ("" + "PM"); break;
2590
- case '0': c = ""; padNext = true; break;
2591
- }
2592
- if (c && padNext) {
2593
- c = leftPad(c);
2594
- padNext = false;
2595
- }
2596
- r.push(c);
2597
- if (!padNext)
2598
- escape = false;
2599
- }
2600
- else {
2601
- if (c == "%")
2602
- escape = true;
2603
- else
2604
- r.push(c);
2605
- }
2606
- }
2607
- return r.join("");
2608
- };
2609
-
2610
- // round to nearby lower multiple of base
2611
- function floorInBase(n, base) {
2612
- return base * Math.floor(n / base);
2613
- }
2614
-
2615
- })(jQuery);