highcharts-rails 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,703 @@
1
+ /**
2
+ * @license Highcharts JS v2.1.6 (2011-07-08)
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
+ createElement = HC.createElement,
20
+ discardElement = HC.discardElement,
21
+ css = HC.css,
22
+ merge = HC.merge,
23
+ each = HC.each,
24
+ extend = HC.extend,
25
+ math = Math,
26
+ mathMax = math.max,
27
+ doc = document,
28
+ win = window,
29
+ hasTouch = 'ontouchstart' in doc.documentElement,
30
+ M = 'M',
31
+ L = 'L',
32
+ DIV = 'div',
33
+ HIDDEN = 'hidden',
34
+ NONE = 'none',
35
+ PREFIX = 'highcharts-',
36
+ ABSOLUTE = 'absolute',
37
+ PX = 'px',
38
+ UNDEFINED = undefined,
39
+
40
+ // Add language and get the defaultOptions
41
+ defaultOptions = HC.setOptions({
42
+ lang: {
43
+ downloadPNG: 'Download PNG image',
44
+ downloadJPEG: 'Download JPEG image',
45
+ downloadPDF: 'Download PDF document',
46
+ downloadSVG: 'Download SVG vector image',
47
+ exportButtonTitle: 'Export to raster or vector image',
48
+ printButtonTitle: 'Print the chart'
49
+ }
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
+ enableImages: false,
109
+ buttons: {
110
+ exportButton: {
111
+ //enabled: true,
112
+ symbol: 'exportIcon',
113
+ x: -10,
114
+ symbolFill: '#A8BF77',
115
+ hoverSymbolFill: '#768F3E',
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
+ text: 'View SVG',
145
+ onclick: function() {
146
+ var svg = this.getSVG()
147
+ .replace(/</g, '\n&lt;')
148
+ .replace(/>/g, '&gt;');
149
+
150
+ doc.body.innerHTML = '<pre>'+ svg +'</pre>';
151
+ }
152
+ }*/]
153
+
154
+ },
155
+ printButton: {
156
+ //enabled: true,
157
+ symbol: 'printIcon',
158
+ x: -36,
159
+ symbolFill: '#B5C9DF',
160
+ hoverSymbolFill: '#779ABF',
161
+ _titleKey: 'printButtonTitle',
162
+ onclick: function() {
163
+ this.print();
164
+ }
165
+ }
166
+ }
167
+ };
168
+
169
+
170
+
171
+ extend(Chart.prototype, {
172
+ /**
173
+ * Return an SVG representation of the chart
174
+ *
175
+ * @param additionalOptions {Object} Additional chart options for the generated SVG representation
176
+ */
177
+ getSVG: function(additionalOptions) {
178
+ var chart = this,
179
+ chartCopy,
180
+ sandbox,
181
+ svg,
182
+ seriesOptions,
183
+ config,
184
+ pointOptions,
185
+ pointMarker,
186
+ options = merge(chart.options, additionalOptions); // copy the options and add extra options
187
+
188
+ // IE compatibility hack for generating SVG content that it doesn't really understand
189
+ if (!doc.createElementNS) {
190
+ doc.createElementNS = function(ns, tagName) {
191
+ var elem = doc.createElement(tagName);
192
+ elem.getBBox = function() {
193
+ return HC.Renderer.prototype.Element.prototype.getBBox.apply({ element: elem });
194
+ };
195
+ return elem;
196
+ };
197
+ }
198
+
199
+ // create a sandbox where a new chart will be generated
200
+ sandbox = createElement(DIV, null, {
201
+ position: ABSOLUTE,
202
+ top: '-9999em',
203
+ width: chart.chartWidth + PX,
204
+ height: chart.chartHeight + PX
205
+ }, doc.body);
206
+
207
+ // override some options
208
+ extend(options.chart, {
209
+ renderTo: sandbox,
210
+ forExport: true
211
+ });
212
+ options.exporting.enabled = false; // hide buttons in print
213
+
214
+ if (!options.exporting.enableImages) {
215
+ options.chart.plotBackgroundImage = null; // the converter doesn't handle images
216
+ }
217
+
218
+ // prepare for replicating the chart
219
+ options.series = [];
220
+ each(chart.series, function(serie) {
221
+ seriesOptions = serie.options;
222
+
223
+ seriesOptions.animation = false; // turn off animation
224
+ seriesOptions.showCheckbox = false;
225
+ seriesOptions.visible = serie.visible;
226
+
227
+ if (!options.exporting.enableImages) {
228
+ // remove image markers
229
+ if (seriesOptions && seriesOptions.marker && /^url\(/.test(seriesOptions.marker.symbol)) {
230
+ seriesOptions.marker.symbol = 'circle';
231
+ }
232
+ }
233
+
234
+ seriesOptions.data = [];
235
+
236
+ each(serie.data, function(point) {
237
+
238
+ // extend the options by those values that can be expressed in a number or array config
239
+ config = point.config;
240
+ pointOptions = {
241
+ x: point.x,
242
+ y: point.y,
243
+ name: point.name
244
+ };
245
+
246
+ if (typeof config == 'object' && point.config && config.constructor != Array) {
247
+ extend(pointOptions, config);
248
+ }
249
+
250
+ pointOptions.visible = point.visible;
251
+ seriesOptions.data.push(pointOptions); // copy fresh updated data
252
+
253
+ if (!options.exporting.enableImages) {
254
+ // remove image markers
255
+ pointMarker = point.config && point.config.marker;
256
+ if (pointMarker && /^url\(/.test(pointMarker.symbol)) {
257
+ delete pointMarker.symbol;
258
+ }
259
+ }
260
+ });
261
+
262
+ options.series.push(seriesOptions);
263
+ });
264
+
265
+ // generate the chart copy
266
+ chartCopy = new Highcharts.Chart(options);
267
+
268
+ // reflect axis extremes in the export
269
+ each(['xAxis', 'yAxis'], function(axisType) {
270
+ each (chart[axisType], function(axis, i) {
271
+ var axisCopy = chartCopy[axisType][i],
272
+ extremes = axis.getExtremes(),
273
+ userMin = extremes.userMin,
274
+ userMax = extremes.userMax;
275
+
276
+ if (userMin !== UNDEFINED || userMax !== UNDEFINED) {
277
+ axisCopy.setExtremes(userMin, userMax, true, false);
278
+ }
279
+ });
280
+ });
281
+
282
+ // get the SVG from the container's innerHTML
283
+ svg = chartCopy.container.innerHTML;
284
+
285
+ // free up memory
286
+ options = null;
287
+ chartCopy.destroy();
288
+ discardElement(sandbox);
289
+
290
+ // sanitize
291
+ svg = svg
292
+ .replace(/zIndex="[^"]+"/g, '')
293
+ .replace(/isShadow="[^"]+"/g, '')
294
+ .replace(/symbolName="[^"]+"/g, '')
295
+ .replace(/jQuery[0-9]+="[^"]+"/g, '')
296
+ .replace(/isTracker="[^"]+"/g, '')
297
+ .replace(/url\([^#]+#/g, 'url(#')
298
+ .replace(/<svg /, '<svg xmlns:xlink="http://www.w3.org/1999/xlink" ')
299
+ .replace(/ href=/g, ' xlink:href=')
300
+ /*.replace(/preserveAspectRatio="none">/g, 'preserveAspectRatio="none"/>')*/
301
+ /* This fails in IE < 8
302
+ .replace(/([0-9]+)\.([0-9]+)/g, function(s1, s2, s3) { // round off to save weight
303
+ return s2 +'.'+ s3[0];
304
+ })*/
305
+
306
+ // IE specific
307
+ .replace(/id=([^" >]+)/g, 'id="$1"')
308
+ .replace(/class=([^" ]+)/g, 'class="$1"')
309
+ .replace(/ transform /g, ' ')
310
+ .replace(/:(path|rect)/g, '$1')
311
+ .replace(/<img ([^>]*)>/gi, '<image $1 />')
312
+ .replace(/<\/image>/g, '') // remove closing tags for images as they'll never have any content
313
+ .replace(/<image ([^>]*)([^\/])>/gi, '<image $1$2 />') // closes image tags for firefox
314
+ .replace(/width=(\d+)/g, 'width="$1"')
315
+ .replace(/height=(\d+)/g, 'height="$1"')
316
+ .replace(/hc-svg-href="/g, 'xlink:href="')
317
+ .replace(/style="([^"]+)"/g, function(s) {
318
+ return s.toLowerCase();
319
+ });
320
+
321
+ // IE9 beta bugs with innerHTML. Test again with final IE9.
322
+ svg = svg.replace(/(url\(#highcharts-[0-9]+)&quot;/g, '$1')
323
+ .replace(/&quot;/g, "'");
324
+ if (svg.match(/ xmlns="/g).length == 2) {
325
+ svg = svg.replace(/xmlns="[^"]+"/, '');
326
+ }
327
+
328
+ return svg;
329
+ },
330
+
331
+ /**
332
+ * Submit the SVG representation of the chart to the server
333
+ * @param {Object} options Exporting options. Possible members are url, type and width.
334
+ * @param {Object} chartOptions Additional chart options for the SVG representation of the chart
335
+ */
336
+ exportChart: function(options, chartOptions) {
337
+ var form,
338
+ chart = this,
339
+ svg = chart.getSVG(chartOptions);
340
+
341
+ // merge the options
342
+ options = merge(chart.options.exporting, options);
343
+
344
+ // create the form
345
+ form = createElement('form', {
346
+ method: 'post',
347
+ action: options.url
348
+ }, {
349
+ display: NONE
350
+ }, doc.body);
351
+
352
+ // add the values
353
+ each(['filename', 'type', 'width', 'svg'], function(name) {
354
+ createElement('input', {
355
+ type: HIDDEN,
356
+ name: name,
357
+ value: {
358
+ filename: options.filename || 'chart',
359
+ type: options.type,
360
+ width: options.width,
361
+ svg: svg
362
+ }[name]
363
+ }, null, form);
364
+ });
365
+
366
+ // submit
367
+ form.submit();
368
+
369
+ // clean up
370
+ discardElement(form);
371
+ },
372
+
373
+ /**
374
+ * Print the chart
375
+ */
376
+ print: function() {
377
+
378
+ var chart = this,
379
+ container = chart.container,
380
+ origDisplay = [],
381
+ origParent = container.parentNode,
382
+ body = doc.body,
383
+ childNodes = body.childNodes;
384
+
385
+ if (chart.isPrinting) { // block the button while in printing mode
386
+ return;
387
+ }
388
+
389
+ chart.isPrinting = true;
390
+
391
+ // hide all body content
392
+ each(childNodes, function(node, i) {
393
+ if (node.nodeType == 1) {
394
+ origDisplay[i] = node.style.display;
395
+ node.style.display = NONE;
396
+ }
397
+ });
398
+
399
+ // pull out the chart
400
+ body.appendChild(container);
401
+
402
+ // print
403
+ win.print();
404
+
405
+ // allow the browser to prepare before reverting
406
+ setTimeout(function() {
407
+
408
+ // put the chart back in
409
+ origParent.appendChild(container);
410
+
411
+ // restore all body content
412
+ each(childNodes, function(node, i) {
413
+ if (node.nodeType == 1) {
414
+ node.style.display = origDisplay[i];
415
+ }
416
+ });
417
+
418
+ chart.isPrinting = false;
419
+
420
+ }, 1000);
421
+
422
+ },
423
+
424
+ /**
425
+ * Display a popup menu for choosing the export type
426
+ *
427
+ * @param {String} name An identifier for the menu
428
+ * @param {Array} items A collection with text and onclicks for the items
429
+ * @param {Number} x The x position of the opener button
430
+ * @param {Number} y The y position of the opener button
431
+ * @param {Number} width The width of the opener button
432
+ * @param {Number} height The height of the opener button
433
+ */
434
+ contextMenu: function(name, items, x, y, width, height) {
435
+ var chart = this,
436
+ navOptions = chart.options.navigation,
437
+ menuItemStyle = navOptions.menuItemStyle,
438
+ chartWidth = chart.chartWidth,
439
+ chartHeight = chart.chartHeight,
440
+ cacheName = 'cache-'+ name,
441
+ menu = chart[cacheName],
442
+ menuPadding = mathMax(width, height), // for mouse leave detection
443
+ boxShadow = '3px 3px 10px #888',
444
+ innerMenu,
445
+ hide,
446
+ menuStyle;
447
+
448
+ // create the menu only the first time
449
+ if (!menu) {
450
+
451
+ // create a HTML element above the SVG
452
+ chart[cacheName] = menu = createElement(DIV, {
453
+ className: PREFIX + name
454
+ }, {
455
+ position: ABSOLUTE,
456
+ zIndex: 1000,
457
+ padding: menuPadding + PX
458
+ }, chart.container);
459
+
460
+ innerMenu = createElement(DIV, null,
461
+ extend({
462
+ MozBoxShadow: boxShadow,
463
+ WebkitBoxShadow: boxShadow,
464
+ boxShadow: boxShadow
465
+ }, navOptions.menuStyle) , menu);
466
+
467
+ // hide on mouse out
468
+ hide = function() {
469
+ css(menu, { display: NONE });
470
+ };
471
+
472
+ addEvent(menu, 'mouseleave', hide);
473
+
474
+
475
+ // create the items
476
+ each(items, function(item) {
477
+ if (item) {
478
+ var div = createElement(DIV, {
479
+ onmouseover: function() {
480
+ css(this, navOptions.menuItemHoverStyle);
481
+ },
482
+ onmouseout: function() {
483
+ css(this, menuItemStyle);
484
+ },
485
+ innerHTML: item.text || HC.getOptions().lang[item.textKey]
486
+ }, extend({
487
+ cursor: 'pointer'
488
+ }, menuItemStyle), innerMenu);
489
+
490
+ div[hasTouch ? 'ontouchstart' : 'onclick'] = function() {
491
+ hide();
492
+ item.onclick.apply(chart, arguments);
493
+ };
494
+
495
+ }
496
+ });
497
+
498
+ chart.exportMenuWidth = menu.offsetWidth;
499
+ chart.exportMenuHeight = menu.offsetHeight;
500
+ }
501
+
502
+ menuStyle = { display: 'block' };
503
+
504
+ // if outside right, right align it
505
+ if (x + chart.exportMenuWidth > chartWidth) {
506
+ menuStyle.right = (chartWidth - x - width - menuPadding) + PX;
507
+ } else {
508
+ menuStyle.left = (x - menuPadding) + PX;
509
+ }
510
+ // if outside bottom, bottom align it
511
+ if (y + height + chart.exportMenuHeight > chartHeight) {
512
+ menuStyle.bottom = (chartHeight - y - menuPadding) + PX;
513
+ } else {
514
+ menuStyle.top = (y + height - menuPadding) + PX;
515
+ }
516
+
517
+ css(menu, menuStyle);
518
+ },
519
+
520
+ /**
521
+ * Add the export button to the chart
522
+ */
523
+ addButton: function(options) {
524
+ var chart = this,
525
+ renderer = chart.renderer,
526
+ btnOptions = merge(chart.options.navigation.buttonOptions, options),
527
+ onclick = btnOptions.onclick,
528
+ menuItems = btnOptions.menuItems,
529
+ /*position = chart.getAlignment(btnOptions),
530
+ buttonLeft = position.x,
531
+ buttonTop = position.y,*/
532
+ buttonWidth = btnOptions.width,
533
+ buttonHeight = btnOptions.height,
534
+ box,
535
+ symbol,
536
+ button,
537
+ borderWidth = btnOptions.borderWidth,
538
+ boxAttr = {
539
+ stroke: btnOptions.borderColor
540
+
541
+ },
542
+ symbolAttr = {
543
+ stroke: btnOptions.symbolStroke,
544
+ fill: btnOptions.symbolFill
545
+ };
546
+
547
+ if (btnOptions.enabled === false) {
548
+ return;
549
+ }
550
+
551
+ // element to capture the click
552
+ function revert() {
553
+ symbol.attr(symbolAttr);
554
+ box.attr(boxAttr);
555
+ }
556
+
557
+ // the box border
558
+ box = renderer.rect(
559
+ 0,
560
+ 0,
561
+ buttonWidth,
562
+ buttonHeight,
563
+ btnOptions.borderRadius,
564
+ borderWidth
565
+ )
566
+ //.translate(buttonLeft, buttonTop) // to allow gradients
567
+ .align(btnOptions, true)
568
+ .attr(extend({
569
+ fill: btnOptions.backgroundColor,
570
+ 'stroke-width': borderWidth,
571
+ zIndex: 19
572
+ }, boxAttr)).add();
573
+
574
+ // the invisible element to track the clicks
575
+ button = renderer.rect(
576
+ 0,
577
+ 0,
578
+ buttonWidth,
579
+ buttonHeight,
580
+ 0
581
+ )
582
+ .align(btnOptions)
583
+ .attr({
584
+ fill: 'rgba(255, 255, 255, 0.001)',
585
+ title: HC.getOptions().lang[btnOptions._titleKey],
586
+ zIndex: 21
587
+ }).css({
588
+ cursor: 'pointer'
589
+ })
590
+ .on('mouseover', function() {
591
+ symbol.attr({
592
+ stroke: btnOptions.hoverSymbolStroke,
593
+ fill: btnOptions.hoverSymbolFill
594
+ });
595
+ box.attr({
596
+ stroke: btnOptions.hoverBorderColor
597
+ });
598
+ })
599
+ .on('mouseout', revert)
600
+ .on('click', revert)
601
+ .add();
602
+
603
+ //addEvent(button.element, 'click', revert);
604
+
605
+ // add the click event
606
+ if (menuItems) {
607
+ onclick = function(e) {
608
+ revert();
609
+ var bBox = button.getBBox();
610
+ chart.contextMenu('export-menu', menuItems, bBox.x, bBox.y, buttonWidth, buttonHeight);
611
+ };
612
+ }
613
+ /*addEvent(button.element, 'click', function() {
614
+ onclick.apply(chart, arguments);
615
+ });*/
616
+ button.on('click', function() {
617
+ onclick.apply(chart, arguments);
618
+ });
619
+
620
+ // the icon
621
+ symbol = renderer.symbol(
622
+ btnOptions.symbol,
623
+ btnOptions.symbolX,
624
+ btnOptions.symbolY,
625
+ (btnOptions.symbolSize || 12) / 2
626
+ )
627
+ .align(btnOptions, true)
628
+ .attr(extend(symbolAttr, {
629
+ 'stroke-width': btnOptions.symbolStrokeWidth || 1,
630
+ zIndex: 20
631
+ })).add();
632
+
633
+
634
+
635
+ }
636
+ });
637
+
638
+ // Create the export icon
639
+ HC.Renderer.prototype.symbols.exportIcon = function(x, y, radius) {
640
+ return [
641
+ M, // the disk
642
+ x - radius, y + radius,
643
+ L,
644
+ x + radius, y + radius,
645
+ x + radius, y + radius * 0.5,
646
+ x - radius, y + radius * 0.5,
647
+ 'Z',
648
+ M, // the arrow
649
+ x, y + radius * 0.5,
650
+ L,
651
+ x - radius * 0.5, y - radius / 3,
652
+ x - radius / 6, y - radius / 3,
653
+ x - radius / 6, y - radius,
654
+ x + radius / 6, y - radius,
655
+ x + radius / 6, y - radius / 3,
656
+ x + radius * 0.5, y - radius / 3,
657
+ 'Z'
658
+ ];
659
+ };
660
+ // Create the print icon
661
+ HC.Renderer.prototype.symbols.printIcon = function(x, y, radius) {
662
+ return [
663
+ M, // the printer
664
+ x - radius, y + radius * 0.5,
665
+ L,
666
+ x + radius, y + radius * 0.5,
667
+ x + radius, y - radius / 3,
668
+ x - radius, y - radius / 3,
669
+ 'Z',
670
+ M, // the upper sheet
671
+ x - radius * 0.5, y - radius / 3,
672
+ L,
673
+ x - radius * 0.5, y - radius,
674
+ x + radius * 0.5, y - radius,
675
+ x + radius * 0.5, y - radius / 3,
676
+ 'Z',
677
+ M, // the lower sheet
678
+ x - radius * 0.5, y + radius * 0.5,
679
+ L,
680
+ x - radius * 0.75, y + radius,
681
+ x + radius * 0.75, y + radius,
682
+ x + radius * 0.5, y + radius * 0.5,
683
+ 'Z'
684
+ ];
685
+ };
686
+
687
+
688
+ // Add the buttons on chart load
689
+ Chart.prototype.callbacks.push(function(chart) {
690
+ var n,
691
+ exportingOptions = chart.options.exporting,
692
+ buttons = exportingOptions.buttons;
693
+
694
+ if (exportingOptions.enabled !== false) {
695
+
696
+ for (n in buttons) {
697
+ chart.addButton(buttons[n]);
698
+ }
699
+ }
700
+ });
701
+
702
+
703
+ })();