Better looking settings tabs in pmahomme
[phpmyadmin.git] / js / highcharts / exporting.js
blob6ac3a87ddcc464e75f7f886e140041e9b57195fb
1 /** 
2  * @license Highcharts JS v2.1.4 (2011-03-02)
3  * Exporting module
4  * 
5  * (c) 2010 Torstein Hønsi
6  * 
7  * License: www.highcharts.com/license
8  *
9  * Please Note: This file has been adjusted for use in phpMyAdmin, 
10  * to allow chart exporting without the batik library
11  */
13 // JSLint options:
14 /*global Highcharts, document, window, Math, setTimeout */
16 (function() { // encapsulate
18 // create shortcuts
19 var HC = Highcharts,
20     Chart = HC.Chart,
21     addEvent = HC.addEvent,
22     createElement = HC.createElement,
23     discardElement = HC.discardElement,
24     css = HC.css,
25     merge = HC.merge,
26     each = HC.each,
27     extend = HC.extend,
28     math = Math,
29     mathMax = math.max,
30     doc = document,
31     win = window,
32     hasTouch = 'ontouchstart' in doc.documentElement,   
33     M = 'M',
34     L = 'L',
35     DIV = 'div',
36     HIDDEN = 'hidden',
37     NONE = 'none',
38     PREFIX = 'highcharts-',
39     ABSOLUTE = 'absolute',
40     PX = 'px',
44     // Add language and get the defaultOptions
45     defaultOptions = HC.setOptions({
46         lang: {
47             downloadPNG: 'Download PNG image',
48             downloadJPEG: 'Download JPEG image',
49             downloadPDF: 'Download PDF document',
50             downloadSVG: 'Download SVG vector image',
51             exportButtonTitle: 'Export to raster or vector image',
52             printButton: 'Print the chart'
53         }
54     });
56 // Buttons and menus are collected in a separate config option set called 'navigation'.
57 // This can be extended later to add control buttons like zoom and pan right click menus.
58 defaultOptions.navigation = {
59     menuStyle: {
60         border: '1px solid #A0A0A0',
61         background: '#FFFFFF'
62     },
63     menuItemStyle: {
64         padding: '0 5px',
65         background: NONE,
66         color: '#303030',
67         fontSize: hasTouch ? '14px' : '11px'
68     },
69     menuItemHoverStyle: {
70         background: '#4572A5',
71         color: '#FFFFFF'
72     },
73     
74     buttonOptions: {
75         align: 'right',
76         backgroundColor: {
77             linearGradient: [0, 0, 0, 20],
78             stops: [
79                 [0.4, '#F7F7F7'],
80                 [0.6, '#E3E3E3']
81             ]
82         },
83         borderColor: '#B0B0B0',
84         borderRadius: 3,
85         borderWidth: 1,
86         //enabled: true,
87         height: 20,
88         hoverBorderColor: '#909090',
89         hoverSymbolFill: '#81A7CF',
90         hoverSymbolStroke: '#4572A5',
91         symbolFill: '#E0E0E0',
92         //symbolSize: 12,
93         symbolStroke: '#A0A0A0',
94         //symbolStrokeWidth: 1,
95         symbolX: 11.5,
96         symbolY: 10.5,
97         verticalAlign: 'top',
98         width: 24,
99         y: 10           
100     }
105 // Add the export related options
106 defaultOptions.exporting = {
107     //enabled: true,
108     //filename: 'chart',
109     type: 'image/png',
110     url: 'file_echo.php',
111     width: 800,
112     buttons:  {
113         exportButton: {
114             //enabled: true,
115             symbol: 'exportIcon',
116             x: -10,
117             symbolFill: '#A8BF77',
118             hoverSymbolFill: '#768F3E',
119             _titleKey: 'exportButtonTitle',
120             menuName: 'export',
121             menuItems: [{
122                 textKey: 'downloadPNG',
123                 onclick: function() {
124                     this.exportChart();
125                 }
126             },{
127                 textKey: 'downloadSVG',
128                 onclick: function() {
129                     this.exportChart({
130                         type: 'image/svg+xml'
131                     });
132                 }
133             },{
134                 textKey: 'printButton',
135                 onclick: function() {
136                     this.print();
137                 }                
138             }]
139             
140         }
141     }
146 extend(Chart.prototype, {
147     /**
148      * Return an SVG representation of the chart
149      * 
150      * @param additionalOptions {Object} Additional chart options for the generated SVG representation
151      */ 
152      getSVG: function(additionalOptions) {
153         var chart = this,
154             chartCopy,
155             sandbox,
156             svg,
157             seriesOptions,
158             config,
159             pointOptions,
160             pointMarker,
161             options = merge(chart.options, additionalOptions); // copy the options and add extra options
162         
163         // IE compatibility hack for generating SVG content that it doesn't really understand
164         if (!doc.createElementNS) {
165             doc.createElementNS = function(ns, tagName) {
166                 var elem = doc.createElement(tagName);
167                 elem.getBBox = function() {
168                     return chart.renderer.Element.prototype.getBBox.apply({ element: elem });
169                 };
170                 return elem;
171             };
172         }
173         
174         // create a sandbox where a new chart will be generated
175         sandbox = createElement(DIV, null, {
176             position: ABSOLUTE,
177             top: '-9999em',
178             width: chart.chartWidth + PX,
179             height: chart.chartHeight + PX
180         }, doc.body);
181         
182         // override some options
183         extend(options.chart, {
184             renderTo: sandbox,
185             forExport: true
186         });
187         options.exporting.enabled = false; // hide buttons in print
188         options.chart.plotBackgroundImage = null; // the converter doesn't handle images
189         // prepare for replicating the chart
190         options.series = [];
191         each(chart.series, function(serie) {
192             seriesOptions = serie.options;                      
193             
194             seriesOptions.animation = false; // turn off animation
195             seriesOptions.showCheckbox = false;
196             
197             // remove image markers
198             if (seriesOptions && seriesOptions.marker && /^url\(/.test(seriesOptions.marker.symbol)) { 
199                 seriesOptions.marker.symbol = 'circle';
200             }
201             
202             seriesOptions.data = [];
203             
204             each(serie.data, function(point) {
205                 
206                 // extend the options by those values that can be expressed in a number or array config
207                 config = point.config;
208                 pointOptions = {
209                     x: point.x,
210                     y: point.y,
211                     name: point.name
212                 };
214                 if (typeof config == 'object' && point.config && config.constructor != Array) {
215                     extend(pointOptions, config);
216                 }
218                 seriesOptions.data.push(pointOptions); // copy fresh updated data
219                                 
220                 // remove image markers
221                 pointMarker = point.config && point.config.marker;
222                 if (pointMarker && /^url\(/.test(pointMarker.symbol)) { 
223                     delete pointMarker.symbol;
224                 }
225             }); 
226             
227             options.series.push(seriesOptions);
228         });
229         
230         // generate the chart copy
231         chartCopy = new Highcharts.Chart(options);
232         
233         // get the SVG from the container's innerHTML
234         svg = chartCopy.container.innerHTML;
235         
236         // free up memory
237         options = null;
238         chartCopy.destroy();
239         discardElement(sandbox);
240         
241         // sanitize
242         svg = svg
243             .replace(/zIndex="[^"]+"/g, '') 
244             .replace(/isShadow="[^"]+"/g, '')
245             .replace(/symbolName="[^"]+"/g, '')
246             .replace(/jQuery[0-9]+="[^"]+"/g, '')
247             .replace(/isTracker="[^"]+"/g, '')
248             .replace(/url\([^#]+#/g, 'url(#')
249             /*.replace(/<svg /, '<svg xmlns:xlink="http://www.w3.org/1999/xlink" ')
250             .replace(/ href=/, ' xlink:href=')
251             .replace(/preserveAspectRatio="none">/g, 'preserveAspectRatio="none"/>')*/
252             /* This fails in IE < 8
253             .replace(/([0-9]+)\.([0-9]+)/g, function(s1, s2, s3) { // round off to save weight
254                 return s2 +'.'+ s3[0];
255             })*/ 
256             
257             // IE specific
258             .replace(/id=([^" >]+)/g, 'id="$1"') 
259             .replace(/class=([^" ]+)/g, 'class="$1"')
260             .replace(/ transform /g, ' ')
261             .replace(/:(path|rect)/g, '$1')
262             .replace(/style="([^"]+)"/g, function(s) {
263                 return s.toLowerCase();
264             });
265             
266         // IE9 beta bugs with innerHTML. Test again with final IE9.
267         svg = svg.replace(/(url\(#highcharts-[0-9]+)&quot;/g, '$1')
268             .replace(/&quot;/g, "'");
269         if (svg.match(/ xmlns="/g).length == 2) {
270             svg = svg.replace(/xmlns="[^"]+"/, '');
271         }
272             
273         return svg;
274     },
275     
276     /**
277      * Submit the SVG representation of the chart to the server
278      * @param {Object} options Exporting options. Possible members are url, type and width.
279      * @param {Object} chartOptions Additional chart options for the SVG representation of the chart
280      */
281     exportChart: function(options, chartOptions) {
282         var form,
283             chart = this,
284             canvas=createElement('canvas');
285         
286         $('body').append(canvas);
287         $(canvas).css('position','absolute');
288         $(canvas).css('left','-10000px');
289         
290         var submitData = function(chartData) {
291                 // merge the options
292                 options = merge(chart.options.exporting, options);
293                 
294                 // create the form
295                 form = createElement('form', {
296                     method: 'post',
297                     action: options.url
298                 }, {
299                     display: NONE
300                 }, doc.body);
301                 
302                 // add the values
303                 each(['filename', 'type', 'width', 'image','token'], function(name) {
304                     createElement('input', {
305                         type: HIDDEN,
306                         name: name,
307                         value: { 
308                             filename: options.filename || 'chart', 
309                             type: options.type, 
310                             width: options.width, 
311                             image: chartData,
312                             token: pma_token 
313                         }[name]
314                     }, null, form);
315                 });
316                 
317                 // submit
318                 form.submit();
319                 
320                 // clean up
321                 discardElement(form);
322         }
323         
324         if(options && options.type=='image/svg+xml') {
325             submitData(chart.getSVG(chartOptions));
326         } else {
327             if (typeof FlashCanvas != "undefined") {
328                 FlashCanvas.initElement(canvas);
329             }
330             
331             // Generate data uri and submit once done
332             canvg(canvas, chart.getSVG(chartOptions),{
333                 ignoreAnimation:true,
334                 ignoreMouse:true,
335                 renderCallback:function() { 
336                     // IE8 fix: flashcanvas doesn't update the canvas immediately, thus requiring setTimeout. 
337                     // See also http://groups.google.com/group/flashcanvas/browse_thread/thread/e36ff7a03e1bfb0a
338                     setTimeout(function() { submitData(canvas.toDataURL()); }, 100); 
339                 }
340             });
341         }
342     },
343     
344     /**
345      * Print the chart
346      */
347     print: function() {
348         
349         var chart = this,
350             container = chart.container,
351             origDisplay = [],
352             origParent = container.parentNode,
353             body = doc.body,
354             childNodes = body.childNodes;
355             
356         if (chart.isPrinting) { // block the button while in printing mode
357             return;
358         }
359         
360         chart.isPrinting = true;
361         
362         // hide all body content        
363         each(childNodes, function(node, i) {
364             if (node.nodeType == 1) {
365                 origDisplay[i] = node.style.display;
366                 node.style.display = NONE;
367             }
368         });
369             
370         // pull out the chart
371         body.appendChild(container);
372          
373         // print
374         win.print();            
375         
376         // allow the browser to prepare before reverting
377         setTimeout(function() {
379             // put the chart back in
380             origParent.appendChild(container);
381             
382             // restore all body content
383             each(childNodes, function(node, i) {
384                 if (node.nodeType == 1) {
385                     node.style.display = origDisplay[i];
386                 }
387             });
388             
389             chart.isPrinting = false;
390             
391         }, 1000);
393     },
394     
395     /**
396      * Display a popup menu for choosing the export type 
397      * 
398      * @param {String} name An identifier for the menu
399      * @param {Array} items A collection with text and onclicks for the items
400      * @param {Number} x The x position of the opener button
401      * @param {Number} y The y position of the opener button
402      * @param {Number} width The width of the opener button
403      * @param {Number} height The height of the opener button
404      */
405     contextMenu: function(name, items, x, y, width, height) {
406         var chart = this,
407             navOptions = chart.options.navigation,
408             menuItemStyle = navOptions.menuItemStyle,
409             chartWidth = chart.chartWidth,
410             chartHeight = chart.chartHeight,
411             cacheName = 'cache-'+ name,
412             menu = chart[cacheName],
413             menuPadding = mathMax(width, height), // for mouse leave detection
414             boxShadow = '3px 3px 10px #888',
415             innerMenu,
416             hide,
417             menuStyle; 
418         
419         // create the menu only the first time
420         if (!menu) {
421             
422             // create a HTML element above the SVG              
423             chart[cacheName] = menu = createElement(DIV, {
424                 className: PREFIX + name
425             }, {
426                 position: ABSOLUTE,
427                 zIndex: 1000,
428                 padding: menuPadding + PX
429             }, chart.container);
430             
431             innerMenu = createElement(DIV, null, 
432                 extend({
433                     MozBoxShadow: boxShadow,
434                     WebkitBoxShadow: boxShadow,
435                     boxShadow: boxShadow
436                 }, navOptions.menuStyle) , menu);
437             
438             // hide on mouse out
439             hide = function() {
440                 css(menu, { display: NONE });
441             };
442             
443             addEvent(menu, 'mouseleave', hide);
444             
445             
446             // create the items
447             each(items, function(item) {
448                 if (item) {
449                     var div = createElement(DIV, {
450                         onmouseover: function() {
451                             css(this, navOptions.menuItemHoverStyle);
452                         },
453                         onmouseout: function() {
454                             css(this, menuItemStyle);
455                         },
456                         innerHTML: item.text || HC.getOptions().lang[item.textKey]
457                     }, extend({
458                         cursor: 'pointer'
459                     }, menuItemStyle), innerMenu);
460                     
461                     div[hasTouch ? 'ontouchstart' : 'onclick'] = function() {
462                         hide();
463                         item.onclick.apply(chart, arguments);
464                     };
465                         
466                 }
467             });
468             
469             chart.exportMenuWidth = menu.offsetWidth;
470             chart.exportMenuHeight = menu.offsetHeight;
471         }
472         
473         menuStyle = { display: 'block' };
474         
475         // if outside right, right align it
476         if (x + chart.exportMenuWidth > chartWidth) {
477             menuStyle.right = (chartWidth - x - width - menuPadding) + PX;
478         } else {
479             menuStyle.left = (x - menuPadding) + PX;
480         }
481         // if outside bottom, bottom align it
482         if (y + height + chart.exportMenuHeight > chartHeight) {
483             menuStyle.bottom = (chartHeight - y - menuPadding)  + PX;
484         } else {
485             menuStyle.top = (y + height - menuPadding) + PX;
486         }
487         
488         css(menu, menuStyle);
489     },
490     
491     /**
492      * Add the export button to the chart
493      */
494     addButton: function(options) {
495         var chart = this,
496             renderer = chart.renderer,
497             btnOptions = merge(chart.options.navigation.buttonOptions, options),
498             onclick = btnOptions.onclick,
499             menuItems = btnOptions.menuItems,
500             //position = chart.getAlignment(btnOptions),
501             /*buttonLeft = position.x,
502             buttonTop = position.y,*/
503             buttonWidth = btnOptions.width,
504             buttonHeight = btnOptions.height,
505             box,
506             symbol,
507             button,     
508             borderWidth = btnOptions.borderWidth,
509             boxAttr = {
510                 stroke: btnOptions.borderColor
511                 
512             },
513             symbolAttr = {
514                 stroke: btnOptions.symbolStroke,
515                 fill: btnOptions.symbolFill
516             };
517             
518         if (btnOptions.enabled === false) {
519             return;
520         }
521         
522         // element to capture the click
523         function revert() {
524             symbol.attr(symbolAttr);
525             box.attr(boxAttr);
526         }
527         
528         // the box border
529         box = renderer.rect(
530             0,
531             0,
532             buttonWidth, 
533             buttonHeight,
534             btnOptions.borderRadius,
535             borderWidth
536         )
537         //.translate(buttonLeft, buttonTop) // to allow gradients
538         .align(btnOptions, true)
539         .attr(extend({
540             fill: btnOptions.backgroundColor,
541             'stroke-width': borderWidth,
542             zIndex: 19
543         }, boxAttr)).add();
544         
545         // the invisible element to track the clicks
546         button = renderer.rect( 
547                 0,
548                 0,
549                 buttonWidth,
550                 buttonHeight,
551                 0
552             )
553             .align(btnOptions)
554             .attr({
555                 fill: 'rgba(255, 255, 255, 0.001)',
556                 title: HC.getOptions().lang[btnOptions._titleKey],
557                 zIndex: 21
558             }).css({
559                 cursor: 'pointer'
560             })
561             .on('mouseover', function() {
562                 symbol.attr({
563                     stroke: btnOptions.hoverSymbolStroke,
564                     fill: btnOptions.hoverSymbolFill
565                 });
566                 box.attr({
567                     stroke: btnOptions.hoverBorderColor
568                 });
569             })
570             .on('mouseout', revert)
571             .on('click', revert)
572             .add();
573         
574         //addEvent(button.element, 'click', revert);
575         
576         // add the click event
577         if (menuItems) {
578             onclick = function(e) {
579                 revert();
580                 var bBox = button.getBBox();
581                 chart.contextMenu(btnOptions.menuName, menuItems, bBox.x, bBox.y, buttonWidth, buttonHeight);
582             };
583         }
584         /*addEvent(button.element, 'click', function() {
585             onclick.apply(chart, arguments);
586         });*/
587         button.on('click', function() {
588             onclick.apply(chart, arguments);
589         });
590                 
591         // the icon
592         symbol = renderer.symbol(
593                 btnOptions.symbol, 
594                 btnOptions.symbolX, 
595                 btnOptions.symbolY, 
596                 (btnOptions.symbolSize || 12) / 2
597             )
598             .align(btnOptions, true)
599             .attr(extend(symbolAttr, {
600                 'stroke-width': btnOptions.symbolStrokeWidth || 1,
601                 zIndex: 20              
602             })).add();
603         
604         
605         
606     }
609 // Create the export icon
610 HC.Renderer.prototype.symbols.exportIcon = function(x, y, radius) {
611     return [
612         M, // the disk
613         x - radius, y + radius,
614         L,
615         x + radius, y + radius,
616         x + radius, y + radius * 0.5,
617         x - radius, y + radius * 0.5,
618         'Z',
619         M, // the arrow
620         x, y + radius * 0.5,
621         L,
622         x - radius * 0.5, y - radius / 3,
623         x - radius / 6, y - radius / 3,
624         x - radius / 6, y - radius,
625         x + radius / 6, y - radius,
626         x + radius / 6, y - radius / 3,
627         x + radius * 0.5, y - radius / 3,
628         'Z'
629     ];
632 // Add the buttons on chart load
633 Chart.prototype.callbacks.push(function(chart) {
634     var n,
635         exportingOptions = chart.options.exporting,
636         buttons = exportingOptions.buttons;
638     if (exportingOptions.enabled !== false) {
640         for (n in buttons) {
641             chart.addButton(buttons[n]);
642         }
643         for (n in chart.options.buttons) {
644             chart.addButton(chart.options.buttons[n]);
645         }
647     }
651 })();