ItemSeries.js 16 KB

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