UNPKG

devextreme

Version:

JavaScript/TypeScript Component Suite for Responsive Web Development

1,364 lines 61.5 kB
/**
 * DevExtreme (esm/__internal/viz/axes/xy_axes.js)
 * Version: 26.1.4
 * Build date: Fri Jul 24 2026
 *
 * Copyright (c) 2012 - 2026 Developer Express Inc. ALL RIGHTS RESERVED
 * Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
 */
import {
    noop
} from "../../../core/utils/common";
import dateUtils from "../../../core/utils/date";
import {
    extend
} from "../../../core/utils/extend";
import {
    isDefined
} from "../../../core/utils/type";
import formatHelper from "../../../format_helper";
import constants from "../../viz/axes/axes_constants";
import {
    generateDateBreaks
} from "../../viz/axes/datetime_breaks";
import {
    getCosAndSin,
    getLog,
    patchFontOptions
} from "../../viz/core/utils";
import {
    Range
} from "../../viz/translators/range";
const getNextDateUnit = dateUtils.getNextDateUnit;
const correctDateWithUnitBeginning = dateUtils.correctDateWithUnitBeginning;
const _math = Math;
const _max = _math.max;
const TOP = constants.top;
const BOTTOM = constants.bottom;
const LEFT = constants.left;
const RIGHT = constants.right;
const CENTER = constants.center;
const SCALE_BREAK_OFFSET = 3;
const RANGE_RATIO = .3;
const WAVED_LINE_CENTER = 2;
const WAVED_LINE_TOP = 0;
const WAVED_LINE_BOTTOM = 4;
const WAVED_LINE_LENGTH = 24;
const TICKS_CORRECTIONS = {
    left: -1,
    top: -1,
    right: 0,
    bottom: 0,
    center: -.5
};

function prepareDatesDifferences(datesDifferences, tickInterval) {
    let dateUnitInterval;
    let i;
    if ("week" === tickInterval) {
        tickInterval = "day"
    }
    if ("quarter" === tickInterval) {
        tickInterval = "month"
    }
    if (datesDifferences[tickInterval]) {
        for (i = 0; i < dateUtils.dateUnitIntervals.length; i++) {
            dateUnitInterval = dateUtils.dateUnitIntervals[i];
            if (datesDifferences[dateUnitInterval]) {
                datesDifferences[dateUnitInterval] = false;
                datesDifferences.count--
            }
            if (dateUnitInterval === tickInterval) {
                break
            }
        }
    }
}

function sortingBreaks(breaks) {
    return breaks.sort((a, b) => a.from - b.from)
}

function getMarkerDates(min, max, markerInterval) {
    const origMin = min;
    let dates;
    min = correctDateWithUnitBeginning(min, markerInterval);
    max = correctDateWithUnitBeginning(max, markerInterval);
    dates = dateUtils.getSequenceByInterval(min, max, markerInterval);
    if (dates.length && origMin > dates[0]) {
        dates = dates.slice(1)
    }
    return dates
}

function getStripHorizontalAlignmentPosition(alignment) {
    let position = "start";
    if ("center" === alignment) {
        position = "center"
    }
    if ("right" === alignment) {
        position = "end"
    }
    return position
}

function getStripVerticalAlignmentPosition(alignment) {
    let position = "start";
    if ("center" === alignment) {
        position = "center"
    }
    if ("bottom" === alignment) {
        position = "end"
    }
    return position
}

function getMarkerInterval(tickInterval) {
    let markerInterval = getNextDateUnit(tickInterval);
    if ("quarter" === markerInterval) {
        markerInterval = getNextDateUnit(markerInterval)
    }
    return markerInterval
}

function getMarkerFormat(curDate, prevDate, tickInterval, markerInterval) {
    let format = markerInterval;
    const datesDifferences = prevDate && dateUtils.getDatesDifferences(prevDate, curDate);
    if (prevDate && "year" !== tickInterval) {
        prepareDatesDifferences(datesDifferences, tickInterval);
        format = formatHelper.getDateFormatByDifferences(datesDifferences)
    }
    return format
}

function getMaxSide(act, boxes) {
    return boxes.reduce((prevValue, box) => _max(prevValue, act(box)), 0)
}

function getDistanceByAngle(bBox, rotationAngle) {
    rotationAngle = _math.abs(rotationAngle);
    rotationAngle = rotationAngle % 180 >= 90 ? 90 - rotationAngle % 90 : rotationAngle % 90;
    const a = rotationAngle * (_math.PI / 180);
    if (a >= _math.atan(bBox.height / bBox.width)) {
        return bBox.height / _math.abs(_math.sin(a))
    } else {
        return bBox.width
    }
}

function getMaxConstantLinePadding(constantLines) {
    return constantLines.reduce((padding, options) => _max(padding, options.paddingTopBottom), 0)
}

function getConstantLineLabelMarginForVerticalAlignment(constantLines, alignment, labelHeight) {
    return constantLines.some(options => options.label.verticalAlignment === alignment) && labelHeight || 0
}

function getLeftMargin(bBox) {
    return _math.abs(bBox.x) || 0
}

function getRightMargin(bBox) {
    return _math.abs(bBox.width - _math.abs(bBox.x)) || 0
}

function generateRangesOnPoints(points, edgePoints, getRange) {
    let i;
    let length;
    let maxRange = null;
    const ranges = [];
    let curValue;
    let prevValue;
    let curRange;
    for (i = 1, length = points.length; i < length; i++) {
        curValue = points[i];
        prevValue = points[i - 1];
        curRange = getRange(curValue, prevValue);
        if (edgePoints.indexOf(curValue) >= 0) {
            if (!maxRange || curRange > maxRange.length) {
                maxRange = {
                    start: curValue,
                    end: prevValue,
                    length: curRange
                }
            }
        } else {
            if (maxRange && curRange < maxRange.length) {
                ranges.push(maxRange)
            } else {
                ranges.push({
                    start: curValue,
                    end: prevValue,
                    length: curRange
                })
            }
            maxRange = null
        }
    }
    if (maxRange) {
        ranges.push(maxRange)
    }
    return ranges
}

