Subversion-Projekte lars-tiefland.cienc

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

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