mountain-goat 1.0.3 → 1.0.4

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.
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);