overlapping-datalabels.src.js 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. /* *
  2. *
  3. * Highcharts module to hide overlapping data labels.
  4. * This module is included in Highcharts.
  5. *
  6. * (c) 2009-2019 Torstein Honsi
  7. *
  8. * License: www.highcharts.com/license
  9. *
  10. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  11. *
  12. * */
  13. 'use strict';
  14. import H from '../parts/Globals.js';
  15. import U from '../parts/Utilities.js';
  16. var isArray = U.isArray, objectEach = U.objectEach, pick = U.pick;
  17. import '../parts/Chart.js';
  18. var Chart = H.Chart, addEvent = H.addEvent, fireEvent = H.fireEvent;
  19. /* eslint-disable no-invalid-this */
  20. // Collect potensial overlapping data labels. Stack labels probably don't need
  21. // to be considered because they are usually accompanied by data labels that lie
  22. // inside the columns.
  23. addEvent(Chart, 'render', function collectAndHide() {
  24. var labels = [];
  25. // Consider external label collectors
  26. (this.labelCollectors || []).forEach(function (collector) {
  27. labels = labels.concat(collector());
  28. });
  29. (this.yAxis || []).forEach(function (yAxis) {
  30. if (yAxis.options.stackLabels &&
  31. !yAxis.options.stackLabels.allowOverlap) {
  32. objectEach(yAxis.stacks, function (stack) {
  33. objectEach(stack, function (stackItem) {
  34. labels.push(stackItem.label);
  35. });
  36. });
  37. }
  38. });
  39. (this.series || []).forEach(function (series) {
  40. var dlOptions = series.options.dataLabels;
  41. if (series.visible &&
  42. !(dlOptions.enabled === false && !series._hasPointLabels)) { // #3866
  43. series.points.forEach(function (point) {
  44. if (point.visible) {
  45. var dataLabels = (isArray(point.dataLabels) ?
  46. point.dataLabels :
  47. (point.dataLabel ? [point.dataLabel] : []));
  48. dataLabels.forEach(function (label) {
  49. var options = label.options;
  50. label.labelrank = pick(options.labelrank, point.labelrank, point.shapeArgs && point.shapeArgs.height); // #4118
  51. if (!options.allowOverlap) {
  52. labels.push(label);
  53. }
  54. });
  55. }
  56. });
  57. }
  58. });
  59. this.hideOverlappingLabels(labels);
  60. });
  61. /**
  62. * Hide overlapping labels. Labels are moved and faded in and out on zoom to
  63. * provide a smooth visual imression.
  64. *
  65. * @private
  66. * @function Highcharts.Chart#hideOverlappingLabels
  67. * @param {Array<Highcharts.SVGElement>} labels
  68. * Rendered data labels
  69. * @return {void}
  70. * @requires modules/overlapping-datalabels
  71. */
  72. Chart.prototype.hideOverlappingLabels = function (labels) {
  73. var chart = this, len = labels.length, ren = chart.renderer, label, i, j, label1, label2, box1, box2, isLabelAffected = false, isIntersectRect = function (box1, box2) {
  74. return !(box2.x > box1.x + box1.width ||
  75. box2.x + box2.width < box1.x ||
  76. box2.y > box1.y + box1.height ||
  77. box2.y + box2.height < box1.y);
  78. },
  79. // Get the box with its position inside the chart, as opposed to getBBox
  80. // that only reports the position relative to the parent.
  81. getAbsoluteBox = function (label) {
  82. var pos, parent, bBox,
  83. // Substract the padding if no background or border (#4333)
  84. padding = label.box ? 0 : (label.padding || 0), lineHeightCorrection = 0;
  85. if (label &&
  86. (!label.alignAttr || label.placed)) {
  87. pos = label.alignAttr || {
  88. x: label.attr('x'),
  89. y: label.attr('y')
  90. };
  91. parent = label.parentGroup;
  92. // Get width and height if pure text nodes (stack labels)
  93. if (!label.width) {
  94. bBox = label.getBBox();
  95. label.width = bBox.width;
  96. label.height = bBox.height;
  97. // Labels positions are computed from top left corner, so
  98. // we need to substract the text height from text nodes too.
  99. lineHeightCorrection = ren
  100. .fontMetrics(null, label.element).h;
  101. }
  102. return {
  103. x: pos.x + (parent.translateX || 0) + padding,
  104. y: pos.y + (parent.translateY || 0) + padding -
  105. lineHeightCorrection,
  106. width: label.width - 2 * padding,
  107. height: label.height - 2 * padding
  108. };
  109. }
  110. };
  111. for (i = 0; i < len; i++) {
  112. label = labels[i];
  113. if (label) {
  114. // Mark with initial opacity
  115. label.oldOpacity = label.opacity;
  116. label.newOpacity = 1;
  117. label.absoluteBox = getAbsoluteBox(label);
  118. }
  119. }
  120. // Prevent a situation in a gradually rising slope, that each label will
  121. // hide the previous one because the previous one always has lower rank.
  122. labels.sort(function (a, b) {
  123. return (b.labelrank || 0) - (a.labelrank || 0);
  124. });
  125. // Detect overlapping labels
  126. for (i = 0; i < len; i++) {
  127. label1 = labels[i];
  128. box1 = label1 && label1.absoluteBox;
  129. for (j = i + 1; j < len; ++j) {
  130. label2 = labels[j];
  131. box2 = label2 && label2.absoluteBox;
  132. if (box1 &&
  133. box2 &&
  134. label1 !== label2 && // #6465, polar chart with connectEnds
  135. label1.newOpacity !== 0 &&
  136. label2.newOpacity !== 0) {
  137. if (isIntersectRect(box1, box2)) {
  138. (label1.labelrank < label2.labelrank ? label1 : label2)
  139. .newOpacity = 0;
  140. }
  141. }
  142. }
  143. }
  144. // Hide or show
  145. labels.forEach(function (label) {
  146. var complete, newOpacity;
  147. if (label) {
  148. newOpacity = label.newOpacity;
  149. if (label.oldOpacity !== newOpacity) {
  150. // Make sure the label is completely hidden to avoid catching
  151. // clicks (#4362)
  152. if (label.alignAttr && label.placed) { // data labels
  153. if (newOpacity) {
  154. label.show(true);
  155. }
  156. else {
  157. complete = function () {
  158. label.hide(true);
  159. label.placed = false; // avoid animation from top
  160. };
  161. }
  162. isLabelAffected = true;
  163. // Animate or set the opacity
  164. label.alignAttr.opacity = newOpacity;
  165. label[label.isOld ? 'animate' : 'attr'](label.alignAttr, null, complete);
  166. fireEvent(chart, 'afterHideOverlappingLabel');
  167. }
  168. else { // other labels, tick labels
  169. label.attr({
  170. opacity: newOpacity
  171. });
  172. }
  173. }
  174. label.isOld = true;
  175. }
  176. });
  177. if (isLabelAffected) {
  178. fireEvent(chart, 'afterHideAllOverlappingLabels');
  179. }
  180. };