label.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507
  1. /**
  2. * DevExtreme (viz/series/points/label.js)
  3. * Version: 19.1.16
  4. * Build date: Tue Oct 18 2022
  5. *
  6. * Copyright (c) 2012 - 2022 Developer Express Inc. ALL RIGHTS RESERVED
  7. * Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
  8. */
  9. "use strict";
  10. var _format_helper = require("../../../format_helper");
  11. var _utils = require("../../core/utils");
  12. var _iterator = require("../../../core/utils/iterator");
  13. var _extend = require("../../../core/utils/extend");
  14. var _math = Math;
  15. var _round = _math.round;
  16. var _floor = _math.floor;
  17. var _abs = _math.abs;
  18. var CONNECTOR_LENGTH = 12;
  19. var LABEL_BACKGROUND_PADDING_X = 8;
  20. var LABEL_BACKGROUND_PADDING_Y = 4;
  21. function getClosestCoord(point, coords) {
  22. var closestDistance = 1 / 0;
  23. var closestCoord;
  24. (0, _iterator.each)(coords, function(_, coord) {
  25. var x = point[0] - coord[0];
  26. var y = point[1] - coord[1];
  27. var distance = x * x + y * y;
  28. if (distance < closestDistance) {
  29. closestDistance = distance;
  30. closestCoord = coord
  31. }
  32. });
  33. return [_floor(closestCoord[0]), _floor(closestCoord[1])]
  34. }
  35. function getCrossCoord(rect, coord, indexOffset) {
  36. return (coord - rect[0 + indexOffset]) / (rect[2 + indexOffset] - rect[0 + indexOffset]) * (rect[3 - indexOffset] - rect[1 - indexOffset]) + rect[1 - indexOffset]
  37. }
  38. var barPointStrategy = {
  39. isLabelInside: function(labelPoint, figure) {
  40. var xc = labelPoint.x + labelPoint.width / 2;
  41. var yc = labelPoint.y + labelPoint.height / 2;
  42. return figure.x <= xc && xc <= figure.x + figure.width && figure.y <= yc && yc <= figure.y + figure.height
  43. },
  44. prepareLabelPoints: function(bBox, rotatedBBox, isHorizontal, angle, figureCenter) {
  45. var x1 = rotatedBBox.x;
  46. var xc = x1 + rotatedBBox.width / 2;
  47. var x2 = x1 + rotatedBBox.width - 1;
  48. var y1 = rotatedBBox.y;
  49. var yc = y1 + rotatedBBox.height / 2;
  50. var y2 = y1 + rotatedBBox.height - 1;
  51. var labelPoints;
  52. var isRectangular = _abs(angle) % 90 === 0;
  53. if (figureCenter[0] > x1 && figureCenter[0] < x2) {
  54. if (isRectangular) {
  55. labelPoints = [
  56. [figureCenter[0], _abs(figureCenter[1] - y1) < _abs(figureCenter[1] - y2) ? y1 : y2]
  57. ]
  58. } else {
  59. labelPoints = [
  60. [figureCenter[0], getCrossCoord([x1, y1, x2, y2], figureCenter[0], 0)]
  61. ]
  62. }
  63. } else {
  64. if (figureCenter[1] > y1 && figureCenter[1] < y2) {
  65. if (isRectangular) {
  66. labelPoints = [
  67. [_abs(figureCenter[0] - x1) < _abs(figureCenter[0] - x2) ? x1 : x2, figureCenter[1]]
  68. ]
  69. } else {
  70. labelPoints = [
  71. [getCrossCoord([x1, y1, x2, y2], figureCenter[1], 1), figureCenter[1]]
  72. ]
  73. }
  74. } else {
  75. if (isRectangular) {
  76. labelPoints = [
  77. [x1, y1],
  78. [isHorizontal ? x1 : xc, isHorizontal ? yc : y1],
  79. [x2, y1],
  80. [x1, y2],
  81. [isHorizontal ? x2 : xc, isHorizontal ? yc : y2],
  82. [x2, y2]
  83. ]
  84. } else {
  85. labelPoints = [
  86. [xc, yc]
  87. ]
  88. }
  89. }
  90. }
  91. return labelPoints
  92. },
  93. isHorizontal: function(bBox, figure) {
  94. return bBox.x > figure.x + figure.width || bBox.x + bBox.width < figure.x
  95. },
  96. getFigureCenter: function(figure) {
  97. return [_floor(figure.x + figure.width / 2), _floor(figure.y + figure.height / 2)]
  98. },
  99. findFigurePoint: function(figure, labelPoint) {
  100. var figureCenter = barPointStrategy.getFigureCenter(figure);
  101. var point = getClosestCoord(labelPoint, [
  102. [figure.x, figureCenter[1]],
  103. [figureCenter[0], figure.y + figure.height],
  104. [figure.x + figure.width, figureCenter[1]],
  105. [figureCenter[0], figure.y]
  106. ]);
  107. return point
  108. },
  109. adjustPoints: function(points) {
  110. var lineIsVertical = _abs(points[1] - points[3]) <= 1;
  111. var lineIsHorizontal = _abs(points[0] - points[2]) <= 1;
  112. if (lineIsHorizontal) {
  113. points[0] = points[2]
  114. }
  115. if (lineIsVertical) {
  116. points[1] = points[3]
  117. }
  118. return points
  119. }
  120. };
  121. var symbolPointStrategy = {
  122. isLabelInside: function() {
  123. return false
  124. },
  125. prepareLabelPoints: barPointStrategy.prepareLabelPoints,
  126. isHorizontal: function(bBox, figure) {
  127. return bBox.x > figure.x + figure.r || bBox.x + bBox.width < figure.x - figure.r
  128. },
  129. getFigureCenter: function(figure) {
  130. return [figure.x, figure.y]
  131. },
  132. findFigurePoint: function(figure, labelPoint) {
  133. var angle = Math.atan2(figure.y - labelPoint[1], labelPoint[0] - figure.x);
  134. return [_round(figure.x + figure.r * Math.cos(angle)), _round(figure.y - figure.r * Math.sin(angle))]
  135. },
  136. adjustPoints: barPointStrategy.adjustPoints
  137. };
  138. var piePointStrategy = {
  139. isLabelInside: function(_0, _1, isOutside) {
  140. return !isOutside
  141. },
  142. prepareLabelPoints: function(bBox, rotatedBBox, isHorizontal, angle) {
  143. var xl = bBox.x;
  144. var xr = xl + bBox.width;
  145. var xc = xl + _round(bBox.width / 2);
  146. var yt = bBox.y;
  147. var yb = yt + bBox.height;
  148. var yc = yt + _round(bBox.height / 2);
  149. var points = [
  150. [
  151. [xl, yt],
  152. [xr, yt]
  153. ],
  154. [
  155. [xr, yt],
  156. [xr, yb]
  157. ],
  158. [
  159. [xr, yb],
  160. [xl, yb]
  161. ],
  162. [
  163. [xl, yb],
  164. [xl, yt]
  165. ]
  166. ];
  167. var cosSin = (0, _utils.getCosAndSin)(angle);
  168. if (0 === angle) {
  169. points = isHorizontal ? [
  170. [xl, yc],
  171. [xr, yc]
  172. ] : [
  173. [xc, yt],
  174. [xc, yb]
  175. ]
  176. } else {
  177. points = points.map(function(pair) {
  178. return pair.map(function(point) {
  179. return [_round((point[0] - xc) * cosSin.cos + (point[1] - yc) * cosSin.sin + xc), _round(-(point[0] - xc) * cosSin.sin + (point[1] - yc) * cosSin.cos + yc)]
  180. })
  181. }).reduce(function(r, pair) {
  182. var point1x = pair[0][0];
  183. var point1y = pair[0][1];
  184. var point2x = pair[1][0];
  185. var point2y = pair[1][1];
  186. if (isHorizontal) {
  187. if (point1y >= yc && yc >= point2y || point1y <= yc && yc <= point2y) {
  188. r.push([(yc - point1y) * (point2x - point1x) / (point2y - point1y) + point1x, yc])
  189. }
  190. } else {
  191. if (point1x >= xc && xc >= point2x || point1x <= xc && xc <= point2x) {
  192. r.push([xc, (xc - point1x) * (point2y - point1y) / (point2x - point1x) + point1y])
  193. }
  194. }
  195. return r
  196. }, [])
  197. }
  198. return points
  199. },
  200. isHorizontal: function(bBox, figure) {
  201. return bBox.x > figure.x || figure.x > bBox.x + bBox.width
  202. },
  203. getFigureCenter: symbolPointStrategy.getFigureCenter,
  204. findFigurePoint: function(figure, labelPoint, isHorizontal) {
  205. if (!isHorizontal) {
  206. return [figure.x, figure.y]
  207. }
  208. var labelX = labelPoint[0];
  209. var x = _round(figure.x + (figure.y - labelPoint[1]) / Math.tan((0, _utils.degreesToRadians)(figure.angle)));
  210. var points = [figure.x, figure.y, x, labelPoint[1]];
  211. if (!(figure.x <= x && x <= labelX) && !(labelX <= x && x <= figure.x)) {
  212. if (_abs(figure.x - labelX) < CONNECTOR_LENGTH) {
  213. points = [figure.x, figure.y]
  214. } else {
  215. if (figure.x <= labelX) {
  216. points[2] = figure.x + CONNECTOR_LENGTH
  217. } else {
  218. points[2] = figure.x - CONNECTOR_LENGTH
  219. }
  220. }
  221. }
  222. return points
  223. },
  224. adjustPoints: function(points) {
  225. return points
  226. }
  227. };
  228. function selectStrategy(figure) {
  229. return void 0 !== figure.angle && piePointStrategy || void 0 !== figure.r && symbolPointStrategy || barPointStrategy
  230. }
  231. function disposeItem(obj, field) {
  232. obj[field] && obj[field].dispose();
  233. obj[field] = null
  234. }
  235. function checkBackground(background) {
  236. return background && (background.fill && "none" !== background.fill || background["stroke-width"] > 0 && background.stroke && "none" !== background.stroke)
  237. }
  238. function checkConnector(connector) {
  239. return connector && connector["stroke-width"] > 0 && connector.stroke && "none" !== connector.stroke
  240. }
  241. function formatText(data, options) {
  242. var format = options.format;
  243. data.valueText = (0, _format_helper.format)(data.value, format);
  244. data.argumentText = (0, _format_helper.format)(data.argument, options.argumentFormat);
  245. if (void 0 !== data.percent) {
  246. data.percentText = (0, _format_helper.format)(data.percent, {
  247. type: "percent",
  248. precision: format && format.percentPrecision
  249. })
  250. }
  251. if (void 0 !== data.total) {
  252. data.totalText = (0, _format_helper.format)(data.total, format)
  253. }
  254. if (void 0 !== data.openValue) {
  255. data.openValueText = (0, _format_helper.format)(data.openValue, format)
  256. }
  257. if (void 0 !== data.closeValue) {
  258. data.closeValueText = (0, _format_helper.format)(data.closeValue, format)
  259. }
  260. if (void 0 !== data.lowValue) {
  261. data.lowValueText = (0, _format_helper.format)(data.lowValue, format)
  262. }
  263. if (void 0 !== data.highValue) {
  264. data.highValueText = (0, _format_helper.format)(data.highValue, format)
  265. }
  266. if (void 0 !== data.reductionValue) {
  267. data.reductionValueText = (0, _format_helper.format)(data.reductionValue, format)
  268. }
  269. return options.customizeText ? options.customizeText.call(data, data) : data.valueText
  270. }
  271. function Label(renderSettings) {
  272. this._renderer = renderSettings.renderer;
  273. this._container = renderSettings.labelsGroup;
  274. this._point = renderSettings.point;
  275. this._strategy = renderSettings.strategy;
  276. this._rowCount = 1
  277. }
  278. Label.prototype = {
  279. constructor: Label,
  280. setColor: function(color) {
  281. this._color = color
  282. },
  283. setOptions: function(options) {
  284. this._options = options
  285. },
  286. setData: function(data) {
  287. this._data = data
  288. },
  289. setDataField: function(fieldName, fieldValue) {
  290. this._data = this._data || {};
  291. this._data[fieldName] = fieldValue
  292. },
  293. getData: function() {
  294. return this._data
  295. },
  296. setFigureToDrawConnector: function(figure) {
  297. this._figure = figure
  298. },
  299. dispose: function() {
  300. var that = this;
  301. disposeItem(that, "_group");
  302. that._data = that._options = that._textContent = that._visible = that._insideGroup = that._text = that._background = that._connector = that._figure = null
  303. },
  304. _setVisibility: function(value, state) {
  305. this._group && this._group.attr({
  306. visibility: value
  307. });
  308. this._visible = state
  309. },
  310. isVisible: function() {
  311. return this._visible
  312. },
  313. hide: function(holdInvisible) {
  314. this._holdVisibility = !!holdInvisible;
  315. this._hide()
  316. },
  317. _hide: function() {
  318. this._setVisibility("hidden", false)
  319. },
  320. show: function(holdVisible) {
  321. var correctPosition = !this._drawn;
  322. if (this._point.hasValue()) {
  323. this._holdVisibility = !!holdVisible;
  324. this._show();
  325. correctPosition && this._point.correctLabelPosition(this)
  326. }
  327. },
  328. _show: function() {
  329. var that = this;
  330. var renderer = that._renderer;
  331. var container = that._container;
  332. var options = that._options || {};
  333. var text = that._textContent = formatText(that._data, that._options) || null;
  334. if (text) {
  335. if (!that._group) {
  336. that._group = renderer.g().append(container);
  337. that._insideGroup = renderer.g().append(that._group);
  338. that._text = renderer.text("", 0, 0).append(that._insideGroup)
  339. }
  340. that._text.css(options.attributes ? (0, _utils.patchFontOptions)(options.attributes.font) : {});
  341. if (checkBackground(options.background)) {
  342. that._background = that._background || renderer.rect().append(that._insideGroup).toBackground();
  343. that._background.attr(options.background);
  344. that._color && that._background.attr({
  345. fill: that._color
  346. })
  347. } else {
  348. disposeItem(that, "_background")
  349. }
  350. if (checkConnector(options.connector)) {
  351. that._connector = that._connector || renderer.path([], "line").sharp().append(that._group).toBackground();
  352. that._connector.attr(options.connector);
  353. that._color && that._connector.attr({
  354. stroke: that._color
  355. })
  356. } else {
  357. disposeItem(that, "_connector")
  358. }
  359. that._text.attr({
  360. text: text,
  361. align: options.textAlignment,
  362. "class": options.cssClass
  363. });
  364. that._updateBackground(that._text.getBBox());
  365. that._setVisibility("visible", true);
  366. that._drawn = true
  367. } else {
  368. that._hide()
  369. }
  370. },
  371. _getLabelVisibility: function(isVisible) {
  372. return this._holdVisibility ? this.isVisible() : isVisible
  373. },
  374. draw: function(isVisible) {
  375. if (this._getLabelVisibility(isVisible)) {
  376. this._show();
  377. this._point && this._point.correctLabelPosition(this)
  378. } else {
  379. this._drawn = false;
  380. this._hide()
  381. }
  382. return this
  383. },
  384. _updateBackground: function(bBox) {
  385. var that = this;
  386. if (that._background) {
  387. bBox.x -= LABEL_BACKGROUND_PADDING_X;
  388. bBox.y -= LABEL_BACKGROUND_PADDING_Y;
  389. bBox.width += 2 * LABEL_BACKGROUND_PADDING_X;
  390. bBox.height += 2 * LABEL_BACKGROUND_PADDING_Y;
  391. that._background.attr(bBox)
  392. }
  393. that._bBoxWithoutRotation = (0, _extend.extend)({}, bBox);
  394. var rotationAngle = that._options.rotationAngle || 0;
  395. that._insideGroup.rotate(rotationAngle, bBox.x + bBox.width / 2, bBox.y + bBox.height / 2);
  396. bBox = (0, _utils.rotateBBox)(bBox, [bBox.x + bBox.width / 2, bBox.y + bBox.height / 2], -rotationAngle);
  397. that._bBox = bBox
  398. },
  399. getFigureCenter: function() {
  400. var figure = this._figure;
  401. var strategy = this._strategy || selectStrategy(figure);
  402. return strategy.getFigureCenter(figure)
  403. },
  404. _getConnectorPoints: function() {
  405. var that = this;
  406. var figure = that._figure;
  407. var options = that._options;
  408. var strategy = that._strategy || selectStrategy(figure);
  409. var bBox = that._shiftBBox(that._bBoxWithoutRotation);
  410. var rotatedBBox = that.getBoundingRect();
  411. var labelPoint;
  412. var points = [];
  413. var isHorizontal;
  414. if (!strategy.isLabelInside(bBox, figure, "inside" !== options.position)) {
  415. isHorizontal = strategy.isHorizontal(bBox, figure);
  416. var figureCenter = that.getFigureCenter();
  417. points = strategy.prepareLabelPoints(bBox, rotatedBBox, isHorizontal, -options.rotationAngle || 0, figureCenter);
  418. labelPoint = getClosestCoord(figureCenter, points);
  419. points = strategy.findFigurePoint(figure, labelPoint, isHorizontal);
  420. points = points.concat(labelPoint)
  421. }
  422. return strategy.adjustPoints(points)
  423. },
  424. fit: function(maxWidth) {
  425. var padding = this._background ? 2 * LABEL_BACKGROUND_PADDING_X : 0;
  426. var rowCountChanged = false;
  427. if (this._text) {
  428. var result = this._text.setMaxSize(maxWidth - padding, void 0, this._options);
  429. var rowCount = result.rowCount;
  430. if (0 === rowCount) {
  431. rowCount = 1
  432. }
  433. if (rowCount !== this._rowCount) {
  434. rowCountChanged = true;
  435. this._rowCount = rowCount
  436. }
  437. result.textIsEmpty && disposeItem(this, "_background")
  438. }
  439. this._updateBackground(this._text.getBBox());
  440. return rowCountChanged
  441. },
  442. resetEllipsis: function() {
  443. this._text && this._text.restoreText();
  444. this._updateBackground(this._text.getBBox())
  445. },
  446. setTrackerData: function(point) {
  447. this._text.data({
  448. "chart-data-point": point
  449. });
  450. this._background && this._background.data({
  451. "chart-data-point": point
  452. })
  453. },
  454. hideInsideLabel: function(coords) {
  455. return this._point.hideInsideLabel(this, coords)
  456. },
  457. getPoint: function() {
  458. return this._point
  459. },
  460. shift: function(x, y) {
  461. var that = this;
  462. if (that._textContent) {
  463. that._insideGroup.attr({
  464. translateX: that._x = _round(x - that._bBox.x),
  465. translateY: that._y = _round(y - that._bBox.y)
  466. });
  467. if (that._connector) {
  468. that._connector.attr({
  469. points: that._getConnectorPoints()
  470. })
  471. }
  472. }
  473. return that
  474. },
  475. getBoundingRect: function() {
  476. return this._shiftBBox(this._bBox)
  477. },
  478. _shiftBBox: function(bBox) {
  479. return this._textContent ? {
  480. x: bBox.x + this._x,
  481. y: bBox.y + this._y,
  482. width: bBox.width,
  483. height: bBox.height
  484. } : {}
  485. },
  486. getLayoutOptions: function() {
  487. var options = this._options;
  488. return {
  489. alignment: options.alignment,
  490. background: checkBackground(options.background),
  491. horizontalOffset: options.horizontalOffset,
  492. verticalOffset: options.verticalOffset,
  493. radialOffset: options.radialOffset,
  494. position: options.position,
  495. connectorOffset: (checkConnector(options.connector) ? CONNECTOR_LENGTH : 0) + (checkBackground(options.background) ? LABEL_BACKGROUND_PADDING_X : 0)
  496. }
  497. }
  498. };
  499. exports.Label = Label;