first commit
[wnstats.git] / public / javascripts / jqplot / plugins / jqplot.logAxisRenderer.js
blob8d358ec7f5fef112f1d2ae31f73e99bbb16e5631
1 /**
2  * jqPlot
3  * Pure JavaScript plotting plugin using jQuery
4  *
5  * Version: 1.0.4
6  * Revision: 1121
7  *
8  * Copyright (c) 2009-2012 Chris Leonello
9  * jqPlot is currently available for use in all personal or commercial projects 
10  * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL 
11  * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can 
12  * choose the license that best suits your project and use it accordingly. 
13  *
14  * Although not required, the author would appreciate an email letting him 
15  * know of any substantial use of jqPlot.  You can reach the author at: 
16  * chris at jqplot dot com or see http://www.jqplot.com/info.php .
17  *
18  * If you are feeling kind and generous, consider supporting the project by
19  * making a donation at: http://www.jqplot.com/donate.php .
20  *
21  * sprintf functions contained in jqplot.sprintf.js by Ash Searle:
22  *
23  *     version 2007.04.27
24  *     author Ash Searle
25  *     http://hexmen.com/blog/2007/03/printf-sprintf/
26  *     http://hexmen.com/js/sprintf.js
27  *     The author (Ash Searle) has placed this code in the public domain:
28  *     "This code is unrestricted: you are free to use it however you like."
29  * 
30  */
31 (function($) {
32     /**
33     *  class: $.jqplot.LogAxisRenderer
34     *  A plugin for a jqPlot to render a logarithmic axis.
35     * 
36     *  To use this renderer, include the plugin in your source
37     *  > <script type="text/javascript" language="javascript" src="plugins/jqplot.logAxisRenderer.js"></script>
38     *  
39     *  and supply the appropriate options to your plot
40     *  
41     *  > {axes:{xaxis:{renderer:$.jqplot.LogAxisRenderer}}}
42     **/ 
43     $.jqplot.LogAxisRenderer = function() {
44         $.jqplot.LinearAxisRenderer.call(this);
45         // prop: axisDefaults
46         // Default properties which will be applied directly to the series.
47         //
48         // Group: Properties
49         //
50         // Properties
51         //
52         // base - the logarithmic base, commonly 2, 10 or Math.E
53         // tickDistribution - Deprecated.  "power" distribution of ticks
54         // always used.  Option has no effect.
55         this.axisDefaults = {
56             base : 10,
57             tickDistribution :'power'
58         };
59     };
60     
61     $.jqplot.LogAxisRenderer.prototype = new $.jqplot.LinearAxisRenderer();
62     $.jqplot.LogAxisRenderer.prototype.constructor = $.jqplot.LogAxisRenderer;
63     
64     $.jqplot.LogAxisRenderer.prototype.init = function(options) {
65         // prop: drawBaseline
66         // True to draw the axis baseline.
67         this.drawBaseline = true;
68         // prop: minorTicks
69         // Number of ticks to add between "major" ticks.
70         // Major ticks are ticks supplied by user or auto computed.
71         // Minor ticks cannot be created by user.
72         this.minorTicks = 'auto';
73         this._scalefact = 1.0;
75         $.extend(true, this, options);
77         this._autoFormatString = '%d';
78         this._overrideFormatString = false;
80         for (var d in this.renderer.axisDefaults) {
81             if (this[d] == null) {
82                 this[d] = this.renderer.axisDefaults[d];
83             }
84         }
86         this.resetDataBounds();
87     };
88     
89     $.jqplot.LogAxisRenderer.prototype.createTicks = function(plot) {
90         // we're are operating on an axis here
91         var ticks = this._ticks;
92         var userTicks = this.ticks;
93         var name = this.name;
94         var db = this._dataBounds;
95         var dim = (this.name.charAt(0) === 'x') ? this._plotDimensions.width : this._plotDimensions.height;
96         var interval;
97         var min, max;
98         var pos1, pos2;
99         var tt, i;
101         var threshold = 30;
102         // For some reason scalefactor is screwing up ticks.
103         this._scalefact =  (Math.max(dim, threshold+1) - threshold)/300;
105         // if we already have ticks, use them.
106         // ticks must be in order of increasing value.
107         if (userTicks.length) {
108             // ticks could be 1D or 2D array of [val, val, ,,,] or [[val, label], [val, label], ...] or mixed
109             for (i=0; i<userTicks.length; i++){
110                 var ut = userTicks[i];
111                 var t = new this.tickRenderer(this.tickOptions);
112                 if (ut.constructor == Array) {
113                     t.value = ut[0];
114                     t.label = ut[1];
115                     if (!this.showTicks) {
116                         t.showLabel = false;
117                         t.showMark = false;
118                     }
119                     else if (!this.showTickMarks) {
120                         t.showMark = false;
121                     }
122                     t.setTick(ut[0], this.name);
123                     this._ticks.push(t);
124                 }
126                 else if ($.isPlainObject(ut)) {
127                     $.extend(true, t, ut);
128                     t.axis = this.name;
129                     this._ticks.push(t);
130                 }
131                 
132                 else {
133                     t.value = ut;
134                     if (!this.showTicks) {
135                         t.showLabel = false;
136                         t.showMark = false;
137                     }
138                     else if (!this.showTickMarks) {
139                         t.showMark = false;
140                     }
141                     t.setTick(ut, this.name);
142                     this._ticks.push(t);
143                 }
144             }
145             this.numberTicks = userTicks.length;
146             this.min = this._ticks[0].value;
147             this.max = this._ticks[this.numberTicks-1].value;
148         }
149         
150         // we don't have any ticks yet, let's make some!
151         else if (this.min == null && this.max == null) {
152             min = db.min * (2 - this.padMin);
153             max = db.max * this.padMax;
154             
155             // if min and max are same, space them out a bit
156             if (min == max) {
157                 var adj = 0.05;
158                 min = min*(1-adj);
159                 max = max*(1+adj);
160             }
161             
162             // perform some checks
163             if (this.min != null && this.min <= 0) {
164                 throw('log axis minimum must be greater than 0');
165             }
166             if (this.max != null && this.max <= 0) {
167                 throw('log axis maximum must be greater than 0');
168             }
170             function findCeil (val) {
171                 var order = Math.pow(10, Math.floor(Math.log(val)/Math.LN10));
172                 return Math.ceil(val/order) * order;
173             }
175             function findFloor(val) {
176                 var order = Math.pow(10, Math.floor(Math.log(val)/Math.LN10));
177                 return Math.floor(val/order) * order;
178             }
180             // var range = max - min;
181             var rmin, rmax;
183             // for power distribution, open up range to get a nice power of axis.renderer.base.
184             // power distribution won't respect the user's min/max settings.
185             rmin = Math.pow(this.base, Math.floor(Math.log(min)/Math.log(this.base)));
186             rmax = Math.pow(this.base, Math.ceil(Math.log(max)/Math.log(this.base)));
188             // // if min and max are same, space them out a bit
189             // if (rmin === rmax) {
190             //     var adj = 0.05;
191             //     rmin = rmin*(1-adj);
192             //     rmax = rmax*(1+adj);
193             // }
195             var order = Math.round(Math.log(rmin)/Math.LN10);
197             if (this.tickOptions == null || !this.tickOptions.formatString) {
198                 this._overrideFormatString = true;
199             }
201             this.min = rmin;
202             this.max = rmax;
203             var range = this.max - this.min;            
205             var minorTicks = (this.minorTicks === 'auto') ? 0 : this.minorTicks;
206             var numberTicks;
207             if (this.numberTicks == null){
208                 if (dim > 140) {
209                     numberTicks = Math.round(Math.log(this.max/this.min)/Math.log(this.base) + 1);
210                     if (numberTicks < 2) {
211                         numberTicks = 2;
212                     }
213                     if (minorTicks === 0) {
214                         var temp = dim/(numberTicks - 1);
215                         if (temp < 100) {
216                             minorTicks = 0;
217                         }
218                         else if (temp < 190) {
219                             minorTicks = 1;
220                         }
221                         else if (temp < 250) {
222                             minorTicks = 3;
223                         }
224                         else if (temp < 600) {
225                             minorTicks = 4;
226                         }
227                         else {
228                             minorTicks = 9;
229                         }
230                     }
231                 }
232                 else {
233                     numberTicks = 2;
234                     if (minorTicks === 0) {
235                         minorTicks = 1;
236                     }
237                     minorTicks = 0;
238                 }
239             }
240             else {
241                 numberTicks = this.numberTicks;
242             }
244             if (order >= 0 && minorTicks !== 3) {
245                 this._autoFormatString = '%d';
246             }
247             // Adjust format string for case with 3 ticks where we'll have like 1, 2.5, 5, 7.5, 10
248             else if (order <= 0 && minorTicks === 3) {
249                 var temp = -(order - 1);
250                 this._autoFormatString = '%.'+ Math.abs(order-1) + 'f';
251             }
253             // Adjust format string for values less than 1.
254             else if (order < 0) {
255                 var temp = -order;
256                 this._autoFormatString = '%.'+ Math.abs(order) + 'f';
257             }
259             else {
260                 this._autoFormatString = '%d';
261             }
263             var to, t, val, tt1, spread, interval;
264             for (var i=0; i<numberTicks; i++){
265                 tt = Math.pow(this.base, i - numberTicks + 1) * this.max;
267                 t = new this.tickRenderer(this.tickOptions);
268             
269                 if (this._overrideFormatString) {
270                     t.formatString = this._autoFormatString;
271                 }
272                 
273                 if (!this.showTicks) {
274                     t.showLabel = false;
275                     t.showMark = false;
276                 }
277                 else if (!this.showTickMarks) {
278                     t.showMark = false;
279                 }
280                 t.setTick(tt, this.name);
281                 this._ticks.push(t);
283                 if (minorTicks && i<numberTicks-1) {
284                     tt1 = Math.pow(this.base, i - numberTicks + 2) * this.max;
285                     spread = tt1 - tt;
286                     interval = tt1 / (minorTicks+1);
287                     for (var j=minorTicks-1; j>=0; j--) {
288                         val = tt1-interval*(j+1);
289                         t = new this.tickRenderer(this.tickOptions);
290             
291                         if (this._overrideFormatString && this._autoFormatString != '') {
292                             t.formatString = this._autoFormatString;
293                         }
294                         if (!this.showTicks) {
295                             t.showLabel = false;
296                             t.showMark = false;
297                         }
298                         else if (!this.showTickMarks) {
299                             t.showMark = false;
300                         }
301                         t.setTick(val, this.name);
302                         this._ticks.push(t);
303                     }
304                 }       
305             }     
306         }
308         // min and max are set as would be the case with zooming
309         else if (this.min != null && this.max != null) {
310             var opts = $.extend(true, {}, this.tickOptions, {name: this.name, value: null});
311             var nt, ti;
312             // don't have an interval yet, pick one that gives the most
313             // "round" ticks we can get.
314             if (this.numberTicks == null && this.tickInterval == null) {
315                 // var threshold = 30;
316                 var tdim = Math.max(dim, threshold+1);
317                 var nttarget =  Math.ceil((tdim-threshold)/35 + 1);
319                 var ret = $.jqplot.LinearTickGenerator.bestConstrainedInterval(this.min, this.max, nttarget);
321                 this._autoFormatString = ret[3];
322                 nt = ret[2];
323                 ti = ret[4];
325                 for (var i=0; i<nt; i++) {
326                     opts.value = this.min + i * ti;
327                     t = new this.tickRenderer(opts);
328                     
329                     if (this._overrideFormatString && this._autoFormatString != '') {
330                         t.formatString = this._autoFormatString;
331                     }
332                     if (!this.showTicks) {
333                         t.showLabel = false;
334                         t.showMark = false;
335                     }
336                     else if (!this.showTickMarks) {
337                         t.showMark = false;
338                     }
339                     this._ticks.push(t);
340                 }
341             }
343             // for loose zoom, number ticks and interval are also set.
344             else if (this.numberTicks != null && this.tickInterval != null) {
345                 nt = this.numberTicks;
346                 for (var i=0; i<nt; i++) {
347                     opts.value = this.min + i * this.tickInterval;
348                     t = new this.tickRenderer(opts);
349                     
350                     if (this._overrideFormatString && this._autoFormatString != '') {
351                         t.formatString = this._autoFormatString;
352                     }
353                     if (!this.showTicks) {
354                         t.showLabel = false;
355                         t.showMark = false;
356                     }
357                     else if (!this.showTickMarks) {
358                         t.showMark = false;
359                     }
360                     this._ticks.push(t);
361                 }
362             }
363         }
364     };
365     
366     $.jqplot.LogAxisRenderer.prototype.pack = function(pos, offsets) {
367         var lb = parseInt(this.base, 10);
368         var ticks = this._ticks;
369         var trans = function (v) { return Math.log(v)/Math.log(lb); };
370         var invtrans = function (v) { return Math.pow(Math.E, (Math.log(lb)*v)); };
371         var max = trans(this.max);
372         var min = trans(this.min);
373         var offmax = offsets.max;
374         var offmin = offsets.min;
375         var lshow = (this._label == null) ? false : this._label.show;
376         
377         for (var p in pos) {
378             this._elem.css(p, pos[p]);
379         }
380         
381         this._offsets = offsets;
382         // pixellength will be + for x axes and - for y axes becasue pixels always measured from top left.
383         var pixellength = offmax - offmin;
384         var unitlength = max - min;
385         
386         // point to unit and unit to point conversions references to Plot DOM element top left corner.
387         this.p2u = function(p){
388             return invtrans((p - offmin) * unitlength / pixellength + min);
389         };
390         
391         this.u2p = function(u){
392             return (trans(u) - min) * pixellength / unitlength + offmin;
393         };
394         
395         if (this.name == 'xaxis' || this.name == 'x2axis'){
396             this.series_u2p = function(u){
397                 return (trans(u) - min) * pixellength / unitlength;
398             };
399             this.series_p2u = function(p){
400                 return invtrans(p * unitlength / pixellength + min);
401             };
402         }
403         // yaxis is max at top of canvas.
404         else {
405             this.series_u2p = function(u){
406                 return (trans(u) - max) * pixellength / unitlength;
407             };
408             this.series_p2u = function(p){
409                 return invtrans(p * unitlength / pixellength + max);
410             };
411         }
412         
413         if (this.show) {
414             if (this.name == 'xaxis' || this.name == 'x2axis') {
415                 for (var i=0; i<ticks.length; i++) {
416                     var t = ticks[i];
417                     if (t.show && t.showLabel) {
418                         var shim;
419                         
420                         if (t.constructor == $.jqplot.CanvasAxisTickRenderer && t.angle) {
421                             switch (t.labelPosition) {
422                                 case 'auto':
423                                     // position at end
424                                     if (t.angle < 0) {
425                                         shim = -t.getWidth() + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
426                                     }
427                                     // position at start
428                                     else {
429                                         shim = -t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2;
430                                     }
431                                     break;
432                                 case 'end':
433                                     shim = -t.getWidth() + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
434                                     break;
435                                 case 'start':
436                                     shim = -t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2;
437                                     break;
438                                 case 'middle':
439                                     shim = -t.getWidth()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
440                                     break;
441                                 default:
442                                     shim = -t.getWidth()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
443                                     break;
444                             }
445                         }
446                         else {
447                             shim = -t.getWidth()/2;
448                         }
449                         // var shim = t.getWidth()/2;
450                         var val = this.u2p(t.value) + shim + 'px';
451                         t._elem.css('left', val);
452                         t.pack();
453                     }
454                 }
455                 if (lshow) {
456                     var w = this._label._elem.outerWidth(true);
457                     this._label._elem.css('left', offmin + pixellength/2 - w/2 + 'px');
458                     if (this.name == 'xaxis') {
459                         this._label._elem.css('bottom', '0px');
460                     }
461                     else {
462                         this._label._elem.css('top', '0px');
463                     }
464                     this._label.pack();
465                 }
466             }
467             else {
468                 for (var i=0; i<ticks.length; i++) {
469                     var t = ticks[i];
470                     if (t.show && t.showLabel) {                        
471                         var shim;
472                         if (t.constructor == $.jqplot.CanvasAxisTickRenderer && t.angle) {
473                             switch (t.labelPosition) {
474                                 case 'auto':
475                                     // position at end
476                                 case 'end':
477                                     if (t.angle < 0) {
478                                         shim = -t._textRenderer.height * Math.cos(-t._textRenderer.angle) / 2;
479                                     }
480                                     else {
481                                         shim = -t.getHeight() + t._textRenderer.height * Math.cos(t._textRenderer.angle) / 2;
482                                     }
483                                     break;
484                                 case 'start':
485                                     if (t.angle > 0) {
486                                         shim = -t._textRenderer.height * Math.cos(-t._textRenderer.angle) / 2;
487                                     }
488                                     else {
489                                         shim = -t.getHeight() + t._textRenderer.height * Math.cos(t._textRenderer.angle) / 2;
490                                     }
491                                     break;
492                                 case 'middle':
493                                     // if (t.angle > 0) {
494                                     //     shim = -t.getHeight()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
495                                     // }
496                                     // else {
497                                     //     shim = -t.getHeight()/2 - t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2;
498                                     // }
499                                     shim = -t.getHeight()/2;
500                                     break;
501                                 default:
502                                     shim = -t.getHeight()/2;
503                                     break;
504                             }
505                         }
506                         else {
507                             shim = -t.getHeight()/2;
508                         }
509                         
510                         var val = this.u2p(t.value) + shim + 'px';
511                         t._elem.css('top', val);
512                         t.pack();
513                     }
514                 }
515                 if (lshow) {
516                     var h = this._label._elem.outerHeight(true);
517                     this._label._elem.css('top', offmax - pixellength/2 - h/2 + 'px');
518                     if (this.name == 'yaxis') {
519                         this._label._elem.css('left', '0px');
520                     }
521                     else {
522                         this._label._elem.css('right', '0px');
523                     }   
524                     this._label.pack();
525                 }
526             }
527         }        
528     };
529 })(jQuery);