jquerysvg 1.4.5
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/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +52 -0
- data/Rakefile +1 -0
- data/jquerysvg.gemspec +24 -0
- data/lib/jquerysvg.rb +6 -0
- data/lib/jquerysvg/engine.rb +9 -0
- data/lib/jquerysvg/version.rb +3 -0
- data/vendor/assets/javascripts/jquery.svg.js +1394 -0
- data/vendor/assets/javascripts/jquery.svganim.js +473 -0
- data/vendor/assets/javascripts/jquery.svgdom.js +406 -0
- data/vendor/assets/javascripts/jquery.svgfilter.js +402 -0
- data/vendor/assets/javascripts/jquery.svggraph.js +1482 -0
- data/vendor/assets/javascripts/jquery.svgplot.js +817 -0
- data/vendor/assets/javascripts/jquerysvg.js +6 -0
- data/vendor/assets/stylesheets/jquerysvg.scss +16 -0
- metadata +109 -0
|
@@ -0,0 +1,817 @@
|
|
|
1
|
+
/* http://keith-wood.name/svg.html
|
|
2
|
+
SVG plotting extension for jQuery v1.4.5.
|
|
3
|
+
Written by Keith Wood (kbwood{at}iinet.com.au) December 2008.
|
|
4
|
+
Dual licensed under the GPL (http://dev.jquery.com/browser/trunk/jquery/GPL-LICENSE.txt) and
|
|
5
|
+
MIT (http://dev.jquery.com/browser/trunk/jquery/MIT-LICENSE.txt) licenses.
|
|
6
|
+
Please attribute the author if you use it. */
|
|
7
|
+
|
|
8
|
+
(function($) { // Hide scope, no $ conflict
|
|
9
|
+
|
|
10
|
+
$.svg.addExtension('plot', SVGPlot);
|
|
11
|
+
|
|
12
|
+
/* Extension point for SVG plotting.
|
|
13
|
+
Access through svg.plot. */
|
|
14
|
+
function SVGPlot(wrapper) {
|
|
15
|
+
this._wrapper = wrapper; // The attached SVG wrapper object
|
|
16
|
+
this._drawNow = false; // True for immediate update, false to wait for redraw call
|
|
17
|
+
// The plot title and settings
|
|
18
|
+
this._title = {value: '', offset: 25, settings: {textAnchor: 'middle'}};
|
|
19
|
+
this._area = [0.1, 0.1, 0.8, 0.9]; // The chart area: left, top, right, bottom,
|
|
20
|
+
// > 1 in pixels, <= 1 as proportion
|
|
21
|
+
this._areaFormat = {fill: 'none', stroke: 'black'}; // The formatting for the plot area
|
|
22
|
+
this._gridlines = []; // The formatting of the x- and y-gridlines
|
|
23
|
+
this._equalXY = true; // True for equal-sized x- and y-units, false to fill available space
|
|
24
|
+
this._functions = []; // The functions to be plotted, each is an object
|
|
25
|
+
this._onstatus = null; // The callback function for status updates
|
|
26
|
+
this._uuid = new Date().getTime();
|
|
27
|
+
this._plotCont = this._wrapper.svg(0, 0, 0, 0, {class_: 'svg-plot'}); // The main container for the plot
|
|
28
|
+
|
|
29
|
+
this.xAxis = new SVGPlotAxis(this); // The main x-axis
|
|
30
|
+
this.xAxis.title('X', 20);
|
|
31
|
+
this.yAxis = new SVGPlotAxis(this); // The main y-axis
|
|
32
|
+
this.yAxis.title('Y', 20);
|
|
33
|
+
this.legend = new SVGPlotLegend(this); // The plot legend
|
|
34
|
+
this._drawNow = true;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
$.extend(SVGPlot.prototype, {
|
|
38
|
+
|
|
39
|
+
/* Useful indexes. */
|
|
40
|
+
X: 0,
|
|
41
|
+
Y: 1,
|
|
42
|
+
W: 2,
|
|
43
|
+
H: 3,
|
|
44
|
+
L: 0,
|
|
45
|
+
T: 1,
|
|
46
|
+
R: 2,
|
|
47
|
+
B: 3,
|
|
48
|
+
|
|
49
|
+
/* Set or retrieve the container for the plot.
|
|
50
|
+
@param cont (SVG element) the container for the plot
|
|
51
|
+
@return (SVGPlot) this plot object or
|
|
52
|
+
(SVG element) the current container (if no parameters) */
|
|
53
|
+
container: function(cont) {
|
|
54
|
+
if (arguments.length == 0) {
|
|
55
|
+
return this._plotCont;
|
|
56
|
+
}
|
|
57
|
+
this._plotCont = cont;
|
|
58
|
+
return this;
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
/* Set or retrieve the main plotting area.
|
|
62
|
+
@param left (number) > 1 is pixels, <= 1 is proportion of width or
|
|
63
|
+
(number[4]) for left, top, right, bottom
|
|
64
|
+
@param top (number) > 1 is pixels, <= 1 is proportion of height
|
|
65
|
+
@param right (number) > 1 is pixels, <= 1 is proportion of width
|
|
66
|
+
@param bottom (number) > 1 is pixels, <= 1 is proportion of height
|
|
67
|
+
@return (SVGPlot) this plot object or
|
|
68
|
+
(number[4]) the plotting area: left, top, right, bottom (if no parameters) */
|
|
69
|
+
area: function(left, top, right, bottom) {
|
|
70
|
+
if (arguments.length == 0) {
|
|
71
|
+
return this._area;
|
|
72
|
+
}
|
|
73
|
+
this._area = (isArray(left) ? left : [left, top, right, bottom]);
|
|
74
|
+
this._drawPlot();
|
|
75
|
+
return this;
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
/* Set or retrieve the background of the plot area.
|
|
79
|
+
@param fill (string) how to fill the area background
|
|
80
|
+
@param stroke (string) the colour of the outline (optional)
|
|
81
|
+
@param settings (object) additional formatting for the area background (optional)
|
|
82
|
+
@return (SVGPlot) this plot object or
|
|
83
|
+
(object) the area format (if no parameters) */
|
|
84
|
+
format: function(fill, stroke, settings) {
|
|
85
|
+
if (arguments.length == 0) {
|
|
86
|
+
return this._areaFormat;
|
|
87
|
+
}
|
|
88
|
+
if (typeof stroke == 'object') {
|
|
89
|
+
settings = stroke;
|
|
90
|
+
stroke = null;
|
|
91
|
+
}
|
|
92
|
+
this._areaFormat = $.extend({fill: fill},
|
|
93
|
+
(stroke ? {stroke: stroke} : {}), settings || {});
|
|
94
|
+
this._drawPlot();
|
|
95
|
+
return this;
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
/* Set or retrieve the gridlines formatting for the plot area.
|
|
99
|
+
@param xSettings (string) the colour of the gridlines along the x-axis, or
|
|
100
|
+
(object) formatting for the gridlines along the x-axis, or
|
|
101
|
+
null for none
|
|
102
|
+
@param ySettings (string) the colour of the gridlines along the y-axis, or
|
|
103
|
+
(object) formatting for the gridlines along the y-axis, or
|
|
104
|
+
null for none
|
|
105
|
+
@return (SVGPlot) this plot object or
|
|
106
|
+
(object[2]) the gridlines formatting (if no parameters) */
|
|
107
|
+
gridlines: function(xSettings, ySettings) {
|
|
108
|
+
if (arguments.length == 0) {
|
|
109
|
+
return this._gridlines;
|
|
110
|
+
}
|
|
111
|
+
this._gridlines = [(typeof xSettings == 'string' ? {stroke: xSettings} : xSettings),
|
|
112
|
+
(typeof ySettings == 'string' ? {stroke: ySettings} : ySettings)];
|
|
113
|
+
if (this._gridlines[0] == null && this._gridlines[1] == null) {
|
|
114
|
+
this._gridlines = [];
|
|
115
|
+
}
|
|
116
|
+
this._drawPlot();
|
|
117
|
+
return this;
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
/* Set or retrieve the equality of the x- and y-axes.
|
|
121
|
+
@param value (boolean) true for equal x- and y-units, false to fill the available space
|
|
122
|
+
@return (SVGPlot) this plot object or
|
|
123
|
+
(boolean) the current setting (if no parameters) */
|
|
124
|
+
equalXY: function(value) {
|
|
125
|
+
if (arguments.length == 0) {
|
|
126
|
+
return this._equalXY;
|
|
127
|
+
}
|
|
128
|
+
this._equalXY = value;
|
|
129
|
+
return this;
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
/* Set or retrieve the title of the plot and its formatting.
|
|
133
|
+
@param value (string) the title
|
|
134
|
+
@param offset (number) the vertical positioning of the title
|
|
135
|
+
> 1 is pixels, <= 1 is proportion of width (optional)
|
|
136
|
+
@param colour (string) the colour of the title (optional)
|
|
137
|
+
@param settings (object) formatting for the title (optional)
|
|
138
|
+
@return (SVGPlot) this plot object or
|
|
139
|
+
(object) value, offset, and settings for the title (if no parameters) */
|
|
140
|
+
title: function(value, offset, colour, settings) {
|
|
141
|
+
if (arguments.length == 0) {
|
|
142
|
+
return this._title;
|
|
143
|
+
}
|
|
144
|
+
if (typeof offset != 'number') {
|
|
145
|
+
settings = colour;
|
|
146
|
+
colour = offset;
|
|
147
|
+
offset = null;
|
|
148
|
+
}
|
|
149
|
+
if (typeof colour != 'string') {
|
|
150
|
+
settings = colour;
|
|
151
|
+
colour = null;
|
|
152
|
+
}
|
|
153
|
+
this._title = {value: value, offset: offset || this._title.offset,
|
|
154
|
+
settings: $.extend({textAnchor: 'middle'},
|
|
155
|
+
(colour ? {fill: colour} : {}), settings || {})};
|
|
156
|
+
this._drawPlot();
|
|
157
|
+
return this;
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
/* Add a function to be plotted on the plot.
|
|
161
|
+
@param name (string) the name of this series (optional)
|
|
162
|
+
@param fn (function) the function to be plotted
|
|
163
|
+
@param range (number[2]) the range of values to plot (optional)
|
|
164
|
+
@param points (number) the number of points to plot within this range (optional)
|
|
165
|
+
@param stroke (string) the colour of the plotted lines (optional)
|
|
166
|
+
@param strokeWidth (number) the width of the plotted lines (optional)
|
|
167
|
+
@param settings (object) additional settings for the plotted values (optional)
|
|
168
|
+
@return (SVGPlot) this plot object */
|
|
169
|
+
addFunction: function(name, fn, range, points, stroke, strokeWidth, settings) {
|
|
170
|
+
this._functions.push(new SVGPlotFunction(
|
|
171
|
+
this, name, fn, range, points, stroke, strokeWidth, settings));
|
|
172
|
+
this._drawPlot();
|
|
173
|
+
return this;
|
|
174
|
+
},
|
|
175
|
+
|
|
176
|
+
/* Retrieve the function wrappers.
|
|
177
|
+
@param i (number) the function index (optional)
|
|
178
|
+
@return (SVGPlotFunction) the specified function or
|
|
179
|
+
(SVGPlotFunction[]) the list of functions */
|
|
180
|
+
functions: function(i) {
|
|
181
|
+
return (arguments.length > 0 ? this._functions[i] : null) || this._functions;
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
/* Suppress drawing of the plot until redraw() is called.
|
|
185
|
+
@return (SVGPlot) this plot object */
|
|
186
|
+
noDraw: function() {
|
|
187
|
+
this._drawNow = false;
|
|
188
|
+
return this;
|
|
189
|
+
},
|
|
190
|
+
|
|
191
|
+
/* Redraw the entire plot with the current settings and values.
|
|
192
|
+
@return (SVGPlot) this plot object */
|
|
193
|
+
redraw: function() {
|
|
194
|
+
this._drawNow = true;
|
|
195
|
+
this._drawPlot();
|
|
196
|
+
return this;
|
|
197
|
+
},
|
|
198
|
+
|
|
199
|
+
/* Set the callback function for status updates.
|
|
200
|
+
@param onstatus (function) the callback function
|
|
201
|
+
@return (SVGPlot) this plot object */
|
|
202
|
+
status: function(onstatus) {
|
|
203
|
+
this._onstatus = onstatus;
|
|
204
|
+
return this;
|
|
205
|
+
},
|
|
206
|
+
|
|
207
|
+
/* Actually draw the plot (if allowed). */
|
|
208
|
+
_drawPlot: function() {
|
|
209
|
+
if (!this._drawNow) {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
while (this._plotCont.firstChild) {
|
|
213
|
+
this._plotCont.removeChild(this._plotCont.firstChild);
|
|
214
|
+
}
|
|
215
|
+
if (!this._plotCont.parent) {
|
|
216
|
+
this._wrapper._svg.appendChild(this._plotCont);
|
|
217
|
+
}
|
|
218
|
+
// Set sizes if not already there
|
|
219
|
+
if (!this._plotCont.width) {
|
|
220
|
+
this._plotCont.setAttribute('width',
|
|
221
|
+
parseInt(this._plotCont.getAttribute('width'), 10) || this._wrapper._width());
|
|
222
|
+
}
|
|
223
|
+
else if (this._plotCont.width.baseVal) {
|
|
224
|
+
this._plotCont.width.baseVal.value =
|
|
225
|
+
this._plotCont.width.baseVal.value || this._wrapper._width();
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
this._plotCont.width = this._plotCont.width || this._wrapper._width();
|
|
229
|
+
}
|
|
230
|
+
if (!this._plotCont.height) {
|
|
231
|
+
this._plotCont.setAttribute('height',
|
|
232
|
+
parseInt(this._plotCont.getAttribute('height'), 10) || this._wrapper._height());
|
|
233
|
+
}
|
|
234
|
+
else if (this._plotCont.height.baseVal) {
|
|
235
|
+
this._plotCont.height.baseVal.value =
|
|
236
|
+
this._plotCont.height.baseVal.value || this._wrapper._height();
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
this._plotCont.height = this._plotCont.height || this._wrapper._height();
|
|
240
|
+
}
|
|
241
|
+
this._drawChartBackground();
|
|
242
|
+
var dims = this._getDims();
|
|
243
|
+
var clip = this._wrapper.other(this._plotCont, 'clipPath', {id: 'clip' + this._uuid});
|
|
244
|
+
this._wrapper.rect(clip, dims[this.X], dims[this.Y], dims[this.W], dims[this.H]);
|
|
245
|
+
this._plot = this._wrapper.group(this._plotCont,
|
|
246
|
+
{class_: 'foreground', clipPath: 'url(#clip' + this._uuid + ')'});
|
|
247
|
+
this._drawAxis(true);
|
|
248
|
+
this._drawAxis(false);
|
|
249
|
+
for (var i = 0; i < this._functions.length; i++) {
|
|
250
|
+
this._plotFunction(this._functions[i], i);
|
|
251
|
+
}
|
|
252
|
+
this._drawTitle();
|
|
253
|
+
this._drawLegend();
|
|
254
|
+
},
|
|
255
|
+
|
|
256
|
+
/* Decode an attribute value.
|
|
257
|
+
@param node the node to examine
|
|
258
|
+
@param name the attribute name
|
|
259
|
+
@return the actual value */
|
|
260
|
+
_getValue: function(node, name) {
|
|
261
|
+
return (!node[name] ? parseInt(node.getAttribute(name), 10) :
|
|
262
|
+
(node[name].baseVal ? node[name].baseVal.value : node[name]));
|
|
263
|
+
},
|
|
264
|
+
|
|
265
|
+
/* Calculate the actual dimensions of the plot area.
|
|
266
|
+
@param area (number[4]) the area values to evaluate (optional)
|
|
267
|
+
@return (number[4]) an array of dimension values: left, top, width, height */
|
|
268
|
+
_getDims: function(area) {
|
|
269
|
+
var otherArea = (area != null);
|
|
270
|
+
area = area || this._area;
|
|
271
|
+
var availWidth = this._getValue(this._plotCont, 'width');
|
|
272
|
+
var availHeight = this._getValue(this._plotCont, 'height');
|
|
273
|
+
var left = (area[this.L] > 1 ? area[this.L] : availWidth * area[this.L]);
|
|
274
|
+
var top = (area[this.T] > 1 ? area[this.T] : availHeight * area[this.T]);
|
|
275
|
+
var width = (area[this.R] > 1 ? area[this.R] : availWidth * area[this.R]) - left;
|
|
276
|
+
var height = (area[this.B] > 1 ? area[this.B] : availHeight * area[this.B]) - top;
|
|
277
|
+
if (this._equalXY && !otherArea) {
|
|
278
|
+
var scale = Math.min(width / (this.xAxis._scale.max - this.xAxis._scale.min),
|
|
279
|
+
height / (this.yAxis._scale.max - this.yAxis._scale.min));
|
|
280
|
+
width = scale * (this.xAxis._scale.max - this.xAxis._scale.min);
|
|
281
|
+
height = scale * (this.yAxis._scale.max - this.yAxis._scale.min);
|
|
282
|
+
}
|
|
283
|
+
return [left, top, width, height];
|
|
284
|
+
},
|
|
285
|
+
|
|
286
|
+
/* Calculate the scaling factors for the plot area.
|
|
287
|
+
@return (number[2]) the x- and y-scaling factors */
|
|
288
|
+
_getScales: function() {
|
|
289
|
+
var dims = this._getDims();
|
|
290
|
+
return [dims[this.W] / (this.xAxis._scale.max - this.xAxis._scale.min),
|
|
291
|
+
dims[this.H] / (this.yAxis._scale.max - this.yAxis._scale.min)];
|
|
292
|
+
},
|
|
293
|
+
|
|
294
|
+
/* Draw the chart background, including gridlines.
|
|
295
|
+
@param noXGrid (boolean) true to suppress the x-gridlines, false to draw them (optional)
|
|
296
|
+
@param noYGrid (boolean) true to suppress the y-gridlines, false to draw them (optional)
|
|
297
|
+
@return (element) the background group element */
|
|
298
|
+
_drawChartBackground: function(noXGrid, noYGrid) {
|
|
299
|
+
var bg = this._wrapper.group(this._plotCont, {class_: 'background'});
|
|
300
|
+
var dims = this._getDims();
|
|
301
|
+
this._wrapper.rect(bg, dims[this.X], dims[this.Y], dims[this.W], dims[this.H], this._areaFormat);
|
|
302
|
+
if (this._gridlines[0] && this.yAxis._ticks.major && !noYGrid) {
|
|
303
|
+
this._drawGridlines(bg, true, this._gridlines[0], dims);
|
|
304
|
+
}
|
|
305
|
+
if (this._gridlines[1] && this.xAxis._ticks.major && !noXGrid) {
|
|
306
|
+
this._drawGridlines(bg, false, this._gridlines[1], dims);
|
|
307
|
+
}
|
|
308
|
+
return bg;
|
|
309
|
+
},
|
|
310
|
+
|
|
311
|
+
/* Draw one set of gridlines.
|
|
312
|
+
@param bg (element) the background group element
|
|
313
|
+
@param horiz (boolean) true if horizontal, false if vertical
|
|
314
|
+
@param format (object) additional settings for the gridlines */
|
|
315
|
+
_drawGridlines: function(bg, horiz, format, dims) {
|
|
316
|
+
var g = this._wrapper.group(bg, format);
|
|
317
|
+
var axis = (horiz ? this.yAxis : this.xAxis);
|
|
318
|
+
var scales = this._getScales();
|
|
319
|
+
var major = Math.floor(axis._scale.min / axis._ticks.major) * axis._ticks.major;
|
|
320
|
+
major += (major <= axis._scale.min ? axis._ticks.major : 0);
|
|
321
|
+
while (major < axis._scale.max) {
|
|
322
|
+
var v = (horiz ? axis._scale.max - major : major - axis._scale.min) *
|
|
323
|
+
scales[horiz ? 1 : 0] + (horiz ? dims[this.Y] : dims[this.X]);
|
|
324
|
+
this._wrapper.line(g, (horiz ? dims[this.X] : v), (horiz ? v : dims[this.Y]),
|
|
325
|
+
(horiz ? dims[this.X] + dims[this.W] : v), (horiz ? v : dims[this.Y] + dims[this.H]));
|
|
326
|
+
major += axis._ticks.major;
|
|
327
|
+
}
|
|
328
|
+
},
|
|
329
|
+
|
|
330
|
+
/* Draw an axis, its tick marks, and title.
|
|
331
|
+
@param horiz (boolean) true for x-axis, false for y-axis */
|
|
332
|
+
_drawAxis: function(horiz) {
|
|
333
|
+
var id = (horiz ? 'x' : 'y') + 'Axis';
|
|
334
|
+
var axis = (horiz ? this.xAxis : this.yAxis);
|
|
335
|
+
var axis2 = (horiz ? this.yAxis : this.xAxis);
|
|
336
|
+
var dims = this._getDims();
|
|
337
|
+
var scales = this._getScales();
|
|
338
|
+
var gl = this._wrapper.group(this._plot, $.extend({class_: id}, axis._lineFormat));
|
|
339
|
+
var gt = this._wrapper.group(this._plot, $.extend({class_: id + 'Labels',
|
|
340
|
+
textAnchor: (horiz ? 'middle' : 'end')}, axis._labelFormat));
|
|
341
|
+
var zero = (horiz ? axis2._scale.max : -axis2._scale.min) *
|
|
342
|
+
scales[horiz ? 1 : 0] + (horiz ? dims[this.Y] : dims[this.X]);
|
|
343
|
+
this._wrapper.line(gl, (horiz ? dims[this.X] : zero), (horiz ? zero : dims[this.Y]),
|
|
344
|
+
(horiz ? dims[this.X] + dims[this.W] : zero),
|
|
345
|
+
(horiz ? zero : dims[this.Y] + dims[this.H]));
|
|
346
|
+
if (axis._ticks.major) {
|
|
347
|
+
var size = axis._ticks.size;
|
|
348
|
+
var major = Math.floor(axis._scale.min / axis._ticks.major) * axis._ticks.major;
|
|
349
|
+
major = (major < axis._scale.min ? major + axis._ticks.major : major);
|
|
350
|
+
var minor = (!axis._ticks.minor ? axis._scale.max + 1 :
|
|
351
|
+
Math.floor(axis._scale.min / axis._ticks.minor) * axis._ticks.minor);
|
|
352
|
+
minor = (minor < axis._scale.min ? minor + axis._ticks.minor : minor);
|
|
353
|
+
var offsets = [(axis._ticks.position == 'nw' || axis._ticks.position == 'both' ? -1 : 0),
|
|
354
|
+
(axis._ticks.position == 'se' || axis._ticks.position == 'both' ? +1 : 0)];
|
|
355
|
+
while (major <= axis._scale.max || minor <= axis._scale.max) {
|
|
356
|
+
var cur = Math.min(major, minor);
|
|
357
|
+
var len = (cur == major ? size : size / 2);
|
|
358
|
+
var xy = (horiz ? cur - axis._scale.min : axis._scale.max - cur) *
|
|
359
|
+
scales[horiz ? 0 : 1] + (horiz ? dims[this.X] : dims[this.Y]);
|
|
360
|
+
this._wrapper.line(gl, (horiz ? xy : zero + len * offsets[0]),
|
|
361
|
+
(horiz ? zero + len * offsets[0] : xy),
|
|
362
|
+
(horiz ? xy : zero + len * offsets[1]),
|
|
363
|
+
(horiz ? zero + len * offsets[1] : xy));
|
|
364
|
+
if (cur == major && cur != 0) {
|
|
365
|
+
this._wrapper.text(gt, (horiz ? xy : zero - size),
|
|
366
|
+
(horiz ? zero - size : xy), '' + cur);
|
|
367
|
+
}
|
|
368
|
+
major += (cur == major ? axis._ticks.major : 0);
|
|
369
|
+
minor += (cur == minor ? axis._ticks.minor : 0);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
if (axis._title) {
|
|
373
|
+
if (horiz) {
|
|
374
|
+
this._wrapper.text(this._plotCont, dims[this.X] - axis._titleOffset,
|
|
375
|
+
zero, axis._title, $.extend({textAnchor: 'end'}, axis._titleFormat || {}));
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
this._wrapper.text(this._plotCont, zero,
|
|
379
|
+
dims[this.Y] + dims[this.H] + axis._titleOffset,
|
|
380
|
+
axis._title, $.extend({textAnchor : 'middle'}, axis._titleFormat || {}));
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
},
|
|
384
|
+
|
|
385
|
+
/* Plot an individual function. */
|
|
386
|
+
_plotFunction: function(fn, cur) {
|
|
387
|
+
var dims = this._getDims();
|
|
388
|
+
var scales = this._getScales();
|
|
389
|
+
var path = this._wrapper.createPath();
|
|
390
|
+
var range = fn._range || [this.xAxis._scale.min, this.xAxis._scale.max];
|
|
391
|
+
var xScale = (range[1] - range[0]) / fn._points;
|
|
392
|
+
var first = true;
|
|
393
|
+
for (var i = 0; i <= fn._points; i++) {
|
|
394
|
+
var x = range[0] + i * xScale;
|
|
395
|
+
if (x > this.xAxis._scale.max + xScale) {
|
|
396
|
+
break;
|
|
397
|
+
}
|
|
398
|
+
if (x < this.xAxis._scale.min - xScale) {
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
401
|
+
var px = (x - this.xAxis._scale.min) * scales[0] + dims[this.X];
|
|
402
|
+
var py = dims[this.H] - ((fn._fn(x) - this.yAxis._scale.min) * scales[1]) + dims[this.Y];
|
|
403
|
+
path[(first ? 'move' : 'line') + 'To'](px, py);
|
|
404
|
+
first = false;
|
|
405
|
+
}
|
|
406
|
+
var p = this._wrapper.path(this._plot, path,
|
|
407
|
+
$.extend({class_: 'fn' + cur, fill: 'none', stroke: fn._stroke,
|
|
408
|
+
strokeWidth: fn._strokeWidth}, fn._settings || {}));
|
|
409
|
+
this._showStatus(p, fn._name);
|
|
410
|
+
},
|
|
411
|
+
|
|
412
|
+
/* Draw the plot title - centred. */
|
|
413
|
+
_drawTitle: function() {
|
|
414
|
+
this._wrapper.text(this._plotCont, this._getValue(this._plotCont, 'width') / 2,
|
|
415
|
+
this._title.offset, this._title.value, this._title.settings);
|
|
416
|
+
},
|
|
417
|
+
|
|
418
|
+
/* Draw the chart legend. */
|
|
419
|
+
_drawLegend: function() {
|
|
420
|
+
if (!this.legend._show) {
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
var g = this._wrapper.group(this._plotCont, {class_: 'legend'});
|
|
424
|
+
var dims = this._getDims(this.legend._area);
|
|
425
|
+
this._wrapper.rect(g, dims[this.X], dims[this.Y], dims[this.W], dims[this.H],
|
|
426
|
+
this.legend._bgSettings);
|
|
427
|
+
var horiz = dims[this.W] > dims[this.H];
|
|
428
|
+
var numFn = this._functions.length;
|
|
429
|
+
var offset = (horiz ? dims[this.W] : dims[this.H]) / numFn;
|
|
430
|
+
var xBase = dims[this.X] + 5;
|
|
431
|
+
var yBase = dims[this.Y] + ((horiz ? dims[this.H] : offset) + this.legend._sampleSize) / 2;
|
|
432
|
+
for (var i = 0; i < numFn; i++) {
|
|
433
|
+
var fn = this._functions[i];
|
|
434
|
+
this._wrapper.rect(g, xBase + (horiz ? i * offset : 0),
|
|
435
|
+
yBase + (horiz ? 0 : i * offset) - this.legend._sampleSize,
|
|
436
|
+
this.legend._sampleSize, this.legend._sampleSize, {fill: fn._stroke});
|
|
437
|
+
this._wrapper.text(g, xBase + (horiz ? i * offset : 0) + this.legend._sampleSize + 5,
|
|
438
|
+
yBase + (horiz ? 0 : i * offset), fn._name, this.legend._textSettings);
|
|
439
|
+
}
|
|
440
|
+
},
|
|
441
|
+
|
|
442
|
+
/* Show the current value status on hover. */
|
|
443
|
+
_showStatus: function(elem, label) {
|
|
444
|
+
var status = this._onstatus;
|
|
445
|
+
if (this._onstatus) {
|
|
446
|
+
$(elem).hover(function(evt) { status.apply(this, [label]); },
|
|
447
|
+
function() { status.apply(this, ['']); });
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
/* Details about each plot function.
|
|
453
|
+
@param plot (SVGPlot) the owning plot
|
|
454
|
+
@param name (string) the name of this function (optional)
|
|
455
|
+
@param fn (function) the function to be plotted
|
|
456
|
+
@param range (number[2]) the range of values to be plotted (optional)
|
|
457
|
+
@param points (number) the number of points to plot within this range (optional)
|
|
458
|
+
@param stroke (string) the colour of the (out)line for the plot (optional)
|
|
459
|
+
@param strokeWidth (number) the width of the (out)line for the plot (optional)
|
|
460
|
+
@param settings (object) additional formatting settings (optional)
|
|
461
|
+
@return (SVGPlotFunction) the new plot function object */
|
|
462
|
+
function SVGPlotFunction(plot, name, fn, range, points, stroke, strokeWidth, settings) {
|
|
463
|
+
if (typeof name != 'string') {
|
|
464
|
+
settings = strokeWidth;
|
|
465
|
+
strokeWidth = stroke;
|
|
466
|
+
stroke = points;
|
|
467
|
+
points = range;
|
|
468
|
+
range = fn;
|
|
469
|
+
fn = name;
|
|
470
|
+
name = null;
|
|
471
|
+
}
|
|
472
|
+
if (!isArray(range)) {
|
|
473
|
+
settings = strokeWidth;
|
|
474
|
+
strokeWidth = stroke;
|
|
475
|
+
stroke = points;
|
|
476
|
+
points = range;
|
|
477
|
+
range = null;
|
|
478
|
+
}
|
|
479
|
+
if (typeof points != 'number') {
|
|
480
|
+
settings = strokeWidth;
|
|
481
|
+
strokeWidth = stroke;
|
|
482
|
+
stroke = points;
|
|
483
|
+
points = null;
|
|
484
|
+
}
|
|
485
|
+
if (typeof stroke != 'string') {
|
|
486
|
+
settings = strokeWidth;
|
|
487
|
+
strokeWidth = stroke;
|
|
488
|
+
stroke = null;
|
|
489
|
+
}
|
|
490
|
+
if (typeof strokeWidth != 'number') {
|
|
491
|
+
settings = strokeWidth;
|
|
492
|
+
strokeWidth = null;
|
|
493
|
+
}
|
|
494
|
+
this._plot = plot; // The owning plot
|
|
495
|
+
this._name = name || ''; // Display name
|
|
496
|
+
this._fn = fn || identity; // The actual function: y = fn(x)
|
|
497
|
+
this._range = range; // The range of values plotted
|
|
498
|
+
this._points = points || 100; // The number of points plotted
|
|
499
|
+
this._stroke = stroke || 'black'; // The line colour
|
|
500
|
+
this._strokeWidth = strokeWidth || 1; // The line width
|
|
501
|
+
this._settings = settings || {}; // Any other settings
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
$.extend(SVGPlotFunction.prototype, {
|
|
505
|
+
|
|
506
|
+
/* Set or retrieve the name for this function.
|
|
507
|
+
@param name (string) the function's name
|
|
508
|
+
@return (SVGPlotFunction) this plot function object or
|
|
509
|
+
(string) the function name (if no parameters) */
|
|
510
|
+
name: function(name) {
|
|
511
|
+
if (arguments.length == 0) {
|
|
512
|
+
return this._name;
|
|
513
|
+
}
|
|
514
|
+
this._name = name;
|
|
515
|
+
this._plot._drawPlot();
|
|
516
|
+
return this;
|
|
517
|
+
},
|
|
518
|
+
|
|
519
|
+
/* Set or retrieve the function to be plotted.
|
|
520
|
+
@param name (string) the function's name (optional)
|
|
521
|
+
@param fn (function) the function to be ploted
|
|
522
|
+
@return (SVGPlotFunction) this plot function object or
|
|
523
|
+
(function) the actual function (if no parameters) */
|
|
524
|
+
fn: function(name, fn) {
|
|
525
|
+
if (arguments.length == 0) {
|
|
526
|
+
return this._fn;
|
|
527
|
+
}
|
|
528
|
+
if (typeof name == 'function') {
|
|
529
|
+
fn = name;
|
|
530
|
+
name = null;
|
|
531
|
+
}
|
|
532
|
+
this._name = name || this._name;
|
|
533
|
+
this._fn = fn;
|
|
534
|
+
this._plot._drawPlot();
|
|
535
|
+
return this;
|
|
536
|
+
},
|
|
537
|
+
|
|
538
|
+
/* Set or retrieve the range of values to be plotted.
|
|
539
|
+
@param min (number) the minimum value to be plotted
|
|
540
|
+
@param max (number) the maximum value to be plotted
|
|
541
|
+
@return (SVGPlotFunction) this plot function object or
|
|
542
|
+
(number[2]) the value range (if no parameters) */
|
|
543
|
+
range: function(min, max) {
|
|
544
|
+
if (arguments.length == 0) {
|
|
545
|
+
return this._range;
|
|
546
|
+
}
|
|
547
|
+
this._range = (min == null ? null : [min, max]);
|
|
548
|
+
this._plot._drawPlot();
|
|
549
|
+
return this;
|
|
550
|
+
},
|
|
551
|
+
|
|
552
|
+
/* Set or retrieve the number of points to be plotted.
|
|
553
|
+
@param value (number) the number of points to plot
|
|
554
|
+
@return (SVGPlotFunction) this plot function object or
|
|
555
|
+
(number) the number of points (if no parameters) */
|
|
556
|
+
points: function(value) {
|
|
557
|
+
if (arguments.length == 0) {
|
|
558
|
+
return this._points;
|
|
559
|
+
}
|
|
560
|
+
this._points = value;
|
|
561
|
+
this._plot._drawPlot();
|
|
562
|
+
return this;
|
|
563
|
+
},
|
|
564
|
+
|
|
565
|
+
/* Set or retrieve the formatting for this function.
|
|
566
|
+
@param stroke (string) the (out)line colour
|
|
567
|
+
@param strokeWidth (number) the line's width (optional)
|
|
568
|
+
@param settings (object) additional formatting settings for the function (optional)
|
|
569
|
+
@return (SVGPlotFunction) this plot function object or
|
|
570
|
+
(object) formatting settings (if no parameters) */
|
|
571
|
+
format: function(stroke, strokeWidth, settings) {
|
|
572
|
+
if (arguments.length == 0) {
|
|
573
|
+
return $.extend({stroke: this._stroke,
|
|
574
|
+
strokeWidth: this._strokeWidth}, this._settings);
|
|
575
|
+
}
|
|
576
|
+
if (typeof strokeWidth != 'number') {
|
|
577
|
+
settings = strokeWidth;
|
|
578
|
+
strokeWidth = null;
|
|
579
|
+
}
|
|
580
|
+
this._stroke = stroke || this._stroke;
|
|
581
|
+
this._strokeWidth = strokeWidth || this._strokeWidth;
|
|
582
|
+
$.extend(this._settings, settings || {});
|
|
583
|
+
this._plot._drawPlot();
|
|
584
|
+
return this;
|
|
585
|
+
},
|
|
586
|
+
|
|
587
|
+
/* Return to the parent plot. */
|
|
588
|
+
end: function() {
|
|
589
|
+
return this._plot;
|
|
590
|
+
}
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
/* Default function to plot.
|
|
594
|
+
@param x (number) the input value
|
|
595
|
+
@return (number) the same value */
|
|
596
|
+
function identity(x) {
|
|
597
|
+
return x;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/* Details about each plot axis.
|
|
601
|
+
@param plot (SVGPlot) the owning plot
|
|
602
|
+
@param title (string) the title of the axis
|
|
603
|
+
@param min (number) the minimum value displayed on this axis
|
|
604
|
+
@param max (number) the maximum value displayed on this axis
|
|
605
|
+
@param major (number) the distance between major ticks
|
|
606
|
+
@param minor (number) the distance between minor ticks (optional)
|
|
607
|
+
@return (SVGPlotAxis) the new axis object */
|
|
608
|
+
function SVGPlotAxis(plot, title, min, max, major, minor) {
|
|
609
|
+
this._plot = plot; // The owning plot
|
|
610
|
+
this._title = title || ''; // The plot's title
|
|
611
|
+
this._titleFormat = {}; // Formatting settings for the title
|
|
612
|
+
this._titleOffset = 0; // The offset for positioning the title
|
|
613
|
+
this._labelFormat = {}; // Formatting settings for the labels
|
|
614
|
+
this._lineFormat = {stroke: 'black', strokeWidth: 1}; // Formatting settings for the axis lines
|
|
615
|
+
this._ticks = {major: major || 10, minor: minor || 0, size: 10, position: 'both'}; // Tick mark options
|
|
616
|
+
this._scale = {min: min || 0, max: max || 100}; // Axis scale settings
|
|
617
|
+
this._crossAt = 0; // Where this axis crosses the other one. */
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
$.extend(SVGPlotAxis.prototype, {
|
|
621
|
+
|
|
622
|
+
/* Set or retrieve the scale for this axis.
|
|
623
|
+
@param min (number) the minimum value shown
|
|
624
|
+
@param max (number) the maximum value shown
|
|
625
|
+
@return (SVGPlotAxis) this axis object or
|
|
626
|
+
(object) min and max values (if no parameters) */
|
|
627
|
+
scale: function(min, max) {
|
|
628
|
+
if (arguments.length == 0) {
|
|
629
|
+
return this._scale;
|
|
630
|
+
}
|
|
631
|
+
this._scale.min = min;
|
|
632
|
+
this._scale.max = max;
|
|
633
|
+
this._plot._drawPlot();
|
|
634
|
+
return this;
|
|
635
|
+
},
|
|
636
|
+
|
|
637
|
+
/* Set or retrieve the ticks for this axis.
|
|
638
|
+
@param major (number) the distance between major ticks
|
|
639
|
+
@param minor (number) the distance between minor ticks
|
|
640
|
+
@param size (number) the length of the major ticks (minor are half) (optional)
|
|
641
|
+
@param position (string) the location of the ticks:
|
|
642
|
+
'nw', 'se', 'both' (optional)
|
|
643
|
+
@return (SVGPlotAxis) this axis object or
|
|
644
|
+
(object) major, minor, size, and position values (if no parameters) */
|
|
645
|
+
ticks: function(major, minor, size, position) {
|
|
646
|
+
if (arguments.length == 0) {
|
|
647
|
+
return this._ticks;
|
|
648
|
+
}
|
|
649
|
+
if (typeof size == 'string') {
|
|
650
|
+
position = size;
|
|
651
|
+
size = null;
|
|
652
|
+
}
|
|
653
|
+
this._ticks.major = major;
|
|
654
|
+
this._ticks.minor = minor;
|
|
655
|
+
this._ticks.size = size || this._ticks.size;
|
|
656
|
+
this._ticks.position = position || this._ticks.position;
|
|
657
|
+
this._plot._drawPlot();
|
|
658
|
+
return this;
|
|
659
|
+
},
|
|
660
|
+
|
|
661
|
+
/* Set or retrieve the title for this axis.
|
|
662
|
+
@param title (string) the title text
|
|
663
|
+
@param offset (number) the distance to offset the title position (optional)
|
|
664
|
+
@param colour (string) how to colour the title (optional)
|
|
665
|
+
@param format (object) formatting settings for the title (optional)
|
|
666
|
+
@return (SVGPlotAxis) this axis object or
|
|
667
|
+
(object) title, offset, and format values (if no parameters) */
|
|
668
|
+
title: function(title, offset, colour, format) {
|
|
669
|
+
if (arguments.length == 0) {
|
|
670
|
+
return {title: this._title, offset: this._titleOffset, format: this._titleFormat};
|
|
671
|
+
}
|
|
672
|
+
if (typeof offset != 'number') {
|
|
673
|
+
format = colour;
|
|
674
|
+
colour = offset;
|
|
675
|
+
offset = null;
|
|
676
|
+
}
|
|
677
|
+
if (typeof colour != 'string') {
|
|
678
|
+
format = colour;
|
|
679
|
+
colour = null;
|
|
680
|
+
}
|
|
681
|
+
this._title = title;
|
|
682
|
+
this._titleOffset = (offset != null ? offset : this._titleOffset);
|
|
683
|
+
if (colour || format) {
|
|
684
|
+
this._titleFormat = $.extend(format || {}, (colour ? {fill: colour} : {}));
|
|
685
|
+
}
|
|
686
|
+
this._plot._drawPlot();
|
|
687
|
+
return this;
|
|
688
|
+
},
|
|
689
|
+
|
|
690
|
+
/* Set or retrieve the label format for this axis.
|
|
691
|
+
@param colour (string) how to colour the labels (optional)
|
|
692
|
+
@param format (object) formatting settings for the labels (optional)
|
|
693
|
+
@return (SVGPlotAxis) this axis object or
|
|
694
|
+
(object) format values (if no parameters) */
|
|
695
|
+
format: function(colour, format) {
|
|
696
|
+
if (arguments.length == 0) {
|
|
697
|
+
return this._labelFormat;
|
|
698
|
+
}
|
|
699
|
+
if (typeof colour != 'string') {
|
|
700
|
+
format = colour;
|
|
701
|
+
colour = null;
|
|
702
|
+
}
|
|
703
|
+
this._labelFormat = $.extend(format || {}, (colour ? {fill: colour} : {}));
|
|
704
|
+
this._plot._drawPlot();
|
|
705
|
+
return this;
|
|
706
|
+
},
|
|
707
|
+
|
|
708
|
+
/* Set or retrieve the line formatting for this axis.
|
|
709
|
+
@param colour (string) the line's colour
|
|
710
|
+
@param width (number) the line's width (optional)
|
|
711
|
+
@param settings (object) additional formatting settings for the line (optional)
|
|
712
|
+
@return (SVGPlotAxis) this axis object or
|
|
713
|
+
(object) line formatting values (if no parameters) */
|
|
714
|
+
line: function(colour, width, settings) {
|
|
715
|
+
if (arguments.length == 0) {
|
|
716
|
+
return this._lineFormat;
|
|
717
|
+
}
|
|
718
|
+
if (typeof width != 'number') {
|
|
719
|
+
settings = width;
|
|
720
|
+
width = null;
|
|
721
|
+
}
|
|
722
|
+
$.extend(this._lineFormat, {stroke: colour, strokeWidth:
|
|
723
|
+
width || this._lineFormat.strokeWidth}, settings || {});
|
|
724
|
+
this._plot._drawPlot();
|
|
725
|
+
return this;
|
|
726
|
+
},
|
|
727
|
+
|
|
728
|
+
/* Return to the parent plot. */
|
|
729
|
+
end: function() {
|
|
730
|
+
return this._plot;
|
|
731
|
+
}
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
/* Details about the plot legend.
|
|
735
|
+
@param plot (SVGPlot) the owning plot
|
|
736
|
+
@param bgSettings (object) additional formatting settings for the legend background (optional)
|
|
737
|
+
@param textSettings (object) additional formatting settings for the legend text (optional)
|
|
738
|
+
@return (SVGPlotLegend) the new legend object */
|
|
739
|
+
function SVGPlotLegend(plot, bgSettings, textSettings) {
|
|
740
|
+
this._plot = plot; // The owning plot
|
|
741
|
+
this._show = true; // Show the legend?
|
|
742
|
+
this._area = [0.9, 0.1, 1.0, 0.9]; // The legend area: left, top, right, bottom,
|
|
743
|
+
// > 1 in pixels, <= 1 as proportion
|
|
744
|
+
this._sampleSize = 15; // Size of sample box
|
|
745
|
+
this._bgSettings = bgSettings || {stroke: 'gray'}; // Additional formatting settings for the legend background
|
|
746
|
+
this._textSettings = textSettings || {}; // Additional formatting settings for the text
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
$.extend(SVGPlotLegend.prototype, {
|
|
750
|
+
|
|
751
|
+
/* Set or retrieve whether the legend should be shown.
|
|
752
|
+
@param show (boolean) true to display it, false to hide it
|
|
753
|
+
@return (SVGPlotLegend) this legend object or
|
|
754
|
+
(boolean) show the legend? (if no parameters) */
|
|
755
|
+
show: function(show) {
|
|
756
|
+
if (arguments.length == 0) {
|
|
757
|
+
return this._show;
|
|
758
|
+
}
|
|
759
|
+
this._show = show;
|
|
760
|
+
this._plot._drawPlot();
|
|
761
|
+
return this;
|
|
762
|
+
},
|
|
763
|
+
|
|
764
|
+
/* Set or retrieve the legend area.
|
|
765
|
+
@param left (number) > 1 is pixels, <= 1 is proportion of width or
|
|
766
|
+
(number[4]) for left, top, right, bottom
|
|
767
|
+
@param top (number) > 1 is pixels, <= 1 is proportion of height
|
|
768
|
+
@param right (number) > 1 is pixels, <= 1 is proportion of width
|
|
769
|
+
@param bottom (number) > 1 is pixels, <= 1 is proportion of height
|
|
770
|
+
@return (SVGPlotLegend) this legend object or
|
|
771
|
+
(number[4]) the legend area: left, top, right, bottom (if no parameters) */
|
|
772
|
+
area: function(left, top, right, bottom) {
|
|
773
|
+
if (arguments.length == 0) {
|
|
774
|
+
return this._area;
|
|
775
|
+
}
|
|
776
|
+
this._area = (isArray(left) ? left : [left, top, right, bottom]);
|
|
777
|
+
this._plot._drawPlot();
|
|
778
|
+
return this;
|
|
779
|
+
},
|
|
780
|
+
|
|
781
|
+
/* Set or retrieve additional settings for the legend area.
|
|
782
|
+
@param sampleSize (number) the size of the sample box to display (optional)
|
|
783
|
+
@param bgSettings (object) additional formatting settings for the legend background
|
|
784
|
+
@param textSettings (object) additional formatting settings for the legend text (optional)
|
|
785
|
+
@return (SVGPlotLegend) this legend object or
|
|
786
|
+
(object) bgSettings and textSettings for the legend (if no parameters) */
|
|
787
|
+
settings: function(sampleSize, bgSettings, textSettings) {
|
|
788
|
+
if (arguments.length == 0) {
|
|
789
|
+
return {sampleSize: this._sampleSize, bgSettings: this._bgSettings,
|
|
790
|
+
textSettings: this._textSettings};
|
|
791
|
+
}
|
|
792
|
+
if (typeof sampleSize == 'object') {
|
|
793
|
+
textSettings = bgSettings;
|
|
794
|
+
bgSettings = sampleSize;
|
|
795
|
+
sampleSize = null;
|
|
796
|
+
}
|
|
797
|
+
this._sampleSize = sampleSize || this._sampleSize;
|
|
798
|
+
this._bgSettings = bgSettings;
|
|
799
|
+
this._textSettings = textSettings || this._textSettings;
|
|
800
|
+
this._plot._drawPlot();
|
|
801
|
+
return this;
|
|
802
|
+
},
|
|
803
|
+
|
|
804
|
+
/* Return to the parent plot. */
|
|
805
|
+
end: function() {
|
|
806
|
+
return this._plot;
|
|
807
|
+
}
|
|
808
|
+
});
|
|
809
|
+
|
|
810
|
+
//==============================================================================
|
|
811
|
+
|
|
812
|
+
/* Determine whether an object is an array. */
|
|
813
|
+
function isArray(a) {
|
|
814
|
+
return (a && a.constructor == Array);
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
})(jQuery)
|