outfielding-jqplot-rails 1.0.8 → 1.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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