UNPKG

devextreme

Version:

HTML5 JavaScript Component Suite for Responsive Web Development

1,172 lines (1,152 loc) • 105 kB
/** * DevExtreme (esm/viz/axes/base_axis.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/ */ import { smartFormatter as _format, formatRange } from "./smart_formatter"; import { patchFontOptions, getVizRangeObject, getLogExt as getLog, raiseToExt as raiseTo, valueOf, rotateBBox, getCategoriesInfo, adjustVisualRange, getAddFunction, convertVisualRangeObject } from "../core/utils"; import { isDefined, isFunction, isPlainObject, type } from "../../core/utils/type"; import constants from "./axes_constants"; import { extend } from "../../core/utils/extend"; import formatHelper from "../../format_helper"; import { getParser } from "../components/parse_utils"; import { tickGenerator } from "./tick_generator"; import { Translator2D } from "../translators/translator2d"; import { Range } from "../translators/range"; import { tick } from "./tick"; import { adjust } from "../../core/utils/math"; import errors from "../../core/errors"; import dateUtils from "../../core/utils/date"; import { noop as _noop } from "../../core/utils/common"; import xyMethods from "./xy_axes"; import * as polarMethods from "./polar_axes"; import createConstantLine from "./constant_line"; import createStrip from "./strip"; import { Deferred, when } from "../../core/utils/deferred"; import { calculateCanvasMargins, measureLabels } from "./axes_utils"; const convertTicksToValues = constants.convertTicksToValues; const _math = Math; const _abs = _math.abs; const _max = _math.max; const _min = _math.min; const _isArray = Array.isArray; const DEFAULT_AXIS_LABEL_SPACING = 5; const MAX_GRID_BORDER_ADHENSION = 4; const TOP = constants.top; const BOTTOM = constants.bottom; const LEFT = constants.left; const RIGHT = constants.right; const CENTER = constants.center; const KEEP = "keep"; const SHIFT = "shift"; const RESET = "reset"; const ROTATE = "rotate"; const DEFAULT_AXIS_DIVISION_FACTOR = 50; const DEFAULT_MINOR_AXIS_DIVISION_FACTOR = 15; const SCROLL_THRESHOLD = 5; const MIN_BAR_MARGIN = 5; const MAX_MARGIN_VALUE = .8; const dateIntervals = { day: 864e5, week: 6048e5 }; function getTickGenerator(options, incidentOccurred, skipTickGeneration, rangeIsEmpty, adjustDivisionFactor, _ref) { var _options$workWeek; let { allowNegatives: allowNegatives, linearThreshold: linearThreshold } = _ref; return tickGenerator({ axisType: options.type, dataType: options.dataType, logBase: options.logarithmBase, allowNegatives: allowNegatives, linearThreshold: linearThreshold, axisDivisionFactor: adjustDivisionFactor(options.axisDivisionFactor || 50), minorAxisDivisionFactor: adjustDivisionFactor(options.minorAxisDivisionFactor || 15), numberMultipliers: options.numberMultipliers, calculateMinors: options.minorTick.visible || options.minorGrid.visible || options.calculateMinors, allowDecimals: options.allowDecimals, endOnTick: options.endOnTick, incidentOccurred: incidentOccurred, firstDayOfWeek: null === (_options$workWeek = options.workWeek) || void 0 === _options$workWeek ? void 0 : _options$workWeek[0], skipTickGeneration: skipTickGeneration, skipCalculationLimits: options.skipCalculationLimits, generateExtraTick: options.generateExtraTick, minTickInterval: options.minTickInterval, rangeIsEmpty: rangeIsEmpty }) } function createMajorTick(axis, renderer, skippedCategory) { const options = axis.getOptions(); return tick(axis, renderer, options.tick, options.grid, skippedCategory, false) } function createMinorTick(axis, renderer) { const options = axis.getOptions(); return tick(axis, renderer, options.minorTick, options.minorGrid) } function createBoundaryTick(axis, renderer, isFirst) { const options = axis.getOptions(); return tick(axis, renderer, extend({}, options.tick, { visible: options.showCustomBoundaryTicks }), options.grid, void 0, false, isFirst ? -1 : 1) } function callAction(elements, action, actionArgument1, actionArgument2) { (elements || []).forEach((e => e[action](actionArgument1, actionArgument2))) } function initTickCoords(ticks) { callAction(ticks, "initCoords") } function drawTickMarks(ticks, options) { callAction(ticks, "drawMark", options) } function drawGrids(ticks, drawLine) { callAction(ticks, "drawGrid", drawLine) } function updateTicksPosition(ticks, options, animate) { callAction(ticks, "updateTickPosition", options, animate) } function updateGridsPosition(ticks, animate) { callAction(ticks, "updateGridPosition", animate) } function cleanUpInvalidTicks(ticks) { let i = ticks.length - 1; for (i; i >= 0; i--) { if (!removeInvalidTick(ticks, i)) { break } } for (i = 0; i < ticks.length; i++) { if (removeInvalidTick(ticks, i)) { i-- } else { break } } } function removeInvalidTick(ticks, i) { if (null === ticks[i].coords.x || null === ticks[i].coords.y) { ticks.splice(i, 1); return true } return false } function validateAxisOptions(options) { const labelOptions = options.label; let position = options.position; const defaultPosition = options.isHorizontal ? BOTTOM : LEFT; const secondaryPosition = options.isHorizontal ? TOP : RIGHT; let labelPosition = labelOptions.position; if (position !== defaultPosition && position !== secondaryPosition) { position = defaultPosition } if (!labelPosition || "outside" === labelPosition) { labelPosition = position } else if ("inside" === labelPosition) { labelPosition = { [TOP]: BOTTOM, [BOTTOM]: TOP, [LEFT]: RIGHT, [RIGHT]: LEFT } [position] } if (labelPosition !== defaultPosition && labelPosition !== secondaryPosition) { labelPosition = position } if (labelOptions.alignment !== CENTER && !labelOptions.userAlignment) { labelOptions.alignment = { [TOP]: CENTER, [BOTTOM]: CENTER, [LEFT]: RIGHT, [RIGHT]: LEFT } [labelPosition] } options.position = position; labelOptions.position = labelPosition; options.hoverMode = options.hoverMode ? options.hoverMode.toLowerCase() : "none"; labelOptions.minSpacing = labelOptions.minSpacing ?? 5; options.type && (options.type = options.type.toLowerCase()); options.argumentType && (options.argumentType = options.argumentType.toLowerCase()); options.valueType && (options.valueType = options.valueType.toLowerCase()) } function getOptimalAngle(boxes, labelOpt) { const angle = 180 * _math.asin((boxes[0].height + labelOpt.minSpacing) / (boxes[1].x - boxes[0].x)) / _math.PI; return angle < 45 ? -45 : -90 } function updateLabels(ticks, step, func) { ticks.forEach((function(tick, index) { if (tick.getContentContainer()) { if (index % step !== 0) { tick.removeLabel() } else if (func) { func(tick, index) } } })) } function getZoomBoundValue(optionValue, dataValue) { if (void 0 === optionValue) { return dataValue } else if (null === optionValue) { return } else { return optionValue } } function configureGenerator(options, axisDivisionFactor, viewPort, screenDelta, minTickInterval) { const tickGeneratorOptions = extend({}, options, { endOnTick: true, axisDivisionFactor: axisDivisionFactor, skipCalculationLimits: true, generateExtraTick: true, minTickInterval: minTickInterval }); return function(tickInterval, skipTickGeneration, min, max, breaks) { return getTickGenerator(tickGeneratorOptions, _noop, skipTickGeneration, viewPort.isEmpty(), (v => v), viewPort)({ min: min, max: max, categories: viewPort.categories, isSpacedMargin: viewPort.isSpacedMargin }, screenDelta, tickInterval, isDefined(tickInterval), void 0, void 0, void 0, breaks) } } function getConstantLineSharpDirection(coord, axisCanvas) { return Math.max(axisCanvas.start, axisCanvas.end) !== coord ? 1 : -1 } function checkDeprecatedOptions(isValueAxis, options) { if (isValueAxis && "shift" === options.visualRangeUpdateMode) { errors.log("W0016", "valueAxis.visualRangeUpdateMode", "shift", "23.1", "Specify another value") } } export const Axis = function(renderSettings) { this._renderer = renderSettings.renderer; this._incidentOccurred = renderSettings.incidentOccurred; this._eventTrigger = renderSettings.eventTrigger; this._stripsGroup = renderSettings.stripsGroup; this._stripLabelAxesGroup = renderSettings.stripLabelAxesGroup; this._labelsAxesGroup = renderSettings.labelsAxesGroup; this._constantLinesGroup = renderSettings.constantLinesGroup; this._scaleBreaksGroup = renderSettings.scaleBreaksGroup; this._axesContainerGroup = renderSettings.axesContainerGroup; this._gridContainerGroup = renderSettings.gridGroup; this._axisCssPrefix = renderSettings.widgetClass + "-" + (renderSettings.axisClass ? renderSettings.axisClass + "-" : ""); this._setType(renderSettings.axisType, renderSettings.drawingType); this._createAxisGroups(); this._translator = this._createTranslator(); this.isArgumentAxis = renderSettings.isArgumentAxis; this._viewport = {}; this._prevDataInfo = {}; this._firstDrawing = true; this._initRange = {}; this._getTemplate = renderSettings.getTemplate }; Axis.prototype = { constructor: Axis, _drawAxis() { const options = this._options; if (!options.visible) { return } this._axisElement = this._createAxisElement(); this._updateAxisElementPosition(); this._axisElement.attr({ "stroke-width": options.width, stroke: options.color, "stroke-opacity": options.opacity }).sharp(this._getSharpParam(true), this.getAxisSharpDirection()).append(this._axisLineGroup) }, _createPathElement(points, attr, sharpDirection) { return this.sharp(this._renderer.path(points, "line").attr(attr), sharpDirection) }, sharp(svgElement) { let sharpDirection = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : 1; return svgElement.sharp(this._getSharpParam(), sharpDirection) }, customPositionIsAvailable: () => false, getOrthogonalAxis: _noop, getCustomPosition: _noop, getCustomBoundaryPosition: _noop, resolveOverlappingForCustomPositioning: _noop, hasNonBoundaryPosition: () => false, customPositionIsBoundaryOrthogonalAxis: () => false, getResolvedBoundaryPosition() { return this.getOptions().position }, getAxisSharpDirection() { const position = this.getResolvedBoundaryPosition(); return this.hasNonBoundaryPosition() || position !== BOTTOM && position !== RIGHT ? 1 : -1 }, getSharpDirectionByCoords(coords) { const canvas = this._getCanvasStartEnd(); const maxCoord = Math.max(canvas.start, canvas.end); return this.getRadius ? 0 : maxCoord !== coords[this._isHorizontal ? "x" : "y"] ? 1 : -1 }, _getGridLineDrawer: function() { const that = this; return function(tick, gridStyle) { const grid = that._getGridPoints(tick.coords); if (grid.points) { return that._createPathElement(grid.points, gridStyle, that.getSharpDirectionByCoords(tick.coords)) } return null } }, _getGridPoints: function(coords) { const isHorizontal = this._isHorizontal; const tickPositionField = isHorizontal ? "x" : "y"; const orthogonalPositions = this._orthogonalPositions; const positionFrom = orthogonalPositions.start; const positionTo = orthogonalPositions.end; const borderOptions = this.borderOptions; const canvasStart = isHorizontal ? LEFT : TOP; const canvasEnd = isHorizontal ? RIGHT : BOTTOM; const axisCanvas = this.getCanvas(); const canvas = { left: axisCanvas.left, right: axisCanvas.width - axisCanvas.right, top: axisCanvas.top, bottom: axisCanvas.height - axisCanvas.bottom }; const firstBorderLinePosition = borderOptions.visible && borderOptions[canvasStart] ? canvas[canvasStart] : void 0; const lastBorderLinePosition = borderOptions.visible && borderOptions[canvasEnd] ? canvas[canvasEnd] : void 0; const minDelta = 4 + firstBorderLinePosition; const maxDelta = lastBorderLinePosition - 4; if (this.areCoordsOutsideAxis(coords) || void 0 === coords[tickPositionField] || coords[tickPositionField] < minDelta || coords[tickPositionField] > maxDelta) { return { points: null } } return { points: isHorizontal ? null !== coords[tickPositionField] ? [coords[tickPositionField], positionFrom, coords[tickPositionField], positionTo] : null : null !== coords[tickPositionField] ? [positionFrom, coords[tickPositionField], positionTo, coords[tickPositionField]] : null } }, _getConstantLinePos: function(parsedValue, canvasStart, canvasEnd) { const value = this._getTranslatedCoord(parsedValue); if (!isDefined(value) || value < _min(canvasStart, canvasEnd) || value > _max(canvasStart, canvasEnd)) { return } return value }, _getConstantLineGraphicAttributes: function(value) { const positionFrom = this._orthogonalPositions.start; const positionTo = this._orthogonalPositions.end; return { points: this._isHorizontal ? [value, positionFrom, value, positionTo] : [positionFrom, value, positionTo, value] } }, _createConstantLine: function(value, attr) { return this._createPathElement(this._getConstantLineGraphicAttributes(value).points, attr, getConstantLineSharpDirection(value, this._getCanvasStartEnd())) }, _drawConstantLineLabelText: function(text, x, y, _ref2, group) { let { font: font, cssClass: cssClass } = _ref2; return this._renderer.text(text, x, y).css(patchFontOptions(extend({}, this._options.label.font, font))).attr({ align: "center", class: cssClass }).append(group) }, _drawConstantLineLabels: function(parsedValue, lineLabelOptions, value, group) { let text = lineLabelOptions.text; const options = this._options; const labelOptions = options.label; this._checkAlignmentConstantLineLabels(lineLabelOptions); text = text ?? this.formatLabel(parsedValue, labelOptions); const coords = this._getConstantLineLabelsCoords(value, lineLabelOptions); return this._drawConstantLineLabelText(text, coords.x, coords.y, lineLabelOptions, group) }, _getStripPos: function(startValue, endValue, canvasStart, canvasEnd, range) { const isContinuous = !!(range.minVisible || range.maxVisible); const categories = (range.categories || []).reduce((function(result, cat) { result.push(cat.valueOf()); return result }), []); let start; let end; let swap; let startCategoryIndex; let endCategoryIndex; if (!isContinuous) { if (isDefined(startValue) && isDefined(endValue)) { const parsedStartValue = this.parser(startValue); const parsedEndValue = this.parser(endValue); startCategoryIndex = categories.indexOf((null === parsedStartValue || void 0 === parsedStartValue ? void 0 : parsedStartValue.valueOf()) ?? void 0); endCategoryIndex = categories.indexOf((null === parsedEndValue || void 0 === parsedEndValue ? void 0 : parsedEndValue.valueOf()) ?? void 0); if (-1 === startCategoryIndex || -1 === endCategoryIndex) { return { from: 0, to: 0, outOfCanvas: true } } if (startCategoryIndex > endCategoryIndex) { swap = endValue; endValue = startValue; startValue = swap } } } if (isDefined(startValue)) { startValue = this.validateUnit(startValue, "E2105", "strip"); start = this._getTranslatedCoord(startValue, -1) } else { start = canvasStart } if (isDefined(endValue)) { endValue = this.validateUnit(endValue, "E2105", "strip"); end = this._getTranslatedCoord(endValue, 1) } else { end = canvasEnd } const stripPosition = start < end ? { from: start, to: end } : { from: end, to: start }; const visibleArea = this.getVisibleArea(); if (stripPosition.from <= visibleArea[0] && stripPosition.to <= visibleArea[0] || stripPosition.from >= visibleArea[1] && stripPosition.to >= visibleArea[1]) { stripPosition.outOfCanvas = true } return stripPosition }, _getStripGraphicAttributes: function(fromPoint, toPoint) { let x; let y; let width; let height; const orthogonalPositions = this._orthogonalPositions; const positionFrom = orthogonalPositions.start; const positionTo = orthogonalPositions.end; if (this._isHorizontal) { x = fromPoint; y = _min(positionFrom, positionTo); width = toPoint - fromPoint; height = _abs(positionFrom - positionTo) } else { x = _min(positionFrom, positionTo); y = fromPoint; width = _abs(positionFrom - positionTo); height = _abs(fromPoint - toPoint) } return { x: x, y: y, width: width, height: height } }, _createStrip: function(attrs) { return this._renderer.rect(attrs.x, attrs.y, attrs.width, attrs.height) }, _adjustStripLabels: function() { const that = this; this._strips.forEach((function(strip) { if (strip.label) { strip.label.attr(that._getAdjustedStripLabelCoords(strip)) } })) }, _adjustLabelsCoord(offset, maxWidth, checkCanvas) { const getContainerAttrs = tick => this._getLabelAdjustedCoord(tick, offset + (tick.labelOffset || 0), maxWidth, checkCanvas); this._majorTicks.forEach((function(tick) { if (tick.label) { tick.updateMultilineTextAlignment(); tick.label.attr(getContainerAttrs(tick)) } else { tick.templateContainer && tick.templateContainer.attr(getContainerAttrs(tick)) } })) }, _adjustLabels: function(offset) { const options = this.getOptions(); const positionsAreConsistent = options.position === options.label.position; const maxSize = this._majorTicks.reduce((function(size, tick) { if (!tick.getContentContainer()) { return size } const bBox = tick.labelRotationAngle ? rotateBBox(tick.labelBBox, [tick.labelCoords.x, tick.labelCoords.y], -tick.labelRotationAngle) : tick.labelBBox; return { width: _max(size.width || 0, bBox.width), height: _max(size.height || 0, bBox.height), offset: _max(size.offset || 0, tick.labelOffset || 0) } }), {}); const additionalOffset = positionsAreConsistent ? this._isHorizontal ? maxSize.height : maxSize.width : 0; this._adjustLabelsCoord(offset, maxSize.width); return offset + additionalOffset + (additionalOffset && this._options.label.indentFromAxis) + (positionsAreConsistent ? maxSize.offset : 0) }, _getLabelAdjustedCoord: function(tick, offset, maxWidth) { offset = offset || 0; const options = this._options; const templateBox = tick.templateContainer && tick.templateContainer.getBBox(); const box = templateBox || rotateBBox(tick.labelBBox, [tick.labelCoords.x, tick.labelCoords.y], -tick.labelRotationAngle || 0); const textAlign = tick.labelAlignment || options.label.alignment; const isDiscrete = "discrete" === this._options.type; const isFlatLabel = tick.labelRotationAngle % 90 === 0; const indentFromAxis = options.label.indentFromAxis; const labelPosition = options.label.position; const axisPosition = this._axisPosition; const labelCoords = tick.labelCoords; const labelX = labelCoords.x; let translateX; let translateY; if (this._isHorizontal) { if (labelPosition === BOTTOM) { translateY = axisPosition + indentFromAxis - box.y + offset } else { translateY = axisPosition - indentFromAxis - (box.y + box.height) - offset } if (textAlign === RIGHT) { translateX = isDiscrete && isFlatLabel ? tick.coords.x - (box.x + box.width) : labelX - box.x - box.width } else if (textAlign === LEFT) { translateX = isDiscrete && isFlatLabel ? labelX - box.x - (tick.coords.x - labelX) : labelX - box.x } else { translateX = labelX - box.x - box.width / 2 } } else { translateY = labelCoords.y - box.y - box.height / 2; if (labelPosition === LEFT) { if (textAlign === LEFT) { translateX = axisPosition - indentFromAxis - maxWidth - box.x } else if (textAlign === CENTER) { translateX = axisPosition - indentFromAxis - maxWidth / 2 - box.x - box.width / 2 } else { translateX = axisPosition - indentFromAxis - box.x - box.width } translateX -= offset } else { if (textAlign === RIGHT) { translateX = axisPosition + indentFromAxis + maxWidth - box.x - box.width } else if (textAlign === CENTER) { translateX = axisPosition + indentFromAxis + maxWidth / 2 - box.x - box.width / 2 } else { translateX = axisPosition + indentFromAxis - box.x } translateX += offset } } return { translateX: translateX, translateY: translateY } }, _createAxisConstantLineGroups: function() { const renderer = this._renderer; const classSelector = this._axisCssPrefix; const constantLinesClass = classSelector + "constant-lines"; const insideGroup = renderer.g().attr({ class: constantLinesClass }); const outsideGroup1 = renderer.g().attr({ class: constantLinesClass }); const outsideGroup2 = renderer.g().attr({ class: constantLinesClass }); return { inside: insideGroup, outside1: outsideGroup1, left: outsideGroup1, top: outsideGroup1, outside2: outsideGroup2, right: outsideGroup2, bottom: outsideGroup2, remove: function() { this.inside.remove(); this.outside1.remove(); this.outside2.remove() }, clear: function() { this.inside.clear(); this.outside1.clear(); this.outside2.clear() } } }, _createAxisGroups: function() { const renderer = this._renderer; const classSelector = this._axisCssPrefix; this._axisGroup = renderer.g().attr({ class: classSelector + "axis" }).enableLinks(); this._axisStripGroup = renderer.g().attr({ class: classSelector + "strips" }); this._axisGridGroup = renderer.g().attr({ class: classSelector + "grid" }); this._axisElementsGroup = renderer.g().attr({ class: classSelector + "elements" }); this._axisLineGroup = renderer.g().attr({ class: classSelector + "line" }).linkOn(this._axisGroup, "axisLine").linkAppend(); this._axisTitleGroup = renderer.g().attr({ class: classSelector + "title" }).append(this._axisGroup); this._axisConstantLineGroups = { above: this._createAxisConstantLineGroups(), under: this._createAxisConstantLineGroups() }; this._axisStripLabelGroup = renderer.g().attr({ class: classSelector + "axis-labels" }) }, _clearAxisGroups: function() { const that = this; that._axisGroup.remove(); that._axisStripGroup.remove(); that._axisStripLabelGroup.remove(); that._axisConstantLineGroups.above.remove(); that._axisConstantLineGroups.under.remove(); that._axisGridGroup.remove(); that._axisTitleGroup.clear(); if (!that._options.label.template || !that.isRendered()) { that._axisElementsGroup.remove(); that._axisElementsGroup.clear() } that._axisLineGroup && that._axisLineGroup.clear(); that._axisStripGroup && that._axisStripGroup.clear(); that._axisGridGroup && that._axisGridGroup.clear(); that._axisConstantLineGroups.above.clear(); that._axisConstantLineGroups.under.clear(); that._axisStripLabelGroup && that._axisStripLabelGroup.clear() }, _getLabelFormatObject: function(value, labelOptions, range, point, tickInterval, ticks) { range = range || this._getViewportRange(); const formatObject = { value: value, valueText: _format(value, { labelOptions: labelOptions, ticks: ticks || convertTicksToValues(this._majorTicks), tickInterval: tickInterval ?? this._tickInterval, dataType: this._options.dataType, logarithmBase: this._options.logarithmBase, type: this._options.type, showTransition: !this._options.marker.visible, point: point }) || "", min: range.minVisible, max: range.maxVisible }; if (point) { formatObject.point = point } return formatObject }, formatLabel: function(value, labelOptions, range, point, tickInterval, ticks) { const formatObject = this._getLabelFormatObject(value, labelOptions, range, point, tickInterval, ticks); return isFunction(labelOptions.customizeText) ? labelOptions.customizeText.call(formatObject, formatObject) : formatObject.valueText }, formatHint: function(value, labelOptions, range) { const formatObject = this._getLabelFormatObject(value, labelOptions, range); return isFunction(labelOptions.customizeHint) ? labelOptions.customizeHint.call(formatObject, formatObject) : void 0 }, formatRange(startValue, endValue, interval, argumentFormat) { return formatRange({ startValue: startValue, endValue: endValue, tickInterval: interval, argumentFormat: argumentFormat, axisOptions: this.getOptions() }) }, _setTickOffset: function() { const options = this._options; const discreteAxisDivisionMode = options.discreteAxisDivisionMode; this._tickOffset = +("crossLabels" !== discreteAxisDivisionMode || !discreteAxisDivisionMode) }, aggregatedPointBetweenTicks() { return "crossTicks" === this._options.aggregatedPointsPosition }, resetApplyingAnimation: function(isFirstDrawing) { this._resetApplyingAnimation = true; if (isFirstDrawing) { this._firstDrawing = true } }, isFirstDrawing() { return this._firstDrawing }, getMargins: function() { const that = this; const { position: position, offset: offset, customPosition: customPosition, placeholderSize: placeholderSize, grid: grid, tick: tick, crosshairMargin: crosshairMargin } = that._options; const isDefinedCustomPositionOption = isDefined(customPosition); const boundaryPosition = that.getResolvedBoundaryPosition(); const canvas = that.getCanvas(); const cLeft = canvas.left; const cTop = canvas.top; const cRight = canvas.width - canvas.right; const cBottom = canvas.height - canvas.bottom; const edgeMarginCorrection = _max(grid.visible && grid.width || 0, tick.visible && tick.width || 0); const constantLineAboveSeries = that._axisConstantLineGroups.above; const constantLineUnderSeries = that._axisConstantLineGroups.under; const boxes = [that._axisElementsGroup, constantLineAboveSeries.outside1, constantLineAboveSeries.outside2, constantLineUnderSeries.outside1, constantLineUnderSeries.outside2, that._axisLineGroup].map((group => group && group.getBBox())).concat(function(group) { const box = group && group.getBBox(); if (!box || box.isEmpty) { return box } if (that._isHorizontal) { box.x = cLeft; box.width = cRight - cLeft } else { box.y = cTop; box.height = cBottom - cTop } return box }(that._axisTitleGroup)); const margins = calculateCanvasMargins(boxes, canvas); margins[position] += crosshairMargin; if (that.hasNonBoundaryPosition() && isDefinedCustomPositionOption) { margins[boundaryPosition] = 0 } if (placeholderSize) { margins[position] = placeholderSize } if (edgeMarginCorrection) { if (that._isHorizontal && canvas.right < edgeMarginCorrection && margins.right < edgeMarginCorrection) { margins.right = edgeMarginCorrection } if (!that._isHorizontal && canvas.bottom < edgeMarginCorrection && margins.bottom < edgeMarginCorrection) { margins.bottom = edgeMarginCorrection } } if (!isDefinedCustomPositionOption && isDefined(offset)) { const moveByOffset = that.customPositionIsBoundary() && (offset > 0 && (boundaryPosition === LEFT || boundaryPosition === TOP) || offset < 0 && (boundaryPosition === RIGHT || boundaryPosition === BOTTOM)); margins[boundaryPosition] -= moveByOffset ? offset : 0 } return margins }, validateUnit: function(unit, idError, parameters) { const that = this; unit = that.parser(unit); if (void 0 === unit && idError) { that._incidentOccurred(idError, [parameters]) } return unit }, _setType: function(axisType, drawingType) { let axisTypeMethods; switch (axisType) { case "xyAxes": axisTypeMethods = xyMethods; break; case "polarAxes": axisTypeMethods = polarMethods } extend(this, axisTypeMethods[drawingType]) }, _getSharpParam: function() { return true }, _disposeBreaksGroup: _noop, dispose: function() { [this._axisElementsGroup, this._axisStripGroup, this._axisGroup].forEach((function(g) { g.dispose() })); this._strips = this._title = null; this._axisStripGroup = this._axisConstantLineGroups = this._axisStripLabelGroup = this._axisBreaksGroup = null; this._axisLineGroup = this._axisElementsGroup = this._axisGridGroup = null; this._axisGroup = this._axisTitleGroup = null; this._axesContainerGroup = this._stripsGroup = this._constantLinesGroup = this._labelsAxesGroup = null; this._renderer = this._options = this._textOptions = this._textFontStyles = null; this._translator = null; this._majorTicks = this._minorTicks = null; this._disposeBreaksGroup(); this._templatesRendered && this._templatesRendered.reject() }, getOptions: function() { return this._options }, setPane: function(pane) { this.pane = pane; this._options.pane = pane }, setTypes: function(type, axisType, typeSelector) { this._options.type = type || this._options.type; this._options[typeSelector] = axisType || this._options[typeSelector]; this._updateTranslator() }, resetTypes: function(typeSelector) { this._options.type = this._initTypes.type; this._options[typeSelector] = this._initTypes[typeSelector] }, getTranslator: function() { return this._translator }, updateOptions: function(options) { const that = this; const labelOpt = options.label; validateAxisOptions(options); checkDeprecatedOptions(!that.isArgumentAxis, options); that._options = options; options.tick = options.tick || {}; options.minorTick = options.minorTick || {}; options.grid = options.grid || {}; options.minorGrid = options.minorGrid || {}; options.title = options.title || {}; options.marker = options.marker || {}; that._initTypes = { type: options.type, argumentType: options.argumentType, valueType: options.valueType }; that._setTickOffset(); that._isHorizontal = options.isHorizontal; that.pane = options.pane; that.name = options.name; that.priority = options.priority; that._hasLabelFormat = "" !== labelOpt.format && isDefined(labelOpt.format); that._textOptions = { opacity: labelOpt.opacity, align: "center", class: labelOpt.cssClass }; that._textFontStyles = patchFontOptions(labelOpt.font); if (options.type === constants.logarithmic) { if (options.logarithmBaseError) { that._incidentOccurred("E2104"); delete options.logarithmBaseError } } that._updateTranslator(); that._createConstantLines(); that._strips = (options.strips || []).map((o => createStrip(that, o))); that._majorTicks = that._minorTicks = null; that._firstDrawing = true }, calculateInterval: function(value, prevValue) { const options = this._options; if (!options || options.type !== constants.logarithmic) { return _abs(value - prevValue) } const { allowNegatives: allowNegatives, linearThreshold: linearThreshold } = new Range(this.getTranslator().getBusinessRange()); return _abs(getLog(value, options.logarithmBase, allowNegatives, linearThreshold) - getLog(prevValue, options.logarithmBase, allowNegatives, linearThreshold)) }, getCanvasRange() { const translator = this._translator; return { startValue: translator.from(translator.translate("canvas_position_start")), endValue: translator.from(translator.translate("canvas_position_end")) } }, _processCanvas: function(canvas) { return canvas }, updateCanvas: function(canvas, canvasRedesign) { if (!canvasRedesign) { const positions = this._orthogonalPositions = { start: !this._isHorizontal ? canvas.left : canvas.top, end: !this._isHorizontal ? canvas.width - canvas.right : canvas.height - canvas.bottom }; positions.center = positions.start + (positions.end - positions.start) / 2 } else { this._orthogonalPositions = null } this._canvas = canvas; this._translator.updateCanvas(this._processCanvas(canvas)); this._initAxisPositions() }, getCanvas: function() { return this._canvas }, getAxisShift() { return this._axisShift || 0 }, hideTitle: function() { const that = this; if (that._options.title.text) { that._incidentOccurred("W2105", [that._isHorizontal ? "horizontal" : "vertical"]); that._axisTitleGroup.clear() } }, getTitle: function() { return this._title }, hideOuterElements: function() { const that = this; const options = that._options; if ((options.label.visible || that._outsideConstantLines.length) && !that._translator.getBusinessRange().isEmpty()) { that._incidentOccurred("W2106", [that._isHorizontal ? "horizontal" : "vertical"]); that._axisElementsGroup.clear(); callAction(that._outsideConstantLines, "removeLabel") } }, _resolveLogarithmicOptionsForRange(range) { const options = this._options; if (options.type === constants.logarithmic) { range.addRange({ allowNegatives: void 0 !== options.allowNegatives ? options.allowNegatives : range.min <= 0 }); if (!isNaN(options.linearThreshold)) { range.linearThreshold = options.linearThreshold } } }, adjustViewport(businessRange) { const options = this._options; const isDiscrete = options.type === constants.discrete; let categories = this._seriesData && this._seriesData.categories || []; const wholeRange = this.adjustRange(getVizRangeObject(options.wholeRange)); const visualRange = this.getViewport() || {}; const result = new Range(businessRange); this._addConstantLinesToRange(result); let minDefined = isDefined(visualRange.startValue); let maxDefined = isDefined(visualRange.endValue); if (!isDiscrete) { minDefined = minDefined && (!isDefined(wholeRange.endValue) || visualRange.startValue < wholeRange.endValue); maxDefined = maxDefined && (!isDefined(wholeRange.startValue) || visualRange.endValue > wholeRange.startValue) } const minVisible = minDefined ? visualRange.startValue : result.minVisible; const maxVisible = maxDefined ? visualRange.endValue : result.maxVisible; if (!isDiscrete) { result.min = wholeRange.startValue ?? result.min; result.max = wholeRange.endValue ?? result.max } else { const categoriesInfo = getCategoriesInfo(categories, wholeRange.startValue, wholeRange.endValue); categories = categoriesInfo.categories; result.categories = categories } const adjustedVisualRange = adjustVisualRange({ axisType: options.type, dataType: options.dataType, base: options.logarithmBase }, { startValue: minDefined ? visualRange.startValue : void 0, endValue: maxDefined ? visualRange.endValue : void 0, length: visualRange.length }, { categories: categories, min: wholeRange.startValue, max: wholeRange.endValue }, { categories: categories, min: minVisible, max: maxVisible }); result.minVisible = adjustedVisualRange.startValue; result.maxVisible = adjustedVisualRange.endValue; !isDefined(result.min) && (result.min = result.minVisible); !isDefined(result.max) && (result.max = result.maxVisible); result.addRange({}); this._resolveLogarithmicOptionsForRange(result); return result }, adjustRange(range) { range = range || {}; const isDiscrete = this._options.type === constants.discrete; const isLogarithmic = this._options.type === constants.logarithmic; const disabledNegatives = false === this._options.allowNegatives; if (isLogarithmic) { range.startValue = disabledNegatives && range.startValue <= 0 ? null : range.startValue; range.endValue = disabledNegatives && range.endValue <= 0 ? null : range.endValue } if (!isDiscrete && isDefined(range.startValue) && isDefined(range.endValue) && range.startValue > range.endValue) { const tmp = range.endValue; range.endValue = range.startValue; range.startValue = tmp } return range }, _getVisualRangeUpdateMode(viewport, newRange, oppositeValue) { let value = this._options.visualRangeUpdateMode; const translator = this._translator; const range = this._seriesData; const prevDataInfo = this._prevDataInfo; if (prevDataInfo.isEmpty && !prevDataInfo.containsConstantLine) { return KEEP } if (!this.isArgumentAxis) { const viewport = this.getViewport(); const isViewportNotDefined = !isDefined(viewport.startValue) && !isDefined(viewport.endValue) && !isDefined(viewport.length); if (isViewportNotDefined) { const visualRange = this.visualRange(); const isVisualRangeNotDefined = !isDefined(visualRange.startValue) && !isDefined(visualRange.endValue); if (isVisualRangeNotDefined) { return RESET } } } if (this.isArgumentAxis) { if (-1 === [SHIFT, KEEP, RESET].indexOf(value)) { if (range.axisType === constants.discrete) { const categories = range.categories; const newCategories = newRange.categories; const visualRange = this.visualRange(); if (categories && newCategories && categories.length && -1 !== newCategories.map((c => c.valueOf())).join(",").indexOf(categories.map((c => c.valueOf())).join(",")) && (visualRange.startValue.valueOf() !== categories[0].valueOf() || visualRange.endValue.valueOf() !== categories[categories.length - 1].valueOf())) { value = KEEP } else { value = RESET } } else { const minPoint = translator.translate(range.min); const minVisiblePoint = translator.translate(viewport.startValue); const maxPoint = translator.translate(range.max); const maxVisiblePoint = translator.translate(viewport.endValue); if (minPoint === minVisiblePoint && maxPoint === maxVisiblePoint) { value = RESET } else if (minPoint !== minVisiblePoint && maxPoint === maxVisiblePoint) { value = SHIFT } else { value = KEEP } } if (value === KEEP && prevDataInfo.isEmpty && prevDataInfo.containsConstantLine) { value = RESET } } } else if (-1 === [KEEP, RESET].indexOf(value)) { if (oppositeValue === KEEP) { value = KEEP } else { value = RESET } } return value }, _handleBusinessRangeChanged(oppositeVisualRangeUpdateMode, axisReinitialized, newRange) { const that = this; const visualRange = this.visualRange(); if (axisReinitialized || that._translator.getBusinessRange().isEmpty()) { return } const visualRangeUpdateMode = that._lastVisualRangeUpdateMode = that._getVisualRangeUpdateMode(visualRange, newRange, oppositeVisualRangeUpdateMode); if (visualRangeUpdateMode === KEEP) { that._setVisualRange([visualRange.startValue, visualRange.endValue]) } else if (visualRangeUpdateMode === RESET) { that._setVisualRange([null, null]) } else if (visualRangeUpdateMode === SHIFT) { that._setVisualRange({ length: that.getVisualRangeLength() }) } }, getVisualRangeLength(range) { const currentBusinessRange = range || this._translator.getBusinessRange(); const { type: type } = this._options; let length; if (type === constants.logarithmic) { length = adjust(this.calculateInterval(currentBusinessRange.maxVisible, currentBusinessRange.minVisible)) } else if (type === constants.discrete) { const categoriesInfo = getCategoriesInfo(currentBusinessRange.categories, currentBusinessRange.minVisible, currentBusinessRange.maxVisible); length = categoriesInfo.categories.length } else { length = currentBusinessRange.maxVisible - currentBusinessRange.minVisible } return length }, getVisualRangeCenter(range, useMerge) { const translator = this.getTranslator(); const businessRange = translator.getBusinessRange(); const currentBusinessRange = useMerge ? extend(true, {}, businessRange, range || {}) : range || businessRange; const { type: type, logarithmBase: logarithmBase } = this._options; let center; if (!isDefined(currentBusinessRange.minVisible) || !isDefined(currentBusinessRange.maxVisible)) { return } if (type === constants.logarithmic) { const { allowNegatives: allowNegatives, linearThreshold: linearThreshold, minVisible: minVisible, maxVisible: maxVisible } = currentBusinessRange; center = raiseTo(adjust(getLog(maxVisible, logarithmBase, allowNegatives, linearThreshold) + getLog(minVisible, logarithmBase, allowNegatives, linearThreshold)) / 2, logarithmBase, allowNegatives, linearThreshold) } else if (type === constants.discrete) { const categoriesInfo = getCategoriesInfo(currentBusinessRange.categories, currentBusinessRange.minVisible, currentBusinessRange.maxVisible); const index = Math.ceil(categoriesInfo.categories.length / 2) - 1; center = businessRange.categories.indexOf(categoriesInfo.categories[index]) } else { center = translator.toValue((currentBusinessRange.maxVisible.valueOf() + currentBusinessRange.minVisible.valueOf()) / 2) } return center }, setBusinessRange(range, axisReinitialized, oppositeVisualRangeUpdateMode, argCategories) { const that = this; const options = that._options; const isDiscrete = options.type === constants.discrete; that._handleBusinessRangeChanged(oppositeVisualRangeUpdateMode, axisReinitialized, range); that._seriesData = new Range(range); const dataIsEmpty = that._seriesData.isEmpty(); const rangeWithConstantLines = new Range(that._seriesData); that._addConstantLinesToRange(rangeWithConstantLines); that._prevDataInfo = { isEmpty: dataIsEmpty, containsConstantLine: rangeWithConstantLines.containsConstantLine }; that._seriesData.addRange({ categories: options.categories, dataType: options.dataType, axisType: options.type, base: options.logarithmBase, invert: options.inverted }); that._resolveLogarithmicOptionsForRange(that._seriesData); if (!isDiscrete) { if (!isDefined(that._seriesData.min) && !isDefined(that._seriesData.max)) { const visualRange = that.getViewport(); visualRange && that._seriesData.addRange({ min: visualRange.startValue,