rhosync 2.0.9 → 2.1.0.beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. data/CHANGELOG +9 -0
  2. data/Rakefile +1 -1
  3. data/bench/benchapp/sources/mock_adapter.rb +2 -2
  4. data/bench/benchapp/tmp/restart.txt +0 -0
  5. data/bench/spec/mock_adapter_spec.rb +1 -1
  6. data/lib/rhosync/api/stats.rb +21 -0
  7. data/lib/rhosync/bulk_data/bulk_data.rb +1 -1
  8. data/lib/rhosync/client.rb +10 -0
  9. data/lib/rhosync/console/app/public/home.css +9 -4
  10. data/lib/rhosync/console/app/public/images/foot_logo_rhosync.png +0 -0
  11. data/lib/rhosync/console/app/public/images/landing_header.jpg +0 -0
  12. data/lib/rhosync/console/app/public/jqplot/excanvas.min.js +35 -0
  13. data/lib/rhosync/console/app/public/jqplot/jqplot.barRenderer.min.js +34 -0
  14. data/lib/rhosync/console/app/public/jqplot/jqplot.canvasAxisLabelRenderer.js +187 -0
  15. data/lib/rhosync/console/app/public/jqplot/jqplot.canvasAxisTickRenderer.js +226 -0
  16. data/lib/rhosync/console/app/public/jqplot/jqplot.canvasTextRenderer.js +408 -0
  17. data/lib/rhosync/console/app/public/jqplot/jqplot.categoryAxisRenderer.min.js +34 -0
  18. data/lib/rhosync/console/app/public/jqplot/jqplot.cursor.js +952 -0
  19. data/lib/rhosync/console/app/public/jqplot/jqplot.dateAxisRenderer.js +313 -0
  20. data/lib/rhosync/console/app/public/jqplot/jqplot.dateAxisRenderer.min.js +34 -0
  21. data/lib/rhosync/console/app/public/jqplot/jqplot.pointLabels.min.js +34 -0
  22. data/lib/rhosync/console/app/public/jqplot/jquery-1.4.2.min.js +154 -0
  23. data/lib/rhosync/console/app/public/jqplot/jquery.jqplot.min.css +1 -0
  24. data/lib/rhosync/console/app/public/jqplot/jquery.jqplot.min.js +34 -0
  25. data/lib/rhosync/console/app/routes/timing.rb +223 -7
  26. data/lib/rhosync/console/app/views/content.erb +1 -1
  27. data/lib/rhosync/console/app/views/headermenu.erb +4 -4
  28. data/lib/rhosync/console/app/views/jqplot.erb +52 -0
  29. data/lib/rhosync/console/app/views/layout.erb +86 -11
  30. data/lib/rhosync/console/rhosync_api.rb +19 -0
  31. data/lib/rhosync/jobs/bulk_data_job.rb +2 -2
  32. data/lib/rhosync/stats/middleware.rb +4 -6
  33. data/lib/rhosync/stats/record.rb +51 -26
  34. data/lib/rhosync/store.rb +12 -1
  35. data/lib/rhosync/tasks.rb +4 -2
  36. data/lib/rhosync/user.rb +11 -1
  37. data/lib/rhosync/version.rb +2 -2
  38. data/lib/rhosync.rb +3 -3
  39. data/spec/api/rhosync_api_spec.rb +26 -0
  40. data/spec/api/stats_spec.rb +66 -0
  41. data/spec/client_spec.rb +40 -0
  42. data/spec/client_sync_spec.rb +14 -0
  43. data/spec/stats/middleware_spec.rb +11 -5
  44. data/spec/stats/record_spec.rb +30 -11
  45. data/spec/store_spec.rb +15 -1
  46. data/spec/user_spec.rb +44 -0
  47. metadata +32 -14
  48. data/doc/protocol.html +0 -2291
  49. data/doc/public/css/print.css +0 -29
  50. data/doc/public/css/screen.css +0 -257
  51. data/doc/public/css/style.css +0 -20
  52. data/lib/rhosync/console/app/public/images/header_halo copy.jpg +0 -0
