| /** | 
|  * @license Map plugin v0.1 for Highcharts | 
|  * | 
|  * (c) 2011-2013 Torstein Hønsi | 
|  * | 
|  * License: www.highcharts.com/license | 
|  */ | 
|   | 
| /*  | 
|  * See www.highcharts.com/studies/world-map.htm for use case. | 
|  * | 
|  * To do: | 
|  * - Optimize long variable names and alias adapter methods and Highcharts namespace variables | 
|  * - Zoom and pan GUI | 
|  */ | 
| (function (Highcharts) { | 
|     var UNDEFINED, | 
|         Axis = Highcharts.Axis, | 
|         Chart = Highcharts.Chart, | 
|         Point = Highcharts.Point, | 
|         Pointer = Highcharts.Pointer, | 
|         each = Highcharts.each, | 
|         extend = Highcharts.extend, | 
|         merge = Highcharts.merge, | 
|         pick = Highcharts.pick, | 
|         numberFormat = Highcharts.numberFormat, | 
|         defaultOptions = Highcharts.getOptions(), | 
|         seriesTypes = Highcharts.seriesTypes, | 
|         plotOptions = defaultOptions.plotOptions, | 
|         wrap = Highcharts.wrap, | 
|         Color = Highcharts.Color, | 
|         noop = function () {}; | 
|   | 
|      | 
|   | 
|     /* | 
|      * Return an intermediate color between two colors, according to pos where 0 | 
|      * is the from color and 1 is the to color | 
|      */ | 
|     function tweenColors(from, to, pos) { | 
|         var i = 4, | 
|             rgba = []; | 
|   | 
|         while (i--) { | 
|             rgba[i] = Math.round( | 
|                 to.rgba[i] + (from.rgba[i] - to.rgba[i]) * (1 - pos) | 
|             ); | 
|         } | 
|         return 'rgba(' + rgba.join(',') + ')'; | 
|     } | 
|   | 
|     // Set the default map navigation options | 
|     defaultOptions.mapNavigation = { | 
|         buttonOptions: { | 
|             align: 'right', | 
|             verticalAlign: 'bottom', | 
|             x: 0, | 
|             width: 18, | 
|             height: 18, | 
|             style: { | 
|                 fontSize: '15px', | 
|                 fontWeight: 'bold', | 
|                 textAlign: 'center' | 
|             } | 
|         }, | 
|         buttons: { | 
|             zoomIn: { | 
|                 onclick: function () { | 
|                     this.mapZoom(0.5); | 
|                 }, | 
|                 text: '+', | 
|                 y: -32 | 
|             }, | 
|             zoomOut: { | 
|                 onclick: function () { | 
|                     this.mapZoom(2); | 
|                 }, | 
|                 text: '-', | 
|                 y: 0 | 
|             } | 
|         } | 
|         // enableButtons: false, | 
|         // enableTouchZoom: false, | 
|         // zoomOnDoubleClick: false, | 
|         // zoomOnMouseWheel: false | 
|   | 
|     }; | 
|      | 
|     /** | 
|      * Utility for reading SVG paths directly. | 
|      */ | 
|     Highcharts.splitPath = function (path) { | 
|         var i; | 
|   | 
|         // Move letters apart | 
|         path = path.replace(/([A-Za-z])/g, ' $1 '); | 
|         // Trim | 
|         path = path.replace(/^\s*/, "").replace(/\s*$/, ""); | 
|          | 
|         // Split on spaces and commas | 
|         path = path.split(/[ ,]+/); | 
|          | 
|         // Parse numbers | 
|         for (i = 0; i < path.length; i++) { | 
|             if (!/[a-zA-Z]/.test(path[i])) { | 
|                 path[i] = parseFloat(path[i]); | 
|             } | 
|         } | 
|         return path; | 
|     }; | 
|   | 
|     // A placeholder for map definitions | 
|     Highcharts.maps = {}; | 
|      | 
|     /** | 
|      * Override to use the extreme coordinates from the SVG shape, not the | 
|      * data values | 
|      */ | 
|     wrap(Axis.prototype, 'getSeriesExtremes', function (proceed) { | 
|         var isXAxis = this.isXAxis, | 
|             dataMin, | 
|             dataMax, | 
|             xData = []; | 
|   | 
|         // Remove the xData array and cache it locally so that the proceed method doesn't use it | 
|         each(this.series, function (series, i) { | 
|             if (series.useMapGeometry) { | 
|                 xData[i] = series.xData; | 
|                 series.xData = []; | 
|             } | 
|         }); | 
|   | 
|         // Call base to reach normal cartesian series (like mappoint) | 
|         proceed.call(this); | 
|   | 
|         // Run extremes logic for map and mapline | 
|         dataMin = pick(this.dataMin, Number.MAX_VALUE); | 
|         dataMax = pick(this.dataMax, Number.MIN_VALUE); | 
|         each(this.series, function (series, i) { | 
|             if (series.useMapGeometry) { | 
|                 dataMin = Math.min(dataMin, series[isXAxis ? 'minX' : 'minY']); | 
|                 dataMax = Math.max(dataMax, series[isXAxis ? 'maxX' : 'maxY']); | 
|                 series.xData = xData[i]; // Reset xData array | 
|             } | 
|         }); | 
|          | 
|         this.dataMin = dataMin; | 
|         this.dataMax = dataMax; | 
|     }); | 
|      | 
|     /** | 
|      * Override axis translation to make sure the aspect ratio is always kept | 
|      */ | 
|     wrap(Axis.prototype, 'setAxisTranslation', function (proceed) { | 
|         var chart = this.chart, | 
|             mapRatio, | 
|             plotRatio = chart.plotWidth / chart.plotHeight, | 
|             isXAxis = this.isXAxis, | 
|             adjustedAxisLength, | 
|             xAxis = chart.xAxis[0], | 
|             padAxis; | 
|          | 
|         // Run the parent method | 
|         proceed.call(this); | 
|          | 
|         // On Y axis, handle both | 
|         if (chart.options.chart.type === 'map' && !isXAxis && xAxis.transA !== UNDEFINED) { | 
|              | 
|             // Use the same translation for both axes | 
|             this.transA = xAxis.transA = Math.min(this.transA, xAxis.transA); | 
|              | 
|             mapRatio = (xAxis.max - xAxis.min) / (this.max - this.min); | 
|              | 
|             // What axis to pad to put the map in the middle | 
|             padAxis = mapRatio > plotRatio ? this : xAxis; | 
|              | 
|             // Pad it | 
|             adjustedAxisLength = (padAxis.max - padAxis.min) * padAxis.transA; | 
|             padAxis.minPixelPadding = (padAxis.len - adjustedAxisLength) / 2; | 
|         } | 
|     }); | 
|   | 
|   | 
|     //--- Start zooming and panning features | 
|   | 
|     wrap(Chart.prototype, 'render', function (proceed) { | 
|         var chart = this, | 
|             mapNavigation = chart.options.mapNavigation; | 
|   | 
|         proceed.call(chart); | 
|   | 
|         // Render the plus and minus buttons | 
|         chart.renderMapNavigation(); | 
|   | 
|         // Add the double click event | 
|         if (mapNavigation.zoomOnDoubleClick) { | 
|             Highcharts.addEvent(chart.container, 'dblclick', function (e) { | 
|                 chart.pointer.onContainerDblClick(e); | 
|             }); | 
|         } | 
|   | 
|         // Add the mousewheel event | 
|         if (mapNavigation.zoomOnMouseWheel) { | 
|             Highcharts.addEvent(chart.container, document.onmousewheel === undefined ? 'DOMMouseScroll' : 'mousewheel', function (e) { | 
|                 chart.pointer.onContainerMouseWheel(e); | 
|             }); | 
|         } | 
|     }); | 
|   | 
|     // Extend the Pointer | 
|     extend(Pointer.prototype, { | 
|   | 
|         /** | 
|          * The event handler for the doubleclick event | 
|          */ | 
|         onContainerDblClick: function (e) { | 
|             var chart = this.chart; | 
|   | 
|             e = this.normalize(e); | 
|   | 
|             if (chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) { | 
|                 chart.mapZoom( | 
|                     0.5, | 
|                     chart.xAxis[0].toValue(e.chartX), | 
|                     chart.yAxis[0].toValue(e.chartY) | 
|                 ); | 
|             } | 
|         }, | 
|   | 
|         /** | 
|          * The event handler for the mouse scroll event | 
|          */ | 
|         onContainerMouseWheel: function (e) { | 
|             var chart = this.chart, | 
|                 delta; | 
|   | 
|             e = this.normalize(e); | 
|   | 
|             // Firefox uses e.detail, WebKit and IE uses wheelDelta | 
|             delta = e.detail || -(e.wheelDelta / 120); | 
|             if (chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) { | 
|                 chart.mapZoom( | 
|                     delta > 0 ? 2 : 0.5, | 
|                     chart.xAxis[0].toValue(e.chartX), | 
|                     chart.yAxis[0].toValue(e.chartY) | 
|                 ); | 
|             } | 
|         } | 
|     }); | 
|     // Implement the pinchType option | 
|     wrap(Pointer.prototype, 'init', function (proceed, chart, options) { | 
|   | 
|         proceed.call(this, chart, options); | 
|   | 
|         // Pinch status | 
|         if (options.mapNavigation.enableTouchZoom) { | 
|             this.pinchX = this.pinchHor =  | 
|                 this.pinchY = this.pinchVert = true; | 
|         } | 
|     }); | 
|   | 
|     // Add events to the Chart object itself | 
|     extend(Chart.prototype, { | 
|         renderMapNavigation: function () { | 
|             var chart = this, | 
|                 options = this.options.mapNavigation, | 
|                 buttons = options.buttons, | 
|                 n, | 
|                 button, | 
|                 buttonOptions, | 
|                 outerHandler = function () {  | 
|                     this.handler.call(chart);  | 
|                 }; | 
|   | 
|             if (options.enableButtons) { | 
|                 for (n in buttons) { | 
|                     if (buttons.hasOwnProperty(n)) { | 
|                         buttonOptions = merge(options.buttonOptions, buttons[n]); | 
|   | 
|                         button = chart.renderer.button(buttonOptions.text, 0, 0, outerHandler) | 
|                             .attr({ | 
|                                 width: buttonOptions.width, | 
|                                 height: buttonOptions.height | 
|                             }) | 
|                             .css(buttonOptions.style) | 
|                             .add(); | 
|                         button.handler = buttonOptions.onclick; | 
|                         button.align(extend(buttonOptions, { width: button.width, height: button.height }), null, 'spacingBox'); | 
|                     } | 
|                 } | 
|             } | 
|         }, | 
|   | 
|         /** | 
|          * Fit an inner box to an outer. If the inner box overflows left or right, align it to the sides of the | 
|          * outer. If it overflows both sides, fit it within the outer. This is a pattern that occurs more places | 
|          * in Highcharts, perhaps it should be elevated to a common utility function. | 
|          */ | 
|         fitToBox: function (inner, outer) { | 
|             each([['x', 'width'], ['y', 'height']], function (dim) { | 
|                 var pos = dim[0], | 
|                     size = dim[1]; | 
|                 if (inner[pos] + inner[size] > outer[pos] + outer[size]) { // right overflow | 
|                     if (inner[size] > outer[size]) { // the general size is greater, fit fully to outer | 
|                         inner[size] = outer[size]; | 
|                         inner[pos] = outer[pos]; | 
|                     } else { // align right | 
|                         inner[pos] = outer[pos] + outer[size] - inner[size]; | 
|                     } | 
|                 } | 
|                 if (inner[size] > outer[size]) { | 
|                     inner[size] = outer[size]; | 
|                 } | 
|                 if (inner[pos] < outer[pos]) { | 
|                     inner[pos] = outer[pos]; | 
|                 } | 
|                  | 
|             }); | 
|   | 
|             return inner; | 
|         }, | 
|   | 
|         /** | 
|          * Zoom the map in or out by a certain amount. Less than 1 zooms in, greater than 1 zooms out. | 
|          */ | 
|         mapZoom: function (howMuch, centerXArg, centerYArg) { | 
|   | 
|             if (this.isMapZooming) { | 
|                 return; | 
|             } | 
|   | 
|             var chart = this, | 
|                 xAxis = chart.xAxis[0], | 
|                 xRange = xAxis.max - xAxis.min, | 
|                 centerX = pick(centerXArg, xAxis.min + xRange / 2), | 
|                 newXRange = xRange * howMuch, | 
|                 yAxis = chart.yAxis[0], | 
|                 yRange = yAxis.max - yAxis.min, | 
|                 centerY = pick(centerYArg, yAxis.min + yRange / 2), | 
|                 newYRange = yRange * howMuch, | 
|                 newXMin = centerX - newXRange / 2, | 
|                 newYMin = centerY - newYRange / 2, | 
|                 animation = pick(chart.options.chart.animation, true), | 
|                 delay, | 
|                 newExt = chart.fitToBox({ | 
|                     x: newXMin, | 
|                     y: newYMin, | 
|                     width: newXRange, | 
|                     height: newYRange | 
|                 }, { | 
|                     x: xAxis.dataMin, | 
|                     y: yAxis.dataMin, | 
|                     width: xAxis.dataMax - xAxis.dataMin, | 
|                     height: yAxis.dataMax - yAxis.dataMin | 
|                 }); | 
|   | 
|             xAxis.setExtremes(newExt.x, newExt.x + newExt.width, false); | 
|             yAxis.setExtremes(newExt.y, newExt.y + newExt.height, false); | 
|   | 
|             // Prevent zooming until this one is finished animating | 
|             delay = animation ? animation.duration || 500 : 0; | 
|             if (delay) { | 
|                 chart.isMapZooming = true; | 
|                 setTimeout(function () { | 
|                     chart.isMapZooming = false; | 
|                 }, delay); | 
|             } | 
|   | 
|             chart.redraw(); | 
|         } | 
|     }); | 
|      | 
|     /** | 
|      * Extend the default options with map options | 
|      */ | 
|     plotOptions.map = merge(plotOptions.scatter, { | 
|         animation: false, // makes the complex shapes slow | 
|         nullColor: '#F8F8F8', | 
|         borderColor: 'silver', | 
|         borderWidth: 1, | 
|         marker: null, | 
|         stickyTracking: false, | 
|         dataLabels: { | 
|             verticalAlign: 'middle' | 
|         }, | 
|         turboThreshold: 0, | 
|         tooltip: { | 
|             followPointer: true, | 
|             pointFormat: '{point.name}: {point.y}<br/>' | 
|         }, | 
|         states: { | 
|             normal: { | 
|                 animation: true | 
|             } | 
|         } | 
|     }); | 
|   | 
|     var MapAreaPoint = Highcharts.extendClass(Point, { | 
|         /** | 
|          * Extend the Point object to split paths | 
|          */ | 
|         applyOptions: function (options, x) { | 
|   | 
|             var point = Point.prototype.applyOptions.call(this, options, x); | 
|   | 
|             if (point.path && typeof point.path === 'string') { | 
|                 point.path = point.options.path = Highcharts.splitPath(point.path); | 
|             } | 
|   | 
|             return point; | 
|         }, | 
|         /** | 
|          * Stop the fade-out  | 
|          */ | 
|         onMouseOver: function () { | 
|             clearTimeout(this.colorInterval); | 
|             Point.prototype.onMouseOver.call(this); | 
|         }, | 
|         /** | 
|          * Custom animation for tweening out the colors. Animation reduces blinking when hovering | 
|          * over islands and coast lines. We run a custom implementation of animation becuase we | 
|          * need to be able to run this independently from other animations like zoom redraw. Also, | 
|          * adding color animation to the adapters would introduce almost the same amount of code. | 
|          */ | 
|         onMouseOut: function () { | 
|             var point = this, | 
|                 start = +new Date(), | 
|                 normalColor = Color(point.options.color), | 
|                 hoverColor = Color(point.pointAttr.hover.fill), | 
|                 animation = point.series.options.states.normal.animation, | 
|                 duration = animation && (animation.duration || 500); | 
|   | 
|             if (duration && normalColor.rgba.length === 4 && hoverColor.rgba.length === 4) { | 
|                 delete point.pointAttr[''].fill; // avoid resetting it in Point.setState | 
|   | 
|                 clearTimeout(point.colorInterval); | 
|                 point.colorInterval = setInterval(function () { | 
|                     var pos = (new Date() - start) / duration, | 
|                         graphic = point.graphic; | 
|                     if (pos > 1) { | 
|                         pos = 1; | 
|                     } | 
|                     if (graphic) { | 
|                         graphic.attr('fill', tweenColors(hoverColor, normalColor, pos)); | 
|                     } | 
|                     if (pos >= 1) { | 
|                         clearTimeout(point.colorInterval); | 
|                     } | 
|                 }, 13); | 
|             } | 
|             Point.prototype.onMouseOut.call(point); | 
|         } | 
|     }); | 
|   | 
|     /** | 
|      * Add the series type | 
|      */ | 
|     seriesTypes.map = Highcharts.extendClass(seriesTypes.scatter, { | 
|         type: 'map', | 
|         pointAttrToOptions: { // mapping between SVG attributes and the corresponding options | 
|             stroke: 'borderColor', | 
|             'stroke-width': 'borderWidth', | 
|             fill: 'color' | 
|         }, | 
|         colorKey: 'y', | 
|         pointClass: MapAreaPoint, | 
|         trackerGroups: ['group', 'markerGroup', 'dataLabelsGroup'], | 
|         getSymbol: noop, | 
|         supportsDrilldown: true, | 
|         getExtremesFromAll: true, | 
|         useMapGeometry: true, // get axis extremes from paths, not values | 
|         init: function (chart) { | 
|             var series = this, | 
|                 valueDecimals = chart.options.legend.valueDecimals, | 
|                 legendItems = [], | 
|                 name, | 
|                 from, | 
|                 to, | 
|                 fromLabel, | 
|                 toLabel, | 
|                 colorRange, | 
|                 valueRanges, | 
|                 gradientColor, | 
|                 grad, | 
|                 tmpLabel, | 
|                 horizontal = chart.options.legend.layout === 'horizontal'; | 
|   | 
|              | 
|             Highcharts.Series.prototype.init.apply(this, arguments); | 
|             colorRange = series.options.colorRange; | 
|             valueRanges = series.options.valueRanges; | 
|   | 
|             if (valueRanges) { | 
|                 each(valueRanges, function (range) { | 
|                     from = range.from; | 
|                     to = range.to; | 
|                      | 
|                     // Assemble the default name. This can be overridden by legend.options.labelFormatter | 
|                     name = ''; | 
|                     if (from === UNDEFINED) { | 
|                         name = '< '; | 
|                     } else if (to === UNDEFINED) { | 
|                         name = '> '; | 
|                     } | 
|                     if (from !== UNDEFINED) { | 
|                         name += numberFormat(from, valueDecimals); | 
|                     } | 
|                     if (from !== UNDEFINED && to !== UNDEFINED) { | 
|                         name += ' - '; | 
|                     } | 
|                     if (to !== UNDEFINED) { | 
|                         name += numberFormat(to, valueDecimals); | 
|                     } | 
|                      | 
|                     // Add a mock object to the legend items | 
|                     legendItems.push(Highcharts.extend({ | 
|                         chart: series.chart, | 
|                         name: name, | 
|                         options: {}, | 
|                         drawLegendSymbol: seriesTypes.area.prototype.drawLegendSymbol, | 
|                         visible: true, | 
|                         setState: function () {}, | 
|                         setVisible: function () {} | 
|                     }, range)); | 
|                 }); | 
|                 series.legendItems = legendItems; | 
|   | 
|             } else if (colorRange) { | 
|   | 
|                 from = colorRange.from; | 
|                 to = colorRange.to; | 
|                 fromLabel = colorRange.fromLabel; | 
|                 toLabel = colorRange.toLabel; | 
|   | 
|                 // Flips linearGradient variables and label text. | 
|                 grad = horizontal ? [0, 0, 1, 0] : [0, 1, 0, 0];  | 
|                 if (!horizontal) { | 
|                     tmpLabel = fromLabel; | 
|                     fromLabel = toLabel; | 
|                     toLabel = tmpLabel; | 
|                 }  | 
|   | 
|                 // Creates color gradient. | 
|                 gradientColor = { | 
|                     linearGradient: { x1: grad[0], y1: grad[1], x2: grad[2], y2: grad[3] }, | 
|                     stops:  | 
|                     [ | 
|                         [0, from], | 
|                         [1, to] | 
|                     ] | 
|                 }; | 
|   | 
|                 // Add a mock object to the legend items. | 
|                 legendItems = [{ | 
|                     chart: series.chart, | 
|                     options: {}, | 
|                     fromLabel: fromLabel, | 
|                     toLabel: toLabel, | 
|                     color: gradientColor, | 
|                     drawLegendSymbol: this.drawLegendSymbolGradient, | 
|                     visible: true, | 
|                     setState: function () {}, | 
|                     setVisible: function () {} | 
|                 }]; | 
|   | 
|                 series.legendItems = legendItems; | 
|             } | 
|         }, | 
|   | 
|         /** | 
|          * If neither valueRanges nor colorRanges are defined, use basic area symbol. | 
|          */ | 
|         drawLegendSymbol: seriesTypes.area.prototype.drawLegendSymbol, | 
|   | 
|         /** | 
|          * Gets the series' symbol in the legend and extended legend with more information. | 
|          *  | 
|          * @param {Object} legend The legend object | 
|          * @param {Object} item The series (this) or point | 
|          */ | 
|         drawLegendSymbolGradient: function (legend, item) { | 
|             var spacing = legend.options.symbolPadding, | 
|                 padding = pick(legend.options.padding, 8), | 
|                 positionY, | 
|                 positionX, | 
|                 gradientSize = this.chart.renderer.fontMetrics(legend.options.itemStyle.fontSize).h, | 
|                 horizontal = legend.options.layout === 'horizontal', | 
|                 box1, | 
|                 box2, | 
|                 box3, | 
|                 rectangleLength = pick(legend.options.rectangleLength, 200); | 
|   | 
|             // Set local variables based on option. | 
|             if (horizontal) { | 
|                 positionY = -(spacing / 2); | 
|                 positionX = 0; | 
|             } else { | 
|                 positionY = -rectangleLength + legend.baseline - (spacing / 2); | 
|                 positionX = padding + gradientSize; | 
|             } | 
|   | 
|             // Creates the from text. | 
|             item.fromText = this.chart.renderer.text( | 
|                     item.fromLabel,    // Text. | 
|                     positionX,        // Lower left x. | 
|                     positionY        // Lower left y. | 
|                 ).attr({ | 
|                     zIndex: 2 | 
|                 }).add(item.legendGroup); | 
|             box1 = item.fromText.getBBox(); | 
|   | 
|             // Creates legend symbol. | 
|             // Ternary changes variables based on option. | 
|             item.legendSymbol = this.chart.renderer.rect( | 
|                 horizontal ? box1.x + box1.width + spacing : box1.x - gradientSize - spacing,        // Upper left x. | 
|                 box1.y,                                                                                // Upper left y. | 
|                 horizontal ? rectangleLength : gradientSize,                                            // Width. | 
|                 horizontal ? gradientSize : rectangleLength,                                        // Height. | 
|                 2                                                                                    // Corner radius. | 
|             ).attr({ | 
|                 zIndex: 1 | 
|             }).add(item.legendGroup); | 
|             box2 = item.legendSymbol.getBBox(); | 
|   | 
|             // Creates the to text. | 
|             // Vertical coordinate changed based on option. | 
|             item.toText = this.chart.renderer.text( | 
|                     item.toLabel, | 
|                     box2.x + box2.width + spacing, | 
|                     horizontal ? positionY : box2.y + box2.height - spacing | 
|                 ).attr({ | 
|                     zIndex: 2 | 
|                 }).add(item.legendGroup); | 
|             box3 = item.toText.getBBox(); | 
|   | 
|             // Changes legend box settings based on option. | 
|             if (horizontal) { | 
|                 legend.offsetWidth = box1.width + box2.width + box3.width + (spacing * 2) + padding; | 
|                 legend.itemY = gradientSize + padding; | 
|             } else { | 
|                 legend.offsetWidth = Math.max(box1.width, box3.width) + (spacing) + box2.width + padding; | 
|                 legend.itemY = box2.height + padding; | 
|                 legend.itemX = spacing; | 
|             } | 
|         }, | 
|   | 
|         /** | 
|          * Get the bounding box of all paths in the map combined. | 
|          */ | 
|         getBox: function (paths) { | 
|             var maxX = Number.MIN_VALUE,  | 
|                 minX =  Number.MAX_VALUE,  | 
|                 maxY = Number.MIN_VALUE,  | 
|                 minY =  Number.MAX_VALUE; | 
|              | 
|              | 
|             // Find the bounding box | 
|             each(paths || this.options.data, function (point) { | 
|                 var path = point.path, | 
|                     i = path.length, | 
|                     even = false, // while loop reads from the end | 
|                     pointMaxX = Number.MIN_VALUE,  | 
|                     pointMinX =  Number.MAX_VALUE,  | 
|                     pointMaxY = Number.MIN_VALUE,  | 
|                     pointMinY =  Number.MAX_VALUE; | 
|                      | 
|                 while (i--) { | 
|                     if (typeof path[i] === 'number' && !isNaN(path[i])) { | 
|                         if (even) { // even = x | 
|                             pointMaxX = Math.max(pointMaxX, path[i]); | 
|                             pointMinX = Math.min(pointMinX, path[i]); | 
|                         } else { // odd = Y | 
|                             pointMaxY = Math.max(pointMaxY, path[i]); | 
|                             pointMinY = Math.min(pointMinY, path[i]); | 
|                         } | 
|                         even = !even; | 
|                     } | 
|                 } | 
|                 // Cache point bounding box for use to position data labels | 
|                 point._maxX = pointMaxX; | 
|                 point._minX = pointMinX; | 
|                 point._maxY = pointMaxY; | 
|                 point._minY = pointMinY; | 
|   | 
|                 maxX = Math.max(maxX, pointMaxX); | 
|                 minX = Math.min(minX, pointMinX); | 
|                 maxY = Math.max(maxY, pointMaxY); | 
|                 minY = Math.min(minY, pointMinY); | 
|             }); | 
|             this.minY = minY; | 
|             this.maxY = maxY; | 
|             this.minX = minX; | 
|             this.maxX = maxX; | 
|              | 
|         }, | 
|          | 
|          | 
|          | 
|         /** | 
|          * Translate the path so that it automatically fits into the plot area box | 
|          * @param {Object} path | 
|          */ | 
|         translatePath: function (path) { | 
|              | 
|             var series = this, | 
|                 even = false, // while loop reads from the end | 
|                 xAxis = series.xAxis, | 
|                 yAxis = series.yAxis, | 
|                 i; | 
|                  | 
|             // Preserve the original | 
|             path = [].concat(path); | 
|                  | 
|             // Do the translation | 
|             i = path.length; | 
|             while (i--) { | 
|                 if (typeof path[i] === 'number') { | 
|                     if (even) { // even = x | 
|                         path[i] = Math.round(xAxis.translate(path[i])); | 
|                     } else { // odd = Y | 
|                         path[i] = Math.round(yAxis.len - yAxis.translate(path[i])); | 
|                     } | 
|                     even = !even; | 
|                 } | 
|             } | 
|             return path; | 
|         }, | 
|          | 
|         setData: function () { | 
|             Highcharts.Series.prototype.setData.apply(this, arguments); | 
|             this.getBox(); | 
|         }, | 
|          | 
|         /** | 
|          * Add the path option for data points. Find the max value for color calculation. | 
|          */ | 
|         translate: function () { | 
|             var series = this, | 
|                 dataMin = Number.MAX_VALUE, | 
|                 dataMax = Number.MIN_VALUE; | 
|      | 
|             series.generatePoints(); | 
|      | 
|             each(series.data, function (point) { | 
|                  | 
|                 point.shapeType = 'path'; | 
|                 point.shapeArgs = { | 
|                     d: series.translatePath(point.path) | 
|                 }; | 
|                  | 
|                 // TODO: do point colors in drawPoints instead of point.init | 
|                 if (typeof point.y === 'number') { | 
|                     if (point.y > dataMax) { | 
|                         dataMax = point.y; | 
|                     } else if (point.y < dataMin) { | 
|                         dataMin = point.y; | 
|                     } | 
|                 } | 
|             }); | 
|              | 
|             series.translateColors(dataMin, dataMax); | 
|         }, | 
|          | 
|         /** | 
|          * In choropleth maps, the color is a result of the value, so this needs translation too | 
|          */ | 
|         translateColors: function (dataMin, dataMax) { | 
|              | 
|             var seriesOptions = this.options, | 
|                 valueRanges = seriesOptions.valueRanges, | 
|                 colorRange = seriesOptions.colorRange, | 
|                 colorKey = this.colorKey, | 
|                 from, | 
|                 to; | 
|   | 
|             if (colorRange) { | 
|                 from = Color(colorRange.from); | 
|                 to = Color(colorRange.to); | 
|             } | 
|              | 
|             each(this.data, function (point) { | 
|                 var value = point[colorKey], | 
|                     range, | 
|                     color, | 
|                     i, | 
|                     pos; | 
|   | 
|                 if (valueRanges) { | 
|                     i = valueRanges.length; | 
|                     while (i--) { | 
|                         range = valueRanges[i]; | 
|                         from = range.from; | 
|                         to = range.to; | 
|                         if ((from === UNDEFINED || value >= from) && (to === UNDEFINED || value <= to)) { | 
|                             color = range.color; | 
|                             break; | 
|                         } | 
|                              | 
|                     } | 
|                 } else if (colorRange && value !== undefined) { | 
|   | 
|                     pos = 1 - ((dataMax - value) / (dataMax - dataMin)); | 
|                     color = value === null ? seriesOptions.nullColor : tweenColors(from, to, pos); | 
|                 } | 
|   | 
|                 if (color) { | 
|                     point.color = null; // reset from previous drilldowns, use of the same data options | 
|                     point.options.color = color; | 
|                 } | 
|             }); | 
|         }, | 
|          | 
|         drawGraph: noop, | 
|          | 
|         /** | 
|          * We need the points' bounding boxes in order to draw the data labels, so  | 
|          * we skip it now and call if from drawPoints instead. | 
|          */ | 
|         drawDataLabels: noop, | 
|          | 
|         /**  | 
|          * Use the drawPoints method of column, that is able to handle simple shapeArgs. | 
|          * Extend it by assigning the tooltip position. | 
|          */ | 
|         drawPoints: function () { | 
|             var series = this, | 
|                 xAxis = series.xAxis, | 
|                 yAxis = series.yAxis, | 
|                 colorKey = series.colorKey; | 
|              | 
|             // Make points pass test in drawing | 
|             each(series.data, function (point) { | 
|                 point.plotY = 1; // pass null test in column.drawPoints | 
|                 if (point[colorKey] === null) { | 
|                     point[colorKey] = 0; | 
|                     point.isNull = true; | 
|                 } | 
|             }); | 
|              | 
|             // Draw them | 
|             seriesTypes.column.prototype.drawPoints.apply(series); | 
|              | 
|             each(series.data, function (point) { | 
|   | 
|                 var dataLabels = point.dataLabels, | 
|                     minX = xAxis.toPixels(point._minX, true), | 
|                     maxX = xAxis.toPixels(point._maxX, true), | 
|                     minY = yAxis.toPixels(point._minY, true), | 
|                     maxY = yAxis.toPixels(point._maxY, true); | 
|   | 
|                 point.plotX = Math.round(minX + (maxX - minX) * pick(dataLabels && dataLabels.anchorX, 0.5)); | 
|                 point.plotY = Math.round(minY + (maxY - minY) * pick(dataLabels && dataLabels.anchorY, 0.5));  | 
|                  | 
|                  | 
|                 // Reset escaped null points | 
|                 if (point.isNull) { | 
|                     point[colorKey] = null; | 
|                 } | 
|             }); | 
|   | 
|             // Now draw the data labels | 
|             Highcharts.Series.prototype.drawDataLabels.call(series); | 
|              | 
|         }, | 
|   | 
|         /** | 
|          * Animate in the new series from the clicked point in the old series. | 
|          * Depends on the drilldown.js module | 
|          */ | 
|         animateDrilldown: function (init) { | 
|             var toBox = this.chart.plotBox, | 
|                 level = this.chart.drilldownLevels[this.chart.drilldownLevels.length - 1], | 
|                 fromBox = level.bBox, | 
|                 animationOptions = this.chart.options.drilldown.animation, | 
|                 scale; | 
|                  | 
|             if (!init) { | 
|   | 
|                 scale = Math.min(fromBox.width / toBox.width, fromBox.height / toBox.height); | 
|                 level.shapeArgs = { | 
|                     scaleX: scale, | 
|                     scaleY: scale, | 
|                     translateX: fromBox.x, | 
|                     translateY: fromBox.y | 
|                 }; | 
|                  | 
|                 // TODO: Animate this.group instead | 
|                 each(this.points, function (point) { | 
|   | 
|                     point.graphic | 
|                         .attr(level.shapeArgs) | 
|                         .animate({ | 
|                             scaleX: 1, | 
|                             scaleY: 1, | 
|                             translateX: 0, | 
|                             translateY: 0 | 
|                         }, animationOptions); | 
|   | 
|                 }); | 
|   | 
|                 delete this.animate; | 
|             } | 
|              | 
|         }, | 
|   | 
|         /** | 
|          * When drilling up, pull out the individual point graphics from the lower series | 
|          * and animate them into the origin point in the upper series. | 
|          */ | 
|         animateDrillupFrom: function (level) { | 
|             seriesTypes.column.prototype.animateDrillupFrom.call(this, level); | 
|         }, | 
|   | 
|   | 
|         /** | 
|          * When drilling up, keep the upper series invisible until the lower series has | 
|          * moved into place | 
|          */ | 
|         animateDrillupTo: function (init) { | 
|             seriesTypes.column.prototype.animateDrillupTo.call(this, init); | 
|         } | 
|     }); | 
|   | 
|   | 
|     // The mapline series type | 
|     plotOptions.mapline = merge(plotOptions.map, { | 
|         lineWidth: 1, | 
|         backgroundColor: 'none' | 
|     }); | 
|     seriesTypes.mapline = Highcharts.extendClass(seriesTypes.map, { | 
|         type: 'mapline', | 
|         pointAttrToOptions: { // mapping between SVG attributes and the corresponding options | 
|             stroke: 'color', | 
|             'stroke-width': 'lineWidth', | 
|             fill: 'backgroundColor' | 
|         }, | 
|         drawLegendSymbol: seriesTypes.line.prototype.drawLegendSymbol | 
|     }); | 
|   | 
|     // The mappoint series type | 
|     plotOptions.mappoint = merge(plotOptions.scatter, { | 
|         dataLabels: { | 
|             enabled: true, | 
|             format: '{point.name}', | 
|             color: 'black', | 
|             style: { | 
|                 textShadow: '0 0 5px white' | 
|             } | 
|         } | 
|     }); | 
|     seriesTypes.mappoint = Highcharts.extendClass(seriesTypes.scatter, { | 
|         type: 'mappoint' | 
|     }); | 
|      | 
|   | 
|      | 
|     /** | 
|      * A wrapper for Chart with all the default values for a Map | 
|      */ | 
|     Highcharts.Map = function (options, callback) { | 
|          | 
|         var hiddenAxis = { | 
|                 endOnTick: false, | 
|                 gridLineWidth: 0, | 
|                 labels: { | 
|                     enabled: false | 
|                 }, | 
|                 lineWidth: 0, | 
|                 minPadding: 0, | 
|                 maxPadding: 0, | 
|                 startOnTick: false, | 
|                 tickWidth: 0, | 
|                 title: null | 
|             }, | 
|             seriesOptions; | 
|          | 
|         // Don't merge the data | 
|         seriesOptions = options.series; | 
|         options.series = null; | 
|          | 
|         options = merge({ | 
|             chart: { | 
|                 type: 'map', | 
|                 panning: 'xy' | 
|             }, | 
|             xAxis: hiddenAxis, | 
|             yAxis: merge(hiddenAxis, { reversed: true })     | 
|         }, | 
|         options, // user's options | 
|      | 
|         { // forced options | 
|             chart: { | 
|                 inverted: false | 
|             } | 
|         }); | 
|      | 
|         options.series = seriesOptions; | 
|      | 
|      | 
|         return new Highcharts.Chart(options, callback); | 
|     }; | 
| }(Highcharts)); |