UNPKG

devextreme

Version:

HTML5 JavaScript Component Suite for Responsive Web Development

691 lines (688 loc) • 25.9 kB
/** * DevExtreme (cjs/viz/axes/polar_axes.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.linearSpider = exports.linear = exports.circularSpider = exports.circular = void 0; var _utils = require("../core/utils"); var _type = require("../../core/utils/type"); var _extend = require("../../core/utils/extend"); var _axes_constants = _interopRequireDefault(require("./axes_constants")); var _xy_axes = _interopRequireDefault(require("./xy_axes")); var _tick = require("./tick"); var _axes_utils = require("./axes_utils"); var _common = require("../../core/utils/common"); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e } } const { PI: PI, abs: abs, atan: atan, round: round } = Math; const _min = Math.min; const _max = Math.max; const xyAxesLinear = _xy_axes.default.linear; const HALF_PI_ANGLE = 90; function getPolarQuarter(angle) { let quarter; angle = (0, _utils.normalizeAngle)(angle); if (angle >= 315 && angle <= 360 || angle < 45 && angle >= 0) { quarter = 1 } else if (angle >= 45 && angle < 135) { quarter = 2 } else if (angle >= 135 && angle < 225) { quarter = 3 } else if (angle >= 225 && angle < 315) { quarter = 4 } return quarter } const circularAxes = { _calculateValueMargins(ticks) { let { minVisible: minVisible, maxVisible: maxVisible } = this._getViewportRange(); if (ticks && ticks.length > 1) { minVisible = minVisible < ticks[0].value ? minVisible : ticks[0].value; maxVisible = minVisible > ticks[ticks.length - 1].value ? maxVisible : ticks[ticks.length - 1].value } return { minValue: minVisible, maxValue: maxVisible } }, applyMargins() { const margins = this._calculateValueMargins(this._majorTicks); const br = this._translator.getBusinessRange(); br.addRange({ minVisible: margins.minValue, maxVisible: margins.maxValue, interval: this._calculateRangeInterval(br.interval) }); this._translator.updateBusinessRange(br) }, _getTranslatorOptions: function() { return { isHorizontal: true, conversionValue: true, addSpiderCategory: this._getSpiderCategoryOption(), stick: this._getStick() } }, getCenter: function() { return this._center }, getRadius: function() { return this._radius }, getAngles: function() { const options = this._options; return [options.startAngle, options.endAngle] }, _updateRadius(canvas) { const rad = _min(canvas.width - canvas.left - canvas.right, canvas.height - canvas.top - canvas.bottom) / 2; this._radius = rad < 0 ? 0 : rad }, _updateCenter: function(canvas) { this._center = { x: canvas.left + (canvas.width - canvas.right - canvas.left) / 2, y: canvas.top + (canvas.height - canvas.top - canvas.bottom) / 2 } }, _processCanvas: function(canvas) { this._updateRadius(canvas); this._updateCenter(canvas); return { left: 0, right: 0, width: this._getScreenDelta() } }, _createAxisElement: function() { return this._renderer.circle() }, _updateAxisElementPosition: function() { const center = this.getCenter(); this._axisElement.attr({ cx: center.x, cy: center.y, r: this.getRadius() }) }, _boundaryTicksVisibility: { min: true }, _getSpiderCategoryOption: function() { return this._options.firstPointOnStartAngle }, _validateOptions(options) { const that = this; let originValue = options.originValue; const wholeRange = options.wholeRange = {}; const period = options.period; if ((0, _type.isDefined)(originValue)) { originValue = that.validateUnit(originValue) } if (period > 0 && options.argumentType === _axes_constants.default.numeric) { originValue = originValue || 0; wholeRange.endValue = originValue + period; that._viewport = (0, _utils.getVizRangeObject)([originValue, wholeRange.endValue]) } if ((0, _type.isDefined)(originValue)) { wholeRange.startValue = originValue } }, getMargins() { const tickOptions = this._options.tick; const tickOuterLength = _max(tickOptions.visible ? tickOptions.length / 2 + tickOptions.shift : 0, 0); const radius = this.getRadius(); const { x: x, y: y } = this._center; const labelBoxes = this._majorTicks.map((t => t.label && t.label.getBBox())).filter((b => b)); const canvas = (0, _extend.extend)({}, this._canvas, { left: x - radius, top: y - radius, right: this._canvas.width - (x + radius), bottom: this._canvas.height - (y + radius) }); const margins = (0, _axes_utils.calculateCanvasMargins)(labelBoxes, canvas); Object.keys(margins).forEach((k => margins[k] = margins[k] < tickOuterLength ? tickOuterLength : margins[k])); return margins }, _updateLabelsPosition() { (0, _axes_utils.measureLabels)(this._majorTicks); this._adjustLabelsCoord(0, 0, true); this._checkBoundedLabelsOverlapping(this._majorTicks, this._majorTicks.map((t => t.labelBBox))) }, _setVisualRange: _common.noop, applyVisualRangeSetter: _common.noop, _getStick: function() { return this._options.firstPointOnStartAngle || this._options.type !== _axes_constants.default.discrete }, _getTranslatedCoord: function(value, offset) { return this._translator.translate(value, offset) - 90 }, _getCanvasStartEnd: function() { return { start: -90, end: 270 } }, _getStripGraphicAttributes: function(fromAngle, toAngle) { const center = this.getCenter(); const angle = this.getAngles()[0]; const r = this.getRadius(); return { x: center.x, y: center.y, innerRadius: 0, outerRadius: r, startAngle: -toAngle - angle, endAngle: -fromAngle - angle } }, _createStrip: function(coords) { return this._renderer.arc(coords.x, coords.y, coords.innerRadius, coords.outerRadius, coords.startAngle, coords.endAngle) }, _getStripLabelCoords: function(from, to) { const coords = this._getStripGraphicAttributes(from, to); const angle = coords.startAngle + (coords.endAngle - coords.startAngle) / 2; const cosSin = (0, _utils.getCosAndSin)(angle); const halfRad = this.getRadius() / 2; const center = this.getCenter(); const x = round(center.x + halfRad * cosSin.cos); const y = round(center.y - halfRad * cosSin.sin); return { x: x, y: y, align: _axes_constants.default.center } }, _getConstantLineGraphicAttributes: function(value) { const center = this.getCenter(); const r = this.getRadius(); return { points: [center.x, center.y, center.x + r, center.y] } }, _createConstantLine: function(value, attr) { return this._createPathElement(this._getConstantLineGraphicAttributes(value).points, attr) }, _rotateConstantLine(line, value) { const { x: x, y: y } = this.getCenter(); line.rotate(value + this.getAngles()[0], x, y) }, _getConstantLineLabelsCoords: function(value) { const cosSin = (0, _utils.getCosAndSin)(-value - this.getAngles()[0]); const halfRad = this.getRadius() / 2; const center = this.getCenter(); const x = round(center.x + halfRad * cosSin.cos); const y = round(center.y - halfRad * cosSin.sin); return { x: x, y: y } }, _checkAlignmentConstantLineLabels: _common.noop, _adjustDivisionFactor: function(val) { return 180 * val / (this.getRadius() * PI) }, _getScreenDelta: function() { const angles = this.getAngles(); return abs(angles[0] - angles[1]) }, _getTickMarkPoints: function(coords, length, _ref) { let { shift: shift = 0 } = _ref; const center = this.getCenter(); const radiusWithTicks = this.getRadius() + length * { inside: -1, center: -.5, outside: 0 } [this._options.tickOrientation || "center"]; return [center.x + radiusWithTicks + shift, center.y, center.x + radiusWithTicks + length + shift, center.y] }, _getLabelAdjustedCoord: function(tick, _offset, _maxWidth, checkCanvas) { const that = this; const labelCoords = tick.labelCoords; const labelY = labelCoords.y; const labelAngle = labelCoords.angle; const cosSin = (0, _utils.getCosAndSin)(labelAngle); const cos = cosSin.cos; const sin = cosSin.sin; const box = tick.labelBBox; const halfWidth = box.width / 2; const halfHeight = box.height / 2; const indentFromAxis = that._options.label.indentFromAxis || 0; const x = labelCoords.x + indentFromAxis * cos; const y = labelY + (labelY - box.y - halfHeight) + indentFromAxis * sin; let shiftX = 0; let shiftY = 0; switch (getPolarQuarter(labelAngle)) { case 1: shiftX = halfWidth; shiftY = halfHeight * sin; break; case 2: shiftX = halfWidth * cos; shiftY = halfHeight; break; case 3: shiftX = -halfWidth; shiftY = halfHeight * sin; break; case 4: shiftX = halfWidth * cos; shiftY = -halfHeight } if (checkCanvas) { const canvas = that._canvas; const boxShiftX = x - labelCoords.x + shiftX; const boxShiftY = y - labelCoords.y + shiftY; if (box.x + boxShiftX < canvas.originalLeft) { shiftX -= box.x + boxShiftX - canvas.originalLeft } if (box.x + box.width + boxShiftX > canvas.width - canvas.originalRight) { shiftX -= box.x + box.width + boxShiftX - (canvas.width - canvas.originalRight) } if (box.y + boxShiftY < canvas.originalTop) { shiftY -= box.y + boxShiftY - canvas.originalTop } if (box.y + box.height + boxShiftY > canvas.height - canvas.originalBottom) { shiftY -= box.y + box.height + boxShiftY - (canvas.height - canvas.originalBottom) } } return { x: x + shiftX, y: y + shiftY } }, _getGridLineDrawer: function() { const that = this; return function(tick, gridStyle) { const center = that.getCenter(); return that._createPathElement(that._getGridPoints().points, gridStyle).rotate(tick.coords.angle, center.x, center.y) } }, _getGridPoints: function() { const r = this.getRadius(); const center = this.getCenter(); return { points: [center.x, center.y, center.x + r, center.y] } }, _getTranslatedValue: function(value, offset) { const startAngle = this.getAngles()[0]; const angle = this._translator.translate(value, -offset); const coords = (0, _utils.convertPolarToXY)(this.getCenter(), startAngle, angle, this.getRadius()); return { x: coords.x, y: coords.y, angle: this.getTranslatedAngle(angle) } }, _getAdjustedStripLabelCoords: function(strip) { const box = strip.labelBBox; return { translateY: strip.label.attr("y") - box.y - box.height / 2 } }, coordsIn: function(x, y) { return (0, _utils.convertXYToPolar)(this.getCenter(), x, y).r > this.getRadius() }, _rotateTick: function(element, coords) { const center = this.getCenter(); element.rotate(coords.angle, center.x, center.y) }, _validateOverlappingMode: function(mode) { return _axes_constants.default.validateOverlappingMode(mode) }, _validateDisplayMode: function() { return "standard" }, _getStep: function(boxes) { const radius = this.getRadius() + (this._options.label.indentFromAxis || 0); const maxLabelBox = boxes.reduce((function(prevValue, box) { const curValue = prevValue; if (prevValue.width < box.width) { curValue.width = box.width } if (prevValue.height < box.height) { curValue.height = box.height } return curValue }), { width: 0, height: 0 }); const angle1 = abs(2 * atan(maxLabelBox.height / (2 * radius - maxLabelBox.width)) * 180 / PI); const angle2 = abs(2 * atan(maxLabelBox.width / (2 * radius - maxLabelBox.height)) * 180 / PI); return _axes_constants.default.getTicksCountInRange(this._majorTicks, "angle", _max(angle1, angle2)) }, _checkBoundedLabelsOverlapping: function(majorTicks, boxes, mode) { const labelOpt = this._options.label; mode = mode || this._validateOverlappingMode(labelOpt.overlappingBehavior); if ("hide" !== mode) { return } const lastVisibleLabelIndex = majorTicks.reduce(((lastVisibleLabelIndex, tick, index) => tick.label ? index : lastVisibleLabelIndex), null); if (!lastVisibleLabelIndex) { return } if (_axes_constants.default.areLabelsOverlap(boxes[0], boxes[lastVisibleLabelIndex], labelOpt.minSpacing, _axes_constants.default.center)) { "first" === labelOpt.hideFirstOrLast ? majorTicks[0].removeLabel() : majorTicks[lastVisibleLabelIndex].removeLabel() } }, shift: function(margins) { this._axisGroup.attr({ translateX: margins.right, translateY: margins.bottom }); this._axisElementsGroup.attr({ translateX: margins.right, translateY: margins.bottom }) }, getTranslatedAngle(angle) { const startAngle = this.getAngles()[0]; return angle + startAngle - 90 } }; const circular = exports.circular = circularAxes; const circularSpider = exports.circularSpider = (0, _extend.extend)({}, circularAxes, { _createAxisElement: function() { return this._renderer.path([], "area") }, _updateAxisElementPosition: function() { this._axisElement.attr({ points: (0, _utils.map)(this.getSpiderTicks(), (function(tick) { return { x: tick.coords.x, y: tick.coords.y } })) }) }, _getStick: function() { return true }, _getSpiderCategoryOption: function() { return true }, getSpiderTicks: function() { const ticks = this.getFullTicks(); this._spiderTicks = ticks.map((0, _tick.tick)(this, this.renderer, {}, {}, this._getSkippedCategory(ticks), true)); this._spiderTicks.forEach((function(tick) { tick.initCoords() })); return this._spiderTicks }, _getStripGraphicAttributes: function(fromAngle, toAngle) { const center = this.getCenter(); const spiderTicks = this.getSpiderTicks(); let firstTick; let lastTick; let nextTick; let tick; const points = []; let i = 0; const len = spiderTicks.length; while (i < len) { tick = spiderTicks[i].coords; if (tick.angle >= fromAngle && tick.angle <= toAngle) { if (!firstTick) { firstTick = (spiderTicks[i - 1] || spiderTicks[spiderTicks.length - 1]).coords; points.push((tick.x + firstTick.x) / 2, (tick.y + firstTick.y) / 2) } points.push(tick.x, tick.y); nextTick = (spiderTicks[i + 1] || spiderTicks[0]).coords; lastTick = { x: (tick.x + nextTick.x) / 2, y: (tick.y + nextTick.y) / 2 } } i++ } points.push(lastTick.x, lastTick.y); points.push(center.x, center.y); return { points: points } }, _createStrip: function(_ref2) { let { points: points } = _ref2; return this._renderer.path(points, "area") }, _getTranslatedCoord: function(value, offset) { return this._translator.translate(value, offset) - 90 }, _setTickOffset: function() { this._tickOffset = false } }); const linear = exports.linear = { _resetMargins() { this._reinitTranslator(this._getViewportRange()) }, _getStick: xyAxesLinear._getStick, _getSpiderCategoryOption: _common.noop, _getTranslatorOptions: function() { return { isHorizontal: true, stick: this._getStick() } }, getRadius: circularAxes.getRadius, getCenter: circularAxes.getCenter, getAngles: circularAxes.getAngles, _updateRadius: circularAxes._updateRadius, _updateCenter: circularAxes._updateCenter, _processCanvas(canvas) { this._updateRadius(canvas); this._updateCenter(canvas); return { left: 0, right: 0, startPadding: canvas.startPadding, endPadding: canvas.endPadding, width: this.getRadius() } }, _createAxisElement: xyAxesLinear._createAxisElement, _updateAxisElementPosition: function() { const centerCoord = this.getCenter(); this._axisElement.attr({ points: [centerCoord.x, centerCoord.y, centerCoord.x + this.getRadius(), centerCoord.y] }).rotate(this.getAngles()[0] - 90, centerCoord.x, centerCoord.y) }, _getScreenDelta: function() { return this.getRadius() }, _getTickMarkPoints: function(coords, length) { return [coords.x - length / 2, coords.y, coords.x + length / 2, coords.y] }, _getLabelAdjustedCoord: function(tick) { const labelCoords = tick.labelCoords; const labelY = labelCoords.y; const cosSin = (0, _utils.getCosAndSin)(labelCoords.angle); const indentFromAxis = this._options.label.indentFromAxis || 0; const box = tick.labelBBox; const x = labelCoords.x - abs(indentFromAxis * cosSin.sin) + abs(box.width / 2 * cosSin.cos) - box.width / 2; const y = labelY + (labelY - box.y) - abs(box.height / 2 * cosSin.sin) + abs(indentFromAxis * cosSin.cos); return { x: x, y: y } }, _getGridLineDrawer: function() { const that = this; return function(tick, gridStyle) { const grid = that._getGridPoints(tick.coords); return that._renderer.circle(grid.cx, grid.cy, grid.r).attr(gridStyle).sharp() } }, _getGridPoints: function(coords) { const pos = this.getCenter(); const radius = (0, _utils.getDistance)(pos.x, pos.y, coords.x, coords.y); if (radius > this.getRadius()) { return { cx: null, cy: null, r: null } } return { cx: pos.x, cy: pos.y, r: radius } }, _getTranslatedValue: function(value, offset) { const startAngle = this.getAngles()[0]; const xy = (0, _utils.convertPolarToXY)(this.getCenter(), startAngle, 0, this._translator.translate(value, offset)); return { x: xy.x, y: xy.y, angle: startAngle - 90 } }, _getTranslatedCoord: function(value, offset) { return this._translator.translate(value, offset) }, _getCanvasStartEnd() { const invert = this.getTranslator().getBusinessRange().invert; const coords = [0, this.getRadius()]; invert && coords.reverse(); return { start: coords[0], end: coords[1] } }, _getStripGraphicAttributes: function(fromPoint, toPoint) { const center = this.getCenter(); return { x: center.x, y: center.y, innerRadius: fromPoint, outerRadius: toPoint } }, _createStrip: function(attrs) { return this._renderer.arc(attrs.x, attrs.y, attrs.innerRadius, attrs.outerRadius, 0, 360) }, _getAdjustedStripLabelCoords: circularAxes._getAdjustedStripLabelCoords, _getStripLabelCoords: function(from, to) { const labelPos = from + (to - from) / 2; const center = this.getCenter(); const y = round(center.y - labelPos); return { x: center.x, y: y, align: _axes_constants.default.center } }, _getConstantLineGraphicAttributes: function(value) { const center = this.getCenter(); return { cx: center.x, cy: center.y, r: value } }, _createConstantLine: function(value, attr) { const attrs = this._getConstantLineGraphicAttributes(value); return this._renderer.circle(attrs.cx, attrs.cy, attrs.r).attr(attr).sharp() }, _getConstantLineLabelsCoords: function(value) { const center = this.getCenter(); const y = round(center.y - value); return { x: center.x, y: y } }, _checkAlignmentConstantLineLabels: _common.noop, _rotateTick: function(element, coords, isGridLine) { !isGridLine && element.rotate(coords.angle + 90, coords.x, coords.y) }, _validateOverlappingMode: circularAxes._validateOverlappingMode, _validateDisplayMode: circularAxes._validateDisplayMode, _getStep: function(boxes) { const quarter = getPolarQuarter(this.getAngles()[0]); const spacing = this._options.label.minSpacing; const func = 2 === quarter || 4 === quarter ? function(box) { return box.width + spacing } : function(box) { return box.height }; const maxLabelLength = boxes.reduce(((prevValue, box) => _max(prevValue, func(box))), 0); return _axes_constants.default.getTicksCountInRange(this._majorTicks, 2 === quarter || 4 === quarter ? "x" : "y", maxLabelLength) } }; const linearSpider = exports.linearSpider = (0, _extend.extend)({}, linear, { _createPathElement: function(points, attr) { return this._renderer.path(points, "area").attr(attr).sharp() }, setSpiderTicks: function(ticks) { this._spiderTicks = ticks }, _getGridLineDrawer: function() { const that = this; return function(tick, gridStyle) { return that._createPathElement(that._getGridPoints(tick.coords).points, gridStyle) } }, _getGridPoints: function(coords) { const pos = this.getCenter(); const radius = (0, _utils.getDistance)(pos.x, pos.y, coords.x, coords.y); return this._getGridPointsByRadius(radius) }, _getGridPointsByRadius: function(radius) { const pos = this.getCenter(); if (radius > this.getRadius()) { return { points: null } } return { points: (0, _utils.map)(this._spiderTicks, (function(tick) { const cosSin = (0, _utils.getCosAndSin)(tick.coords.angle); return { x: round(pos.x + radius * cosSin.cos), y: round(pos.y + radius * cosSin.sin) } })) } }, _getStripGraphicAttributes: function(fromPoint, toPoint) { const innerPoints = this._getGridPointsByRadius(toPoint).points; const outerPoints = this._getGridPointsByRadius(fromPoint).points; return { points: [outerPoints, innerPoints.reverse()] } }, _createStrip: circularSpider._createStrip, _getConstantLineGraphicAttributes: function(value) { return this._getGridPointsByRadius(value) }, _createConstantLine: function(value, attr) { return this._createPathElement(this._getConstantLineGraphicAttributes(value).points, attr) } });