2
* @license Highstock JS v1.3.10 (2014-03-10)
5
* (c) 2010-2014 Torstein Honsi
7
* License: www.highcharts.com/license
11
/*global Highcharts, document, window, Math, setTimeout */
13
(function (Highcharts) { // encapsulate
16
var Chart = Highcharts.Chart,
17
addEvent = Highcharts.addEvent,
18
removeEvent = Highcharts.removeEvent,
19
createElement = Highcharts.createElement,
20
discardElement = Highcharts.discardElement,
22
merge = Highcharts.merge,
23
each = Highcharts.each,
24
extend = Highcharts.extend,
29
isTouchDevice = Highcharts.isTouchDevice,
35
PREFIX = 'highcharts-',
36
ABSOLUTE = 'absolute',
39
symbols = Highcharts.Renderer.prototype.symbols,
40
defaultOptions = Highcharts.getOptions(),
44
extend(defaultOptions.lang, {
45
printChart: 'Print chart',
46
downloadPNG: 'Download PNG image',
47
downloadJPEG: 'Download JPEG image',
48
downloadPDF: 'Download PDF document',
49
downloadSVG: 'Download SVG vector image',
50
contextButtonTitle: 'Chart context menu'
53
// Buttons and menus are collected in a separate config option set called 'navigation'.
54
// This can be extended later to add control buttons like zoom and pan right click menus.
55
defaultOptions.navigation = {
57
border: '1px solid #A0A0A0',
58
background: '#FFFFFF',
65
fontSize: isTouchDevice ? '14px' : '11px'
68
background: '#4572A5',
73
symbolFill: '#E0E0E0',
84
fill: 'white', // capture hover
94
// Add the export related options
95
defaultOptions.exporting = {
99
url: 'http://export.highcharts.com/',
104
menuClassName: PREFIX + 'contextmenu',
107
_titleKey: 'contextButtonTitle',
109
textKey: 'printChart',
110
onclick: function () {
116
textKey: 'downloadPNG',
117
onclick: function () {
121
textKey: 'downloadJPEG',
122
onclick: function () {
128
textKey: 'downloadPDF',
129
onclick: function () {
131
type: 'application/pdf'
135
textKey: 'downloadSVG',
136
onclick: function () {
138
type: 'image/svg+xml'
142
// Enable this block to add "View SVG" to the dropdown menu
147
onclick: function () {
148
var svg = this.getSVG()
149
.replace(/</g, '\n<')
150
.replace(/>/g, '>');
152
doc.body.innerHTML = '<pre>' + svg + '</pre>';
160
// Add the Highcharts.post utility
161
Highcharts.post = function (url, data, formAttributes) {
166
form = createElement('form', merge({
169
enctype: 'multipart/form-data'
170
}, formAttributes), {
176
createElement('input', {
187
discardElement(form);
190
extend(Chart.prototype, {
193
* Return an SVG representation of the chart
195
* @param additionalOptions {Object} Additional chart options for the generated SVG representation
197
getSVG: function (additionalOptions) {
207
options = merge(chart.options, additionalOptions); // copy the options and add extra options
209
// IE compatibility hack for generating SVG content that it doesn't really understand
210
if (!doc.createElementNS) {
211
/*jslint unparam: true*//* allow unused parameter ns in function below */
212
doc.createElementNS = function (ns, tagName) {
213
return doc.createElement(tagName);
215
/*jslint unparam: false*/
218
// create a sandbox where a new chart will be generated
219
sandbox = createElement(DIV, null, {
222
width: chart.chartWidth + PX,
223
height: chart.chartHeight + PX
226
// get the source size
227
cssWidth = chart.renderTo.style.width;
228
cssHeight = chart.renderTo.style.height;
229
sourceWidth = options.exporting.sourceWidth ||
230
options.chart.width ||
231
(/px$/.test(cssWidth) && parseInt(cssWidth, 10)) ||
233
sourceHeight = options.exporting.sourceHeight ||
234
options.chart.height ||
235
(/px$/.test(cssHeight) && parseInt(cssHeight, 10)) ||
238
// override some options
239
extend(options.chart, {
246
options.exporting.enabled = false; // hide buttons in print
248
// prepare for replicating the chart
250
each(chart.series, function (serie) {
251
seriesOptions = merge(serie.options, {
252
animation: false, // turn off animation
254
visible: serie.visible
257
if (!seriesOptions.isInternal) { // used for the navigator series that has its own option set
258
options.series.push(seriesOptions);
262
// generate the chart copy
263
chartCopy = new Highcharts.Chart(options, chart.callback);
265
// reflect axis extremes in the export
266
each(['xAxis', 'yAxis'], function (axisType) {
267
each(chart[axisType], function (axis, i) {
268
var axisCopy = chartCopy[axisType][i],
269
extremes = axis.getExtremes(),
270
userMin = extremes.userMin,
271
userMax = extremes.userMax;
273
if (axisCopy && (userMin !== UNDEFINED || userMax !== UNDEFINED)) {
274
axisCopy.setExtremes(userMin, userMax, true, false);
279
// get the SVG from the container's innerHTML
280
svg = chartCopy.container.innerHTML;
285
discardElement(sandbox);
289
.replace(/zIndex="[^"]+"/g, '')
290
.replace(/isShadow="[^"]+"/g, '')
291
.replace(/symbolName="[^"]+"/g, '')
292
.replace(/jQuery[0-9]+="[^"]+"/g, '')
293
.replace(/url\([^#]+#/g, 'url(#')
294
.replace(/<svg /, '<svg xmlns:xlink="http://www.w3.org/1999/xlink" ')
295
.replace(/ href=/g, ' xlink:href=')
297
.replace(/<\/svg>.*?$/, '</svg>') // any HTML added to the container after the SVG (#894)
298
/* This fails in IE < 8
299
.replace(/([0-9]+)\.([0-9]+)/g, function(s1, s2, s3) { // round off to save weight
300
return s2 +'.'+ s3[0];
303
// Replace HTML entities, issue #347
304
.replace(/ /g, '\u00A0') // no-break space
305
.replace(/­/g, '\u00AD') // soft hyphen
308
.replace(/<IMG /g, '<image ')
309
.replace(/height=([^" ]+)/g, 'height="$1"')
310
.replace(/width=([^" ]+)/g, 'width="$1"')
311
.replace(/hc-svg-href="([^"]+)">/g, 'xlink:href="$1"/>')
312
.replace(/id=([^" >]+)/g, 'id="$1"')
313
.replace(/class=([^" >]+)/g, 'class="$1"')
314
.replace(/ transform /g, ' ')
315
.replace(/:(path|rect)/g, '$1')
316
.replace(/style="([^"]+)"/g, function (s) {
317
return s.toLowerCase();
320
// IE9 beta bugs with innerHTML. Test again with final IE9.
321
svg = svg.replace(/(url\(#highcharts-[0-9]+)"/g, '$1')
322
.replace(/"/g, "'");
328
* Submit the SVG representation of the chart to the server
329
* @param {Object} options Exporting options. Possible members are url, type, width and formAttributes.
330
* @param {Object} chartOptions Additional chart options for the SVG representation of the chart
332
exportChart: function (options, chartOptions) {
333
options = options || {};
336
chartExportingOptions = chart.options.exporting,
337
svg = chart.getSVG(merge(
338
{ chart: { borderRadius: 0 } },
339
chartExportingOptions.chartOptions,
343
sourceWidth: options.sourceWidth || chartExportingOptions.sourceWidth,
344
sourceHeight: options.sourceHeight || chartExportingOptions.sourceHeight
350
options = merge(chart.options.exporting, options);
353
Highcharts.post(options.url, {
354
filename: options.filename || 'chart',
356
width: options.width || 0, // IE8 fails to post undefined correctly, so use 0
357
scale: options.scale || 2,
359
}, options.formAttributes);
369
container = chart.container,
371
origParent = container.parentNode,
373
childNodes = body.childNodes;
375
if (chart.isPrinting) { // block the button while in printing mode
379
chart.isPrinting = true;
381
// hide all body content
382
each(childNodes, function (node, i) {
383
if (node.nodeType === 1) {
384
origDisplay[i] = node.style.display;
385
node.style.display = NONE;
389
// pull out the chart
390
body.appendChild(container);
393
win.focus(); // #1510
396
// allow the browser to prepare before reverting
397
setTimeout(function () {
399
// put the chart back in
400
origParent.appendChild(container);
402
// restore all body content
403
each(childNodes, function (node, i) {
404
if (node.nodeType === 1) {
405
node.style.display = origDisplay[i];
409
chart.isPrinting = false;
416
* Display a popup menu for choosing the export type
418
* @param {String} className An identifier for the menu
419
* @param {Array} items A collection with text and onclicks for the items
420
* @param {Number} x The x position of the opener button
421
* @param {Number} y The y position of the opener button
422
* @param {Number} width The width of the opener button
423
* @param {Number} height The height of the opener button
425
contextMenu: function (className, items, x, y, width, height, button) {
427
navOptions = chart.options.navigation,
428
menuItemStyle = navOptions.menuItemStyle,
429
chartWidth = chart.chartWidth,
430
chartHeight = chart.chartHeight,
431
cacheName = 'cache-' + className,
432
menu = chart[cacheName],
433
menuPadding = mathMax(width, height), // for mouse leave detection
434
boxShadow = '3px 3px 10px #888',
439
docMouseUpHandler = function (e) {
440
if (!chart.pointer.inClass(e.target, className)) {
445
// create the menu only the first time
448
// create a HTML element above the SVG
449
chart[cacheName] = menu = createElement(DIV, {
454
padding: menuPadding + PX
457
innerMenu = createElement(DIV, null,
459
MozBoxShadow: boxShadow,
460
WebkitBoxShadow: boxShadow,
462
}, navOptions.menuStyle), menu);
466
css(menu, { display: NONE });
470
chart.openMenu = false;
473
// Hide the menu some time after mouse leave (#1357)
474
addEvent(menu, 'mouseleave', function () {
475
hideTimer = setTimeout(hide, 500);
477
addEvent(menu, 'mouseenter', function () {
478
clearTimeout(hideTimer);
482
// Hide it on clicking or touching outside the menu (#2258, #2335, #2407)
483
addEvent(document, 'mouseup', docMouseUpHandler);
484
addEvent(chart, 'destroy', function () {
485
removeEvent(document, 'mouseup', docMouseUpHandler);
490
each(items, function (item) {
492
var element = item.separator ?
493
createElement('hr', null, null, innerMenu) :
495
onmouseover: function () {
496
css(this, navOptions.menuItemHoverStyle);
498
onmouseout: function () {
499
css(this, menuItemStyle);
501
onclick: function () {
503
item.onclick.apply(chart, arguments);
505
innerHTML: item.text || chart.options.lang[item.textKey]
508
}, menuItemStyle), innerMenu);
511
// Keep references to menu divs to be able to destroy them
512
chart.exportDivElements.push(element);
516
// Keep references to menu and innerMenu div to be able to destroy them
517
chart.exportDivElements.push(innerMenu, menu);
519
chart.exportMenuWidth = menu.offsetWidth;
520
chart.exportMenuHeight = menu.offsetHeight;
523
menuStyle = { display: 'block' };
525
// if outside right, right align it
526
if (x + chart.exportMenuWidth > chartWidth) {
527
menuStyle.right = (chartWidth - x - width - menuPadding) + PX;
529
menuStyle.left = (x - menuPadding) + PX;
531
// if outside bottom, bottom align it
532
if (y + height + chart.exportMenuHeight > chartHeight && button.alignOptions.verticalAlign !== 'top') {
533
menuStyle.bottom = (chartHeight - y - menuPadding) + PX;
535
menuStyle.top = (y + height - menuPadding) + PX;
538
css(menu, menuStyle);
539
chart.openMenu = true;
543
* Add the export button to the chart
545
addButton: function (options) {
547
renderer = chart.renderer,
548
btnOptions = merge(chart.options.navigation.buttonOptions, options),
549
onclick = btnOptions.onclick,
550
menuItems = btnOptions.menuItems,
554
stroke: btnOptions.symbolStroke,
555
fill: btnOptions.symbolFill
557
symbolSize = btnOptions.symbolSize || 12;
558
if (!chart.btnCount) {
562
// Keeps references to the button elements
563
if (!chart.exportDivElements) {
564
chart.exportDivElements = [];
565
chart.exportSVGElements = [];
568
if (btnOptions.enabled === false) {
573
var attr = btnOptions.theme,
574
states = attr.states,
575
hover = states && states.hover,
576
select = states && states.select,
582
callback = function () {
583
onclick.apply(chart, arguments);
586
} else if (menuItems) {
587
callback = function () {
589
button.menuClassName,
602
if (btnOptions.text && btnOptions.symbol) {
603
attr.paddingLeft = Highcharts.pick(attr.paddingLeft, 25);
605
} else if (!btnOptions.text) {
607
width: btnOptions.width,
608
height: btnOptions.height,
613
button = renderer.button(btnOptions.text, 0, 0, callback, attr, hover, select)
615
title: chart.options.lang[btnOptions._titleKey],
616
'stroke-linecap': 'round'
618
button.menuClassName = options.menuClassName || PREFIX + 'menu-' + chart.btnCount++;
620
if (btnOptions.symbol) {
621
symbol = renderer.symbol(
623
btnOptions.symbolX - (symbolSize / 2),
624
btnOptions.symbolY - (symbolSize / 2),
628
.attr(extend(symbolAttr, {
629
'stroke-width': btnOptions.symbolStrokeWidth || 1,
635
.align(extend(btnOptions, {
637
x: Highcharts.pick(btnOptions.x, buttonOffset) // #1654
638
}), true, 'spacingBox');
640
buttonOffset += (button.width + btnOptions.buttonSpacing) * (btnOptions.align === 'right' ? -1 : 1);
642
chart.exportSVGElements.push(button, symbol);
647
* Destroy the buttons.
649
destroyExport: function (e) {
650
var chart = e.target,
654
// Destroy the extra buttons added
655
for (i = 0; i < chart.exportSVGElements.length; i++) {
656
elem = chart.exportSVGElements[i];
658
// Destroy and null the svg/vml elements
660
elem.onclick = elem.ontouchstart = null;
661
chart.exportSVGElements[i] = elem.destroy();
665
// Destroy the divs for the menu
666
for (i = 0; i < chart.exportDivElements.length; i++) {
667
elem = chart.exportDivElements[i];
669
// Remove the event handler
670
removeEvent(elem, 'mouseleave');
672
// Remove inline events
673
chart.exportDivElements[i] = elem.onmouseout = elem.onmouseover = elem.ontouchstart = elem.onclick = null;
675
// Destroy the div by moving to garbage bin
676
discardElement(elem);
682
symbols.menu = function (x, y, width, height) {
685
L, x + width, y + 2.5,
686
M, x, y + height / 2 + 0.5,
687
L, x + width, y + height / 2 + 0.5,
688
M, x, y + height - 1.5,
689
L, x + width, y + height - 1.5
694
// Add the buttons on chart load
695
Chart.prototype.callbacks.push(function (chart) {
697
exportingOptions = chart.options.exporting,
698
buttons = exportingOptions.buttons;
702
if (exportingOptions.enabled !== false) {
705
chart.addButton(buttons[n]);
708
// Destroy the export elements at chart destroy
709
addEvent(chart, 'destroy', chart.destroyExport);