highstocks-rails 0.0.1

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.
Files changed (49) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +7 -0
  3. data/CHANGELOG.markdown +4 -0
  4. data/Gemfile +2 -0
  5. data/LICENSE +21 -0
  6. data/README.markdown +27 -0
  7. data/Rakefile +1 -0
  8. data/app/assets/graphics/skies.jpg +0 -0
  9. data/app/assets/graphics/snow.png +0 -0
  10. data/app/assets/graphics/sun.png +0 -0
  11. data/app/assets/images/highstocks/skies.jpg +0 -0
  12. data/app/assets/images/highstocks/snow.png +0 -0
  13. data/app/assets/images/highstocks/sun.png +0 -0
  14. data/app/assets/javascripts/highstocks.js +20919 -0
  15. data/app/assets/javascripts/highstocks/adapters/mootools-adapter.js +13 -0
  16. data/app/assets/javascripts/highstocks/adapters/mootools-adapter.src.js +316 -0
  17. data/app/assets/javascripts/highstocks/adapters/prototype-adapter.js +15 -0
  18. data/app/assets/javascripts/highstocks/adapters/prototype-adapter.src.js +316 -0
  19. data/app/assets/javascripts/highstocks/adapters/standalone-framework.js +17 -0
  20. data/app/assets/javascripts/highstocks/adapters/standalone-framework.src.js +583 -0
  21. data/app/assets/javascripts/highstocks/highstocks-more.js +2439 -0
  22. data/app/assets/javascripts/highstocks/modules/annotations.js +7 -0
  23. data/app/assets/javascripts/highstocks/modules/annotations.src.js +401 -0
  24. data/app/assets/javascripts/highstocks/modules/canvas-tools.js +133 -0
  25. data/app/assets/javascripts/highstocks/modules/canvas-tools.src.js +3113 -0
  26. data/app/assets/javascripts/highstocks/modules/data.js +17 -0
  27. data/app/assets/javascripts/highstocks/modules/data.src.js +582 -0
  28. data/app/assets/javascripts/highstocks/modules/drilldown.js +11 -0
  29. data/app/assets/javascripts/highstocks/modules/drilldown.src.js +449 -0
  30. data/app/assets/javascripts/highstocks/modules/exporting.js +22 -0
  31. data/app/assets/javascripts/highstocks/modules/exporting.src.js +709 -0
  32. data/app/assets/javascripts/highstocks/modules/funnel.js +12 -0
  33. data/app/assets/javascripts/highstocks/modules/funnel.src.js +289 -0
  34. data/app/assets/javascripts/highstocks/modules/heatmap.js +2 -0
  35. data/app/assets/javascripts/highstocks/modules/heatmap.src.js +54 -0
  36. data/app/assets/javascripts/highstocks/modules/map.js +32 -0
  37. data/app/assets/javascripts/highstocks/modules/map.src.js +1273 -0
  38. data/app/assets/javascripts/highstocks/modules/no-data-to-display.js +12 -0
  39. data/app/assets/javascripts/highstocks/modules/no-data-to-display.src.js +128 -0
  40. data/app/assets/javascripts/highstocks/themes/dark-blue.js +254 -0
  41. data/app/assets/javascripts/highstocks/themes/dark-green.js +255 -0
  42. data/app/assets/javascripts/highstocks/themes/gray.js +257 -0
  43. data/app/assets/javascripts/highstocks/themes/grid.js +103 -0
  44. data/app/assets/javascripts/highstocks/themes/skies.js +89 -0
  45. data/highstocks-rails.gemspec +23 -0
  46. data/lib/highstocks-rails.rb +2 -0
  47. data/lib/highstocks/rails.rb +6 -0
  48. data/lib/highstocks/version.rb +3 -0
  49. metadata +133 -0
