highcharts-rails 4.0.4 → 4.0.4.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d185bef7f4a8caf4ea1681609d988ffe9c347458
4
- data.tar.gz: 7e6465866c350bc31edccdb052bcb424516121e3
3
+ metadata.gz: 3644773cb5374bcf7a9164ed6463d6d6e636e7c4
4
+ data.tar.gz: b9094ef3a1009bad92aceffa198bd63c01b56ddb
5
5
  SHA512:
6
- metadata.gz: c785f77428acad531cb8c7f6b4eade835f2f6c80f41ce0e036e8d7d7d684f869f97bd4e83df14595f3fd2513d3f1470691fe3cd694bd1ba35255d5310ec95675
7
- data.tar.gz: ec31b0e80b55506cd16b54975c7c5e940808b90809ced51bb54922c564b440604c95ee923054d1f884354652f89e72dc280c812ba6b44d11088a2294268b48c1
6
+ metadata.gz: d378850d7953c9f74e06bfed2f060f85303326267cc5e55a8f6476b74c772c0b4703e99a289976d67c90307cec90b44d627092ae609045466ffd0905a0a46b5d
7
+ data.tar.gz: 98a59a0861fefd2b25312e3061182200348cd1eae4bd18d6c7354522d2b67d1d1aefbff3d1a7189b32f103f45b70990763fa1e019c0cf402ed75f3eaf3d487e4
checksums.yaml.gz.asc ADDED
@@ -0,0 +1,18 @@
1
+ -----BEGIN PGP SIGNATURE-----
2
+ Version: GnuPG/MacGPG2 v2.0.22 (Darwin)
3
+ Comment: GPGTools - http://gpgtools.org
4
+
5
+ iQIcBAABAgAGBQJUa2yWAAoJEH1ncb0Txu7Xn/QP/3Qz092gifbewAAbI0aCLdx2
6
+ fZ+w0wDfQfpB9MNusZ3sc9sFxZ1GVb/q8MIpoxjdDgHB50ZlCCs6/DdV2o7EkgW7
7
+ zc+Cy099rso5FXwlCz7S2Y4aD3LvHwoj7jHEMe1M+1aQv78tJebYqAHTm02QurI+
8
+ HMw0hXp1//uJwb6BzH6BvVYzv2M9uChmlxcfS9lvNFT3z+YTYeRX5uJAfS4Cn3oA
9
+ OqeAzUBSHivcKxeqRQFIBa3nFQWyHmYwHkROJcy/XEQcjDvIVDT7BUsXAwIUJ5A5
10
+ 1MoAttjaEfyUjiIwe/yrKyaTBoawgsMxBr+8+mfw+fMuHQC51LUpXCAr5lciVeJS
11
+ vf6C/IWSEaBt0oiOJSfnK68jySAlECwE0KvgkfWsMJ+TSSQyaHqPoiv32BqZg8qU
12
+ HrrDgKn3Gl8Hc2jI0sZduds+MoZIWcl4iGl0teitkVJrk0ySEvxbJZ1Oe+j6AHmR
13
+ Gsj4mI7JgxCDlzYVydMengYl1OpjFuHAn4ZxQEyatEhRbkCLCclWkSsZLo4Cu9Zp
14
+ FWX+tnyO5XvqGn4CcDy3+QDG816hDpEYzbJE8EPQqlbAOaAJkeJoEXgCMgivbQcq
15
+ GHNOn9ZjgH45wOIkq3r0WkSUuRjhwgbbg5WLCZK5CP0PHFUflXvPbJIU6wqrA06t
16
+ EcSRzTBVW9L/3UbGt3e6
17
+ =+6PL
18
+ -----END PGP SIGNATURE-----
data.tar.gz.asc ADDED
@@ -0,0 +1,18 @@
1
+ -----BEGIN PGP SIGNATURE-----
2
+ Version: GnuPG/MacGPG2 v2.0.22 (Darwin)
3
+ Comment: GPGTools - http://gpgtools.org
4
+
5
+ iQIcBAABAgAGBQJUa2yWAAoJEH1ncb0Txu7XtPEP/jP5/3oJJtC+xZfnrt5mMvvL
6
+ 20Pfz/OkZlr3SXFYb4RYuMMNyHBLNynOkejFy61Wynx8WYpAhqXPUWp0gSmTXAsd
7
+ UEi2N+u/4XQYBN+CqxnfVoaB6k/SOaMtwOgnN+IIeDfc1cEzMR5t+ZSEFo7Wiac4
8
+ AyKuCdk/WD1coZR4/3xdrpG59D9jIl1Qcee99eptpn/Qm8+Qkb0WP5lOQ92j+TwE
9
+ 31UMJhx6pCS09i5jLw2tv+mEafBxYGO/qbRgAcjvLpP/PKAdYqfTWSVi/gMdrC0z
10
+ DM9hUeIZyAnXrW+I5DrTF+GbGHs3kwwhhuKDKgOZ9CQWgNvUILQwSd9f6kYzZ2aK
11
+ aeVTNbCkImPLiIdqe0m/ynkgL6EZvof5RFKMNTlRyyNcgCNuWX0EC2kBCJ3FVY3Y
12
+ akUSIaxQF0krRe49P6qRy21FLiKiMb0l25UxvPFPyKpVyZ0jAWVaV9ESFzqFQKek
13
+ cqaJqPQ+6WuDFEMCskmZF8ZLvyBbhNx+v1RrXw/0zjTQSNZi22VDONe908LJ2T1k
14
+ jJmCMCuU0MTCl9DixCOo2On2rajClMyAG/lcVAIh9/pJAyw+iKdWB96jlRK9M3xt
15
+ l4p/hCNfj9nXnqhVg6XVbPPS6W4p4OoJSjs0eV7f8e6zpzrJqNBEd6ikX3k6Nit4
16
+ ZKIM3coZuzUTppFMnURu
17
+ =7VNA
18
+ -----END PGP SIGNATURE-----
data/CHANGELOG.markdown CHANGED
@@ -1,3 +1,7 @@
1
+ # 4.0.4.1 / 2014-11-18
2
+
3
+ * Update the bundled Highmaps module to 1.0.4 (#21)
4
+
1
5
  # 4.0.4 / 2014-09-02
2
6
 
3
7
  * Updated Highcharts to 4.0.4
data/README.markdown CHANGED
@@ -50,7 +50,7 @@ Or one of the themes
50
50
  //= require highcharts/themes/grid
51
51
  //= require highcharts/themes/skies
52
52
 
53
- Other than that, refer to the [Highcharts documentation](http://docs.highcharts.com/#home)
53
+ Other than that, refer to the [Highcharts documentation](http://www.highcharts.com/docs)
54
54
 
55
55
  ## Licensing
56
56
 
@@ -1,5 +1,6 @@
1
1
  /**
2
- * @license Map plugin v0.2 for Highcharts
2
+ * @license Highmaps JS v1.0.4 (2014-09-02)
3
+ * Highmaps as a plugin for Highcharts 4.0.1 or Highstock 2.0.1
3
4
  *
4
5
  * (c) 2011-2014 Torstein Honsi
5
6
  *
@@ -7,617 +8,490 @@
7
8
  */
8
9
 
9
10
  /*global HighchartsAdapter*/
10
- (function (H) {
11
- var UNDEFINED,
12
- Axis = H.Axis,
13
- Chart = H.Chart,
14
- Color = H.Color,
15
- Point = H.Point,
16
- Pointer = H.Pointer,
17
- Legend = H.Legend,
18
- Series = H.Series,
19
- SVGRenderer = H.SVGRenderer,
20
- VMLRenderer = H.VMLRenderer,
21
-
22
- symbols = SVGRenderer.prototype.symbols,
23
- each = H.each,
24
- extend = H.extend,
25
- extendClass = H.extendClass,
26
- merge = H.merge,
27
- pick = H.pick,
28
- numberFormat = H.numberFormat,
29
- defaultOptions = H.getOptions(),
30
- seriesTypes = H.seriesTypes,
31
- plotOptions = defaultOptions.plotOptions,
32
- wrap = H.wrap,
33
- noop = function () {};
34
-
35
- // Add language
36
- extend(defaultOptions.lang, {
37
- zoomIn: 'Zoom in',
38
- zoomOut: 'Zoom out'
39
- });
40
-
41
- /*
42
- * Return an intermediate color between two colors, according to pos where 0
43
- * is the from color and 1 is the to color
44
- */
45
- function tweenColors(from, to, pos) {
46
- var i = 4,
47
- val,
48
- rgba = [];
49
-
50
- while (i--) {
51
- val = to.rgba[i] + (from.rgba[i] - to.rgba[i]) * (1 - pos);
52
- rgba[i] = i === 3 ? val : Math.round(val); // Do not round opacity
53
- }
54
- return 'rgba(' + rgba.join(',') + ')';
55
- }
56
-
57
- // Set the default map navigation options
58
- defaultOptions.mapNavigation = {
59
- buttonOptions: {
60
- alignTo: 'plotBox',
61
- align: 'left',
62
- verticalAlign: 'top',
63
- x: 0,
64
- width: 18,
65
- height: 18,
66
- style: {
67
- fontSize: '15px',
68
- fontWeight: 'bold',
69
- textAlign: 'center'
70
- },
71
- theme: {
72
- 'stroke-width': 1
73
- }
74
- },
75
- buttons: {
76
- zoomIn: {
77
- onclick: function () {
78
- this.mapZoom(0.5);
79
- },
80
- text: '+',
81
- y: 0
82
- },
83
- zoomOut: {
84
- onclick: function () {
85
- this.mapZoom(2);
86
- },
87
- text: '-',
88
- y: 28
89
- }
90
- }
91
- // enabled: false,
92
- // enableButtons: null, // inherit from enabled
93
- // enableTouchZoom: null, // inherit from enabled
94
- // enableDoubleClickZoom: null, // inherit from enabled
95
- // enableDoubleClickZoomTo: false
96
- // enableMouseWheelZoom: null, // inherit from enabled
97
- };
11
+ (function (Highcharts) {
12
+
13
+
14
+ var UNDEFINED,
15
+ Axis = Highcharts.Axis,
16
+ Chart = Highcharts.Chart,
17
+ Color = Highcharts.Color,
18
+ Point = Highcharts.Point,
19
+ Pointer = Highcharts.Pointer,
20
+ Legend = Highcharts.Legend,
21
+ LegendSymbolMixin = Highcharts.LegendSymbolMixin,
22
+ Renderer = Highcharts.Renderer,
23
+ Series = Highcharts.Series,
24
+ SVGRenderer = Highcharts.SVGRenderer,
25
+ VMLRenderer = Highcharts.VMLRenderer,
98
26
 
99
- /**
100
- * Utility for reading SVG paths directly.
101
- */
102
- H.splitPath = function (path) {
103
- var i;
104
-
105
- // Move letters apart
106
- path = path.replace(/([A-Za-z])/g, ' $1 ');
107
- // Trim
108
- path = path.replace(/^\s*/, "").replace(/\s*$/, "");
109
-
110
- // Split on spaces and commas
111
- path = path.split(/[ ,]+/);
112
-
113
- // Parse numbers
114
- for (i = 0; i < path.length; i++) {
115
- if (!/[a-zA-Z]/.test(path[i])) {
116
- path[i] = parseFloat(path[i]);
117
- }
118
- }
119
- return path;
120
- };
27
+ addEvent = Highcharts.addEvent,
28
+ each = Highcharts.each,
29
+ extend = Highcharts.extend,
30
+ extendClass = Highcharts.extendClass,
31
+ merge = Highcharts.merge,
32
+ pick = Highcharts.pick,
33
+ numberFormat = Highcharts.numberFormat,
34
+ defaultOptions = Highcharts.getOptions(),
35
+ seriesTypes = Highcharts.seriesTypes,
36
+ defaultPlotOptions = defaultOptions.plotOptions,
37
+ wrap = Highcharts.wrap,
38
+ noop = function () {};
121
39
 
122
- // A placeholder for map definitions
123
- H.maps = {};
124
-
125
40
  /**
126
- * Override to use the extreme coordinates from the SVG shape, not the
127
- * data values
128
- */
129
- wrap(Axis.prototype, 'getSeriesExtremes', function (proceed) {
130
- var isXAxis = this.isXAxis,
131
- dataMin,
132
- dataMax,
133
- xData = [];
134
-
135
- // Remove the xData array and cache it locally so that the proceed method doesn't use it
136
- if (isXAxis) {
137
- each(this.series, function (series, i) {
138
- if (series.useMapGeometry) {
139
- xData[i] = series.xData;
140
- series.xData = [];
141
- }
142
- });
143
- }
41
+ * Override to use the extreme coordinates from the SVG shape, not the
42
+ * data values
43
+ */
44
+ wrap(Axis.prototype, 'getSeriesExtremes', function (proceed) {
45
+ var isXAxis = this.isXAxis,
46
+ dataMin,
47
+ dataMax,
48
+ xData = [],
49
+ useMapGeometry;
50
+
51
+ // Remove the xData array and cache it locally so that the proceed method doesn't use it
52
+ if (isXAxis) {
53
+ each(this.series, function (series, i) {
54
+ if (series.useMapGeometry) {
55
+ xData[i] = series.xData;
56
+ series.xData = [];
57
+ }
58
+ });
59
+ }
144
60
 
145
- // Call base to reach normal cartesian series (like mappoint)
146
- proceed.call(this);
147
-
148
- // Run extremes logic for map and mapline
149
- if (isXAxis) {
150
- dataMin = pick(this.dataMin, Number.MAX_VALUE);
151
- dataMax = pick(this.dataMax, Number.MIN_VALUE);
152
- each(this.series, function (series, i) {
153
- if (series.useMapGeometry) {
154
- dataMin = Math.min(dataMin, pick(series.minX, dataMin));
155
- dataMax = Math.max(dataMax, pick(series.maxX, dataMin));
156
- series.xData = xData[i]; // Reset xData array
157
- }
158
- });
159
-
61
+ // Call base to reach normal cartesian series (like mappoint)
62
+ proceed.call(this);
63
+
64
+ // Run extremes logic for map and mapline
65
+ if (isXAxis) {
66
+ dataMin = pick(this.dataMin, Number.MAX_VALUE);
67
+ dataMax = pick(this.dataMax, Number.MIN_VALUE);
68
+ each(this.series, function (series, i) {
69
+ if (series.useMapGeometry) {
70
+ dataMin = Math.min(dataMin, pick(series.minX, dataMin));
71
+ dataMax = Math.max(dataMax, pick(series.maxX, dataMin));
72
+ series.xData = xData[i]; // Reset xData array
73
+ useMapGeometry = true;
74
+ }
75
+ });
76
+ if (useMapGeometry) {
160
77
  this.dataMin = dataMin;
161
78
  this.dataMax = dataMax;
162
79
  }
163
- });
80
+ }
81
+ });
164
82
 
165
- /**
166
- * Override axis translation to make sure the aspect ratio is always kept
167
- */
168
- wrap(Axis.prototype, 'setAxisTranslation', function (proceed) {
169
- var chart = this.chart,
170
- mapRatio,
171
- plotRatio = chart.plotWidth / chart.plotHeight,
172
- adjustedAxisLength,
173
- xAxis = chart.xAxis[0],
174
- padAxis,
175
- fixTo,
176
- fixDiff;
83
+ /**
84
+ * Override axis translation to make sure the aspect ratio is always kept
85
+ */
86
+ wrap(Axis.prototype, 'setAxisTranslation', function (proceed) {
87
+ var chart = this.chart,
88
+ mapRatio,
89
+ plotRatio = chart.plotWidth / chart.plotHeight,
90
+ adjustedAxisLength,
91
+ xAxis = chart.xAxis[0],
92
+ padAxis,
93
+ fixTo,
94
+ fixDiff;
177
95
 
96
+
97
+ // Run the parent method
98
+ proceed.call(this);
99
+
100
+ // On Y axis, handle both
101
+ if (chart.options.chart.preserveAspectRatio && this.coll === 'yAxis' && xAxis.transA !== UNDEFINED) {
178
102
 
179
- // Run the parent method
180
- proceed.call(this);
103
+ // Use the same translation for both axes
104
+ this.transA = xAxis.transA = Math.min(this.transA, xAxis.transA);
181
105
 
182
- // On Y axis, handle both
183
- if (chart.options.chart.preserveAspectRatio && this.coll === 'yAxis' && xAxis.transA !== UNDEFINED) {
184
-
185
- // Use the same translation for both axes
186
- this.transA = xAxis.transA = Math.min(this.transA, xAxis.transA);
187
-
188
- mapRatio = chart.mapRatio = plotRatio / ((xAxis.max - xAxis.min) / (this.max - this.min));
189
-
190
- // What axis to pad to put the map in the middle
191
- padAxis = mapRatio < 1 ? this : xAxis;
192
-
193
- // Pad it
194
- adjustedAxisLength = (padAxis.max - padAxis.min) * padAxis.transA;
195
- padAxis.pixelPadding = padAxis.len - adjustedAxisLength;
196
- padAxis.minPixelPadding = padAxis.pixelPadding / 2;
197
-
198
- fixTo = padAxis.fixTo;
199
- if (fixTo) {
200
- fixDiff = fixTo[1] - padAxis.toValue(fixTo[0], true);
201
- fixDiff *= padAxis.transA;
202
- if (Math.abs(fixDiff) > padAxis.minPixelPadding) { // zooming out again, keep within restricted area
203
- fixDiff = 0;
204
- }
205
- padAxis.minPixelPadding -= fixDiff;
206
-
106
+ mapRatio = plotRatio / ((xAxis.max - xAxis.min) / (this.max - this.min));
107
+
108
+ // What axis to pad to put the map in the middle
109
+ padAxis = mapRatio < 1 ? this : xAxis;
110
+
111
+ // Pad it
112
+ adjustedAxisLength = (padAxis.max - padAxis.min) * padAxis.transA;
113
+ padAxis.pixelPadding = padAxis.len - adjustedAxisLength;
114
+ padAxis.minPixelPadding = padAxis.pixelPadding / 2;
115
+
116
+ fixTo = padAxis.fixTo;
117
+ if (fixTo) {
118
+ fixDiff = fixTo[1] - padAxis.toValue(fixTo[0], true);
119
+ fixDiff *= padAxis.transA;
120
+ if (Math.abs(fixDiff) > padAxis.minPixelPadding || (padAxis.min === padAxis.dataMin && padAxis.max === padAxis.dataMax)) { // zooming out again, keep within restricted area
121
+ fixDiff = 0;
207
122
  }
123
+ padAxis.minPixelPadding -= fixDiff;
208
124
  }
209
- });
210
-
211
- /**
212
- * Override Axis.render in order to delete the fixTo prop
213
- */
214
- wrap(Axis.prototype, 'render', function (proceed) {
215
- proceed.call(this);
216
- this.fixTo = null;
217
- });
218
-
219
- // Extend the Pointer
220
- extend(Pointer.prototype, {
125
+ }
126
+ });
221
127
 
222
- /**
223
- * The event handler for the doubleclick event
224
- */
225
- onContainerDblClick: function (e) {
226
- var chart = this.chart;
128
+ /**
129
+ * Override Axis.render in order to delete the fixTo prop
130
+ */
131
+ wrap(Axis.prototype, 'render', function (proceed) {
132
+ proceed.call(this);
133
+ this.fixTo = null;
134
+ });
227
135
 
228
- e = this.normalize(e);
229
136
 
230
- if (chart.options.mapNavigation.enableDoubleClickZoomTo) {
231
- if (chart.pointer.inClass(e.target, 'highcharts-tracker')) {
232
- chart.hoverPoint.zoomTo();
233
- }
234
- } else if (chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) {
235
- chart.mapZoom(
236
- 0.5,
237
- chart.xAxis[0].toValue(e.chartX),
238
- chart.yAxis[0].toValue(e.chartY),
239
- e.chartX,
240
- e.chartY
241
- );
242
- }
137
+ /**
138
+ * The ColorAxis object for inclusion in gradient legends
139
+ */
140
+ var ColorAxis = Highcharts.ColorAxis = function () {
141
+ this.isColorAxis = true;
142
+ this.init.apply(this, arguments);
143
+ };
144
+ extend(ColorAxis.prototype, Axis.prototype);
145
+ extend(ColorAxis.prototype, {
146
+ defaultColorAxisOptions: {
147
+ lineWidth: 0,
148
+ gridLineWidth: 1,
149
+ tickPixelInterval: 72,
150
+ startOnTick: true,
151
+ endOnTick: true,
152
+ offset: 0,
153
+ marker: {
154
+ animation: {
155
+ duration: 50
156
+ },
157
+ color: 'gray',
158
+ width: 0.01
243
159
  },
160
+ labels: {
161
+ overflow: 'justify'
162
+ },
163
+ minColor: '#EFEFFF',
164
+ maxColor: '#003875',
165
+ tickLength: 5
166
+ },
167
+ init: function (chart, userOptions) {
168
+ var horiz = chart.options.legend.layout !== 'vertical',
169
+ options;
170
+
171
+ // Build the options
172
+ options = merge(this.defaultColorAxisOptions, {
173
+ side: horiz ? 2 : 1,
174
+ reversed: !horiz
175
+ }, userOptions, {
176
+ isX: horiz,
177
+ opposite: !horiz,
178
+ showEmpty: false,
179
+ title: null,
180
+ isColor: true
181
+ });
244
182
 
245
- /**
246
- * The event handler for the mouse scroll event
247
- */
248
- onContainerMouseWheel: function (e) {
249
- var chart = this.chart,
250
- delta;
251
-
252
- e = this.normalize(e);
253
-
254
- // Firefox uses e.detail, WebKit and IE uses wheelDelta
255
- delta = e.detail || -(e.wheelDelta / 120);
256
- if (chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) {
257
- chart.mapZoom(
258
- delta > 0 ? 2 : 1 / 2,
259
- chart.xAxis[0].toValue(e.chartX),
260
- chart.yAxis[0].toValue(e.chartY),
261
- delta > 0 ? undefined : e.chartX,
262
- delta > 0 ? undefined : e.chartY
263
- );
264
- }
265
- }
266
- });
267
-
268
- // Implement the pinchType option
269
- wrap(Pointer.prototype, 'init', function (proceed, chart, options) {
270
-
271
- proceed.call(this, chart, options);
183
+ Axis.prototype.init.call(this, chart, options);
272
184
 
273
- // Pinch status
274
- if (pick(options.mapNavigation.enableTouchZoom, options.mapNavigation.enabled)) {
275
- this.pinchX = this.pinchHor =
276
- this.pinchY = this.pinchVert = true;
277
- }
278
- });
185
+ // Base init() pushes it to the xAxis array, now pop it again
186
+ //chart[this.isXAxis ? 'xAxis' : 'yAxis'].pop();
279
187
 
280
- // Extend the pinchTranslate method to preserve fixed ratio when zooming
281
- wrap(Pointer.prototype, 'pinchTranslate', function (proceed, zoomHor, zoomVert, pinchDown, touches, transform, selectionMarker, clip, lastValidTouch) {
282
- var xBigger;
283
-
284
- proceed.call(this, zoomHor, zoomVert, pinchDown, touches, transform, selectionMarker, clip, lastValidTouch);
285
-
286
- // Keep ratio
287
- if (this.chart.options.chart.type === 'map') {
288
- xBigger = transform.scaleX > transform.scaleY;
289
- this.pinchTranslateDirection(
290
- !xBigger,
291
- pinchDown,
292
- touches,
293
- transform,
294
- selectionMarker,
295
- clip,
296
- lastValidTouch,
297
- xBigger ? transform.scaleX : transform.scaleY
298
- );
188
+ // Prepare data classes
189
+ if (userOptions.dataClasses) {
190
+ this.initDataClasses(userOptions);
299
191
  }
300
- });
192
+ this.initStops(userOptions);
301
193
 
194
+ // Override original axis properties
195
+ this.isXAxis = true;
196
+ this.horiz = horiz;
197
+ this.zoomEnabled = false;
198
+ },
302
199
 
303
-
304
-
305
- /**
306
- * The ColorAxis object for inclusion in gradient legends
200
+ /*
201
+ * Return an intermediate color between two colors, according to pos where 0
202
+ * is the from color and 1 is the to color
307
203
  */
308
- var ColorAxis = H.ColorAxis = function () {
309
- this.init.apply(this, arguments);
310
- };
311
- extend(ColorAxis.prototype, Axis.prototype);
312
- extend(ColorAxis.prototype, {
313
- defaultColorAxisOptions: {
314
- lineWidth: 0,
315
- gridLineWidth: 1,
316
- tickPixelInterval: 72,
317
- startOnTick: true,
318
- endOnTick: true,
319
- offset: 0,
320
- marker: { // docs: use another name?
321
- animation: {
322
- duration: 50
323
- },
324
- color: 'gray',
325
- width: 0.01
326
- },
327
- labels: {
328
- overflow: 'justify'
329
- },
330
- minColor: '#EFEFFF',
331
- maxColor: '#102d4c'
332
- },
333
- init: function (chart, userOptions) {
334
- var horiz = chart.options.legend.layout !== 'vertical',
335
- options;
336
-
337
- // Build the options
338
- options = merge(this.defaultColorAxisOptions, {
339
- side: horiz ? 2 : 1,
340
- reversed: !horiz
341
- }, userOptions, {
342
- isX: horiz,
343
- opposite: !horiz,
344
- showEmpty: false,
345
- title: null
346
- });
347
-
348
- Axis.prototype.init.call(this, chart, options);
349
-
350
- // Base init() pushes it to the xAxis array, now pop it again
351
- //chart[this.isXAxis ? 'xAxis' : 'yAxis'].pop();
352
-
353
- // Prepare data classes
354
- if (userOptions.dataClasses) {
355
- this.initDataClasses(userOptions);
356
- }
357
-
358
- // Override original axis properties
359
- this.isXAxis = true;
360
- this.horiz = horiz;
361
- },
362
-
363
- initDataClasses: function (userOptions) {
364
- var chart = this.chart,
365
- dataClasses,
366
- colorCounter = 0,
367
- options = this.options;
368
- this.dataClasses = dataClasses = [];
369
-
370
- each(userOptions.dataClasses, function (dataClass, i) {
371
- var colors;
372
-
373
- dataClass = merge(dataClass);
374
- dataClasses.push(dataClass);
375
- if (!dataClass.color) {
376
- if (options.dataClassColor === 'category') {
377
- colors = chart.options.colors;
378
- dataClass.color = colors[colorCounter++];
379
- // loop back to zero
380
- if (colorCounter === colors.length) {
381
- colorCounter = 0;
382
- }
383
- } else {
384
- dataClass.color = tweenColors(Color(options.minColor), Color(options.maxColor), i / (userOptions.dataClasses.length - 1));
204
+ tweenColors: function (from, to, pos) {
205
+ // Check for has alpha, because rgba colors perform worse due to lack of
206
+ // support in WebKit.
207
+ var hasAlpha = (to.rgba[3] !== 1 || from.rgba[3] !== 1);
208
+ return (hasAlpha ? 'rgba(' : 'rgb(') +
209
+ Math.round(to.rgba[0] + (from.rgba[0] - to.rgba[0]) * (1 - pos)) + ',' +
210
+ Math.round(to.rgba[1] + (from.rgba[1] - to.rgba[1]) * (1 - pos)) + ',' +
211
+ Math.round(to.rgba[2] + (from.rgba[2] - to.rgba[2]) * (1 - pos)) +
212
+ (hasAlpha ? (',' + (to.rgba[3] + (from.rgba[3] - to.rgba[3]) * (1 - pos))) : '') + ')';
213
+ },
214
+
215
+ initDataClasses: function (userOptions) {
216
+ var axis = this,
217
+ chart = this.chart,
218
+ dataClasses,
219
+ colorCounter = 0,
220
+ options = this.options,
221
+ len = userOptions.dataClasses.length;
222
+ this.dataClasses = dataClasses = [];
223
+ this.legendItems = [];
224
+
225
+ each(userOptions.dataClasses, function (dataClass, i) {
226
+ var colors;
227
+
228
+ dataClass = merge(dataClass);
229
+ dataClasses.push(dataClass);
230
+ if (!dataClass.color) {
231
+ if (options.dataClassColor === 'category') {
232
+ colors = chart.options.colors;
233
+ dataClass.color = colors[colorCounter++];
234
+ // loop back to zero
235
+ if (colorCounter === colors.length) {
236
+ colorCounter = 0;
385
237
  }
238
+ } else {
239
+ dataClass.color = axis.tweenColors(
240
+ Color(options.minColor),
241
+ Color(options.maxColor),
242
+ len < 2 ? 0.5 : i / (len - 1) // #3219
243
+ );
386
244
  }
387
- });
388
- },
389
-
390
- /**
391
- * Extend the setOptions method to process extreme colors and color
392
- * stops.
393
- */
394
- setOptions: function (userOptions) {
395
- Axis.prototype.setOptions.call(this, userOptions);
396
-
397
- this.options.crosshair = this.options.marker;
398
-
399
- this.stops = userOptions.stops || [
400
- [0, this.options.minColor],
401
- [1, this.options.maxColor]
402
- ];
403
- each(this.stops, function (stop) {
404
- stop.color = Color(stop[1]);
405
- });
406
- this.coll = 'colorAxis';
407
- },
408
-
409
- setAxisSize: function () {
410
- var symbol = this.legendSymbol,
411
- chart = this.chart;
245
+ }
246
+ });
247
+ },
248
+
249
+ initStops: function (userOptions) {
250
+ this.stops = userOptions.stops || [
251
+ [0, this.options.minColor],
252
+ [1, this.options.maxColor]
253
+ ];
254
+ each(this.stops, function (stop) {
255
+ stop.color = Color(stop[1]);
256
+ });
257
+ },
412
258
 
413
- if (symbol) {
414
- this.left = symbol.x;
415
- this.top = symbol.y;
416
- this.width = symbol.width;
417
- this.height = symbol.height;
418
- this.right = chart.chartWidth - this.left - this.width;
419
- this.bottom = chart.chartHeight - this.top - this.height;
259
+ /**
260
+ * Extend the setOptions method to process extreme colors and color
261
+ * stops.
262
+ */
263
+ setOptions: function (userOptions) {
264
+ Axis.prototype.setOptions.call(this, userOptions);
265
+
266
+ this.options.crosshair = this.options.marker;
267
+ this.coll = 'colorAxis';
268
+ },
269
+
270
+ setAxisSize: function () {
271
+ var symbol = this.legendSymbol,
272
+ chart = this.chart,
273
+ x,
274
+ y,
275
+ width,
276
+ height;
277
+
278
+ if (symbol) {
279
+ this.left = x = symbol.attr('x');
280
+ this.top = y = symbol.attr('y');
281
+ this.width = width = symbol.attr('width');
282
+ this.height = height = symbol.attr('height');
283
+ this.right = chart.chartWidth - x - width;
284
+ this.bottom = chart.chartHeight - y - height;
285
+
286
+ this.len = this.horiz ? width : height;
287
+ this.pos = this.horiz ? x : y;
288
+ }
289
+ },
420
290
 
421
- this.len = this.horiz ? this.width : this.height;
422
- this.pos = this.horiz ? this.left : this.top;
291
+ /**
292
+ * Translate from a value to a color
293
+ */
294
+ toColor: function (value, point) {
295
+ var pos,
296
+ stops = this.stops,
297
+ from,
298
+ to,
299
+ color,
300
+ dataClasses = this.dataClasses,
301
+ dataClass,
302
+ i;
303
+
304
+ if (dataClasses) {
305
+ i = dataClasses.length;
306
+ while (i--) {
307
+ dataClass = dataClasses[i];
308
+ from = dataClass.from;
309
+ to = dataClass.to;
310
+ if ((from === UNDEFINED || value >= from) && (to === UNDEFINED || value <= to)) {
311
+ color = dataClass.color;
312
+ if (point) {
313
+ point.dataClass = i;
314
+ }
315
+ break;
316
+ }
423
317
  }
424
- },
425
-
426
- /**
427
- * Translate from a value to a color
428
- */
429
- toColor: function (value, point) {
430
- var pos,
431
- stops = this.stops,
432
- from,
433
- to,
434
- color,
435
- dataClasses = this.dataClasses,
436
- dataClass,
437
- i;
438
-
439
- if (dataClasses) {
440
- i = dataClasses.length;
441
- while (i--) {
442
- dataClass = dataClasses[i];
443
- from = dataClass.from;
444
- to = dataClass.to;
445
- if ((from === UNDEFINED || value >= from) && (to === UNDEFINED || value <= to)) {
446
- color = dataClass.color;
447
- if (point) {
448
- point.dataClass = i;
449
- }
450
- break;
451
- }
452
- }
453
318
 
454
- } else {
319
+ } else {
455
320
 
456
- if (this.isLog) {
457
- value = this.val2lin(value);
458
- }
459
- pos = 1 - ((this.max - value) / (this.max - this.min));
460
- i = stops.length;
461
- while (i--) {
462
- if (pos > stops[i][0]) {
463
- break;
464
- }
321
+ if (this.isLog) {
322
+ value = this.val2lin(value);
323
+ }
324
+ pos = 1 - ((this.max - value) / ((this.max - this.min) || 1));
325
+ i = stops.length;
326
+ while (i--) {
327
+ if (pos > stops[i][0]) {
328
+ break;
465
329
  }
466
- from = stops[i] || stops[i + 1];
467
- to = stops[i + 1] || from;
468
-
469
- // The position within the gradient
470
- pos = 1 - (to[0] - pos) / ((to[0] - from[0]) || 1);
471
-
472
- color = tweenColors(
473
- from.color,
474
- to.color,
475
- pos
476
- );
477
330
  }
478
- return color;
479
- },
331
+ from = stops[i] || stops[i + 1];
332
+ to = stops[i + 1] || from;
480
333
 
481
- getOffset: function () {
482
- var group = this.legendGroup;
483
- if (group) {
334
+ // The position within the gradient
335
+ pos = 1 - (to[0] - pos) / ((to[0] - from[0]) || 1);
484
336
 
485
- Axis.prototype.getOffset.call(this);
486
-
487
- if (!this.axisGroup.parentGroup) {
337
+ color = this.tweenColors(
338
+ from.color,
339
+ to.color,
340
+ pos
341
+ );
342
+ }
343
+ return color;
344
+ },
488
345
 
489
- // Move the axis elements inside the legend group
490
- this.axisGroup.add(group);
491
- this.gridGroup.add(group);
492
- this.labelGroup.add(group);
346
+ getOffset: function () {
347
+ var group = this.legendGroup,
348
+ sideOffset = this.chart.axisOffset[this.side];
349
+
350
+ if (group) {
493
351
 
494
- this.added = true;
495
- }
496
- }
497
- },
352
+ Axis.prototype.getOffset.call(this);
353
+
354
+ if (!this.axisGroup.parentGroup) {
498
355
 
499
- /**
500
- * Create the color gradient
501
- */
502
- setLegendColor: function () {
503
- var grad,
504
- horiz = this.horiz,
505
- options = this.options;
506
-
507
- grad = horiz ? [0, 0, 1, 0] : [0, 0, 0, 1];
508
- this.legendColor = {
509
- linearGradient: { x1: grad[0], y1: grad[1], x2: grad[2], y2: grad[3] },
510
- stops: options.stops || [
511
- [0, options.minColor],
512
- [1, options.maxColor]
513
- ]
514
- };
515
- },
356
+ // Move the axis elements inside the legend group
357
+ this.axisGroup.add(group);
358
+ this.gridGroup.add(group);
359
+ this.labelGroup.add(group);
516
360
 
517
- /**
518
- * The color axis appears inside the legend and has its own legend symbol
519
- */
520
- drawLegendSymbol: function (legend, item) {
521
- var padding = legend.padding,
522
- legendOptions = legend.options,
523
- horiz = this.horiz,
524
- box,
525
- width = pick(legendOptions.symbolWidth, horiz ? 200 : 12),
526
- height = pick(legendOptions.symbolHeight, horiz ? 12 : 200),
527
- labelPadding = pick(legendOptions.labelPadding, horiz ? 10 : 30);
361
+ this.added = true;
362
+ }
363
+ // Reset it to avoid color axis reserving space
364
+ this.chart.axisOffset[this.side] = sideOffset;
365
+ }
366
+ },
528
367
 
529
- this.setLegendColor();
368
+ /**
369
+ * Create the color gradient
370
+ */
371
+ setLegendColor: function () {
372
+ var grad,
373
+ horiz = this.horiz,
374
+ options = this.options;
375
+
376
+ grad = horiz ? [0, 0, 1, 0] : [0, 0, 0, 1];
377
+ this.legendColor = {
378
+ linearGradient: { x1: grad[0], y1: grad[1], x2: grad[2], y2: grad[3] },
379
+ stops: options.stops || [
380
+ [0, options.minColor],
381
+ [1, options.maxColor]
382
+ ]
383
+ };
384
+ },
530
385
 
531
- // Create the gradient
532
- item.legendSymbol = this.chart.renderer.rect(
533
- 0,
534
- legend.baseline - 11,
535
- width,
536
- height
537
- ).attr({
538
- zIndex: 1
539
- }).add(item.legendGroup);
540
- box = item.legendSymbol.getBBox();
541
-
542
- // Set how much space this legend item takes up
543
- this.legendItemWidth = width + padding + (horiz ? 0 : labelPadding);
544
- this.legendItemHeight = height + padding + (horiz ? labelPadding : 0);
545
- },
546
- /**
547
- * Fool the legend
548
- */
549
- setState: noop,
550
- visible: true,
551
- setVisible: noop,
552
- getSeriesExtremes: function () {
553
- var series;
554
- if (this.series.length) {
555
- series = this.series[0];
556
- this.dataMin = series.valueMin;
557
- this.dataMax = series.valueMax;
386
+ /**
387
+ * The color axis appears inside the legend and has its own legend symbol
388
+ */
389
+ drawLegendSymbol: function (legend, item) {
390
+ var padding = legend.padding,
391
+ legendOptions = legend.options,
392
+ horiz = this.horiz,
393
+ box,
394
+ width = pick(legendOptions.symbolWidth, horiz ? 200 : 12),
395
+ height = pick(legendOptions.symbolHeight, horiz ? 12 : 200),
396
+ labelPadding = pick(legendOptions.labelPadding, horiz ? 16 : 30),
397
+ itemDistance = pick(legendOptions.itemDistance, 10);
398
+
399
+ this.setLegendColor();
400
+
401
+ // Create the gradient
402
+ item.legendSymbol = this.chart.renderer.rect(
403
+ 0,
404
+ legend.baseline - 11,
405
+ width,
406
+ height
407
+ ).attr({
408
+ zIndex: 1
409
+ }).add(item.legendGroup);
410
+ box = item.legendSymbol.getBBox();
411
+
412
+ // Set how much space this legend item takes up
413
+ this.legendItemWidth = width + padding + (horiz ? itemDistance : labelPadding);
414
+ this.legendItemHeight = height + padding + (horiz ? labelPadding : 0);
415
+ },
416
+ /**
417
+ * Fool the legend
418
+ */
419
+ setState: noop,
420
+ visible: true,
421
+ setVisible: noop,
422
+ getSeriesExtremes: function () {
423
+ var series;
424
+ if (this.series.length) {
425
+ series = this.series[0];
426
+ this.dataMin = series.valueMin;
427
+ this.dataMax = series.valueMax;
428
+ }
429
+ },
430
+ drawCrosshair: function (e, point) {
431
+ var newCross = !this.cross,
432
+ plotX = point && point.plotX,
433
+ plotY = point && point.plotY,
434
+ crossPos,
435
+ axisPos = this.pos,
436
+ axisLen = this.len;
437
+
438
+ if (point) {
439
+ crossPos = this.toPixels(point.value);
440
+ if (crossPos < axisPos) {
441
+ crossPos = axisPos - 2;
442
+ } else if (crossPos > axisPos + axisLen) {
443
+ crossPos = axisPos + axisLen + 2;
558
444
  }
559
- },
560
- drawCrosshair: function (e, point) {
561
- var newCross = !this.cross,
562
- plotX = point && point.plotX,
563
- plotY = point && point.plotY,
564
- crossPos,
565
- axisPos = this.pos,
566
- axisLen = this.len;
567
445
 
568
- if (point) {
569
- crossPos = this.toPixels(point.value);
570
- if (crossPos < axisPos) {
571
- crossPos = axisPos - 2;
572
- } else if (crossPos > axisPos + axisLen) {
573
- crossPos = axisPos + axisLen + 2;
574
- }
575
-
576
- point.plotX = crossPos;
577
- point.plotY = this.len - crossPos;
578
- Axis.prototype.drawCrosshair.call(this, e, point);
579
- point.plotX = plotX;
580
- point.plotY = plotY;
581
-
582
- if (!newCross && this.cross) {
583
- this.cross
584
- .attr({
585
- fill: this.crosshair.color
586
- })
587
- .add(this.labelGroup);
588
- }
589
- }
590
- },
591
- getPlotLinePath: function (a, b, c, d, pos) {
592
- if (pos) { // crosshairs only
593
- return this.horiz ?
594
- ['M', pos - 4, this.top - 6, 'L', pos + 4, this.top - 6, pos, this.top, 'Z'] :
595
- ['M', this.left, pos, 'L', this.left - 6, pos + 6, this.left - 6, pos - 6, 'Z'];
596
- } else {
597
- return Axis.prototype.getPlotLinePath.call(this, a, b, c, d);
598
- }
599
- },
600
-
601
- update: function (newOptions, redraw) {
602
- Axis.prototype.update.call(this, newOptions, redraw);
603
- if (this.legendItem) {
604
- this.setLegendColor();
605
- this.chart.legend.colorizeItem(this, true);
446
+ point.plotX = crossPos;
447
+ point.plotY = this.len - crossPos;
448
+ Axis.prototype.drawCrosshair.call(this, e, point);
449
+ point.plotX = plotX;
450
+ point.plotY = plotY;
451
+
452
+ if (!newCross && this.cross) {
453
+ this.cross
454
+ .attr({
455
+ fill: this.crosshair.color
456
+ })
457
+ .add(this.labelGroup);
606
458
  }
607
- },
459
+ }
460
+ },
461
+ getPlotLinePath: function (a, b, c, d, pos) {
462
+ if (pos) { // crosshairs only
463
+ return this.horiz ?
464
+ ['M', pos - 4, this.top - 6, 'L', pos + 4, this.top - 6, pos, this.top, 'Z'] :
465
+ ['M', this.left, pos, 'L', this.left - 6, pos + 6, this.left - 6, pos - 6, 'Z'];
466
+ } else {
467
+ return Axis.prototype.getPlotLinePath.call(this, a, b, c, d);
468
+ }
469
+ },
608
470
 
609
- /**
610
- * Get the legend item symbols for data classes
611
- */
612
- getDataClassLegendSymbols: function () {
613
- var axis = this,
614
- chart = this.chart,
615
- legendItems = [],
616
- legendOptions = chart.options.legend,
617
- valueDecimals = legendOptions.valueDecimals,
618
- valueSuffix = legendOptions.valueSuffix || '',
619
- name;
471
+ update: function (newOptions, redraw) {
472
+ each(this.series, function (series) {
473
+ series.isDirtyData = true; // Needed for Axis.update when choropleth colors change
474
+ });
475
+ Axis.prototype.update.call(this, newOptions, redraw);
476
+ if (this.legendItem) {
477
+ this.setLegendColor();
478
+ this.chart.legend.colorizeItem(this, true);
479
+ }
480
+ },
620
481
 
482
+ /**
483
+ * Get the legend item symbols for data classes
484
+ */
485
+ getDataClassLegendSymbols: function () {
486
+ var axis = this,
487
+ chart = this.chart,
488
+ legendItems = this.legendItems,
489
+ legendOptions = chart.options.legend,
490
+ valueDecimals = legendOptions.valueDecimals,
491
+ valueSuffix = legendOptions.valueSuffix || '',
492
+ name;
493
+
494
+ if (!legendItems.length) {
621
495
  each(this.dataClasses, function (dataClass, i) {
622
496
  var vis = true,
623
497
  from = dataClass.from,
@@ -641,11 +515,11 @@
641
515
  }
642
516
 
643
517
  // Add a mock object to the legend items
644
- legendItems.push(H.extend({
518
+ legendItems.push(extend({
645
519
  chart: chart,
646
520
  name: name,
647
521
  options: {},
648
- drawLegendSymbol: H.LegendSymbolMixin.drawRectangle,
522
+ drawLegendSymbol: LegendSymbolMixin.drawRectangle,
649
523
  visible: true,
650
524
  setState: noop,
651
525
  setVisible: function () {
@@ -662,995 +536,1582 @@
662
536
  }
663
537
  }, dataClass));
664
538
  });
665
- return legendItems;
666
539
  }
540
+ return legendItems;
541
+ },
542
+ name: '' // Prevents 'undefined' in legend in IE8
543
+ });
544
+
545
+ /**
546
+ * Handle animation of the color attributes directly
547
+ */
548
+ each(['fill', 'stroke'], function (prop) {
549
+ HighchartsAdapter.addAnimSetter(prop, function (fx) {
550
+ fx.elem.attr(prop, ColorAxis.prototype.tweenColors(Color(fx.start), Color(fx.end), fx.pos));
667
551
  });
552
+ });
668
553
 
669
- /**
670
- * Wrap the legend getAllItems method to add the color axis. This also removes the
671
- * axis' own series to prevent them from showing up individually.
672
- */
673
- wrap(Legend.prototype, 'getAllItems', function (proceed) {
674
- var allItems = [],
675
- colorAxis = this.chart.colorAxis[0];
554
+ /**
555
+ * Extend the chart getAxes method to also get the color axis
556
+ */
557
+ wrap(Chart.prototype, 'getAxes', function (proceed) {
676
558
 
677
- if (colorAxis) {
559
+ var options = this.options,
560
+ colorAxisOptions = options.colorAxis;
678
561
 
679
- // Data classes
680
- if (colorAxis.options.dataClasses) {
681
- allItems = allItems.concat(colorAxis.getDataClassLegendSymbols());
682
- // Gradient legend
683
- } else {
684
- // Add this axis on top
685
- allItems.push(colorAxis);
686
- }
562
+ proceed.call(this);
687
563
 
688
- // Don't add the color axis' series
689
- each(colorAxis.series, function (series) {
690
- series.options.showInLegend = false;
691
- });
564
+ this.colorAxis = [];
565
+ if (colorAxisOptions) {
566
+ proceed = new ColorAxis(this, colorAxisOptions); // Fake assignment for jsLint
567
+ }
568
+ });
569
+
570
+
571
+ /**
572
+ * Wrap the legend getAllItems method to add the color axis. This also removes the
573
+ * axis' own series to prevent them from showing up individually.
574
+ */
575
+ wrap(Legend.prototype, 'getAllItems', function (proceed) {
576
+ var allItems = [],
577
+ colorAxis = this.chart.colorAxis[0];
578
+
579
+ if (colorAxis) {
580
+
581
+ // Data classes
582
+ if (colorAxis.options.dataClasses) {
583
+ allItems = allItems.concat(colorAxis.getDataClassLegendSymbols());
584
+ // Gradient legend
585
+ } else {
586
+ // Add this axis on top
587
+ allItems.push(colorAxis);
692
588
  }
693
589
 
694
- return allItems.concat(proceed.call(this));
695
- });
590
+ // Don't add the color axis' series
591
+ each(colorAxis.series, function (series) {
592
+ series.options.showInLegend = false;
593
+ });
594
+ }
696
595
 
596
+ return allItems.concat(proceed.call(this));
597
+ });/**
598
+ * Mixin for maps and heatmaps
599
+ */
600
+ var colorSeriesMixin = {
601
+
602
+ pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
603
+ stroke: 'borderColor',
604
+ 'stroke-width': 'borderWidth',
605
+ fill: 'color',
606
+ dashstyle: 'dashStyle'
607
+ },
608
+ pointArrayMap: ['value'],
609
+ axisTypes: ['xAxis', 'yAxis', 'colorAxis'],
610
+ optionalAxis: 'colorAxis',
611
+ trackerGroups: ['group', 'markerGroup', 'dataLabelsGroup'],
612
+ getSymbol: noop,
613
+ parallelArrays: ['x', 'y', 'value'],
614
+ colorKey: 'value',
615
+
616
+ /**
617
+ * In choropleth maps, the color is a result of the value, so this needs translation too
618
+ */
619
+ translateColors: function () {
620
+ var series = this,
621
+ nullColor = this.options.nullColor,
622
+ colorAxis = this.colorAxis,
623
+ colorKey = this.colorKey;
697
624
 
698
- // Add events to the Chart object itself
699
- extend(Chart.prototype, {
700
- renderMapNavigation: function () {
701
- var chart = this,
702
- options = this.options.mapNavigation,
703
- buttons = options.buttons,
704
- n,
705
- button,
706
- buttonOptions,
707
- attr,
708
- states,
709
- outerHandler = function () {
710
- this.handler.call(chart);
711
- };
625
+ each(this.data, function (point) {
626
+ var value = point[colorKey],
627
+ color;
712
628
 
713
- if (pick(options.enableButtons, options.enabled) && !chart.renderer.forExport) {
714
- for (n in buttons) {
715
- if (buttons.hasOwnProperty(n)) {
716
- buttonOptions = merge(options.buttonOptions, buttons[n]);
717
- attr = buttonOptions.theme;
718
- states = attr.states;
719
- button = chart.renderer.button(
720
- buttonOptions.text,
721
- 0,
722
- 0,
723
- outerHandler,
724
- attr,
725
- states && states.hover,
726
- states && states.select,
727
- 0,
728
- n === 'zoomIn' ? 'topbutton' : 'bottombutton'
729
- )
730
- .attr({
731
- width: buttonOptions.width,
732
- height: buttonOptions.height,
733
- title: chart.options.lang[n],
734
- zIndex: 5
735
- })
736
- .css(buttonOptions.style)
737
- .add();
738
- button.handler = buttonOptions.onclick;
739
- button.align(extend(buttonOptions, { width: button.width, height: 2 * button.height }), null, buttonOptions.alignTo);
740
- }
741
- }
629
+ color = value === null ? nullColor : (colorAxis && value !== undefined) ? colorAxis.toColor(value, point) : point.color || series.color;
630
+
631
+ if (color) {
632
+ point.color = color;
742
633
  }
743
- },
634
+ });
635
+ }
636
+ };
744
637
 
745
- /**
746
- * Fit an inner box to an outer. If the inner box overflows left or right, align it to the sides of the
747
- * outer. If it overflows both sides, fit it within the outer. This is a pattern that occurs more places
748
- * in Highcharts, perhaps it should be elevated to a common utility function.
749
- */
750
- fitToBox: function (inner, outer) {
751
- each([['x', 'width'], ['y', 'height']], function (dim) {
752
- var pos = dim[0],
753
- size = dim[1];
754
-
755
- if (inner[pos] + inner[size] > outer[pos] + outer[size]) { // right overflow
756
- if (inner[size] > outer[size]) { // the general size is greater, fit fully to outer
757
- inner[size] = outer[size];
758
- inner[pos] = outer[pos];
759
- } else { // align right
760
- inner[pos] = outer[pos] + outer[size] - inner[size];
761
- }
762
- }
763
- if (inner[size] > outer[size]) {
764
- inner[size] = outer[size];
765
- }
766
- if (inner[pos] < outer[pos]) {
767
- inner[pos] = outer[pos];
768
- }
769
- });
770
-
771
638
 
772
- return inner;
773
- },
639
+ /**
640
+ * Wrap the buildText method and add the hook for add text stroke
641
+ */
642
+ wrap(SVGRenderer.prototype, 'buildText', function (proceed, wrapper) {
774
643
 
775
- /**
776
- * Zoom the map in or out by a certain amount. Less than 1 zooms in, greater than 1 zooms out.
777
- */
778
- mapZoom: function (howMuch, centerXArg, centerYArg, mouseX, mouseY) {
779
- /*if (this.isMapZooming) {
780
- this.mapZoomQueue = arguments;
781
- return;
782
- }*/
783
-
784
- var chart = this,
785
- xAxis = chart.xAxis[0],
786
- xRange = xAxis.max - xAxis.min,
787
- centerX = pick(centerXArg, xAxis.min + xRange / 2),
788
- newXRange = xRange * howMuch,
789
- yAxis = chart.yAxis[0],
790
- yRange = yAxis.max - yAxis.min,
791
- centerY = pick(centerYArg, yAxis.min + yRange / 2),
792
- newYRange = yRange * howMuch,
793
- fixToX = mouseX ? ((mouseX - xAxis.pos) / xAxis.len) : 0.5,
794
- fixToY = mouseY ? ((mouseY - yAxis.pos) / yAxis.len) : 0.5,
795
- newXMin = centerX - newXRange * fixToX,
796
- newYMin = centerY - newYRange * fixToY,
797
- newExt = chart.fitToBox({
798
- x: newXMin,
799
- y: newYMin,
800
- width: newXRange,
801
- height: newYRange
802
- }, {
803
- x: xAxis.dataMin,
804
- y: yAxis.dataMin,
805
- width: xAxis.dataMax - xAxis.dataMin,
806
- height: yAxis.dataMax - yAxis.dataMin
807
- });
644
+ var textStroke = wrapper.styles && wrapper.styles.HcTextStroke;
808
645
 
809
- // When mousewheel zooming, fix the point under the mouse
810
- if (mouseX) {
811
- xAxis.fixTo = [mouseX - xAxis.pos, centerXArg];
812
- }
813
- if (mouseY) {
814
- yAxis.fixTo = [mouseY - yAxis.pos, centerYArg];
815
- }
646
+ proceed.call(this, wrapper);
816
647
 
817
- // Zoom
818
- if (howMuch !== undefined) {
819
- xAxis.setExtremes(newExt.x, newExt.x + newExt.width, false);
820
- yAxis.setExtremes(newExt.y, newExt.y + newExt.height, false);
648
+ // Apply the text stroke
649
+ if (textStroke && wrapper.applyTextStroke) {
650
+ wrapper.applyTextStroke(textStroke);
651
+ }
652
+ });
821
653
 
822
- // Reset zoom
823
- } else {
824
- xAxis.setExtremes(undefined, undefined, false);
825
- yAxis.setExtremes(undefined, undefined, false);
654
+ /**
655
+ * Apply an outside text stroke to data labels, based on the custom CSS property, HcTextStroke.
656
+ * Consider moving this to Highcharts core, also makes sense on stacked columns etc.
657
+ */
658
+ SVGRenderer.prototype.Element.prototype.applyTextStroke = function (textStroke) {
659
+ var elem = this.element,
660
+ tspans,
661
+ firstChild;
662
+
663
+ textStroke = textStroke.split(' ');
664
+ tspans = elem.getElementsByTagName('tspan');
665
+ firstChild = elem.firstChild;
666
+
667
+ // In order to get the right y position of the clones,
668
+ // copy over the y setter
669
+ this.ySetter = this.xSetter;
670
+
671
+ each([].slice.call(tspans), function (tspan, y) {
672
+ var clone;
673
+ if (y === 0) {
674
+ tspan.setAttribute('x', elem.getAttribute('x'));
675
+ if ((y = elem.getAttribute('y')) !== null) {
676
+ tspan.setAttribute('y', y);
826
677
  }
827
-
828
- // Prevent zooming until this one is finished animating
829
- /*delay = animation ? animation.duration || 500 : 0;
830
- if (delay) {
831
- chart.isMapZooming = true;
832
- setTimeout(function () {
833
- chart.isMapZooming = false;
834
- if (chart.mapZoomQueue) {
835
- chart.mapZoom.apply(chart, chart.mapZoomQueue);
836
- }
837
- chart.mapZoomQueue = null;
838
- }, delay);
839
- }*/
840
-
841
- chart.redraw();
842
678
  }
679
+ clone = tspan.cloneNode(1);
680
+ clone.setAttribute('stroke', textStroke[1]);
681
+ clone.setAttribute('stroke-width', textStroke[0]);
682
+ clone.setAttribute('stroke-linejoin', 'round');
683
+ elem.insertBefore(clone, firstChild);
843
684
  });
685
+ };
686
+ // Add events to the Chart object itself
687
+ extend(Chart.prototype, {
688
+ renderMapNavigation: function () {
689
+ var chart = this,
690
+ options = this.options.mapNavigation,
691
+ buttons = options.buttons,
692
+ n,
693
+ button,
694
+ buttonOptions,
695
+ attr,
696
+ states,
697
+ outerHandler = function () {
698
+ this.handler.call(chart);
699
+ };
700
+
701
+ if (pick(options.enableButtons, options.enabled) && !chart.renderer.forExport) {
702
+ for (n in buttons) {
703
+ if (buttons.hasOwnProperty(n)) {
704
+ buttonOptions = merge(options.buttonOptions, buttons[n]);
705
+ attr = buttonOptions.theme;
706
+ states = attr.states;
707
+ button = chart.renderer.button(
708
+ buttonOptions.text,
709
+ 0,
710
+ 0,
711
+ outerHandler,
712
+ attr,
713
+ states && states.hover,
714
+ states && states.select,
715
+ 0,
716
+ n === 'zoomIn' ? 'topbutton' : 'bottombutton'
717
+ )
718
+ .attr({
719
+ width: buttonOptions.width,
720
+ height: buttonOptions.height,
721
+ title: chart.options.lang[n],
722
+ zIndex: 5
723
+ })
724
+ .css(buttonOptions.style)
725
+ .add();
726
+ button.handler = buttonOptions.onclick;
727
+ button.align(extend(buttonOptions, { width: button.width, height: 2 * button.height }), null, buttonOptions.alignTo);
728
+ }
729
+ }
730
+ }
731
+ },
844
732
 
845
733
  /**
846
- * Extend the chart getAxes method to also get the color axis
734
+ * Fit an inner box to an outer. If the inner box overflows left or right, align it to the sides of the
735
+ * outer. If it overflows both sides, fit it within the outer. This is a pattern that occurs more places
736
+ * in Highcharts, perhaps it should be elevated to a common utility function.
847
737
  */
848
- wrap(Chart.prototype, 'getAxes', function (proceed) {
738
+ fitToBox: function (inner, outer) {
739
+ each([['x', 'width'], ['y', 'height']], function (dim) {
740
+ var pos = dim[0],
741
+ size = dim[1];
849
742
 
850
- var options = this.options,
851
- colorAxisOptions = options.colorAxis;
852
-
853
- proceed.call(this);
743
+ if (inner[pos] + inner[size] > outer[pos] + outer[size]) { // right overflow
744
+ if (inner[size] > outer[size]) { // the general size is greater, fit fully to outer
745
+ inner[size] = outer[size];
746
+ inner[pos] = outer[pos];
747
+ } else { // align right
748
+ inner[pos] = outer[pos] + outer[size] - inner[size];
749
+ }
750
+ }
751
+ if (inner[size] > outer[size]) {
752
+ inner[size] = outer[size];
753
+ }
754
+ if (inner[pos] < outer[pos]) {
755
+ inner[pos] = outer[pos];
756
+ }
757
+ });
758
+
854
759
 
855
- this.colorAxis = [];
856
- if (colorAxisOptions) {
857
- proceed = new ColorAxis(this, colorAxisOptions); // Fake assignment for jsLint
858
- }
859
- });
760
+ return inner;
761
+ },
860
762
 
861
763
  /**
862
- * Extend the Chart.render method to add zooming and panning
764
+ * Zoom the map in or out by a certain amount. Less than 1 zooms in, greater than 1 zooms out.
863
765
  */
864
- wrap(Chart.prototype, 'render', function (proceed) {
766
+ mapZoom: function (howMuch, centerXArg, centerYArg, mouseX, mouseY) {
767
+ /*if (this.isMapZooming) {
768
+ this.mapZoomQueue = arguments;
769
+ return;
770
+ }*/
771
+
865
772
  var chart = this,
866
- mapNavigation = chart.options.mapNavigation;
773
+ xAxis = chart.xAxis[0],
774
+ xRange = xAxis.max - xAxis.min,
775
+ centerX = pick(centerXArg, xAxis.min + xRange / 2),
776
+ newXRange = xRange * howMuch,
777
+ yAxis = chart.yAxis[0],
778
+ yRange = yAxis.max - yAxis.min,
779
+ centerY = pick(centerYArg, yAxis.min + yRange / 2),
780
+ newYRange = yRange * howMuch,
781
+ fixToX = mouseX ? ((mouseX - xAxis.pos) / xAxis.len) : 0.5,
782
+ fixToY = mouseY ? ((mouseY - yAxis.pos) / yAxis.len) : 0.5,
783
+ newXMin = centerX - newXRange * fixToX,
784
+ newYMin = centerY - newYRange * fixToY,
785
+ newExt = chart.fitToBox({
786
+ x: newXMin,
787
+ y: newYMin,
788
+ width: newXRange,
789
+ height: newYRange
790
+ }, {
791
+ x: xAxis.dataMin,
792
+ y: yAxis.dataMin,
793
+ width: xAxis.dataMax - xAxis.dataMin,
794
+ height: yAxis.dataMax - yAxis.dataMin
795
+ });
867
796
 
868
- proceed.call(chart);
797
+ // When mousewheel zooming, fix the point under the mouse
798
+ if (mouseX) {
799
+ xAxis.fixTo = [mouseX - xAxis.pos, centerXArg];
800
+ }
801
+ if (mouseY) {
802
+ yAxis.fixTo = [mouseY - yAxis.pos, centerYArg];
803
+ }
869
804
 
870
- // Render the plus and minus buttons
871
- chart.renderMapNavigation();
805
+ // Zoom
806
+ if (howMuch !== undefined) {
807
+ xAxis.setExtremes(newExt.x, newExt.x + newExt.width, false);
808
+ yAxis.setExtremes(newExt.y, newExt.y + newExt.height, false);
872
809
 
873
- // Add the double click event
874
- if (pick(mapNavigation.enableDoubleClickZoom, mapNavigation.enabled) || mapNavigation.enableDoubleClickZoomTo) {
875
- H.addEvent(chart.container, 'dblclick', function (e) {
876
- chart.pointer.onContainerDblClick(e);
877
- });
810
+ // Reset zoom
811
+ } else {
812
+ xAxis.setExtremes(undefined, undefined, false);
813
+ yAxis.setExtremes(undefined, undefined, false);
878
814
  }
815
+
816
+ // Prevent zooming until this one is finished animating
817
+ /*chart.holdMapZoom = true;
818
+ setTimeout(function () {
819
+ chart.holdMapZoom = false;
820
+ }, 200);*/
821
+ /*delay = animation ? animation.duration || 500 : 0;
822
+ if (delay) {
823
+ chart.isMapZooming = true;
824
+ setTimeout(function () {
825
+ chart.isMapZooming = false;
826
+ if (chart.mapZoomQueue) {
827
+ chart.mapZoom.apply(chart, chart.mapZoomQueue);
828
+ }
829
+ chart.mapZoomQueue = null;
830
+ }, delay);
831
+ }*/
879
832
 
880
- // Add the mousewheel event
881
- if (pick(mapNavigation.enableMouseWheelZoom, mapNavigation.enabled)) {
882
- H.addEvent(chart.container, document.onmousewheel === undefined ? 'DOMMouseScroll' : 'mousewheel', function (e) {
883
- chart.pointer.onContainerMouseWheel(e);
884
- return false;
885
- });
886
- }
887
- });
833
+ chart.redraw();
834
+ }
835
+ });
888
836
 
837
+ /**
838
+ * Extend the Chart.render method to add zooming and panning
839
+ */
840
+ wrap(Chart.prototype, 'render', function (proceed) {
841
+ var chart = this,
842
+ mapNavigation = chart.options.mapNavigation;
843
+
844
+ // Render the plus and minus buttons. Doing this before the shapes makes getBBox much quicker, at least in Chrome.
845
+ chart.renderMapNavigation();
846
+
847
+ proceed.call(chart);
848
+
849
+ // Add the double click event
850
+ if (pick(mapNavigation.enableDoubleClickZoom, mapNavigation.enabled) || mapNavigation.enableDoubleClickZoomTo) {
851
+ addEvent(chart.container, 'dblclick', function (e) {
852
+ chart.pointer.onContainerDblClick(e);
853
+ });
854
+ }
855
+
856
+ // Add the mousewheel event
857
+ if (pick(mapNavigation.enableMouseWheelZoom, mapNavigation.enabled)) {
858
+ addEvent(chart.container, document.onmousewheel === undefined ? 'DOMMouseScroll' : 'mousewheel', function (e) {
859
+ chart.pointer.onContainerMouseWheel(e);
860
+ return false;
861
+ });
862
+ }
863
+ });
864
+
865
+ // Extend the Pointer
866
+ extend(Pointer.prototype, {
867
+
868
+ /**
869
+ * The event handler for the doubleclick event
870
+ */
871
+ onContainerDblClick: function (e) {
872
+ var chart = this.chart;
873
+
874
+ e = this.normalize(e);
875
+
876
+ if (chart.options.mapNavigation.enableDoubleClickZoomTo) {
877
+ if (chart.pointer.inClass(e.target, 'highcharts-tracker')) {
878
+ chart.hoverPoint.zoomTo();
879
+ }
880
+ } else if (chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) {
881
+ chart.mapZoom(
882
+ 0.5,
883
+ chart.xAxis[0].toValue(e.chartX),
884
+ chart.yAxis[0].toValue(e.chartY),
885
+ e.chartX,
886
+ e.chartY
887
+ );
888
+ }
889
+ },
889
890
 
890
-
891
891
  /**
892
- * Extend the default options with map options
892
+ * The event handler for the mouse scroll event
893
893
  */
894
- plotOptions.map = merge(plotOptions.scatter, {
895
- allAreas: true,
896
- animation: false, // makes the complex shapes slow
897
- nullColor: '#F8F8F8',
898
- borderColor: 'silver',
899
- borderWidth: 1,
900
- marker: null,
901
- stickyTracking: false,
902
- dataLabels: {
903
- format: '{point.value}',
904
- verticalAlign: 'middle'
894
+ onContainerMouseWheel: function (e) {
895
+ var chart = this.chart,
896
+ delta;
897
+
898
+ e = this.normalize(e);
899
+
900
+ // Firefox uses e.detail, WebKit and IE uses wheelDelta
901
+ delta = e.detail || -(e.wheelDelta / 120);
902
+ if (chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) {
903
+ chart.mapZoom(
904
+ //delta > 0 ? 2 : 0.5,
905
+ Math.pow(2, delta),
906
+ chart.xAxis[0].toValue(e.chartX),
907
+ chart.yAxis[0].toValue(e.chartY),
908
+ e.chartX,
909
+ e.chartY
910
+ );
911
+ }
912
+ }
913
+ });
914
+
915
+ // Implement the pinchType option
916
+ wrap(Pointer.prototype, 'init', function (proceed, chart, options) {
917
+
918
+ proceed.call(this, chart, options);
919
+
920
+ // Pinch status
921
+ if (pick(options.mapNavigation.enableTouchZoom, options.mapNavigation.enabled)) {
922
+ this.pinchX = this.pinchHor = this.pinchY = this.pinchVert = this.hasZoom = true;
923
+ }
924
+ });
925
+
926
+ // Extend the pinchTranslate method to preserve fixed ratio when zooming
927
+ wrap(Pointer.prototype, 'pinchTranslate', function (proceed, pinchDown, touches, transform, selectionMarker, clip, lastValidTouch) {
928
+ var xBigger;
929
+ proceed.call(this, pinchDown, touches, transform, selectionMarker, clip, lastValidTouch);
930
+
931
+ // Keep ratio
932
+ if (this.chart.options.chart.type === 'map' && this.hasZoom) {
933
+ xBigger = transform.scaleX > transform.scaleY;
934
+ this.pinchTranslateDirection(
935
+ !xBigger,
936
+ pinchDown,
937
+ touches,
938
+ transform,
939
+ selectionMarker,
940
+ clip,
941
+ lastValidTouch,
942
+ xBigger ? transform.scaleX : transform.scaleY
943
+ );
944
+ }
945
+ });
946
+
947
+
948
+
949
+ /**
950
+ * Extend the default options with map options
951
+ */
952
+ defaultPlotOptions.map = merge(defaultPlotOptions.scatter, {
953
+ allAreas: true,
954
+
955
+ animation: false, // makes the complex shapes slow
956
+ nullColor: '#F8F8F8',
957
+ borderColor: 'silver',
958
+ borderWidth: 1,
959
+ marker: null,
960
+ stickyTracking: false,
961
+ dataLabels: {
962
+ formatter: function () { // #2945
963
+ return this.point.value;
905
964
  },
906
- turboThreshold: 0,
907
- tooltip: {
908
- followPointer: true,
909
- pointFormat: '{point.name}: {point.value}<br/>'
965
+ verticalAlign: 'middle',
966
+ crop: false,
967
+ overflow: false,
968
+ padding: 0,
969
+ style: {
970
+ color: 'white',
971
+ fontWeight: 'bold',
972
+ HcTextStroke: '3px rgba(0,0,0,0.5)'
973
+ }
974
+ },
975
+ turboThreshold: 0,
976
+ tooltip: {
977
+ followPointer: true,
978
+ pointFormat: '{point.name}: {point.value}<br/>'
979
+ },
980
+ states: {
981
+ normal: {
982
+ animation: true
910
983
  },
911
- states: {
912
- normal: {
913
- animation: true
914
- },
915
- hover: {
916
- brightness: 0.2
917
- }
984
+ hover: {
985
+ brightness: 0.2,
986
+ halo: null
918
987
  }
919
- });
988
+ }
989
+ });
920
990
 
991
+ /**
992
+ * The MapAreaPoint object
993
+ */
994
+ var MapAreaPoint = extendClass(Point, {
921
995
  /**
922
- * The MapAreaPoint object
996
+ * Extend the Point object to split paths
923
997
  */
924
- var MapAreaPoint = extendClass(Point, {
925
- /**
926
- * Extend the Point object to split paths
927
- */
928
- applyOptions: function (options, x) {
929
-
930
- var point = Point.prototype.applyOptions.call(this, options, x),
931
- series = this.series,
932
- seriesOptions = series.options,
933
- joinBy = seriesOptions.joinBy,
934
- mapPoint;
935
-
936
- if (seriesOptions.mapData) {
937
- mapPoint = joinBy ?
938
- series.getMapData(joinBy, point[joinBy]) : // Join by a string
939
- seriesOptions.mapData[point.x]; // Use array position (faster)
940
-
941
- if (mapPoint) {
942
- // This applies only to bubbles
943
- if (series.xyFromShape) {
944
- point.x = mapPoint._midX;
945
- point.y = mapPoint._midY;
946
- }
947
- extend(point, mapPoint); // copy over properties
948
- } else {
949
- point.value = point.value || null;
998
+ applyOptions: function (options, x) {
999
+
1000
+ var point = Point.prototype.applyOptions.call(this, options, x),
1001
+ series = this.series,
1002
+ joinBy = series.joinBy,
1003
+ mapPoint;
1004
+
1005
+ if (series.mapData) {
1006
+ mapPoint = point[joinBy[1]] !== undefined && series.mapMap[point[joinBy[1]]];
1007
+ if (mapPoint) {
1008
+ // This applies only to bubbles
1009
+ if (series.xyFromShape) {
1010
+ point.x = mapPoint._midX;
1011
+ point.y = mapPoint._midY;
950
1012
  }
1013
+ extend(point, mapPoint); // copy over properties
1014
+ } else {
1015
+ point.value = point.value || null;
951
1016
  }
952
-
953
- return point;
954
- },
1017
+ }
1018
+
1019
+ return point;
1020
+ },
955
1021
 
956
- /**
957
- * Set the visibility of a single map area
958
- */
959
- setVisible: function (vis) {
960
- var point = this,
961
- method = vis ? 'show' : 'hide';
962
-
963
- // Show and hide associated elements
964
- each(['graphic', 'dataLabel'], function (key) {
965
- if (point[key]) {
966
- point[key][method]();
967
- }
968
- });
969
- },
1022
+ /**
1023
+ * Set the visibility of a single map area
1024
+ */
1025
+ setVisible: function (vis) {
1026
+ var point = this,
1027
+ method = vis ? 'show' : 'hide';
1028
+
1029
+ // Show and hide associated elements
1030
+ each(['graphic', 'dataLabel'], function (key) {
1031
+ if (point[key]) {
1032
+ point[key][method]();
1033
+ }
1034
+ });
1035
+ },
970
1036
 
971
- /**
972
- * Stop the fade-out
973
- */
974
- onMouseOver: function (e) {
975
- clearTimeout(this.colorInterval);
1037
+ /**
1038
+ * Stop the fade-out
1039
+ */
1040
+ onMouseOver: function (e) {
1041
+ clearTimeout(this.colorInterval);
1042
+ if (this.value !== null) {
976
1043
  Point.prototype.onMouseOver.call(this, e);
977
- },
978
- /**
979
- * Custom animation for tweening out the colors. Animation reduces blinking when hovering
980
- * over islands and coast lines. We run a custom implementation of animation becuase we
981
- * need to be able to run this independently from other animations like zoom redraw. Also,
982
- * adding color animation to the adapters would introduce almost the same amount of code.
983
- */
984
- onMouseOut: function () {
985
- var point = this,
986
- start = +new Date(),
987
- normalColor = Color(point.options.color),
988
- hoverColor = Color(point.pointAttr.hover.fill),
989
- animation = point.series.options.states.normal.animation,
990
- duration = animation && (animation.duration || 500);
991
-
992
- if (duration && normalColor.rgba.length === 4 && hoverColor.rgba.length === 4 && point.state !== 'select') {
993
- delete point.pointAttr[''].fill; // avoid resetting it in Point.setState
994
-
995
- clearTimeout(point.colorInterval);
996
- point.colorInterval = setInterval(function () {
997
- var pos = (new Date() - start) / duration,
998
- graphic = point.graphic;
999
- if (pos > 1) {
1000
- pos = 1;
1001
- }
1002
- if (graphic) {
1003
- graphic.attr('fill', tweenColors(hoverColor, normalColor, pos));
1004
- }
1005
- if (pos >= 1) {
1006
- clearTimeout(point.colorInterval);
1007
- }
1008
- }, 13);
1009
- }
1010
- Point.prototype.onMouseOut.call(point);
1011
- },
1044
+ }
1045
+ },
1046
+ /**
1047
+ * Custom animation for tweening out the colors. Animation reduces blinking when hovering
1048
+ * over islands and coast lines. We run a custom implementation of animation becuase we
1049
+ * need to be able to run this independently from other animations like zoom redraw. Also,
1050
+ * adding color animation to the adapters would introduce almost the same amount of code.
1051
+ */
1052
+ onMouseOut: function () {
1053
+ var point = this,
1054
+ start = +new Date(),
1055
+ normalColor = Color(point.color),
1056
+ hoverColor = Color(point.pointAttr.hover.fill),
1057
+ animation = point.series.options.states.normal.animation,
1058
+ duration = animation && (animation.duration || 500),
1059
+ fill;
1060
+
1061
+ if (duration && normalColor.rgba.length === 4 && hoverColor.rgba.length === 4 && point.state !== 'select') {
1062
+ fill = point.pointAttr[''].fill;
1063
+ delete point.pointAttr[''].fill; // avoid resetting it in Point.setState
1064
+
1065
+ clearTimeout(point.colorInterval);
1066
+ point.colorInterval = setInterval(function () {
1067
+ var pos = (new Date() - start) / duration,
1068
+ graphic = point.graphic;
1069
+ if (pos > 1) {
1070
+ pos = 1;
1071
+ }
1072
+ if (graphic) {
1073
+ graphic.attr('fill', ColorAxis.prototype.tweenColors.call(0, hoverColor, normalColor, pos));
1074
+ }
1075
+ if (pos >= 1) {
1076
+ clearTimeout(point.colorInterval);
1077
+ }
1078
+ }, 13);
1079
+ }
1080
+ Point.prototype.onMouseOut.call(point);
1012
1081
 
1013
- /**
1014
- * Zoom the chart to view a specific area point
1015
- */
1016
- zoomTo: function () {
1017
- var point = this,
1018
- series = point.series;
1019
-
1020
- series.xAxis.setExtremes(
1021
- point._minX,
1022
- point._maxX,
1023
- false
1024
- );
1025
- series.yAxis.setExtremes(
1026
- point._minY,
1027
- point._maxY,
1028
- false
1029
- );
1030
- series.chart.redraw();
1082
+ if (fill) {
1083
+ point.pointAttr[''].fill = fill;
1031
1084
  }
1032
- });
1085
+ },
1033
1086
 
1034
1087
  /**
1035
- * Add the series type
1088
+ * Zoom the chart to view a specific area point
1036
1089
  */
1037
- seriesTypes.map = extendClass(seriesTypes.scatter, {
1038
- type: 'map',
1039
- pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
1040
- stroke: 'borderColor',
1041
- 'stroke-width': 'borderWidth',
1042
- fill: 'color',
1043
- dashstyle: 'dashStyle'
1044
- },
1045
- pointClass: MapAreaPoint,
1046
- pointArrayMap: ['value'],
1047
- axisTypes: ['xAxis', 'yAxis', 'colorAxis'],
1048
- optionalAxis: 'colorAxis',
1049
- trackerGroups: ['group', 'markerGroup', 'dataLabelsGroup'],
1050
- getSymbol: noop,
1051
- supportsDrilldown: true,
1052
- getExtremesFromAll: true,
1053
- useMapGeometry: true, // get axis extremes from paths, not values
1054
- parallelArrays: ['x', 'y', 'value'],
1090
+ zoomTo: function () {
1091
+ var point = this,
1092
+ series = point.series;
1093
+
1094
+ series.xAxis.setExtremes(
1095
+ point._minX,
1096
+ point._maxX,
1097
+ false
1098
+ );
1099
+ series.yAxis.setExtremes(
1100
+ point._minY,
1101
+ point._maxY,
1102
+ false
1103
+ );
1104
+ series.chart.redraw();
1105
+ }
1106
+ });
1055
1107
 
1056
- /**
1057
- * Get the bounding box of all paths in the map combined.
1058
- */
1059
- getBox: function (paths) {
1060
- var maxX = Number.MIN_VALUE,
1061
- minX = Number.MAX_VALUE,
1062
- maxY = Number.MIN_VALUE,
1063
- minY = Number.MAX_VALUE,
1064
- hasBox;
1065
-
1066
- // Find the bounding box
1067
- each(paths || [], function (point) {
1108
+ /**
1109
+ * Add the series type
1110
+ */
1111
+ seriesTypes.map = extendClass(seriesTypes.scatter, merge(colorSeriesMixin, {
1112
+ type: 'map',
1113
+ pointClass: MapAreaPoint,
1114
+ supportsDrilldown: true,
1115
+ getExtremesFromAll: true,
1116
+ useMapGeometry: true, // get axis extremes from paths, not values
1117
+ forceDL: true,
1118
+ /**
1119
+ * Get the bounding box of all paths in the map combined.
1120
+ */
1121
+ getBox: function (paths) {
1122
+ var MAX_VALUE = Number.MAX_VALUE,
1123
+ maxX = -MAX_VALUE,
1124
+ minX = MAX_VALUE,
1125
+ maxY = -MAX_VALUE,
1126
+ minY = MAX_VALUE,
1127
+ minRange = MAX_VALUE,
1128
+ xAxis = this.xAxis,
1129
+ yAxis = this.yAxis,
1130
+ hasBox;
1131
+
1132
+ // Find the bounding box
1133
+ each(paths || [], function (point) {
1068
1134
 
1069
- if (point.path) {
1070
- if (typeof point.path === 'string') {
1071
- point.path = H.splitPath(point.path);
1072
- }
1135
+ if (point.path) {
1136
+ if (typeof point.path === 'string') {
1137
+ point.path = Highcharts.splitPath(point.path);
1138
+ }
1073
1139
 
1074
- var path = point.path || [],
1075
- i = path.length,
1076
- even = false, // while loop reads from the end
1077
- pointMaxX = Number.MIN_VALUE,
1078
- pointMinX = Number.MAX_VALUE,
1079
- pointMaxY = Number.MIN_VALUE,
1080
- pointMinY = Number.MAX_VALUE;
1081
-
1082
- // The first time a map point is used, analyze its box
1083
- if (!point._foundBox) {
1084
- while (i--) {
1085
- if (typeof path[i] === 'number' && !isNaN(path[i])) {
1086
- if (even) { // even = x
1087
- pointMaxX = Math.max(pointMaxX, path[i]);
1088
- pointMinX = Math.min(pointMinX, path[i]);
1089
- } else { // odd = Y
1090
- pointMaxY = Math.max(pointMaxY, path[i]);
1091
- pointMinY = Math.min(pointMinY, path[i]);
1092
- }
1093
- even = !even;
1140
+ var path = point.path || [],
1141
+ i = path.length,
1142
+ even = false, // while loop reads from the end
1143
+ pointMaxX = -MAX_VALUE,
1144
+ pointMinX = MAX_VALUE,
1145
+ pointMaxY = -MAX_VALUE,
1146
+ pointMinY = MAX_VALUE,
1147
+ properties = point.properties;
1148
+
1149
+ // The first time a map point is used, analyze its box
1150
+ if (!point._foundBox) {
1151
+ while (i--) {
1152
+ if (typeof path[i] === 'number' && !isNaN(path[i])) {
1153
+ if (even) { // even = x
1154
+ pointMaxX = Math.max(pointMaxX, path[i]);
1155
+ pointMinX = Math.min(pointMinX, path[i]);
1156
+ } else { // odd = Y
1157
+ pointMaxY = Math.max(pointMaxY, path[i]);
1158
+ pointMinY = Math.min(pointMinY, path[i]);
1094
1159
  }
1160
+ even = !even;
1095
1161
  }
1096
- // Cache point bounding box for use to position data labels, bubbles etc
1097
- point._midX = pointMinX + (pointMaxX - pointMinX) * (point.middleX || 0.5); // pick is slower and very marginally needed
1098
- point._midY = pointMinY + (pointMaxY - pointMinY) * (point.middleY || 0.5);
1099
- point._maxX = pointMaxX;
1100
- point._minX = pointMinX;
1101
- point._maxY = pointMaxY;
1102
- point._minY = pointMinY;
1103
- point._foundBox = true;
1104
1162
  }
1105
-
1106
- maxX = Math.max(maxX, point._maxX);
1107
- minX = Math.min(minX, point._minX);
1108
- maxY = Math.max(maxY, point._maxY);
1109
- minY = Math.min(minY, point._minY);
1110
-
1111
- hasBox = true;
1163
+ // Cache point bounding box for use to position data labels, bubbles etc
1164
+ point._midX = pointMinX + (pointMaxX - pointMinX) *
1165
+ (point.middleX || (properties && properties['hc-middle-x']) || 0.5); // pick is slower and very marginally needed
1166
+ point._midY = pointMinY + (pointMaxY - pointMinY) *
1167
+ (point.middleY || (properties && properties['hc-middle-y']) || 0.5);
1168
+ point._maxX = pointMaxX;
1169
+ point._minX = pointMinX;
1170
+ point._maxY = pointMaxY;
1171
+ point._minY = pointMinY;
1172
+ point.labelrank = pick(point.labelrank, (pointMaxX - pointMinX) * (pointMaxY - pointMinY));
1173
+ point._foundBox = true;
1112
1174
  }
1113
- });
1114
1175
 
1115
- // Set the box for the whole series
1116
- if (hasBox) {
1117
- this.minY = Math.min(minY, pick(this.minY, Number.MAX_VALUE));
1118
- this.maxY = Math.max(maxY, pick(this.maxY, Number.MIN_VALUE));
1119
- this.minX = Math.min(minX, pick(this.minX, Number.MAX_VALUE));
1120
- this.maxX = Math.max(maxX, pick(this.maxX, Number.MIN_VALUE));
1176
+ maxX = Math.max(maxX, point._maxX);
1177
+ minX = Math.min(minX, point._minX);
1178
+ maxY = Math.max(maxY, point._maxY);
1179
+ minY = Math.min(minY, point._minY);
1180
+ minRange = Math.min(point._maxX - point._minX, point._maxY - point._minY, minRange);
1181
+ hasBox = true;
1121
1182
  }
1122
- },
1123
-
1124
- getExtremes: function () {
1125
- // Get the actual value extremes for colors
1126
- Series.prototype.getExtremes.call(this, this.valueData);
1183
+ });
1127
1184
 
1128
- // Recalculate box on updated data
1129
- if (this.chart.hasRendered && this.isDirtyData) {
1130
- this.getBox(this.options.data);
1185
+ // Set the box for the whole series
1186
+ if (hasBox) {
1187
+ this.minY = Math.min(minY, pick(this.minY, MAX_VALUE));
1188
+ this.maxY = Math.max(maxY, pick(this.maxY, -MAX_VALUE));
1189
+ this.minX = Math.min(minX, pick(this.minX, MAX_VALUE));
1190
+ this.maxX = Math.max(maxX, pick(this.maxX, -MAX_VALUE));
1191
+
1192
+ // If no minRange option is set, set the default minimum zooming range to 5 times the
1193
+ // size of the smallest element
1194
+ if (xAxis && xAxis.options.minRange === undefined) {
1195
+ xAxis.minRange = Math.min(5 * minRange, (this.maxX - this.minX) / 5, xAxis.minRange || MAX_VALUE);
1196
+ }
1197
+ if (yAxis && yAxis.options.minRange === undefined) {
1198
+ yAxis.minRange = Math.min(5 * minRange, (this.maxY - this.minY) / 5, yAxis.minRange || MAX_VALUE);
1131
1199
  }
1200
+ }
1201
+ },
1202
+
1203
+ getExtremes: function () {
1204
+ // Get the actual value extremes for colors
1205
+ Series.prototype.getExtremes.call(this, this.valueData);
1206
+
1207
+ // Recalculate box on updated data
1208
+ if (this.chart.hasRendered && this.isDirtyData) {
1209
+ this.getBox(this.options.data);
1210
+ }
1132
1211
 
1133
- this.valueMin = this.dataMin;
1134
- this.valueMax = this.dataMax;
1212
+ this.valueMin = this.dataMin;
1213
+ this.valueMax = this.dataMax;
1135
1214
 
1136
- // Extremes for the mock Y axis
1137
- this.dataMin = this.minY;
1138
- this.dataMax = this.maxY;
1139
- },
1215
+ // Extremes for the mock Y axis
1216
+ this.dataMin = this.minY;
1217
+ this.dataMax = this.maxY;
1218
+ },
1219
+
1220
+ /**
1221
+ * Translate the path so that it automatically fits into the plot area box
1222
+ * @param {Object} path
1223
+ */
1224
+ translatePath: function (path) {
1140
1225
 
1141
- /**
1142
- * Translate the path so that it automatically fits into the plot area box
1143
- * @param {Object} path
1144
- */
1145
- translatePath: function (path) {
1146
-
1147
- var series = this,
1148
- even = false, // while loop reads from the end
1149
- xAxis = series.xAxis,
1150
- yAxis = series.yAxis,
1151
- xMin = xAxis.min,
1152
- xTransA = xAxis.transA,
1153
- xMinPixelPadding = xAxis.minPixelPadding,
1154
- yMin = yAxis.min,
1155
- yTransA = yAxis.transA,
1156
- yMinPixelPadding = yAxis.minPixelPadding,
1157
- i,
1158
- ret = []; // Preserve the original
1159
-
1160
- // Do the translation
1161
- if (path) {
1162
- i = path.length;
1163
- while (i--) {
1164
- if (typeof path[i] === 'number') {
1165
- ret[i] = even ?
1166
- (path[i] - xMin) * xTransA + xMinPixelPadding :
1167
- (path[i] - yMin) * yTransA + yMinPixelPadding;
1168
- even = !even;
1169
- } else {
1170
- ret[i] = path[i];
1171
- }
1226
+ var series = this,
1227
+ even = false, // while loop reads from the end
1228
+ xAxis = series.xAxis,
1229
+ yAxis = series.yAxis,
1230
+ xMin = xAxis.min,
1231
+ xTransA = xAxis.transA,
1232
+ xMinPixelPadding = xAxis.minPixelPadding,
1233
+ yMin = yAxis.min,
1234
+ yTransA = yAxis.transA,
1235
+ yMinPixelPadding = yAxis.minPixelPadding,
1236
+ i,
1237
+ ret = []; // Preserve the original
1238
+
1239
+ // Do the translation
1240
+ if (path) {
1241
+ i = path.length;
1242
+ while (i--) {
1243
+ if (typeof path[i] === 'number') {
1244
+ ret[i] = even ?
1245
+ (path[i] - xMin) * xTransA + xMinPixelPadding :
1246
+ (path[i] - yMin) * yTransA + yMinPixelPadding;
1247
+ even = !even;
1248
+ } else {
1249
+ ret[i] = path[i];
1172
1250
  }
1173
1251
  }
1252
+ }
1174
1253
 
1175
- return ret;
1176
- },
1177
-
1178
- /**
1179
- * Extend setData to join in mapData. If the allAreas option is true, all areas
1180
- * from the mapData are used, and those that don't correspond to a data value
1181
- * are given null values.
1182
- */
1183
- setData: function (data, redraw) {
1184
- var options = this.options,
1185
- mapData = options.mapData,
1186
- joinBy = options.joinBy,
1187
- dataUsed = [];
1188
-
1254
+ return ret;
1255
+ },
1256
+
1257
+ /**
1258
+ * Extend setData to join in mapData. If the allAreas option is true, all areas
1259
+ * from the mapData are used, and those that don't correspond to a data value
1260
+ * are given null values.
1261
+ */
1262
+ setData: function (data, redraw) {
1263
+ var options = this.options,
1264
+ mapData = options.mapData,
1265
+ joinBy = options.joinBy,
1266
+ joinByNull = joinBy === null,
1267
+ dataUsed = [],
1268
+ mapPoint,
1269
+ props,
1270
+ i;
1271
+
1272
+ if (joinByNull) {
1273
+ joinBy = '_i';
1274
+ }
1275
+ joinBy = this.joinBy = Highcharts.splat(joinBy);
1276
+ if (!joinBy[1]) {
1277
+ joinBy[1] = joinBy[0];
1278
+ }
1279
+
1280
+ // Pick up numeric values, add index
1281
+ if (data) {
1282
+ each(data, function (val, i) {
1283
+ if (typeof val === 'number') {
1284
+ data[i] = {
1285
+ value: val
1286
+ };
1287
+ }
1288
+ if (joinByNull) {
1289
+ data[i]._i = i;
1290
+ }
1291
+ });
1292
+ }
1293
+
1294
+ this.getBox(data);
1295
+ if (mapData) {
1296
+ if (mapData.type === 'FeatureCollection') {
1297
+ mapData = Highcharts.geojson(mapData, this.type, this);
1298
+ }
1189
1299
 
1190
- this.getBox(data);
1191
1300
  this.getBox(mapData);
1192
- if (options.allAreas && mapData) {
1301
+ this.mapData = mapData;
1302
+ this.mapMap = {};
1303
+
1304
+ for (i = 0; i < mapData.length; i++) {
1305
+ mapPoint = mapData[i];
1306
+ props = mapPoint.properties;
1307
+
1308
+ mapPoint._i = i;
1309
+ // Copy the property over to root for faster access
1310
+ if (joinBy[0] && props && props[joinBy[0]]) {
1311
+ mapPoint[joinBy[0]] = props[joinBy[0]];
1312
+ }
1313
+ this.mapMap[mapPoint[joinBy[0]]] = mapPoint;
1314
+ }
1315
+
1316
+ if (options.allAreas) {
1193
1317
 
1194
1318
  data = data || [];
1195
1319
 
1196
1320
  // Registered the point codes that actually hold data
1197
- if (joinBy) {
1321
+ if (joinBy[1]) {
1198
1322
  each(data, function (point) {
1199
- dataUsed.push(point[joinBy]);
1323
+ dataUsed.push(point[joinBy[1]]);
1200
1324
  });
1201
1325
  }
1202
1326
 
1203
1327
  // Add those map points that don't correspond to data, which will be drawn as null points
1204
1328
  dataUsed = '|' + dataUsed.join('|') + '|'; // String search is faster than array.indexOf
1329
+
1205
1330
  each(mapData, function (mapPoint) {
1206
- if (!joinBy || dataUsed.indexOf('|' + mapPoint[joinBy] + '|') === -1) {
1331
+ if (!joinBy[0] || dataUsed.indexOf('|' + mapPoint[joinBy[0]] + '|') === -1) {
1207
1332
  data.push(merge(mapPoint, { value: null }));
1208
1333
  }
1209
1334
  });
1210
1335
  }
1211
- Series.prototype.setData.call(this, data, redraw);
1212
- },
1213
-
1214
- /**
1215
- * For each point, get the corresponding map data
1216
- */
1217
- getMapData: function (key, value) {
1218
- var options = this.options,
1219
- mapData = options.mapData,
1220
- mapMap = this.mapMap,
1221
- i = mapData.length;
1222
-
1223
- // Create a cache for quicker lookup second time
1224
- if (!mapMap) {
1225
- mapMap = this.mapMap = {};
1226
- }
1227
- if (mapMap[value] !== undefined) {
1228
- return mapData[mapMap[value]];
1229
-
1230
- } else if (value !== undefined) {
1231
- while (i--) {
1232
- if (mapData[i][key] === value) {
1233
- mapMap[value] = i; // cache it
1234
- return mapData[i];
1235
- }
1236
- }
1237
- }
1238
- },
1239
-
1240
- /**
1241
- * In choropleth maps, the color is a result of the value, so this needs translation too
1242
- */
1243
- translateColors: function () {
1244
- var series = this,
1245
- nullColor = this.options.nullColor,
1246
- colorAxis = this.colorAxis;
1336
+ }
1337
+ Series.prototype.setData.call(this, data, redraw);
1338
+ },
1247
1339
 
1248
- each(this.data, function (point) {
1249
- var value = point.value,
1250
- color;
1340
+
1341
+ /**
1342
+ * No graph for the map series
1343
+ */
1344
+ drawGraph: noop,
1345
+
1346
+ /**
1347
+ * We need the points' bounding boxes in order to draw the data labels, so
1348
+ * we skip it now and call it from drawPoints instead.
1349
+ */
1350
+ drawDataLabels: noop,
1251
1351
 
1252
- color = value === null ? nullColor : colorAxis ? colorAxis.toColor(value, point) : (point.color) || series.color;
1352
+ /**
1353
+ * Allow a quick redraw by just translating the area group. Used for zooming and panning
1354
+ * in capable browsers.
1355
+ */
1356
+ doFullTranslate: function () {
1357
+ return this.isDirtyData || this.chart.renderer.isVML || !this.baseTrans;
1358
+ },
1359
+
1360
+ /**
1361
+ * Add the path option for data points. Find the max value for color calculation.
1362
+ */
1363
+ translate: function () {
1364
+ var series = this,
1365
+ xAxis = series.xAxis,
1366
+ yAxis = series.yAxis,
1367
+ doFullTranslate = series.doFullTranslate();
1253
1368
 
1254
- if (color) {
1255
- point.color = point.options.color = color;
1256
- }
1257
- });
1258
- },
1369
+ series.generatePoints();
1259
1370
 
1260
- /**
1261
- * No graph for the map series
1262
- */
1263
- drawGraph: noop,
1371
+ each(series.data, function (point) {
1264
1372
 
1265
- /**
1266
- * We need the points' bounding boxes in order to draw the data labels, so
1267
- * we skip it now and call it from drawPoints instead.
1268
- */
1269
- drawDataLabels: noop,
1270
-
1271
- /**
1272
- * Add the path option for data points. Find the max value for color calculation.
1273
- */
1274
- translate: function () {
1275
- var series = this,
1276
- xAxis = series.xAxis,
1277
- yAxis = series.yAxis;
1278
-
1279
- series.generatePoints();
1280
-
1281
- each(series.data, function (point) {
1282
-
1283
- // Record the middle point (loosely based on centroid), determined
1284
- // by the middleX and middleY options.
1285
- point.plotX = xAxis.toPixels(point._midX, true);
1286
- point.plotY = yAxis.toPixels(point._midY, true);
1373
+ // Record the middle point (loosely based on centroid), determined
1374
+ // by the middleX and middleY options.
1375
+ point.plotX = xAxis.toPixels(point._midX, true);
1376
+ point.plotY = yAxis.toPixels(point._midY, true);
1287
1377
 
1288
- if (series.isDirtyData || series.chart.renderer.isVML) {
1289
-
1290
- point.shapeType = 'path';
1291
- point.shapeArgs = {
1292
- //d: display ? series.translatePath(point.path) : ''
1293
- d: series.translatePath(point.path),
1294
- 'vector-effect': 'non-scaling-stroke'
1295
- };
1296
- }
1297
- });
1298
-
1299
- series.translateColors();
1300
- },
1378
+ if (doFullTranslate) {
1301
1379
 
1302
- /**
1303
- * Use the drawPoints method of column, that is able to handle simple shapeArgs.
1304
- * Extend it by assigning the tooltip position.
1305
- */
1306
- drawPoints: function () {
1307
- var series = this,
1308
- xAxis = series.xAxis,
1309
- yAxis = series.yAxis,
1310
- scale,
1311
- translateX,
1312
- group = series.group,
1313
- chart = series.chart,
1314
- renderer = chart.renderer,
1315
- translateY,
1316
- getTranslate = function (axis, mapRatio) {
1317
- var dataMin = axis.dataMin,
1318
- dataMax = axis.dataMax,
1319
- fullDataMin = dataMin - ((dataMax - dataMin) * (mapRatio - 1) / 2),
1320
- fullMin = axis.min - axis.minPixelPadding / axis.transA,
1321
- minOffset = fullMin - fullDataMin,
1322
- centerOffset = (dataMax - dataMin - axis.max + axis.min) * mapRatio,
1323
- center = minOffset / centerOffset;
1324
- return (axis.len * (1 - scale)) * center;
1380
+ point.shapeType = 'path';
1381
+ point.shapeArgs = {
1382
+ //d: display ? series.translatePath(point.path) : ''
1383
+ d: series.translatePath(point.path),
1384
+ 'vector-effect': 'non-scaling-stroke'
1325
1385
  };
1326
-
1327
- // Set a group that handles transform during zooming and panning in order to preserve clipping
1328
- // on series.group
1329
- if (!series.transformGroup) {
1330
- series.transformGroup = renderer.g()
1331
- .attr({
1332
- scaleX: 1,
1333
- scaleY: 1
1334
- })
1335
- .add(group);
1336
1386
  }
1337
-
1338
- // Draw the shapes again
1339
- if (series.isDirtyData || renderer.isVML) {
1340
-
1341
- // Draw them in transformGroup
1342
- series.group = series.transformGroup;
1343
- seriesTypes.column.prototype.drawPoints.apply(series);
1344
- series.group = group; // Reset
1387
+ });
1388
+
1389
+ series.translateColors();
1390
+ },
1391
+
1392
+ /**
1393
+ * Use the drawPoints method of column, that is able to handle simple shapeArgs.
1394
+ * Extend it by assigning the tooltip position.
1395
+ */
1396
+ drawPoints: function () {
1397
+ var series = this,
1398
+ xAxis = series.xAxis,
1399
+ yAxis = series.yAxis,
1400
+ group = series.group,
1401
+ chart = series.chart,
1402
+ renderer = chart.renderer,
1403
+ scaleX,
1404
+ scaleY,
1405
+ translateX,
1406
+ translateY,
1407
+ baseTrans = this.baseTrans;
1408
+
1409
+ // Set a group that handles transform during zooming and panning in order to preserve clipping
1410
+ // on series.group
1411
+ if (!series.transformGroup) {
1412
+ series.transformGroup = renderer.g()
1413
+ .attr({
1414
+ scaleX: 1,
1415
+ scaleY: 1
1416
+ })
1417
+ .add(group);
1418
+ }
1419
+
1420
+ // Draw the shapes again
1421
+ if (series.doFullTranslate()) {
1345
1422
 
1346
- // Individual point actions
1423
+ // Individual point actions
1424
+ if (chart.hasRendered && series.pointAttrToOptions.fill === 'color') {
1347
1425
  each(series.points, function (point) {
1348
1426
 
1349
1427
  // Reset color on update/redraw
1350
- if (chart.hasRendered && point.graphic) {
1351
- point.graphic.attr('fill', point.options.color);
1428
+ if (point.graphic) {
1429
+ point.graphic.attr('fill', point.color);
1352
1430
  }
1353
1431
 
1354
1432
  });
1433
+ }
1355
1434
 
1356
- // Set the base for later scale-zooming
1357
- this.transA = xAxis.transA;
1435
+ // Draw them in transformGroup
1436
+ series.group = series.transformGroup;
1437
+ seriesTypes.column.prototype.drawPoints.apply(series);
1438
+ series.group = group; // Reset
1358
1439
 
1359
- // Just update the scale and transform for better performance
1360
- } else {
1361
- scale = xAxis.transA / this.transA;
1362
- if (scale > 0.99 && scale < 1.01) { // rounding errors
1363
- translateX = 0;
1364
- translateY = 0;
1365
- scale = 1;
1366
-
1367
- } else {
1368
- translateX = getTranslate(xAxis, Math.max(1, series.chart.mapRatio));
1369
- translateY = getTranslate(yAxis, 1 / Math.min(1, series.chart.mapRatio));
1370
- }
1371
-
1372
- this.transformGroup.animate({
1373
- translateX: translateX,
1374
- translateY: translateY,
1375
- scaleX: scale,
1376
- scaleY: scale
1377
- });
1440
+ // Add class names
1441
+ each(series.points, function (point) {
1442
+ if (point.graphic) {
1443
+ if (point.name) {
1444
+ point.graphic.addClass('highcharts-name-' + point.name.replace(' ', '-').toLowerCase());
1445
+ }
1446
+ if (point.properties && point.properties['hc-key']) {
1447
+ point.graphic.addClass('highcharts-key-' + point.properties['hc-key'].toLowerCase());
1448
+ }
1449
+ }
1450
+ });
1378
1451
 
1379
- }
1452
+ // Set the base for later scale-zooming. The originX and originY properties are the
1453
+ // axis values in the plot area's upper left corner.
1454
+ this.baseTrans = {
1455
+ originX: xAxis.min - xAxis.minPixelPadding / xAxis.transA,
1456
+ originY: yAxis.min - yAxis.minPixelPadding / yAxis.transA + (yAxis.reversed ? 0 : yAxis.len / yAxis.transA),
1457
+ transAX: xAxis.transA,
1458
+ transAY: yAxis.transA
1459
+ };
1380
1460
 
1381
-
1382
- // Now draw the data labels
1383
- Series.prototype.drawDataLabels.call(series);
1384
-
1385
- },
1461
+ // Just update the scale and transform for better performance
1462
+ } else {
1463
+ scaleX = xAxis.transA / baseTrans.transAX;
1464
+ scaleY = yAxis.transA / baseTrans.transAY;
1465
+ if (scaleX > 0.99 && scaleX < 1.01 && scaleY > 0.99 && scaleY < 1.01) { // rounding errors
1466
+ translateX = 0;
1467
+ translateY = 0;
1468
+ scaleX = 1;
1469
+ scaleY = 1;
1470
+
1471
+ } else {
1472
+ translateX = xAxis.toPixels(baseTrans.originX, true);
1473
+ translateY = yAxis.toPixels(baseTrans.originY, true);
1474
+ }
1475
+
1476
+ this.transformGroup.animate({
1477
+ translateX: translateX,
1478
+ translateY: translateY,
1479
+ scaleX: scaleX,
1480
+ scaleY: scaleY
1481
+ });
1386
1482
 
1387
- /**
1388
- * Override render to throw in an async call in IE8. Otherwise it chokes on the US counties demo.
1389
- */
1390
- render: function () {
1391
- var series = this,
1392
- render = Series.prototype.render;
1393
-
1394
- // Give IE8 some time to breathe.
1395
- if (series.chart.renderer.isVML && series.data.length > 3000) {
1396
- setTimeout(function () {
1397
- render.call(series);
1398
- });
1399
- } else {
1400
- render.call(series);
1483
+ }
1484
+
1485
+ this.drawMapDataLabels();
1486
+
1487
+
1488
+ },
1489
+
1490
+ /**
1491
+ * Draw the data labels. Special for maps is the time that the data labels are drawn (after points),
1492
+ * and the clipping of the dataLabelsGroup.
1493
+ */
1494
+ drawMapDataLabels: function () {
1495
+
1496
+ Series.prototype.drawDataLabels.call(this);
1497
+ if (this.dataLabelsGroup) {
1498
+ this.dataLabelsGroup.clip(this.chart.clipRect);
1499
+ }
1500
+
1501
+ this.hideOverlappingDataLabels();
1502
+ },
1503
+
1504
+ /**
1505
+ * Hide overlapping labels. Labels are moved and faded in and out on zoom to provide a smooth
1506
+ * visual imression.
1507
+ */
1508
+ hideOverlappingDataLabels: function () {
1509
+
1510
+ var points = this.points,
1511
+ len = points.length,
1512
+ i,
1513
+ j,
1514
+ label1,
1515
+ label2,
1516
+ intersectRect = function (pos1, pos2, size1, size2) {
1517
+ return !(
1518
+ pos2.x > pos1.x + size1.width ||
1519
+ pos2.x + size2.width < pos1.x ||
1520
+ pos2.y > pos1.y + size1.height ||
1521
+ pos2.y + size2.height < pos1.y
1522
+ );
1523
+ };
1524
+
1525
+ // Mark with initial opacity
1526
+ each(points, function (point, label) {
1527
+ label = point.dataLabel;
1528
+ if (label) {
1529
+ label.oldOpacity = label.opacity;
1530
+ label.newOpacity = 1;
1401
1531
  }
1402
- },
1532
+ });
1403
1533
 
1404
- /**
1405
- * The initial animation for the map series. By default, animation is disabled.
1406
- * Animation of map shapes is not at all supported in VML browsers.
1407
- */
1408
- animate: function (init) {
1409
- var chart = this.chart,
1410
- animation = this.options.animation,
1411
- group = this.group,
1412
- xAxis = this.xAxis,
1413
- yAxis = this.yAxis,
1414
- left = xAxis.pos,
1415
- top = yAxis.pos;
1416
-
1417
- if (chart.renderer.isSVG) {
1418
-
1419
- if (animation === true) {
1420
- animation = {
1421
- duration: 1000
1422
- };
1534
+ // Detect overlapping labels
1535
+ for (i = 0; i < len - 1; ++i) {
1536
+ label1 = points[i].dataLabel;
1537
+
1538
+ for (j = i + 1; j < len; ++j) {
1539
+ label2 = points[j].dataLabel;
1540
+ if (label1 && label2 && label1.newOpacity !== 0 && label2.newOpacity !== 0 &&
1541
+ intersectRect(label1.alignAttr, label2.alignAttr, label1, label2)) {
1542
+ (points[i].labelrank < points[j].labelrank ? label1 : label2).newOpacity = 0;
1423
1543
  }
1424
-
1425
- // Initialize the animation
1426
- if (init) {
1427
-
1428
- // Scale down the group and place it in the center
1429
- group.attr({
1430
- translateX: left + xAxis.len / 2,
1431
- translateY: top + yAxis.len / 2,
1432
- scaleX: 0.001, // #1499
1433
- scaleY: 0.001
1434
- });
1435
-
1436
- // Run the animation
1437
- } else {
1438
- group.animate({
1439
- translateX: left,
1440
- translateY: top,
1441
- scaleX: 1,
1442
- scaleY: 1
1443
- }, animation);
1444
-
1445
- // Delete this function to allow it only once
1446
- this.animate = null;
1544
+ }
1545
+ }
1546
+
1547
+ // Hide or show
1548
+ each(points, function (point, label) {
1549
+ label = point.dataLabel;
1550
+ if (label) {
1551
+ if (label.oldOpacity !== label.newOpacity) {
1552
+ label[label.isOld ? 'animate' : 'attr'](extend({ opacity: label.newOpacity }, label.alignAttr));
1447
1553
  }
1554
+ label.isOld = true;
1448
1555
  }
1449
- },
1556
+ });
1557
+ },
1450
1558
 
1451
- /**
1452
- * Animate in the new series from the clicked point in the old series.
1453
- * Depends on the drilldown.js module
1454
- */
1455
- animateDrilldown: function (init) {
1456
- var toBox = this.chart.plotBox,
1457
- level = this.chart.drilldownLevels[this.chart.drilldownLevels.length - 1],
1458
- fromBox = level.bBox,
1459
- animationOptions = this.chart.options.drilldown.animation,
1460
- scale;
1461
-
1462
- if (!init) {
1463
-
1464
- scale = Math.min(fromBox.width / toBox.width, fromBox.height / toBox.height);
1465
- level.shapeArgs = {
1466
- scaleX: scale,
1467
- scaleY: scale,
1468
- translateX: fromBox.x,
1469
- translateY: fromBox.y
1559
+ /**
1560
+ * Override render to throw in an async call in IE8. Otherwise it chokes on the US counties demo.
1561
+ */
1562
+ render: function () {
1563
+ var series = this,
1564
+ render = Series.prototype.render;
1565
+
1566
+ // Give IE8 some time to breathe.
1567
+ if (series.chart.renderer.isVML && series.data.length > 3000) {
1568
+ setTimeout(function () {
1569
+ render.call(series);
1570
+ });
1571
+ } else {
1572
+ render.call(series);
1573
+ }
1574
+ },
1575
+
1576
+ /**
1577
+ * The initial animation for the map series. By default, animation is disabled.
1578
+ * Animation of map shapes is not at all supported in VML browsers.
1579
+ */
1580
+ animate: function (init) {
1581
+ var chart = this.chart,
1582
+ animation = this.options.animation,
1583
+ group = this.group,
1584
+ xAxis = this.xAxis,
1585
+ yAxis = this.yAxis,
1586
+ left = xAxis.pos,
1587
+ top = yAxis.pos;
1588
+
1589
+ if (chart.renderer.isSVG) {
1590
+
1591
+ if (animation === true) {
1592
+ animation = {
1593
+ duration: 1000
1470
1594
  };
1471
-
1472
- // TODO: Animate this.group instead
1473
- each(this.points, function (point) {
1474
-
1475
- point.graphic
1476
- .attr(level.shapeArgs)
1477
- .animate({
1478
- scaleX: 1,
1479
- scaleY: 1,
1480
- translateX: 0,
1481
- translateY: 0
1482
- }, animationOptions);
1595
+ }
1483
1596
 
1597
+ // Initialize the animation
1598
+ if (init) {
1599
+
1600
+ // Scale down the group and place it in the center
1601
+ group.attr({
1602
+ translateX: left + xAxis.len / 2,
1603
+ translateY: top + yAxis.len / 2,
1604
+ scaleX: 0.001, // #1499
1605
+ scaleY: 0.001
1484
1606
  });
1485
-
1607
+
1608
+ // Run the animation
1609
+ } else {
1610
+ group.animate({
1611
+ translateX: left,
1612
+ translateY: top,
1613
+ scaleX: 1,
1614
+ scaleY: 1
1615
+ }, animation);
1616
+
1617
+ // Delete this function to allow it only once
1486
1618
  this.animate = null;
1487
1619
  }
1488
-
1489
- },
1620
+ }
1621
+ },
1490
1622
 
1491
- drawLegendSymbol: H.LegendSymbolMixin.drawRectangle,
1623
+ /**
1624
+ * Animate in the new series from the clicked point in the old series.
1625
+ * Depends on the drilldown.js module
1626
+ */
1627
+ animateDrilldown: function (init) {
1628
+ var toBox = this.chart.plotBox,
1629
+ level = this.chart.drilldownLevels[this.chart.drilldownLevels.length - 1],
1630
+ fromBox = level.bBox,
1631
+ animationOptions = this.chart.options.drilldown.animation,
1632
+ scale;
1633
+
1634
+ if (!init) {
1635
+
1636
+ scale = Math.min(fromBox.width / toBox.width, fromBox.height / toBox.height);
1637
+ level.shapeArgs = {
1638
+ scaleX: scale,
1639
+ scaleY: scale,
1640
+ translateX: fromBox.x,
1641
+ translateY: fromBox.y
1642
+ };
1643
+
1644
+ // TODO: Animate this.group instead
1645
+ each(this.points, function (point) {
1492
1646
 
1493
- /**
1494
- * When drilling up, pull out the individual point graphics from the lower series
1495
- * and animate them into the origin point in the upper series.
1496
- */
1497
- animateDrillupFrom: function (level) {
1498
- seriesTypes.column.prototype.animateDrillupFrom.call(this, level);
1499
- },
1647
+ point.graphic
1648
+ .attr(level.shapeArgs)
1649
+ .animate({
1650
+ scaleX: 1,
1651
+ scaleY: 1,
1652
+ translateX: 0,
1653
+ translateY: 0
1654
+ }, animationOptions);
1500
1655
 
1656
+ });
1501
1657
 
1502
- /**
1503
- * When drilling up, keep the upper series invisible until the lower series has
1504
- * moved into place
1505
- */
1506
- animateDrillupTo: function (init) {
1507
- seriesTypes.column.prototype.animateDrillupTo.call(this, init);
1658
+ this.animate = null;
1508
1659
  }
1509
- });
1660
+
1661
+ },
1510
1662
 
1663
+ drawLegendSymbol: LegendSymbolMixin.drawRectangle,
1511
1664
 
1512
- // The mapline series type
1513
- plotOptions.mapline = merge(plotOptions.map, {
1514
- lineWidth: 1,
1515
- fillColor: 'none'
1516
- });
1517
- seriesTypes.mapline = extendClass(seriesTypes.map, {
1518
- type: 'mapline',
1519
- pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
1520
- stroke: 'color',
1521
- 'stroke-width': 'lineWidth',
1522
- fill: 'fillColor'
1665
+ /**
1666
+ * When drilling up, pull out the individual point graphics from the lower series
1667
+ * and animate them into the origin point in the upper series.
1668
+ */
1669
+ animateDrillupFrom: function (level) {
1670
+ seriesTypes.column.prototype.animateDrillupFrom.call(this, level);
1671
+ },
1672
+
1673
+
1674
+ /**
1675
+ * When drilling up, keep the upper series invisible until the lower series has
1676
+ * moved into place
1677
+ */
1678
+ animateDrillupTo: function (init) {
1679
+ seriesTypes.column.prototype.animateDrillupTo.call(this, init);
1680
+ }
1681
+ }));
1682
+
1683
+
1684
+ // The mapline series type
1685
+ defaultPlotOptions.mapline = merge(defaultPlotOptions.map, {
1686
+ lineWidth: 1,
1687
+ fillColor: 'none'
1688
+ });
1689
+ seriesTypes.mapline = extendClass(seriesTypes.map, {
1690
+ type: 'mapline',
1691
+ pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
1692
+ stroke: 'color',
1693
+ 'stroke-width': 'lineWidth',
1694
+ fill: 'fillColor',
1695
+ dashstyle: 'dashStyle'
1696
+ },
1697
+ drawLegendSymbol: seriesTypes.line.prototype.drawLegendSymbol
1698
+ });
1699
+
1700
+ // The mappoint series type
1701
+ defaultPlotOptions.mappoint = merge(defaultPlotOptions.scatter, {
1702
+ dataLabels: {
1703
+ enabled: true,
1704
+ formatter: function () { // #2945
1705
+ return this.point.name;
1523
1706
  },
1524
- drawLegendSymbol: seriesTypes.line.prototype.drawLegendSymbol
1525
- });
1707
+ color: 'black',
1708
+ crop: false,
1709
+ defer: false,
1710
+ overflow: false,
1711
+ style: {
1712
+ HcTextStroke: '3px rgba(255,255,255,0.5)'
1713
+ }
1714
+ }
1715
+ });
1716
+ seriesTypes.mappoint = extendClass(seriesTypes.scatter, {
1717
+ type: 'mappoint',
1718
+ forceDL: true
1719
+ });
1526
1720
 
1527
- // The mappoint series type
1528
- plotOptions.mappoint = merge(plotOptions.scatter, {
1529
- dataLabels: {
1530
- enabled: true,
1531
- format: '{point.name}',
1532
- color: 'black',
1533
- style: {
1534
- textShadow: '0 0 5px white'
1535
- }
1721
+ // The mapbubble series type
1722
+ if (seriesTypes.bubble) {
1723
+
1724
+ defaultPlotOptions.mapbubble = merge(defaultPlotOptions.bubble, {
1725
+ animationLimit: 500,
1726
+ tooltip: {
1727
+ pointFormat: '{point.name}: {point.z}'
1536
1728
  }
1537
1729
  });
1538
- seriesTypes.mappoint = extendClass(seriesTypes.scatter, {
1539
- type: 'mappoint'
1730
+ seriesTypes.mapbubble = extendClass(seriesTypes.bubble, {
1731
+ pointClass: extendClass(Point, {
1732
+ applyOptions: MapAreaPoint.prototype.applyOptions
1733
+ }),
1734
+ xyFromShape: true,
1735
+ type: 'mapbubble',
1736
+ pointArrayMap: ['z'], // If one single value is passed, it is interpreted as z
1737
+ /**
1738
+ * Return the map area identified by the dataJoinBy option
1739
+ */
1740
+ getMapData: seriesTypes.map.prototype.getMapData,
1741
+ getBox: seriesTypes.map.prototype.getBox,
1742
+ setData: seriesTypes.map.prototype.setData
1540
1743
  });
1744
+ }
1541
1745
 
1542
- // The mapbubble series type
1543
- if (seriesTypes.bubble) {
1544
-
1545
- plotOptions.mapbubble = merge(plotOptions.bubble, {
1546
- tooltip: {
1547
- pointFormat: '{point.name}: {point.z}'
1548
- }
1549
- });
1550
- seriesTypes.mapbubble = extendClass(seriesTypes.bubble, {
1551
- pointClass: extendClass(Point, {
1552
- applyOptions: MapAreaPoint.prototype.applyOptions
1553
- }),
1554
- xyFromShape: true,
1555
- type: 'mapbubble',
1556
- pointArrayMap: ['z'], // If one single value is passed, it is interpreted as z
1557
- /**
1558
- * Return the map area identified by the dataJoinBy option
1559
- */
1560
- getMapData: seriesTypes.map.prototype.getMapData,
1561
- getBox: seriesTypes.map.prototype.getBox,
1562
- setData: seriesTypes.map.prototype.setData
1563
- });
1564
- }
1565
-
1566
- // Create symbols for the zoom buttons
1567
- function selectiveRoundedRect(attr, x, y, w, h, rTopLeft, rTopRight, rBottomRight, rBottomLeft) {
1568
- var normalize = (attr['stroke-width'] % 2 / 2);
1569
-
1570
- x -= normalize;
1571
- y -= normalize;
1572
-
1573
- return ['M', x + rTopLeft, y,
1574
- // top side
1575
- 'L', x + w - rTopRight, y,
1576
- // top right corner
1577
- 'C', x + w - rTopRight / 2, y, x + w, y + rTopRight / 2, x + w, y + rTopRight,
1578
- // right side
1579
- 'L', x + w, y + h - rBottomRight,
1580
- // bottom right corner
1581
- 'C', x + w, y + h - rBottomRight / 2, x + w - rBottomRight / 2, y + h, x + w - rBottomRight, y + h,
1582
- // bottom side
1583
- 'L', x + rBottomLeft, y + h,
1584
- // bottom left corner
1585
- 'C', x + rBottomLeft / 2, y + h, x, y + h - rBottomLeft / 2, x, y + h - rBottomLeft,
1586
- // left side
1587
- 'L', x, y + rTopLeft,
1588
- // top left corner
1589
- 'C', x, y + rTopLeft / 2, x + rTopLeft / 2, y, x + rTopLeft, y,
1590
- 'Z'
1591
- ];
1746
+ /**
1747
+ * Extend the default options with map options
1748
+ */
1749
+ defaultOptions.plotOptions.heatmap = merge(defaultOptions.plotOptions.scatter, {
1750
+ animation: false,
1751
+ borderWidth: 0,
1752
+ nullColor: '#F8F8F8',
1753
+ dataLabels: {
1754
+ formatter: function () { // #2945
1755
+ return this.point.value;
1756
+ },
1757
+ verticalAlign: 'middle',
1758
+ crop: false,
1759
+ overflow: false,
1760
+ style: {
1761
+ color: 'white',
1762
+ fontWeight: 'bold',
1763
+ HcTextStroke: '1px rgba(0,0,0,0.5)'
1764
+ }
1765
+ },
1766
+ marker: null,
1767
+ tooltip: {
1768
+ pointFormat: '{point.x}, {point.y}: {point.value}<br/>'
1769
+ },
1770
+ states: {
1771
+ normal: {
1772
+ animation: true
1773
+ },
1774
+ hover: {
1775
+ brightness: 0.2
1776
+ }
1592
1777
  }
1593
- symbols.topbutton = function (x, y, w, h, attr) {
1594
- return selectiveRoundedRect(attr, x, y, w, h, attr.r, attr.r, 0, 0);
1595
- };
1596
- symbols.bottombutton = function (x, y, w, h, attr) {
1597
- return selectiveRoundedRect(attr, x, y, w, h, 0, 0, attr.r, attr.r);
1598
- };
1599
- // The symbol callbacks are generated on the SVGRenderer object in all browsers. Even
1600
- // VML browsers need this in order to generate shapes in export. Now share
1601
- // them with the VMLRenderer.
1602
- if (H.Renderer === VMLRenderer) {
1603
- each(['topbutton', 'bottombutton'], function (shape) {
1604
- VMLRenderer.prototype.symbols[shape] = symbols[shape];
1778
+ });
1779
+
1780
+ // The Heatmap series type
1781
+ seriesTypes.heatmap = extendClass(seriesTypes.scatter, merge(colorSeriesMixin, {
1782
+ type: 'heatmap',
1783
+ pointArrayMap: ['y', 'value'],
1784
+ hasPointSpecificOptions: true,
1785
+ supportsDrilldown: true,
1786
+ getExtremesFromAll: true,
1787
+ init: function () {
1788
+ seriesTypes.scatter.prototype.init.apply(this, arguments);
1789
+ this.pointRange = this.options.colsize || 1;
1790
+ this.yAxis.axisPointRange = this.options.rowsize || 1; // general point range
1791
+ },
1792
+ translate: function () {
1793
+ var series = this,
1794
+ options = series.options,
1795
+ xAxis = series.xAxis,
1796
+ yAxis = series.yAxis;
1797
+
1798
+ series.generatePoints();
1799
+
1800
+ each(series.points, function (point) {
1801
+ var xPad = (options.colsize || 1) / 2,
1802
+ yPad = (options.rowsize || 1) / 2,
1803
+ x1 = Math.round(xAxis.len - xAxis.translate(point.x - xPad, 0, 1, 0, 1)),
1804
+ x2 = Math.round(xAxis.len - xAxis.translate(point.x + xPad, 0, 1, 0, 1)),
1805
+ y1 = Math.round(yAxis.translate(point.y - yPad, 0, 1, 0, 1)),
1806
+ y2 = Math.round(yAxis.translate(point.y + yPad, 0, 1, 0, 1));
1807
+
1808
+ // Set plotX and plotY for use in K-D-Tree and more
1809
+ point.plotX = (x1 + x2) / 2;
1810
+ point.plotY = (y1 + y2) / 2;
1811
+
1812
+ point.shapeType = 'rect';
1813
+ point.shapeArgs = {
1814
+ x: Math.min(x1, x2),
1815
+ y: Math.min(y1, y2),
1816
+ width: Math.abs(x2 - x1),
1817
+ height: Math.abs(y2 - y1)
1818
+ };
1605
1819
  });
1820
+
1821
+ series.translateColors();
1822
+
1823
+ // Make sure colors are updated on colorAxis update (#2893)
1824
+ if (this.chart.hasRendered) {
1825
+ each(series.points, function (point) {
1826
+ point.shapeArgs.fill = point.options.color || point.color; // #3311
1827
+ });
1828
+ }
1829
+ },
1830
+ drawPoints: seriesTypes.column.prototype.drawPoints,
1831
+ animate: noop,
1832
+ getBox: noop,
1833
+ drawLegendSymbol: LegendSymbolMixin.drawRectangle,
1834
+
1835
+ getExtremes: function () {
1836
+ // Get the extremes from the value data
1837
+ Series.prototype.getExtremes.call(this, this.valueData);
1838
+ this.valueMin = this.dataMin;
1839
+ this.valueMax = this.dataMax;
1840
+
1841
+ // Get the extremes from the y data
1842
+ Series.prototype.getExtremes.call(this);
1606
1843
  }
1844
+
1845
+ }));
1846
+
1607
1847
 
1848
+ /**
1849
+ * Convert a geojson object to map data of a given Highcharts type (map, mappoint or mapline).
1850
+ */
1851
+ Highcharts.geojson = function (geojson, hType, series) {
1852
+ var mapData = [],
1853
+ path = [],
1854
+ polygonToPath = function (polygon) {
1855
+ var i = 0,
1856
+ len = polygon.length;
1857
+ path.push('M');
1858
+ for (; i < len; i++) {
1859
+ if (i === 1) {
1860
+ path.push('L');
1861
+ }
1862
+ path.push(polygon[i][0], -polygon[i][1]);
1863
+ }
1864
+ };
1865
+
1866
+ hType = hType || 'map';
1608
1867
 
1609
- /**
1610
- * A wrapper for Chart with all the default values for a Map
1611
- */
1612
- H.Map = function (options, callback) {
1868
+ each(geojson.features, function (feature) {
1869
+
1870
+ var geometry = feature.geometry,
1871
+ type = geometry.type,
1872
+ coordinates = geometry.coordinates,
1873
+ properties = feature.properties,
1874
+ point;
1613
1875
 
1614
- var hiddenAxis = {
1615
- endOnTick: false,
1616
- gridLineWidth: 0,
1617
- lineWidth: 0,
1618
- minPadding: 0,
1619
- maxPadding: 0,
1620
- startOnTick: false,
1621
- title: null,
1622
- tickPositions: []
1623
- //tickInterval: 500,
1624
- //gridZIndex: 10
1625
- },
1626
- seriesOptions;
1876
+ path = [];
1877
+
1878
+ if (hType === 'map' || hType === 'mapbubble') {
1879
+ if (type === 'Polygon') {
1880
+ each(coordinates, polygonToPath);
1881
+ path.push('Z');
1882
+
1883
+ } else if (type === 'MultiPolygon') {
1884
+ each(coordinates, function (items) {
1885
+ each(items, polygonToPath);
1886
+ });
1887
+ path.push('Z');
1888
+ }
1889
+
1890
+ if (path.length) {
1891
+ point = { path: path };
1892
+ }
1627
1893
 
1628
- // Don't merge the data
1629
- seriesOptions = options.series;
1630
- options.series = null;
1894
+ } else if (hType === 'mapline') {
1895
+ if (type === 'LineString') {
1896
+ polygonToPath(coordinates);
1897
+ } else if (type === 'MultiLineString') {
1898
+ each(coordinates, polygonToPath);
1899
+ }
1900
+
1901
+ if (path.length) {
1902
+ point = { path: path };
1903
+ }
1631
1904
 
1632
- options = merge({
1633
- chart: {
1634
- panning: 'xy',
1635
- type: 'map'
1905
+ } else if (hType === 'mappoint') {
1906
+ if (type === 'Point') {
1907
+ point = {
1908
+ x: coordinates[0],
1909
+ y: -coordinates[1]
1910
+ };
1911
+ }
1912
+ }
1913
+ if (point) {
1914
+ mapData.push(extend(point, {
1915
+ name: properties.name || properties.NAME,
1916
+ properties: properties
1917
+ }));
1918
+ }
1919
+
1920
+ });
1921
+
1922
+ // Create a credits text that includes map source, to be picked up in Chart.showCredits
1923
+ if (series) {
1924
+ series.chart.mapCredits = '<a href="http://www.highcharts.com">Highcharts</a> \u00A9 ' +
1925
+ '<a href="' + geojson.copyrightUrl + '">' + geojson.copyrightShort + '</a>';
1926
+ }
1927
+
1928
+ return mapData;
1929
+ };
1930
+
1931
+ /**
1932
+ * Override showCredits to include map source by default
1933
+ */
1934
+ wrap(Chart.prototype, 'showCredits', function (proceed, credits) {
1935
+
1936
+ if (defaultOptions.credits.text === this.options.credits.text && this.mapCredits) { // default text and mapCredits is set
1937
+ credits.text = this.mapCredits;
1938
+ credits.href = null;
1939
+ }
1940
+
1941
+ proceed.call(this, credits);
1942
+ });
1943
+
1944
+ // Add language
1945
+ extend(defaultOptions.lang, {
1946
+ zoomIn: 'Zoom in',
1947
+ zoomOut: 'Zoom out'
1948
+ });
1949
+
1950
+
1951
+ // Set the default map navigation options
1952
+ defaultOptions.mapNavigation = {
1953
+ buttonOptions: {
1954
+ alignTo: 'plotBox',
1955
+ align: 'left',
1956
+ verticalAlign: 'top',
1957
+ x: 0,
1958
+ width: 18,
1959
+ height: 18,
1960
+ style: {
1961
+ fontSize: '15px',
1962
+ fontWeight: 'bold',
1963
+ textAlign: 'center'
1964
+ },
1965
+ theme: {
1966
+ 'stroke-width': 1
1967
+ }
1968
+ },
1969
+ buttons: {
1970
+ zoomIn: {
1971
+ onclick: function () {
1972
+ this.mapZoom(0.5);
1636
1973
  },
1637
- xAxis: hiddenAxis,
1638
- yAxis: merge(hiddenAxis, { reversed: true })
1974
+ text: '+',
1975
+ y: 0
1639
1976
  },
1640
- options, // user's options
1977
+ zoomOut: {
1978
+ onclick: function () {
1979
+ this.mapZoom(2);
1980
+ },
1981
+ text: '-',
1982
+ y: 28
1983
+ }
1984
+ }
1985
+ // enabled: false,
1986
+ // enableButtons: null, // inherit from enabled
1987
+ // enableTouchZoom: null, // inherit from enabled
1988
+ // enableDoubleClickZoom: null, // inherit from enabled
1989
+ // enableDoubleClickZoomTo: false
1990
+ // enableMouseWheelZoom: null, // inherit from enabled
1991
+ };
1992
+
1993
+ /**
1994
+ * Utility for reading SVG paths directly.
1995
+ */
1996
+ Highcharts.splitPath = function (path) {
1997
+ var i;
1998
+
1999
+ // Move letters apart
2000
+ path = path.replace(/([A-Za-z])/g, ' $1 ');
2001
+ // Trim
2002
+ path = path.replace(/^\s*/, "").replace(/\s*$/, "");
1641
2003
 
1642
- { // forced options
1643
- chart: {
1644
- inverted: false,
1645
- alignTicks: false,
1646
- preserveAspectRatio: true
1647
- }
1648
- });
2004
+ // Split on spaces and commas
2005
+ path = path.split(/[ ,]+/);
1649
2006
 
1650
- options.series = seriesOptions;
2007
+ // Parse numbers
2008
+ for (i = 0; i < path.length; i++) {
2009
+ if (!/[a-zA-Z]/.test(path[i])) {
2010
+ path[i] = parseFloat(path[i]);
2011
+ }
2012
+ }
2013
+ return path;
2014
+ };
2015
+
2016
+ // A placeholder for map definitions
2017
+ Highcharts.maps = {};
2018
+
2019
+
2020
+
2021
+
2022
+
2023
+ // Create symbols for the zoom buttons
2024
+ function selectiveRoundedRect(attr, x, y, w, h, rTopLeft, rTopRight, rBottomRight, rBottomLeft) {
2025
+ var normalize = (attr['stroke-width'] % 2 / 2);
2026
+
2027
+ x -= normalize;
2028
+ y -= normalize;
2029
+
2030
+ return ['M', x + rTopLeft, y,
2031
+ // top side
2032
+ 'L', x + w - rTopRight, y,
2033
+ // top right corner
2034
+ 'C', x + w - rTopRight / 2, y, x + w, y + rTopRight / 2, x + w, y + rTopRight,
2035
+ // right side
2036
+ 'L', x + w, y + h - rBottomRight,
2037
+ // bottom right corner
2038
+ 'C', x + w, y + h - rBottomRight / 2, x + w - rBottomRight / 2, y + h, x + w - rBottomRight, y + h,
2039
+ // bottom side
2040
+ 'L', x + rBottomLeft, y + h,
2041
+ // bottom left corner
2042
+ 'C', x + rBottomLeft / 2, y + h, x, y + h - rBottomLeft / 2, x, y + h - rBottomLeft,
2043
+ // left side
2044
+ 'L', x, y + rTopLeft,
2045
+ // top left corner
2046
+ 'C', x, y + rTopLeft / 2, x + rTopLeft / 2, y, x + rTopLeft, y,
2047
+ 'Z'
2048
+ ];
2049
+ }
2050
+ SVGRenderer.prototype.symbols.topbutton = function (x, y, w, h, attr) {
2051
+ return selectiveRoundedRect(attr, x, y, w, h, attr.r, attr.r, 0, 0);
2052
+ };
2053
+ SVGRenderer.prototype.symbols.bottombutton = function (x, y, w, h, attr) {
2054
+ return selectiveRoundedRect(attr, x, y, w, h, 0, 0, attr.r, attr.r);
2055
+ };
2056
+ // The symbol callbacks are generated on the SVGRenderer object in all browsers. Even
2057
+ // VML browsers need this in order to generate shapes in export. Now share
2058
+ // them with the VMLRenderer.
2059
+ if (Renderer === VMLRenderer) {
2060
+ each(['topbutton', 'bottombutton'], function (shape) {
2061
+ VMLRenderer.prototype.symbols[shape] = SVGRenderer.prototype.symbols[shape];
2062
+ });
2063
+ }
2064
+
2065
+
2066
+ /**
2067
+ * A wrapper for Chart with all the default values for a Map
2068
+ */
2069
+ Highcharts.Map = function (options, callback) {
1651
2070
 
2071
+ var hiddenAxis = {
2072
+ endOnTick: false,
2073
+ gridLineWidth: 0,
2074
+ lineWidth: 0,
2075
+ minPadding: 0,
2076
+ maxPadding: 0,
2077
+ startOnTick: false,
2078
+ title: null,
2079
+ tickPositions: []
2080
+ },
2081
+ seriesOptions;
2082
+
2083
+ /* For visual testing
2084
+ hiddenAxis.gridLineWidth = 1;
2085
+ hiddenAxis.gridZIndex = 10;
2086
+ hiddenAxis.tickPositions = undefined;
2087
+ // */
1652
2088
 
1653
- return new Chart(options, callback);
1654
- };
1655
- }(Highcharts));
2089
+ // Don't merge the data
2090
+ seriesOptions = options.series;
2091
+ options.series = null;
1656
2092
 
2093
+ options = merge({
2094
+ chart: {
2095
+ panning: 'xy',
2096
+ type: 'map'
2097
+ },
2098
+ xAxis: hiddenAxis,
2099
+ yAxis: merge(hiddenAxis, { reversed: true })
2100
+ },
2101
+ options, // user's options
2102
+
2103
+ { // forced options
2104
+ chart: {
2105
+ inverted: false,
2106
+ alignTicks: false,
2107
+ preserveAspectRatio: true
2108
+ }
2109
+ });
2110
+
2111
+ options.series = seriesOptions;
2112
+
2113
+
2114
+ return new Chart(options, callback);
2115
+ };
2116
+
2117
+ }(Highcharts));