devextreme
Version:
HTML5 JavaScript Component Suite for Responsive Web Development
461 lines (449 loc) • 20.7 kB
JavaScript
/**
* DevExtreme (cjs/viz/range_selector/sliders_controller.js)
* Version: 24.2.6
* Build date: Mon Mar 17 2025
*
* Copyright (c) 2012 - 2025 Developer Express Inc. ALL RIGHTS RESERVED
* Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
*/
"use strict";
exports.SlidersController = SlidersController;
var _common = require("../../core/utils/common");
var _common2 = require("./common");
var _slider = _interopRequireDefault(require("./slider"));
var _utils = require("../core/utils");
var _type = require("../../core/utils/type");
var _math = require("../../core/utils/math");
function _interopRequireDefault(e) {
return e && e.__esModule ? e : {
default: e
}
}
const animationSettings = _common2.utils.animationSettings;
const emptySliderMarkerText = _common2.consts.emptySliderMarkerText;
function buildRectPoints(left, top, right, bottom) {
return [left, top, right, top, right, bottom, left, bottom]
}
function isLess(a, b) {
return a < b
}
function isGreater(a, b) {
return a > b
}
function selectClosestValue(target, values) {
let start = 0;
let end = values ? values.length - 1 : 0;
let middle;
let val = target;
while (end - start > 1) {
middle = start + end >> 1;
val = values[middle];
if (val === target) {
return target
} else if (target < val) {
end = middle
} else {
start = middle
}
}
if (values) {
val = values[target - values[start] <= values[end] - target ? start : end]
}
return val
}
function dummyProcessSelectionChanged() {
this._lastSelectedRange = this.getSelectedRange();
delete this._processSelectionChanged
}
function suppressSetSelectedRange(controller) {
controller.setSelectedRange = _common.noop;
if (controller._processSelectionChanged === dummyProcessSelectionChanged) {
controller._processSelectionChanged()
}
}
function restoreSetSelectedRange(controller) {
delete controller.setSelectedRange
}
function SlidersController(params) {
const sliderParams = {
renderer: params.renderer,
root: params.root,
trackersGroup: params.trackersGroup,
translator: params.translator
};
this._params = params;
this._areaTracker = params.renderer.path(null, "area").attr({
class: "area-tracker",
fill: "#000000",
opacity: 1e-4
}).append(params.trackersGroup);
this._selectedAreaTracker = params.renderer.path(null, "area").attr({
class: "selected-area-tracker",
fill: "#000000",
opacity: 1e-4
}).append(params.trackersGroup);
this._shutter = params.renderer.path(null, "area").append(params.root);
this._sliders = [new _slider.default(sliderParams, 0), new _slider.default(sliderParams, 1)];
this._processSelectionChanged = dummyProcessSelectionChanged
}
SlidersController.prototype = {
constructor: SlidersController,
dispose: function() {
this._sliders[0].dispose();
this._sliders[1].dispose()
},
getTrackerTargets: function() {
return {
area: this._areaTracker,
selectedArea: this._selectedAreaTracker,
sliders: this._sliders
}
},
_processSelectionChanged: function(e) {
const that = this;
const selectedRange = that.getSelectedRange();
if (!(0, _utils.rangesAreEqual)(selectedRange, that._lastSelectedRange)) {
that._params.updateSelectedRange(selectedRange, that._lastSelectedRange, e);
that._lastSelectedRange = selectedRange
}
},
update: function(verticalRange, behavior, isCompactMode, sliderHandleOptions, sliderMarkerOptions, shutterOptions, rangeBounds, fullTicks, selectedRangeColor) {
const screenRange = this._params.translator.getScreenRange();
this._verticalRange = verticalRange;
this._minRange = rangeBounds.minRange;
this._maxRange = rangeBounds.maxRange;
this._animationEnabled = behavior.animationEnabled && this._params.renderer.animationEnabled();
this._allowSlidersSwap = behavior.allowSlidersSwap;
this._sliders[0].update(verticalRange, sliderHandleOptions, sliderMarkerOptions);
this._sliders[1].update(verticalRange, sliderHandleOptions, sliderMarkerOptions);
this._sliders[0]._position = this._sliders[1]._position = screenRange[0];
this._values = !this._params.translator.isValueProlonged && behavior.snapToTicks ? fullTicks : null;
this._areaTracker.attr({
points: buildRectPoints(screenRange[0], verticalRange[0], screenRange[1], verticalRange[1])
});
this._isCompactMode = isCompactMode;
this._shutterOffset = sliderHandleOptions.width / 2;
this._updateSelectedView(shutterOptions, selectedRangeColor);
this._isOnMoving = "onhandlemove" === (0, _utils.normalizeEnum)(behavior.valueChangeMode) || "onmoving" === (0, _utils.normalizeEnum)(behavior.callValueChanged);
this._updateSelectedRange();
this._applyTotalPosition(false)
},
_updateSelectedView: function(shutterOptions, selectedRangeColor) {
const settings = {
fill: null,
"fill-opacity": null,
stroke: null,
"stroke-width": null
};
if (this._isCompactMode) {
settings.stroke = selectedRangeColor;
settings["stroke-width"] = 3;
settings.sharp = "v"
} else {
settings.fill = shutterOptions.color;
settings["fill-opacity"] = shutterOptions.opacity
}
this._shutter.attr(settings)
},
_updateSelectedRange: function() {
const that = this;
const sliders = that._sliders;
sliders[0].cancelAnimation();
sliders[1].cancelAnimation();
that._shutter.stopAnimation();
if (that._params.translator.getBusinessRange().isEmpty()) {
sliders[0]._setText(emptySliderMarkerText);
sliders[1]._setText(emptySliderMarkerText);
sliders[0]._value = sliders[1]._value = void 0;
sliders[0]._position = that._params.translator.getScreenRange()[0];
sliders[1]._position = that._params.translator.getScreenRange()[1];
that._applyTotalPosition(false);
suppressSetSelectedRange(that)
} else {
restoreSetSelectedRange(that)
}
},
_applyTotalPosition: function(isAnimated) {
const sliders = this._sliders;
isAnimated = this._animationEnabled && isAnimated;
sliders[0].applyPosition(isAnimated);
sliders[1].applyPosition(isAnimated);
const areOverlapped = sliders[0].getCloudBorder() > sliders[1].getCloudBorder();
sliders[0].setOverlapped(areOverlapped);
sliders[1].setOverlapped(areOverlapped);
this._applyAreaTrackersPosition();
this._applySelectedRangePosition(isAnimated)
},
_applyAreaTrackersPosition: function() {
const position1 = this._sliders[0].getPosition();
const position2 = this._sliders[1].getPosition();
this._selectedAreaTracker.attr({
points: buildRectPoints(position1, this._verticalRange[0], position2, this._verticalRange[1])
}).css({
cursor: Math.abs(this._params.translator.getScreenRange()[1] - this._params.translator.getScreenRange()[0] - position2 + position1) < .001 ? "default" : "pointer"
})
},
_applySelectedRangePosition: function(isAnimated) {
const that = this;
const verticalRange = that._verticalRange;
const pos1 = that._sliders[0].getPosition();
const pos2 = that._sliders[1].getPosition();
let screenRange;
let points;
if (that._isCompactMode) {
points = [pos1 + Math.ceil(that._shutterOffset), (verticalRange[0] + verticalRange[1]) / 2, pos2 - Math.floor(that._shutterOffset), (verticalRange[0] + verticalRange[1]) / 2]
} else {
screenRange = that._params.axis.getVisibleArea();
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])]
}
if (isAnimated) {
that._shutter.animate({
points: points
}, animationSettings)
} else {
that._shutter.attr({
points: points
})
}
},
getSelectedRange: function() {
return {
startValue: this._sliders[0].getValue(),
endValue: this._sliders[1].getValue()
}
},
setSelectedRange: function(visualRange, e) {
visualRange = visualRange || {};
const translator = this._params.translator;
const businessRange = translator.getBusinessRange();
const compare = "discrete" === businessRange.axisType ? function(a, b) {
return a < b
} : function(a, b) {
return a <= b
};
let {
startValue: startValue,
endValue: endValue
} = (0, _utils.adjustVisualRange)({
dataType: businessRange.dataType,
axisType: businessRange.axisType,
base: businessRange.base
}, {
startValue: translator.isValid(visualRange.startValue) ? translator.getCorrectValue(visualRange.startValue, 1) : void 0,
endValue: translator.isValid(visualRange.endValue) ? translator.getCorrectValue(visualRange.endValue, -1) : void 0,
length: visualRange.length
}, {
min: businessRange.minVisible,
max: businessRange.maxVisible,
categories: businessRange.categories
});
startValue = (0, _type.isNumeric)(startValue) ? (0, _math.adjust)(startValue) : startValue;
endValue = (0, _type.isNumeric)(endValue) ? (0, _math.adjust)(endValue) : endValue;
const values = compare(translator.to(startValue, -1), translator.to(endValue, 1)) ? [startValue, endValue] : [endValue, startValue];
this._sliders[0].setDisplayValue(values[0]);
this._sliders[1].setDisplayValue(values[1]);
this._sliders[0]._position = translator.to(values[0], -1);
this._sliders[1]._position = translator.to(values[1], 1);
this._applyTotalPosition(true);
this._processSelectionChanged(e)
},
beginSelectedAreaMoving: function(initialPosition) {
const that = this;
const sliders = that._sliders;
const offset = (sliders[0].getPosition() + sliders[1].getPosition()) / 2 - initialPosition;
let currentPosition = initialPosition;
move.complete = function(e) {
that._dockSelectedArea(e)
};
return move;
function move(position, e) {
if (position !== currentPosition && position > currentPosition === position > (sliders[0].getPosition() + sliders[1].getPosition()) / 2 - offset) {
that._moveSelectedArea(position + offset, false, e)
}
currentPosition = position
}
},
_dockSelectedArea: function(e) {
const translator = this._params.translator;
const sliders = this._sliders;
sliders[0]._position = translator.to(sliders[0].getValue(), -1);
sliders[1]._position = translator.to(sliders[1].getValue(), 1);
this._applyTotalPosition(true);
this._processSelectionChanged(e)
},
moveSelectedArea: function(screenPosition, e) {
this._moveSelectedArea(screenPosition, true, e);
this._dockSelectedArea(e)
},
_moveSelectedArea: function(screenPosition, isAnimated, e) {
const that = this;
const translator = that._params.translator;
const sliders = that._sliders;
const interval = sliders[1].getPosition() - sliders[0].getPosition();
let startPosition = screenPosition - interval / 2;
let endPosition = screenPosition + interval / 2;
if (startPosition < translator.getScreenRange()[0]) {
startPosition = translator.getScreenRange()[0];
endPosition = startPosition + interval
}
if (endPosition > translator.getScreenRange()[1]) {
endPosition = translator.getScreenRange()[1];
startPosition = endPosition - interval
}
const startValue = selectClosestValue(translator.from(startPosition, -1), that._values);
sliders[0].setDisplayValue(startValue);
sliders[1].setDisplayValue(selectClosestValue(translator.from(translator.to(startValue, -1) + interval, 1), that._values));
sliders[0]._position = startPosition;
sliders[1]._position = endPosition;
that._applyTotalPosition(isAnimated);
if (that._isOnMoving) {
that._processSelectionChanged(e)
}
},
placeSliderAndBeginMoving: function(firstPosition, secondPosition, e) {
const that = this;
const translator = that._params.translator;
const sliders = that._sliders;
const index = firstPosition < secondPosition ? 0 : 1;
const dir = index > 0 ? 1 : -1;
const compare = index > 0 ? isGreater : isLess;
const antiCompare = index > 0 ? isLess : isGreater;
let thresholdPosition;
const positions = [];
const values = [];
values[index] = translator.from(firstPosition, dir);
values[1 - index] = translator.from(secondPosition, -dir);
positions[1 - index] = secondPosition;
if (translator.isValueProlonged) {
if (compare(firstPosition, translator.to(values[index], dir))) {
values[index] = translator.from(firstPosition, -dir)
}
if (compare(secondPosition, translator.to(values[index], -dir))) {
values[1 - index] = values[index]
}
}
if (that._minRange) {
thresholdPosition = translator.to(translator.add(selectClosestValue(values[index], that._values), that._minRange, -dir), -dir);
if (compare(secondPosition, thresholdPosition)) {
values[1 - index] = translator.add(values[index], that._minRange, -dir)
}
thresholdPosition = translator.to(translator.add(translator.getRange()[1 - index], that._minRange, dir), -dir);
if (antiCompare(firstPosition, thresholdPosition)) {
values[1 - index] = translator.getRange()[1 - index];
values[index] = translator.add(values[1 - index], that._minRange, dir);
positions[1 - index] = firstPosition
}
}
values[0] = selectClosestValue(values[0], that._values);
values[1] = selectClosestValue(values[1], that._values);
positions[index] = translator.to(values[index], dir);
sliders[0].setDisplayValue(values[0]);
sliders[1].setDisplayValue(values[1]);
sliders[0]._position = positions[0];
sliders[1]._position = positions[1];
that._applyTotalPosition(true);
if (that._isOnMoving) {
that._processSelectionChanged(e)
}
const handler = that.beginSliderMoving(1 - index, secondPosition);
sliders[1 - index]._sliderGroup.stopAnimation();
that._shutter.stopAnimation();
handler(secondPosition);
return handler
},
beginSliderMoving: function(initialIndex, initialPosition) {
const that = this;
const translator = that._params.translator;
const sliders = that._sliders;
const minPosition = translator.getScreenRange()[0];
const maxPosition = translator.getScreenRange()[1];
let index = initialIndex;
const staticPosition = sliders[1 - index].getPosition();
let currentPosition = initialPosition;
let dir = index > 0 ? 1 : -1;
let compareMin = index > 0 ? isLess : isGreater;
let compareMax = index > 0 ? isGreater : isLess;
let moveOffset = sliders[index].getPosition() - initialPosition;
let swapOffset = compareMin(sliders[index].getPosition(), initialPosition) ? -moveOffset : moveOffset;
move.complete = function(e) {
sliders[index]._setValid(true);
that._dockSelectedArea(e)
};
return move;
function move(position, e) {
let isValid;
let temp;
let pos;
let slider;
let value;
if (position !== currentPosition) {
if (compareMin(position + swapOffset, staticPosition)) {
isValid = that._allowSlidersSwap;
if (isValid && !translator.isValueProlonged && that._minRange) {
isValid = translator.isValid(translator.add(sliders[1 - index].getValue(), that._minRange, -dir))
}
if (isValid) {
that._changeMovingSlider(index);
index = 1 - index;
dir = -dir;
temp = compareMin;
compareMin = compareMax;
compareMax = temp;
moveOffset = -dir * Math.abs(moveOffset);
swapOffset = -moveOffset
}
}
if (compareMax(position + moveOffset, staticPosition)) {
slider = sliders[index];
value = sliders[1 - index].getValue();
pos = Math.max(Math.min(position + moveOffset, maxPosition), minPosition);
isValid = translator.isValueProlonged ? !compareMin(pos, translator.to(value, dir)) : true;
let invalidStateValue;
if (isValid && that._minRange) {
isValid = !compareMin(pos, translator.to(translator.add(value, that._minRange, dir), dir));
if (!isValid) {
invalidStateValue = translator.add(value, that._minRange, dir)
}
}
if (isValid && that._maxRange) {
isValid = !compareMax(pos, translator.to(translator.add(value, that._maxRange, dir), dir));
if (!isValid) {
invalidStateValue = translator.add(value, that._maxRange, dir)
}
}
slider._setValid(isValid);
slider.setDisplayValue(isValid ? selectClosestValue(translator.from(pos, dir), that._values) : (0, _type.isDefined)(invalidStateValue) ? invalidStateValue : slider.getValue());
slider._position = pos;
that._applyTotalPosition(false);
slider.toForeground();
if (that._isOnMoving) {
that._processSelectionChanged(e)
}
}
}
currentPosition = position
}
},
_changeMovingSlider: function(index) {
const that = this;
const translator = that._params.translator;
const sliders = that._sliders;
const position = sliders[1 - index].getPosition();
const dir = index > 0 ? 1 : -1;
let newValue;
sliders[index].setDisplayValue(selectClosestValue(translator.from(position, dir), that._values));
newValue = translator.from(position, -dir);
if (translator.isValueProlonged) {
newValue = translator.from(position, dir)
} else if (that._minRange) {
newValue = translator.add(newValue, that._minRange, -dir)
}
sliders[1 - index].setDisplayValue(selectClosestValue(newValue, that._values));
sliders[index]._setValid(true);
sliders[index]._marker._update();
sliders[0]._position = sliders[1]._position = position
},
foregroundSlider: function(index) {
this._sliders[index].toForeground()
}
};