/**
 * DevExtreme (cjs/viz/axes/polar_axes.js)
 * Version: 24.1.7-build-24272-1937
 * Build date: Sat Sep 28 2024
 *
 * Copyright (c) 2012 - 2024 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(obj) {
    return obj && obj.__esModule ? obj : {
        default: obj
    }
}
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)
    }
});