@@ -0,0 +1,2439 @@
1
+ // ==ClosureCompiler==
2
+ // @compilation_level SIMPLE_OPTIMIZATIONS
3
+
4
+ /**
5
+ * @license Highcharts JS v3.0.7 (2013-10-24)
6
+ *
7
+ * (c) 2009-2013 Torstein Hønsi
8
+ *
9
+ * License: www.highcharts.com/license
10
+ */
11
+
12
+ // JSLint options:
13
+ /*global Highcharts, HighchartsAdapter, document, window, navigator, setInterval, clearInterval, clearTimeout, setTimeout, location, jQuery, $, console */
14
+
15
+ (function (Highcharts, UNDEFINED) {
16
+ var arrayMin = Highcharts.arrayMin,
17
+ arrayMax = Highcharts.arrayMax,
18
+ each = Highcharts.each,
19
+ extend = Highcharts.extend,
20
+ merge = Highcharts.merge,
21
+ map = Highcharts.map,
22
+ pick = Highcharts.pick,
23
+ pInt = Highcharts.pInt,
24
+ defaultPlotOptions = Highcharts.getOptions().plotOptions,
25
+ seriesTypes = Highcharts.seriesTypes,
26
+ extendClass = Highcharts.extendClass,
27
+ splat = Highcharts.splat,
28
+ wrap = Highcharts.wrap,
29
+ Axis = Highcharts.Axis,
30
+ Tick = Highcharts.Tick,
31
+ Series = Highcharts.Series,
32
+ colProto = seriesTypes.column.prototype,
33
+ math = Math,
34
+ mathRound = math.round,
35
+ mathFloor = math.floor,
36
+ mathMax = math.max,
37
+ noop = function () {};/**
38
+ * The Pane object allows options that are common to a set of X and Y axes.
39
+ *
40
+ * In the future, this can be extended to basic Highcharts and Highstock.
41
+ */
42
+ function Pane(options, chart, firstAxis) {
43
+ this.init.call(this, options, chart, firstAxis);
44
+ }
45
+
46
+ // Extend the Pane prototype
47
+ extend(Pane.prototype, {
48
+
49
+ /**
50
+ * Initiate the Pane object
51
+ */
52
+ init: function (options, chart, firstAxis) {
53
+ var pane = this,
54
+ backgroundOption,
55
+ defaultOptions = pane.defaultOptions;
56
+
57
+ pane.chart = chart;
58
+
59
+ // Set options
60
+ if (chart.angular) { // gauges
61
+ defaultOptions.background = {}; // gets extended by this.defaultBackgroundOptions
62
+ }
63
+ pane.options = options = merge(defaultOptions, options);
64
+
65
+ backgroundOption = options.background;
66
+
67
+ // To avoid having weighty logic to place, update and remove the backgrounds,
68
+ // push them to the first axis' plot bands and borrow the existing logic there.
69
+ if (backgroundOption) {
70
+ each([].concat(splat(backgroundOption)).reverse(), function (config) {
71
+ var backgroundColor = config.backgroundColor; // if defined, replace the old one (specific for gradients)
72
+ config = merge(pane.defaultBackgroundOptions, config);
73
+ if (backgroundColor) {
74
+ config.backgroundColor = backgroundColor;
75
+ }
76
+ config.color = config.backgroundColor; // due to naming in plotBands
77
+ firstAxis.options.plotBands.unshift(config);
78
+ });
79
+ }
80
+ },
81
+
82
+ /**
83
+ * The default options object
84
+ */
85
+ defaultOptions: {
86
+ // background: {conditional},
87
+ center: ['50%', '50%'],
88
+ size: '85%',
89
+ startAngle: 0
90
+ //endAngle: startAngle + 360
91
+ },
92
+
93
+ /**
94
+ * The default background options
95
+ */
96
+ defaultBackgroundOptions: {
97
+ shape: 'circle',
98
+ borderWidth: 1,
99
+ borderColor: 'silver',
100
+ backgroundColor: {
101
+ linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },
102
+ stops: [
103
+ [0, '#FFF'],
104
+ [1, '#DDD']
105
+ ]
106
+ },
107
+ from: Number.MIN_VALUE, // corrected to axis min
108
+ innerRadius: 0,
109
+ to: Number.MAX_VALUE, // corrected to axis max
110
+ outerRadius: '105%'
111
+ }
112
+
113
+ });
114
+ var axisProto = Axis.prototype,
115
+ tickProto = Tick.prototype;
116
+
117
+ /**
118
+ * Augmented methods for the x axis in order to hide it completely, used for the X axis in gauges
119
+ */
120
+ var hiddenAxisMixin = {
121
+ getOffset: noop,
122
+ redraw: function () {
123
+ this.isDirty = false; // prevent setting Y axis dirty
124
+ },
125
+ render: function () {
126
+ this.isDirty = false; // prevent setting Y axis dirty
127
+ },
128
+ setScale: noop,
129
+ setCategories: noop,
130
+ setTitle: noop
131
+ };
132
+
133
+ /**
134
+ * Augmented methods for the value axis
135
+ */
136
+ /*jslint unparam: true*/
137
+ var radialAxisMixin = {
138
+ isRadial: true,
139
+
140
+ /**
141
+ * The default options extend defaultYAxisOptions
142
+ */
143
+ defaultRadialGaugeOptions: {
144
+ labels: {
145
+ align: 'center',
146
+ x: 0,
147
+ y: null // auto
148
+ },
149
+ minorGridLineWidth: 0,
150
+ minorTickInterval: 'auto',
151
+ minorTickLength: 10,
152
+ minorTickPosition: 'inside',
153
+ minorTickWidth: 1,
154
+ plotBands: [],
155
+ tickLength: 10,
156
+ tickPosition: 'inside',
157
+ tickWidth: 2,
158
+ title: {
159
+ rotation: 0
160
+ },
161
+ zIndex: 2 // behind dials, points in the series group
162
+ },
163
+
164
+ // Circular axis around the perimeter of a polar chart
165
+ defaultRadialXOptions: {
166
+ gridLineWidth: 1, // spokes
167
+ labels: {
168
+ align: null, // auto
169
+ distance: 15,
170
+ x: 0,
171
+ y: null // auto
172
+ },
173
+ maxPadding: 0,
174
+ minPadding: 0,
175
+ plotBands: [],
176
+ showLastLabel: false,
177
+ tickLength: 0
178
+ },
179
+
180
+ // Radial axis, like a spoke in a polar chart
181
+ defaultRadialYOptions: {
182
+ gridLineInterpolation: 'circle',
183
+ labels: {
184
+ align: 'right',
185
+ x: -3,
186
+ y: -2
187
+ },
188
+ plotBands: [],
189
+ showLastLabel: false,
190
+ title: {
191
+ x: 4,
192
+ text: null,
193
+ rotation: 90
194
+ }
195
+ },
196
+
197
+ /**
198
+ * Merge and set options
199
+ */
200
+ setOptions: function (userOptions) {
201
+
202
+ this.options = merge(
203
+ this.defaultOptions,
204
+ this.defaultRadialOptions,
205
+ userOptions
206
+ );
207
+
208
+ },
209
+
210
+ /**
211
+ * Wrap the getOffset method to return zero offset for title or labels in a radial
212
+ * axis
213
+ */
214
+ getOffset: function () {
215
+ // Call the Axis prototype method (the method we're in now is on the instance)
216
+ axisProto.getOffset.call(this);
217
+
218
+ // Title or label offsets are not counted
219
+ this.chart.axisOffset[this.side] = 0;
220
+ },
221
+
222
+
223
+ /**
224
+ * Get the path for the axis line. This method is also referenced in the getPlotLinePath
225
+ * method.
226
+ */
227
+ getLinePath: function (lineWidth, radius) {
228
+ var center = this.center;
229
+ radius = pick(radius, center[2] / 2 - this.offset);
230
+
231
+ return this.chart.renderer.symbols.arc(
232
+ this.left + center[0],
233
+ this.top + center[1],
234
+ radius,
235
+ radius,
236
+ {
237
+ start: this.startAngleRad,
238
+ end: this.endAngleRad,
239
+ open: true,
240
+ innerR: 0
241
+ }
242
+ );
243
+ },
244
+
245
+ /**
246
+ * Override setAxisTranslation by setting the translation to the difference
247
+ * in rotation. This allows the translate method to return angle for
248
+ * any given value.
249
+ */
250
+ setAxisTranslation: function () {
251
+
252
+ // Call uber method
253
+ axisProto.setAxisTranslation.call(this);
254
+
255
+ // Set transA and minPixelPadding
256
+ if (this.center) { // it's not defined the first time
257
+ if (this.isCircular) {
258
+
259
+ this.transA = (this.endAngleRad - this.startAngleRad) /
260
+ ((this.max - this.min) || 1);
261
+
262
+
263
+ } else {
264
+ this.transA = (this.center[2] / 2) / ((this.max - this.min) || 1);
265
+ }
266
+
267
+ if (this.isXAxis) {
268
+ this.minPixelPadding = this.transA * this.minPointOffset +
269
+ (this.reversed ? (this.endAngleRad - this.startAngleRad) / 4 : 0); // ???
270
+ }
271
+ }
272
+ },
273
+
274
+ /**
275
+ * In case of auto connect, add one closestPointRange to the max value right before
276
+ * tickPositions are computed, so that ticks will extend passed the real max.
277
+ */
278
+ beforeSetTickPositions: function () {
279
+ if (this.autoConnect) {
280
+ this.max += (this.categories && 1) || this.pointRange || this.closestPointRange || 0; // #1197, #2260
281
+ }
282
+ },
283
+
284
+ /**
285
+ * Override the setAxisSize method to use the arc's circumference as length. This
286
+ * allows tickPixelInterval to apply to pixel lengths along the perimeter
287
+ */
288
+ setAxisSize: function () {
289
+
290
+ axisProto.setAxisSize.call(this);
291
+
292
+ if (this.isRadial) {
293
+
294
+ // Set the center array
295
+ this.center = this.pane.center = seriesTypes.pie.prototype.getCenter.call(this.pane);
296
+
297
+ this.len = this.width = this.height = this.isCircular ?
298
+ this.center[2] * (this.endAngleRad - this.startAngleRad) / 2 :
299
+ this.center[2] / 2;
300
+ }
301
+ },
302
+
303
+ /**
304
+ * Returns the x, y coordinate of a point given by a value and a pixel distance
305
+ * from center
306
+ */
307
+ getPosition: function (value, length) {
308
+ if (!this.isCircular) {
309
+ length = this.translate(value);
310
+ value = this.min;
311
+ }
312
+
313
+ return this.postTranslate(
314
+ this.translate(value),
315
+ pick(length, this.center[2] / 2) - this.offset
316
+ );
317
+ },
318
+
319
+ /**
320
+ * Translate from intermediate plotX (angle), plotY (axis.len - radius) to final chart coordinates.
321
+ */
322
+ postTranslate: function (angle, radius) {
323
+
324
+ var chart = this.chart,
325
+ center = this.center;
326
+
327
+ angle = this.startAngleRad + angle;
328
+
329
+ return {
330
+ x: chart.plotLeft + center[0] + Math.cos(angle) * radius,
331
+ y: chart.plotTop + center[1] + Math.sin(angle) * radius
332
+ };
333
+
334
+ },
335
+
336
+ /**
337
+ * Find the path for plot bands along the radial axis
338
+ */
339
+ getPlotBandPath: function (from, to, options) {
340
+ var center = this.center,
341
+ startAngleRad = this.startAngleRad,
342
+ fullRadius = center[2] / 2,
343
+ radii = [
344
+ pick(options.outerRadius, '100%'),
345
+ options.innerRadius,
346
+ pick(options.thickness, 10)
347
+ ],
348
+ percentRegex = /%$/,
349
+ start,
350
+ end,
351
+ open,
352
+ isCircular = this.isCircular, // X axis in a polar chart
353
+ ret;
354
+
355
+ // Polygonal plot bands
356
+ if (this.options.gridLineInterpolation === 'polygon') {
357
+ ret = this.getPlotLinePath(from).concat(this.getPlotLinePath(to, true));
358
+
359
+ // Circular grid bands
360
+ } else {
361
+
362
+ // Plot bands on Y axis (radial axis) - inner and outer radius depend on to and from
363
+ if (!isCircular) {
364
+ radii[0] = this.translate(from);
365
+ radii[1] = this.translate(to);
366
+ }
367
+
368
+ // Convert percentages to pixel values
369
+ radii = map(radii, function (radius) {
370
+ if (percentRegex.test(radius)) {
371
+ radius = (pInt(radius, 10) * fullRadius) / 100;
372
+ }
373
+ return radius;
374
+ });
375
+
376
+ // Handle full circle
377
+ if (options.shape === 'circle' || !isCircular) {
378
+ start = -Math.PI / 2;
379
+ end = Math.PI * 1.5;
380
+ open = true;
381
+ } else {
382
+ start = startAngleRad + this.translate(from);
383
+ end = startAngleRad + this.translate(to);
384
+ }
385
+
386
+
387
+ ret = this.chart.renderer.symbols.arc(
388
+ this.left + center[0],
389
+ this.top + center[1],
390
+ radii[0],
391
+ radii[0],
392
+ {
393
+ start: start,
394
+ end: end,
395
+ innerR: pick(radii[1], radii[0] - radii[2]),
396
+ open: open
397
+ }
398
+ );
399
+ }
400
+
401
+ return ret;
402
+ },
403
+
404
+ /**
405
+ * Find the path for plot lines perpendicular to the radial axis.
406
+ */
407
+ getPlotLinePath: function (value, reverse) {
408
+ var axis = this,
409
+ center = axis.center,
410
+ chart = axis.chart,
411
+ end = axis.getPosition(value),
412
+ xAxis,
413
+ xy,
414
+ tickPositions,
415
+ ret;
416
+
417
+ // Spokes
418
+ if (axis.isCircular) {
419
+ ret = ['M', center[0] + chart.plotLeft, center[1] + chart.plotTop, 'L', end.x, end.y];
420
+
421
+ // Concentric circles
422
+ } else if (axis.options.gridLineInterpolation === 'circle') {
423
+ value = axis.translate(value);
424
+ if (value) { // a value of 0 is in the center
425
+ ret = axis.getLinePath(0, value);
426
+ }
427
+ // Concentric polygons
428
+ } else {
429
+ xAxis = chart.xAxis[0];
430
+ ret = [];
431
+ value = axis.translate(value);
432
+ tickPositions = xAxis.tickPositions;
433
+ if (xAxis.autoConnect) {
434
+ tickPositions = tickPositions.concat([tickPositions[0]]);
435
+ }
436
+ // Reverse the positions for concatenation of polygonal plot bands
437
+ if (reverse) {
438
+ tickPositions = [].concat(tickPositions).reverse();
439
+ }
440
+
441
+ each(tickPositions, function (pos, i) {
442
+ xy = xAxis.getPosition(pos, value);
443
+ ret.push(i ? 'L' : 'M', xy.x, xy.y);
444
+ });
445
+
446
+ }
447
+ return ret;
448
+ },
449
+
450
+ /**
451
+ * Find the position for the axis title, by default inside the gauge
452
+ */
453
+ getTitlePosition: function () {
454
+ var center = this.center,
455
+ chart = this.chart,
456
+ titleOptions = this.options.title;
457
+
458
+ return {
459
+ x: chart.plotLeft + center[0] + (titleOptions.x || 0),
460
+ y: chart.plotTop + center[1] - ({ high: 0.5, middle: 0.25, low: 0 }[titleOptions.align] *
461
+ center[2]) + (titleOptions.y || 0)
462
+ };
463
+ }
464
+
465
+ };
466
+ /*jslint unparam: false*/
467
+
468
+ /**
469
+ * Override axisProto.init to mix in special axis instance functions and function overrides
470
+ */
471
+ wrap(axisProto, 'init', function (proceed, chart, userOptions) {
472
+ var axis = this,
473
+ angular = chart.angular,
474
+ polar = chart.polar,
475
+ isX = userOptions.isX,
476
+ isHidden = angular && isX,
477
+ isCircular,
478
+ startAngleRad,
479
+ endAngleRad,
480
+ options,
481
+ chartOptions = chart.options,
482
+ paneIndex = userOptions.pane || 0,
483
+ pane,
484
+ paneOptions;
485
+
486
+ // Before prototype.init
487
+ if (angular) {
488
+ extend(this, isHidden ? hiddenAxisMixin : radialAxisMixin);
489
+ isCircular = !isX;
490
+ if (isCircular) {
491
+ this.defaultRadialOptions = this.defaultRadialGaugeOptions;
492
+ }
493
+
494
+ } else if (polar) {
495
+ //extend(this, userOptions.isX ? radialAxisMixin : radialAxisMixin);
496
+ extend(this, radialAxisMixin);
497
+ isCircular = isX;
498
+ this.defaultRadialOptions = isX ? this.defaultRadialXOptions : merge(this.defaultYAxisOptions, this.defaultRadialYOptions);
499
+
500
+ }
501
+
502
+ // Run prototype.init
503
+ proceed.call(this, chart, userOptions);
504
+
505
+ if (!isHidden && (angular || polar)) {
506
+ options = this.options;
507
+
508
+ // Create the pane and set the pane options.
509
+ if (!chart.panes) {
510
+ chart.panes = [];
511
+ }
512
+ this.pane = pane = chart.panes[paneIndex] = chart.panes[paneIndex] || new Pane(
513
+ splat(chartOptions.pane)[paneIndex],
514
+ chart,
515
+ axis
516
+ );
517
+ paneOptions = pane.options;
518
+
519
+
520
+ // Disable certain features on angular and polar axes
521
+ chart.inverted = false;
522
+ chartOptions.chart.zoomType = null;
523
+
524
+ // Start and end angle options are
525
+ // given in degrees relative to top, while internal computations are
526
+ // in radians relative to right (like SVG).
527
+ this.startAngleRad = startAngleRad = (paneOptions.startAngle - 90) * Math.PI / 180;
528
+ this.endAngleRad = endAngleRad = (pick(paneOptions.endAngle, paneOptions.startAngle + 360) - 90) * Math.PI / 180;
529
+ this.offset = options.offset || 0;
530
+
531
+ this.isCircular = isCircular;
532
+
533
+ // Automatically connect grid lines?
534
+ if (isCircular && userOptions.max === UNDEFINED && endAngleRad - startAngleRad === 2 * Math.PI) {
535
+ this.autoConnect = true;
536
+ }
537
+ }
538
+
539
+ });
540
+
541
+ /**
542
+ * Add special cases within the Tick class' methods for radial axes.
543
+ */
544
+ wrap(tickProto, 'getPosition', function (proceed, horiz, pos, tickmarkOffset, old) {
545
+ var axis = this.axis;
546
+
547
+ return axis.getPosition ?
548
+ axis.getPosition(pos) :
549
+ proceed.call(this, horiz, pos, tickmarkOffset, old);
550
+ });
551
+
552
+ /**
553
+ * Wrap the getLabelPosition function to find the center position of the label
554
+ * based on the distance option
555
+ */
556
+ wrap(tickProto, 'getLabelPosition', function (proceed, x, y, label, horiz, labelOptions, tickmarkOffset, index, step) {
557
+ var axis = this.axis,
558
+ optionsY = labelOptions.y,
559
+ ret,
560
+ align = labelOptions.align,
561
+ angle = ((axis.translate(this.pos) + axis.startAngleRad + Math.PI / 2) / Math.PI * 180) % 360;
562
+
563
+ if (axis.isRadial) {
564
+ ret = axis.getPosition(this.pos, (axis.center[2] / 2) + pick(labelOptions.distance, -25));
565
+
566
+ // Automatically rotated
567
+ if (labelOptions.rotation === 'auto') {
568
+ label.attr({
569
+ rotation: angle
570
+ });
571
+
572
+ // Vertically centered
573
+ } else if (optionsY === null) {
574
+ optionsY = pInt(label.styles.lineHeight) * 0.9 - label.getBBox().height / 2;
575
+
576
+ }
577
+
578
+ // Automatic alignment
579
+ if (align === null) {
580
+ if (axis.isCircular) {
581
+ if (angle > 20 && angle < 160) {
582
+ align = 'left'; // right hemisphere
583
+ } else if (angle > 200 && angle < 340) {
584
+ align = 'right'; // left hemisphere
585
+ } else {
586
+ align = 'center'; // top or bottom
587
+ }
588
+ } else {
589
+ align = 'center';
590
+ }
591
+ label.attr({
592
+ align: align
593
+ });
594
+ }
595
+
596
+ ret.x += labelOptions.x;
597
+ ret.y += optionsY;
598
+
599
+ } else {
600
+ ret = proceed.call(this, x, y, label, horiz, labelOptions, tickmarkOffset, index, step);
601
+ }
602
+ return ret;
603
+ });
604
+
605
+ /**
606
+ * Wrap the getMarkPath function to return the path of the radial marker
607
+ */
608
+ wrap(tickProto, 'getMarkPath', function (proceed, x, y, tickLength, tickWidth, horiz, renderer) {
609
+ var axis = this.axis,
610
+ endPoint,
611
+ ret;
612
+
613
+ if (axis.isRadial) {
614
+ endPoint = axis.getPosition(this.pos, axis.center[2] / 2 + tickLength);
615
+ ret = [
616
+ 'M',
617
+ x,
618
+ y,
619
+ 'L',
620
+ endPoint.x,
621
+ endPoint.y
622
+ ];
623
+ } else {
624
+ ret = proceed.call(this, x, y, tickLength, tickWidth, horiz, renderer);
625
+ }
626
+ return ret;
627
+ });/*
628
+ * The AreaRangeSeries class
629
+ *
630
+ */
631
+
632
+ /**
633
+ * Extend the default options with map options
634
+ */
635
+ defaultPlotOptions.arearange = merge(defaultPlotOptions.area, {
636
+ lineWidth: 1,
637
+ marker: null,
638
+ threshold: null,
639
+ tooltip: {
640
+ pointFormat: '<span style="color:{series.color}">{series.name}</span>: <b>{point.low}</b> - <b>{point.high}</b><br/>'
641
+ },
642
+ trackByArea: true,
643
+ dataLabels: {
644
+ verticalAlign: null,
645
+ xLow: 0,
646
+ xHigh: 0,
647
+ yLow: 0,
648
+ yHigh: 0
649
+ }
650
+ });
651
+
652
+ /**
653
+ * Add the series type
654
+ */
655
+ seriesTypes.arearange = Highcharts.extendClass(seriesTypes.area, {
656
+ type: 'arearange',
657
+ pointArrayMap: ['low', 'high'],
658
+ toYData: function (point) {
659
+ return [point.low, point.high];
660
+ },
661
+ pointValKey: 'low',
662
+
663
+ /**
664
+ * Extend getSegments to force null points if the higher value is null. #1703.
665
+ */
666
+ getSegments: function () {
667
+ var series = this;
668
+
669
+ each(series.points, function (point) {
670
+ if (!series.options.connectNulls && (point.low === null || point.high === null)) {
671
+ point.y = null;
672
+ } else if (point.low === null && point.high !== null) {
673
+ point.y = point.high;
674
+ }
675
+ });
676
+ Series.prototype.getSegments.call(this);
677
+ },
678
+
679
+ /**
680
+ * Translate data points from raw values x and y to plotX and plotY
681
+ */
682
+ translate: function () {
683
+ var series = this,
684
+ yAxis = series.yAxis;
685
+
686
+ seriesTypes.area.prototype.translate.apply(series);
687
+
688
+ // Set plotLow and plotHigh
689
+ each(series.points, function (point) {
690
+
691
+ var low = point.low,
692
+ high = point.high,
693
+ plotY = point.plotY;
694
+
695
+ if (high === null && low === null) {
696
+ point.y = null;
697
+ } else if (low === null) {
698
+ point.plotLow = point.plotY = null;
699
+ point.plotHigh = yAxis.translate(high, 0, 1, 0, 1);
700
+ } else if (high === null) {
701
+ point.plotLow = plotY;
702
+ point.plotHigh = null;
703
+ } else {
704
+ point.plotLow = plotY;
705
+ point.plotHigh = yAxis.translate(high, 0, 1, 0, 1);
706
+ }
707
+ });
708
+ },
709
+
710
+ /**
711
+ * Extend the line series' getSegmentPath method by applying the segment
712
+ * path to both lower and higher values of the range
713
+ */
714
+ getSegmentPath: function (segment) {
715
+
716
+ var lowSegment,
717
+ highSegment = [],
718
+ i = segment.length,
719
+ baseGetSegmentPath = Series.prototype.getSegmentPath,
720
+ point,
721
+ linePath,
722
+ lowerPath,
723
+ options = this.options,
724
+ step = options.step,
725
+ higherPath;
726
+
727
+ // Remove nulls from low segment
728
+ lowSegment = HighchartsAdapter.grep(segment, function (point) {
729
+ return point.plotLow !== null;
730
+ });
731
+
732
+ // Make a segment with plotX and plotY for the top values
733
+ while (i--) {
734
+ point = segment[i];
735
+ if (point.plotHigh !== null) {
736
+ highSegment.push({
737
+ plotX: point.plotX,
738
+ plotY: point.plotHigh
739
+ });
740
+ }
741
+ }
742
+
743
+ // Get the paths
744
+ lowerPath = baseGetSegmentPath.call(this, lowSegment);
745
+ if (step) {
746
+ if (step === true) {
747
+ step = 'left';
748
+ }
749
+ options.step = { left: 'right', center: 'center', right: 'left' }[step]; // swap for reading in getSegmentPath
750
+ }
751
+ higherPath = baseGetSegmentPath.call(this, highSegment);
752
+ options.step = step;
753
+
754
+ // Create a line on both top and bottom of the range
755
+ linePath = [].concat(lowerPath, higherPath);
756
+
757
+ // For the area path, we need to change the 'move' statement into 'lineTo' or 'curveTo'
758
+ higherPath[0] = 'L'; // this probably doesn't work for spline
759
+ this.areaPath = this.areaPath.concat(lowerPath, higherPath);
760
+
761
+ return linePath;
762
+ },
763
+
764
+ /**
765
+ * Extend the basic drawDataLabels method by running it for both lower and higher
766
+ * values.
767
+ */
768
+ drawDataLabels: function () {
769
+
770
+ var data = this.data,
771
+ length = data.length,
772
+ i,
773
+ originalDataLabels = [],
774
+ seriesProto = Series.prototype,
775
+ dataLabelOptions = this.options.dataLabels,
776
+ point,
777
+ inverted = this.chart.inverted;
778
+
779
+ if (dataLabelOptions.enabled || this._hasPointLabels) {
780
+
781
+ // Step 1: set preliminary values for plotY and dataLabel and draw the upper labels
782
+ i = length;
783
+ while (i--) {
784
+ point = data[i];
785
+
786
+ // Set preliminary values
787
+ point.y = point.high;
788
+ point.plotY = point.plotHigh;
789
+
790
+ // Store original data labels and set preliminary label objects to be picked up
791
+ // in the uber method
792
+ originalDataLabels[i] = point.dataLabel;
793
+ point.dataLabel = point.dataLabelUpper;
794
+
795
+ // Set the default offset
796
+ point.below = false;
797
+ if (inverted) {
798
+ dataLabelOptions.align = 'left';
799
+ dataLabelOptions.x = dataLabelOptions.xHigh;
800
+ } else {
801
+ dataLabelOptions.y = dataLabelOptions.yHigh;
802
+ }
803
+ }
804
+ seriesProto.drawDataLabels.apply(this, arguments); // #1209
805
+
806
+ // Step 2: reorganize and handle data labels for the lower values
807
+ i = length;
808
+ while (i--) {
809
+ point = data[i];
810
+
811
+ // Move the generated labels from step 1, and reassign the original data labels
812
+ point.dataLabelUpper = point.dataLabel;
813
+ point.dataLabel = originalDataLabels[i];
814
+
815
+ // Reset values
816
+ point.y = point.low;
817
+ point.plotY = point.plotLow;
818
+
819
+ // Set the default offset
820
+ point.below = true;
821
+ if (inverted) {
822
+ dataLabelOptions.align = 'right';
823
+ dataLabelOptions.x = dataLabelOptions.xLow;
824
+ } else {
825
+ dataLabelOptions.y = dataLabelOptions.yLow;
826
+ }
827
+ }
828
+ seriesProto.drawDataLabels.apply(this, arguments);
829
+ }
830
+
831
+ },
832
+
833
+ alignDataLabel: seriesTypes.column.prototype.alignDataLabel,
834
+
835
+ getSymbol: seriesTypes.column.prototype.getSymbol,
836
+
837
+ drawPoints: noop
838
+ });/**
839
+ * The AreaSplineRangeSeries class
840
+ */
841
+
842
+ defaultPlotOptions.areasplinerange = merge(defaultPlotOptions.arearange);
843
+
844
+ /**
845
+ * AreaSplineRangeSeries object
846
+ */
847
+ seriesTypes.areasplinerange = extendClass(seriesTypes.arearange, {
848
+ type: 'areasplinerange',
849
+ getPointSpline: seriesTypes.spline.prototype.getPointSpline
850
+ });/**
851
+ * The ColumnRangeSeries class
852
+ */
853
+ defaultPlotOptions.columnrange = merge(defaultPlotOptions.column, defaultPlotOptions.arearange, {
854
+ lineWidth: 1,
855
+ pointRange: null
856
+ });
857
+
858
+ /**
859
+ * ColumnRangeSeries object
860
+ */
861
+ seriesTypes.columnrange = extendClass(seriesTypes.arearange, {
862
+ type: 'columnrange',
863
+ /**
864
+ * Translate data points from raw values x and y to plotX and plotY
865
+ */
866
+ translate: function () {
867
+ var series = this,
868
+ yAxis = series.yAxis,
869
+ plotHigh;
870
+
871
+ colProto.translate.apply(series);
872
+
873
+ // Set plotLow and plotHigh
874
+ each(series.points, function (point) {
875
+ var shapeArgs = point.shapeArgs,
876
+ minPointLength = series.options.minPointLength,
877
+ heightDifference,
878
+ height,
879
+ y;
880
+
881
+ point.plotHigh = plotHigh = yAxis.translate(point.high, 0, 1, 0, 1);
882
+ point.plotLow = point.plotY;
883
+
884
+ // adjust shape
885
+ y = plotHigh;
886
+ height = point.plotY - plotHigh;
887
+
888
+ if (height < minPointLength) {
889
+ heightDifference = (minPointLength - height);
890
+ height += heightDifference;
891
+ y -= heightDifference / 2;
892
+ }
893
+ shapeArgs.height = height;
894
+ shapeArgs.y = y;
895
+ });
896
+ },
897
+ trackerGroups: ['group', 'dataLabels'],
898
+ drawGraph: noop,
899
+ pointAttrToOptions: colProto.pointAttrToOptions,
900
+ drawPoints: colProto.drawPoints,
901
+ drawTracker: colProto.drawTracker,
902
+ animate: colProto.animate,
903
+ getColumnMetrics: colProto.getColumnMetrics
904
+ });
905
+ /*
906
+ * The GaugeSeries class
907
+ */
908
+
909
+
910
+
911
+ /**
912
+ * Extend the default options
913
+ */
914
+ defaultPlotOptions.gauge = merge(defaultPlotOptions.line, {
915
+ dataLabels: {
916
+ enabled: true,
917
+ y: 15,
918
+ borderWidth: 1,
919
+ borderColor: 'silver',
920
+ borderRadius: 3,
921
+ style: {
922
+ fontWeight: 'bold'
923
+ },
924
+ verticalAlign: 'top',
925
+ zIndex: 2
926
+ },
927
+ dial: {
928
+ // radius: '80%',
929
+ // backgroundColor: 'black',
930
+ // borderColor: 'silver',
931
+ // borderWidth: 0,
932
+ // baseWidth: 3,
933
+ // topWidth: 1,
934
+ // baseLength: '70%' // of radius
935
+ // rearLength: '10%'
936
+ },
937
+ pivot: {
938
+ //radius: 5,
939
+ //borderWidth: 0
940
+ //borderColor: 'silver',
941
+ //backgroundColor: 'black'
942
+ },
943
+ tooltip: {
944
+ headerFormat: ''
945
+ },
946
+ showInLegend: false
947
+ });
948
+
949
+ /**
950
+ * Extend the point object
951
+ */
952
+ var GaugePoint = Highcharts.extendClass(Highcharts.Point, {
953
+ /**
954
+ * Don't do any hover colors or anything
955
+ */
956
+ setState: function (state) {
957
+ this.state = state;
958
+ }
959
+ });
960
+
961
+
962
+ /**
963
+ * Add the series type
964
+ */
965
+ var GaugeSeries = {
966
+ type: 'gauge',
967
+ pointClass: GaugePoint,
968
+
969
+ // chart.angular will be set to true when a gauge series is present, and this will
970
+ // be used on the axes
971
+ angular: true,
972
+ drawGraph: noop,
973
+ fixedBox: true,
974
+ trackerGroups: ['group', 'dataLabels'],
975
+
976
+ /**
977
+ * Calculate paths etc
978
+ */
979
+ translate: function () {
980
+
981
+ var series = this,
982
+ yAxis = series.yAxis,
983
+ options = series.options,
984
+ center = yAxis.center;
985
+
986
+ series.generatePoints();
987
+
988
+ each(series.points, function (point) {
989
+
990
+ var dialOptions = merge(options.dial, point.dial),
991
+ radius = (pInt(pick(dialOptions.radius, 80)) * center[2]) / 200,
992
+ baseLength = (pInt(pick(dialOptions.baseLength, 70)) * radius) / 100,
993
+ rearLength = (pInt(pick(dialOptions.rearLength, 10)) * radius) / 100,
994
+ baseWidth = dialOptions.baseWidth || 3,
995
+ topWidth = dialOptions.topWidth || 1,
996
+ rotation = yAxis.startAngleRad + yAxis.translate(point.y, null, null, null, true);
997
+
998
+ // Handle the wrap option
999
+ if (options.wrap === false) {
1000
+ rotation = Math.max(yAxis.startAngleRad, Math.min(yAxis.endAngleRad, rotation));
1001
+ }
1002
+ rotation = rotation * 180 / Math.PI;
1003
+
1004
+ point.shapeType = 'path';
1005
+ point.shapeArgs = {
1006
+ d: dialOptions.path || [
1007
+ 'M',
1008
+ -rearLength, -baseWidth / 2,
1009
+ 'L',
1010
+ baseLength, -baseWidth / 2,
1011
+ radius, -topWidth / 2,
1012
+ radius, topWidth / 2,
1013
+ baseLength, baseWidth / 2,
1014
+ -rearLength, baseWidth / 2,
1015
+ 'z'
1016
+ ],
1017
+ translateX: center[0],
1018
+ translateY: center[1],
1019
+ rotation: rotation
1020
+ };
1021
+
1022
+ // Positions for data label
1023
+ point.plotX = center[0];
1024
+ point.plotY = center[1];
1025
+ });
1026
+ },
1027
+
1028
+ /**
1029
+ * Draw the points where each point is one needle
1030
+ */
1031
+ drawPoints: function () {
1032
+
1033
+ var series = this,
1034
+ center = series.yAxis.center,
1035
+ pivot = series.pivot,
1036
+ options = series.options,
1037
+ pivotOptions = options.pivot,
1038
+ renderer = series.chart.renderer;
1039
+
1040
+ each(series.points, function (point) {
1041
+
1042
+ var graphic = point.graphic,
1043
+ shapeArgs = point.shapeArgs,
1044
+ d = shapeArgs.d,
1045
+ dialOptions = merge(options.dial, point.dial); // #1233
1046
+
1047
+ if (graphic) {
1048
+ graphic.animate(shapeArgs);
1049
+ shapeArgs.d = d; // animate alters it
1050
+ } else {
1051
+ point.graphic = renderer[point.shapeType](shapeArgs)
1052
+ .attr({
1053
+ stroke: dialOptions.borderColor || 'none',
1054
+ 'stroke-width': dialOptions.borderWidth || 0,
1055
+ fill: dialOptions.backgroundColor || 'black',
1056
+ rotation: shapeArgs.rotation // required by VML when animation is false
1057
+ })
1058
+ .add(series.group);
1059
+ }
1060
+ });
1061
+
1062
+ // Add or move the pivot
1063
+ if (pivot) {
1064
+ pivot.animate({ // #1235
1065
+ translateX: center[0],
1066
+ translateY: center[1]
1067
+ });
1068
+ } else {
1069
+ series.pivot = renderer.circle(0, 0, pick(pivotOptions.radius, 5))
1070
+ .attr({
1071
+ 'stroke-width': pivotOptions.borderWidth || 0,
1072
+ stroke: pivotOptions.borderColor || 'silver',
1073
+ fill: pivotOptions.backgroundColor || 'black'
1074
+ })
1075
+ .translate(center[0], center[1])
1076
+ .add(series.group);
1077
+ }
1078
+ },
1079
+
1080
+ /**
1081
+ * Animate the arrow up from startAngle
1082
+ */
1083
+ animate: function (init) {
1084
+ var series = this;
1085
+
1086
+ if (!init) {
1087
+ each(series.points, function (point) {
1088
+ var graphic = point.graphic;
1089
+
1090
+ if (graphic) {
1091
+ // start value
1092
+ graphic.attr({
1093
+ rotation: series.yAxis.startAngleRad * 180 / Math.PI
1094
+ });
1095
+
1096
+ // animate
1097
+ graphic.animate({
1098
+ rotation: point.shapeArgs.rotation
1099
+ }, series.options.animation);
1100
+ }
1101
+ });
1102
+
1103
+ // delete this function to allow it only once
1104
+ series.animate = null;
1105
+ }
1106
+ },
1107
+
1108
+ render: function () {
1109
+ this.group = this.plotGroup(
1110
+ 'group',
1111
+ 'series',
1112
+ this.visible ? 'visible' : 'hidden',
1113
+ this.options.zIndex,
1114
+ this.chart.seriesGroup
1115
+ );
1116
+ seriesTypes.pie.prototype.render.call(this);
1117
+ this.group.clip(this.chart.clipRect);
1118
+ },
1119
+
1120
+ setData: seriesTypes.pie.prototype.setData,
1121
+ drawTracker: seriesTypes.column.prototype.drawTracker
1122
+ };
1123
+ seriesTypes.gauge = Highcharts.extendClass(seriesTypes.line, GaugeSeries);/* ****************************************************************************
1124
+ * Start Box plot series code *
1125
+ *****************************************************************************/
1126
+
1127
+ // Set default options
1128
+ defaultPlotOptions.boxplot = merge(defaultPlotOptions.column, {
1129
+ fillColor: '#FFFFFF',
1130
+ lineWidth: 1,
1131
+ //medianColor: null,
1132
+ medianWidth: 2,
1133
+ states: {
1134
+ hover: {
1135
+ brightness: -0.3
1136
+ }
1137
+ },
1138
+ //stemColor: null,
1139
+ //stemDashStyle: 'solid'
1140
+ //stemWidth: null,
1141
+ threshold: null,
1142
+ tooltip: {
1143
+ pointFormat: '<span style="color:{series.color};font-weight:bold">{series.name}</span><br/>' +
1144
+ 'Maximum: {point.high}<br/>' +
1145
+ 'Upper quartile: {point.q3}<br/>' +
1146
+ 'Median: {point.median}<br/>' +
1147
+ 'Lower quartile: {point.q1}<br/>' +
1148
+ 'Minimum: {point.low}<br/>'
1149
+
1150
+ },
1151
+ //whiskerColor: null,
1152
+ whiskerLength: '50%',
1153
+ whiskerWidth: 2
1154
+ });
1155
+
1156
+ // Create the series object
1157
+ seriesTypes.boxplot = extendClass(seriesTypes.column, {
1158
+ type: 'boxplot',
1159
+ pointArrayMap: ['low', 'q1', 'median', 'q3', 'high'], // array point configs are mapped to this
1160
+ toYData: function (point) { // return a plain array for speedy calculation
1161
+ return [point.low, point.q1, point.median, point.q3, point.high];
1162
+ },
1163
+ pointValKey: 'high', // defines the top of the tracker
1164
+
1165
+ /**
1166
+ * One-to-one mapping from options to SVG attributes
1167
+ */
1168
+ pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
1169
+ fill: 'fillColor',
1170
+ stroke: 'color',
1171
+ 'stroke-width': 'lineWidth'
1172
+ },
1173
+
1174
+ /**
1175
+ * Disable data labels for box plot
1176
+ */
1177
+ drawDataLabels: noop,
1178
+
1179
+ /**
1180
+ * Translate data points from raw values x and y to plotX and plotY
1181
+ */
1182
+ translate: function () {
1183
+ var series = this,
1184
+ yAxis = series.yAxis,
1185
+ pointArrayMap = series.pointArrayMap;
1186
+
1187
+ seriesTypes.column.prototype.translate.apply(series);
1188
+
1189
+ // do the translation on each point dimension
1190
+ each(series.points, function (point) {
1191
+ each(pointArrayMap, function (key) {
1192
+ if (point[key] !== null) {
1193
+ point[key + 'Plot'] = yAxis.translate(point[key], 0, 1, 0, 1);
1194
+ }
1195
+ });
1196
+ });
1197
+ },
1198
+
1199
+ /**
1200
+ * Draw the data points
1201
+ */
1202
+ drawPoints: function () {
1203
+ var series = this, //state = series.state,
1204
+ points = series.points,
1205
+ options = series.options,
1206
+ chart = series.chart,
1207
+ renderer = chart.renderer,
1208
+ pointAttr,
1209
+ q1Plot,
1210
+ q3Plot,
1211
+ highPlot,
1212
+ lowPlot,
1213
+ medianPlot,
1214
+ crispCorr,
1215
+ crispX,
1216
+ graphic,
1217
+ stemPath,
1218
+ stemAttr,
1219
+ boxPath,
1220
+ whiskersPath,
1221
+ whiskersAttr,
1222
+ medianPath,
1223
+ medianAttr,
1224
+ width,
1225
+ left,
1226
+ right,
1227
+ halfWidth,
1228
+ shapeArgs,
1229
+ color,
1230
+ doQuartiles = series.doQuartiles !== false, // error bar inherits this series type but doesn't do quartiles
1231
+ whiskerLength = parseInt(series.options.whiskerLength, 10) / 100;
1232
+
1233
+
1234
+ each(points, function (point) {
1235
+
1236
+ graphic = point.graphic;
1237
+ shapeArgs = point.shapeArgs; // the box
1238
+ stemAttr = {};
1239
+ whiskersAttr = {};
1240
+ medianAttr = {};
1241
+ color = point.color || series.color;
1242
+
1243
+ if (point.plotY !== UNDEFINED) {
1244
+
1245
+ pointAttr = point.pointAttr[point.selected ? 'selected' : ''];
1246
+
1247
+ // crisp vector coordinates
1248
+ width = shapeArgs.width;
1249
+ left = mathFloor(shapeArgs.x);
1250
+ right = left + width;
1251
+ halfWidth = mathRound(width / 2);
1252
+ //crispX = mathRound(left + halfWidth) + crispCorr;
1253
+ q1Plot = mathFloor(doQuartiles ? point.q1Plot : point.lowPlot);// + crispCorr;
1254
+ q3Plot = mathFloor(doQuartiles ? point.q3Plot : point.lowPlot);// + crispCorr;
1255
+ highPlot = mathFloor(point.highPlot);// + crispCorr;
1256
+ lowPlot = mathFloor(point.lowPlot);// + crispCorr;
1257
+
1258
+ // Stem attributes
1259
+ stemAttr.stroke = point.stemColor || options.stemColor || color;
1260
+ stemAttr['stroke-width'] = pick(point.stemWidth, options.stemWidth, options.lineWidth);
1261
+ stemAttr.dashstyle = point.stemDashStyle || options.stemDashStyle;
1262
+
1263
+ // Whiskers attributes
1264
+ whiskersAttr.stroke = point.whiskerColor || options.whiskerColor || color;
1265
+ whiskersAttr['stroke-width'] = pick(point.whiskerWidth, options.whiskerWidth, options.lineWidth);
1266
+
1267
+ // Median attributes
1268
+ medianAttr.stroke = point.medianColor || options.medianColor || color;
1269
+ medianAttr['stroke-width'] = pick(point.medianWidth, options.medianWidth, options.lineWidth);
1270
+ medianAttr['stroke-linecap'] = 'round'; // #1638
1271
+
1272
+
1273
+ // The stem
1274
+ crispCorr = (stemAttr['stroke-width'] % 2) / 2;
1275
+ crispX = left + halfWidth + crispCorr;
1276
+ stemPath = [
1277
+ // stem up
1278
+ 'M',
1279
+ crispX, q3Plot,
1280
+ 'L',
1281
+ crispX, highPlot,
1282
+
1283
+ // stem down
1284
+ 'M',
1285
+ crispX, q1Plot,
1286
+ 'L',
1287
+ crispX, lowPlot,
1288
+ 'z'
1289
+ ];
1290
+
1291
+ // The box
1292
+ if (doQuartiles) {
1293
+ crispCorr = (pointAttr['stroke-width'] % 2) / 2;
1294
+ crispX = mathFloor(crispX) + crispCorr;
1295
+ q1Plot = mathFloor(q1Plot) + crispCorr;
1296
+ q3Plot = mathFloor(q3Plot) + crispCorr;
1297
+ left += crispCorr;
1298
+ right += crispCorr;
1299
+ boxPath = [
1300
+ 'M',
1301
+ left, q3Plot,
1302
+ 'L',
1303
+ left, q1Plot,
1304
+ 'L',
1305
+ right, q1Plot,
1306
+ 'L',
1307
+ right, q3Plot,
1308
+ 'L',
1309
+ left, q3Plot,
1310
+ 'z'
1311
+ ];
1312
+ }
1313
+
1314
+ // The whiskers
1315
+ if (whiskerLength) {
1316
+ crispCorr = (whiskersAttr['stroke-width'] % 2) / 2;
1317
+ highPlot = highPlot + crispCorr;
1318
+ lowPlot = lowPlot + crispCorr;
1319
+ whiskersPath = [
1320
+ // High whisker
1321
+ 'M',
1322
+ crispX - halfWidth * whiskerLength,
1323
+ highPlot,
1324
+ 'L',
1325
+ crispX + halfWidth * whiskerLength,
1326
+ highPlot,
1327
+
1328
+ // Low whisker
1329
+ 'M',
1330
+ crispX - halfWidth * whiskerLength,
1331
+ lowPlot,
1332
+ 'L',
1333
+ crispX + halfWidth * whiskerLength,
1334
+ lowPlot
1335
+ ];
1336
+ }
1337
+
1338
+ // The median
1339
+ crispCorr = (medianAttr['stroke-width'] % 2) / 2;
1340
+ medianPlot = mathRound(point.medianPlot) + crispCorr;
1341
+ medianPath = [
1342
+ 'M',
1343
+ left,
1344
+ medianPlot,
1345
+ 'L',
1346
+ right,
1347
+ medianPlot,
1348
+ 'z'
1349
+ ];
1350
+
1351
+ // Create or update the graphics
1352
+ if (graphic) { // update
1353
+
1354
+ point.stem.animate({ d: stemPath });
1355
+ if (whiskerLength) {
1356
+ point.whiskers.animate({ d: whiskersPath });
1357
+ }
1358
+ if (doQuartiles) {
1359
+ point.box.animate({ d: boxPath });
1360
+ }
1361
+ point.medianShape.animate({ d: medianPath });
1362
+
1363
+ } else { // create new
1364
+ point.graphic = graphic = renderer.g()
1365
+ .add(series.group);
1366
+
1367
+ point.stem = renderer.path(stemPath)
1368
+ .attr(stemAttr)
1369
+ .add(graphic);
1370
+
1371
+ if (whiskerLength) {
1372
+ point.whiskers = renderer.path(whiskersPath)
1373
+ .attr(whiskersAttr)
1374
+ .add(graphic);
1375
+ }
1376
+ if (doQuartiles) {
1377
+ point.box = renderer.path(boxPath)
1378
+ .attr(pointAttr)
1379
+ .add(graphic);
1380
+ }
1381
+ point.medianShape = renderer.path(medianPath)
1382
+ .attr(medianAttr)
1383
+ .add(graphic);
1384
+ }
1385
+ }
1386
+ });
1387
+
1388
+ }
1389
+
1390
+
1391
+ });
1392
+
1393
+ /* ****************************************************************************
1394
+ * End Box plot series code *
1395
+ *****************************************************************************/
1396
+ /* ****************************************************************************
1397
+ * Start error bar series code *
1398
+ *****************************************************************************/
1399
+
1400
+ // 1 - set default options
1401
+ defaultPlotOptions.errorbar = merge(defaultPlotOptions.boxplot, {
1402
+ color: '#000000',
1403
+ grouping: false,
1404
+ linkedTo: ':previous',
1405
+ tooltip: {
1406
+ pointFormat: defaultPlotOptions.arearange.tooltip.pointFormat
1407
+ },
1408
+ whiskerWidth: null
1409
+ });
1410
+
1411
+ // 2 - Create the series object
1412
+ seriesTypes.errorbar = extendClass(seriesTypes.boxplot, {
1413
+ type: 'errorbar',
1414
+ pointArrayMap: ['low', 'high'], // array point configs are mapped to this
1415
+ toYData: function (point) { // return a plain array for speedy calculation
1416
+ return [point.low, point.high];
1417
+ },
1418
+ pointValKey: 'high', // defines the top of the tracker
1419
+ doQuartiles: false,
1420
+
1421
+ /**
1422
+ * Get the width and X offset, either on top of the linked series column
1423
+ * or standalone
1424
+ */
1425
+ getColumnMetrics: function () {
1426
+ return (this.linkedParent && this.linkedParent.columnMetrics) ||
1427
+ seriesTypes.column.prototype.getColumnMetrics.call(this);
1428
+ }
1429
+ });
1430
+
1431
+ /* ****************************************************************************
1432
+ * End error bar series code *
1433
+ *****************************************************************************/
1434
+ /* ****************************************************************************
1435
+ * Start Waterfall series code *
1436
+ *****************************************************************************/
1437
+
1438
+ // 1 - set default options
1439
+ defaultPlotOptions.waterfall = merge(defaultPlotOptions.column, {
1440
+ lineWidth: 1,
1441
+ lineColor: '#333',
1442
+ dashStyle: 'dot',
1443
+ borderColor: '#333'
1444
+ });
1445
+
1446
+
1447
+ // 2 - Create the series object
1448
+ seriesTypes.waterfall = extendClass(seriesTypes.column, {
1449
+ type: 'waterfall',
1450
+
1451
+ upColorProp: 'fill',
1452
+
1453
+ pointArrayMap: ['low', 'y'],
1454
+
1455
+ pointValKey: 'y',
1456
+
1457
+ /**
1458
+ * Init waterfall series, force stacking
1459
+ */
1460
+ init: function (chart, options) {
1461
+ // force stacking
1462
+ options.stacking = true;
1463
+
1464
+ seriesTypes.column.prototype.init.call(this, chart, options);
1465
+ },
1466
+
1467
+
1468
+ /**
1469
+ * Translate data points from raw values
1470
+ */
1471
+ translate: function () {
1472
+ var series = this,
1473
+ options = series.options,
1474
+ axis = series.yAxis,
1475
+ len,
1476
+ i,
1477
+ points,
1478
+ point,
1479
+ shapeArgs,
1480
+ stack,
1481
+ y,
1482
+ previousY,
1483
+ stackPoint,
1484
+ threshold = options.threshold,
1485
+ crispCorr = (options.borderWidth % 2) / 2;
1486
+
1487
+ // run column series translate
1488
+ seriesTypes.column.prototype.translate.apply(this);
1489
+
1490
+ previousY = threshold;
1491
+ points = series.points;
1492
+
1493
+ for (i = 0, len = points.length; i < len; i++) {
1494
+ // cache current point object
1495
+ point = points[i];
1496
+ shapeArgs = point.shapeArgs;
1497
+
1498
+ // get current stack
1499
+ stack = series.getStack(i);
1500
+ stackPoint = stack.points[series.index];
1501
+
1502
+ // override point value for sums
1503
+ if (isNaN(point.y)) {
1504
+ point.y = series.yData[i];
1505
+ }
1506
+
1507
+ // up points
1508
+ y = mathMax(previousY, previousY + point.y) + stackPoint[0];
1509
+ shapeArgs.y = axis.translate(y, 0, 1);
1510
+
1511
+
1512
+ // sum points
1513
+ if (point.isSum || point.isIntermediateSum) {
1514
+ shapeArgs.y = axis.translate(stackPoint[1], 0, 1);
1515
+ shapeArgs.height = axis.translate(stackPoint[0], 0, 1) - shapeArgs.y;
1516
+
1517
+ // if it's not the sum point, update previous stack end position
1518
+ } else {
1519
+ previousY += stack.total;
1520
+ }
1521
+
1522
+ // negative points
1523
+ if (shapeArgs.height < 0) {
1524
+ shapeArgs.y += shapeArgs.height;
1525
+ shapeArgs.height *= -1;
1526
+ }
1527
+
1528
+ point.plotY = shapeArgs.y = mathRound(shapeArgs.y) - crispCorr;
1529
+ shapeArgs.height = mathRound(shapeArgs.height);
1530
+ point.yBottom = shapeArgs.y + shapeArgs.height;
1531
+ }
1532
+ },
1533
+
1534
+ /**
1535
+ * Call default processData then override yData to reflect waterfall's extremes on yAxis
1536
+ */
1537
+ processData: function (force) {
1538
+ var series = this,
1539
+ options = series.options,
1540
+ yData = series.yData,
1541
+ points = series.points,
1542
+ point,
1543
+ dataLength = yData.length,
1544
+ threshold = options.threshold || 0,
1545
+ subSum,
1546
+ sum,
1547
+ dataMin,
1548
+ dataMax,
1549
+ y,
1550
+ i;
1551
+
1552
+ sum = subSum = dataMin = dataMax = threshold;
1553
+
1554
+ for (i = 0; i < dataLength; i++) {
1555
+ y = yData[i];
1556
+ point = points && points[i] ? points[i] : {};
1557
+
1558
+ if (y === "sum" || point.isSum) {
1559
+ yData[i] = sum;
1560
+ } else if (y === "intermediateSum" || point.isIntermediateSum) {
1561
+ yData[i] = subSum;
1562
+ subSum = threshold;
1563
+ } else {
1564
+ sum += y;
1565
+ subSum += y;
1566
+ }
1567
+ dataMin = Math.min(sum, dataMin);
1568
+ dataMax = Math.max(sum, dataMax);
1569
+ }
1570
+
1571
+ Series.prototype.processData.call(this, force);
1572
+
1573
+ // Record extremes
1574
+ series.dataMin = dataMin;
1575
+ series.dataMax = dataMax;
1576
+ },
1577
+
1578
+ /**
1579
+ * Return y value or string if point is sum
1580
+ */
1581
+ toYData: function (pt) {
1582
+ if (pt.isSum) {
1583
+ return "sum";
1584
+ } else if (pt.isIntermediateSum) {
1585
+ return "intermediateSum";
1586
+ }
1587
+
1588
+ return pt.y;
1589
+ },
1590
+
1591
+ /**
1592
+ * Postprocess mapping between options and SVG attributes
1593
+ */
1594
+ getAttribs: function () {
1595
+ seriesTypes.column.prototype.getAttribs.apply(this, arguments);
1596
+
1597
+ var series = this,
1598
+ options = series.options,
1599
+ stateOptions = options.states,
1600
+ upColor = options.upColor || series.color,
1601
+ hoverColor = Highcharts.Color(upColor).brighten(0.1).get(),
1602
+ seriesDownPointAttr = merge(series.pointAttr),
1603
+ upColorProp = series.upColorProp;
1604
+
1605
+ seriesDownPointAttr[''][upColorProp] = upColor;
1606
+ seriesDownPointAttr.hover[upColorProp] = stateOptions.hover.upColor || hoverColor;
1607
+ seriesDownPointAttr.select[upColorProp] = stateOptions.select.upColor || upColor;
1608
+
1609
+ each(series.points, function (point) {
1610
+ if (point.y > 0 && !point.color) {
1611
+ point.pointAttr = seriesDownPointAttr;
1612
+ point.color = upColor;
1613
+ }
1614
+ });
1615
+ },
1616
+
1617
+ /**
1618
+ * Draw columns' connector lines
1619
+ */
1620
+ getGraphPath: function () {
1621
+
1622
+ var data = this.data,
1623
+ length = data.length,
1624
+ lineWidth = this.options.lineWidth + this.options.borderWidth,
1625
+ normalizer = mathRound(lineWidth) % 2 / 2,
1626
+ path = [],
1627
+ M = 'M',
1628
+ L = 'L',
1629
+ prevArgs,
1630
+ pointArgs,
1631
+ i,
1632
+ d;
1633
+
1634
+ for (i = 1; i < length; i++) {
1635
+ pointArgs = data[i].shapeArgs;
1636
+ prevArgs = data[i - 1].shapeArgs;
1637
+
1638
+ d = [
1639
+ M,
1640
+ prevArgs.x + prevArgs.width, prevArgs.y + normalizer,
1641
+ L,
1642
+ pointArgs.x, prevArgs.y + normalizer
1643
+ ];
1644
+
1645
+ if (data[i - 1].y < 0) {
1646
+ d[2] += prevArgs.height;
1647
+ d[5] += prevArgs.height;
1648
+ }
1649
+
1650
+ path = path.concat(d);
1651
+ }
1652
+
1653
+ return path;
1654
+ },
1655
+
1656
+ /**
1657
+ * Extremes are recorded in processData
1658
+ */
1659
+ getExtremes: noop,
1660
+
1661
+ /**
1662
+ * Return stack for given index
1663
+ */
1664
+ getStack: function (i) {
1665
+ var axis = this.yAxis,
1666
+ stacks = axis.stacks,
1667
+ key = this.stackKey;
1668
+
1669
+ if (this.processedYData[i] < this.options.threshold) {
1670
+ key = '-' + key;
1671
+ }
1672
+
1673
+ return stacks[key][i];
1674
+ },
1675
+
1676
+ drawGraph: Series.prototype.drawGraph
1677
+ });
1678
+
1679
+ /* ****************************************************************************
1680
+ * End Waterfall series code *
1681
+ *****************************************************************************/
1682
+ /* ****************************************************************************
1683
+ * Start Bubble series code *
1684
+ *****************************************************************************/
1685
+
1686
+ // 1 - set default options
1687
+ defaultPlotOptions.bubble = merge(defaultPlotOptions.scatter, {
1688
+ dataLabels: {
1689
+ inside: true,
1690
+ style: {
1691
+ color: 'white',
1692
+ textShadow: '0px 0px 3px black'
1693
+ },
1694
+ verticalAlign: 'middle'
1695
+ },
1696
+ // displayNegative: true,
1697
+ marker: {
1698
+ // fillOpacity: 0.5,
1699
+ lineColor: null, // inherit from series.color
1700
+ lineWidth: 1
1701
+ },
1702
+ minSize: 8,
1703
+ maxSize: '20%',
1704
+ // negativeColor: null,
1705
+ // sizeBy: 'area'
1706
+ tooltip: {
1707
+ pointFormat: '({point.x}, {point.y}), Size: {point.z}'
1708
+ },
1709
+ turboThreshold: 0,
1710
+ zThreshold: 0
1711
+ });
1712
+
1713
+ // 2 - Create the series object
1714
+ seriesTypes.bubble = extendClass(seriesTypes.scatter, {
1715
+ type: 'bubble',
1716
+ pointArrayMap: ['y', 'z'],
1717
+ trackerGroups: ['group', 'dataLabelsGroup'],
1718
+ bubblePadding: true,
1719
+
1720
+ /**
1721
+ * Mapping between SVG attributes and the corresponding options
1722
+ */
1723
+ pointAttrToOptions: {
1724
+ stroke: 'lineColor',
1725
+ 'stroke-width': 'lineWidth',
1726
+ fill: 'fillColor'
1727
+ },
1728
+
1729
+ /**
1730
+ * Apply the fillOpacity to all fill positions
1731
+ */
1732
+ applyOpacity: function (fill) {
1733
+ var markerOptions = this.options.marker,
1734
+ fillOpacity = pick(markerOptions.fillOpacity, 0.5);
1735
+
1736
+ // When called from Legend.colorizeItem, the fill isn't predefined
1737
+ fill = fill || markerOptions.fillColor || this.color;
1738
+
1739
+ if (fillOpacity !== 1) {
1740
+ fill = Highcharts.Color(fill).setOpacity(fillOpacity).get('rgba');
1741
+ }
1742
+ return fill;
1743
+ },
1744
+
1745
+ /**
1746
+ * Extend the convertAttribs method by applying opacity to the fill
1747
+ */
1748
+ convertAttribs: function () {
1749
+ var obj = Series.prototype.convertAttribs.apply(this, arguments);
1750
+
1751
+ obj.fill = this.applyOpacity(obj.fill);
1752
+
1753
+ return obj;
1754
+ },
1755
+
1756
+ /**
1757
+ * Get the radius for each point based on the minSize, maxSize and each point's Z value. This
1758
+ * must be done prior to Series.translate because the axis needs to add padding in
1759
+ * accordance with the point sizes.
1760
+ */
1761
+ getRadii: function (zMin, zMax, minSize, maxSize) {
1762
+ var len,
1763
+ i,
1764
+ pos,
1765
+ zData = this.zData,
1766
+ radii = [],
1767
+ sizeByArea = this.options.sizeBy !== 'width',
1768
+ zRange;
1769
+
1770
+ // Set the shape type and arguments to be picked up in drawPoints
1771
+ for (i = 0, len = zData.length; i < len; i++) {
1772
+ zRange = zMax - zMin;
1773
+ pos = zRange > 0 ? // relative size, a number between 0 and 1
1774
+ (zData[i] - zMin) / (zMax - zMin) :
1775
+ 0.5;
1776
+ if (sizeByArea) {
1777
+ pos = Math.sqrt(pos);
1778
+ }
1779
+ radii.push(math.ceil(minSize + pos * (maxSize - minSize)) / 2);
1780
+ }
1781
+ this.radii = radii;
1782
+ },
1783
+
1784
+ /**
1785
+ * Perform animation on the bubbles
1786
+ */
1787
+ animate: function (init) {
1788
+ var animation = this.options.animation;
1789
+
1790
+ if (!init) { // run the animation
1791
+ each(this.points, function (point) {
1792
+ var graphic = point.graphic,
1793
+ shapeArgs = point.shapeArgs;
1794
+
1795
+ if (graphic && shapeArgs) {
1796
+ // start values
1797
+ graphic.attr('r', 1);
1798
+
1799
+ // animate
1800
+ graphic.animate({
1801
+ r: shapeArgs.r
1802
+ }, animation);
1803
+ }
1804
+ });
1805
+
1806
+ // delete this function to allow it only once
1807
+ this.animate = null;
1808
+ }
1809
+ },
1810
+
1811
+ /**
1812
+ * Extend the base translate method to handle bubble size
1813
+ */
1814
+ translate: function () {
1815
+
1816
+ var i,
1817
+ data = this.data,
1818
+ point,
1819
+ radius,
1820
+ radii = this.radii;
1821
+
1822
+ // Run the parent method
1823
+ seriesTypes.scatter.prototype.translate.call(this);
1824
+
1825
+ // Set the shape type and arguments to be picked up in drawPoints
1826
+ i = data.length;
1827
+
1828
+ while (i--) {
1829
+ point = data[i];
1830
+ radius = radii ? radii[i] : 0; // #1737
1831
+
1832
+ // Flag for negativeColor to be applied in Series.js
1833
+ point.negative = point.z < (this.options.zThreshold || 0);
1834
+
1835
+ if (radius >= this.minPxSize / 2) {
1836
+ // Shape arguments
1837
+ point.shapeType = 'circle';
1838
+ point.shapeArgs = {
1839
+ x: point.plotX,
1840
+ y: point.plotY,
1841
+ r: radius
1842
+ };
1843
+
1844
+ // Alignment box for the data label
1845
+ point.dlBox = {
1846
+ x: point.plotX - radius,
1847
+ y: point.plotY - radius,
1848
+ width: 2 * radius,
1849
+ height: 2 * radius
1850
+ };
1851
+ } else { // below zThreshold
1852
+ point.shapeArgs = point.plotY = point.dlBox = UNDEFINED; // #1691
1853
+ }
1854
+ }
1855
+ },
1856
+
1857
+ /**
1858
+ * Get the series' symbol in the legend
1859
+ *
1860
+ * @param {Object} legend The legend object
1861
+ * @param {Object} item The series (this) or point
1862
+ */
1863
+ drawLegendSymbol: function (legend, item) {
1864
+ var radius = pInt(legend.itemStyle.fontSize) / 2;
1865
+
1866
+ item.legendSymbol = this.chart.renderer.circle(
1867
+ radius,
1868
+ legend.baseline - radius,
1869
+ radius
1870
+ ).attr({
1871
+ zIndex: 3
1872
+ }).add(item.legendGroup);
1873
+ item.legendSymbol.isMarker = true;
1874
+
1875
+ },
1876
+
1877
+ drawPoints: seriesTypes.column.prototype.drawPoints,
1878
+ alignDataLabel: seriesTypes.column.prototype.alignDataLabel
1879
+ });
1880
+
1881
+ /**
1882
+ * Add logic to pad each axis with the amount of pixels
1883
+ * necessary to avoid the bubbles to overflow.
1884
+ */
1885
+ Axis.prototype.beforePadding = function () {
1886
+ var axis = this,
1887
+ axisLength = this.len,
1888
+ chart = this.chart,
1889
+ pxMin = 0,
1890
+ pxMax = axisLength,
1891
+ isXAxis = this.isXAxis,
1892
+ dataKey = isXAxis ? 'xData' : 'yData',
1893
+ min = this.min,
1894
+ extremes = {},
1895
+ smallestSize = math.min(chart.plotWidth, chart.plotHeight),
1896
+ zMin = Number.MAX_VALUE,
1897
+ zMax = -Number.MAX_VALUE,
1898
+ range = this.max - min,
1899
+ transA = axisLength / range,
1900
+ activeSeries = [];
1901
+
1902
+ // Handle padding on the second pass, or on redraw
1903
+ if (this.tickPositions) {
1904
+ each(this.series, function (series) {
1905
+
1906
+ var seriesOptions = series.options,
1907
+ zData;
1908
+
1909
+ if (series.bubblePadding && series.visible) {
1910
+
1911
+ // Correction for #1673
1912
+ axis.allowZoomOutside = true;
1913
+
1914
+ // Cache it
1915
+ activeSeries.push(series);
1916
+
1917
+ if (isXAxis) { // because X axis is evaluated first
1918
+
1919
+ // For each series, translate the size extremes to pixel values
1920
+ each(['minSize', 'maxSize'], function (prop) {
1921
+ var length = seriesOptions[prop],
1922
+ isPercent = /%$/.test(length);
1923
+
1924
+ length = pInt(length);
1925
+ extremes[prop] = isPercent ?
1926
+ smallestSize * length / 100 :
1927
+ length;
1928
+
1929
+ });
1930
+ series.minPxSize = extremes.minSize;
1931
+
1932
+ // Find the min and max Z
1933
+ zData = series.zData;
1934
+ if (zData.length) { // #1735
1935
+ zMin = math.min(
1936
+ zMin,
1937
+ math.max(
1938
+ arrayMin(zData),
1939
+ seriesOptions.displayNegative === false ? seriesOptions.zThreshold : -Number.MAX_VALUE
1940
+ )
1941
+ );
1942
+ zMax = math.max(zMax, arrayMax(zData));
1943
+ }
1944
+ }
1945
+ }
1946
+ });
1947
+
1948
+ each(activeSeries, function (series) {
1949
+
1950
+ var data = series[dataKey],
1951
+ i = data.length,
1952
+ radius;
1953
+
1954
+ if (isXAxis) {
1955
+ series.getRadii(zMin, zMax, extremes.minSize, extremes.maxSize);
1956
+ }
1957
+
1958
+ if (range > 0) {
1959
+ while (i--) {
1960
+ if (data[i] !== null) {
1961
+ radius = series.radii[i];
1962
+ pxMin = Math.min(((data[i] - min) * transA) - radius, pxMin);
1963
+ pxMax = Math.max(((data[i] - min) * transA) + radius, pxMax);
1964
+ }
1965
+ }
1966
+ }
1967
+ });
1968
+
1969
+ if (activeSeries.length && range > 0 && pick(this.options.min, this.userMin) === UNDEFINED && pick(this.options.max, this.userMax) === UNDEFINED) {
1970
+ pxMax -= axisLength;
1971
+ transA *= (axisLength + pxMin - pxMax) / axisLength;
1972
+ this.min += pxMin / transA;
1973
+ this.max += pxMax / transA;
1974
+ }
1975
+ }
1976
+ };
1977
+
1978
+ /* ****************************************************************************
1979
+ * End Bubble series code *
1980
+ *****************************************************************************/
1981
+ /**
1982
+ * Extensions for polar charts. Additionally, much of the geometry required for polar charts is
1983
+ * gathered in RadialAxes.js.
1984
+ *
1985
+ */
1986
+
1987
+ var seriesProto = Series.prototype,
1988
+ pointerProto = Highcharts.Pointer.prototype;
1989
+
1990
+
1991
+
1992
+ /**
1993
+ * Translate a point's plotX and plotY from the internal angle and radius measures to
1994
+ * true plotX, plotY coordinates
1995
+ */
1996
+ seriesProto.toXY = function (point) {
1997
+ var xy,
1998
+ chart = this.chart,
1999
+ plotX = point.plotX,
2000
+ plotY = point.plotY;
2001
+
2002
+ // Save rectangular plotX, plotY for later computation
2003
+ point.rectPlotX = plotX;
2004
+ point.rectPlotY = plotY;
2005
+
2006
+ // Record the angle in degrees for use in tooltip
2007
+ point.clientX = ((plotX / Math.PI * 180) + this.xAxis.pane.options.startAngle) % 360;
2008
+
2009
+ // Find the polar plotX and plotY
2010
+ xy = this.xAxis.postTranslate(point.plotX, this.yAxis.len - plotY);
2011
+ point.plotX = point.polarPlotX = xy.x - chart.plotLeft;
2012
+ point.plotY = point.polarPlotY = xy.y - chart.plotTop;
2013
+ };
2014
+
2015
+ /**
2016
+ * Order the tooltip points to get the mouse capture ranges correct. #1915.
2017
+ */
2018
+ seriesProto.orderTooltipPoints = function (points) {
2019
+ if (this.chart.polar) {
2020
+ points.sort(function (a, b) {
2021
+ return a.clientX - b.clientX;
2022
+ });
2023
+
2024
+ // Wrap mouse tracking around to capture movement on the segment to the left
2025
+ // of the north point (#1469, #2093).
2026
+ if (points[0]) {
2027
+ points[0].wrappedClientX = points[0].clientX + 360;
2028
+ points.push(points[0]);
2029
+ }
2030
+ }
2031
+ };
2032
+
2033
+
2034
+ /**
2035
+ * Add some special init logic to areas and areasplines
2036
+ */
2037
+ function initArea(proceed, chart, options) {
2038
+ proceed.call(this, chart, options);
2039
+ if (this.chart.polar) {
2040
+
2041
+ /**
2042
+ * Overridden method to close a segment path. While in a cartesian plane the area
2043
+ * goes down to the threshold, in the polar chart it goes to the center.
2044
+ */
2045
+ this.closeSegment = function (path) {
2046
+ var center = this.xAxis.center;
2047
+ path.push(
2048
+ 'L',
2049
+ center[0],
2050
+ center[1]
2051
+ );
2052
+ };
2053
+
2054
+ // Instead of complicated logic to draw an area around the inner area in a stack,
2055
+ // just draw it behind
2056
+ this.closedStacks = true;
2057
+ }
2058
+ }
2059
+ wrap(seriesTypes.area.prototype, 'init', initArea);
2060
+ wrap(seriesTypes.areaspline.prototype, 'init', initArea);
2061
+
2062
+
2063
+ /**
2064
+ * Overridden method for calculating a spline from one point to the next
2065
+ */
2066
+ wrap(seriesTypes.spline.prototype, 'getPointSpline', function (proceed, segment, point, i) {
2067
+
2068
+ var ret,
2069
+ smoothing = 1.5, // 1 means control points midway between points, 2 means 1/3 from the point, 3 is 1/4 etc;
2070
+ denom = smoothing + 1,
2071
+ plotX,
2072
+ plotY,
2073
+ lastPoint,
2074
+ nextPoint,
2075
+ lastX,
2076
+ lastY,
2077
+ nextX,
2078
+ nextY,
2079
+ leftContX,
2080
+ leftContY,
2081
+ rightContX,
2082
+ rightContY,
2083
+ distanceLeftControlPoint,
2084
+ distanceRightControlPoint,
2085
+ leftContAngle,
2086
+ rightContAngle,
2087
+ jointAngle;
2088
+
2089
+
2090
+ if (this.chart.polar) {
2091
+
2092
+ plotX = point.plotX;
2093
+ plotY = point.plotY;
2094
+ lastPoint = segment[i - 1];
2095
+ nextPoint = segment[i + 1];
2096
+
2097
+ // Connect ends
2098
+ if (this.connectEnds) {
2099
+ if (!lastPoint) {
2100
+ lastPoint = segment[segment.length - 2]; // not the last but the second last, because the segment is already connected
2101
+ }
2102
+ if (!nextPoint) {
2103
+ nextPoint = segment[1];
2104
+ }
2105
+ }
2106
+
2107
+ // find control points
2108
+ if (lastPoint && nextPoint) {
2109
+
2110
+ lastX = lastPoint.plotX;
2111
+ lastY = lastPoint.plotY;
2112
+ nextX = nextPoint.plotX;
2113
+ nextY = nextPoint.plotY;
2114
+ leftContX = (smoothing * plotX + lastX) / denom;
2115
+ leftContY = (smoothing * plotY + lastY) / denom;
2116
+ rightContX = (smoothing * plotX + nextX) / denom;
2117
+ rightContY = (smoothing * plotY + nextY) / denom;
2118
+ distanceLeftControlPoint = Math.sqrt(Math.pow(leftContX - plotX, 2) + Math.pow(leftContY - plotY, 2));
2119
+ distanceRightControlPoint = Math.sqrt(Math.pow(rightContX - plotX, 2) + Math.pow(rightContY - plotY, 2));
2120
+ leftContAngle = Math.atan2(leftContY - plotY, leftContX - plotX);
2121
+ rightContAngle = Math.atan2(rightContY - plotY, rightContX - plotX);
2122
+ jointAngle = (Math.PI / 2) + ((leftContAngle + rightContAngle) / 2);
2123
+
2124
+
2125
+ // Ensure the right direction, jointAngle should be in the same quadrant as leftContAngle
2126
+ if (Math.abs(leftContAngle - jointAngle) > Math.PI / 2) {
2127
+ jointAngle -= Math.PI;
2128
+ }
2129
+
2130
+ // Find the corrected control points for a spline straight through the point
2131
+ leftContX = plotX + Math.cos(jointAngle) * distanceLeftControlPoint;
2132
+ leftContY = plotY + Math.sin(jointAngle) * distanceLeftControlPoint;
2133
+ rightContX = plotX + Math.cos(Math.PI + jointAngle) * distanceRightControlPoint;
2134
+ rightContY = plotY + Math.sin(Math.PI + jointAngle) * distanceRightControlPoint;
2135
+
2136
+ // Record for drawing in next point
2137
+ point.rightContX = rightContX;
2138
+ point.rightContY = rightContY;
2139
+
2140
+ }
2141
+
2142
+
2143
+ // moveTo or lineTo
2144
+ if (!i) {
2145
+ ret = ['M', plotX, plotY];
2146
+ } else { // curve from last point to this
2147
+ ret = [
2148
+ 'C',
2149
+ lastPoint.rightContX || lastPoint.plotX,
2150
+ lastPoint.rightContY || lastPoint.plotY,
2151
+ leftContX || plotX,
2152
+ leftContY || plotY,
2153
+ plotX,
2154
+ plotY
2155
+ ];
2156
+ lastPoint.rightContX = lastPoint.rightContY = null; // reset for updating series later
2157
+ }
2158
+
2159
+
2160
+ } else {
2161
+ ret = proceed.call(this, segment, point, i);
2162
+ }
2163
+ return ret;
2164
+ });
2165
+
2166
+ /**
2167
+ * Extend translate. The plotX and plotY values are computed as if the polar chart were a
2168
+ * cartesian plane, where plotX denotes the angle in radians and (yAxis.len - plotY) is the pixel distance from
2169
+ * center.
2170
+ */
2171
+ wrap(seriesProto, 'translate', function (proceed) {
2172
+
2173
+ // Run uber method
2174
+ proceed.call(this);
2175
+
2176
+ // Postprocess plot coordinates
2177
+ if (this.chart.polar && !this.preventPostTranslate) {
2178
+ var points = this.points,
2179
+ i = points.length;
2180
+ while (i--) {
2181
+ // Translate plotX, plotY from angle and radius to true plot coordinates
2182
+ this.toXY(points[i]);
2183
+ }
2184
+ }
2185
+ });
2186
+
2187
+ /**
2188
+ * Extend getSegmentPath to allow connecting ends across 0 to provide a closed circle in
2189
+ * line-like series.
2190
+ */
2191
+ wrap(seriesProto, 'getSegmentPath', function (proceed, segment) {
2192
+
2193
+ var points = this.points;
2194
+
2195
+ // Connect the path
2196
+ if (this.chart.polar && this.options.connectEnds !== false &&
2197
+ segment[segment.length - 1] === points[points.length - 1] && points[0].y !== null) {
2198
+ this.connectEnds = true; // re-used in splines
2199
+ segment = [].concat(segment, [points[0]]);
2200
+ }
2201
+
2202
+ // Run uber method
2203
+ return proceed.call(this, segment);
2204
+
2205
+ });
2206
+
2207
+
2208
+ function polarAnimate(proceed, init) {
2209
+ var chart = this.chart,
2210
+ animation = this.options.animation,
2211
+ group = this.group,
2212
+ markerGroup = this.markerGroup,
2213
+ center = this.xAxis.center,
2214
+ plotLeft = chart.plotLeft,
2215
+ plotTop = chart.plotTop,
2216
+ attribs;
2217
+
2218
+ // Specific animation for polar charts
2219
+ if (chart.polar) {
2220
+
2221
+ // Enable animation on polar charts only in SVG. In VML, the scaling is different, plus animation
2222
+ // would be so slow it would't matter.
2223
+ if (chart.renderer.isSVG) {
2224
+
2225
+ if (animation === true) {
2226
+ animation = {};
2227
+ }
2228
+
2229
+ // Initialize the animation
2230
+ if (init) {
2231
+
2232
+ // Scale down the group and place it in the center
2233
+ attribs = {
2234
+ translateX: center[0] + plotLeft,
2235
+ translateY: center[1] + plotTop,
2236
+ scaleX: 0.001, // #1499
2237
+ scaleY: 0.001
2238
+ };
2239
+
2240
+ group.attr(attribs);
2241
+ if (markerGroup) {
2242
+ markerGroup.attrSetters = group.attrSetters;
2243
+ markerGroup.attr(attribs);
2244
+ }
2245
+
2246
+ // Run the animation
2247
+ } else {
2248
+ attribs = {
2249
+ translateX: plotLeft,
2250
+ translateY: plotTop,
2251
+ scaleX: 1,
2252
+ scaleY: 1
2253
+ };
2254
+ group.animate(attribs, animation);
2255
+ if (markerGroup) {
2256
+ markerGroup.animate(attribs, animation);
2257
+ }
2258
+
2259
+ // Delete this function to allow it only once
2260
+ this.animate = null;
2261
+ }
2262
+ }
2263
+
2264
+ // For non-polar charts, revert to the basic animation
2265
+ } else {
2266
+ proceed.call(this, init);
2267
+ }
2268
+ }
2269
+
2270
+ // Define the animate method for both regular series and column series and their derivatives
2271
+ wrap(seriesProto, 'animate', polarAnimate);
2272
+ wrap(colProto, 'animate', polarAnimate);
2273
+
2274
+
2275
+ /**
2276
+ * Throw in a couple of properties to let setTooltipPoints know we're indexing the points
2277
+ * in degrees (0-360), not plot pixel width.
2278
+ */
2279
+ wrap(seriesProto, 'setTooltipPoints', function (proceed, renew) {
2280
+
2281
+ if (this.chart.polar) {
2282
+ extend(this.xAxis, {
2283
+ tooltipLen: 360 // degrees are the resolution unit of the tooltipPoints array
2284
+ });
2285
+ }
2286
+
2287
+ // Run uber method
2288
+ return proceed.call(this, renew);
2289
+ });
2290
+
2291
+
2292
+ /**
2293
+ * Extend the column prototype's translate method
2294
+ */
2295
+ wrap(colProto, 'translate', function (proceed) {
2296
+
2297
+ var xAxis = this.xAxis,
2298
+ len = this.yAxis.len,
2299
+ center = xAxis.center,
2300
+ startAngleRad = xAxis.startAngleRad,
2301
+ renderer = this.chart.renderer,
2302
+ start,
2303
+ points,
2304
+ point,
2305
+ i;
2306
+
2307
+ this.preventPostTranslate = true;
2308
+
2309
+ // Run uber method
2310
+ proceed.call(this);
2311
+
2312
+ // Postprocess plot coordinates
2313
+ if (xAxis.isRadial) {
2314
+ points = this.points;
2315
+ i = points.length;
2316
+ while (i--) {
2317
+ point = points[i];
2318
+ start = point.barX + startAngleRad;
2319
+ point.shapeType = 'path';
2320
+ point.shapeArgs = {
2321
+ d: renderer.symbols.arc(
2322
+ center[0],
2323
+ center[1],
2324
+ len - point.plotY,
2325
+ null,
2326
+ {
2327
+ start: start,
2328
+ end: start + point.pointWidth,
2329
+ innerR: len - pick(point.yBottom, len)
2330
+ }
2331
+ )
2332
+ };
2333
+ this.toXY(point); // provide correct plotX, plotY for tooltip
2334
+ }
2335
+ }
2336
+ });
2337
+
2338
+
2339
+ /**
2340
+ * Align column data labels outside the columns. #1199.
2341
+ */
2342
+ wrap(colProto, 'alignDataLabel', function (proceed, point, dataLabel, options, alignTo, isNew) {
2343
+
2344
+ if (this.chart.polar) {
2345
+ var angle = point.rectPlotX / Math.PI * 180,
2346
+ align,
2347
+ verticalAlign;
2348
+
2349
+ // Align nicely outside the perimeter of the columns
2350
+ if (options.align === null) {
2351
+ if (angle > 20 && angle < 160) {
2352
+ align = 'left'; // right hemisphere
2353
+ } else if (angle > 200 && angle < 340) {
2354
+ align = 'right'; // left hemisphere
2355
+ } else {
2356
+ align = 'center'; // top or bottom
2357
+ }
2358
+ options.align = align;
2359
+ }
2360
+ if (options.verticalAlign === null) {
2361
+ if (angle < 45 || angle > 315) {
2362
+ verticalAlign = 'bottom'; // top part
2363
+ } else if (angle > 135 && angle < 225) {
2364
+ verticalAlign = 'top'; // bottom part
2365
+ } else {
2366
+ verticalAlign = 'middle'; // left or right
2367
+ }
2368
+ options.verticalAlign = verticalAlign;
2369
+ }
2370
+
2371
+ seriesProto.alignDataLabel.call(this, point, dataLabel, options, alignTo, isNew);
2372
+ } else {
2373
+ proceed.call(this, point, dataLabel, options, alignTo, isNew);
2374
+ }
2375
+
2376
+ });
2377
+
2378
+ /**
2379
+ * Extend the mouse tracker to return the tooltip position index in terms of
2380
+ * degrees rather than pixels
2381
+ */
2382
+ wrap(pointerProto, 'getIndex', function (proceed, e) {
2383
+ var ret,
2384
+ chart = this.chart,
2385
+ center,
2386
+ x,
2387
+ y;
2388
+
2389
+ if (chart.polar) {
2390
+ center = chart.xAxis[0].center;
2391
+ x = e.chartX - center[0] - chart.plotLeft;
2392
+ y = e.chartY - center[1] - chart.plotTop;
2393
+
2394
+ ret = 180 - Math.round(Math.atan2(x, y) / Math.PI * 180);
2395
+
2396
+ } else {
2397
+
2398
+ // Run uber method
2399
+ ret = proceed.call(this, e);
2400
+ }
2401
+ return ret;
2402
+ });
2403
+
2404
+ /**
2405
+ * Extend getCoordinates to prepare for polar axis values
2406
+ */
2407
+ wrap(pointerProto, 'getCoordinates', function (proceed, e) {
2408
+ var chart = this.chart,
2409
+ ret = {
2410
+ xAxis: [],
2411
+ yAxis: []
2412
+ };
2413
+
2414
+ if (chart.polar) {
2415
+
2416
+ each(chart.axes, function (axis) {
2417
+ var isXAxis = axis.isXAxis,
2418
+ center = axis.center,
2419
+ x = e.chartX - center[0] - chart.plotLeft,
2420
+ y = e.chartY - center[1] - chart.plotTop;
2421
+
2422
+ ret[isXAxis ? 'xAxis' : 'yAxis'].push({
2423
+ axis: axis,
2424
+ value: axis.translate(
2425
+ isXAxis ?
2426
+ Math.PI - Math.atan2(x, y) : // angle
2427
+ Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)), // distance from center
2428
+ true
2429
+ )
2430
+ });
2431
+ });
2432
+
2433
+ } else {
2434
+ ret = proceed.call(this, e);
2435
+ }
2436
+
2437
+ return ret;
2438
+ });
2439
+ }(Highcharts));