outfielding-jqplot-rails 1.0.8 → 1.0.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +2 -0
  3. data/CHANGELOG.md +8 -4
  4. data/changes-jqplot.txt +48 -37
  5. data/copyright-jqplot.txt +17 -17
  6. data/lib/outfielding-jqplot-rails/version.rb +1 -1
  7. data/vendor/assets/javascripts/excanvas.js +1438 -1438
  8. data/vendor/assets/javascripts/jqplot-plugins/jqplot.BezierCurveRenderer.js +313 -313
  9. data/vendor/assets/javascripts/jqplot-plugins/jqplot.barRenderer.js +801 -801
  10. data/vendor/assets/javascripts/jqplot-plugins/jqplot.blockRenderer.js +234 -234
  11. data/vendor/assets/javascripts/jqplot-plugins/jqplot.bubbleRenderer.js +758 -758
  12. data/vendor/assets/javascripts/jqplot-plugins/jqplot.canvasAxisLabelRenderer.js +202 -202
  13. data/vendor/assets/javascripts/jqplot-plugins/jqplot.canvasAxisTickRenderer.js +252 -252
  14. data/vendor/assets/javascripts/jqplot-plugins/jqplot.canvasOverlay.js +1020 -1020
  15. data/vendor/assets/javascripts/jqplot-plugins/jqplot.canvasTextRenderer.js +448 -448
  16. data/vendor/assets/javascripts/jqplot-plugins/jqplot.categoryAxisRenderer.js +679 -679
  17. data/vendor/assets/javascripts/jqplot-plugins/jqplot.ciParser.js +115 -115
  18. data/vendor/assets/javascripts/jqplot-plugins/jqplot.cursor.js +1108 -1108
  19. data/vendor/assets/javascripts/jqplot-plugins/jqplot.dateAxisRenderer.js +741 -741
  20. data/vendor/assets/javascripts/jqplot-plugins/jqplot.donutRenderer.js +816 -805
  21. data/vendor/assets/javascripts/jqplot-plugins/jqplot.dragable.js +224 -224
  22. data/vendor/assets/javascripts/jqplot-plugins/jqplot.enhancedLegendRenderer.js +305 -305
  23. data/vendor/assets/javascripts/jqplot-plugins/jqplot.enhancedPieLegendRenderer.js +261 -0
  24. data/vendor/assets/javascripts/jqplot-plugins/jqplot.funnelRenderer.js +942 -942
  25. data/vendor/assets/javascripts/jqplot-plugins/jqplot.highlighter.js +464 -464
  26. data/vendor/assets/javascripts/jqplot-plugins/jqplot.json2.js +475 -475
  27. data/vendor/assets/javascripts/jqplot-plugins/jqplot.logAxisRenderer.js +533 -533
  28. data/vendor/assets/javascripts/jqplot-plugins/jqplot.mekkoAxisRenderer.js +611 -611
  29. data/vendor/assets/javascripts/jqplot-plugins/jqplot.mekkoRenderer.js +437 -437
  30. data/vendor/assets/javascripts/jqplot-plugins/jqplot.meterGaugeRenderer.js +1029 -1029
  31. data/vendor/assets/javascripts/jqplot-plugins/jqplot.mobile.js +2 -2
  32. data/vendor/assets/javascripts/jqplot-plugins/jqplot.ohlcRenderer.js +373 -373
  33. data/vendor/assets/javascripts/jqplot-plugins/jqplot.pieRenderer.js +945 -903
  34. data/vendor/assets/javascripts/jqplot-plugins/jqplot.pointLabels.js +379 -377
  35. data/vendor/assets/javascripts/jqplot-plugins/jqplot.pyramidAxisRenderer.js +728 -728
  36. data/vendor/assets/javascripts/jqplot-plugins/jqplot.pyramidGridRenderer.js +428 -428
  37. data/vendor/assets/javascripts/jqplot-plugins/jqplot.pyramidRenderer.js +513 -513
  38. data/vendor/assets/javascripts/jqplot-plugins/jqplot.trendline.js +222 -222
  39. data/vendor/assets/javascripts/jquery.jqplot.js +11477 -11411
  40. data/vendor/assets/stylesheets/jquery.jqplot.css +259 -259
  41. metadata +9 -10
