Built files from Bizgaze WebServer
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

chartjs-plugin-datalabels.js 32KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358
  1. /*!
  2. * chartjs-plugin-datalabels v2.0.0
  3. * https://chartjs-plugin-datalabels.netlify.app
  4. * (c) 2017-2021 chartjs-plugin-datalabels contributors
  5. * Released under the MIT license
  6. */
  7. (function (global, factory) {
  8. typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('chart.js/helpers'), require('chart.js')) :
  9. typeof define === 'function' && define.amd ? define(['chart.js/helpers', 'chart.js'], factory) :
  10. (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.ChartDataLabels = factory(global.Chart.helpers, global.Chart));
  11. }(this, (function (helpers, chart_js) { 'use strict';
  12. var devicePixelRatio = (function() {
  13. if (typeof window !== 'undefined') {
  14. if (window.devicePixelRatio) {
  15. return window.devicePixelRatio;
  16. }
  17. // devicePixelRatio is undefined on IE10
  18. // https://stackoverflow.com/a/20204180/8837887
  19. // https://github.com/chartjs/chartjs-plugin-datalabels/issues/85
  20. var screen = window.screen;
  21. if (screen) {
  22. return (screen.deviceXDPI || 1) / (screen.logicalXDPI || 1);
  23. }
  24. }
  25. return 1;
  26. }());
  27. var utils = {
  28. // @todo move this in Chart.helpers.toTextLines
  29. toTextLines: function(inputs) {
  30. var lines = [];
  31. var input;
  32. inputs = [].concat(inputs);
  33. while (inputs.length) {
  34. input = inputs.pop();
  35. if (typeof input === 'string') {
  36. lines.unshift.apply(lines, input.split('\n'));
  37. } else if (Array.isArray(input)) {
  38. inputs.push.apply(inputs, input);
  39. } else if (!helpers.isNullOrUndef(inputs)) {
  40. lines.unshift('' + input);
  41. }
  42. }
  43. return lines;
  44. },
  45. // @todo move this in Chart.helpers.canvas.textSize
  46. // @todo cache calls of measureText if font doesn't change?!
  47. textSize: function(ctx, lines, font) {
  48. var items = [].concat(lines);
  49. var ilen = items.length;
  50. var prev = ctx.font;
  51. var width = 0;
  52. var i;
  53. ctx.font = font.string;
  54. for (i = 0; i < ilen; ++i) {
  55. width = Math.max(ctx.measureText(items[i]).width, width);
  56. }
  57. ctx.font = prev;
  58. return {
  59. height: ilen * font.lineHeight,
  60. width: width
  61. };
  62. },
  63. /**
  64. * Returns value bounded by min and max. This is equivalent to max(min, min(value, max)).
  65. * @todo move this method in Chart.helpers.bound
  66. * https://doc.qt.io/qt-5/qtglobal.html#qBound
  67. */
  68. bound: function(min, value, max) {
  69. return Math.max(min, Math.min(value, max));
  70. },
  71. /**
  72. * Returns an array of pair [value, state] where state is:
  73. * * -1: value is only in a0 (removed)
  74. * * 1: value is only in a1 (added)
  75. */
  76. arrayDiff: function(a0, a1) {
  77. var prev = a0.slice();
  78. var updates = [];
  79. var i, j, ilen, v;
  80. for (i = 0, ilen = a1.length; i < ilen; ++i) {
  81. v = a1[i];
  82. j = prev.indexOf(v);
  83. if (j === -1) {
  84. updates.push([v, 1]);
  85. } else {
  86. prev.splice(j, 1);
  87. }
  88. }
  89. for (i = 0, ilen = prev.length; i < ilen; ++i) {
  90. updates.push([prev[i], -1]);
  91. }
  92. return updates;
  93. },
  94. /**
  95. * https://github.com/chartjs/chartjs-plugin-datalabels/issues/70
  96. */
  97. rasterize: function(v) {
  98. return Math.round(v * devicePixelRatio) / devicePixelRatio;
  99. }
  100. };
  101. function orient(point, origin) {
  102. var x0 = origin.x;
  103. var y0 = origin.y;
  104. if (x0 === null) {
  105. return {x: 0, y: -1};
  106. }
  107. if (y0 === null) {
  108. return {x: 1, y: 0};
  109. }
  110. var dx = point.x - x0;
  111. var dy = point.y - y0;
  112. var ln = Math.sqrt(dx * dx + dy * dy);
  113. return {
  114. x: ln ? dx / ln : 0,
  115. y: ln ? dy / ln : -1
  116. };
  117. }
  118. function aligned(x, y, vx, vy, align) {
  119. switch (align) {
  120. case 'center':
  121. vx = vy = 0;
  122. break;
  123. case 'bottom':
  124. vx = 0;
  125. vy = 1;
  126. break;
  127. case 'right':
  128. vx = 1;
  129. vy = 0;
  130. break;
  131. case 'left':
  132. vx = -1;
  133. vy = 0;
  134. break;
  135. case 'top':
  136. vx = 0;
  137. vy = -1;
  138. break;
  139. case 'start':
  140. vx = -vx;
  141. vy = -vy;
  142. break;
  143. case 'end':
  144. // keep natural orientation
  145. break;
  146. default:
  147. // clockwise rotation (in degree)
  148. align *= (Math.PI / 180);
  149. vx = Math.cos(align);
  150. vy = Math.sin(align);
  151. break;
  152. }
  153. return {
  154. x: x,
  155. y: y,
  156. vx: vx,
  157. vy: vy
  158. };
  159. }
  160. // Line clipping (Cohen–Sutherland algorithm)
  161. // https://en.wikipedia.org/wiki/Cohen–Sutherland_algorithm
  162. var R_INSIDE = 0;
  163. var R_LEFT = 1;
  164. var R_RIGHT = 2;
  165. var R_BOTTOM = 4;
  166. var R_TOP = 8;
  167. function region(x, y, rect) {
  168. var res = R_INSIDE;
  169. if (x < rect.left) {
  170. res |= R_LEFT;
  171. } else if (x > rect.right) {
  172. res |= R_RIGHT;
  173. }
  174. if (y < rect.top) {
  175. res |= R_TOP;
  176. } else if (y > rect.bottom) {
  177. res |= R_BOTTOM;
  178. }
  179. return res;
  180. }
  181. function clipped(segment, area) {
  182. var x0 = segment.x0;
  183. var y0 = segment.y0;
  184. var x1 = segment.x1;
  185. var y1 = segment.y1;
  186. var r0 = region(x0, y0, area);
  187. var r1 = region(x1, y1, area);
  188. var r, x, y;
  189. // eslint-disable-next-line no-constant-condition
  190. while (true) {
  191. if (!(r0 | r1) || (r0 & r1)) {
  192. // both points inside or on the same side: no clipping
  193. break;
  194. }
  195. // at least one point is outside
  196. r = r0 || r1;
  197. if (r & R_TOP) {
  198. x = x0 + (x1 - x0) * (area.top - y0) / (y1 - y0);
  199. y = area.top;
  200. } else if (r & R_BOTTOM) {
  201. x = x0 + (x1 - x0) * (area.bottom - y0) / (y1 - y0);
  202. y = area.bottom;
  203. } else if (r & R_RIGHT) {
  204. y = y0 + (y1 - y0) * (area.right - x0) / (x1 - x0);
  205. x = area.right;
  206. } else if (r & R_LEFT) {
  207. y = y0 + (y1 - y0) * (area.left - x0) / (x1 - x0);
  208. x = area.left;
  209. }
  210. if (r === r0) {
  211. x0 = x;
  212. y0 = y;
  213. r0 = region(x0, y0, area);
  214. } else {
  215. x1 = x;
  216. y1 = y;
  217. r1 = region(x1, y1, area);
  218. }
  219. }
  220. return {
  221. x0: x0,
  222. x1: x1,
  223. y0: y0,
  224. y1: y1
  225. };
  226. }
  227. function compute$1(range, config) {
  228. var anchor = config.anchor;
  229. var segment = range;
  230. var x, y;
  231. if (config.clamp) {
  232. segment = clipped(segment, config.area);
  233. }
  234. if (anchor === 'start') {
  235. x = segment.x0;
  236. y = segment.y0;
  237. } else if (anchor === 'end') {
  238. x = segment.x1;
  239. y = segment.y1;
  240. } else {
  241. x = (segment.x0 + segment.x1) / 2;
  242. y = (segment.y0 + segment.y1) / 2;
  243. }
  244. return aligned(x, y, range.vx, range.vy, config.align);
  245. }
  246. var positioners = {
  247. arc: function(el, config) {
  248. var angle = (el.startAngle + el.endAngle) / 2;
  249. var vx = Math.cos(angle);
  250. var vy = Math.sin(angle);
  251. var r0 = el.innerRadius;
  252. var r1 = el.outerRadius;
  253. return compute$1({
  254. x0: el.x + vx * r0,
  255. y0: el.y + vy * r0,
  256. x1: el.x + vx * r1,
  257. y1: el.y + vy * r1,
  258. vx: vx,
  259. vy: vy
  260. }, config);
  261. },
  262. point: function(el, config) {
  263. var v = orient(el, config.origin);
  264. var rx = v.x * el.options.radius;
  265. var ry = v.y * el.options.radius;
  266. return compute$1({
  267. x0: el.x - rx,
  268. y0: el.y - ry,
  269. x1: el.x + rx,
  270. y1: el.y + ry,
  271. vx: v.x,
  272. vy: v.y
  273. }, config);
  274. },
  275. bar: function(el, config) {
  276. var v = orient(el, config.origin);
  277. var x = el.x;
  278. var y = el.y;
  279. var sx = 0;
  280. var sy = 0;
  281. if (el.horizontal) {
  282. x = Math.min(el.x, el.base);
  283. sx = Math.abs(el.base - el.x);
  284. } else {
  285. y = Math.min(el.y, el.base);
  286. sy = Math.abs(el.base - el.y);
  287. }
  288. return compute$1({
  289. x0: x,
  290. y0: y + sy,
  291. x1: x + sx,
  292. y1: y,
  293. vx: v.x,
  294. vy: v.y
  295. }, config);
  296. },
  297. fallback: function(el, config) {
  298. var v = orient(el, config.origin);
  299. return compute$1({
  300. x0: el.x,
  301. y0: el.y,
  302. x1: el.x,
  303. y1: el.y,
  304. vx: v.x,
  305. vy: v.y
  306. }, config);
  307. }
  308. };
  309. var rasterize = utils.rasterize;
  310. function boundingRects(model) {
  311. var borderWidth = model.borderWidth || 0;
  312. var padding = model.padding;
  313. var th = model.size.height;
  314. var tw = model.size.width;
  315. var tx = -tw / 2;
  316. var ty = -th / 2;
  317. return {
  318. frame: {
  319. x: tx - padding.left - borderWidth,
  320. y: ty - padding.top - borderWidth,
  321. w: tw + padding.width + borderWidth * 2,
  322. h: th + padding.height + borderWidth * 2
  323. },
  324. text: {
  325. x: tx,
  326. y: ty,
  327. w: tw,
  328. h: th
  329. }
  330. };
  331. }
  332. function getScaleOrigin(el, context) {
  333. var scale = context.chart.getDatasetMeta(context.datasetIndex).vScale;
  334. if (!scale) {
  335. return null;
  336. }
  337. if (scale.xCenter !== undefined && scale.yCenter !== undefined) {
  338. return {x: scale.xCenter, y: scale.yCenter};
  339. }
  340. var pixel = scale.getBasePixel();
  341. return el.horizontal ?
  342. {x: pixel, y: null} :
  343. {x: null, y: pixel};
  344. }
  345. function getPositioner(el) {
  346. if (el instanceof chart_js.ArcElement) {
  347. return positioners.arc;
  348. }
  349. if (el instanceof chart_js.PointElement) {
  350. return positioners.point;
  351. }
  352. if (el instanceof chart_js.BarElement) {
  353. return positioners.bar;
  354. }
  355. return positioners.fallback;
  356. }
  357. function drawRoundedRect(ctx, x, y, w, h, radius) {
  358. var HALF_PI = Math.PI / 2;
  359. if (radius) {
  360. var r = Math.min(radius, h / 2, w / 2);
  361. var left = x + r;
  362. var top = y + r;
  363. var right = x + w - r;
  364. var bottom = y + h - r;
  365. ctx.moveTo(x, top);
  366. if (left < right && top < bottom) {
  367. ctx.arc(left, top, r, -Math.PI, -HALF_PI);
  368. ctx.arc(right, top, r, -HALF_PI, 0);
  369. ctx.arc(right, bottom, r, 0, HALF_PI);
  370. ctx.arc(left, bottom, r, HALF_PI, Math.PI);
  371. } else if (left < right) {
  372. ctx.moveTo(left, y);
  373. ctx.arc(right, top, r, -HALF_PI, HALF_PI);
  374. ctx.arc(left, top, r, HALF_PI, Math.PI + HALF_PI);
  375. } else if (top < bottom) {
  376. ctx.arc(left, top, r, -Math.PI, 0);
  377. ctx.arc(left, bottom, r, 0, Math.PI);
  378. } else {
  379. ctx.arc(left, top, r, -Math.PI, Math.PI);
  380. }
  381. ctx.closePath();
  382. ctx.moveTo(x, y);
  383. } else {
  384. ctx.rect(x, y, w, h);
  385. }
  386. }
  387. function drawFrame(ctx, rect, model) {
  388. var bgColor = model.backgroundColor;
  389. var borderColor = model.borderColor;
  390. var borderWidth = model.borderWidth;
  391. if (!bgColor && (!borderColor || !borderWidth)) {
  392. return;
  393. }
  394. ctx.beginPath();
  395. drawRoundedRect(
  396. ctx,
  397. rasterize(rect.x) + borderWidth / 2,
  398. rasterize(rect.y) + borderWidth / 2,
  399. rasterize(rect.w) - borderWidth,
  400. rasterize(rect.h) - borderWidth,
  401. model.borderRadius);
  402. ctx.closePath();
  403. if (bgColor) {
  404. ctx.fillStyle = bgColor;
  405. ctx.fill();
  406. }
  407. if (borderColor && borderWidth) {
  408. ctx.strokeStyle = borderColor;
  409. ctx.lineWidth = borderWidth;
  410. ctx.lineJoin = 'miter';
  411. ctx.stroke();
  412. }
  413. }
  414. function textGeometry(rect, align, font) {
  415. var h = font.lineHeight;
  416. var w = rect.w;
  417. var x = rect.x;
  418. var y = rect.y + h / 2;
  419. if (align === 'center') {
  420. x += w / 2;
  421. } else if (align === 'end' || align === 'right') {
  422. x += w;
  423. }
  424. return {
  425. h: h,
  426. w: w,
  427. x: x,
  428. y: y
  429. };
  430. }
  431. function drawTextLine(ctx, text, cfg) {
  432. var shadow = ctx.shadowBlur;
  433. var stroked = cfg.stroked;
  434. var x = rasterize(cfg.x);
  435. var y = rasterize(cfg.y);
  436. var w = rasterize(cfg.w);
  437. if (stroked) {
  438. ctx.strokeText(text, x, y, w);
  439. }
  440. if (cfg.filled) {
  441. if (shadow && stroked) {
  442. // Prevent drawing shadow on both the text stroke and fill, so
  443. // if the text is stroked, remove the shadow for the text fill.
  444. ctx.shadowBlur = 0;
  445. }
  446. ctx.fillText(text, x, y, w);
  447. if (shadow && stroked) {
  448. ctx.shadowBlur = shadow;
  449. }
  450. }
  451. }
  452. function drawText(ctx, lines, rect, model) {
  453. var align = model.textAlign;
  454. var color = model.color;
  455. var filled = !!color;
  456. var font = model.font;
  457. var ilen = lines.length;
  458. var strokeColor = model.textStrokeColor;
  459. var strokeWidth = model.textStrokeWidth;
  460. var stroked = strokeColor && strokeWidth;
  461. var i;
  462. if (!ilen || (!filled && !stroked)) {
  463. return;
  464. }
  465. // Adjust coordinates based on text alignment and line height
  466. rect = textGeometry(rect, align, font);
  467. ctx.font = font.string;
  468. ctx.textAlign = align;
  469. ctx.textBaseline = 'middle';
  470. ctx.shadowBlur = model.textShadowBlur;
  471. ctx.shadowColor = model.textShadowColor;
  472. if (filled) {
  473. ctx.fillStyle = color;
  474. }
  475. if (stroked) {
  476. ctx.lineJoin = 'round';
  477. ctx.lineWidth = strokeWidth;
  478. ctx.strokeStyle = strokeColor;
  479. }
  480. for (i = 0, ilen = lines.length; i < ilen; ++i) {
  481. drawTextLine(ctx, lines[i], {
  482. stroked: stroked,
  483. filled: filled,
  484. w: rect.w,
  485. x: rect.x,
  486. y: rect.y + rect.h * i
  487. });
  488. }
  489. }
  490. var Label = function(config, ctx, el, index) {
  491. var me = this;
  492. me._config = config;
  493. me._index = index;
  494. me._model = null;
  495. me._rects = null;
  496. me._ctx = ctx;
  497. me._el = el;
  498. };
  499. helpers.merge(Label.prototype, {
  500. /**
  501. * @private
  502. */
  503. _modelize: function(display, lines, config, context) {
  504. var me = this;
  505. var index = me._index;
  506. var font = helpers.toFont(helpers.resolve([config.font, {}], context, index));
  507. var color = helpers.resolve([config.color, chart_js.defaults.color], context, index);
  508. return {
  509. align: helpers.resolve([config.align, 'center'], context, index),
  510. anchor: helpers.resolve([config.anchor, 'center'], context, index),
  511. area: context.chart.chartArea,
  512. backgroundColor: helpers.resolve([config.backgroundColor, null], context, index),
  513. borderColor: helpers.resolve([config.borderColor, null], context, index),
  514. borderRadius: helpers.resolve([config.borderRadius, 0], context, index),
  515. borderWidth: helpers.resolve([config.borderWidth, 0], context, index),
  516. clamp: helpers.resolve([config.clamp, false], context, index),
  517. clip: helpers.resolve([config.clip, false], context, index),
  518. color: color,
  519. display: display,
  520. font: font,
  521. lines: lines,
  522. offset: helpers.resolve([config.offset, 0], context, index),
  523. opacity: helpers.resolve([config.opacity, 1], context, index),
  524. origin: getScaleOrigin(me._el, context),
  525. padding: helpers.toPadding(helpers.resolve([config.padding, 0], context, index)),
  526. positioner: getPositioner(me._el),
  527. rotation: helpers.resolve([config.rotation, 0], context, index) * (Math.PI / 180),
  528. size: utils.textSize(me._ctx, lines, font),
  529. textAlign: helpers.resolve([config.textAlign, 'start'], context, index),
  530. textShadowBlur: helpers.resolve([config.textShadowBlur, 0], context, index),
  531. textShadowColor: helpers.resolve([config.textShadowColor, color], context, index),
  532. textStrokeColor: helpers.resolve([config.textStrokeColor, color], context, index),
  533. textStrokeWidth: helpers.resolve([config.textStrokeWidth, 0], context, index)
  534. };
  535. },
  536. update: function(context) {
  537. var me = this;
  538. var model = null;
  539. var rects = null;
  540. var index = me._index;
  541. var config = me._config;
  542. var value, label, lines;
  543. // We first resolve the display option (separately) to avoid computing
  544. // other options in case the label is hidden (i.e. display: false).
  545. var display = helpers.resolve([config.display, true], context, index);
  546. if (display) {
  547. value = context.dataset.data[index];
  548. label = helpers.valueOrDefault(helpers.callback(config.formatter, [value, context]), value);
  549. lines = helpers.isNullOrUndef(label) ? [] : utils.toTextLines(label);
  550. if (lines.length) {
  551. model = me._modelize(display, lines, config, context);
  552. rects = boundingRects(model);
  553. }
  554. }
  555. me._model = model;
  556. me._rects = rects;
  557. },
  558. geometry: function() {
  559. return this._rects ? this._rects.frame : {};
  560. },
  561. rotation: function() {
  562. return this._model ? this._model.rotation : 0;
  563. },
  564. visible: function() {
  565. return this._model && this._model.opacity;
  566. },
  567. model: function() {
  568. return this._model;
  569. },
  570. draw: function(chart, center) {
  571. var me = this;
  572. var ctx = chart.ctx;
  573. var model = me._model;
  574. var rects = me._rects;
  575. var area;
  576. if (!this.visible()) {
  577. return;
  578. }
  579. ctx.save();
  580. if (model.clip) {
  581. area = model.area;
  582. ctx.beginPath();
  583. ctx.rect(
  584. area.left,
  585. area.top,
  586. area.right - area.left,
  587. area.bottom - area.top);
  588. ctx.clip();
  589. }
  590. ctx.globalAlpha = utils.bound(0, model.opacity, 1);
  591. ctx.translate(rasterize(center.x), rasterize(center.y));
  592. ctx.rotate(model.rotation);
  593. drawFrame(ctx, rects.frame, model);
  594. drawText(ctx, model.lines, rects.text, model);
  595. ctx.restore();
  596. }
  597. });
  598. var MIN_INTEGER = Number.MIN_SAFE_INTEGER || -9007199254740991; // eslint-disable-line es/no-number-minsafeinteger
  599. var MAX_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991; // eslint-disable-line es/no-number-maxsafeinteger
  600. function rotated(point, center, angle) {
  601. var cos = Math.cos(angle);
  602. var sin = Math.sin(angle);
  603. var cx = center.x;
  604. var cy = center.y;
  605. return {
  606. x: cx + cos * (point.x - cx) - sin * (point.y - cy),
  607. y: cy + sin * (point.x - cx) + cos * (point.y - cy)
  608. };
  609. }
  610. function projected(points, axis) {
  611. var min = MAX_INTEGER;
  612. var max = MIN_INTEGER;
  613. var origin = axis.origin;
  614. var i, pt, vx, vy, dp;
  615. for (i = 0; i < points.length; ++i) {
  616. pt = points[i];
  617. vx = pt.x - origin.x;
  618. vy = pt.y - origin.y;
  619. dp = axis.vx * vx + axis.vy * vy;
  620. min = Math.min(min, dp);
  621. max = Math.max(max, dp);
  622. }
  623. return {
  624. min: min,
  625. max: max
  626. };
  627. }
  628. function toAxis(p0, p1) {
  629. var vx = p1.x - p0.x;
  630. var vy = p1.y - p0.y;
  631. var ln = Math.sqrt(vx * vx + vy * vy);
  632. return {
  633. vx: (p1.x - p0.x) / ln,
  634. vy: (p1.y - p0.y) / ln,
  635. origin: p0,
  636. ln: ln
  637. };
  638. }
  639. var HitBox = function() {
  640. this._rotation = 0;
  641. this._rect = {
  642. x: 0,
  643. y: 0,
  644. w: 0,
  645. h: 0
  646. };
  647. };
  648. helpers.merge(HitBox.prototype, {
  649. center: function() {
  650. var r = this._rect;
  651. return {
  652. x: r.x + r.w / 2,
  653. y: r.y + r.h / 2
  654. };
  655. },
  656. update: function(center, rect, rotation) {
  657. this._rotation = rotation;
  658. this._rect = {
  659. x: rect.x + center.x,
  660. y: rect.y + center.y,
  661. w: rect.w,
  662. h: rect.h
  663. };
  664. },
  665. contains: function(point) {
  666. var me = this;
  667. var margin = 1;
  668. var rect = me._rect;
  669. point = rotated(point, me.center(), -me._rotation);
  670. return !(point.x < rect.x - margin
  671. || point.y < rect.y - margin
  672. || point.x > rect.x + rect.w + margin * 2
  673. || point.y > rect.y + rect.h + margin * 2);
  674. },
  675. // Separating Axis Theorem
  676. // https://gamedevelopment.tutsplus.com/tutorials/collision-detection-using-the-separating-axis-theorem--gamedev-169
  677. intersects: function(other) {
  678. var r0 = this._points();
  679. var r1 = other._points();
  680. var axes = [
  681. toAxis(r0[0], r0[1]),
  682. toAxis(r0[0], r0[3])
  683. ];
  684. var i, pr0, pr1;
  685. if (this._rotation !== other._rotation) {
  686. // Only separate with r1 axis if the rotation is different,
  687. // else it's enough to separate r0 and r1 with r0 axis only!
  688. axes.push(
  689. toAxis(r1[0], r1[1]),
  690. toAxis(r1[0], r1[3])
  691. );
  692. }
  693. for (i = 0; i < axes.length; ++i) {
  694. pr0 = projected(r0, axes[i]);
  695. pr1 = projected(r1, axes[i]);
  696. if (pr0.max < pr1.min || pr1.max < pr0.min) {
  697. return false;
  698. }
  699. }
  700. return true;
  701. },
  702. /**
  703. * @private
  704. */
  705. _points: function() {
  706. var me = this;
  707. var rect = me._rect;
  708. var angle = me._rotation;
  709. var center = me.center();
  710. return [
  711. rotated({x: rect.x, y: rect.y}, center, angle),
  712. rotated({x: rect.x + rect.w, y: rect.y}, center, angle),
  713. rotated({x: rect.x + rect.w, y: rect.y + rect.h}, center, angle),
  714. rotated({x: rect.x, y: rect.y + rect.h}, center, angle)
  715. ];
  716. }
  717. });
  718. function coordinates(el, model, geometry) {
  719. var point = model.positioner(el, model);
  720. var vx = point.vx;
  721. var vy = point.vy;
  722. if (!vx && !vy) {
  723. // if aligned center, we don't want to offset the center point
  724. return {x: point.x, y: point.y};
  725. }
  726. var w = geometry.w;
  727. var h = geometry.h;
  728. // take in account the label rotation
  729. var rotation = model.rotation;
  730. var dx = Math.abs(w / 2 * Math.cos(rotation)) + Math.abs(h / 2 * Math.sin(rotation));
  731. var dy = Math.abs(w / 2 * Math.sin(rotation)) + Math.abs(h / 2 * Math.cos(rotation));
  732. // scale the unit vector (vx, vy) to get at least dx or dy equal to
  733. // w or h respectively (else we would calculate the distance to the
  734. // ellipse inscribed in the bounding rect)
  735. var vs = 1 / Math.max(Math.abs(vx), Math.abs(vy));
  736. dx *= vx * vs;
  737. dy *= vy * vs;
  738. // finally, include the explicit offset
  739. dx += model.offset * vx;
  740. dy += model.offset * vy;
  741. return {
  742. x: point.x + dx,
  743. y: point.y + dy
  744. };
  745. }
  746. function collide(labels, collider) {
  747. var i, j, s0, s1;
  748. // IMPORTANT Iterate in the reverse order since items at the end of the
  749. // list have an higher weight/priority and thus should be less impacted
  750. // by the overlapping strategy.
  751. for (i = labels.length - 1; i >= 0; --i) {
  752. s0 = labels[i].$layout;
  753. for (j = i - 1; j >= 0 && s0._visible; --j) {
  754. s1 = labels[j].$layout;
  755. if (s1._visible && s0._box.intersects(s1._box)) {
  756. collider(s0, s1);
  757. }
  758. }
  759. }
  760. return labels;
  761. }
  762. function compute(labels) {
  763. var i, ilen, label, state, geometry, center, proxy;
  764. // Initialize labels for overlap detection
  765. for (i = 0, ilen = labels.length; i < ilen; ++i) {
  766. label = labels[i];
  767. state = label.$layout;
  768. if (state._visible) {
  769. // Chart.js 3 removed el._model in favor of getProps(), making harder to
  770. // abstract reading values in positioners. Also, using string arrays to
  771. // read values (i.e. var {a,b,c} = el.getProps(["a","b","c"])) would make
  772. // positioners inefficient in the normal case (i.e. not the final values)
  773. // and the code a bit ugly, so let's use a Proxy instead.
  774. proxy = new Proxy(label._el, {get: (el, p) => el.getProps([p], true)[p]});
  775. geometry = label.geometry();
  776. center = coordinates(proxy, label.model(), geometry);
  777. state._box.update(center, geometry, label.rotation());
  778. }
  779. }
  780. // Auto hide overlapping labels
  781. return collide(labels, function(s0, s1) {
  782. var h0 = s0._hidable;
  783. var h1 = s1._hidable;
  784. if ((h0 && h1) || h1) {
  785. s1._visible = false;
  786. } else if (h0) {
  787. s0._visible = false;
  788. }
  789. });
  790. }
  791. var layout = {
  792. prepare: function(datasets) {
  793. var labels = [];
  794. var i, j, ilen, jlen, label;
  795. for (i = 0, ilen = datasets.length; i < ilen; ++i) {
  796. for (j = 0, jlen = datasets[i].length; j < jlen; ++j) {
  797. label = datasets[i][j];
  798. labels.push(label);
  799. label.$layout = {
  800. _box: new HitBox(),
  801. _hidable: false,
  802. _visible: true,
  803. _set: i,
  804. _idx: j
  805. };
  806. }
  807. }
  808. // TODO New `z` option: labels with a higher z-index are drawn
  809. // of top of the ones with a lower index. Lowest z-index labels
  810. // are also discarded first when hiding overlapping labels.
  811. labels.sort(function(a, b) {
  812. var sa = a.$layout;
  813. var sb = b.$layout;
  814. return sa._idx === sb._idx
  815. ? sb._set - sa._set
  816. : sb._idx - sa._idx;
  817. });
  818. this.update(labels);
  819. return labels;
  820. },
  821. update: function(labels) {
  822. var dirty = false;
  823. var i, ilen, label, model, state;
  824. for (i = 0, ilen = labels.length; i < ilen; ++i) {
  825. label = labels[i];
  826. model = label.model();
  827. state = label.$layout;
  828. state._hidable = model && model.display === 'auto';
  829. state._visible = label.visible();
  830. dirty |= state._hidable;
  831. }
  832. if (dirty) {
  833. compute(labels);
  834. }
  835. },
  836. lookup: function(labels, point) {
  837. var i, state;
  838. // IMPORTANT Iterate in the reverse order since items at the end of
  839. // the list have an higher z-index, thus should be picked first.
  840. for (i = labels.length - 1; i >= 0; --i) {
  841. state = labels[i].$layout;
  842. if (state && state._visible && state._box.contains(point)) {
  843. return labels[i];
  844. }
  845. }
  846. return null;
  847. },
  848. draw: function(chart, labels) {
  849. var i, ilen, label, state, geometry, center;
  850. for (i = 0, ilen = labels.length; i < ilen; ++i) {
  851. label = labels[i];
  852. state = label.$layout;
  853. if (state._visible) {
  854. geometry = label.geometry();
  855. center = coordinates(label._el, label.model(), geometry);
  856. state._box.update(center, geometry, label.rotation());
  857. label.draw(chart, center);
  858. }
  859. }
  860. }
  861. };
  862. var formatter = function(value) {
  863. if (helpers.isNullOrUndef(value)) {
  864. return null;
  865. }
  866. var label = value;
  867. var keys, klen, k;
  868. if (helpers.isObject(value)) {
  869. if (!helpers.isNullOrUndef(value.label)) {
  870. label = value.label;
  871. } else if (!helpers.isNullOrUndef(value.r)) {
  872. label = value.r;
  873. } else {
  874. label = '';
  875. keys = Object.keys(value);
  876. for (k = 0, klen = keys.length; k < klen; ++k) {
  877. label += (k !== 0 ? ', ' : '') + keys[k] + ': ' + value[keys[k]];
  878. }
  879. }
  880. }
  881. return '' + label;
  882. };
  883. /**
  884. * IMPORTANT: make sure to also update tests and TypeScript definition
  885. * files (`/test/specs/defaults.spec.js` and `/types/options.d.ts`)
  886. */
  887. var defaults = {
  888. align: 'center',
  889. anchor: 'center',
  890. backgroundColor: null,
  891. borderColor: null,
  892. borderRadius: 0,
  893. borderWidth: 0,
  894. clamp: false,
  895. clip: false,
  896. color: undefined,
  897. display: true,
  898. font: {
  899. family: undefined,
  900. lineHeight: 1.2,
  901. size: undefined,
  902. style: undefined,
  903. weight: null
  904. },
  905. formatter: formatter,
  906. labels: undefined,
  907. listeners: {},
  908. offset: 4,
  909. opacity: 1,
  910. padding: {
  911. top: 4,
  912. right: 4,
  913. bottom: 4,
  914. left: 4
  915. },
  916. rotation: 0,
  917. textAlign: 'start',
  918. textStrokeColor: undefined,
  919. textStrokeWidth: 0,
  920. textShadowBlur: 0,
  921. textShadowColor: undefined
  922. };
  923. /**
  924. * @see https://github.com/chartjs/Chart.js/issues/4176
  925. */
  926. var EXPANDO_KEY = '$datalabels';
  927. var DEFAULT_KEY = '$default';
  928. function configure(dataset, options) {
  929. var override = dataset.datalabels;
  930. var listeners = {};
  931. var configs = [];
  932. var labels, keys;
  933. if (override === false) {
  934. return null;
  935. }
  936. if (override === true) {
  937. override = {};
  938. }
  939. options = helpers.merge({}, [options, override]);
  940. labels = options.labels || {};
  941. keys = Object.keys(labels);
  942. delete options.labels;
  943. if (keys.length) {
  944. keys.forEach(function(key) {
  945. if (labels[key]) {
  946. configs.push(helpers.merge({}, [
  947. options,
  948. labels[key],
  949. {_key: key}
  950. ]));
  951. }
  952. });
  953. } else {
  954. // Default label if no "named" label defined.
  955. configs.push(options);
  956. }
  957. // listeners: {<event-type>: {<label-key>: <fn>}}
  958. listeners = configs.reduce(function(target, config) {
  959. helpers.each(config.listeners || {}, function(fn, event) {
  960. target[event] = target[event] || {};
  961. target[event][config._key || DEFAULT_KEY] = fn;
  962. });
  963. delete config.listeners;
  964. return target;
  965. }, {});
  966. return {
  967. labels: configs,
  968. listeners: listeners
  969. };
  970. }
  971. function dispatchEvent(chart, listeners, label) {
  972. if (!listeners) {
  973. return;
  974. }
  975. var context = label.$context;
  976. var groups = label.$groups;
  977. var callback;
  978. if (!listeners[groups._set]) {
  979. return;
  980. }
  981. callback = listeners[groups._set][groups._key];
  982. if (!callback) {
  983. return;
  984. }
  985. if (helpers.callback(callback, [context]) === true) {
  986. // Users are allowed to tweak the given context by injecting values that can be
  987. // used in scriptable options to display labels differently based on the current
  988. // event (e.g. highlight an hovered label). That's why we update the label with
  989. // the output context and schedule a new chart render by setting it dirty.
  990. chart[EXPANDO_KEY]._dirty = true;
  991. label.update(context);
  992. }
  993. }
  994. function dispatchMoveEvents(chart, listeners, previous, label) {
  995. var enter, leave;
  996. if (!previous && !label) {
  997. return;
  998. }
  999. if (!previous) {
  1000. enter = true;
  1001. } else if (!label) {
  1002. leave = true;
  1003. } else if (previous !== label) {
  1004. leave = enter = true;
  1005. }
  1006. if (leave) {
  1007. dispatchEvent(chart, listeners.leave, previous);
  1008. }
  1009. if (enter) {
  1010. dispatchEvent(chart, listeners.enter, label);
  1011. }
  1012. }
  1013. function handleMoveEvents(chart, event) {
  1014. var expando = chart[EXPANDO_KEY];
  1015. var listeners = expando._listeners;
  1016. var previous, label;
  1017. if (!listeners.enter && !listeners.leave) {
  1018. return;
  1019. }
  1020. if (event.type === 'mousemove') {
  1021. label = layout.lookup(expando._labels, event);
  1022. } else if (event.type !== 'mouseout') {
  1023. return;
  1024. }
  1025. previous = expando._hovered;
  1026. expando._hovered = label;
  1027. dispatchMoveEvents(chart, listeners, previous, label);
  1028. }
  1029. function handleClickEvents(chart, event) {
  1030. var expando = chart[EXPANDO_KEY];
  1031. var handlers = expando._listeners.click;
  1032. var label = handlers && layout.lookup(expando._labels, event);
  1033. if (label) {
  1034. dispatchEvent(chart, handlers, label);
  1035. }
  1036. }
  1037. var plugin = {
  1038. id: 'datalabels',
  1039. defaults: defaults,
  1040. beforeInit: function(chart) {
  1041. chart[EXPANDO_KEY] = {
  1042. _actives: []
  1043. };
  1044. },
  1045. beforeUpdate: function(chart) {
  1046. var expando = chart[EXPANDO_KEY];
  1047. expando._listened = false;
  1048. expando._listeners = {}; // {<event-type>: {<dataset-index>: {<label-key>: <fn>}}}
  1049. expando._datasets = []; // per dataset labels: [Label[]]
  1050. expando._labels = []; // layouted labels: Label[]
  1051. },
  1052. afterDatasetUpdate: function(chart, args, options) {
  1053. var datasetIndex = args.index;
  1054. var expando = chart[EXPANDO_KEY];
  1055. var labels = expando._datasets[datasetIndex] = [];
  1056. var visible = chart.isDatasetVisible(datasetIndex);
  1057. var dataset = chart.data.datasets[datasetIndex];
  1058. var config = configure(dataset, options);
  1059. var elements = args.meta.data || [];
  1060. var ctx = chart.ctx;
  1061. var i, j, ilen, jlen, cfg, key, el, label;
  1062. ctx.save();
  1063. for (i = 0, ilen = elements.length; i < ilen; ++i) {
  1064. el = elements[i];
  1065. el[EXPANDO_KEY] = [];
  1066. if (visible && el && chart.getDataVisibility(i) && !el.skip) {
  1067. for (j = 0, jlen = config.labels.length; j < jlen; ++j) {
  1068. cfg = config.labels[j];
  1069. key = cfg._key;
  1070. label = new Label(cfg, ctx, el, i);
  1071. label.$groups = {
  1072. _set: datasetIndex,
  1073. _key: key || DEFAULT_KEY
  1074. };
  1075. label.$context = {
  1076. active: false,
  1077. chart: chart,
  1078. dataIndex: i,
  1079. dataset: dataset,
  1080. datasetIndex: datasetIndex
  1081. };
  1082. label.update(label.$context);
  1083. el[EXPANDO_KEY].push(label);
  1084. labels.push(label);
  1085. }
  1086. }
  1087. }
  1088. ctx.restore();
  1089. // Store listeners at the chart level and per event type to optimize
  1090. // cases where no listeners are registered for a specific event.
  1091. helpers.merge(expando._listeners, config.listeners, {
  1092. merger: function(event, target, source) {
  1093. target[event] = target[event] || {};
  1094. target[event][args.index] = source[event];
  1095. expando._listened = true;
  1096. }
  1097. });
  1098. },
  1099. afterUpdate: function(chart, options) {
  1100. chart[EXPANDO_KEY]._labels = layout.prepare(
  1101. chart[EXPANDO_KEY]._datasets,
  1102. options);
  1103. },
  1104. // Draw labels on top of all dataset elements
  1105. // https://github.com/chartjs/chartjs-plugin-datalabels/issues/29
  1106. // https://github.com/chartjs/chartjs-plugin-datalabels/issues/32
  1107. afterDatasetsDraw: function(chart) {
  1108. layout.draw(chart, chart[EXPANDO_KEY]._labels);
  1109. },
  1110. beforeEvent: function(chart, args) {
  1111. // If there is no listener registered for this chart, `listened` will be false,
  1112. // meaning we can immediately ignore the incoming event and avoid useless extra
  1113. // computation for users who don't implement label interactions.
  1114. if (chart[EXPANDO_KEY]._listened) {
  1115. var event = args.event;
  1116. switch (event.type) {
  1117. case 'mousemove':
  1118. case 'mouseout':
  1119. handleMoveEvents(chart, event);
  1120. break;
  1121. case 'click':
  1122. handleClickEvents(chart, event);
  1123. break;
  1124. }
  1125. }
  1126. },
  1127. afterEvent: function(chart) {
  1128. var expando = chart[EXPANDO_KEY];
  1129. var previous = expando._actives;
  1130. var actives = expando._actives = chart.getActiveElements();
  1131. var updates = utils.arrayDiff(previous, actives);
  1132. var i, ilen, j, jlen, update, label, labels;
  1133. for (i = 0, ilen = updates.length; i < ilen; ++i) {
  1134. update = updates[i];
  1135. if (update[1]) {
  1136. labels = update[0].element[EXPANDO_KEY] || [];
  1137. for (j = 0, jlen = labels.length; j < jlen; ++j) {
  1138. label = labels[j];
  1139. label.$context.active = (update[1] === 1);
  1140. label.update(label.$context);
  1141. }
  1142. }
  1143. }
  1144. if (expando._dirty || updates.length) {
  1145. layout.update(expando._labels);
  1146. chart.render();
  1147. }
  1148. delete expando._dirty;
  1149. }
  1150. };
  1151. return plugin;
  1152. })));