756
|
1 /* http://keith-wood.name/svg.html
|
1151
|
2 SVG plotting extension for jQuery v1.4.5.
|
756
|
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)
|