@@ -0,0 +1,261 @@
1
+ /**
2
+ * jqPlot
3
+ * Pure JavaScript plotting plugin using jQuery
4
+ *
5
+ * Version: 1.0.9
6
+ * Revision: d96a669
7
+ *
8
+ * Copyright (c) 2009-2016 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
+ // class $.jqplot.EnhancedPieLegendRenderer
33
+ // Legend renderer which can specify the number of rows and/or columns in the legend
34
+ // Similar to EnhancedLegendRenderer, but for pie charts
35
+ $.jqplot.EnhancedPieLegendRenderer = function(){
36
+ $.jqplot.TableLegendRenderer.call(this);
37
+ };
38
+
39
+ $.jqplot.EnhancedPieLegendRenderer.prototype = new $.jqplot.TableLegendRenderer();
40
+ $.jqplot.EnhancedPieLegendRenderer.prototype.constructor = $.jqplot.EnhancedPieLegendRenderer;
41
+
42
+ // called with scope of legend.
43
+ $.jqplot.EnhancedPieLegendRenderer.prototype.init = function(options) {
44
+ // prop: numberRows
45
+ // Maximum number of rows in the legend. 0 or null for unlimited.
46
+ this.numberRows = null;
47
+ // prop: numberColumns
48
+ // Maximum number of columns in the legend. 0 or null for unlimited.
49
+ this.numberColumns = null;
50
+ // prop: seriesToggle
51
+ // false to not enable series on/off toggling on the legend.
52
+ // true or a fadein/fadeout speed (number of milliseconds or 'fast', 'normal', 'slow')
53
+ // to enable show/hide of series on click of legend item.
54
+ this.seriesToggle = 'normal';
55
+ // prop: seriesToggleReplot
56
+ // True to replot the chart after toggling series on/off.
57
+ // This will set the series show property to false.
58
+ // This allows for rescaling or other maniplation of chart.
59
+ // Set to an options object (e.g. {resetAxes: true}) for replot options.
60
+ this.seriesToggleReplot = false;
61
+ // prop: disableIEFading
62
+ // true to toggle series with a show/hide method only and not allow fading in/out.
63
+ // This is to overcome poor performance of fade in some versions of IE.
64
+ this.disableIEFading = true;
65
+ // prop: toolTips
66
+ // optional array of toolTip text corresponding to each pie slice
67
+ this.toolTips = [];
68
+ $.extend(true, this, options);
69
+
70
+ if (this.seriesToggle) {
71
+ $.jqplot.postDrawHooks.push(postDraw);
72
+ }
73
+ };
74
+
75
+ // called with scope of legend
76
+ $.jqplot.EnhancedPieLegendRenderer.prototype.draw = function(offsets, plot) {
77
+ var legend = this;
78
+ if (this.show) {
79
+ var series = this._series;
80
+ var s;
81
+ var ss = 'position:absolute;';
82
+ ss += (this.background) ? 'background:'+this.background+';' : '';
83
+ ss += (this.border) ? 'border:'+this.border+';' : '';
84
+ ss += (this.fontSize) ? 'font-size:'+this.fontSize+';' : '';
85
+ ss += (this.fontFamily) ? 'font-family:'+this.fontFamily+';' : '';
86
+ ss += (this.textColor) ? 'color:'+this.textColor+';' : '';
87
+ ss += (this.marginTop != null) ? 'margin-top:'+this.marginTop+';' : '';
88
+ ss += (this.marginBottom != null) ? 'margin-bottom:'+this.marginBottom+';' : '';
89
+ ss += (this.marginLeft != null) ? 'margin-left:'+this.marginLeft+';' : '';
90
+ ss += (this.marginRight != null) ? 'margin-right:'+this.marginRight+';' : '';
91
+ this._elem = $('<table class="jqplot-table-legend" style="'+ss+'"></table>');
92
+ if (this.seriesToggle) {
93
+ this._elem.css('z-index', '3');
94
+ }
95
+
96
+ var pad = false,
97
+ reverse = false,
98
+ nr, nc;
99
+ var s = series[0];
100
+ var slen = s.data.length;
101
+ var colorGenerator = new $.jqplot.ColorGenerator(s.seriesColors);
102
+
103
+ if (this.numberRows) {
104
+ nr = this.numberRows;
105
+ if (!this.numberColumns){
106
+ nc = Math.ceil(slen/nr);
107
+ }
108
+ else{
109
+ nc = this.numberColumns;
110
+ }
111
+ }
112
+ else if (this.numberColumns) {
113
+ nc = this.numberColumns;
114
+ nr = Math.ceil(slen/this.numberColumns);
115
+ }
116
+ else {
117
+ nr = slen;
118
+ nc = 1;
119
+ }
120
+
121
+ var i, j, tr, td1, td2, lt, rs, div, div0, div1;
122
+ var idx = 0;
123
+ // check to see if we need to reverse
124
+ for (i=series.length-1; i>=0; i--) {
125
+ if (nc == 1 && series[i]._stack || series[i].renderer.constructor == $.jqplot.BezierCurveRenderer){
126
+ reverse = true;
127
+ }
128
+ }
129
+
130
+ for (i=0; i<nr; i++) {
131
+ tr = $(document.createElement('tr'));
132
+ tr.addClass('jqplot-table-legend');
133
+ if (reverse){
134
+ tr.prependTo(this._elem);
135
+ }
136
+ else{
137
+ tr.appendTo(this._elem);
138
+ }
139
+ for (j=0; j<nc; j++) {
140
+ if (idx < slen){
141
+ lt = this.labels[idx] || s.data[idx][0].toString();
142
+ tt = this.toolTips[idx];
143
+ if (lt) {
144
+ var color = colorGenerator.next();
145
+ if (!reverse){
146
+ if (i>0){
147
+ pad = true;
148
+ }
149
+ else{
150
+ pad = false;
151
+ }
152
+ }
153
+ else{
154
+ if (i == nr -1){
155
+ pad = false;
156
+ }
157
+ else{
158
+ pad = true;
159
+ }
160
+ }
161
+ rs = (pad) ? this.rowSpacing : '0';
162
+
163
+ td1 = $(document.createElement('td'));
164
+ td1.addClass('jqplot-table-legend jqplot-table-legend-swatch');
165
+ td1.css({textAlign: 'center', paddingTop: rs});
166
+
167
+ div0 = $(document.createElement('div'));
168
+ div0.addClass('jqplot-table-legend-swatch-outline');
169
+ if (tt !== undefined) {
170
+ div0.attr("title", tt);
171
+ }
172
+
173
+ div1 = $(document.createElement('div'));
174
+ div1.addClass('jqplot-table-legend-swatch');
175
+ div1.css({backgroundColor: color, borderColor: color});
176
+
177
+ td1.append(div0.append(div1));
178
+
179
+ td2 = $(document.createElement('td'));
180
+ td2.addClass('jqplot-table-legend jqplot-table-legend-label');
181
+ td2.css('paddingTop', rs);
182
+ if (tt !== undefined) {
183
+ td2.attr("title", tt);
184
+ }
185
+
186
+ if (this.escapeHtml){
187
+ td2.text(lt);
188
+ }
189
+ else {
190
+ td2.html(lt);
191
+ }
192
+ if (reverse) {
193
+ if (this.showLabels) {td2.prependTo(tr);}
194
+ if (this.showSwatches) {td1.prependTo(tr);}
195
+ }
196
+ else {
197
+ if (this.showSwatches) {td1.appendTo(tr);}
198
+ if (this.showLabels) {td2.appendTo(tr);}
199
+ }
200
+
201
+ if (this.seriesToggle) {
202
+
203
+ var speed;
204
+ if (typeof(this.seriesToggle) === 'string' || typeof(this.seriesToggle) === 'number') {
205
+ if (!$.jqplot.use_excanvas || !this.disableIEFading) {
206
+ speed = this.seriesToggle;
207
+ }
208
+ }
209
+ if (this.showSwatches) {
210
+ td1.bind('click', {series:s, index:idx, speed:speed, plot: plot, replot:this.seriesToggleReplot}, handleToggle);
211
+ td1.addClass('jqplot-seriesToggle');
212
+ }
213
+ if (this.showLabels) {
214
+ td2.bind('click', {series:s, index:idx, speed:speed, plot: plot, replot:this.seriesToggleReplot}, handleToggle);
215
+ td2.addClass('jqplot-seriesToggle');
216
+ }
217
+
218
+ // for slices that are already hidden, add the hidden class
219
+ if (s.showSlice[idx] === false && s.showLabel) {
220
+ td1.addClass('jqplot-series-hidden');
221
+ td2.addClass('jqplot-series-hidden');
222
+ }
223
+ }
224
+
225
+ pad = true;
226
+ }
227
+ }
228
+ idx++;
229
+ }
230
+
231
+ td1 = td2 = div0 = div1 = null;
232
+ }
233
+ }
234
+ return this._elem;
235
+ };
236
+
237
+ var handleToggle = function (ev) {
238
+ var d = ev.data,
239
+ replot = d.replot,
240
+ plot = d.plot,
241
+ idx = d.index;
242
+
243
+ d.series.showSlice[idx] = (d.series.showSlice[idx] === false) ? true : false;
244
+
245
+ var opts = {};
246
+
247
+ if ($.isPlainObject(replot)) {
248
+ $.extend(true, opts, replot);
249
+ }
250
+
251
+ plot.replot(opts);
252
+ };
253
+
254
+ // called with scope of plot.
255
+ var postDraw = function () {
256
+ if (this.legend.renderer.constructor == $.jqplot.EnhancedPieLegendRenderer && this.legend.seriesToggle) {
257
+ var e = this.legend._elem.detach();
258
+ this.eventCanvas._elem.after(e);
259
+ }
260
+ };
261
+ })(jQuery);
@@ -1,943 +1,943 @@
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
-
1
+ /**
2
+ * jqPlot
3
+ * Pure JavaScript plotting plugin using jQuery
4
+ *
5
+ * Version: 1.0.9
6
+ * Revision: d96a669
7
+ *
8
+ * Copyright (c) 2009-2016 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
943