UNPKG

devextreme

Version:

HTML5 JavaScript Component Suite for Responsive Web Development

426 lines (373 loc) • 14.3 kB
"use strict"; var noop = require("../../core/utils/common").noop, typeUtils = require("../../core/utils/type"), extend = require("../../core/utils/extend").extend, each = require("../../core/utils/iterator").each, adjust = require("../../core/utils/math").adjust, isDefined = typeUtils.isDefined, isNumber = typeUtils.isNumeric, isExponential = typeUtils.isExponential, _math = Math, _round = _math.round, _sqrt = Math.sqrt; var PI = Math.PI, MAX_PIXEL_COUNT = 1E10, PI_DIV_180 = PI / 180, LN10 = Math.LN10; var cosFunc = Math.cos, sinFunc = Math.sin, abs = Math.abs, log = Math.log, floor = Math.floor, ceil = Math.ceil, max = Math.max, _isNaN = isNaN, _Number = Number, _NaN = NaN; var getLog = function getLog(value, base) { if (!value) { return _NaN; } return Math.log(value) / Math.log(base); }; var getAdjustedLog10 = function getAdjustedLog10(value) { return adjust(getLog(value, 10)); }; var raiseTo = function raiseTo(power, base) { return Math.pow(base, power); }; // Translates angle to [0, 360) // Expects number, no validation var normalizeAngle = function normalizeAngle(angle) { return (angle % 360 + 360) % 360; }; // Maps angle in trigonometric space to angle in 'renderer' space // Expects numbers, no validation var convertAngleToRendererSpace = function convertAngleToRendererSpace(angle) { return 90 - angle; }; // Maps angle in degrees to angle in radians // Expects number, no validation var degreesToRadians = function degreesToRadians(value) { return PI * value / 180; }; // Calculates sin and cos for <angle> in degrees // Expects number, no validation var getCosAndSin = function getCosAndSin(angle) { var angleInRadians = degreesToRadians(angle); return { cos: cosFunc(angleInRadians), sin: sinFunc(angleInRadians) }; }; // Because Math.log(1000) / Math.LN10 < 3 though it is exactly 3 // Same happens for 1E6, 1E9, 1E12, 1E13, 1E15, ... var DECIMAL_ORDER_THRESHOLD = 1E-14; // ____________________ // / 2 2 // \/ (y2-y1) + (x2-x1) var getDistance = function getDistance(x1, y1, x2, y2) { var diffX = x2 - x1, diffY = y2 - y1; return Math.sqrt(diffY * diffY + diffX * diffX); }; var getDecimalOrder = function getDecimalOrder(number) { var n = abs(number), cn; if (!_isNaN(n)) { if (n > 0) { n = log(n) / LN10; cn = ceil(n); return cn - n < DECIMAL_ORDER_THRESHOLD ? cn : floor(n); } return 0; } return _NaN; }; var getAppropriateFormat = function getAppropriateFormat(start, end, count) { var order = max(getDecimalOrder(start), getDecimalOrder(end)), precision = -getDecimalOrder(abs(end - start) / count), format; if (!_isNaN(order) && !_isNaN(precision)) { if (abs(order) <= 4) { format = 'fixedPoint'; precision < 0 && (precision = 0); precision > 4 && (precision = 4); } else { format = 'exponential'; precision += order - 1; precision > 3 && (precision = 3); } return { type: format, precision: precision }; } return null; }; var roundValue = function roundValue(value, precision) { if (precision > 20) { precision = 20; } if (isNumber(value)) { if (isExponential(value)) { return _Number(value.toExponential(precision)); } else { return _Number(value.toFixed(precision)); } } }; var getPower = function getPower(value) { return value.toExponential().split("e")[1]; }; function map(array, callback) { var i = 0, len = array.length, result = [], value; while (i < len) { value = callback(array[i], i); if (value !== null) { result.push(value); } i++; } return result; } function selectByKeys(object, keys) { return map(keys, function (key) { return object[key] ? object[key] : null; }); } function decreaseFields(object, keys, eachDecrease, decrease) { var dec = decrease; each(keys, function (_, key) { if (object[key]) { object[key] -= eachDecrease; dec -= eachDecrease; } }); return dec; } function normalizeEnum(value) { return String(value).toLowerCase(); } function setCanvasValues(canvas) { if (canvas) { canvas.originalTop = canvas.top; canvas.originalBottom = canvas.bottom; canvas.originalLeft = canvas.left; canvas.originalRight = canvas.right; } } function normalizeBBoxField(value) { return -MAX_PIXEL_COUNT < value && value < +MAX_PIXEL_COUNT ? value : 0; } function normalizeBBox(bBox) { var xl = normalizeBBoxField(floor(bBox.x)), yt = normalizeBBoxField(floor(bBox.y)), xr = normalizeBBoxField(ceil(bBox.width + bBox.x)), yb = normalizeBBoxField(ceil(bBox.height + bBox.y)), result = { x: xl, y: yt, width: xr - xl, height: yb - yt }; result.isEmpty = !result.x && !result.y && !result.width && !result.height; return result; } // Angle is expected to be from right-handed cartesian (not svg) space - positive is counterclockwise function rotateBBox(bBox, center, angle) { var cos = _Number(cosFunc(angle * PI_DIV_180).toFixed(3)), sin = _Number(sinFunc(angle * PI_DIV_180).toFixed(3)), w2 = bBox.width / 2, h2 = bBox.height / 2, centerX = bBox.x + w2, centerY = bBox.y + h2, w2_ = abs(w2 * cos) + abs(h2 * sin), h2_ = abs(w2 * sin) + abs(h2 * cos), // Note that the following slightly differs from theoretical formula: // x' = x * cos - y * sin, y' = x * sin + y * cos // That is because in svg y goes down (not up) - so sign of sin is reverted // x' = x * cos + y * sin, y' = -x * sin + y * cos centerX_ = center[0] + (centerX - center[0]) * cos + (centerY - center[1]) * sin, centerY_ = center[1] - (centerX - center[0]) * sin + (centerY - center[1]) * cos; return normalizeBBox({ x: centerX_ - w2_, y: centerY_ - h2_, width: 2 * w2_, height: 2 * h2_ }); } extend(exports, { decreaseGaps: function decreaseGaps(object, keys, decrease) { var arrayGaps; do { arrayGaps = selectByKeys(object, keys); arrayGaps.push(_math.ceil(decrease / arrayGaps.length)); decrease = decreaseFields(object, keys, _math.min.apply(null, arrayGaps), decrease); } while (decrease > 0 && arrayGaps.length > 1); return decrease; }, normalizeEnum: normalizeEnum, parseScalar: function parseScalar(value, defaultValue) { return value !== undefined ? value : defaultValue; }, enumParser: function enumParser(values) { var stored = {}, i, ii; for (i = 0, ii = values.length; i < ii; ++i) { stored[normalizeEnum(values[i])] = 1; } return function (value, defaultValue) { var _value = normalizeEnum(value); return stored[_value] ? _value : defaultValue; }; }, patchFontOptions: function patchFontOptions(options) { var fontOptions = {}; each(options || {}, function (key, value) { if (/^(cursor|opacity)$/i.test(key)) { // TODO check other properties, add tests } else if (key === "color") { key = "fill"; } else { key = "font-" + key; } fontOptions[key] = value; }); return fontOptions; }, convertPolarToXY: function convertPolarToXY(centerCoords, startAngle, angle, radius) { var shiftAngle = 90, cosSin; angle = isDefined(angle) ? angle + startAngle - shiftAngle : 0; cosSin = getCosAndSin(angle); return { x: _round(centerCoords.x + radius * cosSin.cos), y: _round(centerCoords.y + radius * cosSin.sin) }; }, convertXYToPolar: function convertXYToPolar(centerCoords, x, y) { var radius = getDistance(centerCoords.x, centerCoords.y, x, y), angle = _math.atan2(y - centerCoords.y, x - centerCoords.x); return { phi: _round(normalizeAngle(angle * 180 / _math.PI)), r: _round(radius) }; }, processSeriesTemplate: function processSeriesTemplate(seriesTemplate, items) { var customizeSeries = typeUtils.isFunction(seriesTemplate.customizeSeries) ? seriesTemplate.customizeSeries : noop, nameField = seriesTemplate.nameField, generatedSeries = {}, seriesOrder = [], series, i = 0, length, data; items = items || []; for (length = items.length; i < length; i++) { data = items[i]; if (nameField in data) { series = generatedSeries[data[nameField]]; if (!series) { series = generatedSeries[data[nameField]] = { name: data[nameField] }; seriesOrder.push(series.name); } } } return map(seriesOrder, function (orderedName) { var group = generatedSeries[orderedName]; return extend(group, customizeSeries.call(null, group.name)); }); }, getCategoriesInfo: function getCategoriesInfo(categories, startValue, endValue) { if (categories.length === 0) { return { categories: [] }; } startValue = isDefined(startValue) ? startValue : categories[0]; endValue = isDefined(endValue) ? endValue : categories[categories.length - 1]; var categoriesValue = map(categories, function (category) { return isDefined(category) ? category.valueOf() : null; }), visibleCategories, indexStartValue = categoriesValue.indexOf(startValue.valueOf()), indexEndValue = categoriesValue.indexOf(endValue.valueOf()), swapBuf, inverted = false, lastIdx; indexStartValue < 0 && (indexStartValue = 0); indexEndValue < 0 && (indexEndValue = categories.length - 1); if (indexEndValue < indexStartValue) { swapBuf = indexEndValue; indexEndValue = indexStartValue; indexStartValue = swapBuf; inverted = true; } visibleCategories = categories.slice(indexStartValue, indexEndValue + 1); lastIdx = visibleCategories.length - 1; return { categories: visibleCategories, start: visibleCategories[inverted ? lastIdx : 0], end: visibleCategories[inverted ? 0 : lastIdx], inverted: inverted }; }, setCanvasValues: setCanvasValues, updatePanesCanvases: function updatePanesCanvases(panes, canvas, rotated) { var weightSum = 0; each(panes, function (_, pane) { pane.weight = pane.weight || 1; weightSum += pane.weight; }); var distributedSpace = 0, padding = panes.padding || 10, paneSpace = rotated ? canvas.width - canvas.left - canvas.right : canvas.height - canvas.top - canvas.bottom, oneWeight = (paneSpace - padding * (panes.length - 1)) / weightSum, startName = rotated ? "left" : "top", endName = rotated ? "right" : "bottom"; each(panes, function (_, pane) { var calcLength = _round(pane.weight * oneWeight); pane.canvas = pane.canvas || {}; extend(pane.canvas, canvas); pane.canvas[startName] = canvas[startName] + distributedSpace; pane.canvas[endName] = canvas[endName] + (paneSpace - calcLength - distributedSpace); distributedSpace = distributedSpace + calcLength + padding; setCanvasValues(pane.canvas); }); }, unique: function unique(array) { var values = {}; return map(array, function (item) { var result = !values[item] ? item : null; values[item] = true; return result; }); }, map: map, getVerticallyShiftedAngularCoords: function getVerticallyShiftedAngularCoords(bBox, dy, center) { // TODO: Use center instead of left top corner - that is more correct and allows to get rid of "isPositive" // horizontalOffset1 = bBox.x + bBox.width / 2 - center.x // horizontalOffset2 = bBox.y + bBox.height / 2 - center.y // verticalOffset2 = newCoord.y + bBox.height / 2 - center.y var isPositive = bBox.x + bBox.width / 2 >= center.x, horizontalOffset1 = (isPositive ? bBox.x : bBox.x + bBox.width) - center.x, verticalOffset1 = bBox.y - center.y, verticalOffset2 = verticalOffset1 + dy, horizontalOffset2 = _round(_sqrt(horizontalOffset1 * horizontalOffset1 + verticalOffset1 * verticalOffset1 - verticalOffset2 * verticalOffset2)), dx = (isPositive ? +horizontalOffset2 : -horizontalOffset2) || horizontalOffset1; return { x: center.x + (isPositive ? dx : dx - bBox.width), y: bBox.y + dy }; }, mergeMarginOptions: function mergeMarginOptions(opt1, opt2) { return { checkInterval: opt1.checkInterval || opt2.checkInterval, size: Math.max(opt1.size || 0, opt2.size || 0), percentStick: opt1.percentStick || opt2.percentStick, sizePointNormalState: Math.max(opt1.sizePointNormalState || 0, opt2.sizePointNormalState || 0) }; } }); exports.getLog = getLog; exports.getAdjustedLog10 = getAdjustedLog10; exports.raiseTo = raiseTo; exports.normalizeAngle = normalizeAngle; exports.convertAngleToRendererSpace = convertAngleToRendererSpace; exports.degreesToRadians = degreesToRadians; exports.getCosAndSin = getCosAndSin; exports.getDecimalOrder = getDecimalOrder; exports.getAppropriateFormat = getAppropriateFormat; exports.getDistance = getDistance; exports.roundValue = roundValue; exports.getPower = getPower; exports.rotateBBox = rotateBBox; exports.normalizeBBox = normalizeBBox;