ses-proxy 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. data/app/public/bootstrap/css/bootstrap-responsive.css +1092 -0
  2. data/app/public/bootstrap/css/bootstrap-responsive.min.css +9 -0
  3. data/app/public/bootstrap/css/bootstrap.css +6039 -0
  4. data/app/public/bootstrap/css/bootstrap.min.css +9 -0
  5. data/app/public/bootstrap/img/glyphicons-halflings-white.png +0 -0
  6. data/app/public/bootstrap/img/glyphicons-halflings.png +0 -0
  7. data/app/public/bootstrap/js/bootstrap.js +2159 -0
  8. data/app/public/bootstrap/js/bootstrap.min.js +6 -0
  9. data/app/public/css/application.css +16 -0
  10. data/app/public/datepicker/css/datepicker.css +7 -0
  11. data/app/public/datepicker/js/bootstrap-datepicker.js +454 -0
  12. data/app/public/datepicker/less/datepicker.less +119 -0
  13. data/app/public/highcharts/adapters/mootools-adapter.js +13 -0
  14. data/app/public/highcharts/adapters/mootools-adapter.src.js +327 -0
  15. data/app/public/highcharts/adapters/prototype-adapter.js +16 -0
  16. data/app/public/highcharts/adapters/prototype-adapter.src.js +385 -0
  17. data/app/public/highcharts/highcharts-more.js +35 -0
  18. data/app/public/highcharts/highcharts.js +246 -0
  19. data/app/public/highcharts/highcharts.src.js +15111 -0
  20. data/app/public/highcharts/modules/canvas-tools.js +133 -0
  21. data/app/public/highcharts/modules/canvas-tools.src.js +3113 -0
  22. data/app/public/highcharts/modules/data.js +11 -0
  23. data/app/public/highcharts/modules/data.src.js +277 -0
  24. data/app/public/highcharts/modules/exporting.js +23 -0
  25. data/app/public/highcharts/modules/exporting.src.js +736 -0
  26. data/app/public/highcharts/themes/dark-blue.js +263 -0
  27. data/app/public/highcharts/themes/dark-green.js +263 -0
  28. data/app/public/highcharts/themes/gray.js +262 -0
  29. data/app/public/highcharts/themes/grid.js +95 -0
  30. data/app/public/highcharts/themes/skies.js +89 -0
  31. data/app/public/images/loader.gif +0 -0
  32. data/app/public/js/application.js +81 -0
  33. data/app/views/_chart.haml +2 -0
  34. data/app/views/_search_form.haml +23 -0
  35. data/app/views/bounces.haml +23 -0
  36. data/app/views/kaminari/_first_page.html.erb +3 -0
  37. data/app/views/kaminari/_gap.html.erb +3 -0
  38. data/app/views/kaminari/_last_page.html.erb +3 -0
  39. data/app/views/kaminari/_next_page.html.erb +3 -0
  40. data/app/views/kaminari/_page.html.erb +3 -0
  41. data/app/views/kaminari/_paginator.html.erb +17 -0
  42. data/app/views/kaminari/_prev_page.html.erb +3 -0
  43. data/app/views/layout.haml +25 -0
  44. data/app/views/mails.haml +26 -0
  45. data/app/web_panel.rb +149 -0
  46. data/bin/ses_proxy +20 -0
  47. data/lib/ses_proxy/conf.rb +9 -0
  48. data/lib/ses_proxy/main_command.rb +154 -0
  49. data/lib/ses_proxy/models/bounce.rb +14 -0
  50. data/lib/ses_proxy/models/complaint.rb +13 -0
  51. data/lib/ses_proxy/models/email.rb +15 -0
  52. data/lib/ses_proxy/smtp_server.rb +122 -0
  53. data/lib/ses_proxy/sns_endpoint.rb +199 -0
  54. data/ses_proxy.rb +10 -0
  55. data/template/mongoid.yml +12 -0
  56. data/template/ses-proxy.yml +19 -0
  57. metadata +294 -0
