highcharts-rails 4.0.4 → 4.0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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));