Point.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681
  1. /**
  2. * (c) 2010-2019 Torstein Honsi
  3. *
  4. * License: www.highcharts.com/license
  5. */
  6. /**
  7. * Configuration hash for the data label and tooltip formatters.
  8. *
  9. * @interface Highcharts.PointLabelObject
  10. *//**
  11. * The point's current color.
  12. * @name Highcharts.PointLabelObject#color
  13. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  14. *//**
  15. * The point's current color index, used in styled mode instead of `color`. The
  16. * color index is inserted in class names used for styling.
  17. * @name Highcharts.PointLabelObject#colorIndex
  18. * @type {number}
  19. *//**
  20. * The name of the related point.
  21. * @name Highcharts.PointLabelObject#key
  22. * @type {number|string}
  23. *//**
  24. * The percentage for related points in a stacked series or pies.
  25. * @name Highcharts.PointLabelObject#percentage
  26. * @type {number}
  27. *//**
  28. * The related point.
  29. * @name Highcharts.PointLabelObject#point
  30. * @type {Highcharts.Point}
  31. *//**
  32. * The related series.
  33. * @name Highcharts.PointLabelObject#series
  34. * @type {Highcharts.Series}
  35. *//**
  36. * The total of values in either a stack for stacked series, or a pie in a pie
  37. * series.
  38. * @name Highcharts.PointLabelObject#total
  39. * @type {number}
  40. *//**
  41. * For categorized axes this property holds the category name for the point. For
  42. * other axes it holds the X value.
  43. * @name Highcharts.PointLabelObject#x
  44. * @type {number|string}
  45. *//**
  46. * The y value of the point.
  47. * @name Highcharts.PointLabelObject#y
  48. * @type {number|undefined}
  49. */
  50. 'use strict';
  51. import Highcharts from './Globals.js';
  52. import './Utilities.js';
  53. var Point,
  54. H = Highcharts,
  55. extend = H.extend,
  56. erase = H.erase,
  57. fireEvent = H.fireEvent,
  58. format = H.format,
  59. isArray = H.isArray,
  60. isNumber = H.isNumber,
  61. pick = H.pick,
  62. uniqueKey = H.uniqueKey,
  63. defined = H.defined,
  64. removeEvent = H.removeEvent;
  65. /**
  66. * The Point object. The point objects are generated from the `series.data`
  67. * configuration objects or raw numbers. They can be accessed from the
  68. * `Series.points` array. Other ways to instantiate points are through {@link
  69. * Highcharts.Series#addPoint} or {@link Highcharts.Series#setData}.
  70. *
  71. * @class
  72. * @name Highcharts.Point
  73. */
  74. Highcharts.Point = Point = function () {};
  75. Highcharts.Point.prototype = {
  76. /**
  77. * Initialize the point. Called internally based on the `series.data`
  78. * option.
  79. *
  80. * @function Highcharts.Point#init
  81. *
  82. * @param {Highcharts.Series} series
  83. * The series object containing this point.
  84. *
  85. * @param {number|object|Array<number|string>|null} options
  86. * The data in either number, array or object format.
  87. *
  88. * @param {number} [x]
  89. * Optionally, the X value of the point.
  90. *
  91. * @return {Highcharts.Point}
  92. * The Point instance.
  93. *
  94. * @fires Highcharts.Point#event:afterInit
  95. */
  96. init: function (series, options, x) {
  97. var point = this,
  98. colors,
  99. optionsChart = series.chart.options.chart,
  100. colorCount = optionsChart.colorCount,
  101. styledMode = series.chart.styledMode,
  102. colorIndex;
  103. /**
  104. * The series object associated with the point.
  105. *
  106. * @name Highcharts.Point#series
  107. * @type {Highcharts.Series}
  108. */
  109. point.series = series;
  110. /**
  111. * The point's current color.
  112. *
  113. * @name Highcharts.Point#color
  114. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  115. */
  116. if (!styledMode) {
  117. point.color = series.color; // #3445
  118. }
  119. point.applyOptions(options, x);
  120. // Add a unique ID to the point if none is assigned
  121. point.id = defined(point.id) ? point.id : uniqueKey();
  122. if (series.options.colorByPoint) {
  123. if (!styledMode) {
  124. colors = series.options.colors || series.chart.options.colors;
  125. point.color = point.color || colors[series.colorCounter];
  126. colorCount = colors.length;
  127. }
  128. colorIndex = series.colorCounter;
  129. series.colorCounter++;
  130. // loop back to zero
  131. if (series.colorCounter === colorCount) {
  132. series.colorCounter = 0;
  133. }
  134. } else {
  135. colorIndex = series.colorIndex;
  136. }
  137. /**
  138. * The point's current color index, used in styled mode instead of
  139. * `color`. The color index is inserted in class names used for styling.
  140. *
  141. * @name Highcharts.Point#colorIndex
  142. * @type {number}
  143. */
  144. point.colorIndex = pick(point.colorIndex, colorIndex);
  145. series.chart.pointCount++;
  146. fireEvent(point, 'afterInit');
  147. return point;
  148. },
  149. /**
  150. * Apply the options containing the x and y data and possible some extra
  151. * properties. Called on point init or from point.update.
  152. *
  153. * @private
  154. * @function Highcharts.Point#applyOptions
  155. *
  156. * @param {number|object|Array<number|string>|null} options
  157. * The point options as defined in series.data.
  158. *
  159. * @param {number} [x]
  160. * Optionally, the x value.
  161. *
  162. * @return {Highcharts.Point}
  163. * The Point instance.
  164. */
  165. applyOptions: function (options, x) {
  166. var point = this,
  167. series = point.series,
  168. pointValKey = series.options.pointValKey || series.pointValKey;
  169. options = Point.prototype.optionsToObject.call(this, options);
  170. // copy options directly to point
  171. extend(point, options);
  172. /**
  173. * The point's options as applied in the initial configuration, or
  174. * extended through `Point.update`.
  175. * @name Highcharts.Point#options
  176. * @type {object}
  177. */
  178. point.options = point.options ?
  179. extend(point.options, options) :
  180. options;
  181. // Since options are copied into the Point instance, some accidental
  182. // options must be shielded (#5681)
  183. if (options.group) {
  184. delete point.group;
  185. }
  186. if (options.dataLabels) {
  187. delete point.dataLabels;
  188. }
  189. /**
  190. * The y value of the point.
  191. * @name Highcharts.Point#y
  192. * @type {number|undefined}
  193. */
  194. // For higher dimension series types. For instance, for ranges, point.y
  195. // is mapped to point.low.
  196. if (pointValKey) {
  197. point.y = point[pointValKey];
  198. }
  199. point.isNull = pick(
  200. point.isValid && !point.isValid(),
  201. point.x === null || !isNumber(point.y, true)
  202. ); // #3571, check for NaN
  203. // The point is initially selected by options (#5777)
  204. if (point.selected) {
  205. point.state = 'select';
  206. }
  207. /**
  208. * The x value of the point.
  209. * @name Highcharts.Point#x
  210. * @type {number}
  211. */
  212. // If no x is set by now, get auto incremented value. All points must
  213. // have an x value, however the y value can be null to create a gap in
  214. // the series
  215. if (
  216. 'name' in point &&
  217. x === undefined &&
  218. series.xAxis &&
  219. series.xAxis.hasNames
  220. ) {
  221. point.x = series.xAxis.nameToX(point);
  222. }
  223. if (point.x === undefined && series) {
  224. if (x === undefined) {
  225. point.x = series.autoIncrement(point);
  226. } else {
  227. point.x = x;
  228. }
  229. }
  230. return point;
  231. },
  232. /**
  233. * Set a value in an object, on the property defined by key. The key
  234. * supports nested properties using dot notation. The function modifies the
  235. * input object and does not make a copy.
  236. *
  237. * @function Highcharts.Point#setNestedProperty
  238. *
  239. * @param {object} object
  240. * The object to set the value on.
  241. *
  242. * @param {*} value
  243. * The value to set.
  244. *
  245. * @param {string} key
  246. * Key to the property to set.
  247. *
  248. * @return {object}
  249. * The modified object.
  250. */
  251. setNestedProperty: function (object, value, key) {
  252. var nestedKeys = key.split('.');
  253. nestedKeys.reduce(function (result, key, i, arr) {
  254. var isLastKey = arr.length - 1 === i;
  255. result[key] = (
  256. isLastKey ?
  257. value :
  258. (H.isObject(result[key], true) ? result[key] : {})
  259. );
  260. return result[key];
  261. }, object);
  262. return object;
  263. },
  264. /**
  265. * Transform number or array configs into objects. Used internally to unify
  266. * the different configuration formats for points. For example, a simple
  267. * number `10` in a line series will be transformed to `{ y: 10 }`, and an
  268. * array config like `[1, 10]` in a scatter series will be transformed to
  269. * `{ x: 1, y: 10 }`.
  270. *
  271. * @function Highcharts.Point#optionsToObject
  272. *
  273. * @param {number|object|Array<number|string>|null} options
  274. * The input option.
  275. *
  276. * @return {object}
  277. * Transformed options.
  278. */
  279. optionsToObject: function (options) {
  280. var ret = {},
  281. series = this.series,
  282. keys = series.options.keys,
  283. pointArrayMap = keys || series.pointArrayMap || ['y'],
  284. valueCount = pointArrayMap.length,
  285. firstItemType,
  286. i = 0,
  287. j = 0;
  288. if (isNumber(options) || options === null) {
  289. ret[pointArrayMap[0]] = options;
  290. } else if (isArray(options)) {
  291. // with leading x value
  292. if (!keys && options.length > valueCount) {
  293. firstItemType = typeof options[0];
  294. if (firstItemType === 'string') {
  295. ret.name = options[0];
  296. } else if (firstItemType === 'number') {
  297. ret.x = options[0];
  298. }
  299. i++;
  300. }
  301. while (j < valueCount) {
  302. // Skip undefined positions for keys
  303. if (!keys || options[i] !== undefined) {
  304. if (pointArrayMap[j].indexOf('.') > 0) {
  305. // Handle nested keys, e.g. ['color.pattern.image']
  306. // Avoid function call unless necessary.
  307. H.Point.prototype.setNestedProperty(
  308. ret, options[i], pointArrayMap[j]
  309. );
  310. } else {
  311. ret[pointArrayMap[j]] = options[i];
  312. }
  313. }
  314. i++;
  315. j++;
  316. }
  317. } else if (typeof options === 'object') {
  318. ret = options;
  319. // This is the fastest way to detect if there are individual point
  320. // dataLabels that need to be considered in drawDataLabels. These
  321. // can only occur in object configs.
  322. if (options.dataLabels) {
  323. series._hasPointLabels = true;
  324. }
  325. // Same approach as above for markers
  326. if (options.marker) {
  327. series._hasPointMarkers = true;
  328. }
  329. }
  330. return ret;
  331. },
  332. /**
  333. * Get the CSS class names for individual points. Used internally where the
  334. * returned value is set on every point.
  335. *
  336. * @function Highcharts.Point#getClassName
  337. *
  338. * @return {string}
  339. * The class names.
  340. */
  341. getClassName: function () {
  342. return 'highcharts-point' +
  343. (this.selected ? ' highcharts-point-select' : '') +
  344. (this.negative ? ' highcharts-negative' : '') +
  345. (this.isNull ? ' highcharts-null-point' : '') +
  346. (this.colorIndex !== undefined ? ' highcharts-color-' +
  347. this.colorIndex : '') +
  348. (this.options.className ? ' ' + this.options.className : '') +
  349. (this.zone && this.zone.className ? ' ' +
  350. this.zone.className.replace('highcharts-negative', '') : '');
  351. },
  352. /**
  353. * In a series with `zones`, return the zone that the point belongs to.
  354. *
  355. * @function Highcharts.Point#getZone
  356. *
  357. * @return {Highcharts.PlotSeriesZonesOptions}
  358. * The zone item.
  359. */
  360. getZone: function () {
  361. var series = this.series,
  362. zones = series.zones,
  363. zoneAxis = series.zoneAxis || 'y',
  364. i = 0,
  365. zone;
  366. zone = zones[i];
  367. while (this[zoneAxis] >= zone.value) {
  368. zone = zones[++i];
  369. }
  370. // For resetting or reusing the point (#8100)
  371. if (!this.nonZonedColor) {
  372. this.nonZonedColor = this.color;
  373. }
  374. if (zone && zone.color && !this.options.color) {
  375. this.color = zone.color;
  376. } else {
  377. this.color = this.nonZonedColor;
  378. }
  379. return zone;
  380. },
  381. /**
  382. * Destroy a point to clear memory. Its reference still stays in
  383. * `series.data`.
  384. *
  385. * @private
  386. * @function Highcharts.Point#destroy
  387. */
  388. destroy: function () {
  389. var point = this,
  390. series = point.series,
  391. chart = series.chart,
  392. hoverPoints = chart.hoverPoints,
  393. prop;
  394. chart.pointCount--;
  395. if (hoverPoints) {
  396. point.setState();
  397. erase(hoverPoints, point);
  398. if (!hoverPoints.length) {
  399. chart.hoverPoints = null;
  400. }
  401. }
  402. if (point === chart.hoverPoint) {
  403. point.onMouseOut();
  404. }
  405. // Remove all events and elements
  406. if (point.graphic || point.dataLabel || point.dataLabels) {
  407. removeEvent(point);
  408. point.destroyElements();
  409. }
  410. if (point.legendItem) { // pies have legend items
  411. chart.legend.destroyItem(point);
  412. }
  413. for (prop in point) {
  414. point[prop] = null;
  415. }
  416. },
  417. /**
  418. * Destroy SVG elements associated with the point.
  419. *
  420. * @private
  421. * @function Highcharts.Point#destroyElements
  422. */
  423. destroyElements: function () {
  424. var point = this,
  425. props = [
  426. 'graphic',
  427. 'dataLabel',
  428. 'dataLabelUpper',
  429. 'connector',
  430. 'shadowGroup'
  431. ],
  432. prop,
  433. i = 6;
  434. while (i--) {
  435. prop = props[i];
  436. if (point[prop]) {
  437. point[prop] = point[prop].destroy();
  438. }
  439. }
  440. // Handle point.dataLabels and point.connectors
  441. if (point.dataLabels) {
  442. point.dataLabels.forEach(function (label) {
  443. if (label.element) {
  444. label.destroy();
  445. }
  446. });
  447. delete point.dataLabels;
  448. }
  449. if (point.connectors) {
  450. point.connectors.forEach(function (connector) {
  451. if (connector.element) {
  452. connector.destroy();
  453. }
  454. });
  455. delete point.connectors;
  456. }
  457. },
  458. /**
  459. * Return the configuration hash needed for the data label and tooltip
  460. * formatters.
  461. *
  462. * @function Highcharts.Point#getLabelConfig
  463. *
  464. * @return {Highcharts.PointLabelObject}
  465. * Abstract object used in formatters and formats.
  466. */
  467. getLabelConfig: function () {
  468. return {
  469. x: this.category,
  470. y: this.y,
  471. color: this.color,
  472. colorIndex: this.colorIndex,
  473. key: this.name || this.category,
  474. series: this.series,
  475. point: this,
  476. percentage: this.percentage,
  477. total: this.total || this.stackTotal
  478. };
  479. },
  480. /**
  481. * Extendable method for formatting each point's tooltip line.
  482. *
  483. * @function Highcharts.Point#tooltipFormatter
  484. *
  485. * @param {string} pointFormat
  486. * The point format.
  487. *
  488. * @return {string}
  489. * A string to be concatenated in to the common tooltip text.
  490. */
  491. tooltipFormatter: function (pointFormat) {
  492. // Insert options for valueDecimals, valuePrefix, and valueSuffix
  493. var series = this.series,
  494. seriesTooltipOptions = series.tooltipOptions,
  495. valueDecimals = pick(seriesTooltipOptions.valueDecimals, ''),
  496. valuePrefix = seriesTooltipOptions.valuePrefix || '',
  497. valueSuffix = seriesTooltipOptions.valueSuffix || '';
  498. // Replace default point style with class name
  499. if (series.chart.styledMode) {
  500. pointFormat = series.chart.tooltip.styledModeFormat(pointFormat);
  501. }
  502. // Loop over the point array map and replace unformatted values with
  503. // sprintf formatting markup
  504. (series.pointArrayMap || ['y']).forEach(function (key) {
  505. key = '{point.' + key; // without the closing bracket
  506. if (valuePrefix || valueSuffix) {
  507. pointFormat = pointFormat.replace(
  508. RegExp(key + '}', 'g'),
  509. valuePrefix + key + '}' + valueSuffix
  510. );
  511. }
  512. pointFormat = pointFormat.replace(
  513. RegExp(key + '}', 'g'),
  514. key + ':,.' + valueDecimals + 'f}'
  515. );
  516. });
  517. return format(pointFormat, {
  518. point: this,
  519. series: this.series
  520. }, series.chart.time);
  521. },
  522. /**
  523. * Fire an event on the Point object.
  524. *
  525. * @private
  526. * @function Highcharts.Point#firePointEvent
  527. *
  528. * @param {string} eventType
  529. * Type of the event.
  530. *
  531. * @param {object} eventArgs
  532. * Additional event arguments.
  533. *
  534. * @param {Function} defaultFunction
  535. * Default event handler.
  536. *
  537. * @fires Highcharts.Point#event:*
  538. */
  539. firePointEvent: function (eventType, eventArgs, defaultFunction) {
  540. var point = this,
  541. series = this.series,
  542. seriesOptions = series.options;
  543. // load event handlers on demand to save time on mouseover/out
  544. if (
  545. seriesOptions.point.events[eventType] ||
  546. (
  547. point.options &&
  548. point.options.events &&
  549. point.options.events[eventType]
  550. )
  551. ) {
  552. this.importEvents();
  553. }
  554. // add default handler if in selection mode
  555. if (eventType === 'click' && seriesOptions.allowPointSelect) {
  556. defaultFunction = function (event) {
  557. // Control key is for Windows, meta (= Cmd key) for Mac, Shift
  558. // for Opera.
  559. if (point.select) { // #2911
  560. point.select(
  561. null,
  562. event.ctrlKey || event.metaKey || event.shiftKey
  563. );
  564. }
  565. };
  566. }
  567. fireEvent(this, eventType, eventArgs, defaultFunction);
  568. },
  569. /**
  570. * For categorized axes this property holds the category name for the
  571. * point. For other axes it holds the X value.
  572. *
  573. * @name Highcharts.Point#category
  574. * @type {number|string}
  575. */
  576. /**
  577. * The name of the point. The name can be given as the first position of the
  578. * point configuration array, or as a `name` property in the configuration:
  579. *
  580. * @example
  581. * // Array config
  582. * data: [
  583. * ['John', 1],
  584. * ['Jane', 2]
  585. * ]
  586. *
  587. * // Object config
  588. * data: [{
  589. * name: 'John',
  590. * y: 1
  591. * }, {
  592. * name: 'Jane',
  593. * y: 2
  594. * }]
  595. *
  596. * @name Highcharts.Point#name
  597. * @type {string}
  598. */
  599. /**
  600. * The percentage for points in a stacked series or pies.
  601. *
  602. * @name Highcharts.Point#percentage
  603. * @type {number}
  604. */
  605. /**
  606. * The total of values in either a stack for stacked series, or a pie in a
  607. * pie series.
  608. *
  609. * @name Highcharts.Point#total
  610. * @type {number}
  611. */
  612. /**
  613. * For certain series types, like pie charts, where individual points can
  614. * be shown or hidden.
  615. *
  616. * @name Highcharts.Point#visible
  617. * @type {boolean}
  618. */
  619. visible: true
  620. };