highcharts-js-rails 0.2.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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));