ItemSeries.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506
  1. /* *
  2. *
  3. * (c) 2019-2021 Torstein Honsi
  4. *
  5. * Item series type for Highcharts
  6. *
  7. * License: www.highcharts.com/license
  8. *
  9. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  10. *
  11. * */
  12. 'use strict';
  13. var __extends = (this && this.__extends) || (function () {
  14. var extendStatics = function (d, b) {
  15. extendStatics = Object.setPrototypeOf ||
  16. ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
  17. function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
  18. return extendStatics(d, b);
  19. };
  20. return function (d, b) {
  21. extendStatics(d, b);
  22. function __() { this.constructor = d; }
  23. d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
  24. };
  25. })();
  26. import H from '../../Core/Globals.js';
  27. import ItemPoint from './ItemPoint.js';
  28. import O from '../../Core/Options.js';
  29. var defaultOptions = O.defaultOptions;
  30. import SeriesRegistry from '../../Core/Series/SeriesRegistry.js';
  31. var PieSeries = SeriesRegistry.seriesTypes.pie;
  32. import U from '../../Core/Utilities.js';
  33. var defined = U.defined, extend = U.extend, fireEvent = U.fireEvent, isNumber = U.isNumber, merge = U.merge, objectEach = U.objectEach, pick = U.pick;
  34. /* *
  35. *
  36. * Class
  37. *
  38. * */
  39. // Inherits pie as the most tested non-cartesian series with individual point
  40. // legend, tooltips etc. Only downside is we need to re-enable marker options.
  41. /**
  42. * The item series type.
  43. *
  44. * @requires module:modules/item-series
  45. *
  46. * @private
  47. * @class
  48. * @name Highcharts.seriesTypes.item
  49. *
  50. * @augments Highcharts.seriesTypes.pie
  51. */
  52. var ItemSeries = /** @class */ (function (_super) {
  53. __extends(ItemSeries, _super);
  54. function ItemSeries() {
  55. /* *
  56. *
  57. * Static Properties
  58. *
  59. * */
  60. var _this = _super !== null && _super.apply(this, arguments) || this;
  61. _this.data = void 0;
  62. _this.options = void 0;
  63. _this.points = void 0;
  64. return _this;
  65. /* eslint-enable valid-jsdoc */
  66. }
  67. /* *
  68. *
  69. * Functions
  70. *
  71. * */
  72. /* eslint-disable valid-jsdoc */
  73. /**
  74. * Fade in the whole chart.
  75. * @private
  76. */
  77. ItemSeries.prototype.animate = function (init) {
  78. if (init) {
  79. this.group.attr({
  80. opacity: 0
  81. });
  82. }
  83. else {
  84. this.group.animate({
  85. opacity: 1
  86. }, this.options.animation);
  87. }
  88. };
  89. ItemSeries.prototype.drawDataLabels = function () {
  90. if (this.center && this.slots) {
  91. H.seriesTypes.pie.prototype.drawDataLabels.call(this);
  92. // else, it's just a dot chart with no natural place to put the
  93. // data labels
  94. }
  95. else {
  96. this.points.forEach(function (point) {
  97. point.destroyElements({ dataLabel: 1 });
  98. });
  99. }
  100. };
  101. ItemSeries.prototype.drawPoints = function () {
  102. var series = this, options = this.options, renderer = series.chart.renderer, seriesMarkerOptions = options.marker, borderWidth = this.borderWidth, crisp = borderWidth % 2 ? 0.5 : 1, i = 0, rows = this.getRows(), cols = Math.ceil(this.total / rows), cellWidth = this.chart.plotWidth / cols, cellHeight = this.chart.plotHeight / rows, itemSize = this.itemSize || Math.min(cellWidth, cellHeight);
  103. /* @todo: remove if not needed
  104. this.slots.forEach(slot => {
  105. this.chart.renderer.circle(slot.x, slot.y, 6)
  106. .attr({
  107. fill: 'silver'
  108. })
  109. .add(this.group);
  110. });
  111. //*/
  112. this.points.forEach(function (point) {
  113. var attr, graphics, pointAttr, pointMarkerOptions = point.marker || {}, symbol = (pointMarkerOptions.symbol ||
  114. seriesMarkerOptions.symbol), r = pick(pointMarkerOptions.radius, seriesMarkerOptions.radius), size = defined(r) ? 2 * r : itemSize, padding = size * options.itemPadding, x, y, width, height;
  115. point.graphics = graphics = point.graphics || {};
  116. if (!series.chart.styledMode) {
  117. pointAttr = series.pointAttribs(point, point.selected && 'select');
  118. }
  119. if (!point.isNull && point.visible) {
  120. if (!point.graphic) {
  121. point.graphic = renderer.g('point')
  122. .add(series.group);
  123. }
  124. for (var val = 0; val < point.y; val++) {
  125. // Semi-circle
  126. if (series.center && series.slots) {
  127. // Fill up the slots from left to right
  128. var slot = series.slots.shift();
  129. x = slot.x - itemSize / 2;
  130. y = slot.y - itemSize / 2;
  131. }
  132. else if (options.layout === 'horizontal') {
  133. x = cellWidth * (i % cols);
  134. y = cellHeight * Math.floor(i / cols);
  135. }
  136. else {
  137. x = cellWidth * Math.floor(i / rows);
  138. y = cellHeight * (i % rows);
  139. }
  140. x += padding;
  141. y += padding;
  142. width = Math.round(size - 2 * padding);
  143. height = width;
  144. if (series.options.crisp) {
  145. x = Math.round(x) - crisp;
  146. y = Math.round(y) + crisp;
  147. }
  148. attr = {
  149. x: x,
  150. y: y,
  151. width: width,
  152. height: height
  153. };
  154. if (typeof r !== 'undefined') {
  155. attr.r = r;
  156. }
  157. if (graphics[val]) {
  158. graphics[val].animate(attr);
  159. }
  160. else {
  161. graphics[val] = renderer
  162. .symbol(symbol, null, null, null, null, {
  163. backgroundSize: 'within'
  164. })
  165. .attr(extend(attr, pointAttr))
  166. .add(point.graphic);
  167. }
  168. graphics[val].isActive = true;
  169. i++;
  170. }
  171. }
  172. objectEach(graphics, function (graphic, key) {
  173. if (!graphic.isActive) {
  174. graphic.destroy();
  175. delete graphics[key];
  176. }
  177. else {
  178. graphic.isActive = false;
  179. }
  180. });
  181. });
  182. };
  183. ItemSeries.prototype.getRows = function () {
  184. var rows = this.options.rows, cols, ratio;
  185. // Get the row count that gives the most square cells
  186. if (!rows) {
  187. ratio = this.chart.plotWidth / this.chart.plotHeight;
  188. rows = Math.sqrt(this.total);
  189. if (ratio > 1) {
  190. rows = Math.ceil(rows);
  191. while (rows > 0) {
  192. cols = this.total / rows;
  193. if (cols / rows > ratio) {
  194. break;
  195. }
  196. rows--;
  197. }
  198. }
  199. else {
  200. rows = Math.floor(rows);
  201. while (rows < this.total) {
  202. cols = this.total / rows;
  203. if (cols / rows < ratio) {
  204. break;
  205. }
  206. rows++;
  207. }
  208. }
  209. }
  210. return rows;
  211. };
  212. /**
  213. * Get the semi-circular slots.
  214. * @private
  215. */
  216. ItemSeries.prototype.getSlots = function () {
  217. var center = this.center, diameter = center[2], innerSize = center[3], row, slots = this.slots, x, y, rowRadius, rowLength, colCount, increment, angle, col, itemSize = 0, rowCount, fullAngle = (this.endAngleRad - this.startAngleRad), itemCount = Number.MAX_VALUE, finalItemCount, rows, testRows, rowsOption = this.options.rows,
  218. // How many rows (arcs) should be used
  219. rowFraction = (diameter - innerSize) / diameter, isCircle = fullAngle % (2 * Math.PI) === 0;
  220. // Increase the itemSize until we find the best fit
  221. while (itemCount > this.total + (rows && isCircle ? rows.length : 0)) {
  222. finalItemCount = itemCount;
  223. // Reset
  224. slots.length = 0;
  225. itemCount = 0;
  226. // Now rows is the last successful run
  227. rows = testRows;
  228. testRows = [];
  229. itemSize++;
  230. // Total number of rows (arcs) from the center to the
  231. // perimeter
  232. rowCount = diameter / itemSize / 2;
  233. if (rowsOption) {
  234. innerSize = ((rowCount - rowsOption) / rowCount) * diameter;
  235. if (innerSize >= 0) {
  236. rowCount = rowsOption;
  237. // If innerSize is negative, we are trying to set too
  238. // many rows in the rows option, so fall back to
  239. // treating it as innerSize 0
  240. }
  241. else {
  242. innerSize = 0;
  243. rowFraction = 1;
  244. }
  245. }
  246. else {
  247. rowCount = Math.floor(rowCount * rowFraction);
  248. }
  249. for (row = rowCount; row > 0; row--) {
  250. rowRadius = (innerSize + (row / rowCount) *
  251. (diameter - innerSize - itemSize)) / 2;
  252. rowLength = fullAngle * rowRadius;
  253. colCount = Math.ceil(rowLength / itemSize);
  254. testRows.push({
  255. rowRadius: rowRadius,
  256. rowLength: rowLength,
  257. colCount: colCount
  258. });
  259. itemCount += colCount + 1;
  260. }
  261. }
  262. if (!rows) {
  263. return;
  264. }
  265. // We now have more slots than we have total items. Loop over
  266. // the rows and remove the last slot until the count is correct.
  267. // For each iteration we sort the last slot by the angle, and
  268. // remove those with the highest angles.
  269. var overshoot = finalItemCount - this.total -
  270. (isCircle ? rows.length : 0);
  271. /**
  272. * @private
  273. * @param {Highcharts.ItemRowContainerObject} item
  274. * Wrapped object with angle and row
  275. * @return {void}
  276. */
  277. function cutOffRow(item) {
  278. if (overshoot > 0) {
  279. item.row.colCount--;
  280. overshoot--;
  281. }
  282. }
  283. while (overshoot > 0) {
  284. rows
  285. // Return a simplified representation of the angle of
  286. // the last slot within each row.
  287. .map(function (row) {
  288. return {
  289. angle: row.colCount / row.rowLength,
  290. row: row
  291. };
  292. })
  293. // Sort by the angles...
  294. .sort(function (a, b) {
  295. return b.angle - a.angle;
  296. })
  297. // ...so that we can ignore the items with the lowest
  298. // angles...
  299. .slice(0, Math.min(overshoot, Math.ceil(rows.length / 2)))
  300. // ...and remove the ones with the highest angles
  301. .forEach(cutOffRow);
  302. }
  303. rows.forEach(function (row) {
  304. var rowRadius = row.rowRadius, colCount = row.colCount;
  305. increment = colCount ? fullAngle / colCount : 0;
  306. for (col = 0; col <= colCount; col += 1) {
  307. angle = this.startAngleRad + col * increment;
  308. x = center[0] + Math.cos(angle) * rowRadius;
  309. y = center[1] + Math.sin(angle) * rowRadius;
  310. slots.push({ x: x, y: y, angle: angle });
  311. }
  312. }, this);
  313. // Sort by angle
  314. slots.sort(function (a, b) {
  315. return a.angle - b.angle;
  316. });
  317. this.itemSize = itemSize;
  318. return slots;
  319. };
  320. ItemSeries.prototype.translate = function (_positions) {
  321. // Initialize chart without setting data, #13379.
  322. if (this.total === 0) {
  323. this.center = this.getCenter();
  324. }
  325. if (!this.slots) {
  326. this.slots = [];
  327. }
  328. if (isNumber(this.options.startAngle) &&
  329. isNumber(this.options.endAngle)) {
  330. H.seriesTypes.pie.prototype.translate.apply(this, arguments);
  331. this.slots = this.getSlots();
  332. }
  333. else {
  334. this.generatePoints();
  335. fireEvent(this, 'afterTranslate');
  336. }
  337. };
  338. /**
  339. * An item chart is an infographic chart where a number of items are laid
  340. * out in either a rectangular or circular pattern. It can be used to
  341. * visualize counts within a group, or for the circular pattern, typically
  342. * a parliament.
  343. *
  344. * The circular layout has much in common with a pie chart. Many of the item
  345. * series options, like `center`, `size` and data label positioning, are
  346. * inherited from the pie series and don't apply for rectangular layouts.
  347. *
  348. * @sample highcharts/demo/parliament-chart
  349. * Parliament chart (circular item chart)
  350. * @sample highcharts/series-item/rectangular
  351. * Rectangular item chart
  352. * @sample highcharts/series-item/symbols
  353. * Infographic with symbols
  354. *
  355. * @extends plotOptions.pie
  356. * @since 7.1.0
  357. * @product highcharts
  358. * @excluding borderColor, borderWidth, depth, linecap, shadow,
  359. * slicedOffset
  360. * @requires modules/item-series
  361. * @optionparent plotOptions.item
  362. */
  363. ItemSeries.defaultOptions = merge(PieSeries.defaultOptions, {
  364. /**
  365. * In circular view, the end angle of the item layout, in degrees where
  366. * 0 is up.
  367. *
  368. * @sample highcharts/demo/parliament-chart
  369. * Parliament chart
  370. * @type {undefined|number}
  371. */
  372. endAngle: void 0,
  373. /**
  374. * In circular view, the size of the inner diameter of the circle. Can
  375. * be a percentage or pixel value. Percentages are relative to the outer
  376. * perimeter. Pixel values are given as integers.
  377. *
  378. * If the `rows` option is set, it overrides the `innerSize` setting.
  379. *
  380. * @sample highcharts/demo/parliament-chart
  381. * Parliament chart
  382. * @type {string|number}
  383. */
  384. innerSize: '40%',
  385. /**
  386. * The padding between the items, given in relative size where the size
  387. * of the item is 1.
  388. * @type {number}
  389. */
  390. itemPadding: 0.1,
  391. /**
  392. * The layout of the items in rectangular view. Can be either
  393. * `horizontal` or `vertical`.
  394. * @sample highcharts/series-item/symbols
  395. * Horizontal layout
  396. * @type {string}
  397. */
  398. layout: 'vertical',
  399. /**
  400. * @extends plotOptions.series.marker
  401. */
  402. marker: merge(defaultOptions.plotOptions.line.marker, {
  403. radius: null
  404. }),
  405. /**
  406. * The number of rows to display in the rectangular or circular view. If
  407. * the `innerSize` is set, it will be overridden by the `rows` setting.
  408. *
  409. * @sample highcharts/series-item/rows-columns
  410. * Fixed row count
  411. * @type {number}
  412. */
  413. rows: void 0,
  414. crisp: false,
  415. showInLegend: true,
  416. /**
  417. * In circular view, the start angle of the item layout, in degrees
  418. * where 0 is up.
  419. *
  420. * @sample highcharts/demo/parliament-chart
  421. * Parliament chart
  422. * @type {undefined|number}
  423. */
  424. startAngle: void 0
  425. });
  426. return ItemSeries;
  427. }(PieSeries));
  428. extend(ItemSeries.prototype, {
  429. markerAttribs: void 0
  430. });
  431. ItemSeries.prototype.pointClass = ItemPoint;
  432. SeriesRegistry.registerSeriesType('item', ItemSeries);
  433. /* *
  434. *
  435. * Default Export
  436. *
  437. * */
  438. export default ItemSeries;
  439. /* *
  440. *
  441. * API Options
  442. *
  443. * */
  444. /**
  445. * An `item` series. If the [type](#series.item.type) option is not specified,
  446. * it is inherited from [chart.type](#chart.type).
  447. *
  448. * @extends series,plotOptions.item
  449. * @excluding dataParser, dataURL, stack, xAxis, yAxis, dataSorting,
  450. * boostThreshold, boostBlending
  451. * @product highcharts
  452. * @requires modules/item-series
  453. * @apioption series.item
  454. */
  455. /**
  456. * An array of data points for the series. For the `item` series type,
  457. * points can be given in the following ways:
  458. *
  459. * 1. An array of numerical values. In this case, the numerical values will be
  460. * interpreted as `y` options. Example:
  461. * ```js
  462. * data: [0, 5, 3, 5]
  463. * ```
  464. *
  465. * 2. An array of objects with named values. The following snippet shows only a
  466. * few settings, see the complete options set below. If the total number of
  467. * data points exceeds the series'
  468. * [turboThreshold](#series.item.turboThreshold),
  469. * this option is not available.
  470. * ```js
  471. * data: [{
  472. * y: 1,
  473. * name: "Point2",
  474. * color: "#00FF00"
  475. * }, {
  476. * y: 7,
  477. * name: "Point1",
  478. * color: "#FF00FF"
  479. * }]
  480. * ```
  481. *
  482. * @sample {highcharts} highcharts/chart/reflow-true/
  483. * Numerical values
  484. * @sample {highcharts} highcharts/series/data-array-of-arrays/
  485. * Arrays of numeric x and y
  486. * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/
  487. * Arrays of datetime x and y
  488. * @sample {highcharts} highcharts/series/data-array-of-name-value/
  489. * Arrays of point.name and y
  490. * @sample {highcharts} highcharts/series/data-array-of-objects/
  491. * Config objects
  492. *
  493. * @type {Array<number|Array<string,(number|null)>|null|*>}
  494. * @extends series.pie.data
  495. * @excludes sliced
  496. * @product highcharts
  497. * @apioption series.item.data
  498. */
  499. /**
  500. * The sequential index of the data point in the legend.
  501. *
  502. * @type {number}
  503. * @product highcharts
  504. * @apioption series.pie.data.legendIndex
  505. */
  506. ''; // adds the doclets above to the transpiled file