function generateAutoBreaks(_ref, series, _ref2) {
    let {
        logarithmBase: logarithmBase,
        type: type,
        maxAutoBreakCount: maxAutoBreakCount
    } = _ref;
    let {
        minVisible: minVisible,
        maxVisible: maxVisible
    } = _ref2;
    const breaks = [];
    const getRange = "logarithmic" === type ? (min, max) => getLog(max / min, logarithmBase) : (min, max) => max - min;
    let visibleRange = getRange(minVisible, maxVisible);
    const points = series.reduce((result, s) => {
        const points = s.getPointsInViewPort();
        result[0] = result[0].concat(points[0]);
        result[1] = result[1].concat(points[1]);
        return result
    }, [
        [],
        []
    ]);
    const sortedAllPoints = points[0].concat(points[1]).sort((a, b) => b - a);
    const edgePoints = points[1].filter(p => points[0].indexOf(p) < 0);
    let minDiff = .3 * visibleRange;
    const ranges = generateRangesOnPoints(sortedAllPoints, edgePoints, getRange).filter(_ref3 => {
        let {
            length: length
        } = _ref3;
        return !!length
    }).sort((a, b) => b.length - a.length);
    const epsilon = _math.min.apply(null, ranges.map(r => r.length)) / 1e3;
    const _maxAutoBreakCount = isDefined(maxAutoBreakCount) ? _math.min(maxAutoBreakCount, ranges.length) : ranges.length;
    for (let i = 0; i < _maxAutoBreakCount; i++) {
        if (ranges[i].length >= minDiff) {
            if (visibleRange <= ranges[i].length) {
                break
            }
            visibleRange -= ranges[i].length;
            if (visibleRange > epsilon || visibleRange < -epsilon) {
                breaks.push({
                    from: ranges[i].start,
                    to: ranges[i].end
                });
                minDiff = .3 * visibleRange
            }
        } else {
            break
        }
    }
    sortingBreaks(breaks);
    return breaks
}
export default {
    linear: {
        _getStep(boxes, rotationAngle) {
            const spacing = this._options.label.minSpacing;
            const func = this._isHorizontal ? function(box) {
                return box.width + spacing
            } : function(box) {
                return box.height
            };
            let maxLabelLength = getMaxSide(func, boxes);
            if (rotationAngle) {
                maxLabelLength = getDistanceByAngle({
                    width: maxLabelLength,
                    height: this._getMaxLabelHeight(boxes, 0)
                }, rotationAngle)
            }
            return constants.getTicksCountInRange(this._majorTicks, this._isHorizontal ? "x" : "y", maxLabelLength)
        },
        _getMaxLabelHeight: (boxes, spacing) => getMaxSide(box => box.height, boxes) + spacing,
        _validateOverlappingMode(mode, displayMode) {
            if (this._isHorizontal && ("rotate" === displayMode || "stagger" === displayMode) || !this._isHorizontal) {
                return constants.validateOverlappingMode(mode)
            }
            return mode
        },
        _validateDisplayMode(mode) {
            return this._isHorizontal ? mode : "standard"
        },
        getMarkerTrackers() {
            return this._markerTrackers
        },
        _getSharpParam(opposite) {
            return this._isHorizontal ^ opposite ? "h" : "v"
        },
        _createAxisElement() {
            return this._renderer.path([], "line")
        },
        _updateAxisElementPosition() {
            const axisCoord = this._axisPosition;
            const canvas = this._getCanvasStartEnd();
            this._axisElement.attr({
                points: this._isHorizontal ? [canvas.start, axisCoord, canvas.end, axisCoord] : [axisCoord, canvas.start, axisCoord, canvas.end]
            })
        },
        _getTranslatedCoord(value, offset) {
            return this._translator.translate(value, offset)
        },
        _initAxisPositions() {
            const that = this;
            if (that.customPositionIsAvailable()) {
                that._customBoundaryPosition = that.getCustomBoundaryPosition()
            }
            if (!that.customPositionIsAvailable() || that.customPositionIsBoundary()) {
                that._axisPosition = that.getPredefinedPosition(that.getResolvedBoundaryPosition())
            } else {
                that._axisPosition = that.getCustomPosition()
            }
        },
        _getTickMarkPoints(coords, length, tickOptions) {
            const isHorizontal = this._isHorizontal;
            const tickOrientation = this._options.tickOrientation;
            const labelPosition = this._options.label.position;
            let tickStartCoord;
            if (isDefined(tickOrientation)) {
                tickStartCoord = TICKS_CORRECTIONS[tickOrientation] * length
            } else {
                let shift = tickOptions.shift || 0;
                if (!isHorizontal && labelPosition === LEFT || isHorizontal && labelPosition !== BOTTOM) {
                    shift = -shift
                }
                tickStartCoord = shift + this.getTickStartPositionShift(length)
            }
            return [coords.x + (isHorizontal ? 0 : tickStartCoord), coords.y + (isHorizontal ? tickStartCoord : 0), coords.x + (isHorizontal ? 0 : tickStartCoord + length), coords.y + (isHorizontal ? tickStartCoord + length : 0)]
        },
        getTickStartPositionShift(length) {
            const width = this._options.width;
            const position = this.getResolvedBoundaryPosition();
            return length % 2 === 1 ? width % 2 === 0 && (position === LEFT || position === TOP) || width % 2 === 1 && (position === RIGHT || position === BOTTOM) && !this.hasNonBoundaryPosition() ? Math.floor(-length / 2) : -Math.floor(length / 2) : -length / 2 + (width % 2 === 0 ? 0 : position === BOTTOM || position === RIGHT ? -1 : 1)
        },
        _getTitleCoords() {
            const horizontal = this._isHorizontal;
            let x = this._axisPosition;
            let y = this._axisPosition;
            const align = this._options.title.alignment;
            const canvas = this._getCanvasStartEnd();
            const fromStartToEnd = horizontal || this._options.position === LEFT;
            const canvasStart = fromStartToEnd ? canvas.start : canvas.end;
            const canvasEnd = fromStartToEnd ? canvas.end : canvas.start;
            const coord = align === LEFT ? canvasStart : align === RIGHT ? canvasEnd : canvas.start + (canvas.end - canvas.start) / 2;
            if (horizontal) {
                x = coord
            } else {
                y = coord
            }
            return {
                x: x,
                y: y
            }
        },
        _drawTitleText(group, coords) {
            const options = this._options;
            const titleOptions = options.title;
            const attrs = {
                opacity: titleOptions.opacity,
                align: titleOptions.alignment,
                class: titleOptions.cssClass
            };
            if (!titleOptions.text || !group) {
                return
            }
            coords = coords || this._getTitleCoords();
            if (!this._isHorizontal) {
                attrs.rotate = options.position === LEFT ? 270 : 90
            }
            const text = this._renderer.text(titleOptions.text, coords.x, coords.y).css(patchFontOptions(titleOptions.font)).attr(attrs).append(group);
            this._checkTitleOverflow(text);
            return text
        },
        _updateTitleCoords() {
            this._title && this._title.element.attr(this._getTitleCoords())
        },
        _drawTitle() {
            const title = this._drawTitleText(this._axisTitleGroup);
            if (title) {
                this._title = {
                    element: title
                }
            }
        },
        _measureTitle() {
            if (this._title) {
                if (this._title.bBox && !this._title.originalSize) {
                    this._title.originalSize = this._title.bBox
                }
                this._title.bBox = this._title.element.getBBox()
            }
        },
        _drawDateMarker(date, options, range) {
            const that = this;
            const markerOptions = that._options.marker;
            const invert = that._translator.getBusinessRange().invert;
            const textIndent = markerOptions.width + markerOptions.textLeftIndent;
            let pathElement;
            if (null === options.x) {
                return
            }
            if (!options.withoutStick) {
                pathElement = that._renderer.path([options.x, options.y, options.x, options.y + markerOptions.separatorHeight], "line").attr({
                    "stroke-width": markerOptions.width,
                    stroke: markerOptions.color,
                    "stroke-opacity": markerOptions.opacity,
                    sharp: "h"
                }).append(that._axisElementsGroup)
            }
            const text = String(that.formatLabel(date, options.labelOptions, range));
            return {
                date: date,
                x: options.x,
                y: options.y,
                cropped: options.withoutStick,
                label: that._renderer.text(text, options.x, options.y).css(patchFontOptions(markerOptions.label.font)).append(that._axisElementsGroup),
                line: pathElement,
                getContentContainer() {
                    return this.label
                },
                getEnd() {
                    return this.x + (invert ? -1 : 1) * (textIndent + this.labelBBox.width)
                },
                setTitle() {
                    this.title = text
                },
                hideLabel() {
                    this.label.dispose();
                    this.label = null;
                    this.title = text
                },
                hide() {
                    if (pathElement) {
                        pathElement.dispose();
                        pathElement = null
                    }
                    this.label.dispose();
                    this.label = null;
                    this.hidden = true
                }
            }
        },
        _drawDateMarkers() {
            const that = this;
            const options = that._options;
            const translator = that._translator;
            const viewport = that._getViewportRange();
            const minBound = viewport.minVisible;
            let dateMarkers = [];
            let dateMarker;

            function draw(markerDate, format, withoutStick) {
                return that._drawDateMarker(markerDate, {
                    x: translator.translate(markerDate),
                    y: markersAreaTop,
                    labelOptions: that._getLabelFormatOptions(format),
                    withoutStick: withoutStick
                }, viewport)
            }
            if (viewport.isEmpty() || !options.marker.visible || "datetime" !== options.argumentType || "discrete" === options.type || that._majorTicks.length <= 1) {
                return []
            }
            const markersAreaTop = that._axisPosition + options.marker.topIndent;
            const tickInterval = dateUtils.getDateUnitInterval(this._tickInterval);
            const markerInterval = getMarkerInterval(tickInterval);
            const markerDates = getMarkerDates(minBound, viewport.maxVisible, markerInterval);
            if (markerDates.length > 1 || 1 === markerDates.length && minBound < markerDates[0]) {
                dateMarkers = markerDates.reduce((markers, curDate, i, dates) => {
                    const marker = draw(curDate, getMarkerFormat(curDate, dates[i - 1] || minBound < curDate && minBound, tickInterval, markerInterval));
                    marker && markers.push(marker);
                    return markers
                }, []);
                if (minBound < markerDates[0]) {
                    dateMarker = draw(minBound, getMarkerFormat(minBound, markerDates[0], tickInterval, markerInterval), true);
                    dateMarker && dateMarkers.unshift(dateMarker)
                }
            }
            return dateMarkers
        },
        _adjustDateMarkers(offset) {
            offset = offset || 0;
            const that = this;
            const markerOptions = this._options.marker;
            const textIndent = markerOptions.width + markerOptions.textLeftIndent;
            const invert = this._translator.getBusinessRange().invert;
            const canvas = that._getCanvasStartEnd();
            const dateMarkers = this._dateMarkers;
            if (!dateMarkers.length) {
                return offset
            }
            if (dateMarkers[0].cropped) {
                if (!this._checkMarkersPosition(invert, dateMarkers[1], dateMarkers[0])) {
                    dateMarkers[0].hideLabel()
                }
            }
            let prevDateMarker;
            dateMarkers.forEach((marker, i, markers) => {
                if (marker.cropped) {
                    return
                }
                if (invert ? marker.getEnd() < canvas.end : marker.getEnd() > canvas.end) {
                    marker.hideLabel()
                } else if (that._checkMarkersPosition(invert, marker, prevDateMarker)) {
                    prevDateMarker = marker
                } else {
                    marker.hide()
                }
            });
            this._dateMarkers.forEach(marker => {
                if (marker.label) {
                    const labelBBox = marker.labelBBox;
                    const dy = marker.y + markerOptions.textTopIndent - labelBBox.y;
                    marker.label.attr({
                        translateX: invert ? marker.x - textIndent - labelBBox.x - labelBBox.width : marker.x + textIndent - labelBBox.x,
                        translateY: dy + offset
                    })
                }
                if (marker.line) {
                    marker.line.attr({
                        translateY: offset
                    })
                }
            });
            that._initializeMarkersTrackers(offset);
            return offset + markerOptions.topIndent + markerOptions.separatorHeight
        },
        _checkMarkersPosition(invert, dateMarker, prevDateMarker) {
            if (void 0 === prevDateMarker) {
                return true
            }
            return invert ? dateMarker.x < prevDateMarker.getEnd() : dateMarker.x > prevDateMarker.getEnd()
        },
        _initializeMarkersTrackers(offset) {
            const separatorHeight = this._options.marker.separatorHeight;
            const renderer = this._renderer;
            const businessRange = this._translator.getBusinessRange();
            const canvas = this._getCanvasStartEnd();
            const group = this._axisElementsGroup;
            this._markerTrackers = this._dateMarkers.filter(marker => !marker.hidden).map((marker, i, markers) => {
                const nextMarker = markers[i + 1] || {
                    x: canvas.end,
                    date: businessRange.max
                };
                const x = marker.x;
                const y = marker.y + offset;
                const markerTracker = renderer.path([x, y, x, y + separatorHeight, nextMarker.x, y + separatorHeight, nextMarker.x, y, x, y], "area").attr({
                    "stroke-width": 1,
                    stroke: "grey",
                    fill: "grey",
                    opacity: 1e-4
                }).append(group);
                markerTracker.data("range", {
                    startValue: marker.date,
                    endValue: nextMarker.date
                });
                if (marker.title) {
                    markerTracker.setTitle(marker.title)
                }
                return markerTracker
            })
        },
        _getLabelFormatOptions(formatString) {
            const that = this;
            let markerLabelOptions = that._markerLabelOptions;
            if (!markerLabelOptions) {
                that._markerLabelOptions = markerLabelOptions = extend(true, {}, that._options.marker.label)
            }
            if (!isDefined(that._options.marker.label.format)) {
                markerLabelOptions.format = formatString
            }
            return markerLabelOptions
        },
        _adjustConstantLineLabels(constantLines) {
            const that = this;
            const axisPosition = that._options.position;
            const canvas = that.getCanvas();
            const canvasLeft = canvas.left;
            const canvasRight = canvas.width - canvas.right;
            const canvasTop = canvas.top;
            const canvasBottom = canvas.height - canvas.bottom;
            const verticalCenter = canvasTop + (canvasBottom - canvasTop) / 2;
            const horizontalCenter = canvasLeft + (canvasRight - canvasLeft) / 2;
            let maxLabel = 0;
            constantLines.forEach(item => {
                const isHorizontal = that._isHorizontal;
                const linesOptions = item.options;
                const paddingTopBottom = linesOptions.paddingTopBottom;
                const paddingLeftRight = linesOptions.paddingLeftRight;
                const labelOptions = linesOptions.label;
                const labelVerticalAlignment = labelOptions.verticalAlignment;
                const labelHorizontalAlignment = labelOptions.horizontalAlignment;
                const labelIsInside = "inside" === labelOptions.position;
                const label = item.label;
                const box = item.labelBBox;
                let translateX;
                let translateY;
                if (null === label || box.isEmpty) {
                    return
                }
                if (isHorizontal) {
                    if (labelIsInside) {
                        if (labelHorizontalAlignment === LEFT) {
                            translateX = item.coord - paddingLeftRight - box.x - box.width
                        } else {
                            translateX = item.coord + paddingLeftRight - box.x
                        }
                        switch (labelVerticalAlignment) {
                            case CENTER:
                                translateY = verticalCenter - box.y - box.height / 2;
                                break;
                            case BOTTOM:
                                translateY = canvasBottom - paddingTopBottom - box.y - box.height;
                                break;
                            default:
                                translateY = canvasTop + paddingTopBottom - box.y
                        }
                    } else {
                        if (axisPosition === labelVerticalAlignment) {
                            maxLabel = _max(maxLabel, box.height + paddingTopBottom)
                        }
                        translateX = item.coord - box.x - box.width / 2;
                        if (labelVerticalAlignment === BOTTOM) {
                            translateY = canvasBottom + paddingTopBottom - box.y
                        } else {
                            translateY = canvasTop - paddingTopBottom - box.y - box.height
                        }
                    }
                } else if (labelIsInside) {
                    if (labelVerticalAlignment === BOTTOM) {
                        translateY = item.coord + paddingTopBottom - box.y
                    } else {
                        translateY = item.coord - paddingTopBottom - box.y - box.height
                    }
                    switch (labelHorizontalAlignment) {
                        case CENTER:
                            translateX = horizontalCenter - box.x - box.width / 2;
                            break;
                        case RIGHT:
                            translateX = canvasRight - paddingLeftRight - box.x - box.width;
                            break;
                        default:
                            translateX = canvasLeft + paddingLeftRight - box.x
                    }
                } else {
                    if (axisPosition === labelHorizontalAlignment) {
                        maxLabel = _max(maxLabel, box.width + paddingLeftRight)
                    }
                    translateY = item.coord - box.y - box.height / 2;
                    if (labelHorizontalAlignment === RIGHT) {
                        translateX = canvasRight + paddingLeftRight - box.x
                    } else {
                        translateX = canvasLeft - paddingLeftRight - box.x - box.width
                    }
                }
                label.attr({
                    translateX: translateX,
                    translateY: translateY
                })
            });
            return maxLabel
        },
        _drawConstantLinesForEstimating(constantLines) {
            const that = this;
            const renderer = this._renderer;
            const group = renderer.g();
            constantLines.forEach(options => {
                that._drawConstantLineLabelText(options.label.text, 0, 0, options.label, group).attr({
                    align: "center"
                })
            });
            return group.append(renderer.root)
        },
        _estimateLabelHeight(bBox, labelOptions) {
            let height = bBox.height;
            const drawingType = labelOptions.drawingType;
            if ("stagger" === this._validateDisplayMode(drawingType) || "stagger" === this._validateOverlappingMode(labelOptions.overlappingBehavior, drawingType)) {
                height = 2 * height + labelOptions.staggeringSpacing
            }
            if ("rotate" === this._validateDisplayMode(drawingType) || "rotate" === this._validateOverlappingMode(labelOptions.overlappingBehavior, drawingType)) {
                const sinCos = getCosAndSin(labelOptions.rotationAngle);
                height = height * sinCos.cos + bBox.width * sinCos.sin
            }
            return height && (height + labelOptions.indentFromAxis || 0) || 0
        },
        estimateMargins(canvas) {
            this.updateCanvas(canvas);
            const {
                position: position,
                placeholderSize: placeholderSize
            } = this._options;
            const range = this._getViewportRange();
            const ticksData = this._createTicksAndLabelFormat(range);
            const ticks = ticksData.ticks;
            const tickInterval = ticksData.tickInterval;
            const options = this._options;
            const constantLineOptions = this._outsideConstantLines.filter(l => l.labelOptions.visible).map(l => l.options);
            const rootElement = this._renderer.root;
            const labelIsVisible = options.label.visible && !range.isEmpty() && ticks.length;
            const labelValue = labelIsVisible && this.formatLabel(ticks[ticks.length - 1], options.label, void 0, void 0, tickInterval, ticks);
            const labelElement = labelIsVisible && this._renderer.text(labelValue, 0, 0).css(this._textFontStyles).attr(this._textOptions).append(rootElement);
            const titleElement = this._drawTitleText(rootElement, {
                x: 0,
                y: 0
            });
            const constantLinesLabelsElement = this._drawConstantLinesForEstimating(constantLineOptions);
            const labelBox = !options.label.template && labelElement && labelElement.getBBox() || {
                x: 0,
                y: 0,
                width: 0,
                height: 0
            };
            const titleBox = titleElement && titleElement.getBBox() || {
                x: 0,
                y: 0,
                width: 0,
                height: 0
            };
            const constantLinesBox = constantLinesLabelsElement.getBBox();
            const titleHeight = titleBox.height ? titleBox.height + options.title.margin : 0;
            const labelHeight = this._estimateLabelHeight(labelBox, options.label);
            const constantLinesHeight = constantLinesBox.height ? constantLinesBox.height + getMaxConstantLinePadding(constantLineOptions) : 0;
            const height = labelHeight + titleHeight;
            const margins = {
                left: _max(getLeftMargin(labelBox), getLeftMargin(constantLinesBox)),
                right: _max(getRightMargin(labelBox), getRightMargin(constantLinesBox)),
                top: ("top" === options.position ? height : 0) + getConstantLineLabelMarginForVerticalAlignment(constantLineOptions, "top", constantLinesHeight),
                bottom: ("top" !== options.position ? height : 0) + getConstantLineLabelMarginForVerticalAlignment(constantLineOptions, "bottom", constantLinesHeight)
            };
            if (placeholderSize) {
                margins[position] = placeholderSize
            }
            labelElement && labelElement.remove();
            titleElement && titleElement.remove();
            constantLinesLabelsElement && constantLinesLabelsElement.remove();
            return margins
        },
        _checkAlignmentConstantLineLabels(labelOptions) {
            const position = labelOptions.position;
            let verticalAlignment = (labelOptions.verticalAlignment || "").toLowerCase();
            let horizontalAlignment = (labelOptions.horizontalAlignment || "").toLowerCase();
            if (this._isHorizontal) {
                if ("outside" === position) {
                    verticalAlignment = verticalAlignment === BOTTOM ? BOTTOM : TOP;
                    horizontalAlignment = CENTER
                } else {
                    verticalAlignment = verticalAlignment === CENTER ? CENTER : verticalAlignment === BOTTOM ? BOTTOM : TOP;
                    horizontalAlignment = horizontalAlignment === LEFT ? LEFT : RIGHT
                }
            } else if ("outside" === position) {
                verticalAlignment = CENTER;
                horizontalAlignment = horizontalAlignment === LEFT ? LEFT : RIGHT
            } else {
                verticalAlignment = verticalAlignment === BOTTOM ? BOTTOM : TOP;
                horizontalAlignment = horizontalAlignment === RIGHT ? RIGHT : horizontalAlignment === CENTER ? CENTER : LEFT
            }
            labelOptions.verticalAlignment = verticalAlignment;
            labelOptions.horizontalAlignment = horizontalAlignment
        },
        _getConstantLineLabelsCoords(value, lineLabelOptions) {
            const that = this;
            let x = value;
            let y = value;
            if (that._isHorizontal) {
                y = that._orthogonalPositions["top" === lineLabelOptions.verticalAlignment ? "start" : "end"]
            } else {
                x = that._orthogonalPositions["right" === lineLabelOptions.horizontalAlignment ? "end" : "start"]
            }
            return {
                x: x,
                y: y
            }
        },
        _getAdjustedStripLabelCoords(strip) {
            const stripOptions = strip.options;
            const paddingTopBottom = stripOptions.paddingTopBottom;
            const paddingLeftRight = stripOptions.paddingLeftRight;
            const horizontalAlignment = stripOptions.label.horizontalAlignment;
            const verticalAlignment = stripOptions.label.verticalAlignment;
            const box = strip.labelBBox;
            const labelHeight = box.height;
            const labelWidth = box.width;
            const labelCoords = strip.labelCoords;
            let y = labelCoords.y - box.y;
            let x = labelCoords.x - box.x;
            if (verticalAlignment === TOP) {
                y += paddingTopBottom
            } else if (verticalAlignment === CENTER) {
                y -= labelHeight / 2
            } else if (verticalAlignment === BOTTOM) {
                y -= paddingTopBottom + labelHeight
            }
            if (horizontalAlignment === LEFT) {
                x += paddingLeftRight
            } else if (horizontalAlignment === CENTER) {
                x -= labelWidth / 2
            } else if (horizontalAlignment === RIGHT) {
                x -= paddingLeftRight + labelWidth
            }
            return {
                translateX: x,
                translateY: y
            }
        },
        _adjustTitle(offset) {
            offset = offset || 0;
            if (!this._title) {
                return
            }
            const options = this._options;
            const position = options.position;
            const margin = options.title.margin;
            const title = this._title;
            const boxTitle = title.bBox;
            const x = boxTitle.x;
            const y = boxTitle.y;
            const width = boxTitle.width;
            const height = boxTitle.height;
            const axisPosition = this._axisPosition;
            const loCoord = axisPosition - margin - offset;
            const hiCoord = axisPosition + margin + offset;
            const params = {};
            if (this._isHorizontal) {
                if (position === TOP) {
                    params.translateY = loCoord - (y + height)
                } else {
                    params.translateY = hiCoord - y
                }
            } else if (position === LEFT) {
                params.translateX = loCoord - (x + width)
            } else {
                params.translateX = hiCoord - x
            }
            title.element.attr(params)
        },
        _checkTitleOverflow(titleElement) {
            if (!this._title && !titleElement) {
                return
            }
            const canvasLength = this._getScreenDelta();
            const title = titleElement ? {
                bBox: titleElement.getBBox(),
                element: titleElement
            } : this._title;
            const titleOptions = this._options.title;
            const boxTitle = title.bBox;
            if ((this._isHorizontal ? boxTitle.width : boxTitle.height) > canvasLength) {
                title.element.setMaxSize(canvasLength, void 0, {
                    wordWrap: titleOptions.wordWrap || "none",
                    textOverflow: titleOptions.textOverflow || "ellipsis"
                });
                this._wrapped = titleOptions.wordWrap && "none" !== titleOptions.wordWrap
            } else {
                const moreThanOriginalSize = title.originalSize && canvasLength > (this._isHorizontal ? title.originalSize.width : title.originalSize.height);
                !this._wrapped && moreThanOriginalSize && title.element.restoreText()
            }
        },
        coordsIn(x, y) {
            const canvas = this.getCanvas();
            const isHorizontal = this._options.isHorizontal;
            const position = this._options.position;
            const coord = isHorizontal ? y : x;
            if (isHorizontal && (x < canvas.left || x > canvas.width - canvas.right) || !isHorizontal && (y < canvas.top || y > canvas.height - canvas.bottom)) {
                return false
            }
            if (isHorizontal && position === constants.top || !isHorizontal && position === constants.left) {
                return coord < canvas[position]
            }
            return coord > canvas[isHorizontal ? "height" : "width"] - canvas[position]
        },
        _boundaryTicksVisibility: {
            min: true,
            max: true
        },
        adjust() {
            const seriesData = this._seriesData;
            const viewport = this._series.filter(s => s.isVisible()).reduce((range, s) => {
                const seriesRange = s.getViewport();
                range.min = isDefined(seriesRange.min) ? range.min < seriesRange.min ? range.min : seriesRange.min : range.min;
                range.max = isDefined(seriesRange.max) ? range.max > seriesRange.max ? range.max : seriesRange.max : range.max;
                if (s.showZero) {
                    range = new Range(range);
                    range.correctValueZeroLevel()
                }
                return range
            }, {});
            if (isDefined(viewport.min) && isDefined(viewport.max)) {
                seriesData.minVisible = viewport.min;
                seriesData.maxVisible = viewport.max
            }
            seriesData.userBreaks = this._getScaleBreaks(this._options, {
                minVisible: seriesData.minVisible,
                maxVisible: seriesData.maxVisible
            }, this._series, this.isArgumentAxis);
            this._translator.updateBusinessRange(this._getViewportRange())
        },
        hasWrap() {
            return this._wrapped
        },
        getAxisPosition() {
            return this._axisPosition
        },
        _getStick() {
            return !this._options.valueMarginsEnabled
        },
        _getStripLabelCoords(from, to, stripLabelOptions) {
            const orthogonalPositions = this._orthogonalPositions;
            const isHorizontal = this._isHorizontal;
            const horizontalAlignment = stripLabelOptions.horizontalAlignment;
            const verticalAlignment = stripLabelOptions.verticalAlignment;
            let x;
            let y;
            if (isHorizontal) {
                if (horizontalAlignment === CENTER) {
                    x = from + (to - from) / 2
                } else if (horizontalAlignment === LEFT) {
                    x = from
                } else if (horizontalAlignment === RIGHT) {
                    x = to
                }
                y = orthogonalPositions[getStripVerticalAlignmentPosition(verticalAlignment)]
            } else {
                x = orthogonalPositions[getStripHorizontalAlignmentPosition(horizontalAlignment)];
                if (verticalAlignment === TOP) {
                    y = from
                } else if (verticalAlignment === CENTER) {
                    y = to + (from - to) / 2
                } else if (verticalAlignment === BOTTOM) {
                    y = to
                }
            }
            return {
                x: x,
                y: y
            }
        },
        _getTranslatedValue(value, offset) {
            let interval;
            if ("semidiscrete" === this._options.type) {
                interval = this._options.tickInterval
            }
            const pos1 = this._translator.translate(value, offset, false, interval);
            const pos2 = this._axisPosition;
            const isHorizontal = this._isHorizontal;
            return {
                x: isHorizontal ? pos1 : pos2,
                y: isHorizontal ? pos2 : pos1
            }
        },
        areCoordsOutsideAxis(coords) {
            const coord = this._isHorizontal ? coords.x : coords.y;
            const visibleArea = this.getVisibleArea();
            if (coord < visibleArea[0] || coord > visibleArea[1]) {
                return true
            }
            return false
        },
        _getSkippedCategory(ticks) {
            let skippedCategory;
            if (this._options.type === constants.discrete && this._tickOffset && 0 !== ticks.length) {
                skippedCategory = ticks[ticks.length - 1]
            }
            return skippedCategory
        },
        _filterBreaks(breaks, viewport, breakStyle) {
            const minVisible = viewport.minVisible;
            const maxVisible = viewport.maxVisible;
            const breakSize = breakStyle ? breakStyle.width : 0;
            return breaks.reduce((result, currentBreak) => {
                let from = currentBreak.from;
                let to = currentBreak.to;
                const lastResult = result[result.length - 1];
                let newBreak;
                if (!isDefined(from) || !isDefined(to)) {
                    return result
                }
                if (from > to) {
                    to = [from, from = to][0]
                }
                if (result.length && from < lastResult.to) {
                    if (to > lastResult.to) {
                        lastResult.to = to > maxVisible ? maxVisible : to;
                        if (lastResult.gapSize) {
                            lastResult.gapSize = void 0;
                            lastResult.cumulativeWidth += breakSize
                        }
                    }
                } else if (from >= minVisible && from < maxVisible || to <= maxVisible && to > minVisible) {
                    from = from >= minVisible ? from : minVisible;
                    to = to <= maxVisible ? to : maxVisible;
                    if (to - from < maxVisible - minVisible) {
                        newBreak = {
                            from: from,
                            to: to,
                            cumulativeWidth: ((null === lastResult || void 0 === lastResult ? void 0 : lastResult.cumulativeWidth) ?? 0) + breakSize
                        };
                        if (currentBreak.gapSize) {
                            newBreak.gapSize = dateUtils.convertMillisecondsToDateUnits(to - from);
                            newBreak.cumulativeWidth = (null === lastResult || void 0 === lastResult ? void 0 : lastResult.cumulativeWidth) ?? 0
                        }
                        result.push(newBreak)
                    }
                }
                return result
            }, [])
        },
        _getScaleBreaks(axisOptions, viewport, series, isArgumentAxis) {
            const that = this;
            let breaks = (axisOptions.breaks || []).map(b => ({
                from: that.parser(b.startValue),
                to: that.parser(b.endValue)
            }));
            if ("discrete" !== axisOptions.type && "datetime" === axisOptions.dataType && axisOptions.workdaysOnly) {
                breaks = breaks.concat(generateDateBreaks(viewport.minVisible, viewport.maxVisible, axisOptions.workWeek, axisOptions.singleWorkdays, axisOptions.holidays))
            }
            if (!isArgumentAxis && "discrete" !== axisOptions.type && "datetime" !== axisOptions.dataType && axisOptions.autoBreaksEnabled && 0 !== axisOptions.maxAutoBreakCount) {
                breaks = breaks.concat(generateAutoBreaks(axisOptions, series, viewport))
            }
            return sortingBreaks(breaks)
        },
        _drawBreak(translatedEnd, positionFrom, positionTo, width, options, group) {
            const breakStart = translatedEnd - (!this._translator.isInverted() ? width + 1 : 0);
            const attr = {
                "stroke-width": 1,
                stroke: options.borderColor,
                sharp: !options.isWaved ? options.isHorizontal ? "h" : "v" : void 0
            };
            const spaceAttr = {
                stroke: options.color,
                "stroke-width": width
            };
            const getPoints = this._isHorizontal ? rotateLine : function(p) {
                return p
            };
            const drawer = getLineDrawer(this._renderer, group, getPoints, positionFrom, breakStart, positionTo, options.isWaved);
            drawer(width / 2, spaceAttr);
            drawer(0, attr);
            drawer(width, attr)
        },
        _createBreakClipRect(from, to) {
            const that = this;
            const canvas = that._canvas;
            const clipWidth = to - from;
            let clipRect;
            if (that._isHorizontal) {
                clipRect = that._renderer.clipRect(canvas.left, from, canvas.width, clipWidth)
            } else {
                clipRect = that._renderer.clipRect(from, canvas.top, clipWidth, canvas.height)
            }
            that._breaksElements = that._breaksElements || [];
            that._breaksElements.push(clipRect);
            return clipRect.id
        },
        _createBreaksGroup(clipFrom, clipTo) {
            const group = this._renderer.g().attr({
                class: `${this._axisCssPrefix}breaks`,
                "clip-path": this._createBreakClipRect(clipFrom, clipTo)
            }).append(this._scaleBreaksGroup);
            this._breaksElements = this._breaksElements || [];
            this._breaksElements.push(group);
            return group
        },
        _disposeBreaksGroup() {
            (this._breaksElements || []).forEach(clipRect => {
                clipRect.dispose()
            });
            this._breaksElements = null
        },
        drawScaleBreaks(customCanvas) {
            const that = this;
            const options = that._options;
            const breakStyle = options.breakStyle;
            const position = options.position;
            let positionFrom;
            let positionTo;
            const breaks = that._translator.getBusinessRange().breaks || [];
            let additionGroup;
            let additionBreakFrom;
            let additionBreakTo;
            that._disposeBreaksGroup();
            if (!(breaks && breaks.length)) {
                return
            }
            const breakOptions = {
                color: that._options.containerColor,
                borderColor: breakStyle.color,
                isHorizontal: that._isHorizontal,
                isWaved: "straight" !== breakStyle.line.toLowerCase()
            };
            if (customCanvas) {
                positionFrom = customCanvas.start;
                positionTo = customCanvas.end
            } else {
                positionFrom = that._orthogonalPositions.start - (options.visible && !that._axisShift && (position === LEFT || position === TOP) ? 3 : 0);
                positionTo = that._orthogonalPositions.end + (options.visible && (position === RIGHT || position === BOTTOM) ? 3 : 0)
            }
            const mainGroup = that._createBreaksGroup(positionFrom, positionTo);
            if (that._axisShift && options.visible) {
                additionBreakFrom = that._axisPosition - that._axisShift - 3;
                additionBreakTo = additionBreakFrom + 6;
                additionGroup = that._createBreaksGroup(additionBreakFrom, additionBreakTo)
            }
            breaks.forEach(br => {
                if (!br.gapSize) {
                    const breakCoord = that._getTranslatedCoord(br.to);
                    that._drawBreak(breakCoord, positionFrom, positionTo, breakStyle.width, breakOptions, mainGroup);
                    if (that._axisShift && options.visible) {
                        that._drawBreak(breakCoord, additionBreakFrom, additionBreakTo, breakStyle.width, breakOptions, additionGroup)
                    }
                }
            })
        },
        _getSpiderCategoryOption: noop,
        shift(margins) {
            const options = this._options;
            const isHorizontal = options.isHorizontal;
            const axesSpacing = this.getMultipleAxesSpacing();
            const constantLinesGroups = this._axisConstantLineGroups;

            function shiftGroup(side, group) {
                const attr = {
                    translateX: 0,
                    translateY: 0
                };
                const shift = margins[side] ? margins[side] + axesSpacing : 0;
                attr[isHorizontal ? "translateY" : "translateX"] = (side === LEFT || side === TOP ? -1 : 1) * shift;
                (group[side] || group).attr(attr);
                return shift
            }
            this._axisShift = shiftGroup(options.position, this._axisGroup);
            shiftGroup(options.position, this._axisElementsGroup);
            (isHorizontal ? [TOP, BOTTOM] : [LEFT, RIGHT]).forEach(side => {
                shiftGroup(side, constantLinesGroups.above);
                shiftGroup(side, constantLinesGroups.under)
            })
        },
        getCustomPosition(position) {
            const that = this;
            const orthogonalAxis = that.getOrthogonalAxis();
            const resolvedPosition = position ?? that.getResolvedPositionOption();
            const offset = that.getOptions().offset;
            const orthogonalTranslator = orthogonalAxis.getTranslator();
            const orthogonalAxisType = orthogonalAxis.getOptions().type;
            let validPosition = orthogonalAxis.validateUnit(resolvedPosition);
            let currentPosition;
            if ("discrete" === orthogonalAxisType && (!orthogonalTranslator._categories || orthogonalTranslator._categories.indexOf(validPosition) < 0)) {
                validPosition = void 0
            }
            if (that.positionIsBoundary(resolvedPosition)) {
                currentPosition = that.getPredefinedPosition(resolvedPosition)
            } else if (!isDefined(validPosition)) {
                currentPosition = that.getPredefinedPosition(that.getOptions().position)
            } else {
                currentPosition = orthogonalTranslator.to(validPosition, -1)
            }
            if (isFinite(currentPosition) && isFinite(offset)) {
                currentPosition += offset
            }
            return currentPosition
        },
        getCustomBoundaryPosition(position) {
            const that = this;
            const {
                customPosition: customPosition,
                offset: offset
            } = that.getOptions();
            const resolvedPosition = position ?? that.getResolvedPositionOption();
            const orthogonalAxis = that.getOrthogonalAxis();
            const orthogonalTranslator = orthogonalAxis.getTranslator();
            const visibleArea = orthogonalTranslator.getCanvasVisibleArea();
            if (!isDefined(orthogonalAxis._orthogonalPositions) || 0 === orthogonalTranslator.canvasLength) {
                return
            }
            const currentPosition = that.getCustomPosition(resolvedPosition);
            if (!isDefined(currentPosition)) {
                return that.getResolvedBoundaryPosition()
            } else if (isDefined(customPosition)) {
                if (currentPosition <= visibleArea.min) {
                    return that._isHorizontal ? TOP : LEFT
                } else if (currentPosition >= visibleArea.max) {
                    return that._isHorizontal ? BOTTOM : RIGHT
                }
            } else if (isDefined(offset)) {
                if (currentPosition <= that._orthogonalPositions.start) {
                    return that._isHorizontal ? TOP : LEFT
                } else if (currentPosition >= that._orthogonalPositions.end) {
                    return that._isHorizontal ? BOTTOM : RIGHT
                }
            }
            return currentPosition
        },
        getResolvedPositionOption() {
            const options = this.getOptions();
            return options.customPosition ?? options.position
        },
        customPositionIsAvailable() {
            const options = this.getOptions();
            return isDefined(this.getOrthogonalAxis()) && (isDefined(options.customPosition) || isFinite(options.offset))
        },
        hasNonBoundaryPosition() {
            return this.customPositionIsAvailable() && !this.customPositionIsBoundary()
        },
        getResolvedBoundaryPosition() {
            return this.customPositionIsBoundary() ? this._customBoundaryPosition : this.getOptions().position
        },
        customPositionEqualsToPredefined() {
            return this.customPositionIsBoundary() && this._customBoundaryPosition === this.getOptions().position
        },
        customPositionIsBoundary() {
            return this.positionIsBoundary(this._customBoundaryPosition)
        },
        positionIsBoundary: position => [TOP, LEFT, BOTTOM, RIGHT].includes(position),
        getPredefinedPosition(position) {
            var _this$_orthogonalPosi;
            return null === (_this$_orthogonalPosi = this._orthogonalPositions) || void 0 === _this$_orthogonalPosi ? void 0 : _this$_orthogonalPosi[position === TOP || position === LEFT ? "start" : "end"]
        },
        resolveOverlappingForCustomPositioning(oppositeAxes) {
            const that = this;
            if (!that.hasNonBoundaryPosition() && !that.customPositionIsBoundary() && !oppositeAxes.some(a => a.hasNonBoundaryPosition())) {
                return
            }
            const overlappingObj = {
                axes: [],
                ticks: []
            };
            oppositeAxes.filter(orthogonalAxis => orthogonalAxis.pane === that.pane).forEach(orthogonalAxis => {
                for (let i = 0; i < that._majorTicks.length; i++) {
                    const tick = that._majorTicks[i];
                    const label = tick.label;
                    if (label) {
                        if (!overlappingObj.axes.includes(orthogonalAxis) && that._detectElementsOverlapping(label, orthogonalAxis._axisElement)) {
                            overlappingObj.axes.push(orthogonalAxis);
                            that._shiftThroughOrthogonalAxisOverlappedTick(label, orthogonalAxis)
                        }
                        for (let j = 0; j < orthogonalAxis._majorTicks.length; j++) {
                            const oppositeTick = orthogonalAxis._majorTicks[j];
                            const oppositeLabel = oppositeTick.label;
                            if (oppositeLabel && that._detectElementsOverlapping(label, oppositeLabel)) {
                                overlappingObj.ticks.push(tick);
                                that._shiftThroughAxisOverlappedTick(tick);
                                i = that._majorTicks.length;
                                break
                            }
                        }
                    }
                    if (tick.mark && !overlappingObj.ticks.includes(tick)) {
                        if (that._isHorizontal && tick.mark.attr("translateY")) {
                            tick.mark.attr({
                                translateY: 0
                            })
                        } else if (!that._isHorizontal && tick.mark.attr("translateX")) {
                            tick.mark.attr({
                                translateX: 0
                            })
                        }
                    }
                }
            })
        },
        _shiftThroughOrthogonalAxisOverlappedTick(label, orthogonalAxis) {
            const labelBBox = label.getBBox();
            const orthogonalAxisPosition = orthogonalAxis.getAxisPosition();
            const orthogonalAxisLabelOptions = orthogonalAxis.getOptions().label;
            const orthogonalAxisLabelPosition = orthogonalAxisLabelOptions.position;
            const orthogonalAxisLabelIndent = orthogonalAxisLabelOptions.indentFromAxis / 2;
            const translateCoordName = this._isHorizontal ? "translateX" : "translateY";
            const defaultOrthogonalAxisLabelPosition = this._isHorizontal ? LEFT : TOP;
            const translate = label.attr(translateCoordName);
            const labelCoord = (this._isHorizontal ? labelBBox.x : labelBBox.y) + translate;
            const labelSize = this._isHorizontal ? labelBBox.width : labelBBox.height;
            const outsidePart = orthogonalAxisPosition - labelCoord;
            const insidePart = labelCoord + labelSize - orthogonalAxisPosition;
            const attr = {};
            attr[translateCoordName] = translate;
            if (outsidePart > 0 && insidePart > 0) {
                if (insidePart - outsidePart > 1) {
                    attr[translateCoordName] += outsidePart + orthogonalAxisLabelIndent
                } else if (outsidePart - insidePart > 1) {
                    attr[translateCoordName] -= insidePart + orthogonalAxisLabelIndent
                } else {
                    attr[translateCoordName] += orthogonalAxisLabelPosition === defaultOrthogonalAxisLabelPosition ? outsidePart + orthogonalAxisLabelIndent : -(insidePart + orthogonalAxisLabelIndent)
                }
                label.attr(attr)
            }
        },
        _shiftThroughAxisOverlappedTick(tick) {
            var _tick$mark;
            const that = this;
            const label = tick.label;
            if (!label) {
                return
            }
            const labelBBox = label.getBBox();
            const tickMarkBBox = null === (_tick$mark = tick.mark) || void 0 === _tick$mark ? void 0 : _tick$mark.getBBox();
            const axisPosition = that.getAxisPosition();
            const labelOptions = that.getOptions().label;
            const labelIndent = labelOptions.indentFromAxis;
            const labelPosition = labelOptions.position;
            const defaultLabelPosition = that._isHorizontal ? TOP : LEFT;
            const translateCoordName = that._isHorizontal ? "translateY" : "translateX";
            const translate = label.attr(translateCoordName);
            const labelCoord = (that._isHorizontal ? labelBBox.y : labelBBox.x) + translate;
            const labelSize = that._isHorizontal ? labelBBox.height : labelBBox.width;
            const attr = {};
            attr[translateCoordName] = translate + (labelPosition === defaultLabelPosition ? axisPosition - labelCoord + labelIndent : -(labelCoord - axisPosition + labelSize + labelIndent));
            label.attr(attr);
            if (tick.mark) {
                const markerSize = that._isHorizontal ? tickMarkBBox.height : tickMarkBBox.width;
                const dir = labelPosition === defaultLabelPosition ? 1 : -1;
                attr[translateCoordName] = dir * (markerSize - 1);
                tick.mark.attr(attr)
            }
        },
        _detectElementsOverlapping(element1, element2) {
            if (!element1 || !element2) {
                return false
            }
            const bBox1 = element1.getBBox();
            const x1 = bBox1.x + element1.attr("translateX");
            const y1 = bBox1.y + element1.attr("translateY");
            const bBox2 = element2.getBBox();
            const x2 = bBox2.x + element2.attr("translateX");
            const y2 = bBox2.y + element2.attr("translateY");
            return (x2 >= x1 && x2 <= x1 + bBox1.width || x1 >= x2 && x1 <= x2 + bBox2.width) && (y2 >= y1 && y2 <= y1 + bBox1.height || y1 >= y2 && y1 <= y2 + bBox2.height)
        }
    }
};

