MapSeries.js 44 KB

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