devextreme
Version:
HTML5 JavaScript Component Suite for Responsive Web Development
464 lines (453 loc) • 20.6 kB
JavaScript
/**
* DevExtreme (viz/translators/translator2d.js)
* Version: 18.1.3
* Build date: Tue May 15 2018
*
* Copyright (c) 2012 - 2018 Developer Express Inc. ALL RIGHTS RESERVED
* Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
*/
"use strict";
var extend = require("../../core/utils/extend").extend,
each = require("../../core/utils/iterator").each,
numericTranslator = require("./numeric_translator"),
categoryTranslator = require("./category_translator"),
intervalTranslator = require("./interval_translator"),
datetimeTranslator = require("./datetime_translator"),
logarithmicTranslator = require("./logarithmic_translator"),
vizUtils = require("../core/utils"),
typeUtils = require("../../core/utils/type"),
rangeModule = require("./range"),
getLog = vizUtils.getLog,
getPower = vizUtils.getPower,
isDefined = typeUtils.isDefined,
_abs = Math.abs,
CANVAS_PROP = ["width", "height", "left", "top", "bottom", "right"],
NUMBER_EQUALITY_CORRECTION = 1,
DATETIME_EQUALITY_CORRECTION = 6e4,
_noop = require("../../core/utils/common").noop,
_Translator2d, addInterval = require("../../core/utils/date").addInterval;
var validateCanvas = function(canvas) {
each(CANVAS_PROP, function(_, prop) {
canvas[prop] = parseInt(canvas[prop]) || 0
});
return canvas
};
var makeCategoriesToPoints = function(categories) {
var categoriesToPoints = {};
categories.forEach(function(item, i) {
categoriesToPoints[item.valueOf()] = i
});
return categoriesToPoints
};
var validateBusinessRange = function(businessRange) {
function validate(valueSelector, baseValueSelector) {
if (!isDefined(businessRange[valueSelector]) && isDefined(businessRange[baseValueSelector])) {
businessRange[valueSelector] = businessRange[baseValueSelector]
}
}
validate("minVisible", "min");
validate("maxVisible", "max");
return businessRange
};
function valuesAreDefinedAndEqual(val1, val2) {
return isDefined(val1) && isDefined(val2) && val1.valueOf() === val2.valueOf()
}
function prepareBreaks(breaks, range) {
var br, transformFrom, transformTo, i, transform = "logarithmic" === range.axisType ? function(value) {
return getLog(value, range.base)
} : function(value) {
return value
},
array = [],
length = breaks.length,
sum = 0;
for (i = 0; i < length; i++) {
br = breaks[i];
transformFrom = transform(br.from);
transformTo = transform(br.to);
sum += transformTo - transformFrom;
array.push({
trFrom: transformFrom,
trTo: transformTo,
from: br.from,
to: br.to,
length: sum,
cumulativeWidth: br.cumulativeWidth
})
}
return array
}
function getCanvasBounds(range) {
var newMin, newMax, min = range.min,
max = range.max,
minVisible = range.minVisible,
maxVisible = range.maxVisible,
base = range.base,
isDateTime = typeUtils.isDate(max) || typeUtils.isDate(min),
correction = isDateTime ? DATETIME_EQUALITY_CORRECTION : NUMBER_EQUALITY_CORRECTION,
isLogarithmic = "logarithmic" === range.axisType;
if (isLogarithmic) {
maxVisible = getLog(maxVisible, base);
minVisible = getLog(minVisible, base);
min = getLog(min, base);
max = getLog(max, base)
}
if (valuesAreDefinedAndEqual(min, max)) {
newMin = min.valueOf() - correction;
newMax = max.valueOf() + correction;
if (isDateTime) {
min = new Date(newMin);
max = new Date(newMax)
} else {
min = 0 !== min || isLogarithmic ? newMin : 0;
max = newMax
}
}
if (valuesAreDefinedAndEqual(minVisible, maxVisible)) {
newMin = minVisible.valueOf() - correction;
newMax = maxVisible.valueOf() + correction;
if (isDateTime) {
minVisible = newMin < min.valueOf() ? min : new Date(newMin);
maxVisible = newMax > max.valueOf() ? max : new Date(newMax)
} else {
if (0 !== minVisible || isLogarithmic) {
minVisible = newMin < min ? min : newMin
}
maxVisible = newMax > max ? max : newMax
}
}
return {
base: base,
rangeMin: min,
rangeMax: max,
rangeMinVisible: minVisible,
rangeMaxVisible: maxVisible
}
}
function getEqualityCorrection(range) {
var isDateTime = typeUtils.isDate(range.min) || typeUtils.isDate(range.max);
return isDateTime ? DATETIME_EQUALITY_CORRECTION : NUMBER_EQUALITY_CORRECTION
}
function zoomArgsIsEqualCanvas(zoomArgs) {
var businessRange = this.getBusinessRange();
return valuesAreDefinedAndEqual(businessRange.min, businessRange.max) && this.isEqualRange(zoomArgs)
}
function isEqualRange(range) {
var that = this;
var businessRange = that.getBusinessRange();
var canvasOptions = getCanvasBounds(businessRange);
var correctionPrecision = getEqualityCorrection(businessRange) / 100;
var comparingRange = new rangeModule.Range(range);
if ("discrete" === businessRange.axisType) {
return false
}
if (range && "logarithmic" === businessRange.axisType) {
comparingRange.min = comparingRange.min && getLog(comparingRange.min, businessRange.base);
comparingRange.max = comparingRange.max && getLog(comparingRange.max, businessRange.base)
}
return range && (!isDefined(comparingRange.min) || _abs(comparingRange.min - canvasOptions.rangeMin) <= correctionPrecision) && (!isDefined(comparingRange.max) || _abs(comparingRange.max - canvasOptions.rangeMax) <= correctionPrecision)
}
function checkGestureEventsForScaleEdges(scrollThreshold, scale, scroll, touches, zoomArgs) {
var that = this,
businessRange = that.getBusinessRange(),
isDiscreteAxis = "discrete" === businessRange.axisType,
scrollBarNearMin = that.scrollHasExtremePosition(scrollThreshold, false),
scrollBarNearMax = that.scrollHasExtremePosition(scrollThreshold, true),
isOriginalScale = that.checkScrollForOriginalScale(scrollThreshold),
scalingEventAtMin = scrollBarNearMin && ((businessRange.rotated ? scroll > 0 : scroll < 0) || 1 !== scale),
scalingEventAtMax = scrollBarNearMax && ((businessRange.rotated ? scroll < 0 : scroll > 0) || 1 !== scale);
return 2 === touches && 1 === scale || (zoomArgs || isDiscreteAxis || !this.isEqualRange({
min: businessRange.minVisible,
max: businessRange.maxVisible
})) && !scrollBarNearMin && !scrollBarNearMax || !isOriginalScale && (scalingEventAtMin || scalingEventAtMax) || isOriginalScale && scale > 1
}
function checkScrollForOriginalScale(scrollThreshold) {
return this.scrollHasExtremePosition(scrollThreshold, false) && this.scrollHasExtremePosition(scrollThreshold, true)
}
function scrollHasExtremePosition(scrollThreshold, isMax) {
var equalityCorrection, distanceToExtremum, that = this,
businessRange = that.getBusinessRange(),
isDiscreteAxis = "discrete" === businessRange.axisType,
min = isDiscreteAxis ? businessRange.categories[0] : businessRange.min,
max = isDiscreteAxis ? businessRange.categories[businessRange.categories.length - 1] : businessRange.max,
isSinglePoint = min === max,
isMaxExtremum = !businessRange.invert && isMax || businessRange.invert && !isMax,
axisExtremum = isMaxExtremum ? max : min,
scrollExtremum = isMaxExtremum ? businessRange.maxVisible : businessRange.minVisible;
if (isDiscreteAxis) {
return axisExtremum.valueOf() === scrollExtremum.valueOf()
} else {
if ("logarithmic" === businessRange.axisType) {
axisExtremum = vizUtils.getLog(axisExtremum, businessRange.base);
scrollExtremum = vizUtils.getLog(scrollExtremum, businessRange.base)
}
equalityCorrection = axisExtremum.valueOf() === scrollExtremum.valueOf() ? 0 : getEqualityCorrection(businessRange);
distanceToExtremum = isSinglePoint ? Math.abs(axisExtremum + (isMaxExtremum ? 1 : -1) * equalityCorrection - scrollExtremum) : Math.abs(axisExtremum - scrollExtremum);
return distanceToExtremum * that._canvasOptions.ratioOfCanvasRange < scrollThreshold
}
}
function getCheckingMethodsAboutBreaks(inverted) {
return {
isStartSide: !inverted ? function(pos, breaks, start, end) {
return pos < breaks[0][start]
} : function(pos, breaks, start, end) {
return pos <= breaks[breaks.length - 1][end]
},
isEndSide: !inverted ? function(pos, breaks, start, end) {
return pos >= breaks[breaks.length - 1][end]
} : function(pos, breaks, start, end) {
return pos > breaks[0][start]
},
isInBreak: !inverted ? function(pos, br, start, end) {
return pos >= br[start] && pos < br[end]
} : function(pos, br, start, end) {
return pos > br[end] && pos <= br[start]
},
isBetweenBreaks: !inverted ? function(pos, br, prevBreak, start, end) {
return pos < br[start] && pos >= prevBreak[end]
} : function(pos, br, prevBreak, start, end) {
return pos >= br[end] && pos < prevBreak[start]
},
getLength: !inverted ? function(br) {
return br.length
} : function(br, lastBreak) {
return lastBreak.length - br.length
},
getBreaksSize: !inverted ? function(br) {
return br.cumulativeWidth
} : function(br, lastBreak) {
return lastBreak.cumulativeWidth - br.cumulativeWidth
}
}
}
exports.Translator2D = _Translator2d = function(businessRange, canvas, options) {
this.update(businessRange, canvas, options)
};
_Translator2d.prototype = {
constructor: _Translator2d,
reinit: function() {
var that = this,
options = that._options,
range = that._businessRange,
categories = range.categories || [],
script = {},
canvasOptions = that._prepareCanvasOptions(),
visibleCategories = vizUtils.getCategoriesInfo(categories, range.minVisible, range.maxVisible).categories,
categoriesLength = visibleCategories.length;
switch (range.axisType) {
case "logarithmic":
script = logarithmicTranslator;
break;
case "semidiscrete":
script = intervalTranslator;
canvasOptions.ratioOfCanvasRange = canvasOptions.canvasLength / (addInterval(canvasOptions.rangeMaxVisible, options.interval) - canvasOptions.rangeMinVisible);
break;
case "discrete":
script = categoryTranslator;
that._categories = categories;
canvasOptions.interval = that._getDiscreteInterval(options.addSpiderCategory ? categoriesLength + 1 : categoriesLength, canvasOptions);
that._categoriesToPoints = makeCategoriesToPoints(categories, canvasOptions.invert);
if (categoriesLength) {
canvasOptions.startPointIndex = that._categoriesToPoints[visibleCategories[0].valueOf()];
that.visibleCategories = visibleCategories
}
break;
default:
if ("datetime" === range.dataType) {
script = datetimeTranslator
} else {
script = numericTranslator
}
}
extend(that, script);
that._conversionValue = options.conversionValue ? function(value) {
return value
} : function(value) {
return Math.round(value)
};
that._calculateSpecialValues();
that._checkingMethodsAboutBreaks = [getCheckingMethodsAboutBreaks(false), getCheckingMethodsAboutBreaks(that.isInverted())];
that._translateBreaks()
},
_translateBreaks: function() {
var i, b, end, length, breaks = this._breaks,
size = this._options.breaksSize;
if (void 0 === breaks) {
return
}
for (i = 0, length = breaks.length; i < length; i++) {
b = breaks[i];
end = this.translate(b.to);
b.end = end;
b.start = !b.gapSize ? !this.isInverted() ? end - size : end + size : end
}
},
_checkValueAboutBreaks: function(breaks, pos, start, end, methods) {
var i, length, br, prevBreak, prop = {
length: 0,
breaksSize: void 0,
inBreak: false
},
lastBreak = breaks[breaks.length - 1];
if (methods.isStartSide(pos, breaks, start, end)) {
return prop
} else {
if (methods.isEndSide(pos, breaks, start, end)) {
return {
length: lastBreak.length,
breaksSize: lastBreak.cumulativeWidth,
inBreak: false
}
}
}
for (i = 0, length = breaks.length; i < length; i++) {
br = breaks[i];
prevBreak = breaks[i - 1];
if (methods.isInBreak(pos, br, start, end)) {
prop.inBreak = true;
prop.break = br;
break
}
if (prevBreak && methods.isBetweenBreaks(pos, br, prevBreak, start, end)) {
prop = {
length: methods.getLength(prevBreak, lastBreak),
breaksSize: methods.getBreaksSize(prevBreak, lastBreak),
inBreak: false
};
break
}
}
return prop
},
isInverted: function() {
return !(this._options.isHorizontal ^ this._businessRange.invert)
},
_getDiscreteInterval: function(categoriesLength, canvasOptions) {
var correctedCategoriesCount = categoriesLength - (this._options.stick ? 1 : 0);
return correctedCategoriesCount > 0 ? canvasOptions.canvasLength / correctedCategoriesCount : canvasOptions.canvasLength
},
_prepareCanvasOptions: function() {
var length, that = this,
businessRange = that._businessRange,
canvasOptions = that._canvasOptions = getCanvasBounds(businessRange),
canvas = that._canvas,
breaks = that._breaks;
if (that._options.isHorizontal) {
canvasOptions.startPoint = canvas.left;
length = canvas.width;
canvasOptions.endPoint = canvas.width - canvas.right;
canvasOptions.invert = businessRange.invert
} else {
canvasOptions.startPoint = canvas.top;
length = canvas.height;
canvasOptions.endPoint = canvas.height - canvas.bottom;
canvasOptions.invert = !businessRange.invert
}
that.canvasLength = canvasOptions.canvasLength = canvasOptions.endPoint - canvasOptions.startPoint;
canvasOptions.rangeDoubleError = Math.pow(10, getPower(canvasOptions.rangeMax - canvasOptions.rangeMin) - getPower(length) - 2);
canvasOptions.ratioOfCanvasRange = canvasOptions.canvasLength / (canvasOptions.rangeMaxVisible - canvasOptions.rangeMinVisible);
if (void 0 !== breaks) {
canvasOptions.ratioOfCanvasRange = (canvasOptions.canvasLength - breaks[breaks.length - 1].cumulativeWidth) / (canvasOptions.rangeMaxVisible - canvasOptions.rangeMinVisible - breaks[breaks.length - 1].length)
}
return canvasOptions
},
updateCanvas: function(canvas) {
this._canvas = validateCanvas(canvas);
this.reinit()
},
updateBusinessRange: function(businessRange) {
var that = this,
breaks = businessRange.breaks || [];
that._businessRange = validateBusinessRange(businessRange);
that._breaks = breaks.length ? prepareBreaks(breaks, that._businessRange) : void 0;
that.reinit()
},
update: function(businessRange, canvas, options) {
var that = this;
that._options = extend(that._options || {}, options);
that._canvas = validateCanvas(canvas);
that.updateBusinessRange(businessRange)
},
getBusinessRange: function() {
return this._businessRange
},
getCanvasVisibleArea: function() {
return {
min: this._canvasOptions.startPoint,
max: this._canvasOptions.endPoint
}
},
_calculateSpecialValues: function() {
var invert, canvas_position_default, canvas_position_center_middle, that = this,
canvasOptions = that._canvasOptions,
startPoint = canvasOptions.startPoint,
endPoint = canvasOptions.endPoint,
range = that._businessRange,
minVisible = range.minVisible,
maxVisible = range.maxVisible;
if (minVisible <= 0 && maxVisible >= 0) {
that.sc = {};
canvas_position_default = that.translate(0)
} else {
invert = range.invert ^ (minVisible <= 0 && maxVisible <= 0);
if (that._options.isHorizontal) {
canvas_position_default = invert ? endPoint : startPoint
} else {
canvas_position_default = invert ? startPoint : endPoint
}
}
canvas_position_center_middle = startPoint + canvasOptions.canvasLength / 2;
that.sc = {
canvas_position_default: canvas_position_default,
canvas_position_left: startPoint,
canvas_position_top: startPoint,
canvas_position_center: canvas_position_center_middle,
canvas_position_middle: canvas_position_center_middle,
canvas_position_right: endPoint,
canvas_position_bottom: endPoint,
canvas_position_start: canvasOptions.invert ? endPoint : startPoint,
canvas_position_end: canvasOptions.invert ? startPoint : endPoint
}
},
translateSpecialCase: function(value) {
return this.sc[value]
},
_calculateProjection: function(distance) {
var canvasOptions = this._canvasOptions;
return canvasOptions.invert ? canvasOptions.endPoint - distance : canvasOptions.startPoint + distance
},
_calculateUnProjection: function(distance) {
var canvasOptions = this._canvasOptions;
return canvasOptions.invert ? canvasOptions.rangeMaxVisible.valueOf() - distance : canvasOptions.rangeMinVisible.valueOf() + distance
},
getMinBarSize: function(minBarSize) {
var visibleArea = this.getCanvasVisibleArea(),
minValue = this.untranslate(visibleArea.min + minBarSize);
return _abs(this.untranslate(visibleArea.min) - (!isDefined(minValue) ? this.untranslate(visibleArea.max) : minValue))
},
checkMinBarSize: function(value, minShownValue, stackValue) {
return _abs(value) < minShownValue ? value >= 0 ? minShownValue : -minShownValue : value
},
translate: _noop,
untranslate: _noop,
getInterval: _noop,
zoom: _noop,
getMinScale: _noop,
zoomArgsIsEqualCanvas: zoomArgsIsEqualCanvas,
isEqualRange: isEqualRange,
checkScrollForOriginalScale: checkScrollForOriginalScale,
scrollHasExtremePosition: scrollHasExtremePosition,
checkGestureEventsForScaleEdges: checkGestureEventsForScaleEdges,
getRange: function() {
return [this.untranslate(this._canvasOptions.startPoint, -1), this.untranslate(this._canvasOptions.endPoint, 1)]
},
isEmptyValueRange: function() {
return this._businessRange.stubData
},
getScreenRange: function() {
return [this._canvasOptions.startPoint, this._canvasOptions.endPoint]
},
add: function(value, diff, dir) {
return this._add(value, diff, (this._businessRange.invert ? -1 : 1) * dir)
}
};