56a1f8f02a737aa0175409c1c8cd8c25654a8c64b900c6ab842f4a8ad0fa8d2785066924073cd984c10f4bf0cb47c1a1b9c5cc84d67a6db71a1ef1eb1b0994 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. /**
  2. * @see https://github.com/chartjs/Chart.js/issues/4176
  3. */
  4. 'use strict';
  5. import Chart from 'chart.js';
  6. import Label from './label';
  7. import utils from './utils';
  8. import defaults from './defaults';
  9. var helpers = Chart.helpers;
  10. var EXPANDO_KEY = '$datalabels';
  11. Chart.defaults.global.plugins.datalabels = defaults;
  12. function configure(dataset, options) {
  13. var override = dataset.datalabels;
  14. var config = {};
  15. if (override === false) {
  16. return null;
  17. }
  18. if (override === true) {
  19. override = {};
  20. }
  21. return helpers.merge(config, [options, override]);
  22. }
  23. function drawLabels(chart, datasetIndex) {
  24. var meta = chart.getDatasetMeta(datasetIndex);
  25. var elements = meta.data || [];
  26. var ilen = elements.length;
  27. var i, el, label;
  28. for (i = 0; i < ilen; ++i) {
  29. el = elements[i];
  30. label = el[EXPANDO_KEY];
  31. if (label) {
  32. label.draw(chart.ctx);
  33. }
  34. }
  35. }
  36. function labelAtXY(chart, x, y) {
  37. var items = chart[EXPANDO_KEY].labels;
  38. var i, j, labels, label;
  39. // Until we support z-index, let's hit test in the drawing reverse order
  40. for (i = items.length - 1; i >= 0; --i) {
  41. labels = items[i] || [];
  42. for (j = labels.length - 1; j >= 0; --j) {
  43. label = labels[j];
  44. if (label.contains(x, y)) {
  45. return {dataset: i, label: label};
  46. }
  47. }
  48. }
  49. return null;
  50. }
  51. function dispatchEvent(chart, listeners, target) {
  52. var callback = listeners && listeners[target.dataset];
  53. if (!callback) {
  54. return;
  55. }
  56. var label = target.label;
  57. var context = label.$context;
  58. if (helpers.callback(callback, [context]) === true) {
  59. // Users are allowed to tweak the given context by injecting values that can be
  60. // used in scriptable options to display labels differently based on the current
  61. // event (e.g. highlight an hovered label). That's why we update the label with
  62. // the output context and schedule a new chart render by setting it dirty.
  63. chart[EXPANDO_KEY].dirty = true;
  64. label.update(context);
  65. }
  66. }
  67. function dispatchMoveEvents(chart, listeners, previous, target) {
  68. var enter, leave;
  69. if (!previous && !target) {
  70. return;
  71. }
  72. if (!previous) {
  73. enter = true;
  74. } else if (!target) {
  75. leave = true;
  76. } else if (previous.label !== target.label) {
  77. leave = enter = true;
  78. }
  79. if (leave) {
  80. dispatchEvent(chart, listeners.leave, previous);
  81. }
  82. if (enter) {
  83. dispatchEvent(chart, listeners.enter, target);
  84. }
  85. }
  86. function handleMoveEvents(chart, event) {
  87. var expando = chart[EXPANDO_KEY];
  88. var listeners = expando.listeners;
  89. var previous, target;
  90. if (!listeners.enter && !listeners.leave) {
  91. return;
  92. }
  93. if (event.type === 'mousemove') {
  94. target = labelAtXY(chart, event.x, event.y);
  95. } else if (event.type !== 'mouseout') {
  96. return;
  97. }
  98. previous = expando.hovered;
  99. expando.hovered = target;
  100. dispatchMoveEvents(chart, listeners, previous, target);
  101. }
  102. function handleClickEvents(chart, event) {
  103. var handlers = chart[EXPANDO_KEY].listeners.click;
  104. var target = handlers && labelAtXY(chart, event.x, event.y);
  105. if (target) {
  106. dispatchEvent(chart, handlers, target);
  107. }
  108. }
  109. Chart.defaults.global.plugins.datalabels = defaults;
  110. Chart.plugins.register({
  111. id: 'datalabels',
  112. beforeInit: function(chart) {
  113. chart[EXPANDO_KEY] = {
  114. actives: []
  115. };
  116. },
  117. beforeUpdate: function(chart) {
  118. var expando = chart[EXPANDO_KEY];
  119. expando.listened = false;
  120. expando.listeners = {}; // {event-type: {dataset-index: function}}
  121. expando.labels = []; // [dataset-index: [labels]]
  122. },
  123. afterDatasetUpdate: function(chart, args, options) {
  124. var datasetIndex = args.index;
  125. var expando = chart[EXPANDO_KEY];
  126. var labels = expando.labels[datasetIndex] = [];
  127. var dataset = chart.data.datasets[datasetIndex];
  128. var config = configure(dataset, options);
  129. var elements = args.meta.data || [];
  130. var ilen = elements.length;
  131. var ctx = chart.ctx;
  132. var i, el, label;
  133. ctx.save();
  134. for (i = 0; i < ilen; ++i) {
  135. el = elements[i];
  136. if (el && !el.hidden && !el._model.skip) {
  137. labels.push(label = new Label(config, ctx, el, i));
  138. label.update(label.$context = {
  139. active: false,
  140. chart: chart,
  141. dataIndex: i,
  142. dataset: dataset,
  143. datasetIndex: datasetIndex
  144. });
  145. } else {
  146. label = null;
  147. }
  148. el[EXPANDO_KEY] = label;
  149. }
  150. ctx.restore();
  151. // Store listeners at the chart level and per event type to optimize
  152. // cases where no listeners are registered for a specific event
  153. helpers.merge(expando.listeners, config.listeners || {}, {
  154. merger: function(key, target, source) {
  155. target[key] = target[key] || {};
  156. target[key][args.index] = source[key];
  157. expando.listened = true;
  158. }
  159. });
  160. },
  161. // Draw labels on top of all dataset elements
  162. // https://github.com/chartjs/chartjs-plugin-datalabels/issues/29
  163. // https://github.com/chartjs/chartjs-plugin-datalabels/issues/32
  164. afterDatasetsDraw: function(chart) {
  165. for (var i = 0, ilen = chart.data.datasets.length; i < ilen; ++i) {
  166. drawLabels(chart, i);
  167. }
  168. },
  169. beforeEvent: function(chart, event) {
  170. // If there is no listener registered for this chart, `listened` will be false,
  171. // meaning we can immediately ignore the incoming event and avoid useless extra
  172. // computation for users who don't implement label interactions.
  173. if (chart[EXPANDO_KEY].listened) {
  174. switch (event.type) {
  175. case 'mousemove':
  176. case 'mouseout':
  177. handleMoveEvents(chart, event);
  178. break;
  179. case 'click':
  180. handleClickEvents(chart, event);
  181. break;
  182. default:
  183. }
  184. }
  185. },
  186. afterEvent: function(chart) {
  187. var expando = chart[EXPANDO_KEY];
  188. var previous = expando.actives;
  189. var actives = expando.actives = chart.lastActive || []; // public API?!
  190. var updates = utils.arrayDiff(previous, actives);
  191. var i, ilen, update, label;
  192. for (i = 0, ilen = updates.length; i < ilen; ++i) {
  193. update = updates[i];
  194. if (update[1]) {
  195. label = update[0][EXPANDO_KEY];
  196. label.$context.active = (update[1] === 1);
  197. label.update(label.$context);
  198. }
  199. }
  200. if ((expando.dirty || updates.length) && !chart.animating) {
  201. chart.render();
  202. }
  203. delete expando.dirty;
  204. }
  205. });