@@ -0,0 +1,11 @@
1
+ /*
2
+ Data plugin for Highcharts v0.1
3
+
4
+ (c) 2012 Torstein Hønsi
5
+
6
+ License: www.highcharts.com/license
7
+ */
8
+ (function(m){var l=m.each,n=function(a){this.init(a)};m.extend(n.prototype,{init:function(a){this.options=a;this.columns=[];this.parseCSV();this.parseTable();this.parseTypes();this.findHeaderRow();this.parsed();this.complete()},parseCSV:function(){var a=this.options,b=a.csv,d=this.columns,c=a.startRow||0,f=a.endRow||Number.MAX_VALUE,e=a.startColumn||0,j=a.endColumn||Number.MAX_VALUE;b&&(b=b.split(a.lineDelimiter||"\n"),l(b,function(b,k){if(k>=c&&k<=f){var h=b.split(a.itemDelimiter||",");l(h,function(a,
9
+ b){b>=e&&b<=j&&(d[b-e]||(d[b-e]=[]),d[b-e][k-c]=a)})}}))},parseTable:function(){var a=this.options,b=a.table,d=this.columns,c=a.startRow||0,f=a.endRow||Number.MAX_VALUE,e=a.startColumn||0,j=a.endColumn||Number.MAX_VALUE,g;b&&(typeof b==="string"&&(b=document.getElementById(b)),l(b.getElementsByTagName("tr"),function(a,b){g=0;b>=c&&b<=f&&l(a.childNodes,function(a){if((a.tagName==="TD"||a.tagName==="TH")&&g>=e&&g<=j)d[g]||(d[g]=[]),d[g][b-c]=a.innerHTML,g+=1})}))},findHeaderRow:function(){l(this.columns,
10
+ function(){});this.headerRow=0},trim:function(a){return a.replace(/^\s+|\s+$/g,"")},parseTypes:function(){for(var a=this.columns,b=a.length,d,c,f,e;b--;)for(d=a[b].length;d--;)c=a[b][d],f=parseFloat(c),e=this.trim(c),e==f?(a[b][d]=f,f>31536E6?a[b].isDatetime=!0:a[b].isNumeric=!0):(c=Date.parse(c),b===0&&typeof c==="number"&&!isNaN(c)?(a[b][d]=c,a[b].isDatetime=!0):a[b][d]=e)},parsed:function(){this.options.parsed&&this.options.parsed.call(this,this.columns)},complete:function(){var a=this.columns,
11
+ b,d,c,f,e=this.options,j,g,k,h,i;if(e.complete){a.length>1&&(c=a.shift(),this.headerRow===0&&c.shift(),(b=c.isNumeric||c.isDatetime)||(d=c),c.isDatetime&&(f="datetime"));j=[];for(h=0;h<a.length;h++){this.headerRow===0&&(k=a[h].shift());g=[];for(i=0;i<a[h].length;i++)g[i]=a[h][i]!==void 0?b?[c[i],a[h][i]]:a[h][i]:null;j[h]={name:k,data:g}}e.complete({xAxis:{categories:d,type:f},series:j})}}});m.Data=n;m.data=function(a){return new n(a)}})(Highcharts);
@@ -0,0 +1,277 @@
1
+ /**
2
+ * @license Data plugin for Highcharts v0.1
3
+ *
4
+ * (c) 2012 Torstein Hønsi
5
+ *
6
+ * License: www.highcharts.com/license
7
+ */
8
+
9
+ /*
10
+ * Demo: http://jsfiddle.net/highcharts/SnLFj/
11
+ */
12
+
13
+ (function (Highcharts) {
14
+
15
+ // Utilities
16
+ var each = Highcharts.each;
17
+
18
+
19
+ // The Data constructor
20
+ var Data = function (options) {
21
+ this.init(options);
22
+ };
23
+
24
+ // Set the prototype properties
25
+ Highcharts.extend(Data.prototype, {
26
+
27
+ /**
28
+ * Initialize the Data object with the given options
29
+ */
30
+ init: function (options) {
31
+ this.options = options;
32
+ this.columns = [];
33
+
34
+
35
+ // Parse a CSV string if options.csv is given
36
+ this.parseCSV();
37
+
38
+ // Parse a HTML table if options.table is given
39
+ this.parseTable();
40
+
41
+ // Interpret the values into right types
42
+ this.parseTypes();
43
+
44
+ // Use first row for series names?
45
+ this.findHeaderRow();
46
+
47
+ // Handle columns if a handleColumns callback is given
48
+ this.parsed();
49
+
50
+ // Complete if a complete callback is given
51
+ this.complete();
52
+
53
+ },
54
+
55
+ /**
56
+ * Parse a CSV input string
57
+ */
58
+ parseCSV: function () {
59
+ var options = this.options,
60
+ csv = options.csv,
61
+ columns = this.columns,
62
+ startRow = options.startRow || 0,
63
+ endRow = options.endRow || Number.MAX_VALUE,
64
+ startColumn = options.startColumn || 0,
65
+ endColumn = options.endColumn || Number.MAX_VALUE,
66
+ lines;
67
+
68
+ if (csv) {
69
+ lines = csv.split(options.lineDelimiter || '\n');
70
+
71
+ each(lines, function (line, rowNo) {
72
+ if (rowNo >= startRow && rowNo <= endRow) {
73
+ var items = line.split(options.itemDelimiter || ',');
74
+ each(items, function (item, colNo) {
75
+ if (colNo >= startColumn && colNo <= endColumn) {
76
+ if (!columns[colNo - startColumn]) {
77
+ columns[colNo - startColumn] = [];
78
+ }
79
+
80
+ columns[colNo - startColumn][rowNo - startRow] = item;
81
+ }
82
+ });
83
+ }
84
+ });
85
+ }
86
+ },
87
+
88
+ /**
89
+ * Parse a HTML table
90
+ */
91
+ parseTable: function () {
92
+ var options = this.options,
93
+ table = options.table,
94
+ columns = this.columns,
95
+ startRow = options.startRow || 0,
96
+ endRow = options.endRow || Number.MAX_VALUE,
97
+ startColumn = options.startColumn || 0,
98
+ endColumn = options.endColumn || Number.MAX_VALUE,
99
+ colNo;
100
+
101
+ if (table) {
102
+
103
+ if (typeof table === 'string') {
104
+ table = document.getElementById(table);
105
+ }
106
+
107
+ each(table.getElementsByTagName('tr'), function (tr, rowNo) {
108
+ colNo = 0;
109
+ if (rowNo >= startRow && rowNo <= endRow) {
110
+ each(tr.childNodes, function (item) {
111
+ if ((item.tagName === 'TD' || item.tagName === 'TH') && colNo >= startColumn && colNo <= endColumn) {
112
+ if (!columns[colNo]) {
113
+ columns[colNo] = [];
114
+ }
115
+ columns[colNo][rowNo - startRow] = item.innerHTML;
116
+
117
+ colNo += 1;
118
+ }
119
+ });
120
+ }
121
+ });
122
+ }
123
+ },
124
+
125
+ /**
126
+ * Find the header row. For now, we just check whether the first row contains
127
+ * numbers or strings. Later we could loop down and find the first row with
128
+ * numbers.
129
+ */
130
+ findHeaderRow: function () {
131
+ var headerRow = 0;
132
+ each(this.columns, function (column) {
133
+ if (typeof column[0] !== 'string') {
134
+ headerRow = null;
135
+ }
136
+ });
137
+ this.headerRow = 0;
138
+ },
139
+
140
+ /**
141
+ * Trim a string from whitespace
142
+ */
143
+ trim: function (str) {
144
+ return str.replace(/^\s+|\s+$/g, '');
145
+ },
146
+
147
+ /**
148
+ * Parse numeric cells in to number types and date types in to true dates.
149
+ * @param {Object} columns
150
+ */
151
+ parseTypes: function () {
152
+ var columns = this.columns,
153
+ col = columns.length,
154
+ row,
155
+ val,
156
+ floatVal,
157
+ trimVal,
158
+ dateVal;
159
+
160
+ while (col--) {
161
+ row = columns[col].length;
162
+ while (row--) {
163
+ val = columns[col][row];
164
+ floatVal = parseFloat(val);
165
+ trimVal = this.trim(val);
166
+ /*jslint eqeq: true*/
167
+ if (trimVal == floatVal) { // is numeric
168
+ /*jslint eqeq: false*/
169
+ columns[col][row] = floatVal;
170
+
171
+ // If the number is greater than milliseconds in a year, assume datetime
172
+ if (floatVal > 365 * 24 * 3600 * 1000) {
173
+ columns[col].isDatetime = true;
174
+ } else {
175
+ columns[col].isNumeric = true;
176
+ }
177
+
178
+ } else { // string, continue to determine if it is a date string or really a string
179
+ dateVal = Date.parse(val);
180
+
181
+ if (col === 0 && typeof dateVal === 'number' && !isNaN(dateVal)) { // is date
182
+ columns[col][row] = dateVal;
183
+ columns[col].isDatetime = true;
184
+
185
+ } else { // string
186
+ columns[col][row] = trimVal;
187
+ }
188
+ }
189
+
190
+ }
191
+ }
192
+ },
193
+
194
+ parsed: function () {
195
+ if (this.options.parsed) {
196
+ this.options.parsed.call(this, this.columns);
197
+ }
198
+ },
199
+
200
+ /**
201
+ * If a complete callback function is provided in the options, interpret the
202
+ * columns into a Highcharts options object.
203
+ */
204
+ complete: function () {
205
+
206
+ var columns = this.columns,
207
+ hasXData,
208
+ categories,
209
+ firstCol,
210
+ type,
211
+ options = this.options,
212
+ series,
213
+ data,
214
+ name,
215
+ i,
216
+ j;
217
+
218
+
219
+ if (options.complete) {
220
+
221
+ // Use first column for X data or categories?
222
+ if (columns.length > 1) {
223
+ firstCol = columns.shift();
224
+ if (this.headerRow === 0) {
225
+ firstCol.shift(); // remove the first cell
226
+ }
227
+
228
+ // Use the first column for categories or X values
229
+ hasXData = firstCol.isNumeric || firstCol.isDatetime;
230
+ if (!hasXData) { // means type is neither datetime nor linear
231
+ categories = firstCol;
232
+ }
233
+
234
+ if (firstCol.isDatetime) {
235
+ type = 'datetime';
236
+ }
237
+ }
238
+
239
+ // Use the next columns for series
240
+ series = [];
241
+ for (i = 0; i < columns.length; i++) {
242
+ if (this.headerRow === 0) {
243
+ name = columns[i].shift();
244
+ }
245
+ data = [];
246
+ for (j = 0; j < columns[i].length; j++) {
247
+ data[j] = columns[i][j] !== undefined ?
248
+ (hasXData ?
249
+ [firstCol[j], columns[i][j]] :
250
+ columns[i][j]
251
+ ) :
252
+ null;
253
+ }
254
+ series[i] = {
255
+ name: name,
256
+ data: data
257
+ };
258
+ }
259
+
260
+ // Do the callback
261
+ options.complete({
262
+ xAxis: {
263
+ categories: categories,
264
+ type: type
265
+ },
266
+ series: series
267
+ });
268
+ }
269
+ }
270
+ });
271
+
272
+ // Register the Data prototype and data function on Highcharts
273
+ Highcharts.Data = Data;
274
+ Highcharts.data = function (options) {
275
+ return new Data(options);
276
+ };
277
+ }(Highcharts));
@@ -0,0 +1,23 @@
1
+ /*
2
+ Highcharts JS v2.3.3 (2012-10-04)
3
+ Exporting module
4
+
5
+ (c) 2010-2011 Torstein Hønsi
6
+
7
+ License: www.highcharts.com/license
8
+ */
9
+ (function(){function x(a){for(var b=a.length;b--;)typeof a[b]==="number"&&(a[b]=Math.round(a[b])-0.5);return a}var g=Highcharts,y=g.Chart,z=g.addEvent,B=g.removeEvent,r=g.createElement,u=g.discardElement,t=g.css,s=g.merge,k=g.each,n=g.extend,C=Math.max,h=document,D=window,A=h.documentElement.ontouchstart!==void 0,v=g.getOptions();n(v.lang,{downloadPNG:"Download PNG image",downloadJPEG:"Download JPEG image",downloadPDF:"Download PDF document",downloadSVG:"Download SVG vector image",exportButtonTitle:"Export to raster or vector image",
10
+ printButtonTitle:"Print the chart"});v.navigation={menuStyle:{border:"1px solid #A0A0A0",background:"#FFFFFF"},menuItemStyle:{padding:"0 5px",background:"none",color:"#303030",fontSize:A?"14px":"11px"},menuItemHoverStyle:{background:"#4572A5",color:"#FFFFFF"},buttonOptions:{align:"right",backgroundColor:{linearGradient:[0,0,0,20],stops:[[0.4,"#F7F7F7"],[0.6,"#E3E3E3"]]},borderColor:"#B0B0B0",borderRadius:3,borderWidth:1,height:20,hoverBorderColor:"#909090",hoverSymbolFill:"#81A7CF",hoverSymbolStroke:"#4572A5",
11
+ symbolFill:"#E0E0E0",symbolStroke:"#A0A0A0",symbolX:11.5,symbolY:10.5,verticalAlign:"top",width:24,y:10}};v.exporting={type:"image/png",url:"http://export.highcharts.com/",width:800,buttons:{exportButton:{symbol:"exportIcon",x:-10,symbolFill:"#A8BF77",hoverSymbolFill:"#768F3E",_id:"exportButton",_titleKey:"exportButtonTitle",menuItems:[{textKey:"downloadPNG",onclick:function(){this.exportChart()}},{textKey:"downloadJPEG",onclick:function(){this.exportChart({type:"image/jpeg"})}},{textKey:"downloadPDF",
12
+ onclick:function(){this.exportChart({type:"application/pdf"})}},{textKey:"downloadSVG",onclick:function(){this.exportChart({type:"image/svg+xml"})}}]},printButton:{symbol:"printIcon",x:-36,symbolFill:"#B5C9DF",hoverSymbolFill:"#779ABF",_id:"printButton",_titleKey:"printButtonTitle",onclick:function(){this.print()}}}};n(y.prototype,{getSVG:function(a){var b=this,c,d,e,f=s(b.options,a);if(!h.createElementNS)h.createElementNS=function(a,b){return h.createElement(b)};a=r("div",null,{position:"absolute",
13
+ top:"-9999em",width:b.chartWidth+"px",height:b.chartHeight+"px"},h.body);n(f.chart,{renderTo:a,forExport:!0});f.exporting.enabled=!1;f.chart.plotBackgroundImage=null;f.series=[];k(b.series,function(a){e=s(a.options,{animation:!1,showCheckbox:!1,visible:a.visible});if(!e.isInternal){if(e&&e.marker&&/^url\(/.test(e.marker.symbol))e.marker.symbol="circle";f.series.push(e)}});c=new Highcharts.Chart(f);k(["xAxis","yAxis"],function(a){k(b[a],function(b,d){var e=c[a][d],f=b.getExtremes(),g=f.userMin,f=f.userMax;
14
+ (g!==void 0||f!==void 0)&&e.setExtremes(g,f,!0,!1)})});d=c.container.innerHTML;f=null;c.destroy();u(a);d=d.replace(/zIndex="[^"]+"/g,"").replace(/isShadow="[^"]+"/g,"").replace(/symbolName="[^"]+"/g,"").replace(/jQuery[0-9]+="[^"]+"/g,"").replace(/isTracker="[^"]+"/g,"").replace(/url\([^#]+#/g,"url(#").replace(/<svg /,'<svg xmlns:xlink="http://www.w3.org/1999/xlink" ').replace(/ href=/g," xlink:href=").replace(/\n/," ").replace(/<\/svg>.*?$/,"</svg>").replace(/&nbsp;/g," ").replace(/&shy;/g,"­").replace(/<IMG /g,
15
+ "<image ").replace(/height=([^" ]+)/g,'height="$1"').replace(/width=([^" ]+)/g,'width="$1"').replace(/hc-svg-href="([^"]+)">/g,'xlink:href="$1"/>').replace(/id=([^" >]+)/g,'id="$1"').replace(/class=([^" ]+)/g,'class="$1"').replace(/ transform /g," ").replace(/:(path|rect)/g,"$1").replace(/style="([^"]+)"/g,function(a){return a.toLowerCase()});d=d.replace(/(url\(#highcharts-[0-9]+)&quot;/g,"$1").replace(/&quot;/g,"'");d.match(/ xmlns="/g).length===2&&(d=d.replace(/xmlns="[^"]+"/,""));return d},exportChart:function(a,
16
+ b){var c,d=this.getSVG(s(this.options.exporting.chartOptions,b)),a=s(this.options.exporting,a);c=r("form",{method:"post",action:a.url,enctype:"multipart/form-data"},{display:"none"},h.body);k(["filename","type","width","svg"],function(b){r("input",{type:"hidden",name:b,value:{filename:a.filename||"chart",type:a.type,width:a.width,svg:d}[b]},null,c)});c.submit();u(c)},print:function(){var a=this,b=a.container,c=[],d=b.parentNode,e=h.body,f=e.childNodes;if(!a.isPrinting)a.isPrinting=!0,k(f,function(a,
17
+ b){if(a.nodeType===1)c[b]=a.style.display,a.style.display="none"}),e.appendChild(b),D.print(),setTimeout(function(){d.appendChild(b);k(f,function(a,b){if(a.nodeType===1)a.style.display=c[b]});a.isPrinting=!1},1E3)},contextMenu:function(a,b,c,d,e,f){var i=this,g=i.options.navigation,h=g.menuItemStyle,o=i.chartWidth,p=i.chartHeight,q="cache-"+a,j=i[q],l=C(e,f),m,w;if(!j)i[q]=j=r("div",{className:"highcharts-"+a},{position:"absolute",zIndex:1E3,padding:l+"px"},i.container),m=r("div",null,n({MozBoxShadow:"3px 3px 10px #888",
18
+ WebkitBoxShadow:"3px 3px 10px #888",boxShadow:"3px 3px 10px #888"},g.menuStyle),j),w=function(){t(j,{display:"none"})},z(j,"mouseleave",w),k(b,function(a){if(a){var b=r("div",{onmouseover:function(){t(this,g.menuItemHoverStyle)},onmouseout:function(){t(this,h)},innerHTML:a.text||i.options.lang[a.textKey]},n({cursor:"pointer"},h),m);b[A?"ontouchstart":"onclick"]=function(){w();a.onclick.apply(i,arguments)};i.exportDivElements.push(b)}}),i.exportDivElements.push(m,j),i.exportMenuWidth=j.offsetWidth,
19
+ i.exportMenuHeight=j.offsetHeight;a={display:"block"};c+i.exportMenuWidth>o?a.right=o-c-e-l+"px":a.left=c-l+"px";d+f+i.exportMenuHeight>p?a.bottom=p-d-l+"px":a.top=d+f-l+"px";t(j,a)},addButton:function(a){function b(){p.attr(l);o.attr(j)}var c=this,d=c.renderer,e=s(c.options.navigation.buttonOptions,a),f=e.onclick,g=e.menuItems,h=e.width,k=e.height,o,p,q,a=e.borderWidth,j={stroke:e.borderColor},l={stroke:e.symbolStroke,fill:e.symbolFill},m=e.symbolSize||12;if(!c.exportDivElements)c.exportDivElements=
20
+ [],c.exportSVGElements=[];e.enabled!==!1&&(o=d.rect(0,0,h,k,e.borderRadius,a).align(e,!0).attr(n({fill:e.backgroundColor,"stroke-width":a,zIndex:19},j)).add(),q=d.rect(0,0,h,k,0).align(e).attr({id:e._id,fill:"rgba(255, 255, 255, 0.001)",title:c.options.lang[e._titleKey],zIndex:21}).css({cursor:"pointer"}).on("mouseover",function(){p.attr({stroke:e.hoverSymbolStroke,fill:e.hoverSymbolFill});o.attr({stroke:e.hoverBorderColor})}).on("mouseout",b).on("click",b).add(),g&&(f=function(){b();var a=q.getBBox();
21
+ c.contextMenu("export-menu",g,a.x,a.y,h,k)}),q.on("click",function(){f.apply(c,arguments)}),p=d.symbol(e.symbol,e.symbolX-m/2,e.symbolY-m/2,m,m).align(e,!0).attr(n(l,{"stroke-width":e.symbolStrokeWidth||1,zIndex:20})).add(),c.exportSVGElements.push(o,q,p))},destroyExport:function(){var a,b;for(a=0;a<this.exportSVGElements.length;a++)b=this.exportSVGElements[a],b.onclick=b.ontouchstart=null,this.exportSVGElements[a]=b.destroy();for(a=0;a<this.exportDivElements.length;a++)b=this.exportDivElements[a],
22
+ B(b,"mouseleave"),this.exportDivElements[a]=b.onmouseout=b.onmouseover=b.ontouchstart=b.onclick=null,u(b)}});g.Renderer.prototype.symbols.exportIcon=function(a,b,c,d){return x(["M",a,b+c,"L",a+c,b+d,a+c,b+d*0.8,a,b+d*0.8,"Z","M",a+c*0.5,b+d*0.8,"L",a+c*0.8,b+d*0.4,a+c*0.4,b+d*0.4,a+c*0.4,b,a+c*0.6,b,a+c*0.6,b+d*0.4,a+c*0.2,b+d*0.4,"Z"])};g.Renderer.prototype.symbols.printIcon=function(a,b,c,d){return x(["M",a,b+d*0.7,"L",a+c,b+d*0.7,a+c,b+d*0.4,a,b+d*0.4,"Z","M",a+c*0.2,b+d*0.4,"L",a+c*0.2,b,a+c*
23
+ 0.8,b,a+c*0.8,b+d*0.4,"Z","M",a+c*0.2,b+d*0.7,"L",a,b+d,a+c,b+d,a+c*0.8,b+d*0.7,"Z"])};y.prototype.callbacks.push(function(a){var b,c=a.options.exporting,d=c.buttons;if(c.enabled!==!1){for(b in d)a.addButton(d[b]);z(a,"destroy",a.destroyExport)}})})();
@@ -0,0 +1,736 @@
1
+ /**
2
+ * @license Highcharts JS v2.3.3 (2012-10-04)
3
+ * Exporting module
4
+ *
5
+ * (c) 2010-2011 Torstein Hønsi
6
+ *
7
+ * License: www.highcharts.com/license
8
+ */
9
+
10
+ // JSLint options:
11
+ /*global Highcharts, document, window, Math, setTimeout */
12
+
13
+ (function () { // encapsulate
14
+
15
+ // create shortcuts
16
+ var HC = Highcharts,
17
+ Chart = HC.Chart,
18
+ addEvent = HC.addEvent,
19
+ removeEvent = HC.removeEvent,
20
+ createElement = HC.createElement,
21
+ discardElement = HC.discardElement,
22
+ css = HC.css,
23
+ merge = HC.merge,
24
+ each = HC.each,
25
+ extend = HC.extend,
26
+ math = Math,
27
+ mathMax = math.max,
28
+ doc = document,
29
+ win = window,
30
+ hasTouch = doc.documentElement.ontouchstart !== undefined,
31
+ M = 'M',
32
+ L = 'L',
33
+ DIV = 'div',
34
+ HIDDEN = 'hidden',
35
+ NONE = 'none',
36
+ PREFIX = 'highcharts-',
37
+ ABSOLUTE = 'absolute',
38
+ PX = 'px',
39
+ UNDEFINED,
40
+ defaultOptions = HC.getOptions();
41
+
42
+ // Add language
43
+ extend(defaultOptions.lang, {
44
+ downloadPNG: 'Download PNG image',
45
+ downloadJPEG: 'Download JPEG image',
46
+ downloadPDF: 'Download PDF document',
47
+ downloadSVG: 'Download SVG vector image',
48
+ exportButtonTitle: 'Export to raster or vector image',
49
+ printButtonTitle: 'Print the chart'
50
+ });
51
+
52
+ // Buttons and menus are collected in a separate config option set called 'navigation'.
53
+ // This can be extended later to add control buttons like zoom and pan right click menus.
54
+ defaultOptions.navigation = {
55
+ menuStyle: {
56
+ border: '1px solid #A0A0A0',
57
+ background: '#FFFFFF'
58
+ },
59
+ menuItemStyle: {
60
+ padding: '0 5px',
61
+ background: NONE,
62
+ color: '#303030',
63
+ fontSize: hasTouch ? '14px' : '11px'
64
+ },
65
+ menuItemHoverStyle: {
66
+ background: '#4572A5',
67
+ color: '#FFFFFF'
68
+ },
69
+
70
+ buttonOptions: {
71
+ align: 'right',
72
+ backgroundColor: {
73
+ linearGradient: [0, 0, 0, 20],
74
+ stops: [
75
+ [0.4, '#F7F7F7'],
76
+ [0.6, '#E3E3E3']
77
+ ]
78
+ },
79
+ borderColor: '#B0B0B0',
80
+ borderRadius: 3,
81
+ borderWidth: 1,
82
+ //enabled: true,
83
+ height: 20,
84
+ hoverBorderColor: '#909090',
85
+ hoverSymbolFill: '#81A7CF',
86
+ hoverSymbolStroke: '#4572A5',
87
+ symbolFill: '#E0E0E0',
88
+ //symbolSize: 12,
89
+ symbolStroke: '#A0A0A0',
90
+ //symbolStrokeWidth: 1,
91
+ symbolX: 11.5,
92
+ symbolY: 10.5,
93
+ verticalAlign: 'top',
94
+ width: 24,
95
+ y: 10
96
+ }
97
+ };
98
+
99
+
100
+
101
+ // Add the export related options
102
+ defaultOptions.exporting = {
103
+ //enabled: true,
104
+ //filename: 'chart',
105
+ type: 'image/png',
106
+ url: 'http://export.highcharts.com/',
107
+ width: 800,
108
+ buttons: {
109
+ exportButton: {
110
+ //enabled: true,
111
+ symbol: 'exportIcon',
112
+ x: -10,
113
+ symbolFill: '#A8BF77',
114
+ hoverSymbolFill: '#768F3E',
115
+ _id: 'exportButton',
116
+ _titleKey: 'exportButtonTitle',
117
+ menuItems: [{
118
+ textKey: 'downloadPNG',
119
+ onclick: function () {
120
+ this.exportChart();
121
+ }
122
+ }, {
123
+ textKey: 'downloadJPEG',
124
+ onclick: function () {
125
+ this.exportChart({
126
+ type: 'image/jpeg'
127
+ });
128
+ }
129
+ }, {
130
+ textKey: 'downloadPDF',
131
+ onclick: function () {
132
+ this.exportChart({
133
+ type: 'application/pdf'
134
+ });
135
+ }
136
+ }, {
137
+ textKey: 'downloadSVG',
138
+ onclick: function () {
139
+ this.exportChart({
140
+ type: 'image/svg+xml'
141
+ });
142
+ }
143
+ }
144
+ // Enable this block to add "View SVG" to the dropdown menu
145
+ /*
146
+ ,{
147
+
148
+ text: 'View SVG',
149
+ onclick: function () {
150
+ var svg = this.getSVG()
151
+ .replace(/</g, '\n&lt;')
152
+ .replace(/>/g, '&gt;');
153
+
154
+ doc.body.innerHTML = '<pre>' + svg + '</pre>';
155
+ }
156
+ } // */
157
+ ]
158
+
159
+ },
160
+ printButton: {
161
+ //enabled: true,
162
+ symbol: 'printIcon',
163
+ x: -36,
164
+ symbolFill: '#B5C9DF',
165
+ hoverSymbolFill: '#779ABF',
166
+ _id: 'printButton',
167
+ _titleKey: 'printButtonTitle',
168
+ onclick: function () {
169
+ this.print();
170
+ }
171
+ }
172
+ }
173
+ };
174
+
175
+
176
+
177
+ extend(Chart.prototype, {
178
+ /**
179
+ * Return an SVG representation of the chart
180
+ *
181
+ * @param additionalOptions {Object} Additional chart options for the generated SVG representation
182
+ */
183
+ getSVG: function (additionalOptions) {
184
+ var chart = this,
185
+ chartCopy,
186
+ sandbox,
187
+ svg,
188
+ seriesOptions,
189
+ options = merge(chart.options, additionalOptions); // copy the options and add extra options
190
+
191
+ // IE compatibility hack for generating SVG content that it doesn't really understand
192
+ if (!doc.createElementNS) {
193
+ /*jslint unparam: true*//* allow unused parameter ns in function below */
194
+ doc.createElementNS = function (ns, tagName) {
195
+ return doc.createElement(tagName);
196
+ };
197
+ /*jslint unparam: false*/
198
+ }
199
+
200
+ // create a sandbox where a new chart will be generated
201
+ sandbox = createElement(DIV, null, {
202
+ position: ABSOLUTE,
203
+ top: '-9999em',
204
+ width: chart.chartWidth + PX,
205
+ height: chart.chartHeight + PX
206
+ }, doc.body);
207
+
208
+ // override some options
209
+ extend(options.chart, {
210
+ renderTo: sandbox,
211
+ forExport: true
212
+ });
213
+ options.exporting.enabled = false; // hide buttons in print
214
+ options.chart.plotBackgroundImage = null; // the converter doesn't handle images
215
+
216
+ // prepare for replicating the chart
217
+ options.series = [];
218
+ each(chart.series, function (serie) {
219
+ seriesOptions = merge(serie.options, {
220
+ animation: false, // turn off animation
221
+ showCheckbox: false,
222
+ visible: serie.visible
223
+ });
224
+
225
+ if (!seriesOptions.isInternal) { // used for the navigator series that has its own option set
226
+
227
+ // remove image markers
228
+ if (seriesOptions && seriesOptions.marker && /^url\(/.test(seriesOptions.marker.symbol)) {
229
+ seriesOptions.marker.symbol = 'circle';
230
+ }
231
+
232
+ options.series.push(seriesOptions);
233
+ }
234
+ });
235
+
236
+ // generate the chart copy
237
+ chartCopy = new Highcharts.Chart(options);
238
+
239
+ // reflect axis extremes in the export
240
+ each(['xAxis', 'yAxis'], function (axisType) {
241
+ each(chart[axisType], function (axis, i) {
242
+ var axisCopy = chartCopy[axisType][i],
243
+ extremes = axis.getExtremes(),
244
+ userMin = extremes.userMin,
245
+ userMax = extremes.userMax;
246
+
247
+ if (userMin !== UNDEFINED || userMax !== UNDEFINED) {
248
+ axisCopy.setExtremes(userMin, userMax, true, false);
249
+ }
250
+ });
251
+ });
252
+
253
+ // get the SVG from the container's innerHTML
254
+ svg = chartCopy.container.innerHTML;
255
+
256
+ // free up memory
257
+ options = null;
258
+ chartCopy.destroy();
259
+ discardElement(sandbox);
260
+
261
+ // sanitize
262
+ svg = svg
263
+ .replace(/zIndex="[^"]+"/g, '')
264
+ .replace(/isShadow="[^"]+"/g, '')
265
+ .replace(/symbolName="[^"]+"/g, '')
266
+ .replace(/jQuery[0-9]+="[^"]+"/g, '')
267
+ .replace(/isTracker="[^"]+"/g, '')
268
+ .replace(/url\([^#]+#/g, 'url(#')
269
+ .replace(/<svg /, '<svg xmlns:xlink="http://www.w3.org/1999/xlink" ')
270
+ .replace(/ href=/g, ' xlink:href=')
271
+ .replace(/\n/, ' ')
272
+ .replace(/<\/svg>.*?$/, '</svg>') // any HTML added to the container after the SVG (#894)
273
+ /* This fails in IE < 8
274
+ .replace(/([0-9]+)\.([0-9]+)/g, function(s1, s2, s3) { // round off to save weight
275
+ return s2 +'.'+ s3[0];
276
+ })*/
277
+
278
+ // Replace HTML entities, issue #347
279
+ .replace(/&nbsp;/g, '\u00A0') // no-break space
280
+ .replace(/&shy;/g, '\u00AD') // soft hyphen
281
+
282
+ // IE specific
283
+ .replace(/<IMG /g, '<image ')
284
+ .replace(/height=([^" ]+)/g, 'height="$1"')
285
+ .replace(/width=([^" ]+)/g, 'width="$1"')
286
+ .replace(/hc-svg-href="([^"]+)">/g, 'xlink:href="$1"/>')
287
+ .replace(/id=([^" >]+)/g, 'id="$1"')
288
+ .replace(/class=([^" ]+)/g, 'class="$1"')
289
+ .replace(/ transform /g, ' ')
290
+ .replace(/:(path|rect)/g, '$1')
291
+ .replace(/style="([^"]+)"/g, function (s) {
292
+ return s.toLowerCase();
293
+ });
294
+
295
+ // IE9 beta bugs with innerHTML. Test again with final IE9.
296
+ svg = svg.replace(/(url\(#highcharts-[0-9]+)&quot;/g, '$1')
297
+ .replace(/&quot;/g, "'");
298
+ if (svg.match(/ xmlns="/g).length === 2) {
299
+ svg = svg.replace(/xmlns="[^"]+"/, '');
300
+ }
301
+
302
+ return svg;
303
+ },
304
+
305
+ /**
306
+ * Submit the SVG representation of the chart to the server
307
+ * @param {Object} options Exporting options. Possible members are url, type and width.
308
+ * @param {Object} chartOptions Additional chart options for the SVG representation of the chart
309
+ */
310
+ exportChart: function (options, chartOptions) {
311
+ var form,
312
+ chart = this,
313
+ svg = chart.getSVG(merge(chart.options.exporting.chartOptions, chartOptions)); // docs
314
+
315
+ // merge the options
316
+ options = merge(chart.options.exporting, options);
317
+
318
+ // create the form
319
+ form = createElement('form', {
320
+ method: 'post',
321
+ action: options.url,
322
+ enctype: 'multipart/form-data'
323
+ }, {
324
+ display: NONE
325
+ }, doc.body);
326
+
327
+ // add the values
328
+ each(['filename', 'type', 'width', 'svg'], function (name) {
329
+ createElement('input', {
330
+ type: HIDDEN,
331
+ name: name,
332
+ value: {
333
+ filename: options.filename || 'chart',
334
+ type: options.type,
335
+ width: options.width,
336
+ svg: svg
337
+ }[name]
338
+ }, null, form);
339
+ });
340
+
341
+ // submit
342
+ form.submit();
343
+
344
+ // clean up
345
+ discardElement(form);
346
+ },
347
+
348
+ /**
349
+ * Print the chart
350
+ */
351
+ print: function () {
352
+
353
+ var chart = this,
354
+ container = chart.container,
355
+ origDisplay = [],
356
+ origParent = container.parentNode,
357
+ body = doc.body,
358
+ childNodes = body.childNodes;
359
+
360
+ if (chart.isPrinting) { // block the button while in printing mode
361
+ return;
362
+ }
363
+
364
+ chart.isPrinting = true;
365
+
366
+ // hide all body content
367
+ each(childNodes, function (node, i) {
368
+ if (node.nodeType === 1) {
369
+ origDisplay[i] = node.style.display;
370
+ node.style.display = NONE;
371
+ }
372
+ });
373
+
374
+ // pull out the chart
375
+ body.appendChild(container);
376
+
377
+ // print
378
+ win.print();
379
+
380
+ // allow the browser to prepare before reverting
381
+ setTimeout(function () {
382
+
383
+ // put the chart back in
384
+ origParent.appendChild(container);
385
+
386
+ // restore all body content
387
+ each(childNodes, function (node, i) {
388
+ if (node.nodeType === 1) {
389
+ node.style.display = origDisplay[i];
390
+ }
391
+ });
392
+
393
+ chart.isPrinting = false;
394
+
395
+ }, 1000);
396
+
397
+ },
398
+
399
+ /**
400
+ * Display a popup menu for choosing the export type
401
+ *
402
+ * @param {String} name An identifier for the menu
403
+ * @param {Array} items A collection with text and onclicks for the items
404
+ * @param {Number} x The x position of the opener button
405
+ * @param {Number} y The y position of the opener button
406
+ * @param {Number} width The width of the opener button
407
+ * @param {Number} height The height of the opener button
408
+ */
409
+ contextMenu: function (name, items, x, y, width, height) {
410
+ var chart = this,
411
+ navOptions = chart.options.navigation,
412
+ menuItemStyle = navOptions.menuItemStyle,
413
+ chartWidth = chart.chartWidth,
414
+ chartHeight = chart.chartHeight,
415
+ cacheName = 'cache-' + name,
416
+ menu = chart[cacheName],
417
+ menuPadding = mathMax(width, height), // for mouse leave detection
418
+ boxShadow = '3px 3px 10px #888',
419
+ innerMenu,
420
+ hide,
421
+ menuStyle;
422
+
423
+ // create the menu only the first time
424
+ if (!menu) {
425
+
426
+ // create a HTML element above the SVG
427
+ chart[cacheName] = menu = createElement(DIV, {
428
+ className: PREFIX + name
429
+ }, {
430
+ position: ABSOLUTE,
431
+ zIndex: 1000,
432
+ padding: menuPadding + PX
433
+ }, chart.container);
434
+
435
+ innerMenu = createElement(DIV, null,
436
+ extend({
437
+ MozBoxShadow: boxShadow,
438
+ WebkitBoxShadow: boxShadow,
439
+ boxShadow: boxShadow
440
+ }, navOptions.menuStyle), menu);
441
+
442
+ // hide on mouse out
443
+ hide = function () {
444
+ css(menu, { display: NONE });
445
+ };
446
+
447
+ addEvent(menu, 'mouseleave', hide);
448
+
449
+
450
+ // create the items
451
+ each(items, function (item) {
452
+ if (item) {
453
+ var div = createElement(DIV, {
454
+ onmouseover: function () {
455
+ css(this, navOptions.menuItemHoverStyle);
456
+ },
457
+ onmouseout: function () {
458
+ css(this, menuItemStyle);
459
+ },
460
+ innerHTML: item.text || chart.options.lang[item.textKey]
461
+ }, extend({
462
+ cursor: 'pointer'
463
+ }, menuItemStyle), innerMenu);
464
+
465
+ div[hasTouch ? 'ontouchstart' : 'onclick'] = function () {
466
+ hide();
467
+ item.onclick.apply(chart, arguments);
468
+ };
469
+
470
+ // Keep references to menu divs to be able to destroy them
471
+ chart.exportDivElements.push(div);
472
+ }
473
+ });
474
+
475
+ // Keep references to menu and innerMenu div to be able to destroy them
476
+ chart.exportDivElements.push(innerMenu, menu);
477
+
478
+ chart.exportMenuWidth = menu.offsetWidth;
479
+ chart.exportMenuHeight = menu.offsetHeight;
480
+ }
481
+
482
+ menuStyle = { display: 'block' };
483
+
484
+ // if outside right, right align it
485
+ if (x + chart.exportMenuWidth > chartWidth) {
486
+ menuStyle.right = (chartWidth - x - width - menuPadding) + PX;
487
+ } else {
488
+ menuStyle.left = (x - menuPadding) + PX;
489
+ }
490
+ // if outside bottom, bottom align it
491
+ if (y + height + chart.exportMenuHeight > chartHeight) {
492
+ menuStyle.bottom = (chartHeight - y - menuPadding) + PX;
493
+ } else {
494
+ menuStyle.top = (y + height - menuPadding) + PX;
495
+ }
496
+
497
+ css(menu, menuStyle);
498
+ },
499
+
500
+ /**
501
+ * Add the export button to the chart
502
+ */
503
+ addButton: function (options) {
504
+ var chart = this,
505
+ renderer = chart.renderer,
506
+ btnOptions = merge(chart.options.navigation.buttonOptions, options),
507
+ onclick = btnOptions.onclick,
508
+ menuItems = btnOptions.menuItems,
509
+ buttonWidth = btnOptions.width,
510
+ buttonHeight = btnOptions.height,
511
+ box,
512
+ symbol,
513
+ button,
514
+ borderWidth = btnOptions.borderWidth,
515
+ boxAttr = {
516
+ stroke: btnOptions.borderColor
517
+
518
+ },
519
+ symbolAttr = {
520
+ stroke: btnOptions.symbolStroke,
521
+ fill: btnOptions.symbolFill
522
+ },
523
+ symbolSize = btnOptions.symbolSize || 12;
524
+
525
+ // Keeps references to the button elements
526
+ if (!chart.exportDivElements) {
527
+ chart.exportDivElements = [];
528
+ chart.exportSVGElements = [];
529
+ }
530
+
531
+ if (btnOptions.enabled === false) {
532
+ return;
533
+ }
534
+
535
+ // element to capture the click
536
+ function revert() {
537
+ symbol.attr(symbolAttr);
538
+ box.attr(boxAttr);
539
+ }
540
+
541
+ // the box border
542
+ box = renderer.rect(
543
+ 0,
544
+ 0,
545
+ buttonWidth,
546
+ buttonHeight,
547
+ btnOptions.borderRadius,
548
+ borderWidth
549
+ )
550
+ //.translate(buttonLeft, buttonTop) // to allow gradients
551
+ .align(btnOptions, true)
552
+ .attr(extend({
553
+ fill: btnOptions.backgroundColor,
554
+ 'stroke-width': borderWidth,
555
+ zIndex: 19
556
+ }, boxAttr)).add();
557
+
558
+ // the invisible element to track the clicks
559
+ button = renderer.rect(
560
+ 0,
561
+ 0,
562
+ buttonWidth,
563
+ buttonHeight,
564
+ 0
565
+ )
566
+ .align(btnOptions)
567
+ .attr({
568
+ id: btnOptions._id,
569
+ fill: 'rgba(255, 255, 255, 0.001)',
570
+ title: chart.options.lang[btnOptions._titleKey],
571
+ zIndex: 21
572
+ }).css({
573
+ cursor: 'pointer'
574
+ })
575
+ .on('mouseover', function () {
576
+ symbol.attr({
577
+ stroke: btnOptions.hoverSymbolStroke,
578
+ fill: btnOptions.hoverSymbolFill
579
+ });
580
+ box.attr({
581
+ stroke: btnOptions.hoverBorderColor
582
+ });
583
+ })
584
+ .on('mouseout', revert)
585
+ .on('click', revert)
586
+ .add();
587
+
588
+ // add the click event
589
+ if (menuItems) {
590
+ onclick = function () {
591
+ revert();
592
+ var bBox = button.getBBox();
593
+ chart.contextMenu('export-menu', menuItems, bBox.x, bBox.y, buttonWidth, buttonHeight);
594
+ };
595
+ }
596
+ /*addEvent(button.element, 'click', function() {
597
+ onclick.apply(chart, arguments);
598
+ });*/
599
+ button.on('click', function () {
600
+ onclick.apply(chart, arguments);
601
+ });
602
+
603
+ // the icon
604
+ symbol = renderer.symbol(
605
+ btnOptions.symbol,
606
+ btnOptions.symbolX - (symbolSize / 2),
607
+ btnOptions.symbolY - (symbolSize / 2),
608
+ symbolSize,
609
+ symbolSize
610
+ )
611
+ .align(btnOptions, true)
612
+ .attr(extend(symbolAttr, {
613
+ 'stroke-width': btnOptions.symbolStrokeWidth || 1,
614
+ zIndex: 20
615
+ })).add();
616
+
617
+ // Keep references to the renderer element so to be able to destroy them later.
618
+ chart.exportSVGElements.push(box, button, symbol);
619
+ },
620
+
621
+ /**
622
+ * Destroy the buttons.
623
+ */
624
+ destroyExport: function () {
625
+ var i,
626
+ chart = this,
627
+ elem;
628
+
629
+ // Destroy the extra buttons added
630
+ for (i = 0; i < chart.exportSVGElements.length; i++) {
631
+ elem = chart.exportSVGElements[i];
632
+ // Destroy and null the svg/vml elements
633
+ elem.onclick = elem.ontouchstart = null;
634
+ chart.exportSVGElements[i] = elem.destroy();
635
+ }
636
+
637
+ // Destroy the divs for the menu
638
+ for (i = 0; i < chart.exportDivElements.length; i++) {
639
+ elem = chart.exportDivElements[i];
640
+
641
+ // Remove the event handler
642
+ removeEvent(elem, 'mouseleave');
643
+
644
+ // Remove inline events
645
+ chart.exportDivElements[i] = elem.onmouseout = elem.onmouseover = elem.ontouchstart = elem.onclick = null;
646
+
647
+ // Destroy the div by moving to garbage bin
648
+ discardElement(elem);
649
+ }
650
+ }
651
+ });
652
+
653
+ /**
654
+ * Crisp for 1px stroke width, which is default. In the future, consider a smarter,
655
+ * global function.
656
+ */
657
+ function crisp(arr) {
658
+ var i = arr.length;
659
+ while (i--) {
660
+ if (typeof arr[i] === 'number') {
661
+ arr[i] = Math.round(arr[i]) - 0.5;
662
+ }
663
+ }
664
+ return arr;
665
+ }
666
+
667
+ // Create the export icon
668
+ HC.Renderer.prototype.symbols.exportIcon = function (x, y, width, height) {
669
+ return crisp([
670
+ M, // the disk
671
+ x, y + width,
672
+ L,
673
+ x + width, y + height,
674
+ x + width, y + height * 0.8,
675
+ x, y + height * 0.8,
676
+ 'Z',
677
+ M, // the arrow
678
+ x + width * 0.5, y + height * 0.8,
679
+ L,
680
+ x + width * 0.8, y + height * 0.4,
681
+ x + width * 0.4, y + height * 0.4,
682
+ x + width * 0.4, y,
683
+ x + width * 0.6, y,
684
+ x + width * 0.6, y + height * 0.4,
685
+ x + width * 0.2, y + height * 0.4,
686
+ 'Z'
687
+ ]);
688
+ };
689
+ // Create the print icon
690
+ HC.Renderer.prototype.symbols.printIcon = function (x, y, width, height) {
691
+ return crisp([
692
+ M, // the printer
693
+ x, y + height * 0.7,
694
+ L,
695
+ x + width, y + height * 0.7,
696
+ x + width, y + height * 0.4,
697
+ x, y + height * 0.4,
698
+ 'Z',
699
+ M, // the upper sheet
700
+ x + width * 0.2, y + height * 0.4,
701
+ L,
702
+ x + width * 0.2, y,
703
+ x + width * 0.8, y,
704
+ x + width * 0.8, y + height * 0.4,
705
+ 'Z',
706
+ M, // the lower sheet
707
+ x + width * 0.2, y + height * 0.7,
708
+ L,
709
+ x, y + height,
710
+ x + width, y + height,
711
+ x + width * 0.8, y + height * 0.7,
712
+ 'Z'
713
+ ]);
714
+ };
715
+
716
+
717
+ // Add the buttons on chart load
718
+ Chart.prototype.callbacks.push(function (chart) {
719
+ var n,
720
+ exportingOptions = chart.options.exporting,
721
+ buttons = exportingOptions.buttons;
722
+
723
+ if (exportingOptions.enabled !== false) {
724
+
725
+ for (n in buttons) {
726
+ chart.addButton(buttons[n]);
727
+ }
728
+
729
+ // Destroy the export elements at chart destroy
730
+ addEvent(chart, 'destroy', chart.destroyExport);
731
+ }
732
+
733
+ });
734
+
735
+
736
+ }());