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