UNPKG

98.9 kBJavaScriptView Raw
1/**
2 * @license Highmaps JS v5.0.2 (2016-10-26)
3 * Highmaps as a plugin for Highcharts 4.1.x or Highstock 2.1.x (x being the patch version of this file)
4 *
5 * (c) 2011-2016 Torstein Honsi
6 *
7 * License: www.highcharts.com/license
8 */
9(function(factory) {
10 if (typeof module === 'object' && module.exports) {
11 module.exports = factory;
12 } else {
13 factory(Highcharts);
14 }
15}(function(Highcharts) {
16 (function(H) {
17 /**
18 * (c) 2010-2016 Torstein Honsi
19 *
20 * License: www.highcharts.com/license
21 */
22 'use strict';
23 var Axis = H.Axis,
24 each = H.each,
25 pick = H.pick,
26 wrap = H.wrap;
27 /**
28 * Override to use the extreme coordinates from the SVG shape, not the
29 * data values
30 */
31 wrap(Axis.prototype, 'getSeriesExtremes', function(proceed) {
32 var isXAxis = this.isXAxis,
33 dataMin,
34 dataMax,
35 xData = [],
36 useMapGeometry;
37
38 // Remove the xData array and cache it locally so that the proceed method doesn't use it
39 if (isXAxis) {
40 each(this.series, function(series, i) {
41 if (series.useMapGeometry) {
42 xData[i] = series.xData;
43 series.xData = [];
44 }
45 });
46 }
47
48 // Call base to reach normal cartesian series (like mappoint)
49 proceed.call(this);
50
51 // Run extremes logic for map and mapline
52 if (isXAxis) {
53 dataMin = pick(this.dataMin, Number.MAX_VALUE);
54 dataMax = pick(this.dataMax, -Number.MAX_VALUE);
55 each(this.series, function(series, i) {
56 if (series.useMapGeometry) {
57 dataMin = Math.min(dataMin, pick(series.minX, dataMin));
58 dataMax = Math.max(dataMax, pick(series.maxX, dataMin));
59 series.xData = xData[i]; // Reset xData array
60 useMapGeometry = true;
61 }
62 });
63 if (useMapGeometry) {
64 this.dataMin = dataMin;
65 this.dataMax = dataMax;
66 }
67 }
68 });
69
70 /**
71 * Override axis translation to make sure the aspect ratio is always kept
72 */
73 wrap(Axis.prototype, 'setAxisTranslation', function(proceed) {
74 var chart = this.chart,
75 mapRatio,
76 plotRatio = chart.plotWidth / chart.plotHeight,
77 adjustedAxisLength,
78 xAxis = chart.xAxis[0],
79 padAxis,
80 fixTo,
81 fixDiff,
82 preserveAspectRatio;
83
84
85 // Run the parent method
86 proceed.call(this);
87
88 // Check for map-like series
89 if (this.coll === 'yAxis' && xAxis.transA !== undefined) {
90 each(this.series, function(series) {
91 if (series.preserveAspectRatio) {
92 preserveAspectRatio = true;
93 }
94 });
95 }
96
97 // On Y axis, handle both
98 if (preserveAspectRatio) {
99
100 // Use the same translation for both axes
101 this.transA = xAxis.transA = Math.min(this.transA, xAxis.transA);
102
103 mapRatio = plotRatio / ((xAxis.max - xAxis.min) / (this.max - this.min));
104
105 // What axis to pad to put the map in the middle
106 padAxis = mapRatio < 1 ? this : xAxis;
107
108 // Pad it
109 adjustedAxisLength = (padAxis.max - padAxis.min) * padAxis.transA;
110 padAxis.pixelPadding = padAxis.len - adjustedAxisLength;
111 padAxis.minPixelPadding = padAxis.pixelPadding / 2;
112
113 fixTo = padAxis.fixTo;
114 if (fixTo) {
115 fixDiff = fixTo[1] - padAxis.toValue(fixTo[0], true);
116 fixDiff *= padAxis.transA;
117 if (Math.abs(fixDiff) > padAxis.minPixelPadding || (padAxis.min === padAxis.dataMin && padAxis.max === padAxis.dataMax)) { // zooming out again, keep within restricted area
118 fixDiff = 0;
119 }
120 padAxis.minPixelPadding -= fixDiff;
121 }
122 }
123 });
124
125 /**
126 * Override Axis.render in order to delete the fixTo prop
127 */
128 wrap(Axis.prototype, 'render', function(proceed) {
129 proceed.call(this);
130 this.fixTo = null;
131 });
132
133 }(Highcharts));
134 (function(H) {
135 /**
136 * (c) 2010-2016 Torstein Honsi
137 *
138 * License: www.highcharts.com/license
139 */
140 'use strict';
141 var Axis = H.Axis,
142 Chart = H.Chart,
143 color = H.color,
144 ColorAxis,
145 each = H.each,
146 extend = H.extend,
147 isNumber = H.isNumber,
148 Legend = H.Legend,
149 LegendSymbolMixin = H.LegendSymbolMixin,
150 noop = H.noop,
151 merge = H.merge,
152 pick = H.pick,
153 wrap = H.wrap;
154
155 /**
156 * The ColorAxis object for inclusion in gradient legends
157 */
158 ColorAxis = H.ColorAxis = function() {
159 this.init.apply(this, arguments);
160 };
161 extend(ColorAxis.prototype, Axis.prototype);
162 extend(ColorAxis.prototype, {
163 defaultColorAxisOptions: {
164 lineWidth: 0,
165 minPadding: 0,
166 maxPadding: 0,
167 gridLineWidth: 1,
168 tickPixelInterval: 72,
169 startOnTick: true,
170 endOnTick: true,
171 offset: 0,
172 marker: {
173 animation: {
174 duration: 50
175 },
176 width: 0.01,
177
178 color: '#999999'
179
180 },
181 labels: {
182 overflow: 'justify'
183 },
184 minColor: '#e6ebf5',
185 maxColor: '#003399',
186 tickLength: 5,
187 showInLegend: true
188 },
189 init: function(chart, userOptions) {
190 var horiz = chart.options.legend.layout !== 'vertical',
191 options;
192
193 this.coll = 'colorAxis';
194
195 // Build the options
196 options = merge(this.defaultColorAxisOptions, {
197 side: horiz ? 2 : 1,
198 reversed: !horiz
199 }, userOptions, {
200 opposite: !horiz,
201 showEmpty: false,
202 title: null
203 });
204
205 Axis.prototype.init.call(this, chart, options);
206
207 // Base init() pushes it to the xAxis array, now pop it again
208 //chart[this.isXAxis ? 'xAxis' : 'yAxis'].pop();
209
210 // Prepare data classes
211 if (userOptions.dataClasses) {
212 this.initDataClasses(userOptions);
213 }
214 this.initStops(userOptions);
215
216 // Override original axis properties
217 this.horiz = horiz;
218 this.zoomEnabled = false;
219
220 // Add default values
221 this.defaultLegendLength = 200;
222 },
223
224 /*
225 * Return an intermediate color between two colors, according to pos where 0
226 * is the from color and 1 is the to color.
227 * NOTE: Changes here should be copied
228 * to the same function in drilldown.src.js and solid-gauge-src.js.
229 */
230 tweenColors: function(from, to, pos) {
231 // Check for has alpha, because rgba colors perform worse due to lack of
232 // support in WebKit.
233 var hasAlpha,
234 ret;
235
236 // Unsupported color, return to-color (#3920)
237 if (!to.rgba.length || !from.rgba.length) {
238 ret = to.input || 'none';
239
240 // Interpolate
241 } else {
242 from = from.rgba;
243 to = to.rgba;
244 hasAlpha = (to[3] !== 1 || from[3] !== 1);
245 ret = (hasAlpha ? 'rgba(' : 'rgb(') +
246 Math.round(to[0] + (from[0] - to[0]) * (1 - pos)) + ',' +
247 Math.round(to[1] + (from[1] - to[1]) * (1 - pos)) + ',' +
248 Math.round(to[2] + (from[2] - to[2]) * (1 - pos)) +
249 (hasAlpha ? (',' + (to[3] + (from[3] - to[3]) * (1 - pos))) : '') + ')';
250 }
251 return ret;
252 },
253
254 initDataClasses: function(userOptions) {
255 var axis = this,
256 chart = this.chart,
257 dataClasses,
258 colorCounter = 0,
259 colorCount = chart.options.chart.colorCount,
260 options = this.options,
261 len = userOptions.dataClasses.length;
262 this.dataClasses = dataClasses = [];
263 this.legendItems = [];
264
265 each(userOptions.dataClasses, function(dataClass, i) {
266 var colors;
267
268 dataClass = merge(dataClass);
269 dataClasses.push(dataClass);
270 if (!dataClass.color) {
271 if (options.dataClassColor === 'category') {
272
273 colors = chart.options.colors;
274 colorCount = colors.length;
275 dataClass.color = colors[colorCounter];
276
277 dataClass.colorIndex = colorCounter;
278
279 // increase and loop back to zero
280 colorCounter++;
281 if (colorCounter === colorCount) {
282 colorCounter = 0;
283 }
284 } else {
285 dataClass.color = axis.tweenColors(
286 color(options.minColor),
287 color(options.maxColor),
288 len < 2 ? 0.5 : i / (len - 1) // #3219
289 );
290 }
291 }
292 });
293 },
294
295 initStops: function(userOptions) {
296 this.stops = userOptions.stops || [
297 [0, this.options.minColor],
298 [1, this.options.maxColor]
299 ];
300 each(this.stops, function(stop) {
301 stop.color = color(stop[1]);
302 });
303 },
304
305 /**
306 * Extend the setOptions method to process extreme colors and color
307 * stops.
308 */
309 setOptions: function(userOptions) {
310 Axis.prototype.setOptions.call(this, userOptions);
311
312 this.options.crosshair = this.options.marker;
313 },
314
315 setAxisSize: function() {
316 var symbol = this.legendSymbol,
317 chart = this.chart,
318 legendOptions = chart.options.legend || {},
319 x,
320 y,
321 width,
322 height;
323
324 if (symbol) {
325 this.left = x = symbol.attr('x');
326 this.top = y = symbol.attr('y');
327 this.width = width = symbol.attr('width');
328 this.height = height = symbol.attr('height');
329 this.right = chart.chartWidth - x - width;
330 this.bottom = chart.chartHeight - y - height;
331
332 this.len = this.horiz ? width : height;
333 this.pos = this.horiz ? x : y;
334 } else {
335 // Fake length for disabled legend to avoid tick issues and such (#5205)
336 this.len = (this.horiz ? legendOptions.symbolWidth : legendOptions.symbolHeight) || this.defaultLegendLength;
337 }
338 },
339
340 /**
341 * Translate from a value to a color
342 */
343 toColor: function(value, point) {
344 var pos,
345 stops = this.stops,
346 from,
347 to,
348 color,
349 dataClasses = this.dataClasses,
350 dataClass,
351 i;
352
353 if (dataClasses) {
354 i = dataClasses.length;
355 while (i--) {
356 dataClass = dataClasses[i];
357 from = dataClass.from;
358 to = dataClass.to;
359 if ((from === undefined || value >= from) && (to === undefined || value <= to)) {
360 color = dataClass.color;
361 if (point) {
362 point.dataClass = i;
363 point.colorIndex = dataClass.colorIndex;
364 }
365 break;
366 }
367 }
368
369 } else {
370
371 if (this.isLog) {
372 value = this.val2lin(value);
373 }
374 pos = 1 - ((this.max - value) / ((this.max - this.min) || 1));
375 i = stops.length;
376 while (i--) {
377 if (pos > stops[i][0]) {
378 break;
379 }
380 }
381 from = stops[i] || stops[i + 1];
382 to = stops[i + 1] || from;
383
384 // The position within the gradient
385 pos = 1 - (to[0] - pos) / ((to[0] - from[0]) || 1);
386
387 color = this.tweenColors(
388 from.color,
389 to.color,
390 pos
391 );
392 }
393 return color;
394 },
395
396 /**
397 * Override the getOffset method to add the whole axis groups inside the legend.
398 */
399 getOffset: function() {
400 var group = this.legendGroup,
401 sideOffset = this.chart.axisOffset[this.side];
402
403 if (group) {
404
405 // Hook for the getOffset method to add groups to this parent group
406 this.axisParent = group;
407
408 // Call the base
409 Axis.prototype.getOffset.call(this);
410
411 // First time only
412 if (!this.added) {
413
414 this.added = true;
415
416 this.labelLeft = 0;
417 this.labelRight = this.width;
418 }
419 // Reset it to avoid color axis reserving space
420 this.chart.axisOffset[this.side] = sideOffset;
421 }
422 },
423
424 /**
425 * Create the color gradient
426 */
427 setLegendColor: function() {
428 var grad,
429 horiz = this.horiz,
430 options = this.options,
431 reversed = this.reversed,
432 one = reversed ? 1 : 0,
433 zero = reversed ? 0 : 1;
434
435 grad = horiz ? [one, 0, zero, 0] : [0, zero, 0, one]; // #3190
436 this.legendColor = {
437 linearGradient: {
438 x1: grad[0],
439 y1: grad[1],
440 x2: grad[2],
441 y2: grad[3]
442 },
443 stops: options.stops || [
444 [0, options.minColor],
445 [1, options.maxColor]
446 ]
447 };
448 },
449
450 /**
451 * The color axis appears inside the legend and has its own legend symbol
452 */
453 drawLegendSymbol: function(legend, item) {
454 var padding = legend.padding,
455 legendOptions = legend.options,
456 horiz = this.horiz,
457 width = pick(legendOptions.symbolWidth, horiz ? this.defaultLegendLength : 12),
458 height = pick(legendOptions.symbolHeight, horiz ? 12 : this.defaultLegendLength),
459 labelPadding = pick(legendOptions.labelPadding, horiz ? 16 : 30),
460 itemDistance = pick(legendOptions.itemDistance, 10);
461
462 this.setLegendColor();
463
464 // Create the gradient
465 item.legendSymbol = this.chart.renderer.rect(
466 0,
467 legend.baseline - 11,
468 width,
469 height
470 ).attr({
471 zIndex: 1
472 }).add(item.legendGroup);
473
474 // Set how much space this legend item takes up
475 this.legendItemWidth = width + padding + (horiz ? itemDistance : labelPadding);
476 this.legendItemHeight = height + padding + (horiz ? labelPadding : 0);
477 },
478 /**
479 * Fool the legend
480 */
481 setState: noop,
482 visible: true,
483 setVisible: noop,
484 getSeriesExtremes: function() {
485 var series;
486 if (this.series.length) {
487 series = this.series[0];
488 this.dataMin = series.valueMin;
489 this.dataMax = series.valueMax;
490 }
491 },
492 drawCrosshair: function(e, point) {
493 var plotX = point && point.plotX,
494 plotY = point && point.plotY,
495 crossPos,
496 axisPos = this.pos,
497 axisLen = this.len;
498
499 if (point) {
500 crossPos = this.toPixels(point[point.series.colorKey]);
501 if (crossPos < axisPos) {
502 crossPos = axisPos - 2;
503 } else if (crossPos > axisPos + axisLen) {
504 crossPos = axisPos + axisLen + 2;
505 }
506
507 point.plotX = crossPos;
508 point.plotY = this.len - crossPos;
509 Axis.prototype.drawCrosshair.call(this, e, point);
510 point.plotX = plotX;
511 point.plotY = plotY;
512
513 if (this.cross) {
514 this.cross
515 .addClass('highcharts-coloraxis-marker')
516 .add(this.legendGroup);
517
518
519 this.cross.attr({
520 fill: this.crosshair.color
521 });
522
523
524 }
525 }
526 },
527 getPlotLinePath: function(a, b, c, d, pos) {
528 return isNumber(pos) ? // crosshairs only // #3969 pos can be 0 !!
529 (this.horiz ? ['M', pos - 4, this.top - 6, 'L', pos + 4, this.top - 6, pos, this.top, 'Z'] : ['M', this.left, pos, 'L', this.left - 6, pos + 6, this.left - 6, pos - 6, 'Z']) :
530 Axis.prototype.getPlotLinePath.call(this, a, b, c, d);
531 },
532
533 update: function(newOptions, redraw) {
534 var chart = this.chart,
535 legend = chart.legend;
536
537 each(this.series, function(series) {
538 series.isDirtyData = true; // Needed for Axis.update when choropleth colors change
539 });
540
541 // When updating data classes, destroy old items and make sure new ones are created (#3207)
542 if (newOptions.dataClasses && legend.allItems) {
543 each(legend.allItems, function(item) {
544 if (item.isDataClass) {
545 item.legendGroup.destroy();
546 }
547 });
548 chart.isDirtyLegend = true;
549 }
550
551 // Keep the options structure updated for export. Unlike xAxis and yAxis, the colorAxis is
552 // not an array. (#3207)
553 chart.options[this.coll] = merge(this.userOptions, newOptions);
554
555 Axis.prototype.update.call(this, newOptions, redraw);
556 if (this.legendItem) {
557 this.setLegendColor();
558 legend.colorizeItem(this, true);
559 }
560 },
561
562 /**
563 * Get the legend item symbols for data classes
564 */
565 getDataClassLegendSymbols: function() {
566 var axis = this,
567 chart = this.chart,
568 legendItems = this.legendItems,
569 legendOptions = chart.options.legend,
570 valueDecimals = legendOptions.valueDecimals,
571 valueSuffix = legendOptions.valueSuffix || '',
572 name;
573
574 if (!legendItems.length) {
575 each(this.dataClasses, function(dataClass, i) {
576 var vis = true,
577 from = dataClass.from,
578 to = dataClass.to;
579
580 // Assemble the default name. This can be overridden by legend.options.labelFormatter
581 name = '';
582 if (from === undefined) {
583 name = '< ';
584 } else if (to === undefined) {
585 name = '> ';
586 }
587 if (from !== undefined) {
588 name += H.numberFormat(from, valueDecimals) + valueSuffix;
589 }
590 if (from !== undefined && to !== undefined) {
591 name += ' - ';
592 }
593 if (to !== undefined) {
594 name += H.numberFormat(to, valueDecimals) + valueSuffix;
595 }
596 // Add a mock object to the legend items
597 legendItems.push(extend({
598 chart: chart,
599 name: name,
600 options: {},
601 drawLegendSymbol: LegendSymbolMixin.drawRectangle,
602 visible: true,
603 setState: noop,
604 isDataClass: true,
605 setVisible: function() {
606 vis = this.visible = !vis;
607 each(axis.series, function(series) {
608 each(series.points, function(point) {
609 if (point.dataClass === i) {
610 point.setVisible(vis);
611 }
612 });
613 });
614
615 chart.legend.colorizeItem(this, vis);
616 }
617 }, dataClass));
618 });
619 }
620 return legendItems;
621 },
622 name: '' // Prevents 'undefined' in legend in IE8
623 });
624
625 /**
626 * Handle animation of the color attributes directly
627 */
628 each(['fill', 'stroke'], function(prop) {
629 H.Fx.prototype[prop + 'Setter'] = function() {
630 this.elem.attr(prop, ColorAxis.prototype.tweenColors(color(this.start), color(this.end), this.pos));
631 };
632 });
633
634 /**
635 * Extend the chart getAxes method to also get the color axis
636 */
637 wrap(Chart.prototype, 'getAxes', function(proceed) {
638
639 var options = this.options,
640 colorAxisOptions = options.colorAxis;
641
642 proceed.call(this);
643
644 this.colorAxis = [];
645 if (colorAxisOptions) {
646 new ColorAxis(this, colorAxisOptions); // eslint-disable-line no-new
647 }
648 });
649
650
651 /**
652 * Wrap the legend getAllItems method to add the color axis. This also removes the
653 * axis' own series to prevent them from showing up individually.
654 */
655 wrap(Legend.prototype, 'getAllItems', function(proceed) {
656 var allItems = [],
657 colorAxis = this.chart.colorAxis[0];
658
659 if (colorAxis && colorAxis.options) {
660 if (colorAxis.options.showInLegend) {
661 // Data classes
662 if (colorAxis.options.dataClasses) {
663 allItems = allItems.concat(colorAxis.getDataClassLegendSymbols());
664 // Gradient legend
665 } else {
666 // Add this axis on top
667 allItems.push(colorAxis);
668 }
669 }
670
671 // Don't add the color axis' series
672 each(colorAxis.series, function(series) {
673 series.options.showInLegend = false;
674 });
675 }
676
677 return allItems.concat(proceed.call(this));
678 });
679
680 wrap(Legend.prototype, 'colorizeItem', function(proceed, item, visible) {
681 proceed.call(this, item, visible);
682 if (visible && item.legendColor) {
683 item.legendSymbol.attr({
684 fill: item.legendColor
685 });
686 }
687 });
688
689 }(Highcharts));
690 (function(H) {
691 /**
692 * (c) 2010-2016 Torstein Honsi
693 *
694 * License: www.highcharts.com/license
695 */
696 'use strict';
697 var defined = H.defined,
698 each = H.each,
699 noop = H.noop,
700 seriesTypes = H.seriesTypes;
701
702 /**
703 * Mixin for maps and heatmaps
704 */
705 H.colorPointMixin = {
706 /**
707 * Color points have a value option that determines whether or not it is a null point
708 */
709 isValid: function() {
710 return this.value !== null;
711 },
712
713 /**
714 * Set the visibility of a single point
715 */
716 setVisible: function(vis) {
717 var point = this,
718 method = vis ? 'show' : 'hide';
719
720 // Show and hide associated elements
721 each(['graphic', 'dataLabel'], function(key) {
722 if (point[key]) {
723 point[key][method]();
724 }
725 });
726 }
727 };
728
729 H.colorSeriesMixin = {
730 pointArrayMap: ['value'],
731 axisTypes: ['xAxis', 'yAxis', 'colorAxis'],
732 optionalAxis: 'colorAxis',
733 trackerGroups: ['group', 'markerGroup', 'dataLabelsGroup'],
734 getSymbol: noop,
735 parallelArrays: ['x', 'y', 'value'],
736 colorKey: 'value',
737
738
739 pointAttribs: seriesTypes.column.prototype.pointAttribs,
740
741
742 /**
743 * In choropleth maps, the color is a result of the value, so this needs translation too
744 */
745 translateColors: function() {
746 var series = this,
747 nullColor = this.options.nullColor,
748 colorAxis = this.colorAxis,
749 colorKey = this.colorKey;
750
751 each(this.data, function(point) {
752 var value = point[colorKey],
753 color;
754
755 color = point.options.color ||
756 (point.isNull ? nullColor : (colorAxis && value !== undefined) ? colorAxis.toColor(value, point) : point.color || series.color);
757
758 if (color) {
759 point.color = color;
760 }
761 });
762 },
763
764 /**
765 * Get the color attibutes to apply on the graphic
766 */
767 colorAttribs: function(point) {
768 var ret = {};
769 if (defined(point.color)) {
770 ret[this.colorProp || 'fill'] = point.color;
771 }
772 return ret;
773 }
774 };
775
776 }(Highcharts));
777 (function(H) {
778 /**
779 * (c) 2010-2016 Torstein Honsi
780 *
781 * License: www.highcharts.com/license
782 */
783 'use strict';
784 var addEvent = H.addEvent,
785 Chart = H.Chart,
786 doc = H.doc,
787 each = H.each,
788 extend = H.extend,
789 merge = H.merge,
790 pick = H.pick,
791 wrap = H.wrap;
792
793 function stopEvent(e) {
794 if (e) {
795 if (e.preventDefault) {
796 e.preventDefault();
797 }
798 if (e.stopPropagation) {
799 e.stopPropagation();
800 }
801 e.cancelBubble = true;
802 }
803 }
804
805 // Add events to the Chart object itself
806 extend(Chart.prototype, {
807 renderMapNavigation: function() {
808 var chart = this,
809 options = this.options.mapNavigation,
810 buttons = options.buttons,
811 n,
812 button,
813 buttonOptions,
814 attr,
815 states,
816 hoverStates,
817 selectStates,
818 outerHandler = function(e) {
819 this.handler.call(chart, e);
820 stopEvent(e); // Stop default click event (#4444)
821 };
822
823 if (pick(options.enableButtons, options.enabled) && !chart.renderer.forExport) {
824 chart.mapNavButtons = [];
825 for (n in buttons) {
826 if (buttons.hasOwnProperty(n)) {
827 buttonOptions = merge(options.buttonOptions, buttons[n]);
828
829
830 // Presentational
831 attr = buttonOptions.theme;
832 attr.style = merge(buttonOptions.theme.style, buttonOptions.style); // #3203
833 states = attr.states;
834 hoverStates = states && states.hover;
835 selectStates = states && states.select;
836
837
838 button = chart.renderer.button(
839 buttonOptions.text,
840 0,
841 0,
842 outerHandler,
843 attr,
844 hoverStates,
845 selectStates,
846 0,
847 n === 'zoomIn' ? 'topbutton' : 'bottombutton'
848 )
849 .addClass('highcharts-map-navigation')
850 .attr({
851 width: buttonOptions.width,
852 height: buttonOptions.height,
853 title: chart.options.lang[n],
854 padding: buttonOptions.padding,
855 zIndex: 5
856 })
857 .add();
858 button.handler = buttonOptions.onclick;
859 button.align(extend(buttonOptions, {
860 width: button.width,
861 height: 2 * button.height
862 }), null, buttonOptions.alignTo);
863 addEvent(button.element, 'dblclick', stopEvent); // Stop double click event (#4444)
864 chart.mapNavButtons.push(button);
865 }
866 }
867 }
868 },
869
870 /**
871 * Fit an inner box to an outer. If the inner box overflows left or right, align it to the sides of the
872 * outer. If it overflows both sides, fit it within the outer. This is a pattern that occurs more places
873 * in Highcharts, perhaps it should be elevated to a common utility function.
874 */
875 fitToBox: function(inner, outer) {
876 each([
877 ['x', 'width'],
878 ['y', 'height']
879 ], function(dim) {
880 var pos = dim[0],
881 size = dim[1];
882
883 if (inner[pos] + inner[size] > outer[pos] + outer[size]) { // right overflow
884 if (inner[size] > outer[size]) { // the general size is greater, fit fully to outer
885 inner[size] = outer[size];
886 inner[pos] = outer[pos];
887 } else { // align right
888 inner[pos] = outer[pos] + outer[size] - inner[size];
889 }
890 }
891 if (inner[size] > outer[size]) {
892 inner[size] = outer[size];
893 }
894 if (inner[pos] < outer[pos]) {
895 inner[pos] = outer[pos];
896 }
897 });
898
899
900 return inner;
901 },
902
903 /**
904 * Zoom the map in or out by a certain amount. Less than 1 zooms in, greater than 1 zooms out.
905 */
906 mapZoom: function(howMuch, centerXArg, centerYArg, mouseX, mouseY) {
907 /*if (this.isMapZooming) {
908 this.mapZoomQueue = arguments;
909 return;
910 }*/
911
912 var chart = this,
913 xAxis = chart.xAxis[0],
914 xRange = xAxis.max - xAxis.min,
915 centerX = pick(centerXArg, xAxis.min + xRange / 2),
916 newXRange = xRange * howMuch,
917 yAxis = chart.yAxis[0],
918 yRange = yAxis.max - yAxis.min,
919 centerY = pick(centerYArg, yAxis.min + yRange / 2),
920 newYRange = yRange * howMuch,
921 fixToX = mouseX ? ((mouseX - xAxis.pos) / xAxis.len) : 0.5,
922 fixToY = mouseY ? ((mouseY - yAxis.pos) / yAxis.len) : 0.5,
923 newXMin = centerX - newXRange * fixToX,
924 newYMin = centerY - newYRange * fixToY,
925 newExt = chart.fitToBox({
926 x: newXMin,
927 y: newYMin,
928 width: newXRange,
929 height: newYRange
930 }, {
931 x: xAxis.dataMin,
932 y: yAxis.dataMin,
933 width: xAxis.dataMax - xAxis.dataMin,
934 height: yAxis.dataMax - yAxis.dataMin
935 }),
936 zoomOut = newExt.x <= xAxis.dataMin &&
937 newExt.width >= xAxis.dataMax - xAxis.dataMin &&
938 newExt.y <= yAxis.dataMin &&
939 newExt.height >= yAxis.dataMax - yAxis.dataMin;
940
941 // When mousewheel zooming, fix the point under the mouse
942 if (mouseX) {
943 xAxis.fixTo = [mouseX - xAxis.pos, centerXArg];
944 }
945 if (mouseY) {
946 yAxis.fixTo = [mouseY - yAxis.pos, centerYArg];
947 }
948
949 // Zoom
950 if (howMuch !== undefined && !zoomOut) {
951 xAxis.setExtremes(newExt.x, newExt.x + newExt.width, false);
952 yAxis.setExtremes(newExt.y, newExt.y + newExt.height, false);
953
954 // Reset zoom
955 } else {
956 xAxis.setExtremes(undefined, undefined, false);
957 yAxis.setExtremes(undefined, undefined, false);
958 }
959
960 // Prevent zooming until this one is finished animating
961 /*chart.holdMapZoom = true;
962 setTimeout(function () {
963 chart.holdMapZoom = false;
964 }, 200);*/
965 /*delay = animation ? animation.duration || 500 : 0;
966 if (delay) {
967 chart.isMapZooming = true;
968 setTimeout(function () {
969 chart.isMapZooming = false;
970 if (chart.mapZoomQueue) {
971 chart.mapZoom.apply(chart, chart.mapZoomQueue);
972 }
973 chart.mapZoomQueue = null;
974 }, delay);
975 }*/
976
977 chart.redraw();
978 }
979 });
980
981 /**
982 * Extend the Chart.render method to add zooming and panning
983 */
984 wrap(Chart.prototype, 'render', function(proceed) {
985 var chart = this,
986 mapNavigation = chart.options.mapNavigation;
987
988 // Render the plus and minus buttons. Doing this before the shapes makes getBBox much quicker, at least in Chrome.
989 chart.renderMapNavigation();
990
991 proceed.call(chart);
992
993 // Add the double click event
994 if (pick(mapNavigation.enableDoubleClickZoom, mapNavigation.enabled) || mapNavigation.enableDoubleClickZoomTo) {
995 addEvent(chart.container, 'dblclick', function(e) {
996 chart.pointer.onContainerDblClick(e);
997 });
998 }
999
1000 // Add the mousewheel event
1001 if (pick(mapNavigation.enableMouseWheelZoom, mapNavigation.enabled)) {
1002 addEvent(chart.container, doc.onmousewheel === undefined ? 'DOMMouseScroll' : 'mousewheel', function(e) {
1003 chart.pointer.onContainerMouseWheel(e);
1004 stopEvent(e); // Issue #5011, returning false from non-jQuery event does not prevent default
1005 return false;
1006 });
1007 }
1008 });
1009
1010 }(Highcharts));
1011 (function(H) {
1012 /**
1013 * (c) 2010-2016 Torstein Honsi
1014 *
1015 * License: www.highcharts.com/license
1016 */
1017 'use strict';
1018 var extend = H.extend,
1019 pick = H.pick,
1020 Pointer = H.Pointer,
1021 wrap = H.wrap;
1022
1023 // Extend the Pointer
1024 extend(Pointer.prototype, {
1025
1026 /**
1027 * The event handler for the doubleclick event
1028 */
1029 onContainerDblClick: function(e) {
1030 var chart = this.chart;
1031
1032 e = this.normalize(e);
1033
1034 if (chart.options.mapNavigation.enableDoubleClickZoomTo) {
1035 if (chart.pointer.inClass(e.target, 'highcharts-tracker') && chart.hoverPoint) {
1036 chart.hoverPoint.zoomTo();
1037 }
1038 } else if (chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) {
1039 chart.mapZoom(
1040 0.5,
1041 chart.xAxis[0].toValue(e.chartX),
1042 chart.yAxis[0].toValue(e.chartY),
1043 e.chartX,
1044 e.chartY
1045 );
1046 }
1047 },
1048
1049 /**
1050 * The event handler for the mouse scroll event
1051 */
1052 onContainerMouseWheel: function(e) {
1053 var chart = this.chart,
1054 delta;
1055
1056 e = this.normalize(e);
1057
1058 // Firefox uses e.detail, WebKit and IE uses wheelDelta
1059 delta = e.detail || -(e.wheelDelta / 120);
1060 if (chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) {
1061 chart.mapZoom(
1062 Math.pow(chart.options.mapNavigation.mouseWheelSensitivity, delta),
1063 chart.xAxis[0].toValue(e.chartX),
1064 chart.yAxis[0].toValue(e.chartY),
1065 e.chartX,
1066 e.chartY
1067 );
1068 }
1069 }
1070 });
1071
1072 // Implement the pinchType option
1073 wrap(Pointer.prototype, 'zoomOption', function(proceed) {
1074
1075 var mapNavigation = this.chart.options.mapNavigation;
1076
1077 proceed.apply(this, [].slice.call(arguments, 1));
1078
1079 // Pinch status
1080 if (pick(mapNavigation.enableTouchZoom, mapNavigation.enabled)) {
1081 this.pinchX = this.pinchHor = this.pinchY = this.pinchVert = this.hasZoom = true;
1082 }
1083 });
1084
1085 // Extend the pinchTranslate method to preserve fixed ratio when zooming
1086 wrap(Pointer.prototype, 'pinchTranslate', function(proceed, pinchDown, touches, transform, selectionMarker, clip, lastValidTouch) {
1087 var xBigger;
1088 proceed.call(this, pinchDown, touches, transform, selectionMarker, clip, lastValidTouch);
1089
1090 // Keep ratio
1091 if (this.chart.options.chart.type === 'map' && this.hasZoom) {
1092 xBigger = transform.scaleX > transform.scaleY;
1093 this.pinchTranslateDirection(!xBigger,
1094 pinchDown,
1095 touches,
1096 transform,
1097 selectionMarker,
1098 clip,
1099 lastValidTouch,
1100 xBigger ? transform.scaleX : transform.scaleY
1101 );
1102 }
1103 });
1104
1105 }(Highcharts));
1106 (function(H) {
1107 /**
1108 * (c) 2010-2016 Torstein Honsi
1109 *
1110 * License: www.highcharts.com/license
1111 */
1112 'use strict';
1113 var color = H.color,
1114 ColorAxis = H.ColorAxis,
1115 colorPointMixin = H.colorPointMixin,
1116 colorSeriesMixin = H.colorSeriesMixin,
1117 doc = H.doc,
1118 each = H.each,
1119 extend = H.extend,
1120 isNumber = H.isNumber,
1121 LegendSymbolMixin = H.LegendSymbolMixin,
1122 map = H.map,
1123 merge = H.merge,
1124 noop = H.noop,
1125 pick = H.pick,
1126 isArray = H.isArray,
1127 Point = H.Point,
1128 Series = H.Series,
1129 seriesType = H.seriesType,
1130 seriesTypes = H.seriesTypes,
1131 splat = H.splat;
1132
1133 // The vector-effect attribute is not supported in IE <= 11 (at least), so we need
1134 // diffent logic (#3218)
1135 var supportsVectorEffect = doc.documentElement.style.vectorEffect !== undefined;
1136
1137
1138 /**
1139 * The MapAreaPoint object
1140 */
1141 /**
1142 * Add the map series type
1143 */
1144 seriesType('map', 'scatter', {
1145 allAreas: true,
1146
1147 animation: false, // makes the complex shapes slow
1148 nullColor: '#f7f7f7',
1149 borderColor: '#cccccc',
1150 borderWidth: 1,
1151 marker: null,
1152 stickyTracking: false,
1153 joinBy: 'hc-key',
1154 dataLabels: {
1155 formatter: function() { // #2945
1156 return this.point.value;
1157 },
1158 inside: true, // for the color
1159 verticalAlign: 'middle',
1160 crop: false,
1161 overflow: false,
1162 padding: 0
1163 },
1164 turboThreshold: 0,
1165 tooltip: {
1166 followPointer: true,
1167 pointFormat: '{point.name}: {point.value}<br/>'
1168 },
1169 states: {
1170 normal: {
1171 animation: true
1172 },
1173 hover: {
1174 brightness: 0.2,
1175 halo: null
1176 },
1177 select: {
1178 color: '#cccccc'
1179 }
1180 }
1181
1182 // Prototype members
1183 }, merge(colorSeriesMixin, {
1184 type: 'map',
1185 supportsDrilldown: true,
1186 getExtremesFromAll: true,
1187 useMapGeometry: true, // get axis extremes from paths, not values
1188 forceDL: true,
1189 searchPoint: noop,
1190 directTouch: true, // When tooltip is not shared, this series (and derivatives) requires direct touch/hover. KD-tree does not apply.
1191 preserveAspectRatio: true, // X axis and Y axis must have same translation slope
1192 pointArrayMap: ['value'],
1193 /**
1194 * Get the bounding box of all paths in the map combined.
1195 */
1196 getBox: function(paths) {
1197 var MAX_VALUE = Number.MAX_VALUE,
1198 maxX = -MAX_VALUE,
1199 minX = MAX_VALUE,
1200 maxY = -MAX_VALUE,
1201 minY = MAX_VALUE,
1202 minRange = MAX_VALUE,
1203 xAxis = this.xAxis,
1204 yAxis = this.yAxis,
1205 hasBox;
1206
1207 // Find the bounding box
1208 each(paths || [], function(point) {
1209
1210 if (point.path) {
1211 if (typeof point.path === 'string') {
1212 point.path = H.splitPath(point.path);
1213 }
1214
1215 var path = point.path || [],
1216 i = path.length,
1217 even = false, // while loop reads from the end
1218 pointMaxX = -MAX_VALUE,
1219 pointMinX = MAX_VALUE,
1220 pointMaxY = -MAX_VALUE,
1221 pointMinY = MAX_VALUE,
1222 properties = point.properties;
1223
1224 // The first time a map point is used, analyze its box
1225 if (!point._foundBox) {
1226 while (i--) {
1227 if (isNumber(path[i])) {
1228 if (even) { // even = x
1229 pointMaxX = Math.max(pointMaxX, path[i]);
1230 pointMinX = Math.min(pointMinX, path[i]);
1231 } else { // odd = Y
1232 pointMaxY = Math.max(pointMaxY, path[i]);
1233 pointMinY = Math.min(pointMinY, path[i]);
1234 }
1235 even = !even;
1236 }
1237 }
1238 // Cache point bounding box for use to position data labels, bubbles etc
1239 point._midX = pointMinX + (pointMaxX - pointMinX) *
1240 (point.middleX || (properties && properties['hc-middle-x']) || 0.5); // pick is slower and very marginally needed
1241 point._midY = pointMinY + (pointMaxY - pointMinY) *
1242 (point.middleY || (properties && properties['hc-middle-y']) || 0.5);
1243 point._maxX = pointMaxX;
1244 point._minX = pointMinX;
1245 point._maxY = pointMaxY;
1246 point._minY = pointMinY;
1247 point.labelrank = pick(point.labelrank, (pointMaxX - pointMinX) * (pointMaxY - pointMinY));
1248 point._foundBox = true;
1249 }
1250
1251 maxX = Math.max(maxX, point._maxX);
1252 minX = Math.min(minX, point._minX);
1253 maxY = Math.max(maxY, point._maxY);
1254 minY = Math.min(minY, point._minY);
1255 minRange = Math.min(point._maxX - point._minX, point._maxY - point._minY, minRange);
1256 hasBox = true;
1257 }
1258 });
1259
1260 // Set the box for the whole series
1261 if (hasBox) {
1262 this.minY = Math.min(minY, pick(this.minY, MAX_VALUE));
1263 this.maxY = Math.max(maxY, pick(this.maxY, -MAX_VALUE));
1264 this.minX = Math.min(minX, pick(this.minX, MAX_VALUE));
1265 this.maxX = Math.max(maxX, pick(this.maxX, -MAX_VALUE));
1266
1267 // If no minRange option is set, set the default minimum zooming range to 5 times the
1268 // size of the smallest element
1269 if (xAxis && xAxis.options.minRange === undefined) {
1270 xAxis.minRange = Math.min(5 * minRange, (this.maxX - this.minX) / 5, xAxis.minRange || MAX_VALUE);
1271 }
1272 if (yAxis && yAxis.options.minRange === undefined) {
1273 yAxis.minRange = Math.min(5 * minRange, (this.maxY - this.minY) / 5, yAxis.minRange || MAX_VALUE);
1274 }
1275 }
1276 },
1277
1278 getExtremes: function() {
1279 // Get the actual value extremes for colors
1280 Series.prototype.getExtremes.call(this, this.valueData);
1281
1282 // Recalculate box on updated data
1283 if (this.chart.hasRendered && this.isDirtyData) {
1284 this.getBox(this.options.data);
1285 }
1286
1287 this.valueMin = this.dataMin;
1288 this.valueMax = this.dataMax;
1289
1290 // Extremes for the mock Y axis
1291 this.dataMin = this.minY;
1292 this.dataMax = this.maxY;
1293 },
1294
1295 /**
1296 * Translate the path so that it automatically fits into the plot area box
1297 * @param {Object} path
1298 */
1299 translatePath: function(path) {
1300
1301 var series = this,
1302 even = false, // while loop reads from the end
1303 xAxis = series.xAxis,
1304 yAxis = series.yAxis,
1305 xMin = xAxis.min,
1306 xTransA = xAxis.transA,
1307 xMinPixelPadding = xAxis.minPixelPadding,
1308 yMin = yAxis.min,
1309 yTransA = yAxis.transA,
1310 yMinPixelPadding = yAxis.minPixelPadding,
1311 i,
1312 ret = []; // Preserve the original
1313
1314 // Do the translation
1315 if (path) {
1316 i = path.length;
1317 while (i--) {
1318 if (isNumber(path[i])) {
1319 ret[i] = even ?
1320 (path[i] - xMin) * xTransA + xMinPixelPadding :
1321 (path[i] - yMin) * yTransA + yMinPixelPadding;
1322 even = !even;
1323 } else {
1324 ret[i] = path[i];
1325 }
1326 }
1327 }
1328
1329 return ret;
1330 },
1331
1332 /**
1333 * Extend setData to join in mapData. If the allAreas option is true, all areas
1334 * from the mapData are used, and those that don't correspond to a data value
1335 * are given null values.
1336 */
1337 setData: function(data, redraw, animation, updatePoints) {
1338 var options = this.options,
1339 chartOptions = this.chart.options.chart,
1340 globalMapData = chartOptions && chartOptions.map,
1341 mapData = options.mapData,
1342 joinBy = options.joinBy,
1343 joinByNull = joinBy === null,
1344 pointArrayMap = options.keys || this.pointArrayMap,
1345 dataUsed = [],
1346 mapMap = {},
1347 mapPoint,
1348 transform,
1349 mapTransforms = this.chart.mapTransforms,
1350 props,
1351 i;
1352
1353 // Collect mapData from chart options if not defined on series
1354 if (!mapData && globalMapData) {
1355 mapData = typeof globalMapData === 'string' ? H.maps[globalMapData] : globalMapData;
1356 }
1357
1358 if (joinByNull) {
1359 joinBy = '_i';
1360 }
1361 joinBy = this.joinBy = splat(joinBy);
1362 if (!joinBy[1]) {
1363 joinBy[1] = joinBy[0];
1364 }
1365
1366 // Pick up numeric values, add index
1367 // Convert Array point definitions to objects using pointArrayMap
1368 if (data) {
1369 each(data, function(val, i) {
1370 var ix = 0;
1371 if (isNumber(val)) {
1372 data[i] = {
1373 value: val
1374 };
1375 } else if (isArray(val)) {
1376 data[i] = {};
1377 // Automatically copy first item to hc-key if there is an extra leading string
1378 if (!options.keys && val.length > pointArrayMap.length && typeof val[0] === 'string') {
1379 data[i]['hc-key'] = val[0];
1380 ++ix;
1381 }
1382 // Run through pointArrayMap and what's left of the point data array in parallel, copying over the values
1383 for (var j = 0; j < pointArrayMap.length; ++j, ++ix) {
1384 if (pointArrayMap[j]) {
1385 data[i][pointArrayMap[j]] = val[ix];
1386 }
1387 }
1388 }
1389 if (joinByNull) {
1390 data[i]._i = i;
1391 }
1392 });
1393 }
1394
1395 this.getBox(data);
1396
1397 // Pick up transform definitions for chart
1398 this.chart.mapTransforms = mapTransforms = chartOptions && chartOptions.mapTransforms || mapData && mapData['hc-transform'] || mapTransforms;
1399
1400 // Cache cos/sin of transform rotation angle
1401 if (mapTransforms) {
1402 for (transform in mapTransforms) {
1403 if (mapTransforms.hasOwnProperty(transform) && transform.rotation) {
1404 transform.cosAngle = Math.cos(transform.rotation);
1405 transform.sinAngle = Math.sin(transform.rotation);
1406 }
1407 }
1408 }
1409
1410 if (mapData) {
1411 if (mapData.type === 'FeatureCollection') {
1412 this.mapTitle = mapData.title;
1413 mapData = H.geojson(mapData, this.type, this);
1414 }
1415
1416 this.mapData = mapData;
1417 this.mapMap = {};
1418
1419 for (i = 0; i < mapData.length; i++) {
1420 mapPoint = mapData[i];
1421 props = mapPoint.properties;
1422
1423 mapPoint._i = i;
1424 // Copy the property over to root for faster access
1425 if (joinBy[0] && props && props[joinBy[0]]) {
1426 mapPoint[joinBy[0]] = props[joinBy[0]];
1427 }
1428 mapMap[mapPoint[joinBy[0]]] = mapPoint;
1429 }
1430 this.mapMap = mapMap;
1431
1432 // Registered the point codes that actually hold data
1433 if (data && joinBy[1]) {
1434 each(data, function(point) {
1435 if (mapMap[point[joinBy[1]]]) {
1436 dataUsed.push(mapMap[point[joinBy[1]]]);
1437 }
1438 });
1439 }
1440
1441 if (options.allAreas) {
1442 this.getBox(mapData);
1443 data = data || [];
1444
1445 // Registered the point codes that actually hold data
1446 if (joinBy[1]) {
1447 each(data, function(point) {
1448 dataUsed.push(point[joinBy[1]]);
1449 });
1450 }
1451
1452 // Add those map points that don't correspond to data, which will be drawn as null points
1453 dataUsed = '|' + map(dataUsed, function(point) {
1454 return point && point[joinBy[0]];
1455 }).join('|') + '|'; // String search is faster than array.indexOf
1456
1457 each(mapData, function(mapPoint) {
1458 if (!joinBy[0] || dataUsed.indexOf('|' + mapPoint[joinBy[0]] + '|') === -1) {
1459 data.push(merge(mapPoint, {
1460 value: null
1461 }));
1462 updatePoints = false; // #5050 - adding all areas causes the update optimization of setData to kick in, even though the point order has changed
1463 }
1464 });
1465 } else {
1466 this.getBox(dataUsed); // Issue #4784
1467 }
1468 }
1469 Series.prototype.setData.call(this, data, redraw, animation, updatePoints);
1470 },
1471
1472
1473 /**
1474 * No graph for the map series
1475 */
1476 drawGraph: noop,
1477
1478 /**
1479 * We need the points' bounding boxes in order to draw the data labels, so
1480 * we skip it now and call it from drawPoints instead.
1481 */
1482 drawDataLabels: noop,
1483
1484 /**
1485 * Allow a quick redraw by just translating the area group. Used for zooming and panning
1486 * in capable browsers.
1487 */
1488 doFullTranslate: function() {
1489 return this.isDirtyData || this.chart.isResizing || this.chart.renderer.isVML || !this.baseTrans;
1490 },
1491
1492 /**
1493 * Add the path option for data points. Find the max value for color calculation.
1494 */
1495 translate: function() {
1496 var series = this,
1497 xAxis = series.xAxis,
1498 yAxis = series.yAxis,
1499 doFullTranslate = series.doFullTranslate();
1500
1501 series.generatePoints();
1502
1503 each(series.data, function(point) {
1504
1505 // Record the middle point (loosely based on centroid), determined
1506 // by the middleX and middleY options.
1507 point.plotX = xAxis.toPixels(point._midX, true);
1508 point.plotY = yAxis.toPixels(point._midY, true);
1509
1510 if (doFullTranslate) {
1511
1512 point.shapeType = 'path';
1513 point.shapeArgs = {
1514 d: series.translatePath(point.path)
1515 };
1516 }
1517 });
1518
1519 series.translateColors();
1520 },
1521
1522 /**
1523 * Get presentational attributes
1524 */
1525 pointAttribs: function(point, state) {
1526 var attr = seriesTypes.column.prototype.pointAttribs.call(this, point, state);
1527
1528 // Prevent flickering whan called from setState
1529 if (point.isFading) {
1530 delete attr.fill;
1531 }
1532
1533 // If vector-effect is not supported, we set the stroke-width on the group element
1534 // and let all point graphics inherit. That way we don't have to iterate over all
1535 // points to update the stroke-width on zooming. TODO: Check unstyled
1536 if (supportsVectorEffect) {
1537 attr['vector-effect'] = 'non-scaling-stroke';
1538 } else {
1539 attr['stroke-width'] = 'inherit';
1540 }
1541
1542 return attr;
1543 },
1544
1545 /**
1546 * Use the drawPoints method of column, that is able to handle simple shapeArgs.
1547 * Extend it by assigning the tooltip position.
1548 */
1549 drawPoints: function() {
1550 var series = this,
1551 xAxis = series.xAxis,
1552 yAxis = series.yAxis,
1553 group = series.group,
1554 chart = series.chart,
1555 renderer = chart.renderer,
1556 scaleX,
1557 scaleY,
1558 translateX,
1559 translateY,
1560 baseTrans = this.baseTrans;
1561
1562 // Set a group that handles transform during zooming and panning in order to preserve clipping
1563 // on series.group
1564 if (!series.transformGroup) {
1565 series.transformGroup = renderer.g()
1566 .attr({
1567 scaleX: 1,
1568 scaleY: 1
1569 })
1570 .add(group);
1571 series.transformGroup.survive = true;
1572 }
1573
1574 // Draw the shapes again
1575 if (series.doFullTranslate()) {
1576
1577 // Individual point actions. TODO: Check unstyled.
1578
1579 if (chart.hasRendered) {
1580 each(series.points, function(point) {
1581
1582 // Restore state color on update/redraw (#3529)
1583 if (point.shapeArgs) {
1584 point.shapeArgs.fill = series.pointAttribs(point, point.state).fill;
1585 }
1586 });
1587 }
1588
1589
1590 // Draw them in transformGroup
1591 series.group = series.transformGroup;
1592 seriesTypes.column.prototype.drawPoints.apply(series);
1593 series.group = group; // Reset
1594
1595 // Add class names
1596 each(series.points, function(point) {
1597 if (point.graphic) {
1598 if (point.name) {
1599 point.graphic.addClass('highcharts-name-' + point.name.replace(/ /g, '-').toLowerCase());
1600 }
1601 if (point.properties && point.properties['hc-key']) {
1602 point.graphic.addClass('highcharts-key-' + point.properties['hc-key'].toLowerCase());
1603 }
1604 }
1605 });
1606
1607 // Set the base for later scale-zooming. The originX and originY properties are the
1608 // axis values in the plot area's upper left corner.
1609 this.baseTrans = {
1610 originX: xAxis.min - xAxis.minPixelPadding / xAxis.transA,
1611 originY: yAxis.min - yAxis.minPixelPadding / yAxis.transA + (yAxis.reversed ? 0 : yAxis.len / yAxis.transA),
1612 transAX: xAxis.transA,
1613 transAY: yAxis.transA
1614 };
1615
1616 // Reset transformation in case we're doing a full translate (#3789)
1617 this.transformGroup.animate({
1618 translateX: 0,
1619 translateY: 0,
1620 scaleX: 1,
1621 scaleY: 1
1622 });
1623
1624 // Just update the scale and transform for better performance
1625 } else {
1626 scaleX = xAxis.transA / baseTrans.transAX;
1627 scaleY = yAxis.transA / baseTrans.transAY;
1628 translateX = xAxis.toPixels(baseTrans.originX, true);
1629 translateY = yAxis.toPixels(baseTrans.originY, true);
1630
1631 // Handle rounding errors in normal view (#3789)
1632 if (scaleX > 0.99 && scaleX < 1.01 && scaleY > 0.99 && scaleY < 1.01) {
1633 scaleX = 1;
1634 scaleY = 1;
1635 translateX = Math.round(translateX);
1636 translateY = Math.round(translateY);
1637 }
1638
1639 this.transformGroup.animate({
1640 translateX: translateX,
1641 translateY: translateY,
1642 scaleX: scaleX,
1643 scaleY: scaleY
1644 });
1645
1646 }
1647
1648 // Set the stroke-width directly on the group element so the children inherit it. We need to use
1649 // setAttribute directly, because the stroke-widthSetter method expects a stroke color also to be
1650 // set.
1651 if (!supportsVectorEffect) {
1652 series.group.element.setAttribute(
1653 'stroke-width',
1654 series.options[
1655 (series.pointAttrToOptions && series.pointAttrToOptions['stroke-width']) || 'borderWidth'
1656 ] / (scaleX || 1)
1657 );
1658 }
1659
1660 this.drawMapDataLabels();
1661
1662
1663 },
1664
1665 /**
1666 * Draw the data labels. Special for maps is the time that the data labels are drawn (after points),
1667 * and the clipping of the dataLabelsGroup.
1668 */
1669 drawMapDataLabels: function() {
1670
1671 Series.prototype.drawDataLabels.call(this);
1672 if (this.dataLabelsGroup) {
1673 this.dataLabelsGroup.clip(this.chart.clipRect);
1674 }
1675 },
1676
1677 /**
1678 * Override render to throw in an async call in IE8. Otherwise it chokes on the US counties demo.
1679 */
1680 render: function() {
1681 var series = this,
1682 render = Series.prototype.render;
1683
1684 // Give IE8 some time to breathe.
1685 if (series.chart.renderer.isVML && series.data.length > 3000) {
1686 setTimeout(function() {
1687 render.call(series);
1688 });
1689 } else {
1690 render.call(series);
1691 }
1692 },
1693
1694 /**
1695 * The initial animation for the map series. By default, animation is disabled.
1696 * Animation of map shapes is not at all supported in VML browsers.
1697 */
1698 animate: function(init) {
1699 var chart = this.chart,
1700 animation = this.options.animation,
1701 group = this.group,
1702 xAxis = this.xAxis,
1703 yAxis = this.yAxis,
1704 left = xAxis.pos,
1705 top = yAxis.pos;
1706
1707 if (chart.renderer.isSVG) {
1708
1709 if (animation === true) {
1710 animation = {
1711 duration: 1000
1712 };
1713 }
1714
1715 // Initialize the animation
1716 if (init) {
1717
1718 // Scale down the group and place it in the center
1719 group.attr({
1720 translateX: left + xAxis.len / 2,
1721 translateY: top + yAxis.len / 2,
1722 scaleX: 0.001, // #1499
1723 scaleY: 0.001
1724 });
1725
1726 // Run the animation
1727 } else {
1728 group.animate({
1729 translateX: left,
1730 translateY: top,
1731 scaleX: 1,
1732 scaleY: 1
1733 }, animation);
1734
1735 // Delete this function to allow it only once
1736 this.animate = null;
1737 }
1738 }
1739 },
1740
1741 /**
1742 * Animate in the new series from the clicked point in the old series.
1743 * Depends on the drilldown.js module
1744 */
1745 animateDrilldown: function(init) {
1746 var toBox = this.chart.plotBox,
1747 level = this.chart.drilldownLevels[this.chart.drilldownLevels.length - 1],
1748 fromBox = level.bBox,
1749 animationOptions = this.chart.options.drilldown.animation,
1750 scale;
1751
1752 if (!init) {
1753
1754 scale = Math.min(fromBox.width / toBox.width, fromBox.height / toBox.height);
1755 level.shapeArgs = {
1756 scaleX: scale,
1757 scaleY: scale,
1758 translateX: fromBox.x,
1759 translateY: fromBox.y
1760 };
1761
1762 each(this.points, function(point) {
1763 if (point.graphic) {
1764 point.graphic
1765 .attr(level.shapeArgs)
1766 .animate({
1767 scaleX: 1,
1768 scaleY: 1,
1769 translateX: 0,
1770 translateY: 0
1771 }, animationOptions);
1772 }
1773 });
1774
1775 this.animate = null;
1776 }
1777
1778 },
1779
1780 drawLegendSymbol: LegendSymbolMixin.drawRectangle,
1781
1782 /**
1783 * When drilling up, pull out the individual point graphics from the lower series
1784 * and animate them into the origin point in the upper series.
1785 */
1786 animateDrillupFrom: function(level) {
1787 seriesTypes.column.prototype.animateDrillupFrom.call(this, level);
1788 },
1789
1790
1791 /**
1792 * When drilling up, keep the upper series invisible until the lower series has
1793 * moved into place
1794 */
1795 animateDrillupTo: function(init) {
1796 seriesTypes.column.prototype.animateDrillupTo.call(this, init);
1797 }
1798
1799 // Point class
1800 }), extend({
1801 /**
1802 * Extend the Point object to split paths
1803 */
1804 applyOptions: function(options, x) {
1805
1806 var point = Point.prototype.applyOptions.call(this, options, x),
1807 series = this.series,
1808 joinBy = series.joinBy,
1809 mapPoint;
1810
1811 if (series.mapData) {
1812 mapPoint = point[joinBy[1]] !== undefined && series.mapMap[point[joinBy[1]]];
1813 if (mapPoint) {
1814 // This applies only to bubbles
1815 if (series.xyFromShape) {
1816 point.x = mapPoint._midX;
1817 point.y = mapPoint._midY;
1818 }
1819 extend(point, mapPoint); // copy over properties
1820 } else {
1821 point.value = point.value || null;
1822 }
1823 }
1824
1825 return point;
1826 },
1827
1828 /**
1829 * Stop the fade-out
1830 */
1831 onMouseOver: function(e) {
1832 clearTimeout(this.colorInterval);
1833 if (this.value !== null) {
1834 Point.prototype.onMouseOver.call(this, e);
1835 } else { //#3401 Tooltip doesn't hide when hovering over null points
1836 this.series.onMouseOut(e);
1837 }
1838 },
1839
1840 // Todo: check unstyled
1841 /**
1842 * Custom animation for tweening out the colors. Animation reduces blinking when hovering
1843 * over islands and coast lines. We run a custom implementation of animation becuase we
1844 * need to be able to run this independently from other animations like zoom redraw. Also,
1845 * adding color animation to the adapters would introduce almost the same amount of code.
1846 */
1847 onMouseOut: function() {
1848 var point = this,
1849 start = +new Date(),
1850 normalColor = color(this.series.pointAttribs(point).fill),
1851 hoverColor = color(this.series.pointAttribs(point, 'hover').fill),
1852 animation = point.series.options.states.normal.animation,
1853 duration = animation && (animation.duration || 500);
1854
1855 if (duration && normalColor.rgba.length === 4 && hoverColor.rgba.length === 4 && point.state !== 'select') {
1856 clearTimeout(point.colorInterval);
1857 point.colorInterval = setInterval(function() {
1858 var pos = (new Date() - start) / duration,
1859 graphic = point.graphic;
1860 if (pos > 1) {
1861 pos = 1;
1862 }
1863 if (graphic) {
1864 graphic.attr('fill', ColorAxis.prototype.tweenColors.call(0, hoverColor, normalColor, pos));
1865 }
1866 if (pos >= 1) {
1867 clearTimeout(point.colorInterval);
1868 }
1869 }, 13);
1870 }
1871 point.isFading = true;
1872 Point.prototype.onMouseOut.call(point);
1873 point.isFading = null;
1874 },
1875
1876
1877 /**
1878 * Zoom the chart to view a specific area point
1879 */
1880 zoomTo: function() {
1881 var point = this,
1882 series = point.series;
1883
1884 series.xAxis.setExtremes(
1885 point._minX,
1886 point._maxX,
1887 false
1888 );
1889 series.yAxis.setExtremes(
1890 point._minY,
1891 point._maxY,
1892 false
1893 );
1894 series.chart.redraw();
1895 }
1896 }, colorPointMixin));
1897
1898 }(Highcharts));
1899 (function(H) {
1900 /**
1901 * (c) 2010-2016 Torstein Honsi
1902 *
1903 * License: www.highcharts.com/license
1904 */
1905 'use strict';
1906 var seriesType = H.seriesType,
1907 seriesTypes = H.seriesTypes;
1908
1909 // The mapline series type
1910 seriesType('mapline', 'map', {
1911
1912 lineWidth: 1,
1913 fillColor: 'none'
1914
1915 }, {
1916 type: 'mapline',
1917 colorProp: 'stroke',
1918
1919 pointAttrToOptions: {
1920 'stroke': 'color',
1921 'stroke-width': 'lineWidth'
1922 },
1923 /**
1924 * Get presentational attributes
1925 */
1926 pointAttribs: function(point, state) {
1927 var attr = seriesTypes.map.prototype.pointAttribs.call(this, point, state);
1928
1929 // The difference from a map series is that the stroke takes the point color
1930 attr.fill = this.options.fillColor;
1931
1932 return attr;
1933 },
1934
1935 drawLegendSymbol: seriesTypes.line.prototype.drawLegendSymbol
1936 });
1937
1938 }(Highcharts));
1939 (function(H) {
1940 /**
1941 * (c) 2010-2016 Torstein Honsi
1942 *
1943 * License: www.highcharts.com/license
1944 */
1945 'use strict';
1946 var merge = H.merge,
1947 Point = H.Point,
1948 seriesType = H.seriesType;
1949
1950 // The mappoint series type
1951 seriesType('mappoint', 'scatter', {
1952 dataLabels: {
1953 enabled: true,
1954 formatter: function() { // #2945
1955 return this.point.name;
1956 },
1957 crop: false,
1958 defer: false,
1959 overflow: false,
1960 style: {
1961 color: '#000000'
1962 }
1963 }
1964
1965 // Prototype members
1966 }, {
1967 type: 'mappoint',
1968 forceDL: true
1969
1970 // Point class
1971 }, {
1972 applyOptions: function(options, x) {
1973 var mergedOptions = options.lat !== undefined && options.lon !== undefined ? merge(options, this.series.chart.fromLatLonToPoint(options)) : options;
1974 return Point.prototype.applyOptions.call(this, mergedOptions, x);
1975 }
1976 });
1977
1978 }(Highcharts));
1979 (function(H) {
1980 /**
1981 * (c) 2010-2016 Torstein Honsi
1982 *
1983 * License: www.highcharts.com/license
1984 */
1985 'use strict';
1986 var merge = H.merge,
1987 Point = H.Point,
1988 seriesType = H.seriesType,
1989 seriesTypes = H.seriesTypes;
1990
1991 // The mapbubble series type
1992 if (seriesTypes.bubble) {
1993
1994 seriesType('mapbubble', 'bubble', {
1995 animationLimit: 500,
1996 tooltip: {
1997 pointFormat: '{point.name}: {point.z}'
1998 }
1999
2000 // Prototype members
2001 }, {
2002 xyFromShape: true,
2003 type: 'mapbubble',
2004 pointArrayMap: ['z'], // If one single value is passed, it is interpreted as z
2005 /**
2006 * Return the map area identified by the dataJoinBy option
2007 */
2008 getMapData: seriesTypes.map.prototype.getMapData,
2009 getBox: seriesTypes.map.prototype.getBox,
2010 setData: seriesTypes.map.prototype.setData
2011
2012 // Point class
2013 }, {
2014 applyOptions: function(options, x) {
2015 var point;
2016 if (options && options.lat !== undefined && options.lon !== undefined) {
2017 point = Point.prototype.applyOptions.call(
2018 this,
2019 merge(options, this.series.chart.fromLatLonToPoint(options)),
2020 x
2021 );
2022 } else {
2023 point = seriesTypes.map.prototype.pointClass.prototype.applyOptions.call(this, options, x);
2024 }
2025 return point;
2026 },
2027 ttBelow: false
2028 });
2029 }
2030
2031 }(Highcharts));
2032 (function(H) {
2033 /**
2034 * (c) 2010-2016 Torstein Honsi
2035 *
2036 * License: www.highcharts.com/license
2037 */
2038 'use strict';
2039 var colorPointMixin = H.colorPointMixin,
2040 colorSeriesMixin = H.colorSeriesMixin,
2041 each = H.each,
2042 LegendSymbolMixin = H.LegendSymbolMixin,
2043 merge = H.merge,
2044 noop = H.noop,
2045 pick = H.pick,
2046 Series = H.Series,
2047 seriesType = H.seriesType,
2048 seriesTypes = H.seriesTypes;
2049
2050 // The Heatmap series type
2051 seriesType('heatmap', 'scatter', {
2052 animation: false,
2053 borderWidth: 0,
2054
2055 nullColor: '#f7f7f7',
2056
2057 dataLabels: {
2058 formatter: function() { // #2945
2059 return this.point.value;
2060 },
2061 inside: true,
2062 verticalAlign: 'middle',
2063 crop: false,
2064 overflow: false,
2065 padding: 0 // #3837
2066 },
2067 marker: null,
2068 pointRange: null, // dynamically set to colsize by default
2069 tooltip: {
2070 pointFormat: '{point.x}, {point.y}: {point.value}<br/>'
2071 },
2072 states: {
2073 normal: {
2074 animation: true
2075 },
2076 hover: {
2077 halo: false, // #3406, halo is not required on heatmaps
2078 brightness: 0.2
2079 }
2080 }
2081 }, merge(colorSeriesMixin, {
2082 pointArrayMap: ['y', 'value'],
2083 hasPointSpecificOptions: true,
2084 supportsDrilldown: true,
2085 getExtremesFromAll: true,
2086 directTouch: true,
2087
2088 /**
2089 * Override the init method to add point ranges on both axes.
2090 */
2091 init: function() {
2092 var options;
2093 seriesTypes.scatter.prototype.init.apply(this, arguments);
2094
2095 options = this.options;
2096 options.pointRange = pick(options.pointRange, options.colsize || 1); // #3758, prevent resetting in setData
2097 this.yAxis.axisPointRange = options.rowsize || 1; // general point range
2098 },
2099 translate: function() {
2100 var series = this,
2101 options = series.options,
2102 xAxis = series.xAxis,
2103 yAxis = series.yAxis,
2104 between = function(x, a, b) {
2105 return Math.min(Math.max(a, x), b);
2106 };
2107
2108 series.generatePoints();
2109
2110 each(series.points, function(point) {
2111 var xPad = (options.colsize || 1) / 2,
2112 yPad = (options.rowsize || 1) / 2,
2113 x1 = between(Math.round(xAxis.len - xAxis.translate(point.x - xPad, 0, 1, 0, 1)), -xAxis.len, 2 * xAxis.len),
2114 x2 = between(Math.round(xAxis.len - xAxis.translate(point.x + xPad, 0, 1, 0, 1)), -xAxis.len, 2 * xAxis.len),
2115 y1 = between(Math.round(yAxis.translate(point.y - yPad, 0, 1, 0, 1)), -yAxis.len, 2 * yAxis.len),
2116 y2 = between(Math.round(yAxis.translate(point.y + yPad, 0, 1, 0, 1)), -yAxis.len, 2 * yAxis.len);
2117
2118 // Set plotX and plotY for use in K-D-Tree and more
2119 point.plotX = point.clientX = (x1 + x2) / 2;
2120 point.plotY = (y1 + y2) / 2;
2121
2122 point.shapeType = 'rect';
2123 point.shapeArgs = {
2124 x: Math.min(x1, x2),
2125 y: Math.min(y1, y2),
2126 width: Math.abs(x2 - x1),
2127 height: Math.abs(y2 - y1)
2128 };
2129 });
2130
2131 series.translateColors();
2132 },
2133 drawPoints: function() {
2134 seriesTypes.column.prototype.drawPoints.call(this);
2135
2136 each(this.points, function(point) {
2137 point.graphic.attr(this.colorAttribs(point, point.state));
2138 }, this);
2139 },
2140 animate: noop,
2141 getBox: noop,
2142 drawLegendSymbol: LegendSymbolMixin.drawRectangle,
2143 alignDataLabel: seriesTypes.column.prototype.alignDataLabel,
2144 getExtremes: function() {
2145 // Get the extremes from the value data
2146 Series.prototype.getExtremes.call(this, this.valueData);
2147 this.valueMin = this.dataMin;
2148 this.valueMax = this.dataMax;
2149
2150 // Get the extremes from the y data
2151 Series.prototype.getExtremes.call(this);
2152 }
2153
2154 }), colorPointMixin);
2155
2156 }(Highcharts));
2157 (function(H) {
2158 /**
2159 * (c) 2010-2016 Torstein Honsi
2160 *
2161 * License: www.highcharts.com/license
2162 */
2163 'use strict';
2164 var Chart = H.Chart,
2165 each = H.each,
2166 extend = H.extend,
2167 error = H.error,
2168 format = H.format,
2169 merge = H.merge,
2170 win = H.win,
2171 wrap = H.wrap;
2172 /**
2173 * Test for point in polygon. Polygon defined as array of [x,y] points.
2174 */
2175 function pointInPolygon(point, polygon) {
2176 var i,
2177 j,
2178 rel1,
2179 rel2,
2180 c = false,
2181 x = point.x,
2182 y = point.y;
2183
2184 for (i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
2185 rel1 = polygon[i][1] > y;
2186 rel2 = polygon[j][1] > y;
2187 if (rel1 !== rel2 && (x < (polygon[j][0] - polygon[i][0]) * (y - polygon[i][1]) / (polygon[j][1] - polygon[i][1]) + polygon[i][0])) {
2188 c = !c;
2189 }
2190 }
2191
2192 return c;
2193 }
2194
2195 /**
2196 * Get point from latLon using specified transform definition
2197 */
2198 Chart.prototype.transformFromLatLon = function(latLon, transform) {
2199 if (win.proj4 === undefined) {
2200 error(21);
2201 return {
2202 x: 0,
2203 y: null
2204 };
2205 }
2206
2207 var projected = win.proj4(transform.crs, [latLon.lon, latLon.lat]),
2208 cosAngle = transform.cosAngle || (transform.rotation && Math.cos(transform.rotation)),
2209 sinAngle = transform.sinAngle || (transform.rotation && Math.sin(transform.rotation)),
2210 rotated = transform.rotation ? [projected[0] * cosAngle + projected[1] * sinAngle, -projected[0] * sinAngle + projected[1] * cosAngle] : projected;
2211
2212 return {
2213 x: ((rotated[0] - (transform.xoffset || 0)) * (transform.scale || 1) + (transform.xpan || 0)) * (transform.jsonres || 1) + (transform.jsonmarginX || 0),
2214 y: (((transform.yoffset || 0) - rotated[1]) * (transform.scale || 1) + (transform.ypan || 0)) * (transform.jsonres || 1) - (transform.jsonmarginY || 0)
2215 };
2216 };
2217
2218 /**
2219 * Get latLon from point using specified transform definition
2220 */
2221 Chart.prototype.transformToLatLon = function(point, transform) {
2222 if (win.proj4 === undefined) {
2223 error(21);
2224 return;
2225 }
2226
2227 var normalized = {
2228 x: ((point.x - (transform.jsonmarginX || 0)) / (transform.jsonres || 1) - (transform.xpan || 0)) / (transform.scale || 1) + (transform.xoffset || 0),
2229 y: ((-point.y - (transform.jsonmarginY || 0)) / (transform.jsonres || 1) + (transform.ypan || 0)) / (transform.scale || 1) + (transform.yoffset || 0)
2230 },
2231 cosAngle = transform.cosAngle || (transform.rotation && Math.cos(transform.rotation)),
2232 sinAngle = transform.sinAngle || (transform.rotation && Math.sin(transform.rotation)),
2233 // Note: Inverted sinAngle to reverse rotation direction
2234 projected = win.proj4(transform.crs, 'WGS84', transform.rotation ? {
2235 x: normalized.x * cosAngle + normalized.y * -sinAngle,
2236 y: normalized.x * sinAngle + normalized.y * cosAngle
2237 } : normalized);
2238
2239 return {
2240 lat: projected.y,
2241 lon: projected.x
2242 };
2243 };
2244
2245 Chart.prototype.fromPointToLatLon = function(point) {
2246 var transforms = this.mapTransforms,
2247 transform;
2248
2249 if (!transforms) {
2250 error(22);
2251 return;
2252 }
2253
2254 for (transform in transforms) {
2255 if (transforms.hasOwnProperty(transform) && transforms[transform].hitZone &&
2256 pointInPolygon({
2257 x: point.x,
2258 y: -point.y
2259 }, transforms[transform].hitZone.coordinates[0])) {
2260 return this.transformToLatLon(point, transforms[transform]);
2261 }
2262 }
2263
2264 return this.transformToLatLon(point, transforms['default']); // eslint-disable-line dot-notation
2265 };
2266
2267 Chart.prototype.fromLatLonToPoint = function(latLon) {
2268 var transforms = this.mapTransforms,
2269 transform,
2270 coords;
2271
2272 if (!transforms) {
2273 error(22);
2274 return {
2275 x: 0,
2276 y: null
2277 };
2278 }
2279
2280 for (transform in transforms) {
2281 if (transforms.hasOwnProperty(transform) && transforms[transform].hitZone) {
2282 coords = this.transformFromLatLon(latLon, transforms[transform]);
2283 if (pointInPolygon({
2284 x: coords.x,
2285 y: -coords.y
2286 }, transforms[transform].hitZone.coordinates[0])) {
2287 return coords;
2288 }
2289 }
2290 }
2291
2292 return this.transformFromLatLon(latLon, transforms['default']); // eslint-disable-line dot-notation
2293 };
2294
2295 /**
2296 * Convert a geojson object to map data of a given Highcharts type (map, mappoint or mapline).
2297 */
2298 H.geojson = function(geojson, hType, series) {
2299 var mapData = [],
2300 path = [],
2301 polygonToPath = function(polygon) {
2302 var i,
2303 len = polygon.length;
2304 path.push('M');
2305 for (i = 0; i < len; i++) {
2306 if (i === 1) {
2307 path.push('L');
2308 }
2309 path.push(polygon[i][0], -polygon[i][1]);
2310 }
2311 };
2312
2313 hType = hType || 'map';
2314
2315 each(geojson.features, function(feature) {
2316
2317 var geometry = feature.geometry,
2318 type = geometry.type,
2319 coordinates = geometry.coordinates,
2320 properties = feature.properties,
2321 point;
2322
2323 path = [];
2324
2325 if (hType === 'map' || hType === 'mapbubble') {
2326 if (type === 'Polygon') {
2327 each(coordinates, polygonToPath);
2328 path.push('Z');
2329
2330 } else if (type === 'MultiPolygon') {
2331 each(coordinates, function(items) {
2332 each(items, polygonToPath);
2333 });
2334 path.push('Z');
2335 }
2336
2337 if (path.length) {
2338 point = {
2339 path: path
2340 };
2341 }
2342
2343 } else if (hType === 'mapline') {
2344 if (type === 'LineString') {
2345 polygonToPath(coordinates);
2346 } else if (type === 'MultiLineString') {
2347 each(coordinates, polygonToPath);
2348 }
2349
2350 if (path.length) {
2351 point = {
2352 path: path
2353 };
2354 }
2355
2356 } else if (hType === 'mappoint') {
2357 if (type === 'Point') {
2358 point = {
2359 x: coordinates[0],
2360 y: -coordinates[1]
2361 };
2362 }
2363 }
2364 if (point) {
2365 mapData.push(extend(point, {
2366 name: properties.name || properties.NAME,
2367 properties: properties
2368 }));
2369 }
2370
2371 });
2372
2373 // Create a credits text that includes map source, to be picked up in Chart.addCredits
2374 if (series && geojson.copyrightShort) {
2375 series.chart.mapCredits = format(series.chart.options.credits.mapText, {
2376 geojson: geojson
2377 });
2378 series.chart.mapCreditsFull = format(series.chart.options.credits.mapTextFull, {
2379 geojson: geojson
2380 });
2381 }
2382
2383 return mapData;
2384 };
2385
2386 /**
2387 * Override addCredits to include map source by default
2388 */
2389 wrap(Chart.prototype, 'addCredits', function(proceed, credits) {
2390
2391 credits = merge(true, this.options.credits, credits);
2392
2393 // Disable credits link if map credits enabled. This to allow for in-text anchors.
2394 if (this.mapCredits) {
2395 credits.href = null;
2396 }
2397
2398 proceed.call(this, credits);
2399
2400 // Add full map credits to hover
2401 if (this.credits && this.mapCreditsFull) {
2402 this.credits.attr({
2403 title: this.mapCreditsFull
2404 });
2405 }
2406 });
2407
2408 }(Highcharts));
2409 (function(H) {
2410 /**
2411 * (c) 2010-2016 Torstein Honsi
2412 *
2413 * License: www.highcharts.com/license
2414 */
2415 'use strict';
2416 var Chart = H.Chart,
2417 defaultOptions = H.defaultOptions,
2418 each = H.each,
2419 extend = H.extend,
2420 merge = H.merge,
2421 pick = H.pick,
2422 Renderer = H.Renderer,
2423 SVGRenderer = H.SVGRenderer,
2424 VMLRenderer = H.VMLRenderer;
2425
2426
2427 // Add language
2428 extend(defaultOptions.lang, {
2429 zoomIn: 'Zoom in',
2430 zoomOut: 'Zoom out'
2431 });
2432
2433
2434 // Set the default map navigation options
2435 defaultOptions.mapNavigation = {
2436 buttonOptions: {
2437 alignTo: 'plotBox',
2438 align: 'left',
2439 verticalAlign: 'top',
2440 x: 0,
2441 width: 18,
2442 height: 18,
2443 padding: 5,
2444
2445 style: {
2446 fontSize: '15px',
2447 fontWeight: 'bold'
2448 },
2449 theme: {
2450 'stroke-width': 1,
2451 'text-align': 'center'
2452 }
2453
2454 },
2455 buttons: {
2456 zoomIn: {
2457 onclick: function() {
2458 this.mapZoom(0.5);
2459 },
2460 text: '+',
2461 y: 0
2462 },
2463 zoomOut: {
2464 onclick: function() {
2465 this.mapZoom(2);
2466 },
2467 text: '-',
2468 y: 28
2469 }
2470 },
2471 mouseWheelSensitivity: 1.1
2472 // enabled: false,
2473 // enableButtons: null, // inherit from enabled
2474 // enableTouchZoom: null, // inherit from enabled
2475 // enableDoubleClickZoom: null, // inherit from enabled
2476 // enableDoubleClickZoomTo: false
2477 // enableMouseWheelZoom: null, // inherit from enabled
2478 };
2479
2480 /**
2481 * Utility for reading SVG paths directly.
2482 */
2483 H.splitPath = function(path) {
2484 var i;
2485
2486 // Move letters apart
2487 path = path.replace(/([A-Za-z])/g, ' $1 ');
2488 // Trim
2489 path = path.replace(/^\s*/, '').replace(/\s*$/, '');
2490
2491 // Split on spaces and commas
2492 path = path.split(/[ ,]+/); // Extra comma to escape gulp.scripts task
2493
2494 // Parse numbers
2495 for (i = 0; i < path.length; i++) {
2496 if (!/[a-zA-Z]/.test(path[i])) {
2497 path[i] = parseFloat(path[i]);
2498 }
2499 }
2500 return path;
2501 };
2502
2503 // A placeholder for map definitions
2504 H.maps = {};
2505
2506
2507
2508
2509
2510 // Create symbols for the zoom buttons
2511 function selectiveRoundedRect(x, y, w, h, rTopLeft, rTopRight, rBottomRight, rBottomLeft) {
2512 return ['M', x + rTopLeft, y,
2513 // top side
2514 'L', x + w - rTopRight, y,
2515 // top right corner
2516 'C', x + w - rTopRight / 2, y, x + w, y + rTopRight / 2, x + w, y + rTopRight,
2517 // right side
2518 'L', x + w, y + h - rBottomRight,
2519 // bottom right corner
2520 'C', x + w, y + h - rBottomRight / 2, x + w - rBottomRight / 2, y + h, x + w - rBottomRight, y + h,
2521 // bottom side
2522 'L', x + rBottomLeft, y + h,
2523 // bottom left corner
2524 'C', x + rBottomLeft / 2, y + h, x, y + h - rBottomLeft / 2, x, y + h - rBottomLeft,
2525 // left side
2526 'L', x, y + rTopLeft,
2527 // top left corner
2528 'C', x, y + rTopLeft / 2, x + rTopLeft / 2, y, x + rTopLeft, y,
2529 'Z'
2530 ];
2531 }
2532 SVGRenderer.prototype.symbols.topbutton = function(x, y, w, h, attr) {
2533 return selectiveRoundedRect(x - 1, y - 1, w, h, attr.r, attr.r, 0, 0);
2534 };
2535 SVGRenderer.prototype.symbols.bottombutton = function(x, y, w, h, attr) {
2536 return selectiveRoundedRect(x - 1, y - 1, w, h, 0, 0, attr.r, attr.r);
2537 };
2538 // The symbol callbacks are generated on the SVGRenderer object in all browsers. Even
2539 // VML browsers need this in order to generate shapes in export. Now share
2540 // them with the VMLRenderer.
2541 if (Renderer === VMLRenderer) {
2542 each(['topbutton', 'bottombutton'], function(shape) {
2543 VMLRenderer.prototype.symbols[shape] = SVGRenderer.prototype.symbols[shape];
2544 });
2545 }
2546
2547
2548 /**
2549 * A wrapper for Chart with all the default values for a Map
2550 */
2551 H.Map = H.mapChart = function(a, b, c) {
2552
2553 var hasRenderToArg = typeof a === 'string' || a.nodeName,
2554 options = arguments[hasRenderToArg ? 1 : 0],
2555 hiddenAxis = {
2556 endOnTick: false,
2557 visible: false,
2558 minPadding: 0,
2559 maxPadding: 0,
2560 startOnTick: false
2561 },
2562 seriesOptions,
2563 defaultCreditsOptions = H.getOptions().credits;
2564
2565 /* For visual testing
2566 hiddenAxis.gridLineWidth = 1;
2567 hiddenAxis.gridZIndex = 10;
2568 hiddenAxis.tickPositions = undefined;
2569 // */
2570
2571 // Don't merge the data
2572 seriesOptions = options.series;
2573 options.series = null;
2574
2575 options = merge({
2576 chart: {
2577 panning: 'xy',
2578 type: 'map'
2579 },
2580 credits: {
2581 mapText: pick(defaultCreditsOptions.mapText, ' \u00a9 <a href="{geojson.copyrightUrl}">{geojson.copyrightShort}</a>'),
2582 mapTextFull: pick(defaultCreditsOptions.mapTextFull, '{geojson.copyright}')
2583 },
2584 xAxis: hiddenAxis,
2585 yAxis: merge(hiddenAxis, {
2586 reversed: true
2587 })
2588 },
2589 options, // user's options
2590
2591 { // forced options
2592 chart: {
2593 inverted: false,
2594 alignTicks: false
2595 }
2596 }
2597 );
2598
2599 options.series = seriesOptions;
2600
2601
2602 return hasRenderToArg ?
2603 new Chart(a, options, c) :
2604 new Chart(options, b);
2605 };
2606
2607 }(Highcharts));
2608}));