highcharts-rails 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.
@@ -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
+ })();