MapSeries.js 41 KB

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