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