ses-proxy 0.1.0

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 (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
+ }());