1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294 |
- /* *
- * (c) 2010-2019 Torstein Honsi
- *
- * License: www.highcharts.com/license
- */
- /**
- * Map data object.
- *
- * @interface Highcharts.MapDataObject
- *//**
- * The SVG path.
- * @name Highcharts.MapDataObject#path
- * @type {Highcharts.SVGPathArray}
- *//**
- * The name of the data.
- * @name Highcharts.MapDataObject#name
- * @type {string|undefined}
- *//**
- * The GeoJSON meta data.
- * @name Highcharts.MapDataObject#properties
- * @type {object|undefined}
- */
- '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,
- extend = H.extend,
- isNumber = H.isNumber,
- LegendSymbolMixin = H.LegendSymbolMixin,
- 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;
- /**
- * @private
- * @class
- * @name Highcharts.seriesTypes.map
- *
- * @augments Highcharts.Series
- */
- seriesType(
- 'map',
- 'scatter',
- /**
- * The map series is used for basic choropleth maps, where each map area has
- * a color based on its value.
- *
- * @sample maps/demo/all-maps/
- * Choropleth map
- *
- * @extends plotOptions.scatter
- * @excluding marker
- * @product highmaps
- * @optionparent plotOptions.map
- */
- {
- animation: false, // makes the complex shapes slow
- dataLabels: {
- crop: false,
- formatter: function () { // #2945
- return this.point.value;
- },
- inside: true, // for the color
- overflow: false,
- padding: 0,
- verticalAlign: 'middle'
- },
- /** @ignore-option */
- marker: null,
- /**
- * The color to apply to null points.
- *
- * In styled mode, the null point fill is set in the
- * `.highcharts-null-point` class.
- *
- * @sample maps/demo/all-areas-as-null/
- * Null color
- *
- * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
- */
- nullColor: '#f7f7f7',
- /**
- * Whether to allow pointer interaction like tooltips and mouse events
- * on null points.
- *
- * @type {boolean}
- * @since 4.2.7
- * @apioption plotOptions.map.nullInteraction
- */
- stickyTracking: false,
- tooltip: {
- followPointer: true,
- pointFormat: '{point.name}: {point.value}<br/>'
- },
- /** @ignore-option */
- turboThreshold: 0,
- /**
- * 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.
- *
- * @sample maps/plotoptions/series-allareas-false/
- * All areas set to false
- *
- * @type {boolean}
- * @default true
- * @product highmaps
- * @apioption plotOptions.series.allAreas
- */
- allAreas: true,
- /**
- * The border color of the map areas.
- *
- * In styled mode, the border stroke is given in the `.highcharts-point`
- * class.
- *
- * @sample {highmaps} maps/plotoptions/series-border/
- * Borders demo
- *
- * @type {Highcharts.ColorString}
- * @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 maps/plotoptions/series-border/
- * Borders demo
- *
- * @type {number}
- * @default 1
- * @product highmaps
- * @apioption plotOptions.series.borderWidth
- */
- borderWidth: 1,
- /**
- * 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}
- * @product highmaps
- * @apioption plotOptions.series.colorAxis
- */
- /**
- * 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.
- *
- * @sample maps/plotoptions/series-border/
- * Joined by "code"
- * @sample maps/demo/geojson/
- * GeoJSON joined by an array
- * @sample maps/series/joinby-null/
- * Simple data joined by null
- *
- * @type {string|Array<string>}
- * @default hc-key
- * @product highmaps
- * @apioption plotOptions.series.joinBy
- */
- joinBy: 'hc-key',
- /**
- * Define the z index of the series.
- *
- * @type {number}
- * @product highmaps
- * @apioption plotOptions.series.zIndex
- */
- states: {
- hover: {
- halo: null,
- /**
- * The color of the shape in this state.
- *
- * @sample maps/plotoptions/series-states-hover/
- * Hover options
- *
- * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
- * @product highmaps
- * @apioption plotOptions.series.states.hover.color
- */
- /**
- * The border color of the point in this state.
- *
- * @type {Highcharts.ColorString}
- * @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}
- * @product highmaps
- * @default 0.2
- * @apioption plotOptions.series.states.hover.brightness
- */
- brightness: 0.2
- },
- /**
- * Overrides for the normal state.
- *
- * @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.
- *
- * @sample maps/plotoptions/series-states-animation-false/
- * No animation of fill color
- *
- * @type {boolean|Highcharts.AnimationOptionsObject}
- * @default true
- * @product highmaps
- * @apioption plotOptions.series.states.normal.animation
- */
- animation: true
- },
- 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
- (paths || []).forEach(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 it automatically fits into the plot area box
- 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) {
- data.forEach(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]) {
- data.forEach(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]) {
- data.forEach(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 = '|' + dataUsed.map(function (point) {
- return point && point[joinBy[0]];
- }).join('|') + '|'; // Faster than array.indexOf
- mapData.forEach(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();
- series.data.forEach(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 = point.series.chart.styledMode ?
- this.colorAttribs(point) :
- seriesTypes.column.prototype.pointAttribs.call(
- this, point, state
- );
- // 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.
- attr['stroke-width'] = pick(
- point.options[
- (
- this.pointAttrToOptions &&
- this.pointAttrToOptions['stroke-width']
- ) || 'borderWidth'
- ],
- '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.
- if (chart.hasRendered && !chart.styledMode) {
- series.points.forEach(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
- series.points.forEach(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()
- );
- }
- // In styled mode, apply point colors by CSS
- if (chart.styledMode) {
- point.graphic.css(
- series.pointAttribs(
- point,
- point.selected && 'select'
- )
- );
- }
- }
- });
- // 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 (!chart.styledMode) {
- group.element.setAttribute(
- 'stroke-width',
- (
- series.options[
- (
- series.pointAttrToOptions &&
- series.pointAttrToOptions['stroke-width']
- ) || 'borderWidth'
- ] ||
- 1 // Styled mode
- ) / (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
- };
- this.points.forEach(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.
- *
- * @sample maps/members/point-zoomto/
- * Zoom to points from butons
- *
- * @requires module:modules/map
- *
- * @function Highcharts.Point#zoomTo
- */
- 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)
- );
- /**
- * A map data object containing a `path` definition and optionally additional
- * properties to join in the data as per the `joinBy` option.
- *
- * @sample maps/demo/category-map/
- * Map data and joinBy
- *
- * @type {Highcharts.MapDataObject|Array<Highcharts.MapDataObject>}
- * @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).
- *
- * @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 following snippet shows only a
- * few settings, see the complete options set 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<number|Array<string,number>|*>}
- * @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 {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
- * @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).
- *
- * @sample maps/series/data-datalabels/
- * Disable data labels for individual areas
- *
- * @type {Object}
- * @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.
- *
- * @sample maps/demo/map-drilldown/
- * Basic drilldown
- *
- * @type {string}
- * @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()`.
- *
- * @sample maps/series/data-id/
- * Highlight a point by id
- *
- * @type {string}
- * @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.
- *
- * @sample maps/series/data-datalabels/
- * Point names
- *
- * @type {string}
- * @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.
- *
- * @sample maps/series/data-path/
- * Paths defined in data
- *
- * @type {string}
- * @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
- */
|