MapSeries.js 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137
  1. /* *
  2. *
  3. * (c) 2010-2021 Torstein Honsi
  4. *
  5. * License: www.highcharts.com/license
  6. *
  7. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  8. *
  9. * */
  10. 'use strict';
  11. var __extends = (this && this.__extends) || (function () {
  12. var extendStatics = function (d, b) {
  13. extendStatics = Object.setPrototypeOf ||
  14. ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
  15. function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
  16. return extendStatics(d, b);
  17. };
  18. return function (d, b) {
  19. extendStatics(d, b);
  20. function __() { this.constructor = d; }
  21. d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
  22. };
  23. })();
  24. import ColorMapMixin from '../../Mixins/ColorMapSeries.js';
  25. var colorMapSeriesMixin = ColorMapMixin.colorMapSeriesMixin;
  26. import H from '../../Core/Globals.js';
  27. var noop = H.noop;
  28. import LegendSymbolMixin from '../../Mixins/LegendSymbol.js';
  29. import mapModule from '../../Maps/Map.js';
  30. var maps = mapModule.maps, splitPath = mapModule.splitPath;
  31. import MapPoint from './MapPoint.js';
  32. import palette from '../../Core/Color/Palette.js';
  33. import Series from '../../Core/Series/Series.js';
  34. import SeriesRegistry from '../../Core/Series/SeriesRegistry.js';
  35. var
  36. // indirect dependency to keep product size low
  37. _a = SeriesRegistry.seriesTypes, ColumnSeries = _a.column, ScatterSeries = _a.scatter;
  38. import SVGRenderer from '../../Core/Renderer/SVG/SVGRenderer.js';
  39. import U from '../../Core/Utilities.js';
  40. var extend = U.extend, fireEvent = U.fireEvent, getNestedProperty = U.getNestedProperty, isArray = U.isArray, isNumber = U.isNumber, merge = U.merge, objectEach = U.objectEach, pick = U.pick, splat = U.splat;
  41. /* *
  42. *
  43. * Class
  44. *
  45. * */
  46. /**
  47. * @private
  48. * @class
  49. * @name Highcharts.seriesTypes.map
  50. *
  51. * @augments Highcharts.Series
  52. */
  53. var MapSeries = /** @class */ (function (_super) {
  54. __extends(MapSeries, _super);
  55. function MapSeries() {
  56. /* *
  57. *
  58. * Static Properties
  59. *
  60. * */
  61. var _this = _super !== null && _super.apply(this, arguments) || this;
  62. /* *
  63. *
  64. * Properties
  65. *
  66. * */
  67. _this.baseTrans = void 0;
  68. _this.chart = void 0;
  69. _this.data = void 0;
  70. _this.group = void 0;
  71. _this.joinBy = void 0;
  72. _this.options = void 0;
  73. _this.points = void 0;
  74. _this.transformGroup = void 0;
  75. return _this;
  76. /* eslint-enable valid-jsdoc */
  77. }
  78. /* *
  79. *
  80. * Functions
  81. *
  82. * */
  83. /* eslint-disable valid-jsdoc */
  84. /**
  85. * The initial animation for the map series. By default, animation is
  86. * disabled. Animation of map shapes is not at all supported in VML
  87. * browsers.
  88. * @private
  89. */
  90. MapSeries.prototype.animate = function (init) {
  91. var chart = this.chart, animation = this.options.animation, group = this.group, xAxis = this.xAxis, yAxis = this.yAxis, left = xAxis.pos, top = yAxis.pos;
  92. if (chart.renderer.isSVG) {
  93. if (animation === true) {
  94. animation = {
  95. duration: 1000
  96. };
  97. }
  98. // Initialize the animation
  99. if (init) {
  100. // Scale down the group and place it in the center
  101. group.attr({
  102. translateX: left + xAxis.len / 2,
  103. translateY: top + yAxis.len / 2,
  104. scaleX: 0.001,
  105. scaleY: 0.001
  106. });
  107. // Run the animation
  108. }
  109. else {
  110. group.animate({
  111. translateX: left,
  112. translateY: top,
  113. scaleX: 1,
  114. scaleY: 1
  115. }, animation);
  116. }
  117. }
  118. };
  119. /**
  120. * Animate in the new series from the clicked point in the old series.
  121. * Depends on the drilldown.js module
  122. * @private
  123. */
  124. MapSeries.prototype.animateDrilldown = function (init) {
  125. var toBox = this.chart.plotBox, level = this.chart.drilldownLevels[this.chart.drilldownLevels.length - 1], fromBox = level.bBox, animationOptions = this.chart.options.drilldown.animation, scale;
  126. if (!init) {
  127. scale = Math.min(fromBox.width / toBox.width, fromBox.height / toBox.height);
  128. level.shapeArgs = {
  129. scaleX: scale,
  130. scaleY: scale,
  131. translateX: fromBox.x,
  132. translateY: fromBox.y
  133. };
  134. this.points.forEach(function (point) {
  135. if (point.graphic) {
  136. point.graphic
  137. .attr(level.shapeArgs)
  138. .animate({
  139. scaleX: 1,
  140. scaleY: 1,
  141. translateX: 0,
  142. translateY: 0
  143. }, animationOptions);
  144. }
  145. });
  146. }
  147. };
  148. /**
  149. * When drilling up, pull out the individual point graphics from the lower
  150. * series and animate them into the origin point in the upper series.
  151. * @private
  152. */
  153. MapSeries.prototype.animateDrillupFrom = function (level) {
  154. ColumnSeries.prototype.animateDrillupFrom.call(this, level);
  155. };
  156. /**
  157. * When drilling up, keep the upper series invisible until the lower series
  158. * has moved into place.
  159. * @private
  160. */
  161. MapSeries.prototype.animateDrillupTo = function (init) {
  162. ColumnSeries.prototype.animateDrillupTo.call(this, init);
  163. };
  164. /**
  165. * Allow a quick redraw by just translating the area group. Used for zooming
  166. * and panning in capable browsers.
  167. * @private
  168. */
  169. MapSeries.prototype.doFullTranslate = function () {
  170. return (this.isDirtyData ||
  171. this.chart.isResizing ||
  172. this.chart.renderer.isVML ||
  173. !this.baseTrans);
  174. };
  175. /**
  176. * Draw the data labels. Special for maps is the time that the data labels
  177. * are drawn (after points), and the clipping of the dataLabelsGroup.
  178. * @private
  179. */
  180. MapSeries.prototype.drawMapDataLabels = function () {
  181. Series.prototype.drawDataLabels.call(this);
  182. if (this.dataLabelsGroup) {
  183. this.dataLabelsGroup.clip(this.chart.clipRect);
  184. }
  185. };
  186. /**
  187. * Use the drawPoints method of column, that is able to handle simple
  188. * shapeArgs. Extend it by assigning the tooltip position.
  189. * @private
  190. */
  191. MapSeries.prototype.drawPoints = function () {
  192. 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;
  193. // Set a group that handles transform during zooming and panning in
  194. // order to preserve clipping on series.group
  195. if (!series.transformGroup) {
  196. series.transformGroup = renderer.g()
  197. .attr({
  198. scaleX: 1,
  199. scaleY: 1
  200. })
  201. .add(group);
  202. series.transformGroup.survive = true;
  203. }
  204. // Draw the shapes again
  205. if (series.doFullTranslate()) {
  206. // Individual point actions.
  207. if (chart.hasRendered && !chart.styledMode) {
  208. series.points.forEach(function (point) {
  209. // Restore state color on update/redraw (#3529)
  210. if (point.shapeArgs) {
  211. point.shapeArgs.fill = series.pointAttribs(point, point.state).fill;
  212. }
  213. });
  214. }
  215. // Draw them in transformGroup
  216. series.group = series.transformGroup;
  217. ColumnSeries.prototype.drawPoints.apply(series);
  218. series.group = group; // Reset
  219. // Add class names
  220. series.points.forEach(function (point) {
  221. if (point.graphic) {
  222. var className = '';
  223. if (point.name) {
  224. className +=
  225. 'highcharts-name-' +
  226. point.name.replace(/ /g, '-').toLowerCase();
  227. }
  228. if (point.properties &&
  229. point.properties['hc-key']) {
  230. className +=
  231. ' highcharts-key-' +
  232. point.properties['hc-key'].toLowerCase();
  233. }
  234. if (className) {
  235. point.graphic.addClass(className);
  236. }
  237. // In styled mode, apply point colors by CSS
  238. if (chart.styledMode) {
  239. point.graphic.css(series.pointAttribs(point, point.selected && 'select' || void 0));
  240. }
  241. }
  242. });
  243. // Set the base for later scale-zooming. The originX and originY
  244. // properties are the axis values in the plot area's upper left
  245. // corner.
  246. this.baseTrans = {
  247. originX: (xAxis.min -
  248. xAxis.minPixelPadding / xAxis.transA),
  249. originY: (yAxis.min -
  250. yAxis.minPixelPadding / yAxis.transA +
  251. (yAxis.reversed ? 0 : yAxis.len / yAxis.transA)),
  252. transAX: xAxis.transA,
  253. transAY: yAxis.transA
  254. };
  255. // Reset transformation in case we're doing a full translate
  256. // (#3789)
  257. this.transformGroup.animate({
  258. translateX: 0,
  259. translateY: 0,
  260. scaleX: 1,
  261. scaleY: 1
  262. });
  263. // Just update the scale and transform for better performance
  264. }
  265. else {
  266. scaleX = xAxis.transA / baseTrans.transAX;
  267. scaleY = yAxis.transA / baseTrans.transAY;
  268. translateX = xAxis.toPixels(baseTrans.originX, true);
  269. translateY = yAxis.toPixels(baseTrans.originY, true);
  270. // Handle rounding errors in normal view (#3789)
  271. if (scaleX > 0.99 &&
  272. scaleX < 1.01 &&
  273. scaleY > 0.99 &&
  274. scaleY < 1.01) {
  275. scaleX = 1;
  276. scaleY = 1;
  277. translateX = Math.round(translateX);
  278. translateY = Math.round(translateY);
  279. }
  280. /* Animate or move to the new zoom level. In order to prevent
  281. flickering as the different transform components are set out
  282. of sync (#5991), we run a fake animator attribute and set
  283. scale and translation synchronously in the same step.
  284. A possible improvement to the API would be to handle this in
  285. the renderer or animation engine itself, to ensure that when
  286. we are animating multiple properties, we make sure that each
  287. step for each property is performed in the same step. Also,
  288. for symbols and for transform properties, it should induce a
  289. single updateTransform and symbolAttr call. */
  290. transformGroup = this.transformGroup;
  291. if (chart.renderer.globalAnimation) {
  292. startTranslateX = transformGroup.attr('translateX');
  293. startTranslateY = transformGroup.attr('translateY');
  294. startScaleX = transformGroup.attr('scaleX');
  295. startScaleY = transformGroup.attr('scaleY');
  296. transformGroup
  297. .attr({ animator: 0 })
  298. .animate({
  299. animator: 1
  300. }, {
  301. step: function (now, fx) {
  302. transformGroup.attr({
  303. translateX: (startTranslateX +
  304. (translateX - startTranslateX) * fx.pos),
  305. translateY: (startTranslateY +
  306. (translateY - startTranslateY) * fx.pos),
  307. scaleX: (startScaleX +
  308. (scaleX - startScaleX) *
  309. fx.pos),
  310. scaleY: (startScaleY +
  311. (scaleY - startScaleY) * fx.pos)
  312. });
  313. }
  314. });
  315. // When dragging, animation is off.
  316. }
  317. else {
  318. transformGroup.attr({
  319. translateX: translateX,
  320. translateY: translateY,
  321. scaleX: scaleX,
  322. scaleY: scaleY
  323. });
  324. }
  325. }
  326. /* Set the stroke-width directly on the group element so the
  327. children inherit it. We need to use setAttribute directly,
  328. because the stroke-widthSetter method expects a stroke color also
  329. to be set. */
  330. if (!chart.styledMode) {
  331. group.element.setAttribute('stroke-width', (pick(series.options[(series.pointAttrToOptions &&
  332. series.pointAttrToOptions['stroke-width']) || 'borderWidth'], 1 // Styled mode
  333. ) / (scaleX || 1)));
  334. }
  335. this.drawMapDataLabels();
  336. };
  337. /**
  338. * Get the bounding box of all paths in the map combined.
  339. * @private
  340. */
  341. MapSeries.prototype.getBox = function (paths) {
  342. 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;
  343. // Find the bounding box
  344. (paths || []).forEach(function (point) {
  345. if (point.path) {
  346. if (typeof point.path === 'string') {
  347. point.path = splitPath(point.path);
  348. // Legacy one-dimensional array
  349. }
  350. else if (point.path[0] === 'M') {
  351. point.path = SVGRenderer.prototype.pathToSegments(point.path);
  352. }
  353. var path = point.path || [], pointMaxX = -MAX_VALUE, pointMinX = MAX_VALUE, pointMaxY = -MAX_VALUE, pointMinY = MAX_VALUE, properties = point.properties;
  354. // The first time a map point is used, analyze its box
  355. if (!point._foundBox) {
  356. path.forEach(function (seg) {
  357. var x = seg[seg.length - 2];
  358. var y = seg[seg.length - 1];
  359. if (typeof x === 'number' && typeof y === 'number') {
  360. pointMinX = Math.min(pointMinX, x);
  361. pointMaxX = Math.max(pointMaxX, x);
  362. pointMinY = Math.min(pointMinY, y);
  363. pointMaxY = Math.max(pointMaxY, y);
  364. }
  365. });
  366. // Cache point bounding box for use to position data
  367. // labels, bubbles etc
  368. point._midX = (pointMinX + (pointMaxX - pointMinX) * pick(point.middleX, properties &&
  369. properties['hc-middle-x'], 0.5));
  370. point._midY = (pointMinY + (pointMaxY - pointMinY) * pick(point.middleY, properties &&
  371. properties['hc-middle-y'], 0.5));
  372. point._maxX = pointMaxX;
  373. point._minX = pointMinX;
  374. point._maxY = pointMaxY;
  375. point._minY = pointMinY;
  376. point.labelrank = pick(point.labelrank, (pointMaxX - pointMinX) * (pointMaxY - pointMinY));
  377. point._foundBox = true;
  378. }
  379. maxX = Math.max(maxX, point._maxX);
  380. minX = Math.min(minX, point._minX);
  381. maxY = Math.max(maxY, point._maxY);
  382. minY = Math.min(minY, point._minY);
  383. minRange = Math.min(point._maxX - point._minX, point._maxY - point._minY, minRange);
  384. hasBox = true;
  385. }
  386. });
  387. // Set the box for the whole series
  388. if (hasBox) {
  389. this.minY = Math.min(minY, pick(this.minY, MAX_VALUE));
  390. this.maxY = Math.max(maxY, pick(this.maxY, -MAX_VALUE));
  391. this.minX = Math.min(minX, pick(this.minX, MAX_VALUE));
  392. this.maxX = Math.max(maxX, pick(this.maxX, -MAX_VALUE));
  393. // If no minRange option is set, set the default minimum zooming
  394. // range to 5 times the size of the smallest element
  395. if (xAxis && typeof xAxis.options.minRange === 'undefined') {
  396. xAxis.minRange = Math.min(5 * minRange, (this.maxX - this.minX) / 5, xAxis.minRange || MAX_VALUE);
  397. }
  398. if (yAxis && typeof yAxis.options.minRange === 'undefined') {
  399. yAxis.minRange = Math.min(5 * minRange, (this.maxY - this.minY) / 5, yAxis.minRange || MAX_VALUE);
  400. }
  401. }
  402. };
  403. MapSeries.prototype.getExtremes = function () {
  404. // Get the actual value extremes for colors
  405. var _a = Series.prototype.getExtremes
  406. .call(this, this.valueData), dataMin = _a.dataMin, dataMax = _a.dataMax;
  407. // Recalculate box on updated data
  408. if (this.chart.hasRendered && this.isDirtyData) {
  409. this.getBox(this.options.data);
  410. }
  411. if (isNumber(dataMin)) {
  412. this.valueMin = dataMin;
  413. }
  414. if (isNumber(dataMax)) {
  415. this.valueMax = dataMax;
  416. }
  417. // Extremes for the mock Y axis
  418. return { dataMin: this.minY, dataMax: this.maxY };
  419. };
  420. /**
  421. * Define hasData function for non-cartesian series. Returns true if the
  422. * series has points at all.
  423. * @private
  424. */
  425. MapSeries.prototype.hasData = function () {
  426. return !!this.processedXData.length; // != 0
  427. };
  428. /**
  429. * Get presentational attributes. In the maps series this runs in both
  430. * styled and non-styled mode, because colors hold data when a colorAxis is
  431. * used.
  432. * @private
  433. */
  434. MapSeries.prototype.pointAttribs = function (point, state) {
  435. var attr = point.series.chart.styledMode ?
  436. this.colorAttribs(point) :
  437. ColumnSeries.prototype.pointAttribs.call(this, point, state);
  438. // Set the stroke-width on the group element and let all point
  439. // graphics inherit. That way we don't have to iterate over all
  440. // points to update the stroke-width on zooming.
  441. attr['stroke-width'] = pick(point.options[(this.pointAttrToOptions &&
  442. this.pointAttrToOptions['stroke-width']) || 'borderWidth'], 'inherit');
  443. return attr;
  444. };
  445. /**
  446. * Override render to throw in an async call in IE8. Otherwise it chokes on
  447. * the US counties demo.
  448. * @private
  449. */
  450. MapSeries.prototype.render = function () {
  451. var series = this, render = Series.prototype.render;
  452. // Give IE8 some time to breathe.
  453. if (series.chart.renderer.isVML && series.data.length > 3000) {
  454. setTimeout(function () {
  455. render.call(series);
  456. });
  457. }
  458. else {
  459. render.call(series);
  460. }
  461. };
  462. /**
  463. * Extend setData to join in mapData. If the allAreas option is true, all
  464. * areas from the mapData are used, and those that don't correspond to a
  465. * data value are given null values.
  466. * @private
  467. */
  468. MapSeries.prototype.setData = function (data, redraw, animation, updatePoints) {
  469. var options = this.options, chartOptions = this.chart.options.chart, globalMapData = chartOptions && chartOptions.map, mapData = options.mapData, joinBy = this.joinBy, pointArrayMap = options.keys || this.pointArrayMap, dataUsed = [], mapMap = {}, mapPoint, mapTransforms = this.chart.mapTransforms, props, i;
  470. // Collect mapData from chart options if not defined on series
  471. if (!mapData && globalMapData) {
  472. mapData = typeof globalMapData === 'string' ?
  473. maps[globalMapData] :
  474. globalMapData;
  475. }
  476. // Pick up numeric values, add index
  477. // Convert Array point definitions to objects using pointArrayMap
  478. if (data) {
  479. data.forEach(function (val, i) {
  480. var ix = 0;
  481. if (isNumber(val)) {
  482. data[i] = {
  483. value: val
  484. };
  485. }
  486. else if (isArray(val)) {
  487. data[i] = {};
  488. // Automatically copy first item to hc-key if there is
  489. // an extra leading string
  490. if (!options.keys &&
  491. val.length > pointArrayMap.length &&
  492. typeof val[0] === 'string') {
  493. data[i]['hc-key'] = val[0];
  494. ++ix;
  495. }
  496. // Run through pointArrayMap and what's left of the
  497. // point data array in parallel, copying over the values
  498. for (var j = 0; j < pointArrayMap.length; ++j, ++ix) {
  499. if (pointArrayMap[j] &&
  500. typeof val[ix] !== 'undefined') {
  501. if (pointArrayMap[j].indexOf('.') > 0) {
  502. MapPoint.prototype.setNestedProperty(data[i], val[ix], pointArrayMap[j]);
  503. }
  504. else {
  505. data[i][pointArrayMap[j]] =
  506. val[ix];
  507. }
  508. }
  509. }
  510. }
  511. if (joinBy && joinBy[0] === '_i') {
  512. data[i]._i = i;
  513. }
  514. });
  515. }
  516. this.getBox(data);
  517. // Pick up transform definitions for chart
  518. this.chart.mapTransforms = mapTransforms =
  519. chartOptions && chartOptions.mapTransforms ||
  520. mapData && mapData['hc-transform'] ||
  521. mapTransforms;
  522. // Cache cos/sin of transform rotation angle
  523. if (mapTransforms) {
  524. objectEach(mapTransforms, function (transform) {
  525. if (transform.rotation) {
  526. transform.cosAngle = Math.cos(transform.rotation);
  527. transform.sinAngle = Math.sin(transform.rotation);
  528. }
  529. });
  530. }
  531. if (mapData) {
  532. if (mapData.type === 'FeatureCollection') {
  533. this.mapTitle = mapData.title;
  534. mapData = H.geojson(mapData, this.type, this);
  535. }
  536. this.mapData = mapData;
  537. this.mapMap = {};
  538. for (i = 0; i < mapData.length; i++) {
  539. mapPoint = mapData[i];
  540. props = mapPoint.properties;
  541. mapPoint._i = i;
  542. // Copy the property over to root for faster access
  543. if (joinBy[0] && props && props[joinBy[0]]) {
  544. mapPoint[joinBy[0]] = props[joinBy[0]];
  545. }
  546. mapMap[mapPoint[joinBy[0]]] = mapPoint;
  547. }
  548. this.mapMap = mapMap;
  549. // Registered the point codes that actually hold data
  550. if (data && joinBy[1]) {
  551. var joinKey_1 = joinBy[1];
  552. data.forEach(function (pointOptions) {
  553. var mapKey = getNestedProperty(joinKey_1, pointOptions);
  554. if (mapMap[mapKey]) {
  555. dataUsed.push(mapMap[mapKey]);
  556. }
  557. });
  558. }
  559. if (options.allAreas) {
  560. this.getBox(mapData);
  561. data = data || [];
  562. // Registered the point codes that actually hold data
  563. if (joinBy[1]) {
  564. var joinKey_2 = joinBy[1];
  565. data.forEach(function (pointOptions) {
  566. dataUsed.push(getNestedProperty(joinKey_2, pointOptions));
  567. });
  568. }
  569. // Add those map points that don't correspond to data, which
  570. // will be drawn as null points
  571. dataUsed = ('|' + dataUsed.map(function (point) {
  572. return point && point[joinBy[0]];
  573. }).join('|') + '|'); // Faster than array.indexOf
  574. mapData.forEach(function (mapPoint) {
  575. if (!joinBy[0] ||
  576. dataUsed.indexOf('|' + mapPoint[joinBy[0]] + '|') === -1) {
  577. data.push(merge(mapPoint, { value: null }));
  578. // #5050 - adding all areas causes the update
  579. // optimization of setData to kick in, even though
  580. // the point order has changed
  581. updatePoints = false;
  582. }
  583. });
  584. }
  585. else {
  586. this.getBox(dataUsed); // Issue #4784
  587. }
  588. }
  589. Series.prototype.setData.call(this, data, redraw, animation, updatePoints);
  590. };
  591. /**
  592. * Extend setOptions by picking up the joinBy option and applying it to a
  593. * series property.
  594. * @private
  595. */
  596. MapSeries.prototype.setOptions = function (itemOptions) {
  597. var options = Series.prototype.setOptions.call(this, itemOptions), joinBy = options.joinBy, joinByNull = joinBy === null;
  598. if (joinByNull) {
  599. joinBy = '_i';
  600. }
  601. joinBy = this.joinBy = splat(joinBy);
  602. if (!joinBy[1]) {
  603. joinBy[1] = joinBy[0];
  604. }
  605. return options;
  606. };
  607. /**
  608. * Add the path option for data points. Find the max value for color
  609. * calculation.
  610. * @private
  611. */
  612. MapSeries.prototype.translate = function () {
  613. var series = this, xAxis = series.xAxis, yAxis = series.yAxis, doFullTranslate = series.doFullTranslate();
  614. series.generatePoints();
  615. series.data.forEach(function (point) {
  616. // Record the middle point (loosely based on centroid),
  617. // determined by the middleX and middleY options.
  618. if (isNumber(point._midX) && isNumber(point._midY)) {
  619. point.plotX = xAxis.toPixels(point._midX, true);
  620. point.plotY = yAxis.toPixels(point._midY, true);
  621. }
  622. if (doFullTranslate) {
  623. point.shapeType = 'path';
  624. point.shapeArgs = {
  625. d: series.translatePath(point.path)
  626. };
  627. }
  628. });
  629. fireEvent(series, 'afterTranslate');
  630. };
  631. /**
  632. * Translate the path, so it automatically fits into the plot area box.
  633. * @private
  634. */
  635. MapSeries.prototype.translatePath = function (path) {
  636. var series = this, xAxis = series.xAxis, yAxis = series.yAxis, xMin = xAxis.min, xTransA = xAxis.transA, xMinPixelPadding = xAxis.minPixelPadding, yMin = yAxis.min, yTransA = yAxis.transA, yMinPixelPadding = yAxis.minPixelPadding, ret = []; // Preserve the original
  637. // Do the translation
  638. if (path) {
  639. path.forEach(function (seg) {
  640. if (seg[0] === 'M') {
  641. ret.push([
  642. 'M',
  643. (seg[1] - (xMin || 0)) * xTransA + xMinPixelPadding,
  644. (seg[2] - (yMin || 0)) * yTransA + yMinPixelPadding
  645. ]);
  646. }
  647. else if (seg[0] === 'L') {
  648. ret.push([
  649. 'L',
  650. (seg[1] - (xMin || 0)) * xTransA + xMinPixelPadding,
  651. (seg[2] - (yMin || 0)) * yTransA + yMinPixelPadding
  652. ]);
  653. }
  654. else if (seg[0] === 'C') {
  655. ret.push([
  656. 'C',
  657. (seg[1] - (xMin || 0)) * xTransA + xMinPixelPadding,
  658. (seg[2] - (yMin || 0)) * yTransA + yMinPixelPadding,
  659. (seg[3] - (xMin || 0)) * xTransA + xMinPixelPadding,
  660. (seg[4] - (yMin || 0)) * yTransA + yMinPixelPadding,
  661. (seg[5] - (xMin || 0)) * xTransA + xMinPixelPadding,
  662. (seg[6] - (yMin || 0)) * yTransA + yMinPixelPadding
  663. ]);
  664. }
  665. else if (seg[0] === 'Q') {
  666. ret.push([
  667. 'Q',
  668. (seg[1] - (xMin || 0)) * xTransA + xMinPixelPadding,
  669. (seg[2] - (yMin || 0)) * yTransA + yMinPixelPadding,
  670. (seg[3] - (xMin || 0)) * xTransA + xMinPixelPadding,
  671. (seg[4] - (yMin || 0)) * yTransA + yMinPixelPadding
  672. ]);
  673. }
  674. else if (seg[0] === 'Z') {
  675. ret.push(['Z']);
  676. }
  677. });
  678. }
  679. return ret;
  680. };
  681. /**
  682. * The map series is used for basic choropleth maps, where each map area has
  683. * a color based on its value.
  684. *
  685. * @sample maps/demo/all-maps/
  686. * Choropleth map
  687. *
  688. * @extends plotOptions.scatter
  689. * @excluding marker, cluster
  690. * @product highmaps
  691. * @optionparent plotOptions.map
  692. */
  693. MapSeries.defaultOptions = merge(ScatterSeries.defaultOptions, {
  694. animation: false,
  695. dataLabels: {
  696. crop: false,
  697. formatter: function () {
  698. return this.point.value;
  699. },
  700. inside: true,
  701. overflow: false,
  702. padding: 0,
  703. verticalAlign: 'middle'
  704. },
  705. /**
  706. * @ignore-option
  707. *
  708. * @private
  709. */
  710. marker: null,
  711. /**
  712. * The color to apply to null points.
  713. *
  714. * In styled mode, the null point fill is set in the
  715. * `.highcharts-null-point` class.
  716. *
  717. * @sample maps/demo/all-areas-as-null/
  718. * Null color
  719. *
  720. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  721. *
  722. * @private
  723. */
  724. nullColor: palette.neutralColor3,
  725. /**
  726. * Whether to allow pointer interaction like tooltips and mouse events
  727. * on null points.
  728. *
  729. * @type {boolean}
  730. * @since 4.2.7
  731. * @apioption plotOptions.map.nullInteraction
  732. *
  733. * @private
  734. */
  735. stickyTracking: false,
  736. tooltip: {
  737. followPointer: true,
  738. pointFormat: '{point.name}: {point.value}<br/>'
  739. },
  740. /**
  741. * @ignore-option
  742. *
  743. * @private
  744. */
  745. turboThreshold: 0,
  746. /**
  747. * Whether all areas of the map defined in `mapData` should be rendered.
  748. * If `true`, areas which don't correspond to a data point, are rendered
  749. * as `null` points. If `false`, those areas are skipped.
  750. *
  751. * @sample maps/plotoptions/series-allareas-false/
  752. * All areas set to false
  753. *
  754. * @type {boolean}
  755. * @default true
  756. * @product highmaps
  757. * @apioption plotOptions.series.allAreas
  758. *
  759. * @private
  760. */
  761. allAreas: true,
  762. /**
  763. * The border color of the map areas.
  764. *
  765. * In styled mode, the border stroke is given in the `.highcharts-point`
  766. * class.
  767. *
  768. * @sample {highmaps} maps/plotoptions/series-border/
  769. * Borders demo
  770. *
  771. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  772. * @default #cccccc
  773. * @product highmaps
  774. * @apioption plotOptions.series.borderColor
  775. *
  776. * @private
  777. */
  778. borderColor: palette.neutralColor20,
  779. /**
  780. * The border width of each map area.
  781. *
  782. * In styled mode, the border stroke width is given in the
  783. * `.highcharts-point` class.
  784. *
  785. * @sample maps/plotoptions/series-border/
  786. * Borders demo
  787. *
  788. * @type {number}
  789. * @default 1
  790. * @product highmaps
  791. * @apioption plotOptions.series.borderWidth
  792. *
  793. * @private
  794. */
  795. borderWidth: 1,
  796. /**
  797. * @type {string}
  798. * @default value
  799. * @apioption plotOptions.map.colorKey
  800. */
  801. /**
  802. * What property to join the `mapData` to the value data. For example,
  803. * if joinBy is "code", the mapData items with a specific code is merged
  804. * into the data with the same code. For maps loaded from GeoJSON, the
  805. * keys may be held in each point's `properties` object.
  806. *
  807. * The joinBy option can also be an array of two values, where the first
  808. * points to a key in the `mapData`, and the second points to another
  809. * key in the `data`.
  810. *
  811. * When joinBy is `null`, the map items are joined by their position in
  812. * the array, which performs much better in maps with many data points.
  813. * This is the recommended option if you are printing more than a
  814. * thousand data points and have a backend that can preprocess the data
  815. * into a parallel array of the mapData.
  816. *
  817. * @sample maps/plotoptions/series-border/
  818. * Joined by "code"
  819. * @sample maps/demo/geojson/
  820. * GeoJSON joined by an array
  821. * @sample maps/series/joinby-null/
  822. * Simple data joined by null
  823. *
  824. * @type {string|Array<string>}
  825. * @default hc-key
  826. * @product highmaps
  827. * @apioption plotOptions.series.joinBy
  828. *
  829. * @private
  830. */
  831. joinBy: 'hc-key',
  832. /**
  833. * Define the z index of the series.
  834. *
  835. * @type {number}
  836. * @product highmaps
  837. * @apioption plotOptions.series.zIndex
  838. */
  839. /**
  840. * @apioption plotOptions.series.states
  841. *
  842. * @private
  843. */
  844. states: {
  845. /**
  846. * @apioption plotOptions.series.states.hover
  847. */
  848. hover: {
  849. /** @ignore-option */
  850. halo: null,
  851. /**
  852. * The color of the shape in this state.
  853. *
  854. * @sample maps/plotoptions/series-states-hover/
  855. * Hover options
  856. *
  857. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  858. * @product highmaps
  859. * @apioption plotOptions.series.states.hover.color
  860. */
  861. /**
  862. * The border color of the point in this state.
  863. *
  864. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  865. * @product highmaps
  866. * @apioption plotOptions.series.states.hover.borderColor
  867. */
  868. /**
  869. * The border width of the point in this state
  870. *
  871. * @type {number}
  872. * @product highmaps
  873. * @apioption plotOptions.series.states.hover.borderWidth
  874. */
  875. /**
  876. * The relative brightness of the point when hovered, relative
  877. * to the normal point color.
  878. *
  879. * @type {number}
  880. * @product highmaps
  881. * @default 0.2
  882. * @apioption plotOptions.series.states.hover.brightness
  883. */
  884. brightness: 0.2
  885. },
  886. /**
  887. * @apioption plotOptions.series.states.normal
  888. */
  889. normal: {
  890. /**
  891. * @productdesc {highmaps}
  892. * The animation adds some latency in order to reduce the effect
  893. * of flickering when hovering in and out of for example an
  894. * uneven coastline.
  895. *
  896. * @sample {highmaps} maps/plotoptions/series-states-animation-false/
  897. * No animation of fill color
  898. *
  899. * @apioption plotOptions.series.states.normal.animation
  900. */
  901. animation: true
  902. },
  903. /**
  904. * @apioption plotOptions.series.states.select
  905. */
  906. select: {
  907. /**
  908. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  909. * @default ${palette.neutralColor20}
  910. * @product highmaps
  911. * @apioption plotOptions.series.states.select.color
  912. */
  913. color: palette.neutralColor20
  914. },
  915. inactive: {
  916. opacity: 1
  917. }
  918. }
  919. });
  920. return MapSeries;
  921. }(ScatterSeries));
  922. extend(MapSeries.prototype, {
  923. type: 'map',
  924. axisTypes: colorMapSeriesMixin.axisTypes,
  925. colorAttribs: colorMapSeriesMixin.colorAttribs,
  926. colorKey: colorMapSeriesMixin.colorKey,
  927. // When tooltip is not shared, this series (and derivatives) requires
  928. // direct touch/hover. KD-tree does not apply.
  929. directTouch: true,
  930. // We need the points' bounding boxes in order to draw the data labels,
  931. // so we skip it now and call it from drawPoints instead.
  932. drawDataLabels: noop,
  933. // No graph for the map series
  934. drawGraph: noop,
  935. drawLegendSymbol: LegendSymbolMixin.drawRectangle,
  936. forceDL: true,
  937. getExtremesFromAll: true,
  938. getSymbol: colorMapSeriesMixin.getSymbol,
  939. parallelArrays: colorMapSeriesMixin.parallelArrays,
  940. pointArrayMap: colorMapSeriesMixin.pointArrayMap,
  941. pointClass: MapPoint,
  942. // X axis and Y axis must have same translation slope
  943. preserveAspectRatio: true,
  944. searchPoint: noop,
  945. trackerGroups: colorMapSeriesMixin.trackerGroups,
  946. // Get axis extremes from paths, not values
  947. useMapGeometry: true
  948. });
  949. SeriesRegistry.registerSeriesType('map', MapSeries);
  950. /* *
  951. *
  952. * Default Export
  953. *
  954. * */
  955. export default MapSeries;
  956. /* *
  957. *
  958. * API Options
  959. *
  960. * */
  961. /**
  962. * A map data object containing a `path` definition and optionally additional
  963. * properties to join in the data as per the `joinBy` option.
  964. *
  965. * @sample maps/demo/category-map/
  966. * Map data and joinBy
  967. *
  968. * @type {Array<Highcharts.SeriesMapDataOptions>|*}
  969. * @product highmaps
  970. * @apioption series.mapData
  971. */
  972. /**
  973. * A `map` series. If the [type](#series.map.type) option is not specified, it
  974. * is inherited from [chart.type](#chart.type).
  975. *
  976. * @extends series,plotOptions.map
  977. * @excluding dataParser, dataURL, marker
  978. * @product highmaps
  979. * @apioption series.map
  980. */
  981. /**
  982. * An array of data points for the series. For the `map` series type, points can
  983. * be given in the following ways:
  984. *
  985. * 1. An array of numerical values. In this case, the numerical values will be
  986. * interpreted as `value` options. Example:
  987. * ```js
  988. * data: [0, 5, 3, 5]
  989. * ```
  990. *
  991. * 2. An array of arrays with 2 values. In this case, the values correspond to
  992. * `[hc-key, value]`. Example:
  993. * ```js
  994. * data: [
  995. * ['us-ny', 0],
  996. * ['us-mi', 5],
  997. * ['us-tx', 3],
  998. * ['us-ak', 5]
  999. * ]
  1000. * ```
  1001. *
  1002. * 3. An array of objects with named values. The following snippet shows only a
  1003. * few settings, see the complete options set below. If the total number of
  1004. * data points exceeds the series'
  1005. * [turboThreshold](#series.map.turboThreshold),
  1006. * this option is not available.
  1007. * ```js
  1008. * data: [{
  1009. * value: 6,
  1010. * name: "Point2",
  1011. * color: "#00FF00"
  1012. * }, {
  1013. * value: 6,
  1014. * name: "Point1",
  1015. * color: "#FF00FF"
  1016. * }]
  1017. * ```
  1018. *
  1019. * @type {Array<number|Array<string,(number|null)>|null|*>}
  1020. * @product highmaps
  1021. * @apioption series.map.data
  1022. */
  1023. /**
  1024. * Individual color for the point. By default the color is either used
  1025. * to denote the value, or pulled from the global `colors` array.
  1026. *
  1027. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  1028. * @product highmaps
  1029. * @apioption series.map.data.color
  1030. */
  1031. /**
  1032. * Individual data label for each point. The options are the same as
  1033. * the ones for [plotOptions.series.dataLabels](
  1034. * #plotOptions.series.dataLabels).
  1035. *
  1036. * @sample maps/series/data-datalabels/
  1037. * Disable data labels for individual areas
  1038. *
  1039. * @type {Highcharts.DataLabelsOptions}
  1040. * @product highmaps
  1041. * @apioption series.map.data.dataLabels
  1042. */
  1043. /**
  1044. * The `id` of a series in the [drilldown.series](#drilldown.series)
  1045. * array to use for a drilldown for this point.
  1046. *
  1047. * @sample maps/demo/map-drilldown/
  1048. * Basic drilldown
  1049. *
  1050. * @type {string}
  1051. * @product highmaps
  1052. * @apioption series.map.data.drilldown
  1053. */
  1054. /**
  1055. * An id for the point. This can be used after render time to get a
  1056. * pointer to the point object through `chart.get()`.
  1057. *
  1058. * @sample maps/series/data-id/
  1059. * Highlight a point by id
  1060. *
  1061. * @type {string}
  1062. * @product highmaps
  1063. * @apioption series.map.data.id
  1064. */
  1065. /**
  1066. * When data labels are laid out on a map, Highmaps runs a simplified
  1067. * algorithm to detect collision. When two labels collide, the one with
  1068. * the lowest rank is hidden. By default the rank is computed from the
  1069. * area.
  1070. *
  1071. * @type {number}
  1072. * @product highmaps
  1073. * @apioption series.map.data.labelrank
  1074. */
  1075. /**
  1076. * The relative mid point of an area, used to place the data label.
  1077. * Ranges from 0 to 1\. When `mapData` is used, middleX can be defined
  1078. * there.
  1079. *
  1080. * @type {number}
  1081. * @default 0.5
  1082. * @product highmaps
  1083. * @apioption series.map.data.middleX
  1084. */
  1085. /**
  1086. * The relative mid point of an area, used to place the data label.
  1087. * Ranges from 0 to 1\. When `mapData` is used, middleY can be defined
  1088. * there.
  1089. *
  1090. * @type {number}
  1091. * @default 0.5
  1092. * @product highmaps
  1093. * @apioption series.map.data.middleY
  1094. */
  1095. /**
  1096. * The name of the point as shown in the legend, tooltip, dataLabel
  1097. * etc.
  1098. *
  1099. * @sample maps/series/data-datalabels/
  1100. * Point names
  1101. *
  1102. * @type {string}
  1103. * @product highmaps
  1104. * @apioption series.map.data.name
  1105. */
  1106. /**
  1107. * For map and mapline series types, the SVG path for the shape. For
  1108. * compatibily with old IE, not all SVG path definitions are supported,
  1109. * but M, L and C operators are safe.
  1110. *
  1111. * To achieve a better separation between the structure and the data,
  1112. * it is recommended to use `mapData` to define that paths instead
  1113. * of defining them on the data points themselves.
  1114. *
  1115. * @sample maps/series/data-path/
  1116. * Paths defined in data
  1117. *
  1118. * @type {string}
  1119. * @product highmaps
  1120. * @apioption series.map.data.path
  1121. */
  1122. /**
  1123. * The numeric value of the data point.
  1124. *
  1125. * @type {number|null}
  1126. * @product highmaps
  1127. * @apioption series.map.data.value
  1128. */
  1129. /**
  1130. * Individual point events
  1131. *
  1132. * @extends plotOptions.series.point.events
  1133. * @product highmaps
  1134. * @apioption series.map.data.events
  1135. */
  1136. ''; // adds doclets above to the transpiled file