function getLineDrawer(renderer, root, rotatePoints, positionFrom, breakStart, positionTo, isWaved) {
    const elementType = isWaved ? "bezier" : "line";
    const group = renderer.g().append(root);
    return function(offset, attr) {
        renderer.path(rotatePoints(getPoints(positionFrom, breakStart, positionTo, offset, isWaved)), elementType).attr(attr).append(group)
    }
}

function getPoints(positionFrom, breakStart, positionTo, offset, isWaved) {
    if (!isWaved) {
        return [positionFrom, breakStart + offset, positionTo, breakStart + offset]
    }
    breakStart += offset;
    let currentPosition;
    const topPoint = breakStart + 0;
    const centerPoint = breakStart + 2;
    const bottomPoint = breakStart + 4;
    const points = [
        [positionFrom, centerPoint]
    ];
    for (currentPosition = positionFrom; currentPosition < positionTo + 24; currentPosition += 24) {
        points.push([currentPosition + 6, topPoint, currentPosition + 6, topPoint, currentPosition + 12, centerPoint, currentPosition + 18, bottomPoint, currentPosition + 18, bottomPoint, currentPosition + 24, centerPoint])
    }
    return [].concat.apply([], points)
}

function rotateLine(lineCoords) {
    const points = [];
    let i;
    for (i = 0; i < lineCoords.length; i += 2) {
        points.push(lineCoords[i + 1]);
        points.push(lineCoords[i])
    }
    return points
}