Free cookie consent management tool by TermsFeed Policy Generator

source: branches/HiveStatistics/sources/HeuristicLab.Services.Hive.Statistics/3.3/Scripts/jqPlot/plugins/jqplot.funnelRenderer.js @ 9646

Last change on this file since 9646 was 9617, checked in by pfleck, 12 years ago

#2063:
Started integrating Hive statistics into statistics web project.
Added jqPlot library for charting.

File size: 38.7 KB
Line 
1/**
2 * jqPlot
3 * Pure JavaScript plotting plugin using jQuery
4 *
5 * Version: 1.0.0b2_r1012
6 *
7 * Copyright (c) 2009-2011 Chris Leonello
8 * jqPlot is currently available for use in all personal or commercial projects
9 * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL
10 * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can
11 * choose the license that best suits your project and use it accordingly.
12 *
13 * Although not required, the author would appreciate an email letting him
14 * know of any substantial use of jqPlot.  You can reach the author at:
15 * chris at jqplot dot com or see http://www.jqplot.com/info.php .
16 *
17 * If you are feeling kind and generous, consider supporting the project by
18 * making a donation at: http://www.jqplot.com/donate.php .
19 *
20 * sprintf functions contained in jqplot.sprintf.js by Ash Searle:
21 *
22 *     version 2007.04.27
23 *     author Ash Searle
24 *     http://hexmen.com/blog/2007/03/printf-sprintf/
25 *     http://hexmen.com/js/sprintf.js
26 *     The author (Ash Searle) has placed this code in the public domain:
27 *     "This code is unrestricted: you are free to use it however you like."
28 *
29 */
30(function($) {
31    /**
32     * Class: $.jqplot.FunnelRenderer
33     * Plugin renderer to draw a funnel chart.
34     * x values, if present, will be used as labels.
35     * y values give area size.
36     *
37     * Funnel charts will draw a single series
38     * only.
39     *
40     * To use this renderer, you need to include the
41     * funnel renderer plugin, for example:
42     *
43     * > <script type="text/javascript" src="plugins/jqplot.funnelRenderer.js"></script>
44     *
45     * Properties described here are passed into the $.jqplot function
46     * as options on the series renderer.  For example:
47     *
48     * > plot2 = $.jqplot('chart2', [s1, s2], {
49     * >     seriesDefaults: {
50     * >         renderer:$.jqplot.FunnelRenderer,
51     * >         rendererOptions:{
52     * >              sectionMargin: 12,
53     * >              widthRatio: 0.3
54     * >          }
55     * >      }
56     * > });
57     *
58     * IMPORTANT
59     *
60     * *The funnel renderer will reorder data in descending order* so the largest value in
61     * the data set is first and displayed on top of the funnel.  Data will then
62     * be displayed in descending order down the funnel.  The area of each funnel
63     * section will correspond to the value of each data point relative to the sum
64     * of all values.  That is section area is proportional to section value divided by
65     * sum of all section values.
66     *
67     * If your data is not in descending order when passed into the plot, *it will be
68     * reordered* when stored in the series.data property.  A copy of the unordered
69     * data is kept in the series._unorderedData property.
70     *
71     * A funnel plot will trigger events on the plot target
72     * according to user interaction.  All events return the event object,
73     * the series index, the point (section) index, and the point data for
74     * the appropriate section. *Note* the point index will referr to the ordered
75     * data, not the original unordered data.
76     *
77     * 'jqplotDataMouseOver' - triggered when mousing over a section.
78     * 'jqplotDataHighlight' - triggered the first time user mouses over a section,
79     * if highlighting is enabled.
80     * 'jqplotDataUnhighlight' - triggered when a user moves the mouse out of
81     * a highlighted section.
82     * 'jqplotDataClick' - triggered when the user clicks on a section.
83     * 'jqplotDataRightClick' - tiggered when the user right clicks on a section if
84     * the "captureRightClick" option is set to true on the plot.
85     */
86    $.jqplot.FunnelRenderer = function(){
87        $.jqplot.LineRenderer.call(this);
88    };
89   
90    $.jqplot.FunnelRenderer.prototype = new $.jqplot.LineRenderer();
91    $.jqplot.FunnelRenderer.prototype.constructor = $.jqplot.FunnelRenderer;
92   
93    // called with scope of a series
94    $.jqplot.FunnelRenderer.prototype.init = function(options, plot) {
95        // Group: Properties
96        //
97        // prop: padding
98        // padding between the funnel and plot edges, legend, etc.
99        this.padding = {top: 20, right: 20, bottom: 20, left: 20};
100        // prop: sectionMargin
101        // spacing between funnel sections in pixels.
102        this.sectionMargin = 6;
103        // prop: fill
104        // true or false, wether to fill the areas.
105        this.fill = true;
106        // prop: shadowOffset
107        // offset of the shadow from the area and offset of
108        // each succesive stroke of the shadow from the last.
109        this.shadowOffset = 2;
110        // prop: shadowAlpha
111        // transparency of the shadow (0 = transparent, 1 = opaque)
112        this.shadowAlpha = 0.07;
113        // prop: shadowDepth
114        // number of strokes to apply to the shadow,
115        // each stroke offset shadowOffset from the last.
116        this.shadowDepth = 5;
117        // prop: highlightMouseOver
118        // True to highlight area when moused over.
119        // This must be false to enable highlightMouseDown to highlight when clicking on a area.
120        this.highlightMouseOver = true;
121        // prop: highlightMouseDown
122        // True to highlight when a mouse button is pressed over a area.
123        // This will be disabled if highlightMouseOver is true.
124        this.highlightMouseDown = false;
125        // prop: highlightColors
126        // array of colors to use when highlighting an area.
127        this.highlightColors = [];
128        // prop: widthRatio
129        // The ratio of the width of the top of the funnel to the bottom.
130        // a ratio of 0 will make an upside down pyramid.
131        this.widthRatio = 0.2;
132        // prop: lineWidth
133        // width of line if areas are stroked and not filled.
134        this.lineWidth = 2;
135        // prop: dataLabels
136        // Either 'label', 'value', 'percent' or an array of labels to place on the pie slices.
137        // Defaults to percentage of each pie slice.
138        this.dataLabels = 'percent';
139        // prop: showDataLabels
140        // true to show data labels on slices.
141        this.showDataLabels = false;
142        // prop: dataLabelFormatString
143        // Format string for data labels.  If none, '%s' is used for "label" and for arrays, '%d' for value and '%d%%' for percentage.
144        this.dataLabelFormatString = null;
145        // prop: dataLabelThreshold
146        // Threshhold in percentage (0 - 100) of pie area, below which no label will be displayed.
147        // This applies to all label types, not just to percentage labels.
148        this.dataLabelThreshold = 3;
149        this._type = 'funnel';
150       
151        this.tickRenderer = $.jqplot.FunnelTickRenderer;
152       
153        // if user has passed in highlightMouseDown option and not set highlightMouseOver, disable highlightMouseOver
154        if (options.highlightMouseDown && options.highlightMouseOver == null) {
155            options.highlightMouseOver = false;
156        }
157       
158        $.extend(true, this, options);
159       
160        // index of the currenty highlighted point, if any
161        this._highlightedPoint = null;
162       
163        // lengths of bases, or horizontal sides of areas of trapezoid.
164        this._bases = [];
165        // total area
166        this._atot;
167        // areas of segments.
168        this._areas = [];
169        // vertical lengths of segments.
170        this._lengths = [];
171        // angle of the funnel to vertical.
172        this._angle;
173        this._dataIndices = [];
174       
175        // sort data
176        this._unorderedData = $.extend(true, [], this.data);
177        var idxs = $.extend(true, [], this.data);
178        for (var i=0; i<idxs.length; i++) {
179            idxs[i].push(i);
180        }
181        this.data.sort( function (a, b) { return b[1] - a[1]; } );
182        idxs.sort( function (a, b) { return b[1] - a[1]; });
183        for (var i=0; i<idxs.length; i++) {
184            this._dataIndices.push(idxs[i][2]);
185        }
186       
187        // set highlight colors if none provided
188        if (this.highlightColors.length == 0) {
189            for (var i=0; i<this.seriesColors.length; i++){
190                var rgba = $.jqplot.getColorComponents(this.seriesColors[i]);
191                var newrgb = [rgba[0], rgba[1], rgba[2]];
192                var sum = newrgb[0] + newrgb[1] + newrgb[2];
193                for (var j=0; j<3; j++) {
194                    // when darkening, lowest color component can be is 60.
195                    newrgb[j] = (sum > 570) ?  newrgb[j] * 0.8 : newrgb[j] + 0.4 * (255 - newrgb[j]);
196                    newrgb[j] = parseInt(newrgb[j], 10);
197                }
198                this.highlightColors.push('rgb('+newrgb[0]+','+newrgb[1]+','+newrgb[2]+')');
199            }
200        }
201
202        plot.postParseOptionsHooks.addOnce(postParseOptions);
203        plot.postInitHooks.addOnce(postInit);
204        plot.eventListenerHooks.addOnce('jqplotMouseMove', handleMove);
205        plot.eventListenerHooks.addOnce('jqplotMouseDown', handleMouseDown);
206        plot.eventListenerHooks.addOnce('jqplotMouseUp', handleMouseUp);
207        plot.eventListenerHooks.addOnce('jqplotClick', handleClick);
208        plot.eventListenerHooks.addOnce('jqplotRightClick', handleRightClick);
209        plot.postDrawHooks.addOnce(postPlotDraw);       
210       
211    };
212   
213    // gridData will be of form [label, percentage of total]
214    $.jqplot.FunnelRenderer.prototype.setGridData = function(plot) {
215        // set gridData property.  This will hold angle in radians of each data point.
216        var sum = 0;
217        var td = [];
218        for (var i=0; i<this.data.length; i++){
219            sum += this.data[i][1];
220            td.push([this.data[i][0], this.data[i][1]]);
221        }
222       
223        // normalize y values, so areas are proportional.
224        for (var i=0; i<td.length; i++) {
225            td[i][1] = td[i][1]/sum;
226        }
227       
228        this._bases = new Array(td.length + 1);
229        this._lengths = new Array(td.length);
230       
231        this.gridData = td;
232    };
233   
234    $.jqplot.FunnelRenderer.prototype.makeGridData = function(data, plot) {
235        // set gridData property.  This will hold angle in radians of each data point.
236        var sum = 0;
237        var td = [];
238        for (var i=0; i<this.data.length; i++){
239            sum += this.data[i][1];
240            td.push([this.data[i][0], this.data[i][1]]);
241        }
242       
243        // normalize y values, so areas are proportional.
244        for (var i=0; i<td.length; i++) {
245            td[i][1] = td[i][1]/sum;
246        }
247       
248        this._bases = new Array(td.length + 1);
249        this._lengths = new Array(td.length);
250       
251        return td;
252    };
253   
254    $.jqplot.FunnelRenderer.prototype.drawSection = function (ctx, vertices, color, isShadow) {
255        var fill = this.fill;
256        var lineWidth = this.lineWidth;
257        ctx.save();
258       
259        if (isShadow) {
260            for (var i=0; i<this.shadowDepth; i++) {
261                ctx.save();
262                ctx.translate(this.shadowOffset*Math.cos(this.shadowAngle/180*Math.PI), this.shadowOffset*Math.sin(this.shadowAngle/180*Math.PI));
263                doDraw();
264            }
265        }
266       
267        else {
268            doDraw();
269        }
270       
271        function doDraw () {
272            ctx.beginPath(); 
273            ctx.fillStyle = color;
274            ctx.strokeStyle = color;
275            ctx.lineWidth = lineWidth;
276            ctx.moveTo(vertices[0][0], vertices[0][1]);
277            for (var i=1; i<4; i++) {
278                ctx.lineTo(vertices[i][0], vertices[i][1]);
279            }
280            ctx.closePath();
281            if (fill) {
282                ctx.fill();
283            }
284            else {
285                ctx.stroke();
286            }
287        }
288       
289        if (isShadow) {
290            for (var i=0; i<this.shadowDepth; i++) {
291                ctx.restore();
292            }
293        }
294       
295        ctx.restore();
296    };
297   
298    // called with scope of series
299    $.jqplot.FunnelRenderer.prototype.draw = function (ctx, gd, options, plot) {
300        var i;
301        var opts = (options != undefined) ? options : {};
302        // offset and direction of offset due to legend placement
303        var offx = 0;
304        var offy = 0;
305        var trans = 1;
306        this._areas = [];
307        // var colorGenerator = new this.colorGenerator(this.seriesColors);
308        if (options.legendInfo && options.legendInfo.placement == 'insideGrid') {
309            var li = options.legendInfo;
310            switch (li.location) {
311                case 'nw':
312                    offx = li.width + li.xoffset;
313                    break;
314                case 'w':
315                    offx = li.width + li.xoffset;
316                    break;
317                case 'sw':
318                    offx = li.width + li.xoffset;
319                    break;
320                case 'ne':
321                    offx = li.width + li.xoffset;
322                    trans = -1;
323                    break;
324                case 'e':
325                    offx = li.width + li.xoffset;
326                    trans = -1;
327                    break;
328                case 'se':
329                    offx = li.width + li.xoffset;
330                    trans = -1;
331                    break;
332                case 'n':
333                    offy = li.height + li.yoffset;
334                    break;
335                case 's':
336                    offy = li.height + li.yoffset;
337                    trans = -1;
338                    break;
339                default:
340                    break;
341            }
342        }
343       
344        var loff = (trans==1) ? this.padding.left + offx : this.padding.left;
345        var toff = (trans==1) ? this.padding.top + offy : this.padding.top;
346        var roff = (trans==-1) ? this.padding.right + offx : this.padding.right;
347        var boff = (trans==-1) ? this.padding.bottom + offy : this.padding.bottom;
348       
349        var shadow = (opts.shadow != undefined) ? opts.shadow : this.shadow;
350        var showLine = (opts.showLine != undefined) ? opts.showLine : this.showLine;
351        var fill = (opts.fill != undefined) ? opts.fill : this.fill;
352        var cw = ctx.canvas.width;
353        var ch = ctx.canvas.height;
354        this._bases[0] = cw - loff - roff;
355        var ltot = this._length = ch - toff - boff;
356
357        var hend = this._bases[0]*this.widthRatio;
358        this._atot = ltot/2 * (this._bases[0] + this._bases[0]*this.widthRatio);
359
360        this._angle = Math.atan((this._bases[0] - hend)/2/ltot);
361
362        for (i=0; i<gd.length; i++) {
363            this._areas.push(gd[i][1] * this._atot);
364        }
365
366       
367        var guess, err, count, lsum=0;
368        var tolerance = 0.0001;
369
370        for (i=0; i<this._areas.length; i++) {
371            guess = this._areas[i]/this._bases[i];
372            err = 999999;
373            this._lengths[i] = guess;
374            count = 0;
375            while (err > this._lengths[i]*tolerance && count < 100) {
376                this._lengths[i] = this._areas[i]/(this._bases[i] - this._lengths[i] * Math.tan(this._angle));
377                err = Math.abs(this._lengths[i] - guess);
378                this._bases[i+1] = this._bases[i] - (2*this._lengths[i]*Math.tan(this._angle));
379                guess = this._lengths[i];
380                count++;
381            }
382            lsum += this._lengths[i];
383        }
384       
385        // figure out vertices of each section
386        this._vertices = new Array(gd.length);
387       
388        // these are 4 coners of entire trapezoid
389        var p0 = [loff, toff],
390            p1 = [loff+this._bases[0], toff],
391            p2 = [loff + (this._bases[0] - this._bases[this._bases.length-1])/2, toff + this._length],
392            p3 = [p2[0] + this._bases[this._bases.length-1], p2[1]];
393           
394        // equations of right and left sides, returns x, y values given height of section (y value)
395        function findleft (l) {
396            var m = (p0[1] - p2[1])/(p0[0] - p2[0]);
397            var b = p0[1] - m*p0[0];
398            var y = l + p0[1];
399           
400            return [(y - b)/m, y];
401        }
402       
403        function findright (l) {
404            var m = (p1[1] - p3[1])/(p1[0] - p3[0]);
405            var b = p1[1] - m*p1[0];
406            var y = l + p1[1];
407           
408            return [(y - b)/m, y];
409        }
410       
411        var x = offx, y = offy;
412        var h=0, adj=0;
413       
414        for (i=0; i<gd.length; i++) {
415            this._vertices[i] = new Array();
416            var v = this._vertices[i];
417            var sm = this.sectionMargin;
418            if (i == 0) {
419                adj = 0;
420            }
421            if (i == 1) {
422                adj = sm/3;
423            }
424            else if (i > 0 && i < gd.length-1) {
425                adj = sm/2;
426            }
427            else if (i == gd.length -1) {
428                adj = 2*sm/3;
429            }
430            v.push(findleft(h+adj));
431            v.push(findright(h+adj));
432            h += this._lengths[i];
433            if (i == 0) {
434                adj = -2*sm/3;
435            }
436            else if (i > 0 && i < gd.length-1) {
437                adj = -sm/2;
438            }
439            else if (i == gd.length - 1) {
440                adj = 0;
441            }
442            v.push(findright(h+adj));
443            v.push(findleft(h+adj));
444           
445        }
446
447        if (this.shadow) {
448            var shadowColor = 'rgba(0,0,0,'+this.shadowAlpha+')';
449            for (var i=0; i<gd.length; i++) {
450                this.renderer.drawSection.call (this, ctx, this._vertices[i], shadowColor, true);
451            }
452           
453        }
454        for (var i=0; i<gd.length; i++) {
455            var v = this._vertices[i];
456            this.renderer.drawSection.call (this, ctx, v, this.seriesColors[i]);
457           
458            if (this.showDataLabels && gd[i][1]*100 >= this.dataLabelThreshold) {
459                var fstr, label;
460               
461                if (this.dataLabels == 'label') {
462                    fstr = this.dataLabelFormatString || '%s';
463                    label = $.jqplot.sprintf(fstr, gd[i][0]);
464                }
465                else if (this.dataLabels == 'value') {
466                    fstr = this.dataLabelFormatString || '%d';
467                    label = $.jqplot.sprintf(fstr, this.data[i][1]);
468                }
469                else if (this.dataLabels == 'percent') {
470                    fstr = this.dataLabelFormatString || '%d%%';
471                    label = $.jqplot.sprintf(fstr, gd[i][1]*100);
472                }
473                else if (this.dataLabels.constructor == Array) {
474                    fstr = this.dataLabelFormatString || '%s';
475                    label = $.jqplot.sprintf(fstr, this.dataLabels[this._dataIndices[i]]);
476                }
477               
478                var fact = (this._radius ) * this.dataLabelPositionFactor + this.sliceMargin + this.dataLabelNudge;
479               
480                var x = (v[0][0] + v[1][0])/2 + this.canvas._offsets.left;
481                var y = (v[1][1] + v[2][1])/2 + this.canvas._offsets.top;
482               
483                var labelelem = $('<span class="jqplot-funnel-series jqplot-data-label" style="position:absolute;">' + label + '</span>').insertBefore(plot.eventCanvas._elem);
484                x -= labelelem.width()/2;
485                y -= labelelem.height()/2;
486                x = Math.round(x);
487                y = Math.round(y);
488                labelelem.css({left: x, top: y});
489            }
490           
491        }
492               
493    };
494   
495    $.jqplot.FunnelAxisRenderer = function() {
496        $.jqplot.LinearAxisRenderer.call(this);
497    };
498   
499    $.jqplot.FunnelAxisRenderer.prototype = new $.jqplot.LinearAxisRenderer();
500    $.jqplot.FunnelAxisRenderer.prototype.constructor = $.jqplot.FunnelAxisRenderer;
501       
502   
503    // There are no traditional axes on a funnel chart.  We just need to provide
504    // dummy objects with properties so the plot will render.
505    // called with scope of axis object.
506    $.jqplot.FunnelAxisRenderer.prototype.init = function(options){
507        //
508        this.tickRenderer = $.jqplot.FunnelTickRenderer;
509        $.extend(true, this, options);
510        // I don't think I'm going to need _dataBounds here.
511        // have to go Axis scaling in a way to fit chart onto plot area
512        // and provide u2p and p2u functionality for mouse cursor, etc.
513        // for convienence set _dataBounds to 0 and 100 and
514        // set min/max to 0 and 100.
515        this._dataBounds = {min:0, max:100};
516        this.min = 0;
517        this.max = 100;
518        this.showTicks = false;
519        this.ticks = [];
520        this.showMark = false;
521        this.show = false;
522    };
523   
524   
525   
526    /**
527     * Class: $.jqplot.FunnelLegendRenderer
528     * Legend Renderer specific to funnel plots.  Set by default
529     * when the user creates a funnel plot.
530     */
531    $.jqplot.FunnelLegendRenderer = function(){
532        $.jqplot.TableLegendRenderer.call(this);
533    };
534   
535    $.jqplot.FunnelLegendRenderer.prototype = new $.jqplot.TableLegendRenderer();
536    $.jqplot.FunnelLegendRenderer.prototype.constructor = $.jqplot.FunnelLegendRenderer;
537   
538    $.jqplot.FunnelLegendRenderer.prototype.init = function(options) {
539        // Group: Properties
540        //
541        // prop: numberRows
542        // Maximum number of rows in the legend.  0 or null for unlimited.
543        this.numberRows = null;
544        // prop: numberColumns
545        // Maximum number of columns in the legend.  0 or null for unlimited.
546        this.numberColumns = null;
547        $.extend(true, this, options);
548    };
549   
550    // called with context of legend
551    $.jqplot.FunnelLegendRenderer.prototype.draw = function() {
552        var legend = this;
553        if (this.show) {
554            var series = this._series;
555            var ss = 'position:absolute;';
556            ss += (this.background) ? 'background:'+this.background+';' : '';
557            ss += (this.border) ? 'border:'+this.border+';' : '';
558            ss += (this.fontSize) ? 'font-size:'+this.fontSize+';' : '';
559            ss += (this.fontFamily) ? 'font-family:'+this.fontFamily+';' : '';
560            ss += (this.textColor) ? 'color:'+this.textColor+';' : '';
561            ss += (this.marginTop != null) ? 'margin-top:'+this.marginTop+';' : '';
562            ss += (this.marginBottom != null) ? 'margin-bottom:'+this.marginBottom+';' : '';
563            ss += (this.marginLeft != null) ? 'margin-left:'+this.marginLeft+';' : '';
564            ss += (this.marginRight != null) ? 'margin-right:'+this.marginRight+';' : '';
565            this._elem = $('<table class="jqplot-table-legend" style="'+ss+'"></table>');
566            // Funnel charts legends don't go by number of series, but by number of data points
567            // in the series.  Refactor things here for that.
568           
569            var pad = false,
570                reverse = false,
571                nr, nc;
572            var s = series[0];
573            var colorGenerator = new $.jqplot.ColorGenerator(s.seriesColors);
574           
575            if (s.show) {
576                var pd = s.data;
577                if (this.numberRows) {
578                    nr = this.numberRows;
579                    if (!this.numberColumns){
580                        nc = Math.ceil(pd.length/nr);
581                    }
582                    else{
583                        nc = this.numberColumns;
584                    }
585                }
586                else if (this.numberColumns) {
587                    nc = this.numberColumns;
588                    nr = Math.ceil(pd.length/this.numberColumns);
589                }
590                else {
591                    nr = pd.length;
592                    nc = 1;
593                }
594               
595                var i, j, tr, td1, td2, lt, rs, color;
596                var idx = 0;   
597               
598                for (i=0; i<nr; i++) {
599                    if (reverse){
600                        tr = $('<tr class="jqplot-table-legend"></tr>').prependTo(this._elem);
601                    }
602                    else{
603                        tr = $('<tr class="jqplot-table-legend"></tr>').appendTo(this._elem);
604                    }
605                    for (j=0; j<nc; j++) {
606                        if (idx < pd.length){
607                            lt = this.labels[idx] || pd[idx][0].toString();
608                            color = colorGenerator.next();
609                            if (!reverse){
610                                if (i>0){
611                                    pad = true;
612                                }
613                                else{
614                                    pad = false;
615                                }
616                            }
617                            else{
618                                if (i == nr -1){
619                                    pad = false;
620                                }
621                                else{
622                                    pad = true;
623                                }
624                            }
625                            rs = (pad) ? this.rowSpacing : '0';
626               
627                            td1 = $('<td class="jqplot-table-legend" style="text-align:center;padding-top:'+rs+';">'+
628                                '<div><div class="jqplot-table-legend-swatch" style="border-color:'+color+';"></div>'+
629                                '</div></td>');
630                            td2 = $('<td class="jqplot-table-legend" style="padding-top:'+rs+';"></td>');
631                            if (this.escapeHtml){
632                                td2.text(lt);
633                            }
634                            else {
635                                td2.html(lt);
636                            }
637                            if (reverse) {
638                                td2.prependTo(tr);
639                                td1.prependTo(tr);
640                            }
641                            else {
642                                td1.appendTo(tr);
643                                td2.appendTo(tr);
644                            }
645                            pad = true;
646                        }
647                        idx++;
648                    }   
649                }
650            }
651        }
652        return this._elem;               
653    };
654   
655    // $.jqplot.FunnelLegendRenderer.prototype.pack = function(offsets) {
656    //     if (this.show) {
657    //         // fake a grid for positioning
658    //         var grid = {_top:offsets.top, _left:offsets.left, _right:offsets.right, _bottom:this._plotDimensions.height - offsets.bottom};       
659    //         if (this.placement == 'insideGrid') {
660    //             switch (this.location) {
661    //                 case 'nw':
662    //                     var a = grid._left + this.xoffset;
663    //                     var b = grid._top + this.yoffset;
664    //                     this._elem.css('left', a);
665    //                     this._elem.css('top', b);
666    //                     break;
667    //                 case 'n':
668    //                     var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
669    //                     var b = grid._top + this.yoffset;
670    //                     this._elem.css('left', a);
671    //                     this._elem.css('top', b);
672    //                     break;
673    //                 case 'ne':
674    //                     var a = offsets.right + this.xoffset;
675    //                     var b = grid._top + this.yoffset;
676    //                     this._elem.css({right:a, top:b});
677    //                     break;
678    //                 case 'e':
679    //                     var a = offsets.right + this.xoffset;
680    //                     var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
681    //                     this._elem.css({right:a, top:b});
682    //                     break;
683    //                 case 'se':
684    //                     var a = offsets.right + this.xoffset;
685    //                     var b = offsets.bottom + this.yoffset;
686    //                     this._elem.css({right:a, bottom:b});
687    //                     break;
688    //                 case 's':
689    //                     var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
690    //                     var b = offsets.bottom + this.yoffset;
691    //                     this._elem.css({left:a, bottom:b});
692    //                     break;
693    //                 case 'sw':
694    //                     var a = grid._left + this.xoffset;
695    //                     var b = offsets.bottom + this.yoffset;
696    //                     this._elem.css({left:a, bottom:b});
697    //                     break;
698    //                 case 'w':
699    //                     var a = grid._left + this.xoffset;
700    //                     var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
701    //                     this._elem.css({left:a, top:b});
702    //                     break;
703    //                 default:  // same as 'se'
704    //                     var a = grid._right - this.xoffset;
705    //                     var b = grid._bottom + this.yoffset;
706    //                     this._elem.css({right:a, bottom:b});
707    //                     break;
708    //             }
709    //             
710    //         }
711    //         else {
712    //             switch (this.location) {
713    //                 case 'nw':
714    //                     var a = this._plotDimensions.width - grid._left + this.xoffset;
715    //                     var b = grid._top + this.yoffset;
716    //                     this._elem.css('right', a);
717    //                     this._elem.css('top', b);
718    //                     break;
719    //                 case 'n':
720    //                     var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
721    //                     var b = this._plotDimensions.height - grid._top + this.yoffset;
722    //                     this._elem.css('left', a);
723    //                     this._elem.css('bottom', b);
724    //                     break;
725    //                 case 'ne':
726    //                     var a = this._plotDimensions.width - offsets.right + this.xoffset;
727    //                     var b = grid._top + this.yoffset;
728    //                     this._elem.css({left:a, top:b});
729    //                     break;
730    //                 case 'e':
731    //                     var a = this._plotDimensions.width - offsets.right + this.xoffset;
732    //                     var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
733    //                     this._elem.css({left:a, top:b});
734    //                     break;
735    //                 case 'se':
736    //                     var a = this._plotDimensions.width - offsets.right + this.xoffset;
737    //                     var b = offsets.bottom + this.yoffset;
738    //                     this._elem.css({left:a, bottom:b});
739    //                     break;
740    //                 case 's':
741    //                     var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
742    //                     var b = this._plotDimensions.height - offsets.bottom + this.yoffset;
743    //                     this._elem.css({left:a, top:b});
744    //                     break;
745    //                 case 'sw':
746    //                     var a = this._plotDimensions.width - grid._left + this.xoffset;
747    //                     var b = offsets.bottom + this.yoffset;
748    //                     this._elem.css({right:a, bottom:b});
749    //                     break;
750    //                 case 'w':
751    //                     var a = this._plotDimensions.width - grid._left + this.xoffset;
752    //                     var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
753    //                     this._elem.css({right:a, top:b});
754    //                     break;
755    //                 default:  // same as 'se'
756    //                     var a = grid._right - this.xoffset;
757    //                     var b = grid._bottom + this.yoffset;
758    //                     this._elem.css({right:a, bottom:b});
759    //                     break;
760    //             }
761    //         }
762    //     }
763    // };
764   
765    // setup default renderers for axes and legend so user doesn't have to
766    // called with scope of plot
767    function preInit(target, data, options) {
768        options = options || {};
769        options.axesDefaults = options.axesDefaults || {};
770        options.legend = options.legend || {};
771        options.seriesDefaults = options.seriesDefaults || {};
772        // only set these if there is a funnel series
773        var setopts = false;
774        if (options.seriesDefaults.renderer == $.jqplot.FunnelRenderer) {
775            setopts = true;
776        }
777        else if (options.series) {
778            for (var i=0; i < options.series.length; i++) {
779                if (options.series[i].renderer == $.jqplot.FunnelRenderer) {
780                    setopts = true;
781                }
782            }
783        }
784       
785        if (setopts) {
786            options.axesDefaults.renderer = $.jqplot.FunnelAxisRenderer;
787            options.legend.renderer = $.jqplot.FunnelLegendRenderer;
788            options.legend.preDraw = true;
789            options.sortData = false;
790            options.seriesDefaults.pointLabels = {show: false};
791        }
792    }
793   
794    function postInit(target, data, options) {
795        // if multiple series, add a reference to the previous one so that
796        // funnel rings can nest.
797        for (var i=0; i<this.series.length; i++) {
798            if (this.series[i].renderer.constructor == $.jqplot.FunnelRenderer) {
799                // don't allow mouseover and mousedown at same time.
800                if (this.series[i].highlightMouseOver) {
801                    this.series[i].highlightMouseDown = false;
802                }
803            }
804        }
805    }
806   
807    // called with scope of plot
808    function postParseOptions(options) {
809        for (var i=0; i<this.series.length; i++) {
810            this.series[i].seriesColors = this.seriesColors;
811            this.series[i].colorGenerator = $.jqplot.colorGenerator;
812        }
813    }
814   
815    function highlight (plot, sidx, pidx) {
816        var s = plot.series[sidx];
817        var canvas = plot.plugins.funnelRenderer.highlightCanvas;
818        canvas._ctx.clearRect(0,0,canvas._ctx.canvas.width, canvas._ctx.canvas.height);
819        s._highlightedPoint = pidx;
820        plot.plugins.funnelRenderer.highlightedSeriesIndex = sidx;
821        s.renderer.drawSection.call(s, canvas._ctx, s._vertices[pidx], s.highlightColors[pidx], false);
822    }
823   
824    function unhighlight (plot) {
825        var canvas = plot.plugins.funnelRenderer.highlightCanvas;
826        canvas._ctx.clearRect(0,0, canvas._ctx.canvas.width, canvas._ctx.canvas.height);
827        for (var i=0; i<plot.series.length; i++) {
828            plot.series[i]._highlightedPoint = null;
829        }
830        plot.plugins.funnelRenderer.highlightedSeriesIndex = null;
831        plot.target.trigger('jqplotDataUnhighlight');
832    }
833   
834    function handleMove(ev, gridpos, datapos, neighbor, plot) {
835        if (neighbor) {
836            var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
837            var evt1 = jQuery.Event('jqplotDataMouseOver');
838            evt1.pageX = ev.pageX;
839            evt1.pageY = ev.pageY;
840            plot.target.trigger(evt1, ins);
841            if (plot.series[ins[0]].highlightMouseOver && !(ins[0] == plot.plugins.funnelRenderer.highlightedSeriesIndex && ins[1] == plot.series[ins[0]]._highlightedPoint)) {
842                var evt = jQuery.Event('jqplotDataHighlight');
843                evt.pageX = ev.pageX;
844                evt.pageY = ev.pageY;
845                plot.target.trigger(evt, ins);
846                highlight (plot, ins[0], ins[1]);
847            }
848        }
849        else if (neighbor == null) {
850            unhighlight (plot);
851        }
852    }
853   
854    function handleMouseDown(ev, gridpos, datapos, neighbor, plot) {
855        if (neighbor) {
856            var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
857            if (plot.series[ins[0]].highlightMouseDown && !(ins[0] == plot.plugins.funnelRenderer.highlightedSeriesIndex && ins[1] == plot.series[ins[0]]._highlightedPoint)) {
858                var evt = jQuery.Event('jqplotDataHighlight');
859                evt.pageX = ev.pageX;
860                evt.pageY = ev.pageY;
861                plot.target.trigger(evt, ins);
862                highlight (plot, ins[0], ins[1]);
863            }
864        }
865        else if (neighbor == null) {
866            unhighlight (plot);
867        }
868    }
869   
870    function handleMouseUp(ev, gridpos, datapos, neighbor, plot) {
871        var idx = plot.plugins.funnelRenderer.highlightedSeriesIndex;
872        if (idx != null && plot.series[idx].highlightMouseDown) {
873            unhighlight(plot);
874        }
875    }
876   
877    function handleClick(ev, gridpos, datapos, neighbor, plot) {
878        if (neighbor) {
879            var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
880            var evt = jQuery.Event('jqplotDataClick');
881            evt.pageX = ev.pageX;
882            evt.pageY = ev.pageY;
883            plot.target.trigger(evt, ins);
884        }
885    }
886   
887    function handleRightClick(ev, gridpos, datapos, neighbor, plot) {
888        if (neighbor) {
889            var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
890            var idx = plot.plugins.funnelRenderer.highlightedSeriesIndex;
891            if (idx != null && plot.series[idx].highlightMouseDown) {
892                unhighlight(plot);
893            }
894            var evt = jQuery.Event('jqplotDataRightClick');
895            evt.pageX = ev.pageX;
896            evt.pageY = ev.pageY;
897            plot.target.trigger(evt, ins);
898        }
899    }
900   
901    // called within context of plot
902    // create a canvas which we can draw on.
903    // insert it before the eventCanvas, so eventCanvas will still capture events.
904    function postPlotDraw() {
905        // Memory Leaks patch   
906        if (this.plugins.funnelRenderer && this.plugins.funnelRenderer.highlightCanvas) {
907            this.plugins.funnelRenderer.highlightCanvas.resetCanvas();
908            this.plugins.funnelRenderer.highlightCanvas = null;
909        }
910
911        this.plugins.funnelRenderer = {};
912        this.plugins.funnelRenderer.highlightCanvas = new $.jqplot.GenericCanvas();
913       
914        // do we have any data labels?  if so, put highlight canvas before those
915        var labels = $(this.targetId+' .jqplot-data-label');
916        if (labels.length) {
917            $(labels[0]).before(this.plugins.funnelRenderer.highlightCanvas.createElement(this._gridPadding, 'jqplot-funnelRenderer-highlight-canvas', this._plotDimensions, this));
918        }
919        // else put highlight canvas before event canvas.
920        else {
921            this.eventCanvas._elem.before(this.plugins.funnelRenderer.highlightCanvas.createElement(this._gridPadding, 'jqplot-funnelRenderer-highlight-canvas', this._plotDimensions, this));
922        }
923        var hctx = this.plugins.funnelRenderer.highlightCanvas.setContext();
924        this.eventCanvas._elem.bind('mouseleave', {plot:this}, function (ev) { unhighlight(ev.data.plot); });
925    }
926   
927    $.jqplot.preInitHooks.push(preInit);
928   
929    $.jqplot.FunnelTickRenderer = function() {
930        $.jqplot.AxisTickRenderer.call(this);
931    };
932   
933    $.jqplot.FunnelTickRenderer.prototype = new $.jqplot.AxisTickRenderer();
934    $.jqplot.FunnelTickRenderer.prototype.constructor = $.jqplot.FunnelTickRenderer;
935   
936})(jQuery);
937   
938   
Note: See TracBrowser for help on using the repository browser.