@@ -0,0 +1,952 @@
1
+ /**
2
+ * Copyright (c) 2009 - 2010 Chris Leonello
3
+ * jqPlot is currently available for use in all personal or commercial projects
4
+ * under both the MIT and GPL version 2.0 licenses. This means that you can
5
+ * choose the license that best suits your project and use it accordingly.
6
+ *
7
+ * The author would appreciate an email letting him know of any substantial
8
+ * use of jqPlot. You can reach the author at: chris at jqplot dot com
9
+ * or see http://www.jqplot.com/info.php . This is, of course,
10
+ * not required.
11
+ *
12
+ * If you are feeling kind and generous, consider supporting the project by
13
+ * making a donation at: http://www.jqplot.com/donate.php .
14
+ *
15
+ * Thanks for using jqPlot!
16
+ *
17
+ */
18
+ (function($) {
19
+
20
+ /**
21
+ * Class: $.jqplot.Cursor
22
+ * Plugin class representing the cursor as displayed on the plot.
23
+ */
24
+ $.jqplot.Cursor = function(options) {
25
+ // Group: Properties
26
+ //
27
+ // prop: style
28
+ // CSS spec for cursor style
29
+ this.style = 'crosshair';
30
+ this.previousCursor = 'auto';
31
+ // prop: show
32
+ // wether to show the cursor or not.
33
+ this.show = $.jqplot.config.enablePlugins;
34
+ // prop: showTooltip
35
+ // show a cursor position tooltip. Location of the tooltip
36
+ // will be controlled by followMouse and tooltipLocation.
37
+ this.showTooltip = true;
38
+ // prop: followMouse
39
+ // Tooltip follows the mouse, it is not at a fixed location.
40
+ // Tooltip will show on the grid at the location given by
41
+ // tooltipLocation, offset from the grid edge by tooltipOffset.
42
+ this.followMouse = false;
43
+ // prop: tooltipLocation
44
+ // Where to position tooltip. If followMouse is true, this is
45
+ // relative to the cursor, otherwise, it is relative to the grid.
46
+ // One of 'n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw'
47
+ this.tooltipLocation = 'se';
48
+ // prop: tooltipOffset
49
+ // Pixel offset of tooltip from the grid boudaries or cursor center.
50
+ this.tooltipOffset = 6;
51
+ // prop: showTooltipGridPosition
52
+ // show the grid pixel coordinates of the mouse.
53
+ this.showTooltipGridPosition = false;
54
+ // prop: showTooltipUnitPosition
55
+ // show the unit (data) coordinates of the mouse.
56
+ this.showTooltipUnitPosition = true;
57
+ // prop: showTooltipDataPosition
58
+ // Used with showVerticalLine to show intersecting data points in the tooltip.
59
+ this.showTooltipDataPosition = false;
60
+ // prop: tooltipFormatString
61
+ // sprintf format string for the tooltip.
62
+ // Uses Ash Searle's javascript sprintf implementation
63
+ // found here: http://hexmen.com/blog/2007/03/printf-sprintf/
64
+ // See http://perldoc.perl.org/functions/sprintf.html for reference
65
+ // Note, if showTooltipDataPosition is true, the default tooltipFormatString
66
+ // will be set to the cursorLegendFormatString, not the default given here.
67
+ this.tooltipFormatString = '%.4P, %.4P';
68
+ // prop: useAxesFormatters
69
+ // Use the x and y axes formatters to format the text in the tooltip.
70
+ this.useAxesFormatters = true;
71
+ // prop: tooltipAxisGroups
72
+ // Show position for the specified axes.
73
+ // This is an array like [['xaxis', 'yaxis'], ['xaxis', 'y2axis']]
74
+ // Default is to compute automatically for all visible axes.
75
+ this.tooltipAxisGroups = [];
76
+ // prop: zoom
77
+ // Enable plot zooming.
78
+ this.zoom = false;
79
+ // zoomProxy and zoomTarget properties are not directly set by user.
80
+ // They Will be set through call to zoomProxy method.
81
+ this.zoomProxy = false;
82
+ this.zoomTarget = false;
83
+ // prop: clickReset
84
+ // Will reset plot zoom if single click on plot without drag.
85
+ this.clickReset = false;
86
+ // prop: dblClickReset
87
+ // Will reset plot zoom if double click on plot without drag.
88
+ this.dblClickReset = true;
89
+ // prop: showVerticalLine
90
+ // draw a vertical line across the plot which follows the cursor.
91
+ // When the line is near a data point, a special legend and/or tooltip can
92
+ // be updated with the data values.
93
+ this.showVerticalLine = false;
94
+ // prop: showHorizontalLine
95
+ // draw a horizontal line across the plot which follows the cursor.
96
+ this.showHorizontalLine = false;
97
+ // prop: constrainZoomTo
98
+ // 'none', 'x' or 'y'
99
+ this.constrainZoomTo = 'none';
100
+ // // prop: autoscaleConstraint
101
+ // // when a constrained axis is specified, true will
102
+ // // auatoscale the adjacent axis.
103
+ // this.autoscaleConstraint = true;
104
+ this.shapeRenderer = new $.jqplot.ShapeRenderer();
105
+ this._zoom = {start:[], end:[], started: false, zooming:false, isZoomed:false, axes:{start:{}, end:{}}, gridpos:{}, datapos:{}};
106
+ this._tooltipElem;
107
+ this.zoomCanvas;
108
+ this.cursorCanvas;
109
+ // prop: intersectionThreshold
110
+ // pixel distance from data point or marker to consider cursor lines intersecting with point.
111
+ // If data point markers are not shown, this should be >= 1 or will often miss point intersections.
112
+ this.intersectionThreshold = 2;
113
+ // prop: showCursorLegend
114
+ // Replace the plot legend with an enhanced legend displaying intersection information.
115
+ this.showCursorLegend = false;
116
+ // prop: cursorLegendFormatString
117
+ // Format string used in the cursor legend. If showTooltipDataPosition is true,
118
+ // this will also be the default format string used by tooltipFormatString.
119
+ this.cursorLegendFormatString = $.jqplot.Cursor.cursorLegendFormatString;
120
+ // whether the cursor is over the grid or not.
121
+ this._oldHandlers = {onselectstart: null, ondrag: null, onmousedown: null};
122
+ // prop: constrainOutsideZoom
123
+ // True to limit actual zoom area to edges of grid, even when zooming
124
+ // outside of plot area. That is, can't zoom out by mousing outside plot.
125
+ this.constrainOutsideZoom = true;
126
+ // prop: showTooltipOutsideZoom
127
+ // True will keep updating the tooltip when zooming of the grid.
128
+ this.showTooltipOutsideZoom = false;
129
+ // true if mouse is over grid, false if not.
130
+ this.onGrid = false;
131
+ $.extend(true, this, options);
132
+ };
133
+
134
+ $.jqplot.Cursor.cursorLegendFormatString = '%s x:%s, y:%s';
135
+
136
+ // called with scope of plot
137
+ $.jqplot.Cursor.init = function (target, data, opts){
138
+ // add a cursor attribute to the plot
139
+ var options = opts || {};
140
+ this.plugins.cursor = new $.jqplot.Cursor(options.cursor);
141
+ var c = this.plugins.cursor;
142
+
143
+ if (c.show) {
144
+ $.jqplot.eventListenerHooks.push(['jqplotMouseEnter', handleMouseEnter]);
145
+ $.jqplot.eventListenerHooks.push(['jqplotMouseLeave', handleMouseLeave]);
146
+ $.jqplot.eventListenerHooks.push(['jqplotMouseMove', handleMouseMove]);
147
+
148
+ if (c.showCursorLegend) {
149
+ opts.legend = opts.legend || {};
150
+ opts.legend.renderer = $.jqplot.CursorLegendRenderer;
151
+ opts.legend.formatString = this.plugins.cursor.cursorLegendFormatString;
152
+ opts.legend.show = true;
153
+ }
154
+
155
+ if (c.zoom) {
156
+ $.jqplot.eventListenerHooks.push(['jqplotMouseDown', handleMouseDown]);
157
+
158
+ if (c.clickReset) {
159
+ $.jqplot.eventListenerHooks.push(['jqplotClick', handleClick]);
160
+ }
161
+
162
+ if (c.dblClickReset) {
163
+ $.jqplot.eventListenerHooks.push(['jqplotDblClick', handleDblClick]);
164
+ }
165
+ }
166
+
167
+ this.resetZoom = function() {
168
+ var axes = this.axes;
169
+ if (!c.zoomProxy) {
170
+ for (var ax in axes) {
171
+ axes[ax].reset();
172
+ }
173
+ this.redraw();
174
+ }
175
+ else {
176
+ var ctx = this.plugins.cursor.zoomCanvas._ctx;
177
+ ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
178
+ }
179
+ this.plugins.cursor._zoom.isZoomed = false;
180
+ this.target.trigger('jqplotResetZoom', [this, this.plugins.cursor]);
181
+ };
182
+
183
+
184
+ if (c.showTooltipDataPosition) {
185
+ c.showTooltipUnitPosition = false;
186
+ c.showTooltipGridPosition = false;
187
+ if (options.cursor.tooltipFormatString == undefined) {
188
+ c.tooltipFormatString = $.jqplot.Cursor.cursorLegendFormatString;
189
+ }
190
+ }
191
+ }
192
+ };
193
+
194
+ // called with context of plot
195
+ $.jqplot.Cursor.postDraw = function() {
196
+ var c = this.plugins.cursor;
197
+ // if (c.zoom) {
198
+ c.zoomCanvas = new $.jqplot.GenericCanvas();
199
+ this.eventCanvas._elem.before(c.zoomCanvas.createElement(this._gridPadding, 'jqplot-zoom-canvas', this._plotDimensions));
200
+ var zctx = c.zoomCanvas.setContext();
201
+ // }
202
+ c._tooltipElem = $('<div class="jqplot-cursor-tooltip" style="position:absolute;display:none"></div>');
203
+ c.zoomCanvas._elem.before(c._tooltipElem);
204
+ if (c.showVerticalLine || c.showHorizontalLine) {
205
+ c.cursorCanvas = new $.jqplot.GenericCanvas();
206
+ this.eventCanvas._elem.before(c.cursorCanvas.createElement(this._gridPadding, 'jqplot-cursor-canvas', this._plotDimensions));
207
+ var zctx = c.cursorCanvas.setContext();
208
+ }
209
+
210
+ // if we are showing the positions in unit coordinates, and no axes groups
211
+ // were specified, create a default set.
212
+ if (c.showTooltipUnitPosition){
213
+ if (c.tooltipAxisGroups.length === 0) {
214
+ var series = this.series;
215
+ var s;
216
+ var temp = [];
217
+ for (var i=0; i<series.length; i++) {
218
+ s = series[i];
219
+ var ax = s.xaxis+','+s.yaxis;
220
+ if ($.inArray(ax, temp) == -1) {
221
+ temp.push(ax);
222
+ }
223
+ }
224
+ for (var i=0; i<temp.length; i++) {
225
+ c.tooltipAxisGroups.push(temp[i].split(','));
226
+ }
227
+ }
228
+ }
229
+ };
230
+
231
+ // Group: methods
232
+ //
233
+ // method: $.jqplot.Cursor.zoomProxy
234
+ // links targetPlot to controllerPlot so that plot zooming of
235
+ // targetPlot will be controlled by zooming on the controllerPlot.
236
+ // controllerPlot will not actually zoom, but acts as an
237
+ // overview plot. Note, the zoom options must be set to true for
238
+ // zoomProxy to work.
239
+ $.jqplot.Cursor.zoomProxy = function(targetPlot, controllerPlot) {
240
+ var tc = targetPlot.plugins.cursor;
241
+ var cc = controllerPlot.plugins.cursor;
242
+ tc.zoomTarget = true;
243
+ tc.zoom = true;
244
+ tc.style = 'auto';
245
+ tc.dblClickReset = false;
246
+ cc.zoom = true;
247
+ cc.zoomProxy = true;
248
+
249
+ controllerPlot.target.bind('jqplotZoom', plotZoom);
250
+ controllerPlot.target.bind('jqplotResetZoom', plotReset);
251
+
252
+ function plotZoom(ev, gridpos, datapos, plot, cursor) {
253
+ tc.doZoom(gridpos, datapos, targetPlot, cursor);
254
+ }
255
+
256
+ function plotReset(ev, plot, cursor) {
257
+ targetPlot.resetZoom();
258
+ }
259
+ };
260
+
261
+ $.jqplot.Cursor.prototype.resetZoom = function(plot, cursor) {
262
+ var axes = plot.axes;
263
+ var cax = cursor._zoom.axes;
264
+ if (!plot.plugins.cursor.zoomProxy && cursor._zoom.isZoomed) {
265
+ for (var ax in axes) {
266
+ axes[ax]._ticks = [];
267
+ axes[ax].min = cax[ax].min;
268
+ axes[ax].max = cax[ax].max;
269
+ axes[ax].numberTicks = cax[ax].numberTicks;
270
+ axes[ax].tickInterval = cax[ax].tickInterval;
271
+ // for date axes
272
+ axes[ax].daTickInterval = cax[ax].daTickInterval;
273
+ }
274
+ plot.redraw();
275
+ cursor._zoom.isZoomed = false;
276
+ }
277
+ else {
278
+ var ctx = cursor.zoomCanvas._ctx;
279
+ ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
280
+ }
281
+ plot.target.trigger('jqplotResetZoom', [plot, cursor]);
282
+ };
283
+
284
+ $.jqplot.Cursor.resetZoom = function(plot) {
285
+ plot.resetZoom();
286
+ };
287
+
288
+ $.jqplot.Cursor.prototype.doZoom = function (gridpos, datapos, plot, cursor) {
289
+ var c = cursor;
290
+ var axes = plot.axes;
291
+ var zaxes = c._zoom.axes;
292
+ var start = zaxes.start;
293
+ var end = zaxes.end;
294
+ var min, max;
295
+ var ctx = plot.plugins.cursor.zoomCanvas._ctx;
296
+ // don't zoom is zoom area is too small (in pixels)
297
+ if ((c.constrainZoomTo == 'none' && Math.abs(gridpos.x - c._zoom.start[0]) > 6 && Math.abs(gridpos.y - c._zoom.start[1]) > 6) || (c.constrainZoomTo == 'x' && Math.abs(gridpos.x - c._zoom.start[0]) > 6) || (c.constrainZoomTo == 'y' && Math.abs(gridpos.y - c._zoom.start[1]) > 6)) {
298
+ if (!plot.plugins.cursor.zoomProxy) {
299
+ for (var ax in datapos) {
300
+ // make a copy of the original axes to revert back.
301
+ if (c._zoom.axes[ax] == undefined) {
302
+ c._zoom.axes[ax] = {};
303
+ c._zoom.axes[ax].numberTicks = axes[ax].numberTicks;
304
+ c._zoom.axes[ax].tickInterval = axes[ax].tickInterval;
305
+ // for date axes...
306
+ c._zoom.axes[ax].daTickInterval = axes[ax].daTickInterval;
307
+ c._zoom.axes[ax].min = axes[ax].min;
308
+ c._zoom.axes[ax].max = axes[ax].max;
309
+ }
310
+ if ((c.constrainZoomTo == 'none') || (c.constrainZoomTo == 'x' && ax.charAt(0) == 'x') || (c.constrainZoomTo == 'y' && ax.charAt(0) == 'y')) {
311
+ dp = datapos[ax];
312
+ if (dp != null) {
313
+ if (dp > start[ax]) {
314
+ axes[ax].min = start[ax];
315
+ axes[ax].max = dp;
316
+ }
317
+ else {
318
+ span = start[ax] - dp;
319
+ axes[ax].max = start[ax];
320
+ axes[ax].min = dp;
321
+ }
322
+ axes[ax].tickInterval = null;
323
+ // for date axes...
324
+ axes[ax].daTickInterval = null;
325
+ axes[ax]._ticks = [];
326
+ }
327
+ }
328
+
329
+ // if ((c.constrainZoomTo == 'x' && ax.charAt(0) == 'y' && c.autoscaleConstraint) || (c.constrainZoomTo == 'y' && ax.charAt(0) == 'x' && c.autoscaleConstraint)) {
330
+ // dp = datapos[ax];
331
+ // if (dp != null) {
332
+ // axes[ax].max == null;
333
+ // axes[ax].min = null;
334
+ // }
335
+ // }
336
+ }
337
+ ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
338
+ plot.redraw();
339
+ c._zoom.isZoomed = true;
340
+ }
341
+ plot.target.trigger('jqplotZoom', [gridpos, datapos, plot, cursor]);
342
+ }
343
+ };
344
+
345
+ $.jqplot.preInitHooks.push($.jqplot.Cursor.init);
346
+ $.jqplot.postDrawHooks.push($.jqplot.Cursor.postDraw);
347
+
348
+ function updateTooltip(gridpos, datapos, plot) {
349
+ var c = plot.plugins.cursor;
350
+ var s = '';
351
+ var addbr = false;
352
+ if (c.showTooltipGridPosition) {
353
+ s = gridpos.x+', '+gridpos.y;
354
+ addbr = true;
355
+ }
356
+ if (c.showTooltipUnitPosition) {
357
+ var g;
358
+ for (var i=0; i<c.tooltipAxisGroups.length; i++) {
359
+ g = c.tooltipAxisGroups[i];
360
+ if (addbr) {
361
+ s += '<br />';
362
+ }
363
+ if (c.useAxesFormatters) {
364
+ var xf = plot.axes[g[0]]._ticks[0].formatter;
365
+ var yf = plot.axes[g[1]]._ticks[0].formatter;
366
+ var xfstr = plot.axes[g[0]]._ticks[0].formatString;
367
+ var yfstr = plot.axes[g[1]]._ticks[0].formatString;
368
+ s += xf(xfstr, datapos[g[0]]) + ', '+ yf(yfstr, datapos[g[1]]);
369
+ }
370
+ else {
371
+ s += $.jqplot.sprintf(c.tooltipFormatString, datapos[g[0]], datapos[g[1]]);
372
+ }
373
+ addbr = true;
374
+ }
375
+ }
376
+
377
+ if (c.showTooltipDataPosition) {
378
+ var series = plot.series;
379
+ var ret = getIntersectingPoints(plot, gridpos.x, gridpos.y);
380
+ var addbr = false;
381
+
382
+ for (var i = 0; i< series.length; i++) {
383
+ if (series[i].show) {
384
+ var idx = series[i].index;
385
+ var label = series[i].label.toString();
386
+ var cellid = $.inArray(idx, ret.indices);
387
+ var sx = undefined;
388
+ var sy = undefined;
389
+ if (cellid != -1) {
390
+ var data = ret.data[cellid].data;
391
+ if (c.useAxesFormatters) {
392
+ var xf = series[i]._xaxis._ticks[0].formatter;
393
+ var yf = series[i]._yaxis._ticks[0].formatter;
394
+ var xfstr = series[i]._xaxis._ticks[0].formatString;
395
+ var yfstr = series[i]._yaxis._ticks[0].formatString;
396
+ sx = xf(xfstr, data[0]);
397
+ sy = yf(yfstr, data[1]);
398
+ }
399
+ else {
400
+ sx = data[0];
401
+ sy = data[1];
402
+ }
403
+ if (addbr) {
404
+ s += '<br />';
405
+ }
406
+ s += $.jqplot.sprintf(c.tooltipFormatString, label, sx, sy);
407
+ addbr = true;
408
+ }
409
+ }
410
+ }
411
+
412
+ }
413
+ c._tooltipElem.html(s);
414
+ }
415
+
416
+ function moveLine(gridpos, plot) {
417
+ var c = plot.plugins.cursor;
418
+ var ctx = c.cursorCanvas._ctx;
419
+ ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
420
+ if (c.showVerticalLine) {
421
+ c.shapeRenderer.draw(ctx, [[gridpos.x, 0], [gridpos.x, ctx.canvas.height]]);
422
+ }
423
+ if (c.showHorizontalLine) {
424
+ c.shapeRenderer.draw(ctx, [[0, gridpos.y], [ctx.canvas.width, gridpos.y]]);
425
+ }
426
+ var ret = getIntersectingPoints(plot, gridpos.x, gridpos.y);
427
+ if (c.showCursorLegend) {
428
+ var cells = $(plot.targetId + ' td.jqplot-cursor-legend-label');
429
+ for (var i=0; i<cells.length; i++) {
430
+ var idx = $(cells[i]).data('seriesIndex');
431
+ var series = plot.series[idx];
432
+ var label = series.label.toString();
433
+ var cellid = $.inArray(idx, ret.indices);
434
+ var sx = undefined;
435
+ var sy = undefined;
436
+ if (cellid != -1) {
437
+ var data = ret.data[cellid].data;
438
+ if (c.useAxesFormatters) {
439
+ var xf = series._xaxis._ticks[0].formatter;
440
+ var yf = series._yaxis._ticks[0].formatter;
441
+ var xfstr = series._xaxis._ticks[0].formatString;
442
+ var yfstr = series._yaxis._ticks[0].formatString;
443
+ sx = xf(xfstr, data[0]);
444
+ sy = yf(yfstr, data[1]);
445
+ }
446
+ else {
447
+ sx = data[0];
448
+ sy = data[1];
449
+ }
450
+ }
451
+ if (plot.legend.escapeHtml) {
452
+ $(cells[i]).text($.jqplot.sprintf(c.cursorLegendFormatString, label, sx, sy));
453
+ }
454
+ else {
455
+ $(cells[i]).html($.jqplot.sprintf(c.cursorLegendFormatString, label, sx, sy));
456
+ }
457
+ }
458
+ }
459
+ }
460
+
461
+ function getIntersectingPoints(plot, x, y) {
462
+ var ret = {indices:[], data:[]};
463
+ var s, i, d0, d, j, r;
464
+ var threshold;
465
+ var c = plot.plugins.cursor;
466
+ for (var i=0; i<plot.series.length; i++) {
467
+ s = plot.series[i];
468
+ r = s.renderer;
469
+ if (s.show) {
470
+ threshold = c.intersectionThreshold;
471
+ if (s.showMarker) {
472
+ threshold += s.markerRenderer.size/2;
473
+ }
474
+ for (var j=0; j<s.gridData.length; j++) {
475
+ p = s.gridData[j];
476
+ // check vertical line
477
+ if (c.showVerticalLine) {
478
+ if (Math.abs(x-p[0]) <= threshold) {
479
+ ret.indices.push(i);
480
+ ret.data.push({seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]});
481
+ }
482
+ }
483
+ }
484
+ }
485
+ }
486
+ return ret;
487
+ }
488
+
489
+ function moveTooltip(gridpos, plot) {
490
+ var c = plot.plugins.cursor;
491
+ var elem = c._tooltipElem;
492
+ switch (c.tooltipLocation) {
493
+ case 'nw':
494
+ var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true) - c.tooltipOffset;
495
+ var y = gridpos.y + plot._gridPadding.top - c.tooltipOffset - elem.outerHeight(true);
496
+ break;
497
+ case 'n':
498
+ var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true)/2;
499
+ var y = gridpos.y + plot._gridPadding.top - c.tooltipOffset - elem.outerHeight(true);
500
+ break;
501
+ case 'ne':
502
+ var x = gridpos.x + plot._gridPadding.left + c.tooltipOffset;
503
+ var y = gridpos.y + plot._gridPadding.top - c.tooltipOffset - elem.outerHeight(true);
504
+ break;
505
+ case 'e':
506
+ var x = gridpos.x + plot._gridPadding.left + c.tooltipOffset;
507
+ var y = gridpos.y + plot._gridPadding.top - elem.outerHeight(true)/2;
508
+ break;
509
+ case 'se':
510
+ var x = gridpos.x + plot._gridPadding.left + c.tooltipOffset;
511
+ var y = gridpos.y + plot._gridPadding.top + c.tooltipOffset;
512
+ break;
513
+ case 's':
514
+ var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true)/2;
515
+ var y = gridpos.y + plot._gridPadding.top + c.tooltipOffset;
516
+ break;
517
+ case 'sw':
518
+ var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true) - c.tooltipOffset;
519
+ var y = gridpos.y + plot._gridPadding.top + c.tooltipOffset;
520
+ break;
521
+ case 'w':
522
+ var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true) - c.tooltipOffset;
523
+ var y = gridpos.y + plot._gridPadding.top - elem.outerHeight(true)/2;
524
+ break;
525
+ default:
526
+ var x = gridpos.x + plot._gridPadding.left + c.tooltipOffset;
527
+ var y = gridpos.y + plot._gridPadding.top + c.tooltipOffset;
528
+ break;
529
+ }
530
+
531
+ c._tooltipElem.css('left', x);
532
+ c._tooltipElem.css('top', y);
533
+ }
534
+
535
+ function positionTooltip(plot) {
536
+ // fake a grid for positioning
537
+ var grid = plot._gridPadding;
538
+ var c = plot.plugins.cursor;
539
+ var elem = c._tooltipElem;
540
+ switch (c.tooltipLocation) {
541
+ case 'nw':
542
+ var a = grid.left + c.tooltipOffset;
543
+ var b = grid.top + c.tooltipOffset;
544
+ elem.css('left', a);
545
+ elem.css('top', b);
546
+ break;
547
+ case 'n':
548
+ var a = (grid.left + (plot._plotDimensions.width - grid.right))/2 - elem.outerWidth(true)/2;
549
+ var b = grid.top + c.tooltipOffset;
550
+ elem.css('left', a);
551
+ elem.css('top', b);
552
+ break;
553
+ case 'ne':
554
+ var a = grid.right + c.tooltipOffset;
555
+ var b = grid.top + c.tooltipOffset;
556
+ elem.css({right:a, top:b});
557
+ break;
558
+ case 'e':
559
+ var a = grid.right + c.tooltipOffset;
560
+ var b = (grid.top + (plot._plotDimensions.height - grid.bottom))/2 - elem.outerHeight(true)/2;
561
+ elem.css({right:a, top:b});
562
+ break;
563
+ case 'se':
564
+ var a = grid.right + c.tooltipOffset;
565
+ var b = grid.bottom + c.tooltipOffset;
566
+ elem.css({right:a, bottom:b});
567
+ break;
568
+ case 's':
569
+ var a = (grid.left + (plot._plotDimensions.width - grid.right))/2 - elem.outerWidth(true)/2;
570
+ var b = grid.bottom + c.tooltipOffset;
571
+ elem.css({left:a, bottom:b});
572
+ break;
573
+ case 'sw':
574
+ var a = grid.left + c.tooltipOffset;
575
+ var b = grid.bottom + c.tooltipOffset;
576
+ elem.css({left:a, bottom:b});
577
+ break;
578
+ case 'w':
579
+ var a = grid.left + c.tooltipOffset;
580
+ var b = (grid.top + (plot._plotDimensions.height - grid.bottom))/2 - elem.outerHeight(true)/2;
581
+ elem.css({left:a, top:b});
582
+ break;
583
+ default: // same as 'se'
584
+ var a = grid.right - c.tooltipOffset;
585
+ var b = grid.bottom + c.tooltipOffset;
586
+ elem.css({right:a, bottom:b});
587
+ break;
588
+ }
589
+ }
590
+
591
+ function handleClick (ev, gridpos, datapos, neighbor, plot) {
592
+ ev.preventDefault();
593
+ ev.stopImmediatePropagation();
594
+ var c = plot.plugins.cursor;
595
+ if (c.clickReset) {
596
+ c.resetZoom(plot, c);
597
+ }
598
+ var sel = window.getSelection;
599
+ if (document.selection && document.selection.empty)
600
+ {
601
+ document.selection.empty();
602
+ }
603
+ else if (sel && !sel().isCollapsed) {
604
+ sel().collapse();
605
+ }
606
+ return false;
607
+ }
608
+
609
+ function handleDblClick (ev, gridpos, datapos, neighbor, plot) {
610
+ ev.preventDefault();
611
+ ev.stopImmediatePropagation();
612
+ var c = plot.plugins.cursor;
613
+ if (c.dblClickReset) {
614
+ c.resetZoom(plot, c);
615
+ }
616
+ var sel = window.getSelection;
617
+ if (document.selection && document.selection.empty)
618
+ {
619
+ document.selection.empty();
620
+ }
621
+ else if (sel && !sel().isCollapsed) {
622
+ sel().collapse();
623
+ }
624
+ return false;
625
+ }
626
+
627
+ function handleMouseLeave(ev, gridpos, datapos, neighbor, plot) {
628
+ var c = plot.plugins.cursor;
629
+ c.onGrid = false;
630
+ if (c.show) {
631
+ $(ev.target).css('cursor', c.previousCursor);
632
+ if (c.showTooltip && !(c._zoom.zooming && c.showTooltipOutsideZoom && !c.constrainOutsideZoom)) {
633
+ c._tooltipElem.hide();
634
+ }
635
+ if (c.zoom) {
636
+ c._zoom.gridpos = gridpos;
637
+ c._zoom.datapos = datapos;
638
+ }
639
+ if (c.showVerticalLine || c.showHorizontalLine) {
640
+ var ctx = c.cursorCanvas._ctx;
641
+ ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
642
+ }
643
+ if (c.showCursorLegend) {
644
+ var cells = $(plot.targetId + ' td.jqplot-cursor-legend-label');
645
+ for (var i=0; i<cells.length; i++) {
646
+ var idx = $(cells[i]).data('seriesIndex');
647
+ var series = plot.series[idx];
648
+ var label = series.label.toString();
649
+ if (plot.legend.escapeHtml) {
650
+ $(cells[i]).text($.jqplot.sprintf(c.cursorLegendFormatString, label, undefined, undefined));
651
+ }
652
+ else {
653
+ $(cells[i]).html($.jqplot.sprintf(c.cursorLegendFormatString, label, undefined, undefined));
654
+ }
655
+
656
+ }
657
+ }
658
+ }
659
+ }
660
+
661
+ function handleMouseEnter(ev, gridpos, datapos, neighbor, plot) {
662
+ var c = plot.plugins.cursor;
663
+ c.onGrid = true;
664
+ if (c.show) {
665
+ c.previousCursor = ev.target.style.cursor;
666
+ ev.target.style.cursor = c.style;
667
+ if (c.showTooltip) {
668
+ updateTooltip(gridpos, datapos, plot);
669
+ if (c.followMouse) {
670
+ moveTooltip(gridpos, plot);
671
+ }
672
+ else {
673
+ positionTooltip(plot);
674
+ }
675
+ c._tooltipElem.show();
676
+ }
677
+ if (c.showVerticalLine || c.showHorizontalLine) {
678
+ moveLine(gridpos, plot);
679
+ }
680
+ }
681
+
682
+ }
683
+
684
+ function handleMouseMove(ev, gridpos, datapos, neighbor, plot) {
685
+ var c = plot.plugins.cursor;
686
+ var ctx = c.zoomCanvas._ctx;
687
+ if (c.show) {
688
+ if (c.showTooltip) {
689
+ updateTooltip(gridpos, datapos, plot);
690
+ if (c.followMouse) {
691
+ moveTooltip(gridpos, plot);
692
+ }
693
+ }
694
+ if (c.showVerticalLine || c.showHorizontalLine) {
695
+ moveLine(gridpos, plot);
696
+ }
697
+ }
698
+ }
699
+
700
+ function getEventPosition(ev) {
701
+ var plot = ev.data.plot;
702
+ var go = plot.eventCanvas._elem.offset();
703
+ var gridPos = {x:ev.pageX - go.left, y:ev.pageY - go.top};
704
+ var dataPos = {xaxis:null, yaxis:null, x2axis:null, y2axis:null, y3axis:null, y4axis:null, y5axis:null, y6axis:null, y7axis:null, y8axis:null, y9axis:null};
705
+ var an = ['xaxis', 'yaxis', 'x2axis', 'y2axis', 'y3axis', 'y4axis', 'y5axis', 'y6axis', 'y7axis', 'y8axis', 'y9axis'];
706
+ var ax = plot.axes;
707
+ var n, axis;
708
+ for (n=11; n>0; n--) {
709
+ axis = an[n-1];
710
+ if (ax[axis].show) {
711
+ dataPos[axis] = ax[axis].series_p2u(gridPos[axis.charAt(0)]);
712
+ }
713
+ }
714
+
715
+ return {offsets:go, gridPos:gridPos, dataPos:dataPos};
716
+ }
717
+
718
+ function handleZoomMove(ev) {
719
+ var plot = ev.data.plot;
720
+ var c = plot.plugins.cursor;
721
+ // don't do anything if not on grid.
722
+ if (c.show && c.zoom && c._zoom.started && !c.zoomTarget) {
723
+ var ctx = c.zoomCanvas._ctx;
724
+ var positions = getEventPosition(ev);
725
+ var gridpos = positions.gridPos;
726
+ var datapos = positions.dataPos;
727
+ c._zoom.gridpos = gridpos;
728
+ c._zoom.datapos = datapos;
729
+ c._zoom.zooming = true;
730
+ var xpos = gridpos.x;
731
+ var ypos = gridpos.y;
732
+ var height = ctx.canvas.height;
733
+ var width = ctx.canvas.width;
734
+ if (c.showTooltip && !c.onGrid && c.showTooltipOutsideZoom) {
735
+ updateTooltip(gridpos, datapos, plot);
736
+ if (c.followMouse) {
737
+ moveTooltip(gridpos, plot);
738
+ }
739
+ }
740
+ if (c.constrainZoomTo == 'x') {
741
+ c._zoom.end = [xpos, height];
742
+ }
743
+ else if (c.constrainZoomTo == 'y') {
744
+ c._zoom.end = [width, ypos];
745
+ }
746
+ else {
747
+ c._zoom.end = [xpos, ypos];
748
+ }
749
+ var sel = window.getSelection;
750
+ if (document.selection && document.selection.empty)
751
+ {
752
+ document.selection.empty();
753
+ }
754
+ else if (sel && !sel().isCollapsed) {
755
+ sel().collapse();
756
+ }
757
+ drawZoomBox.call(c);
758
+ }
759
+ }
760
+
761
+ function handleMouseDown(ev, gridpos, datapos, neighbor, plot) {
762
+ var c = plot.plugins.cursor;
763
+ $(document).one('mouseup.jqplot_cursor', {plot:plot}, handleMouseUp);
764
+ var axes = plot.axes;
765
+ if (document.onselectstart != undefined) {
766
+ c._oldHandlers.onselectstart = document.onselectstart;
767
+ document.onselectstart = function () { return false; };
768
+ }
769
+ if (document.ondrag != undefined) {
770
+ c._oldHandlers.ondrag = document.ondrag;
771
+ document.ondrag = function () { return false; };
772
+ }
773
+ if (document.onmousedown != undefined) {
774
+ c._oldHandlers.onmousedown = document.onmousedown;
775
+ document.onmousedown = function () { return false; };
776
+ }
777
+ if (c.zoom) {
778
+ if (!c.zoomProxy) {
779
+ var ctx = c.zoomCanvas._ctx;
780
+ ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
781
+ }
782
+ if (c.constrainZoomTo == 'x') {
783
+ c._zoom.start = [gridpos.x, 0];
784
+ }
785
+ else if (c.constrainZoomTo == 'y') {
786
+ c._zoom.start = [0, gridpos.y];
787
+ }
788
+ else {
789
+ c._zoom.start = [gridpos.x, gridpos.y];
790
+ }
791
+ c._zoom.started = true;
792
+ for (var ax in datapos) {
793
+ // get zoom starting position.
794
+ c._zoom.axes.start[ax] = datapos[ax];
795
+ }
796
+ $(document).bind('mousemove.jqplotCursor', {plot:plot}, handleZoomMove);
797
+ }
798
+ }
799
+
800
+ function handleMouseUp(ev) {
801
+ var plot = ev.data.plot;
802
+ var c = plot.plugins.cursor;
803
+ if (c.zoom && c._zoom.zooming && !c.zoomTarget) {
804
+ var xpos = c._zoom.gridpos.x;
805
+ var ypos = c._zoom.gridpos.y;
806
+ var datapos = c._zoom.datapos;
807
+ var height = c.zoomCanvas._ctx.canvas.height;
808
+ var width = c.zoomCanvas._ctx.canvas.width;
809
+ var axes = plot.axes;
810
+
811
+ if (c.constrainOutsideZoom && !c.onGrid) {
812
+ if (xpos < 0) { xpos = 0; }
813
+ else if (xpos > width) { xpos = width; }
814
+ if (ypos < 0) { ypos = 0; }
815
+ else if (ypos > height) { ypos = height; }
816
+
817
+ for (var axis in datapos) {
818
+ if (datapos[axis]) {
819
+ if (axis.charAt(0) == 'x') {
820
+ datapos[axis] = axes[axis].series_p2u(xpos);
821
+ }
822
+ else {
823
+ datapos[axis] = axes[axis].series_p2u(ypos);
824
+ }
825
+ }
826
+ }
827
+ }
828
+
829
+ if (c.constrainZoomTo == 'x') {
830
+ ypos = height;
831
+ }
832
+ else if (c.constrainZoomTo == 'y') {
833
+ xpos = width;
834
+ }
835
+ c._zoom.end = [xpos, ypos];
836
+ c._zoom.gridpos = {x:xpos, y:ypos};
837
+
838
+ c.doZoom(c._zoom.gridpos, datapos, plot, c);
839
+ }
840
+ c._zoom.started = false;
841
+ c._zoom.zooming = false;
842
+
843
+ $(document).unbind('mousemove.jqplotCursor', handleZoomMove);
844
+
845
+ if (document.onselectstart != undefined && c._oldHandlers.onselectstart != null){
846
+ document.onselectstart = c._oldHandlers.onselectstart;
847
+ c._oldHandlers.onselectstart = null;
848
+ }
849
+ if (document.ondrag != undefined && c._oldHandlers.ondrag != null){
850
+ document.ondrag = c._oldHandlers.ondrag;
851
+ c._oldHandlers.ondrag = null;
852
+ }
853
+ if (document.onmousedown != undefined && c._oldHandlers.onmousedown != null){
854
+ document.onmousedown = c._oldHandlers.onmousedown;
855
+ c._oldHandlers.onmousedown = null;
856
+ }
857
+
858
+ }
859
+
860
+ function drawZoomBox() {
861
+ var start = this._zoom.start;
862
+ var end = this._zoom.end;
863
+ var ctx = this.zoomCanvas._ctx;
864
+ var l, t, h, w;
865
+ if (end[0] > start[0]) {
866
+ l = start[0];
867
+ w = end[0] - start[0];
868
+ }
869
+ else {
870
+ l = end[0];
871
+ w = start[0] - end[0];
872
+ }
873
+ if (end[1] > start[1]) {
874
+ t = start[1];
875
+ h = end[1] - start[1];
876
+ }
877
+ else {
878
+ t = end[1];
879
+ h = start[1] - end[1];
880
+ }
881
+ ctx.fillStyle = 'rgba(0,0,0,0.2)';
882
+ ctx.strokeStyle = '#999999';
883
+ ctx.lineWidth = 1.0;
884
+ ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
885
+ ctx.fillRect(0,0,ctx.canvas.width, ctx.canvas.height);
886
+ ctx.clearRect(l, t, w, h);
887
+ // IE won't show transparent fill rect, so stroke a rect also.
888
+ ctx.strokeRect(l,t,w,h);
889
+ }
890
+
891
+ $.jqplot.CursorLegendRenderer = function(options) {
892
+ $.jqplot.TableLegendRenderer.call(this, options);
893
+ this.formatString = '%s';
894
+ };
895
+
896
+ $.jqplot.CursorLegendRenderer.prototype = new $.jqplot.TableLegendRenderer();
897
+ $.jqplot.CursorLegendRenderer.prototype.constructor = $.jqplot.CursorLegendRenderer;
898
+
899
+ // called in context of a Legend
900
+ $.jqplot.CursorLegendRenderer.prototype.draw = function() {
901
+ if (this.show) {
902
+ var series = this._series;
903
+ // make a table. one line label per row.
904
+ this._elem = $('<table class="jqplot-legend jqplot-cursor-legend" style="position:absolute"></table>');
905
+
906
+ var pad = false;
907
+ for (var i = 0; i< series.length; i++) {
908
+ s = series[i];
909
+ if (s.show) {
910
+ var lt = $.jqplot.sprintf(this.formatString, s.label.toString());
911
+ if (lt) {
912
+ var color = s.color;
913
+ if (s._stack && !s.fill) {
914
+ color = '';
915
+ }
916
+ addrow.call(this, lt, color, pad, i);
917
+ pad = true;
918
+ }
919
+ // let plugins add more rows to legend. Used by trend line plugin.
920
+ for (var j=0; j<$.jqplot.addLegendRowHooks.length; j++) {
921
+ var item = $.jqplot.addLegendRowHooks[j].call(this, s);
922
+ if (item) {
923
+ addrow.call(this, item.label, item.color, pad);
924
+ pad = true;
925
+ }
926
+ }
927
+ }
928
+ }
929
+ }
930
+
931
+ function addrow(label, color, pad, idx) {
932
+ var rs = (pad) ? this.rowSpacing : '0';
933
+ var tr = $('<tr class="jqplot-legend jqplot-cursor-legend"></tr>').appendTo(this._elem);
934
+ tr.data('seriesIndex', idx);
935
+ $('<td class="jqplot-legend jqplot-cursor-legend-swatch" style="padding-top:'+rs+';">'+
936
+ '<div style="border:1px solid #cccccc;padding:0.2em;">'+
937
+ '<div class="jqplot-cursor-legend-swatch" style="background-color:'+color+';"></div>'+
938
+ '</div></td>').appendTo(tr);
939
+ var td = $('<td class="jqplot-legend jqplot-cursor-legend-label" style="vertical-align:middle;padding-top:'+rs+';"></td>');
940
+ td.appendTo(tr);
941
+ td.data('seriesIndex', idx);
942
+ if (this.escapeHtml) {
943
+ td.text(label);
944
+ }
945
+ else {
946
+ td.html(label);
947
+ }
948
+ }
949
+ return this._elem;
950
+ };
951
+
952
+ })(jQuery);