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