sliders_controller.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457
  1. /**
  2. * DevExtreme (viz/range_selector/sliders_controller.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 noop = require("../../core/utils/common").noop;
  11. var commonModule = require("./common");
  12. var animationSettings = commonModule.utils.animationSettings;
  13. var emptySliderMarkerText = commonModule.consts.emptySliderMarkerText;
  14. var Slider = require("./slider");
  15. var _normalizeEnum = require("../core/utils").normalizeEnum;
  16. var typeUtils = require("../../core/utils/type");
  17. var isNumeric = typeUtils.isNumeric;
  18. var vizUtils = require("../core/utils");
  19. var adjust = require("../../core/utils/math").adjust;
  20. function buildRectPoints(left, top, right, bottom) {
  21. return [left, top, right, top, right, bottom, left, bottom]
  22. }
  23. function valueOf(value) {
  24. return value && value.valueOf()
  25. }
  26. function isLess(a, b) {
  27. return a < b
  28. }
  29. function isGreater(a, b) {
  30. return a > b
  31. }
  32. function selectClosestValue(target, values) {
  33. var start = 0;
  34. var end = values ? values.length - 1 : 0;
  35. var val = target;
  36. while (end - start > 1) {
  37. var middle = start + end >> 1;
  38. val = values[middle];
  39. if (val === target) {
  40. return target
  41. } else {
  42. if (target < val) {
  43. end = middle
  44. } else {
  45. start = middle
  46. }
  47. }
  48. }
  49. if (values) {
  50. val = values[target - values[start] <= values[end] - target ? start : end]
  51. }
  52. return val
  53. }
  54. function dummyProcessSelectionChanged() {
  55. this._lastSelectedRange = this.getSelectedRange();
  56. delete this._processSelectionChanged
  57. }
  58. function suppressSetSelectedRange(controller) {
  59. controller.setSelectedRange = noop;
  60. if (controller._processSelectionChanged === dummyProcessSelectionChanged) {
  61. controller._processSelectionChanged()
  62. }
  63. }
  64. function restoreSetSelectedRange(controller) {
  65. delete controller.setSelectedRange
  66. }
  67. function SlidersController(params) {
  68. var that = this;
  69. var sliderParams = {
  70. renderer: params.renderer,
  71. root: params.root,
  72. trackersGroup: params.trackersGroup,
  73. translator: params.translator
  74. };
  75. that._params = params;
  76. that._areaTracker = params.renderer.path(null, "area").attr({
  77. "class": "area-tracker",
  78. fill: "#000000",
  79. opacity: 1e-4
  80. }).append(params.trackersGroup);
  81. that._selectedAreaTracker = params.renderer.path(null, "area").attr({
  82. "class": "selected-area-tracker",
  83. fill: "#000000",
  84. opacity: 1e-4
  85. }).append(params.trackersGroup);
  86. that._shutter = params.renderer.path(null, "area").append(params.root);
  87. that._sliders = [new Slider(sliderParams, 0), new Slider(sliderParams, 1)];
  88. that._processSelectionChanged = dummyProcessSelectionChanged
  89. }
  90. SlidersController.prototype = {
  91. constructor: SlidersController,
  92. dispose: function() {
  93. this._sliders[0].dispose();
  94. this._sliders[1].dispose()
  95. },
  96. getTrackerTargets: function() {
  97. return {
  98. area: this._areaTracker,
  99. selectedArea: this._selectedAreaTracker,
  100. sliders: this._sliders
  101. }
  102. },
  103. _processSelectionChanged: function(e) {
  104. var that = this;
  105. var selectedRange = that.getSelectedRange();
  106. if (valueOf(selectedRange.startValue) !== valueOf(that._lastSelectedRange.startValue) || valueOf(selectedRange.endValue) !== valueOf(that._lastSelectedRange.endValue)) {
  107. that._params.updateSelectedRange(selectedRange, that._lastSelectedRange, e);
  108. that._lastSelectedRange = selectedRange
  109. }
  110. },
  111. update: function(verticalRange, behavior, isCompactMode, sliderHandleOptions, sliderMarkerOptions, shutterOptions, rangeBounds, fullTicks, selectedRangeColor) {
  112. var that = this;
  113. var screenRange = that._params.translator.getScreenRange();
  114. that._verticalRange = verticalRange;
  115. that._minRange = rangeBounds.minRange;
  116. that._maxRange = rangeBounds.maxRange;
  117. that._animationEnabled = behavior.animationEnabled && that._params.renderer.animationEnabled();
  118. that._allowSlidersSwap = behavior.allowSlidersSwap;
  119. that._sliders[0].update(verticalRange, sliderHandleOptions, sliderMarkerOptions);
  120. that._sliders[1].update(verticalRange, sliderHandleOptions, sliderMarkerOptions);
  121. that._sliders[0]._position = that._sliders[1]._position = screenRange[0];
  122. that._values = !that._params.translator.isValueProlonged && behavior.snapToTicks ? fullTicks : null;
  123. that._areaTracker.attr({
  124. points: buildRectPoints(screenRange[0], verticalRange[0], screenRange[1], verticalRange[1])
  125. });
  126. that._isCompactMode = isCompactMode;
  127. that._shutterOffset = sliderHandleOptions.width / 2;
  128. that._updateSelectedView(shutterOptions, selectedRangeColor);
  129. that._isOnMoving = "onmoving" === _normalizeEnum(behavior.callValueChanged);
  130. that._updateSelectedRange();
  131. that._applyTotalPosition(false)
  132. },
  133. _updateSelectedView: function(shutterOptions, selectedRangeColor) {
  134. var settings = {
  135. fill: null,
  136. "fill-opacity": null,
  137. stroke: null,
  138. "stroke-width": null
  139. };
  140. if (this._isCompactMode) {
  141. settings.stroke = selectedRangeColor;
  142. settings["stroke-width"] = 3;
  143. settings.sharp = "v"
  144. } else {
  145. settings.fill = shutterOptions.color;
  146. settings["fill-opacity"] = shutterOptions.opacity
  147. }
  148. this._shutter.attr(settings)
  149. },
  150. _updateSelectedRange: function() {
  151. var that = this;
  152. var sliders = that._sliders;
  153. sliders[0].cancelAnimation();
  154. sliders[1].cancelAnimation();
  155. that._shutter.stopAnimation();
  156. if (that._params.translator.getBusinessRange().isEmpty()) {
  157. sliders[0]._setText(emptySliderMarkerText);
  158. sliders[1]._setText(emptySliderMarkerText);
  159. sliders[0]._value = sliders[1]._value = void 0;
  160. sliders[0]._position = that._params.translator.getScreenRange()[0];
  161. sliders[1]._position = that._params.translator.getScreenRange()[1];
  162. that._applyTotalPosition(false);
  163. suppressSetSelectedRange(that)
  164. } else {
  165. restoreSetSelectedRange(that)
  166. }
  167. },
  168. _applyTotalPosition: function(isAnimated) {
  169. var sliders = this._sliders;
  170. isAnimated = this._animationEnabled && isAnimated;
  171. sliders[0].applyPosition(isAnimated);
  172. sliders[1].applyPosition(isAnimated);
  173. var areOverlapped = sliders[0].getCloudBorder() > sliders[1].getCloudBorder();
  174. sliders[0].setOverlapped(areOverlapped);
  175. sliders[1].setOverlapped(areOverlapped);
  176. this._applyAreaTrackersPosition();
  177. this._applySelectedRangePosition(isAnimated)
  178. },
  179. _applyAreaTrackersPosition: function() {
  180. var that = this;
  181. var position1 = that._sliders[0].getPosition();
  182. var position2 = that._sliders[1].getPosition();
  183. that._selectedAreaTracker.attr({
  184. points: buildRectPoints(position1, that._verticalRange[0], position2, that._verticalRange[1])
  185. }).css({
  186. cursor: Math.abs(that._params.translator.getScreenRange()[1] - that._params.translator.getScreenRange()[0] - position2 + position1) < .001 ? "default" : "pointer"
  187. })
  188. },
  189. _applySelectedRangePosition: function(isAnimated) {
  190. var that = this;
  191. var verticalRange = that._verticalRange;
  192. var pos1 = that._sliders[0].getPosition();
  193. var pos2 = that._sliders[1].getPosition();
  194. var points;
  195. if (that._isCompactMode) {
  196. points = [pos1 + Math.ceil(that._shutterOffset), (verticalRange[0] + verticalRange[1]) / 2, pos2 - Math.floor(that._shutterOffset), (verticalRange[0] + verticalRange[1]) / 2]
  197. } else {
  198. var screenRange = that._params.axis.getVisibleArea();
  199. points = [buildRectPoints(screenRange[0], verticalRange[0], Math.max(pos1 - Math.floor(that._shutterOffset), screenRange[0]), verticalRange[1]), buildRectPoints(screenRange[1], verticalRange[0], Math.min(pos2 + Math.ceil(that._shutterOffset), screenRange[1]), verticalRange[1])]
  200. }
  201. if (isAnimated) {
  202. that._shutter.animate({
  203. points: points
  204. }, animationSettings)
  205. } else {
  206. that._shutter.attr({
  207. points: points
  208. })
  209. }
  210. },
  211. getSelectedRange: function() {
  212. return {
  213. startValue: this._sliders[0].getValue(),
  214. endValue: this._sliders[1].getValue()
  215. }
  216. },
  217. setSelectedRange: function(visualRange, e) {
  218. visualRange = visualRange || {};
  219. var that = this;
  220. var translator = that._params.translator;
  221. var businessRange = translator.getBusinessRange();
  222. var compare = "discrete" === businessRange.axisType ? function(a, b) {
  223. return a < b
  224. } : function(a, b) {
  225. return a <= b
  226. };
  227. var _vizUtils$adjustVisua = vizUtils.adjustVisualRange({
  228. dataType: businessRange.dataType,
  229. axisType: businessRange.axisType,
  230. base: businessRange.base
  231. }, {
  232. startValue: translator.isValid(visualRange.startValue) ? translator.getCorrectValue(visualRange.startValue, 1) : void 0,
  233. endValue: translator.isValid(visualRange.endValue) ? translator.getCorrectValue(visualRange.endValue, -1) : void 0,
  234. length: visualRange.length
  235. }, {
  236. min: businessRange.minVisible,
  237. max: businessRange.maxVisible,
  238. categories: businessRange.categories
  239. }),
  240. startValue = _vizUtils$adjustVisua.startValue,
  241. endValue = _vizUtils$adjustVisua.endValue;
  242. startValue = isNumeric(startValue) ? adjust(startValue) : startValue;
  243. endValue = isNumeric(endValue) ? adjust(endValue) : endValue;
  244. var values = compare(translator.to(startValue, -1), translator.to(endValue, 1)) ? [startValue, endValue] : [endValue, startValue];
  245. that._sliders[0].setDisplayValue(values[0]);
  246. that._sliders[1].setDisplayValue(values[1]);
  247. that._sliders[0]._position = translator.to(values[0], -1);
  248. that._sliders[1]._position = translator.to(values[1], 1);
  249. that._applyTotalPosition(true);
  250. that._processSelectionChanged(e)
  251. },
  252. beginSelectedAreaMoving: function(initialPosition) {
  253. var that = this;
  254. var sliders = that._sliders;
  255. var offset = (sliders[0].getPosition() + sliders[1].getPosition()) / 2 - initialPosition;
  256. var currentPosition = initialPosition;
  257. move.complete = function(e) {
  258. that._dockSelectedArea(e)
  259. };
  260. return move;
  261. function move(position, e) {
  262. if (position !== currentPosition && position > currentPosition === position > (sliders[0].getPosition() + sliders[1].getPosition()) / 2 - offset) {
  263. that._moveSelectedArea(position + offset, false, e)
  264. }
  265. currentPosition = position
  266. }
  267. },
  268. _dockSelectedArea: function(e) {
  269. var translator = this._params.translator;
  270. var sliders = this._sliders;
  271. sliders[0]._position = translator.to(sliders[0].getValue(), -1);
  272. sliders[1]._position = translator.to(sliders[1].getValue(), 1);
  273. this._applyTotalPosition(true);
  274. this._processSelectionChanged(e)
  275. },
  276. moveSelectedArea: function(screenPosition, e) {
  277. this._moveSelectedArea(screenPosition, true, e);
  278. this._dockSelectedArea(e)
  279. },
  280. _moveSelectedArea: function(screenPosition, isAnimated, e) {
  281. var that = this;
  282. var translator = that._params.translator;
  283. var sliders = that._sliders;
  284. var interval = sliders[1].getPosition() - sliders[0].getPosition();
  285. var startPosition = screenPosition - interval / 2;
  286. var endPosition = screenPosition + interval / 2;
  287. if (startPosition < translator.getScreenRange()[0]) {
  288. startPosition = translator.getScreenRange()[0];
  289. endPosition = startPosition + interval
  290. }
  291. if (endPosition > translator.getScreenRange()[1]) {
  292. endPosition = translator.getScreenRange()[1];
  293. startPosition = endPosition - interval
  294. }
  295. var startValue = selectClosestValue(translator.from(startPosition, -1), that._values);
  296. sliders[0].setDisplayValue(startValue);
  297. sliders[1].setDisplayValue(selectClosestValue(translator.from(translator.to(startValue, -1) + interval, 1), that._values));
  298. sliders[0]._position = startPosition;
  299. sliders[1]._position = endPosition;
  300. that._applyTotalPosition(isAnimated);
  301. if (that._isOnMoving) {
  302. that._processSelectionChanged(e)
  303. }
  304. },
  305. placeSliderAndBeginMoving: function(firstPosition, secondPosition, e) {
  306. var that = this;
  307. var translator = that._params.translator;
  308. var sliders = that._sliders;
  309. var index = firstPosition < secondPosition ? 0 : 1;
  310. var dir = index > 0 ? 1 : -1;
  311. var compare = index > 0 ? isGreater : isLess;
  312. var antiCompare = index > 0 ? isLess : isGreater;
  313. var thresholdPosition;
  314. var positions = [];
  315. var values = [];
  316. values[index] = translator.from(firstPosition, dir);
  317. values[1 - index] = translator.from(secondPosition, -dir);
  318. positions[1 - index] = secondPosition;
  319. if (translator.isValueProlonged) {
  320. if (compare(firstPosition, translator.to(values[index], dir))) {
  321. values[index] = translator.from(firstPosition, -dir)
  322. }
  323. if (compare(secondPosition, translator.to(values[index], -dir))) {
  324. values[1 - index] = values[index]
  325. }
  326. }
  327. if (that._minRange) {
  328. thresholdPosition = translator.to(translator.add(selectClosestValue(values[index], that._values), that._minRange, -dir), -dir);
  329. if (compare(secondPosition, thresholdPosition)) {
  330. values[1 - index] = translator.add(values[index], that._minRange, -dir)
  331. }
  332. thresholdPosition = translator.to(translator.add(translator.getRange()[1 - index], that._minRange, dir), -dir);
  333. if (antiCompare(firstPosition, thresholdPosition)) {
  334. values[1 - index] = translator.getRange()[1 - index];
  335. values[index] = translator.add(values[1 - index], that._minRange, dir);
  336. positions[1 - index] = firstPosition
  337. }
  338. }
  339. values[0] = selectClosestValue(values[0], that._values);
  340. values[1] = selectClosestValue(values[1], that._values);
  341. positions[index] = translator.to(values[index], dir);
  342. sliders[0].setDisplayValue(values[0]);
  343. sliders[1].setDisplayValue(values[1]);
  344. sliders[0]._position = positions[0];
  345. sliders[1]._position = positions[1];
  346. that._applyTotalPosition(true);
  347. if (that._isOnMoving) {
  348. that._processSelectionChanged(e)
  349. }
  350. var handler = that.beginSliderMoving(1 - index, secondPosition);
  351. sliders[1 - index]._sliderGroup.stopAnimation();
  352. that._shutter.stopAnimation();
  353. handler(secondPosition);
  354. return handler
  355. },
  356. beginSliderMoving: function(initialIndex, initialPosition) {
  357. var that = this;
  358. var translator = that._params.translator;
  359. var sliders = that._sliders;
  360. var minPosition = translator.getScreenRange()[0];
  361. var maxPosition = translator.getScreenRange()[1];
  362. var index = initialIndex;
  363. var staticPosition = sliders[1 - index].getPosition();
  364. var currentPosition = initialPosition;
  365. var dir = index > 0 ? 1 : -1;
  366. var compareMin = index > 0 ? isLess : isGreater;
  367. var compareMax = index > 0 ? isGreater : isLess;
  368. var moveOffset = sliders[index].getPosition() - initialPosition;
  369. var swapOffset = compareMin(sliders[index].getPosition(), initialPosition) ? -moveOffset : moveOffset;
  370. move.complete = function(e) {
  371. sliders[index]._setValid(true);
  372. that._dockSelectedArea(e)
  373. };
  374. return move;
  375. function move(position, e) {
  376. var isValid;
  377. if (position !== currentPosition) {
  378. if (compareMin(position + swapOffset, staticPosition)) {
  379. isValid = that._allowSlidersSwap;
  380. if (isValid && !translator.isValueProlonged && that._minRange) {
  381. isValid = translator.isValid(translator.add(sliders[1 - index].getValue(), that._minRange, -dir))
  382. }
  383. if (isValid) {
  384. that._changeMovingSlider(index);
  385. index = 1 - index;
  386. dir = -dir;
  387. var temp = compareMin;
  388. compareMin = compareMax;
  389. compareMax = temp;
  390. moveOffset = -dir * Math.abs(moveOffset);
  391. swapOffset = -moveOffset
  392. }
  393. }
  394. if (compareMax(position + moveOffset, staticPosition)) {
  395. isValid = true;
  396. var slider = sliders[index];
  397. var value = sliders[1 - index].getValue();
  398. var pos = Math.max(Math.min(position + moveOffset, maxPosition), minPosition);
  399. if (isValid && translator.isValueProlonged) {
  400. isValid = !compareMin(pos, translator.to(value, dir))
  401. }
  402. if (isValid && that._minRange) {
  403. isValid = !compareMin(pos, translator.to(translator.add(value, that._minRange, dir), dir))
  404. }
  405. if (isValid && that._maxRange) {
  406. isValid = !compareMax(pos, translator.to(translator.add(value, that._maxRange, dir), dir))
  407. }
  408. slider._setValid(isValid);
  409. slider.setDisplayValue(isValid ? selectClosestValue(translator.from(pos, dir), that._values) : slider.getValue());
  410. slider._position = pos;
  411. that._applyTotalPosition(false);
  412. slider.toForeground();
  413. if (that._isOnMoving) {
  414. that._processSelectionChanged(e)
  415. }
  416. }
  417. }
  418. currentPosition = position
  419. }
  420. },
  421. _changeMovingSlider: function(index) {
  422. var that = this;
  423. var translator = that._params.translator;
  424. var sliders = that._sliders;
  425. var position = sliders[1 - index].getPosition();
  426. var dir = index > 0 ? 1 : -1;
  427. var newValue;
  428. sliders[index].setDisplayValue(selectClosestValue(translator.from(position, dir), that._values));
  429. newValue = translator.from(position, -dir);
  430. if (translator.isValueProlonged) {
  431. newValue = translator.from(position, dir)
  432. } else {
  433. if (that._minRange) {
  434. newValue = translator.add(newValue, that._minRange, -dir)
  435. }
  436. }
  437. sliders[1 - index].setDisplayValue(selectClosestValue(newValue, that._values));
  438. sliders[index]._setValid(true);
  439. sliders[index]._marker._update();
  440. sliders[0]._position = sliders[1]._position = position
  441. },
  442. foregroundSlider: function(index) {
  443. this._sliders[index].toForeground()
  444. }
  445. };
  446. exports.SlidersController = SlidersController;