multi_axes_synchronizer.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. /**
  2. * DevExtreme (viz/chart_components/multi_axes_synchronizer.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 _console = require("../../core/utils/console");
  11. var _type = require("../../core/utils/type");
  12. var _iterator = require("../../core/utils/iterator");
  13. var _utils = require("../core/utils");
  14. var _math2 = require("../../core/utils/math");
  15. var _math = Math;
  16. var _floor = _math.floor;
  17. var _max = _math.max;
  18. var _abs = _math.abs;
  19. function getValueAxesPerPanes(valueAxes) {
  20. var result = {};
  21. valueAxes.forEach(function(axis) {
  22. var pane = axis.pane;
  23. if (!result[pane]) {
  24. result[pane] = []
  25. }
  26. result[pane].push(axis)
  27. });
  28. return result
  29. }
  30. var linearConverter = {
  31. transform: function(v, b) {
  32. return (0, _math2.adjust)((0, _utils.getLog)(v, b))
  33. },
  34. addInterval: function(v, i) {
  35. return (0, _math2.adjust)(v + i)
  36. },
  37. getInterval: function(base, tickInterval) {
  38. return tickInterval
  39. }
  40. };
  41. var logConverter = {
  42. transform: function(v, b) {
  43. return (0, _math2.adjust)((0, _utils.raiseTo)(v, b))
  44. },
  45. addInterval: function(v, i) {
  46. return (0, _math2.adjust)(v * i)
  47. },
  48. getInterval: function(base, tickInterval) {
  49. return _math.pow(base, tickInterval)
  50. }
  51. };
  52. function convertAxisInfo(axisInfo, converter) {
  53. if (!axisInfo.isLogarithmic) {
  54. return
  55. }
  56. var base = axisInfo.logarithmicBase;
  57. var tickValues = axisInfo.tickValues;
  58. var tick;
  59. var ticks = [];
  60. axisInfo.minValue = converter.transform(axisInfo.minValue, base);
  61. axisInfo.oldMinValue = converter.transform(axisInfo.oldMinValue, base);
  62. axisInfo.maxValue = converter.transform(axisInfo.maxValue, base);
  63. axisInfo.oldMaxValue = converter.transform(axisInfo.oldMaxValue, base);
  64. axisInfo.tickInterval = _math.round(axisInfo.tickInterval);
  65. if (axisInfo.tickInterval < 1) {
  66. axisInfo.tickInterval = 1
  67. }
  68. var interval = converter.getInterval(base, axisInfo.tickInterval);
  69. tick = converter.transform(tickValues[0], base);
  70. while (ticks.length < tickValues.length) {
  71. ticks.push(tick);
  72. tick = converter.addInterval(tick, interval)
  73. }
  74. ticks.tickInterval = axisInfo.tickInterval;
  75. axisInfo.tickValues = ticks
  76. }
  77. function populateAxesInfo(axes) {
  78. return axes.reduce(function(result, axis) {
  79. var ticksValues = axis.getTicksValues();
  80. var majorTicks = ticksValues.majorTicksValues;
  81. var options = axis.getOptions();
  82. var businessRange = axis.getTranslator().getBusinessRange();
  83. var visibleArea = axis.getVisibleArea();
  84. var tickInterval = axis._tickInterval;
  85. var synchronizedValue = options.synchronizedValue;
  86. if (majorTicks && majorTicks.length > 0 && (0, _type.isNumeric)(majorTicks[0]) && "discrete" !== options.type && !businessRange.isEmpty() && !(businessRange.breaks && businessRange.breaks.length) && "zoom" !== axis.getViewport().action) {
  87. axis.applyMargins();
  88. var startValue = axis.getTranslator().from(visibleArea[0]);
  89. var endValue = axis.getTranslator().from(visibleArea[1]);
  90. var minValue = startValue < endValue ? startValue : endValue;
  91. var maxValue = startValue < endValue ? endValue : startValue;
  92. if (minValue === maxValue && (0, _type.isDefined)(synchronizedValue)) {
  93. tickInterval = _abs(majorTicks[0] - synchronizedValue) || 1;
  94. minValue = majorTicks[0] - tickInterval;
  95. maxValue = majorTicks[0] + tickInterval
  96. }
  97. var axisInfo = {
  98. axis: axis,
  99. isLogarithmic: "logarithmic" === options.type,
  100. logarithmicBase: businessRange.base,
  101. tickValues: majorTicks,
  102. minorValues: ticksValues.minorTicksValues,
  103. minorTickInterval: axis._minorTickInterval,
  104. minValue: minValue,
  105. oldMinValue: minValue,
  106. maxValue: maxValue,
  107. oldMaxValue: maxValue,
  108. inverted: businessRange.invert,
  109. tickInterval: tickInterval,
  110. synchronizedValue: synchronizedValue
  111. };
  112. convertAxisInfo(axisInfo, linearConverter);
  113. result.push(axisInfo)
  114. }
  115. return result
  116. }, [])
  117. }
  118. function updateTickValues(axesInfo) {
  119. var maxTicksCount = axesInfo.reduce(function(max, axisInfo) {
  120. return _max(max, axisInfo.tickValues.length)
  121. }, 0);
  122. axesInfo.forEach(function(axisInfo) {
  123. var additionalStartTicksCount = 0;
  124. var synchronizedValue = axisInfo.synchronizedValue;
  125. var tickValues = axisInfo.tickValues;
  126. var tickInterval = axisInfo.tickInterval;
  127. if ((0, _type.isDefined)(synchronizedValue)) {
  128. axisInfo.baseTickValue = axisInfo.invertedBaseTickValue = synchronizedValue;
  129. axisInfo.tickValues = [axisInfo.baseTickValue]
  130. } else {
  131. if (tickValues.length > 1 && tickInterval) {
  132. var ticksMultiplier = _floor((maxTicksCount + 1) / tickValues.length);
  133. var ticksCount = ticksMultiplier > 1 ? _floor((maxTicksCount + 1) / ticksMultiplier) : maxTicksCount;
  134. additionalStartTicksCount = _floor((ticksCount - tickValues.length) / 2);
  135. while (additionalStartTicksCount > 0 && 0 !== tickValues[0]) {
  136. tickValues.unshift((0, _math2.adjust)(tickValues[0] - tickInterval));
  137. additionalStartTicksCount--
  138. }
  139. while (tickValues.length < ticksCount) {
  140. tickValues.push((0, _math2.adjust)(tickValues[tickValues.length - 1] + tickInterval))
  141. }
  142. axisInfo.tickInterval = tickInterval / ticksMultiplier
  143. }
  144. axisInfo.baseTickValue = tickValues[0];
  145. axisInfo.invertedBaseTickValue = tickValues[tickValues.length - 1]
  146. }
  147. })
  148. }
  149. function getAxisRange(axisInfo) {
  150. return axisInfo.maxValue - axisInfo.minValue || 1
  151. }
  152. function getMainAxisInfo(axesInfo) {
  153. for (var i = 0; i < axesInfo.length; i++) {
  154. if (!axesInfo[i].stubData) {
  155. return axesInfo[i]
  156. }
  157. }
  158. return null
  159. }
  160. function correctMinMaxValues(axesInfo) {
  161. var mainAxisInfo = getMainAxisInfo(axesInfo);
  162. var mainAxisInfoTickInterval = mainAxisInfo.tickInterval;
  163. axesInfo.forEach(function(axisInfo) {
  164. var mainAxisBaseValueOffset;
  165. if (axisInfo !== mainAxisInfo) {
  166. if (mainAxisInfoTickInterval && axisInfo.tickInterval) {
  167. if (axisInfo.stubData && (0, _type.isDefined)(axisInfo.synchronizedValue)) {
  168. axisInfo.oldMinValue = axisInfo.minValue = axisInfo.baseTickValue - (mainAxisInfo.baseTickValue - mainAxisInfo.minValue) / mainAxisInfoTickInterval * axisInfo.tickInterval;
  169. axisInfo.oldMaxValue = axisInfo.maxValue = axisInfo.baseTickValue - (mainAxisInfo.baseTickValue - mainAxisInfo.maxValue) / mainAxisInfoTickInterval * axisInfo.tickInterval
  170. }
  171. var scale = mainAxisInfoTickInterval / getAxisRange(mainAxisInfo) / axisInfo.tickInterval * getAxisRange(axisInfo);
  172. axisInfo.maxValue = axisInfo.minValue + getAxisRange(axisInfo) / scale
  173. }
  174. if (mainAxisInfo.inverted && !axisInfo.inverted || !mainAxisInfo.inverted && axisInfo.inverted) {
  175. mainAxisBaseValueOffset = mainAxisInfo.maxValue - mainAxisInfo.invertedBaseTickValue
  176. } else {
  177. mainAxisBaseValueOffset = mainAxisInfo.baseTickValue - mainAxisInfo.minValue
  178. }
  179. var valueFromAxisInfo = getAxisRange(axisInfo);
  180. var move = (mainAxisBaseValueOffset / getAxisRange(mainAxisInfo) - (axisInfo.baseTickValue - axisInfo.minValue) / valueFromAxisInfo) * valueFromAxisInfo;
  181. axisInfo.minValue -= move;
  182. axisInfo.maxValue -= move
  183. }
  184. })
  185. }
  186. function calculatePaddings(axesInfo) {
  187. var startPadding = 0;
  188. var endPadding = 0;
  189. axesInfo.forEach(function(axisInfo) {
  190. var inverted = axisInfo.inverted;
  191. var minPadding = axisInfo.minValue > axisInfo.oldMinValue ? (axisInfo.minValue - axisInfo.oldMinValue) / getAxisRange(axisInfo) : 0;
  192. var maxPadding = axisInfo.maxValue < axisInfo.oldMaxValue ? (axisInfo.oldMaxValue - axisInfo.maxValue) / getAxisRange(axisInfo) : 0;
  193. startPadding = _max(startPadding, inverted ? maxPadding : minPadding);
  194. endPadding = _max(endPadding, inverted ? minPadding : maxPadding)
  195. });
  196. return {
  197. start: startPadding,
  198. end: endPadding
  199. }
  200. }
  201. function correctMinMaxValuesByPaddings(axesInfo, paddings) {
  202. axesInfo.forEach(function(info) {
  203. var range = getAxisRange(info);
  204. var inverted = info.inverted;
  205. info.minValue = (0, _math2.adjust)(info.minValue - paddings[inverted ? "end" : "start"] * range);
  206. info.maxValue = (0, _math2.adjust)(info.maxValue + paddings[inverted ? "start" : "end"] * range)
  207. })
  208. }
  209. function updateTickValuesIfSynchronizedValueUsed(axesInfo) {
  210. var hasSynchronizedValue = false;
  211. axesInfo.forEach(function(info) {
  212. hasSynchronizedValue = hasSynchronizedValue || (0, _type.isDefined)(info.synchronizedValue)
  213. });
  214. axesInfo.forEach(function(info) {
  215. var tickInterval = info.tickInterval;
  216. var tickValues = info.tickValues;
  217. var maxValue = info.maxValue;
  218. var minValue = info.minValue;
  219. var tick;
  220. if (hasSynchronizedValue && tickInterval) {
  221. while ((tick = (0, _math2.adjust)(tickValues[0] - tickInterval)) >= minValue) {
  222. tickValues.unshift(tick)
  223. }
  224. tick = tickValues[tickValues.length - 1];
  225. while ((tick = (0, _math2.adjust)(tick + tickInterval)) <= maxValue) {
  226. tickValues.push(tick)
  227. }
  228. }
  229. while (tickValues[0] + tickInterval / 10 < minValue) {
  230. tickValues.shift()
  231. }
  232. while (tickValues[tickValues.length - 1] - tickInterval / 10 > maxValue) {
  233. tickValues.pop()
  234. }
  235. })
  236. }
  237. function applyMinMaxValues(axesInfo) {
  238. axesInfo.forEach(function(info) {
  239. var axis = info.axis;
  240. var range = axis.getTranslator().getBusinessRange();
  241. if (range.min === range.minVisible) {
  242. range.min = info.minValue
  243. }
  244. if (range.max === range.maxVisible) {
  245. range.max = info.maxValue
  246. }
  247. range.minVisible = info.minValue;
  248. range.maxVisible = info.maxValue;
  249. if (range.min > range.minVisible) {
  250. range.min = range.minVisible
  251. }
  252. if (range.max < range.maxVisible) {
  253. range.max = range.maxVisible
  254. }
  255. axis.getTranslator().updateBusinessRange(range);
  256. axis.setTicks({
  257. majorTicks: info.tickValues,
  258. minorTicks: info.minorValues
  259. })
  260. })
  261. }
  262. function correctAfterSynchronize(axesInfo) {
  263. var invalidAxisInfo = [];
  264. var correctValue;
  265. axesInfo.forEach(function(info) {
  266. if (info.oldMaxValue - info.oldMinValue === 0) {
  267. invalidAxisInfo.push(info)
  268. } else {
  269. if (!(0, _type.isDefined)(correctValue) && !(0, _type.isDefined)(info.synchronizedValue)) {
  270. correctValue = _abs((info.maxValue - info.minValue) / (info.tickValues[_floor(info.tickValues.length / 2)] - info.minValue || info.maxValue))
  271. }
  272. }
  273. });
  274. if (!(0, _type.isDefined)(correctValue)) {
  275. return
  276. }
  277. invalidAxisInfo.forEach(function(info) {
  278. var firstTick = info.tickValues[0];
  279. var correctedTick = firstTick * correctValue;
  280. if (firstTick > 0) {
  281. info.maxValue = correctedTick;
  282. info.minValue = 0
  283. } else {
  284. if (firstTick < 0) {
  285. info.minValue = correctedTick;
  286. info.maxValue = 0
  287. }
  288. }
  289. })
  290. }
  291. function updateMinorTicks(axesInfo) {
  292. axesInfo.forEach(function(axisInfo) {
  293. if (!axisInfo.minorTickInterval) {
  294. return
  295. }
  296. var ticks = [];
  297. var interval = axisInfo.minorTickInterval;
  298. var tickCount = axisInfo.tickInterval / interval - 1;
  299. for (var i = 1; i < axisInfo.tickValues.length; i++) {
  300. var tick = axisInfo.tickValues[i - 1];
  301. for (var j = 0; j < tickCount; j++) {
  302. tick += interval;
  303. ticks.push(tick)
  304. }
  305. }
  306. axisInfo.minorValues = ticks
  307. })
  308. }
  309. var multiAxesSynchronizer = {
  310. synchronize: function(valueAxes) {
  311. (0, _iterator.each)(getValueAxesPerPanes(valueAxes), function(_, axes) {
  312. if (axes.length > 1) {
  313. var axesInfo = populateAxesInfo(axes);
  314. if (axesInfo.length < 2 || !getMainAxisInfo(axesInfo)) {
  315. return
  316. }
  317. updateTickValues(axesInfo);
  318. correctMinMaxValues(axesInfo);
  319. var paddings = calculatePaddings(axesInfo);
  320. correctMinMaxValuesByPaddings(axesInfo, paddings);
  321. correctAfterSynchronize(axesInfo);
  322. updateTickValuesIfSynchronizedValueUsed(axesInfo);
  323. updateMinorTicks(axesInfo);
  324. axesInfo.forEach(function(info) {
  325. convertAxisInfo(info, logConverter)
  326. });
  327. applyMinMaxValues(axesInfo)
  328. }
  329. })
  330. }
  331. };
  332. module.exports = multiAxesSynchronizer;