2 * @license Highcharts JS v2.1.4 (2011-03-02)
5 * (c) 2010 Torstein Hønsi
7 * License: www.highcharts.com/license
9 * Please Note: This file has been adjusted for use in phpMyAdmin,
10 * to allow chart exporting without the batik library
14 /*global Highcharts, document, window, Math, setTimeout */
16 (function() { // encapsulate
21 addEvent = HC.addEvent,
22 createElement = HC.createElement,
23 discardElement = HC.discardElement,
32 hasTouch = 'ontouchstart' in doc.documentElement,
38 PREFIX = 'highcharts-',
39 ABSOLUTE = 'absolute',
44 // Add language and get the defaultOptions
45 defaultOptions = HC.setOptions({
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'
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 = {
60 border: '1px solid #A0A0A0',
67 fontSize: hasTouch ? '14px' : '11px'
70 background: '#4572A5',
77 linearGradient: [0, 0, 0, 20],
83 borderColor: '#B0B0B0',
88 hoverBorderColor: '#909090',
89 hoverSymbolFill: '#81A7CF',
90 hoverSymbolStroke: '#4572A5',
91 symbolFill: '#E0E0E0',
93 symbolStroke: '#A0A0A0',
94 //symbolStrokeWidth: 1,
105 // Add the export related options
106 defaultOptions.exporting = {
110 url: 'file_echo.php',
115 symbol: 'exportIcon',
117 symbolFill: '#A8BF77',
118 hoverSymbolFill: '#768F3E',
119 _titleKey: 'exportButtonTitle',
122 textKey: 'downloadPNG',
123 onclick: function() {
127 textKey: 'downloadSVG',
128 onclick: function() {
130 type: 'image/svg+xml'
134 textKey: 'printButton',
135 onclick: function() {
146 extend(Chart.prototype, {
148 * Return an SVG representation of the chart
150 * @param additionalOptions {Object} Additional chart options for the generated SVG representation
152 getSVG: function(additionalOptions) {
161 options = merge(chart.options, additionalOptions); // copy the options and add extra options
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 });
174 // create a sandbox where a new chart will be generated
175 sandbox = createElement(DIV, null, {
178 width: chart.chartWidth + PX,
179 height: chart.chartHeight + PX
182 // override some options
183 extend(options.chart, {
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
191 each(chart.series, function(serie) {
192 seriesOptions = serie.options;
194 seriesOptions.animation = false; // turn off animation
195 seriesOptions.showCheckbox = false;
197 // remove image markers
198 if (seriesOptions && seriesOptions.marker && /^url\(/.test(seriesOptions.marker.symbol)) {
199 seriesOptions.marker.symbol = 'circle';
202 seriesOptions.data = [];
204 each(serie.data, function(point) {
206 // extend the options by those values that can be expressed in a number or array config
207 config = point.config;
214 if (typeof config == 'object' && point.config && config.constructor != Array) {
215 extend(pointOptions, config);
218 seriesOptions.data.push(pointOptions); // copy fresh updated data
220 // remove image markers
221 pointMarker = point.config && point.config.marker;
222 if (pointMarker && /^url\(/.test(pointMarker.symbol)) {
223 delete pointMarker.symbol;
227 options.series.push(seriesOptions);
230 // generate the chart copy
231 chartCopy = new Highcharts.Chart(options);
233 // get the SVG from the container's innerHTML
234 svg = chartCopy.container.innerHTML;
239 discardElement(sandbox);
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];
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();
266 // IE9 beta bugs with innerHTML. Test again with final IE9.
267 svg = svg.replace(/(url\(#highcharts-[0-9]+)"/g, '$1')
268 .replace(/"/g, "'");
269 if (svg.match(/ xmlns="/g).length == 2) {
270 svg = svg.replace(/xmlns="[^"]+"/, '');
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
281 exportChart: function(options, chartOptions) {
284 canvas=createElement('canvas');
286 $('body').append(canvas);
287 $(canvas).css('position','absolute');
288 $(canvas).css('left','-10000px');
290 var submitData = function(chartData) {
292 options = merge(chart.options.exporting, options);
295 form = createElement('form', {
303 each(['filename', 'type', 'width', 'image','token'], function(name) {
304 createElement('input', {
308 filename: options.filename || 'chart',
310 width: options.width,
321 discardElement(form);
324 if(options && options.type=='image/svg+xml') {
325 submitData(chart.getSVG(chartOptions));
327 if (typeof FlashCanvas != "undefined") {
328 FlashCanvas.initElement(canvas);
331 // Generate data uri and submit once done
332 canvg(canvas, chart.getSVG(chartOptions),{
333 ignoreAnimation: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);
350 container = chart.container,
352 origParent = container.parentNode,
354 childNodes = body.childNodes;
356 if (chart.isPrinting) { // block the button while in printing mode
360 chart.isPrinting = true;
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;
370 // pull out the chart
371 body.appendChild(container);
376 // allow the browser to prepare before reverting
377 setTimeout(function() {
379 // put the chart back in
380 origParent.appendChild(container);
382 // restore all body content
383 each(childNodes, function(node, i) {
384 if (node.nodeType == 1) {
385 node.style.display = origDisplay[i];
389 chart.isPrinting = false;
396 * Display a popup menu for choosing the export type
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
405 contextMenu: function(name, items, x, y, width, height) {
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',
419 // create the menu only the first time
422 // create a HTML element above the SVG
423 chart[cacheName] = menu = createElement(DIV, {
424 className: PREFIX + name
428 padding: menuPadding + PX
431 innerMenu = createElement(DIV, null,
433 MozBoxShadow: boxShadow,
434 WebkitBoxShadow: boxShadow,
436 }, navOptions.menuStyle) , menu);
440 css(menu, { display: NONE });
443 addEvent(menu, 'mouseleave', hide);
447 each(items, function(item) {
449 var div = createElement(DIV, {
450 onmouseover: function() {
451 css(this, navOptions.menuItemHoverStyle);
453 onmouseout: function() {
454 css(this, menuItemStyle);
456 innerHTML: item.text || HC.getOptions().lang[item.textKey]
459 }, menuItemStyle), innerMenu);
461 div[hasTouch ? 'ontouchstart' : 'onclick'] = function() {
463 item.onclick.apply(chart, arguments);
469 chart.exportMenuWidth = menu.offsetWidth;
470 chart.exportMenuHeight = menu.offsetHeight;
473 menuStyle = { display: 'block' };
475 // if outside right, right align it
476 if (x + chart.exportMenuWidth > chartWidth) {
477 menuStyle.right = (chartWidth - x - width - menuPadding) + PX;
479 menuStyle.left = (x - menuPadding) + PX;
481 // if outside bottom, bottom align it
482 if (y + height + chart.exportMenuHeight > chartHeight) {
483 menuStyle.bottom = (chartHeight - y - menuPadding) + PX;
485 menuStyle.top = (y + height - menuPadding) + PX;
488 css(menu, menuStyle);
492 * Add the export button to the chart
494 addButton: function(options) {
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,
508 borderWidth = btnOptions.borderWidth,
510 stroke: btnOptions.borderColor
514 stroke: btnOptions.symbolStroke,
515 fill: btnOptions.symbolFill
518 if (btnOptions.enabled === false) {
522 // element to capture the click
524 symbol.attr(symbolAttr);
534 btnOptions.borderRadius,
537 //.translate(buttonLeft, buttonTop) // to allow gradients
538 .align(btnOptions, true)
540 fill: btnOptions.backgroundColor,
541 'stroke-width': borderWidth,
545 // the invisible element to track the clicks
546 button = renderer.rect(
555 fill: 'rgba(255, 255, 255, 0.001)',
556 title: HC.getOptions().lang[btnOptions._titleKey],
561 .on('mouseover', function() {
563 stroke: btnOptions.hoverSymbolStroke,
564 fill: btnOptions.hoverSymbolFill
567 stroke: btnOptions.hoverBorderColor
570 .on('mouseout', revert)
574 //addEvent(button.element, 'click', revert);
576 // add the click event
578 onclick = function(e) {
580 var bBox = button.getBBox();
581 chart.contextMenu(btnOptions.menuName, menuItems, bBox.x, bBox.y, buttonWidth, buttonHeight);
584 /*addEvent(button.element, 'click', function() {
585 onclick.apply(chart, arguments);
587 button.on('click', function() {
588 onclick.apply(chart, arguments);
592 symbol = renderer.symbol(
596 (btnOptions.symbolSize || 12) / 2
598 .align(btnOptions, true)
599 .attr(extend(symbolAttr, {
600 'stroke-width': btnOptions.symbolStrokeWidth || 1,
609 // Create the export icon
610 HC.Renderer.prototype.symbols.exportIcon = function(x, y, radius) {
613 x - radius, y + radius,
615 x + radius, y + radius,
616 x + radius, y + radius * 0.5,
617 x - radius, y + radius * 0.5,
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,
632 // Add the buttons on chart load
633 Chart.prototype.callbacks.push(function(chart) {
635 exportingOptions = chart.options.exporting,
636 buttons = exportingOptions.buttons;
638 if (exportingOptions.enabled !== false) {
641 chart.addButton(buttons[n]);
643 for (n in chart.options.buttons) {
644 chart.addButton(chart.options.buttons[n]);