line_series.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525
  1. /**
  2. * DevExtreme (viz/series/line_series.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 series = require("./scatter_series");
  11. var chartScatterSeries = series.chart;
  12. var polarScatterSeries = series.polar;
  13. var objectUtils = require("../../core/utils/object");
  14. var extend = require("../../core/utils/extend").extend;
  15. var each = require("../../core/utils/iterator").each;
  16. var vizUtils = require("../core/utils");
  17. var mathUtils = require("../../core/utils/math");
  18. var normalizeAngle = vizUtils.normalizeAngle;
  19. var DISCRETE = "discrete";
  20. var _map = vizUtils.map;
  21. var _extend = extend;
  22. var _each = each;
  23. exports.chart = {};
  24. exports.polar = {};
  25. function clonePoint(point, newX, newY, newAngle) {
  26. var p = objectUtils.clone(point);
  27. p.x = newX;
  28. p.y = newY;
  29. p.angle = newAngle;
  30. return p
  31. }
  32. function getTangentPoint(point, prevPoint, centerPoint, tan, nextStepAngle) {
  33. var correctAngle = point.angle + nextStepAngle;
  34. var cosSin = vizUtils.getCosAndSin(correctAngle);
  35. var x = centerPoint.x + (point.radius + tan * nextStepAngle) * cosSin.cos;
  36. var y = centerPoint.y - (point.radius + tan * nextStepAngle) * cosSin.sin;
  37. return clonePoint(prevPoint, x, y, correctAngle)
  38. }
  39. function obtainCubicBezierTCoef(p, p0, p1, p2, p3) {
  40. var d = p0 - p;
  41. var c = 3 * p1 - 3 * p0;
  42. var b = 3 * p2 - 6 * p1 + 3 * p0;
  43. var a = p3 - 3 * p2 + 3 * p1 - p0;
  44. return mathUtils.solveCubicEquation(a, b, c, d)
  45. }
  46. var lineMethods = {
  47. autoHidePointMarkersEnabled: function() {
  48. return true
  49. },
  50. _applyGroupSettings: function(style, settings, group) {
  51. var that = this;
  52. settings = _extend(settings, style);
  53. that._applyElementsClipRect(settings);
  54. group.attr(settings)
  55. },
  56. _setGroupsSettings: function(animationEnabled) {
  57. var that = this;
  58. var style = that._styles.normal;
  59. that._applyGroupSettings(style.elements, {
  60. "class": "dxc-elements"
  61. }, that._elementsGroup);
  62. that._bordersGroup && that._applyGroupSettings(style.border, {
  63. "class": "dxc-borders"
  64. }, that._bordersGroup);
  65. chartScatterSeries._setGroupsSettings.call(that, animationEnabled);
  66. animationEnabled && that._markersGroup && that._markersGroup.attr({
  67. opacity: .001
  68. })
  69. },
  70. _createGroups: function() {
  71. var that = this;
  72. that._createGroup("_elementsGroup", that, that._group);
  73. that._areBordersVisible() && that._createGroup("_bordersGroup", that, that._group);
  74. chartScatterSeries._createGroups.call(that)
  75. },
  76. _areBordersVisible: function() {
  77. return false
  78. },
  79. _getDefaultSegment: function(segment) {
  80. return {
  81. line: _map(segment.line || [], function(pt) {
  82. return pt.getDefaultCoords()
  83. })
  84. }
  85. },
  86. _prepareSegment: function(points) {
  87. return {
  88. line: points
  89. }
  90. },
  91. _parseLineOptions: function(options, defaultColor) {
  92. return {
  93. stroke: options.color || defaultColor,
  94. "stroke-width": options.width,
  95. dashStyle: options.dashStyle || "solid"
  96. }
  97. },
  98. _parseStyle: function(options, defaultColor) {
  99. return {
  100. elements: this._parseLineOptions(options, defaultColor)
  101. }
  102. },
  103. _applyStyle: function(style) {
  104. var that = this;
  105. that._elementsGroup && that._elementsGroup.attr(style.elements);
  106. _each(that._graphics || [], function(_, graphic) {
  107. graphic.line && graphic.line.attr({
  108. "stroke-width": style.elements["stroke-width"]
  109. }).sharp()
  110. })
  111. },
  112. _drawElement: function(segment, group) {
  113. return {
  114. line: this._createMainElement(segment.line, {
  115. "stroke-width": this._styles.normal.elements["stroke-width"]
  116. }).append(group)
  117. }
  118. },
  119. _removeElement: function(element) {
  120. element.line.remove()
  121. },
  122. _updateElement: function(element, segment, animate, animationComplete) {
  123. var params = {
  124. points: segment.line
  125. };
  126. var lineElement = element.line;
  127. animate ? lineElement.animate(params, {}, animationComplete) : lineElement.attr(params)
  128. },
  129. _animateComplete: function() {
  130. var that = this;
  131. chartScatterSeries._animateComplete.call(that);
  132. that._markersGroup && that._markersGroup.animate({
  133. opacity: 1
  134. }, {
  135. duration: that._defaultDuration
  136. })
  137. },
  138. _animate: function() {
  139. var that = this;
  140. var lastIndex = that._graphics.length - 1;
  141. _each(that._graphics || [], function(i, elem) {
  142. var complete;
  143. if (i === lastIndex) {
  144. complete = function() {
  145. that._animateComplete()
  146. }
  147. }
  148. that._updateElement(elem, that._segments[i], true, complete)
  149. })
  150. },
  151. _drawPoint: function(options) {
  152. chartScatterSeries._drawPoint.call(this, {
  153. point: options.point,
  154. groups: options.groups
  155. })
  156. },
  157. _createMainElement: function(points, settings) {
  158. return this._renderer.path(points, "line").attr(settings).sharp()
  159. },
  160. _sortPoints: function(points, rotated) {
  161. return rotated ? points.sort(function(p1, p2) {
  162. return p2.y - p1.y
  163. }) : points.sort(function(p1, p2) {
  164. return p1.x - p2.x
  165. })
  166. },
  167. _drawSegment: function(points, animationEnabled, segmentCount, lastSegment) {
  168. var that = this;
  169. var rotated = that._options.rotated;
  170. var forceDefaultSegment = false;
  171. var segment = that._prepareSegment(points, rotated, lastSegment);
  172. that._segments.push(segment);
  173. if (!that._graphics[segmentCount]) {
  174. that._graphics[segmentCount] = that._drawElement(animationEnabled ? that._getDefaultSegment(segment) : segment, that._elementsGroup)
  175. } else {
  176. if (!animationEnabled) {
  177. that._updateElement(that._graphics[segmentCount], segment)
  178. } else {
  179. if (forceDefaultSegment) {
  180. that._updateElement(that._graphics[segmentCount], that._getDefaultSegment(segment))
  181. }
  182. }
  183. }
  184. },
  185. _getTrackerSettings: function() {
  186. var that = this;
  187. var defaultTrackerWidth = that._defaultTrackerWidth;
  188. var strokeWidthFromElements = that._styles.normal.elements["stroke-width"];
  189. return {
  190. "stroke-width": strokeWidthFromElements > defaultTrackerWidth ? strokeWidthFromElements : defaultTrackerWidth,
  191. fill: "none"
  192. }
  193. },
  194. _getMainPointsFromSegment: function(segment) {
  195. return segment.line
  196. },
  197. _drawTrackerElement: function(segment) {
  198. return this._createMainElement(this._getMainPointsFromSegment(segment), this._getTrackerSettings(segment))
  199. },
  200. _updateTrackerElement: function(segment, element) {
  201. var settings = this._getTrackerSettings(segment);
  202. settings.points = this._getMainPointsFromSegment(segment);
  203. element.attr(settings)
  204. },
  205. checkSeriesViewportCoord: function(axis, coord) {
  206. if (0 === this._points.length) {
  207. return false
  208. }
  209. var range = axis.isArgumentAxis ? this.getArgumentRange() : this.getViewport();
  210. var min = axis.getTranslator().translate(range.categories ? range.categories[0] : range.min);
  211. var max = axis.getTranslator().translate(range.categories ? range.categories[range.categories.length - 1] : range.max);
  212. var rotated = this.getOptions().rotated;
  213. var inverted = axis.getOptions().inverted;
  214. return axis.isArgumentAxis && (!rotated && !inverted || rotated && inverted) || !axis.isArgumentAxis && (rotated && !inverted || !rotated && inverted) ? coord >= min && coord <= max : coord >= max && coord <= min
  215. },
  216. getSeriesPairCoord: function(coord, isArgument) {
  217. var that = this;
  218. var oppositeCoord = null;
  219. var nearestPoints = this.getNearestPointsByCoord(coord, isArgument);
  220. var needValueCoord = isArgument && !that._options.rotated || !isArgument && that._options.rotated;
  221. for (var i = 0; i < nearestPoints.length; i++) {
  222. var p = nearestPoints[i];
  223. var k = (p[1].vy - p[0].vy) / (p[1].vx - p[0].vx);
  224. var b = p[0].vy - p[0].vx * k;
  225. var tmpCoord = void 0;
  226. if (p[1].vx - p[0].vx === 0) {
  227. tmpCoord = needValueCoord ? p[0].vy : p[0].vx
  228. } else {
  229. tmpCoord = needValueCoord ? k * coord + b : (coord - b) / k
  230. }
  231. if (this.checkAxisVisibleAreaCoord(!isArgument, tmpCoord)) {
  232. oppositeCoord = tmpCoord;
  233. break
  234. }
  235. }
  236. return oppositeCoord
  237. }
  238. };
  239. var lineSeries = exports.chart.line = _extend({}, chartScatterSeries, lineMethods, {
  240. getPointCenterByArg: function(arg) {
  241. var value = this.getArgumentAxis().getTranslator().translate(arg);
  242. return {
  243. x: value,
  244. y: value
  245. }
  246. }
  247. });
  248. exports.chart.stepline = _extend({}, lineSeries, {
  249. _calculateStepLinePoints: function(points) {
  250. var segment = [];
  251. var coordName = this._options.rotated ? "x" : "y";
  252. _each(points, function(i, pt) {
  253. var point;
  254. if (!i) {
  255. segment.push(pt);
  256. return
  257. }
  258. var step = segment[segment.length - 1][coordName];
  259. if (step !== pt[coordName]) {
  260. point = objectUtils.clone(pt);
  261. point[coordName] = step;
  262. segment.push(point)
  263. }
  264. segment.push(pt)
  265. });
  266. return segment
  267. },
  268. _prepareSegment: function(points) {
  269. return lineSeries._prepareSegment(this._calculateStepLinePoints(points))
  270. },
  271. getSeriesPairCoord: function(coord, isArgument) {
  272. var oppositeCoord;
  273. var rotated = this._options.rotated;
  274. var isOpposite = !isArgument && !rotated || isArgument && rotated;
  275. var coordName = !isOpposite ? "vx" : "vy";
  276. var oppositeCoordName = !isOpposite ? "vy" : "vx";
  277. var nearestPoints = this.getNearestPointsByCoord(coord, isArgument);
  278. for (var i = 0; i < nearestPoints.length; i++) {
  279. var p = nearestPoints[i];
  280. var tmpCoord = void 0;
  281. if (isArgument) {
  282. tmpCoord = coord !== p[1][coordName] ? p[0][oppositeCoordName] : p[1][oppositeCoordName]
  283. } else {
  284. tmpCoord = coord === p[0][coordName] ? p[0][oppositeCoordName] : p[1][oppositeCoordName]
  285. }
  286. if (this.checkAxisVisibleAreaCoord(!isArgument, tmpCoord)) {
  287. oppositeCoord = tmpCoord;
  288. break
  289. }
  290. }
  291. return oppositeCoord
  292. }
  293. });
  294. exports.chart.spline = _extend({}, lineSeries, {
  295. _calculateBezierPoints: function(src, rotated) {
  296. var bezierPoints = [];
  297. var pointsCopy = src;
  298. var checkExtremum = function(otherPointCoord, pointCoord, controlCoord) {
  299. return otherPointCoord > pointCoord && controlCoord > otherPointCoord || otherPointCoord < pointCoord && controlCoord < otherPointCoord ? otherPointCoord : controlCoord
  300. };
  301. if (1 !== pointsCopy.length) {
  302. pointsCopy.forEach(function(curPoint, i) {
  303. var leftControlX;
  304. var leftControlY;
  305. var rightControlX;
  306. var rightControlY;
  307. var prevPoint = pointsCopy[i - 1];
  308. var nextPoint = pointsCopy[i + 1];
  309. var x1;
  310. var x2;
  311. var y1;
  312. var y2;
  313. var lambda = .5;
  314. var a;
  315. var b;
  316. var c;
  317. var xc;
  318. var yc;
  319. var shift;
  320. if (!i || i === pointsCopy.length - 1) {
  321. bezierPoints.push(curPoint, curPoint);
  322. return
  323. }
  324. var xCur = curPoint.x;
  325. var yCur = curPoint.y;
  326. x1 = prevPoint.x;
  327. x2 = nextPoint.x;
  328. y1 = prevPoint.y;
  329. y2 = nextPoint.y;
  330. var curIsExtremum = !!(!rotated && (yCur <= prevPoint.y && yCur <= nextPoint.y || yCur >= prevPoint.y && yCur >= nextPoint.y) || rotated && (xCur <= prevPoint.x && xCur <= nextPoint.x || xCur >= prevPoint.x && xCur >= nextPoint.x));
  331. if (curIsExtremum) {
  332. if (!rotated) {
  333. rightControlY = leftControlY = yCur;
  334. rightControlX = (xCur + nextPoint.x) / 2;
  335. leftControlX = (xCur + prevPoint.x) / 2
  336. } else {
  337. rightControlX = leftControlX = xCur;
  338. rightControlY = (yCur + nextPoint.y) / 2;
  339. leftControlY = (yCur + prevPoint.y) / 2
  340. }
  341. } else {
  342. a = y2 - y1;
  343. b = x1 - x2;
  344. c = y1 * x2 - x1 * y2;
  345. if (!rotated) {
  346. if (!b) {
  347. bezierPoints.push(curPoint, curPoint, curPoint);
  348. return
  349. }
  350. xc = xCur;
  351. yc = -1 * (a * xc + c) / b;
  352. shift = yc - yCur;
  353. y1 -= shift;
  354. y2 -= shift
  355. } else {
  356. if (!a) {
  357. bezierPoints.push(curPoint, curPoint, curPoint);
  358. return
  359. }
  360. yc = yCur;
  361. xc = -1 * (b * yc + c) / a;
  362. shift = xc - xCur;
  363. x1 -= shift;
  364. x2 -= shift
  365. }
  366. rightControlX = (xCur + lambda * x2) / (1 + lambda);
  367. rightControlY = (yCur + lambda * y2) / (1 + lambda);
  368. leftControlX = (xCur + lambda * x1) / (1 + lambda);
  369. leftControlY = (yCur + lambda * y1) / (1 + lambda)
  370. }
  371. if (!rotated) {
  372. leftControlY = checkExtremum(prevPoint.y, yCur, leftControlY);
  373. rightControlY = checkExtremum(nextPoint.y, yCur, rightControlY)
  374. } else {
  375. leftControlX = checkExtremum(prevPoint.x, xCur, leftControlX);
  376. rightControlX = checkExtremum(nextPoint.x, xCur, rightControlX)
  377. }
  378. var leftPoint = clonePoint(curPoint, leftControlX, leftControlY);
  379. var rightPoint = clonePoint(curPoint, rightControlX, rightControlY);
  380. bezierPoints.push(leftPoint, curPoint, rightPoint)
  381. })
  382. } else {
  383. bezierPoints.push(pointsCopy[0])
  384. }
  385. return bezierPoints
  386. },
  387. _prepareSegment: function(points, rotated) {
  388. return lineSeries._prepareSegment(this._calculateBezierPoints(points, rotated))
  389. },
  390. _createMainElement: function(points, settings) {
  391. return this._renderer.path(points, "bezier").attr(settings).sharp()
  392. },
  393. getSeriesPairCoord: function(coord, isArgument) {
  394. var that = this;
  395. var oppositeCoord = null;
  396. var isOpposite = !isArgument && !this._options.rotated || isArgument && this._options.rotated;
  397. var coordName = !isOpposite ? "vx" : "vy";
  398. var bezierCoordName = !isOpposite ? "x" : "y";
  399. var oppositeCoordName = !isOpposite ? "vy" : "vx";
  400. var bezierOppositeCoordName = !isOpposite ? "y" : "x";
  401. var axis = !isArgument ? that.getArgumentAxis() : that.getValueAxis();
  402. var visibleArea = axis.getVisibleArea();
  403. var nearestPoints = this.getNearestPointsByCoord(coord, isArgument);
  404. var _loop = function(i) {
  405. var p = nearestPoints[i];
  406. if (1 === p.length) {
  407. visibleArea[0] <= p[0][oppositeCoordName] && visibleArea[1] >= p[0][oppositeCoordName] && (oppositeCoord = p[0][oppositeCoordName])
  408. } else {
  409. var ts = obtainCubicBezierTCoef(coord, p[0][coordName], p[1][bezierCoordName], p[2][bezierCoordName], p[3][coordName]);
  410. ts.forEach(function(t) {
  411. if (t >= 0 && t <= 1) {
  412. var tmpCoord = Math.pow(1 - t, 3) * p[0][oppositeCoordName] + 3 * Math.pow(1 - t, 2) * t * p[1][bezierOppositeCoordName] + 3 * (1 - t) * t * t * p[2][bezierOppositeCoordName] + t * t * t * p[3][oppositeCoordName];
  413. if (visibleArea[0] <= tmpCoord && visibleArea[1] >= tmpCoord) {
  414. oppositeCoord = tmpCoord
  415. }
  416. }
  417. })
  418. }
  419. if (null !== oppositeCoord) {
  420. return "break"
  421. }
  422. };
  423. for (var i = 0; i < nearestPoints.length; i++) {
  424. var _ret = _loop(i);
  425. if ("break" === _ret) {
  426. break
  427. }
  428. }
  429. return oppositeCoord
  430. },
  431. getNearestPointsByCoord: function(coord, isArgument) {
  432. var that = this;
  433. var rotated = that.getOptions().rotated;
  434. var isOpposite = !isArgument && !rotated || isArgument && rotated;
  435. var coordName = isOpposite ? "vy" : "vx";
  436. var points = that.getVisiblePoints();
  437. var allPoints = that.getPoints();
  438. var bezierPoints = that._segments.length > 0 ? that._segments.reduce(function(a, seg) {
  439. return a.concat(seg.line)
  440. }, []) : [];
  441. var nearestPoints = [];
  442. if (that.isVisible() && allPoints.length > 0) {
  443. if (allPoints.length > 1) {
  444. that.findNeighborPointsByCoord(coord, coordName, points.slice(0), allPoints, function(point, nextPoint) {
  445. var index = bezierPoints.indexOf(point);
  446. nearestPoints.push([point, bezierPoints[index + 1], bezierPoints[index + 2], nextPoint])
  447. })
  448. } else {
  449. if (allPoints[0][coordName] === coord) {
  450. nearestPoints.push([allPoints[0]])
  451. }
  452. }
  453. }
  454. return nearestPoints
  455. }
  456. });
  457. exports.polar.line = _extend({}, polarScatterSeries, lineMethods, {
  458. _sortPoints: function(points) {
  459. return points
  460. },
  461. _prepareSegment: function(points, rotated, lastSegment) {
  462. var preparedPoints = [];
  463. var centerPoint = this.getValueAxis().getCenter();
  464. var i;
  465. lastSegment && this._closeSegment(points);
  466. if (this.argumentAxisType !== DISCRETE && this.valueAxisType !== DISCRETE) {
  467. for (i = 1; i < points.length; i++) {
  468. preparedPoints = preparedPoints.concat(this._getTangentPoints(points[i], points[i - 1], centerPoint))
  469. }
  470. if (!preparedPoints.length) {
  471. preparedPoints = points
  472. }
  473. } else {
  474. return lineSeries._prepareSegment.call(this, points)
  475. }
  476. return {
  477. line: preparedPoints
  478. }
  479. },
  480. _getRemainingAngle: function(angle) {
  481. var normAngle = normalizeAngle(angle);
  482. return angle >= 0 ? 360 - normAngle : -normAngle
  483. },
  484. _closeSegment: function(points) {
  485. var point;
  486. var differenceAngle;
  487. if (this._segments.length) {
  488. point = this._segments[0].line[0]
  489. } else {
  490. point = clonePoint(points[0], points[0].x, points[0].y, points[0].angle)
  491. }
  492. if (points[points.length - 1].angle !== point.angle) {
  493. if (normalizeAngle(Math.round(points[points.length - 1].angle)) === normalizeAngle(Math.round(point.angle))) {
  494. point.angle = points[points.length - 1].angle
  495. } else {
  496. differenceAngle = points[points.length - 1].angle - point.angle;
  497. point.angle = points[points.length - 1].angle + this._getRemainingAngle(differenceAngle)
  498. }
  499. points.push(point)
  500. }
  501. },
  502. _getTangentPoints: function(point, prevPoint, centerPoint) {
  503. var tangentPoints = [];
  504. var betweenAngle = Math.round(prevPoint.angle - point.angle);
  505. var tan = (prevPoint.radius - point.radius) / betweenAngle;
  506. var i;
  507. if (0 === betweenAngle) {
  508. tangentPoints = [prevPoint, point]
  509. } else {
  510. if (betweenAngle > 0) {
  511. for (i = betweenAngle; i >= 0; i--) {
  512. tangentPoints.push(getTangentPoint(point, prevPoint, centerPoint, tan, i))
  513. }
  514. } else {
  515. for (i = 0; i >= betweenAngle; i--) {
  516. tangentPoints.push(getTangentPoint(point, prevPoint, centerPoint, tan, betweenAngle - i))
  517. }
  518. }
  519. }
  520. return tangentPoints
  521. }
  522. });