/** * (c) 2010-2017 Torstein Honsi * * License: www.highcharts.com/license */ 'use strict'; import H from '../parts/Globals.js'; import '../parts/Utilities.js'; import '../parts/Color.js'; import '../parts/Options.js'; import '../parts/Legend.js'; import '../parts/Point.js'; import '../parts/Series.js'; import '../parts/ScatterSeries.js'; var colorPointMixin = H.colorPointMixin, colorSeriesMixin = H.colorSeriesMixin, doc = H.doc, each = H.each, extend = H.extend, isNumber = H.isNumber, LegendSymbolMixin = H.LegendSymbolMixin, map = H.map, merge = H.merge, noop = H.noop, pick = H.pick, isArray = H.isArray, Point = H.Point, Series = H.Series, seriesType = H.seriesType, seriesTypes = H.seriesTypes, splat = H.splat; // The vector-effect attribute is not supported in IE <= 11 (at least), so we // need diffent logic (#3218) var supportsVectorEffect = doc.documentElement.style.vectorEffect !== undefined; /** * The map series is used for basic choropleth maps, where each map area has a * color based on its value. * * @sample maps/demo/base/ Choropleth map * @extends {plotOptions.scatter} * @excluding marker * @product highmaps * @optionparent plotOptions.map */ seriesType('map', 'scatter', { /** * Define the z index of the series. * * @type {Number} * @product highmaps * @apioption plotOptions.series.zIndex */ /** * Whether all areas of the map defined in `mapData` should be rendered. * If `true`, areas which don't correspond to a data point, are rendered * as `null` points. If `false`, those areas are skipped. * * @type {Boolean} * @sample {highmaps} maps/plotoptions/series-allareas-false/ * All areas set to false * @default true * @product highmaps * @apioption plotOptions.series.allAreas */ allAreas: true, animation: false, // makes the complex shapes slow /** * The color to apply to null points. * * In styled mode, the null point fill is set in the * `.highcharts-null-point` class. * * @type {Color} * @sample {highmaps} maps/demo/all-areas-as-null/ Null color * @default #f7f7f7 * @product highmaps */ nullColor: '#f7f7f7', /** * The border color of the map areas. * * In styled mode, the border stroke is given in the `.highcharts-point` * class. * * @type {Color} * @sample {highmaps} maps/plotoptions/series-border/ Borders demo * @default #cccccc * @product highmaps * @apioption plotOptions.series.borderColor */ borderColor: '#cccccc', /** * The border width of each map area. * * In styled mode, the border stroke width is given in the * `.highcharts-point` class. * * @sample {highmaps} maps/plotoptions/series-border/ Borders demo * @product highmaps * @apioption plotOptions.series.borderWidth */ borderWidth: 1, /** * Whether to allow pointer interaction like tooltips and mouse events * on null points. * * @type {Boolean} * @default false * @since 4.2.7 * @product highmaps * @apioption plotOptions.map.nullInteraction */ /** * Set this option to `false` to prevent a series from connecting to * the global color axis. This will cause the series to have its own * legend item. * * @type {Boolean} * @default undefined * @product highmaps * @apioption plotOptions.series.colorAxis */ /** * @ignore-option */ marker: null, stickyTracking: false, /** * What property to join the `mapData` to the value data. For example, * if joinBy is "code", the mapData items with a specific code is merged * into the data with the same code. For maps loaded from GeoJSON, the * keys may be held in each point's `properties` object. * * The joinBy option can also be an array of two values, where the first * points to a key in the `mapData`, and the second points to another * key in the `data`. * * When joinBy is `null`, the map items are joined by their position * in the array, which performs much better in maps with many data points. * This is the recommended option if you are printing more than a thousand * data points and have a backend that can preprocess the data into * a parallel array of the mapData. * * @type {String|Array} * @sample {highmaps} maps/plotoptions/series-border/ Joined by "code" * @sample {highmaps} maps/demo/geojson/ GeoJSON joined by an array * @sample {highmaps} maps/series/joinby-null/ Simple data joined by null * @product highmaps * @apioption plotOptions.series.joinBy */ joinBy: 'hc-key', dataLabels: { formatter: function () { // #2945 return this.point.value; }, inside: true, // for the color verticalAlign: 'middle', crop: false, overflow: false, padding: 0 }, /** * @ignore */ turboThreshold: 0, tooltip: { followPointer: true, pointFormat: '{point.name}: {point.value}
' }, states: { /** * Overrides for the normal state. * * @type {Object} * @product highmaps * @apioption plotOptions.series.states.normal */ normal: { /** * Animation options for the fill color when returning from hover * state to normal state. The animation adds some latency in order * to reduce the effect of flickering when hovering in and out of * for example an uneven coastline. * * @type {Object|Boolean} * @sample {highmaps} * maps/plotoptions/series-states-animation-false/ * No animation of fill color * @default true * @product highmaps * @apioption plotOptions.series.states.normal.animation */ animation: true }, hover: { halo: null, /** * The color of the shape in this state * * @type {Color} * @sample {highmaps} maps/plotoptions/series-states-hover/ * Hover options * @product highmaps * @apioption plotOptions.series.states.hover.color */ /** * The border color of the point in this state. * * @type {Color} * @product highmaps * @apioption plotOptions.series.states.hover.borderColor */ /** * The border width of the point in this state * * @type {Number} * @product highmaps * @apioption plotOptions.series.states.hover.borderWidth */ /** * The relative brightness of the point when hovered, relative to * the normal point color. * * @type {Number} * @default 0.2 * @product highmaps * @apioption plotOptions.series.states.hover.brightness */ brightness: 0.2 }, select: { color: '#cccccc' } } // Prototype members }, merge(colorSeriesMixin, { type: 'map', getExtremesFromAll: true, useMapGeometry: true, // get axis extremes from paths, not values forceDL: true, searchPoint: noop, // When tooltip is not shared, this series (and derivatives) requires direct // touch/hover. KD-tree does not apply. directTouch: true, // X axis and Y axis must have same translation slope preserveAspectRatio: true, pointArrayMap: ['value'], /** * Get the bounding box of all paths in the map combined. */ getBox: function (paths) { var MAX_VALUE = Number.MAX_VALUE, maxX = -MAX_VALUE, minX = MAX_VALUE, maxY = -MAX_VALUE, minY = MAX_VALUE, minRange = MAX_VALUE, xAxis = this.xAxis, yAxis = this.yAxis, hasBox; // Find the bounding box each(paths || [], function (point) { if (point.path) { if (typeof point.path === 'string') { point.path = H.splitPath(point.path); } var path = point.path || [], i = path.length, even = false, // while loop reads from the end pointMaxX = -MAX_VALUE, pointMinX = MAX_VALUE, pointMaxY = -MAX_VALUE, pointMinY = MAX_VALUE, properties = point.properties; // The first time a map point is used, analyze its box if (!point._foundBox) { while (i--) { if (isNumber(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, // bubbles etc point._midX = pointMinX + (pointMaxX - pointMinX) * pick( point.middleX, properties && properties['hc-middle-x'], 0.5 ); point._midY = pointMinY + (pointMaxY - pointMinY) * pick( point.middleY, properties && properties['hc-middle-y'], 0.5 ); point._maxX = pointMaxX; point._minX = pointMinX; point._maxY = pointMaxY; point._minY = pointMinY; point.labelrank = pick( point.labelrank, (pointMaxX - pointMinX) * (pointMaxY - pointMinY) ); point._foundBox = true; } maxX = Math.max(maxX, point._maxX); minX = Math.min(minX, point._minX); maxY = Math.max(maxY, point._maxY); minY = Math.min(minY, point._minY); minRange = Math.min( point._maxX - point._minX, point._maxY - point._minY, minRange ); hasBox = true; } }); // Set the box for the whole series if (hasBox) { this.minY = Math.min(minY, pick(this.minY, MAX_VALUE)); this.maxY = Math.max(maxY, pick(this.maxY, -MAX_VALUE)); this.minX = Math.min(minX, pick(this.minX, MAX_VALUE)); this.maxX = Math.max(maxX, pick(this.maxX, -MAX_VALUE)); // If no minRange option is set, set the default minimum zooming // range to 5 times the size of the smallest element if (xAxis && xAxis.options.minRange === undefined) { xAxis.minRange = Math.min( 5 * minRange, (this.maxX - this.minX) / 5, xAxis.minRange || MAX_VALUE ); } if (yAxis && yAxis.options.minRange === undefined) { yAxis.minRange = Math.min( 5 * minRange, (this.maxY - this.minY) / 5, yAxis.minRange || MAX_VALUE ); } } }, getExtremes: function () { // Get the actual value extremes for colors Series.prototype.getExtremes.call(this, this.valueData); // Recalculate box on updated data if (this.chart.hasRendered && this.isDirtyData) { this.getBox(this.options.data); } this.valueMin = this.dataMin; this.valueMax = this.dataMax; // Extremes for the mock Y axis this.dataMin = this.minY; this.dataMax = this.maxY; }, /** * 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, xMin = xAxis.min, xTransA = xAxis.transA, xMinPixelPadding = xAxis.minPixelPadding, yMin = yAxis.min, yTransA = yAxis.transA, yMinPixelPadding = yAxis.minPixelPadding, i, ret = []; // Preserve the original // Do the translation if (path) { i = path.length; while (i--) { if (isNumber(path[i])) { ret[i] = even ? (path[i] - xMin) * xTransA + xMinPixelPadding : (path[i] - yMin) * yTransA + yMinPixelPadding; even = !even; } else { ret[i] = path[i]; } } } return ret; }, /** * Extend setData to join in mapData. If the allAreas option is true, all * areas from the mapData are used, and those that don't correspond to a * data value are given null values. */ setData: function (data, redraw, animation, updatePoints) { var options = this.options, chartOptions = this.chart.options.chart, globalMapData = chartOptions && chartOptions.map, mapData = options.mapData, joinBy = options.joinBy, joinByNull = joinBy === null, pointArrayMap = options.keys || this.pointArrayMap, dataUsed = [], mapMap = {}, mapPoint, mapTransforms = this.chart.mapTransforms, props, i; // Collect mapData from chart options if not defined on series if (!mapData && globalMapData) { mapData = typeof globalMapData === 'string' ? H.maps[globalMapData] : globalMapData; } if (joinByNull) { joinBy = '_i'; } joinBy = this.joinBy = splat(joinBy); if (!joinBy[1]) { joinBy[1] = joinBy[0]; } // Pick up numeric values, add index // Convert Array point definitions to objects using pointArrayMap if (data) { each(data, function (val, i) { var ix = 0; if (isNumber(val)) { data[i] = { value: val }; } else if (isArray(val)) { data[i] = {}; // Automatically copy first item to hc-key if there is an // extra leading string if ( !options.keys && val.length > pointArrayMap.length && typeof val[0] === 'string' ) { data[i]['hc-key'] = val[0]; ++ix; } // Run through pointArrayMap and what's left of the point // data array in parallel, copying over the values for (var j = 0; j < pointArrayMap.length; ++j, ++ix) { if (pointArrayMap[j] && val[ix] !== undefined) { if (pointArrayMap[j].indexOf('.') > 0) { H.Point.prototype.setNestedProperty( data[i], val[ix], pointArrayMap[j] ); } else { data[i][pointArrayMap[j]] = val[ix]; } } } } if (joinByNull) { data[i]._i = i; } }); } this.getBox(data); // Pick up transform definitions for chart this.chart.mapTransforms = mapTransforms = chartOptions && chartOptions.mapTransforms || mapData && mapData['hc-transform'] || mapTransforms; // Cache cos/sin of transform rotation angle if (mapTransforms) { H.objectEach(mapTransforms, function (transform) { if (transform.rotation) { transform.cosAngle = Math.cos(transform.rotation); transform.sinAngle = Math.sin(transform.rotation); } }); } if (mapData) { if (mapData.type === 'FeatureCollection') { this.mapTitle = mapData.title; mapData = H.geojson(mapData, this.type, this); } this.mapData = mapData; this.mapMap = {}; for (i = 0; i < mapData.length; i++) { mapPoint = mapData[i]; props = mapPoint.properties; mapPoint._i = i; // Copy the property over to root for faster access if (joinBy[0] && props && props[joinBy[0]]) { mapPoint[joinBy[0]] = props[joinBy[0]]; } mapMap[mapPoint[joinBy[0]]] = mapPoint; } this.mapMap = mapMap; // Registered the point codes that actually hold data if (data && joinBy[1]) { each(data, function (point) { if (mapMap[point[joinBy[1]]]) { dataUsed.push(mapMap[point[joinBy[1]]]); } }); } if (options.allAreas) { this.getBox(mapData); data = data || []; // Registered the point codes that actually hold data if (joinBy[1]) { each(data, function (point) { dataUsed.push(point[joinBy[1]]); }); } // Add those map points that don't correspond to data, which // will be drawn as null points dataUsed = '|' + map(dataUsed, function (point) { return point && point[joinBy[0]]; }).join('|') + '|'; // Faster than array.indexOf each(mapData, function (mapPoint) { if ( !joinBy[0] || dataUsed.indexOf('|' + mapPoint[joinBy[0]] + '|') === -1 ) { data.push(merge(mapPoint, { value: null })); // #5050 - adding all areas causes the update // optimization of setData to kick in, even though the // point order has changed updatePoints = false; } }); } else { this.getBox(dataUsed); // Issue #4784 } } Series.prototype.setData.call( this, data, redraw, animation, updatePoints ); }, /** * No graph for the map series */ drawGraph: noop, /** * We need the points' bounding boxes in order to draw the data labels, so * we skip it now and call it from drawPoints instead. */ drawDataLabels: noop, /** * Allow a quick redraw by just translating the area group. Used for zooming * and panning in capable browsers. */ doFullTranslate: function () { return ( this.isDirtyData || this.chart.isResizing || this.chart.renderer.isVML || !this.baseTrans ); }, /** * Add the path option for data points. Find the max value for color * calculation. */ translate: function () { var series = this, xAxis = series.xAxis, yAxis = series.yAxis, doFullTranslate = series.doFullTranslate(); series.generatePoints(); each(series.data, function (point) { // Record the middle point (loosely based on centroid), determined // by the middleX and middleY options. point.plotX = xAxis.toPixels(point._midX, true); point.plotY = yAxis.toPixels(point._midY, true); if (doFullTranslate) { point.shapeType = 'path'; point.shapeArgs = { d: series.translatePath(point.path) }; } }); series.translateColors(); }, /** * Get presentational attributes. In the maps series this runs in both * styled and non-styled mode, because colors hold data when a colorAxis * is used. */ pointAttribs: function (point, state) { var attr; attr = seriesTypes.column.prototype.pointAttribs.call( this, point, state ); // If vector-effect is not supported, we set the stroke-width on the // group element and let all point graphics inherit. That way we don't // have to iterate over all points to update the stroke-width on // zooming. if (supportsVectorEffect) { attr['vector-effect'] = 'non-scaling-stroke'; } else { attr['stroke-width'] = 'inherit'; } return attr; }, /** * 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, group = series.group, chart = series.chart, renderer = chart.renderer, scaleX, scaleY, translateX, translateY, baseTrans = this.baseTrans, transformGroup, startTranslateX, startTranslateY, startScaleX, startScaleY; // Set a group that handles transform during zooming and panning in // order to preserve clipping on series.group if (!series.transformGroup) { series.transformGroup = renderer.g() .attr({ scaleX: 1, scaleY: 1 }) .add(group); series.transformGroup.survive = true; } // Draw the shapes again if (series.doFullTranslate()) { // Individual point actions. TODO: Check unstyled. if (chart.hasRendered) { each(series.points, function (point) { // Restore state color on update/redraw (#3529) if (point.shapeArgs) { point.shapeArgs.fill = series.pointAttribs( point, point.state ).fill; } }); } // Draw them in transformGroup series.group = series.transformGroup; seriesTypes.column.prototype.drawPoints.apply(series); series.group = group; // Reset // Add class names each(series.points, function (point) { if (point.graphic) { if (point.name) { point.graphic.addClass( 'highcharts-name-' + point.name.replace(/ /g, '-').toLowerCase() ); } if (point.properties && point.properties['hc-key']) { point.graphic.addClass( 'highcharts-key-' + point.properties['hc-key'].toLowerCase() ); } } }); // Set the base for later scale-zooming. The originX and originY // properties are the axis values in the plot area's upper left // corner. this.baseTrans = { originX: xAxis.min - xAxis.minPixelPadding / xAxis.transA, originY: ( yAxis.min - yAxis.minPixelPadding / yAxis.transA + (yAxis.reversed ? 0 : yAxis.len / yAxis.transA) ), transAX: xAxis.transA, transAY: yAxis.transA }; // Reset transformation in case we're doing a full translate (#3789) this.transformGroup.animate({ translateX: 0, translateY: 0, scaleX: 1, scaleY: 1 }); // Just update the scale and transform for better performance } else { scaleX = xAxis.transA / baseTrans.transAX; scaleY = yAxis.transA / baseTrans.transAY; translateX = xAxis.toPixels(baseTrans.originX, true); translateY = yAxis.toPixels(baseTrans.originY, true); // Handle rounding errors in normal view (#3789) if ( scaleX > 0.99 && scaleX < 1.01 && scaleY > 0.99 && scaleY < 1.01 ) { scaleX = 1; scaleY = 1; translateX = Math.round(translateX); translateY = Math.round(translateY); } // Animate or move to the new zoom level. In order to prevent // flickering as the different transform components are set out of // sync (#5991), we run a fake animator attribute and set scale and // translation synchronously in the same step. // A possible improvement to the API would be to handle this in the // renderer or animation engine itself, to ensure that when we are // animating multiple properties, we make sure that each step for // each property is performed in the same step. Also, for symbols // and for transform properties, it should induce a single // updateTransform and symbolAttr call. transformGroup = this.transformGroup; if (chart.renderer.globalAnimation) { startTranslateX = transformGroup.attr('translateX'); startTranslateY = transformGroup.attr('translateY'); startScaleX = transformGroup.attr('scaleX'); startScaleY = transformGroup.attr('scaleY'); transformGroup .attr({ animator: 0 }) .animate({ animator: 1 }, { step: function (now, fx) { transformGroup.attr({ translateX: startTranslateX + (translateX - startTranslateX) * fx.pos, translateY: startTranslateY + (translateY - startTranslateY) * fx.pos, scaleX: startScaleX + (scaleX - startScaleX) * fx.pos, scaleY: startScaleY + (scaleY - startScaleY) * fx.pos }); } }); // When dragging, animation is off. } else { transformGroup.attr({ translateX: translateX, translateY: translateY, scaleX: scaleX, scaleY: scaleY }); } } // Set the stroke-width directly on the group element so the children // inherit it. We need to use setAttribute directly, because the // stroke-widthSetter method expects a stroke color also to be set. if (!supportsVectorEffect) { series.group.element.setAttribute( 'stroke-width', series.options[ ( series.pointAttrToOptions && series.pointAttrToOptions['stroke-width'] ) || 'borderWidth' ] / (scaleX || 1) ); } this.drawMapDataLabels(); }, /** * Draw the data labels. Special for maps is the time that the data labels * are drawn (after points), and the clipping of the dataLabelsGroup. */ drawMapDataLabels: function () { Series.prototype.drawDataLabels.call(this); if (this.dataLabelsGroup) { this.dataLabelsGroup.clip(this.chart.clipRect); } }, /** * Override render to throw in an async call in IE8. Otherwise it chokes on * the US counties demo. */ render: function () { var series = this, render = Series.prototype.render; // Give IE8 some time to breathe. if (series.chart.renderer.isVML && series.data.length > 3000) { setTimeout(function () { render.call(series); }); } else { render.call(series); } }, /** * The initial animation for the map series. By default, animation is * disabled. Animation of map shapes is not at all supported in VML * browsers. */ animate: function (init) { var chart = this.chart, animation = this.options.animation, group = this.group, xAxis = this.xAxis, yAxis = this.yAxis, left = xAxis.pos, top = yAxis.pos; if (chart.renderer.isSVG) { if (animation === true) { animation = { duration: 1000 }; } // Initialize the animation if (init) { // Scale down the group and place it in the center group.attr({ translateX: left + xAxis.len / 2, translateY: top + yAxis.len / 2, scaleX: 0.001, // #1499 scaleY: 0.001 }); // Run the animation } else { group.animate({ translateX: left, translateY: top, scaleX: 1, scaleY: 1 }, animation); // Delete this function to allow it only once this.animate = null; } } }, /** * 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 }; each(this.points, function (point) { if (point.graphic) { point.graphic .attr(level.shapeArgs) .animate({ scaleX: 1, scaleY: 1, translateX: 0, translateY: 0 }, animationOptions); } }); this.animate = null; } }, drawLegendSymbol: LegendSymbolMixin.drawRectangle, /** * 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); } // Point class }), extend({ /** * Extend the Point object to split paths */ applyOptions: function (options, x) { var point = Point.prototype.applyOptions.call(this, options, x), series = this.series, joinBy = series.joinBy, mapPoint; if (series.mapData) { mapPoint = point[joinBy[1]] !== undefined && series.mapMap[point[joinBy[1]]]; if (mapPoint) { // This applies only to bubbles if (series.xyFromShape) { point.x = mapPoint._midX; point.y = mapPoint._midY; } extend(point, mapPoint); // copy over properties } else { point.value = point.value || null; } } return point; }, /** * Stop the fade-out */ onMouseOver: function (e) { H.clearTimeout(this.colorInterval); if (this.value !== null || this.series.options.nullInteraction) { Point.prototype.onMouseOver.call(this, e); } else { // #3401 Tooltip doesn't hide when hovering over null points this.series.onMouseOut(e); } }, /** * Highmaps only. Zoom in on the point using the global animation. * * @function #zoomTo * @memberOf Point * @sample maps/members/point-zoomto/ * Zoom to points from butons */ zoomTo: function () { var point = this, series = point.series; series.xAxis.setExtremes( point._minX, point._maxX, false ); series.yAxis.setExtremes( point._minY, point._maxY, false ); series.chart.redraw(); } }, colorPointMixin)); /** * An array of objects containing a `path` definition and optionally * a code or property to join in the data as per the `joinBy` option. * * @type {Array} * @sample {highmaps} maps/demo/category-map/ Map data and joinBy * @product highmaps * @apioption series.mapData */ /** * A `map` series. If the [type](#series.map.type) option is not specified, * it is inherited from [chart.type](#chart.type). * * @type {Object} * @extends series,plotOptions.map * @excluding dataParser,dataURL,marker * @product highmaps * @apioption series.map */ /** * An array of data points for the series. For the `map` series type, * points can be given in the following ways: * * 1. An array of numerical values. In this case, the numerical values * will be interpreted as `value` options. Example: * * ```js * data: [0, 5, 3, 5] * ``` * * 2. An array of arrays with 2 values. In this case, the values correspond * to `[hc-key, value]`. Example: * * ```js * data: [ * ['us-ny', 0], * ['us-mi', 5], * ['us-tx', 3], * ['us-ak', 5] * ] * ``` * * 3. An array of objects with named values. The objects are point * configuration objects as seen below. If the total number of data * points exceeds the series' [turboThreshold](#series.map.turboThreshold), * this option is not available. * * ```js * data: [{ * value: 6, * name: "Point2", * color: "#00FF00" * }, { * value: 6, * name: "Point1", * color: "#FF00FF" * }] * ``` * * @type {Array} * @product highmaps * @apioption series.map.data */ /** * Individual color for the point. By default the color is either used * to denote the value, or pulled from the global `colors` array. * * @type {Color} * @default undefined * @product highmaps * @apioption series.map.data.color */ /** * Individual data label for each point. The options are the same as * the ones for [plotOptions.series.dataLabels]( * #plotOptions.series.dataLabels). * * @type {Object} * @sample {highmaps} maps/series/data-datalabels/ * Disable data labels for individual areas * @product highmaps * @apioption series.map.data.dataLabels */ /** * The `id` of a series in the [drilldown.series](#drilldown.series) * array to use for a drilldown for this point. * * @type {String} * @sample {highmaps} maps/demo/map-drilldown/ Basic drilldown * @product highmaps * @apioption series.map.data.drilldown */ /** * An id for the point. This can be used after render time to get a * pointer to the point object through `chart.get()`. * * @type {String} * @sample {highmaps} maps/series/data-id/ Highlight a point by id * @product highmaps * @apioption series.map.data.id */ /** * When data labels are laid out on a map, Highmaps runs a simplified * algorithm to detect collision. When two labels collide, the one with * the lowest rank is hidden. By default the rank is computed from the * area. * * @type {Number} * @product highmaps * @apioption series.map.data.labelrank */ /** * The relative mid point of an area, used to place the data label. * Ranges from 0 to 1\. When `mapData` is used, middleX can be defined * there. * * @type {Number} * @default 0.5 * @product highmaps * @apioption series.map.data.middleX */ /** * The relative mid point of an area, used to place the data label. * Ranges from 0 to 1\. When `mapData` is used, middleY can be defined * there. * * @type {Number} * @default 0.5 * @product highmaps * @apioption series.map.data.middleY */ /** * The name of the point as shown in the legend, tooltip, dataLabel * etc. * * @type {String} * @sample {highmaps} maps/series/data-datalabels/ Point names * @product highmaps * @apioption series.map.data.name */ /** * For map and mapline series types, the SVG path for the shape. For * compatibily with old IE, not all SVG path definitions are supported, * but M, L and C operators are safe. * * To achieve a better separation between the structure and the data, * it is recommended to use `mapData` to define that paths instead * of defining them on the data points themselves. * * @type {String} * @sample {highmaps} maps/series/data-path/ Paths defined in data * @product highmaps * @apioption series.map.data.path */ /** * The numeric value of the data point. * * @type {Number} * @product highmaps * @apioption series.map.data.value */ /** * Individual point events * * @extends plotOptions.series.point.events * @product highmaps * @apioption series.map.data.events */