devextreme
Version:
HTML5 JavaScript Component Suite for Responsive Web Development
391 lines (375 loc) • 15.9 kB
JavaScript
/**
* DevExtreme (cjs/viz/chart_components/multi_axes_synchronizer.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/
*/
;
exports.default = void 0;
var _console = require("../../core/utils/console");
var _type = require("../../core/utils/type");
var _iterator = require("../../core/utils/iterator");
var _utils = require("../core/utils");
var _math2 = require("../../core/utils/math");
const _math = Math;
const _floor = _math.floor;
const _max = _math.max;
const _abs = _math.abs;
function getValueAxesPerPanes(valueAxes) {
const result = {};
valueAxes.forEach((axis => {
const pane = axis.pane;
if (!result[pane]) {
result[pane] = []
}
result[pane].push(axis)
}));
return result
}
const linearConverter = br => ({
transform: function(v, b) {
return (0, _math2.adjust)((0, _utils.getLogExt)(v, b, br.allowNegatives, br.linearThreshold))
},
getTicks: function(interval, tickValues, base) {
const ticks = [];
let tick = this.transform(tickValues[0], base);
while (ticks.length < tickValues.length) {
ticks.push(tick);
tick = (0, _math2.adjust)(tick + interval)
}
return ticks
}
});
const logConverter = br => ({
transform: function(v, b) {
return (0, _math2.adjust)((0, _utils.raiseToExt)(v, b, br.allowNegatives, br.linearThreshold))
},
getTicks: function(interval, tickValues, base) {
const ticks = [];
let tick;
for (let i = 0; i < tickValues.length; i += 1) {
tick = this.transform(tickValues[i], base);
ticks.push(tick)
}
return ticks
}
});
function convertAxisInfo(axisInfo, converter) {
if (!axisInfo.isLogarithmic) {
return
}
const base = axisInfo.logarithmicBase;
const tickValues = axisInfo.tickValues;
axisInfo.minValue = converter.transform(axisInfo.minValue, base);
axisInfo.oldMinValue = converter.transform(axisInfo.oldMinValue, base);
axisInfo.maxValue = converter.transform(axisInfo.maxValue, base);
axisInfo.oldMaxValue = converter.transform(axisInfo.oldMaxValue, base);
axisInfo.tickInterval = _math.round(axisInfo.tickInterval);
if (axisInfo.tickInterval < 1) {
axisInfo.tickInterval = 1
}
const ticks = converter.getTicks(axisInfo.tickInterval, tickValues, base);
ticks.tickInterval = axisInfo.tickInterval;
axisInfo.tickValues = ticks
}
function populateAxesInfo(axes) {
return axes.reduce((function(result, axis) {
const ticksValues = axis.getTicksValues();
const majorTicks = ticksValues.majorTicksValues;
const options = axis.getOptions();
const businessRange = axis.getTranslator().getBusinessRange();
const visibleArea = axis.getVisibleArea();
let axisInfo;
let tickInterval = axis._tickInterval;
const synchronizedValue = options.synchronizedValue;
const action = axis.getViewport().action;
if (majorTicks && majorTicks.length > 0 && (0, _type.isNumeric)(majorTicks[0]) && "discrete" !== options.type && !businessRange.isEmpty() && !(businessRange.breaks && businessRange.breaks.length) && "zoom" !== action && "pan" !== action) {
axis.applyMargins();
const startValue = axis.getTranslator().from(visibleArea[0]);
const endValue = axis.getTranslator().from(visibleArea[1]);
let minValue = startValue < endValue ? startValue : endValue;
let maxValue = startValue < endValue ? endValue : startValue;
if (minValue === maxValue && (0, _type.isDefined)(synchronizedValue)) {
tickInterval = _abs(majorTicks[0] - synchronizedValue) || 1;
minValue = majorTicks[0] - tickInterval;
maxValue = majorTicks[0] + tickInterval
}
axisInfo = {
axis: axis,
isLogarithmic: "logarithmic" === options.type,
logarithmicBase: businessRange.base,
tickValues: majorTicks,
minorValues: ticksValues.minorTicksValues,
minorTickInterval: axis._minorTickInterval,
minValue: minValue,
oldMinValue: minValue,
maxValue: maxValue,
oldMaxValue: maxValue,
inverted: businessRange.invert,
tickInterval: tickInterval,
synchronizedValue: synchronizedValue
};
convertAxisInfo(axisInfo, linearConverter(axis.getTranslator().getBusinessRange()));
result.push(axisInfo)
}
return result
}), [])
}
function updateTickValues(axesInfo) {
const maxTicksCount = axesInfo.reduce(((max, axisInfo) => _max(max, axisInfo.tickValues.length)), 0);
axesInfo.forEach((axisInfo => {
let ticksMultiplier;
let ticksCount;
let additionalStartTicksCount = 0;
const synchronizedValue = axisInfo.synchronizedValue;
const tickValues = axisInfo.tickValues;
const tickInterval = axisInfo.tickInterval;
if ((0, _type.isDefined)(synchronizedValue)) {
axisInfo.baseTickValue = axisInfo.invertedBaseTickValue = synchronizedValue;
axisInfo.tickValues = [axisInfo.baseTickValue]
} else {
if (tickValues.length > 1 && tickInterval) {
ticksMultiplier = _floor((maxTicksCount + 1) / tickValues.length);
ticksCount = ticksMultiplier > 1 ? _floor((maxTicksCount + 1) / ticksMultiplier) : maxTicksCount;
additionalStartTicksCount = _floor((ticksCount - tickValues.length) / 2);
while (additionalStartTicksCount > 0 && 0 !== tickValues[0]) {
tickValues.unshift((0, _math2.adjust)(tickValues[0] - tickInterval));
additionalStartTicksCount--
}
while (tickValues.length < ticksCount) {
tickValues.push((0, _math2.adjust)(tickValues[tickValues.length - 1] + tickInterval))
}
axisInfo.tickInterval = tickInterval / ticksMultiplier
}
axisInfo.baseTickValue = tickValues[0];
axisInfo.invertedBaseTickValue = tickValues[tickValues.length - 1]
}
}))
}
function getAxisRange(axisInfo) {
return axisInfo.maxValue - axisInfo.minValue || 1
}
function getMainAxisInfo(axesInfo) {
for (let i = 0; i < axesInfo.length; i++) {
if (!axesInfo[i].stubData) {
return axesInfo[i]
}
}
return null
}
function correctMinMaxValues(axesInfo) {
const mainAxisInfo = getMainAxisInfo(axesInfo);
const mainAxisInfoTickInterval = mainAxisInfo.tickInterval;
axesInfo.forEach((axisInfo => {
let scale;
let move;
let mainAxisBaseValueOffset;
let valueFromAxisInfo;
if (axisInfo !== mainAxisInfo) {
if (mainAxisInfoTickInterval && axisInfo.tickInterval) {
if (axisInfo.stubData && (0, _type.isDefined)(axisInfo.synchronizedValue)) {
axisInfo.oldMinValue = axisInfo.minValue = axisInfo.baseTickValue - (mainAxisInfo.baseTickValue - mainAxisInfo.minValue) / mainAxisInfoTickInterval * axisInfo.tickInterval;
axisInfo.oldMaxValue = axisInfo.maxValue = axisInfo.baseTickValue - (mainAxisInfo.baseTickValue - mainAxisInfo.maxValue) / mainAxisInfoTickInterval * axisInfo.tickInterval
}
scale = mainAxisInfoTickInterval / getAxisRange(mainAxisInfo) / axisInfo.tickInterval * getAxisRange(axisInfo);
axisInfo.maxValue = axisInfo.minValue + getAxisRange(axisInfo) / scale
}
if (mainAxisInfo.inverted && !axisInfo.inverted || !mainAxisInfo.inverted && axisInfo.inverted) {
mainAxisBaseValueOffset = mainAxisInfo.maxValue - mainAxisInfo.invertedBaseTickValue
} else {
mainAxisBaseValueOffset = mainAxisInfo.baseTickValue - mainAxisInfo.minValue
}
valueFromAxisInfo = getAxisRange(axisInfo);
move = (mainAxisBaseValueOffset / getAxisRange(mainAxisInfo) - (axisInfo.baseTickValue - axisInfo.minValue) / valueFromAxisInfo) * valueFromAxisInfo;
axisInfo.minValue -= move;
axisInfo.maxValue -= move
}
}))
}
function calculatePaddings(axesInfo) {
let minPadding;
let maxPadding;
let startPadding = 0;
let endPadding = 0;
axesInfo.forEach((axisInfo => {
const inverted = axisInfo.inverted;
minPadding = axisInfo.minValue > axisInfo.oldMinValue ? (axisInfo.minValue - axisInfo.oldMinValue) / getAxisRange(axisInfo) : 0;
maxPadding = axisInfo.maxValue < axisInfo.oldMaxValue ? (axisInfo.oldMaxValue - axisInfo.maxValue) / getAxisRange(axisInfo) : 0;
startPadding = _max(startPadding, inverted ? maxPadding : minPadding);
endPadding = _max(endPadding, inverted ? minPadding : maxPadding)
}));
return {
start: startPadding,
end: endPadding
}
}
function correctMinMaxValuesByPaddings(axesInfo, paddings) {
axesInfo.forEach((info => {
const range = getAxisRange(info);
const inverted = info.inverted;
info.minValue = (0, _math2.adjust)(info.minValue - paddings[inverted ? "end" : "start"] * range);
info.maxValue = (0, _math2.adjust)(info.maxValue + paddings[inverted ? "start" : "end"] * range)
}))
}
function updateTickValuesIfSynchronizedValueUsed(axesInfo) {
let hasSynchronizedValue = false;
axesInfo.forEach((info => {
hasSynchronizedValue = hasSynchronizedValue || (0, _type.isDefined)(info.synchronizedValue)
}));
axesInfo.forEach((info => {
const tickInterval = info.tickInterval;
const tickValues = info.tickValues;
const maxValue = info.maxValue;
const minValue = info.minValue;
let tick;
if (hasSynchronizedValue && tickInterval) {
while ((tick = (0, _math2.adjust)(tickValues[0] - tickInterval)) >= minValue) {
tickValues.unshift(tick)
}
tick = tickValues[tickValues.length - 1];
while ((tick = (0, _math2.adjust)(tick + tickInterval)) <= maxValue) {
tickValues.push(tick)
}
}
while (tickValues[0] + tickInterval / 10 < minValue) {
tickValues.shift()
}
while (tickValues[tickValues.length - 1] - tickInterval / 10 > maxValue) {
tickValues.pop()
}
}))
}
function applyMinMaxValues(axesInfo) {
axesInfo.forEach((info => {
const axis = info.axis;
const range = axis.getTranslator().getBusinessRange();
if (range.min === range.minVisible) {
range.min = info.minValue
}
if (range.max === range.maxVisible) {
range.max = info.maxValue
}
range.minVisible = info.minValue;
range.maxVisible = info.maxValue;
if (range.min > range.minVisible) {
range.min = range.minVisible
}
if (range.max < range.maxVisible) {
range.max = range.maxVisible
}
axis.getTranslator().updateBusinessRange(range);
axis.setTicks({
majorTicks: info.tickValues,
minorTicks: info.minorValues
})
}))
}
function correctAfterSynchronize(axesInfo) {
const invalidAxisInfo = [];
let correctValue;
axesInfo.forEach((info => {
if (info.oldMaxValue - info.oldMinValue === 0) {
invalidAxisInfo.push(info)
} else if (!(0, _type.isDefined)(correctValue) && !(0, _type.isDefined)(info.synchronizedValue)) {
correctValue = _abs((info.maxValue - info.minValue) / (info.tickValues[_floor(info.tickValues.length / 2)] - info.minValue || info.maxValue))
}
}));
if (!(0, _type.isDefined)(correctValue)) {
return
}
invalidAxisInfo.forEach((info => {
const firstTick = info.tickValues[0];
const correctedTick = firstTick * correctValue;
if (firstTick > 0) {
info.maxValue = correctedTick;
info.minValue = 0
} else if (firstTick < 0) {
info.minValue = correctedTick;
info.maxValue = 0
}
}))
}
function updateMinorTicks(axesInfo) {
axesInfo.forEach((function(axisInfo) {
if (!axisInfo.minorTickInterval) {
return
}
const ticks = [];
const interval = axisInfo.minorTickInterval;
const tickCount = axisInfo.tickInterval / interval - 1;
for (let i = 1; i < axisInfo.tickValues.length; i++) {
let tick = axisInfo.tickValues[i - 1];
for (let j = 0; j < tickCount; j++) {
tick += interval;
ticks.push(tick)
}
}
axisInfo.minorValues = ticks
}))
}
function allAxesValuesOnSameSideFromZero(axesInfo) {
let allPositive = true;
let allNegative = true;
axesInfo.forEach((axis => {
if (axis.oldMinValue > 0 || axis.oldMaxValue > 0) {
allNegative = false
}
if (axis.oldMinValue < 0 || axis.oldMaxValue < 0) {
allPositive = false
}
}));
return allPositive || allNegative
}
function correctPaddings(axesInfo, paddings) {
if (!allAxesValuesOnSameSideFromZero(axesInfo)) {
return paddings
}
return axesInfo.reduce(((prev, info) => {
const inverted = info.inverted;
const {
start: start,
end: end
} = info.axis.getCorrectedValuesToZero(info.minValue, info.maxValue);
if ((0, _type.isDefined)(start) || (0, _type.isDefined)(end)) {
return inverted ? {
start: prev.start,
end: Math.min(prev.end, end)
} : {
start: Math.min(prev.start, start),
end: prev.end
}
}
return prev
}), paddings)
}
const multiAxesSynchronizer = {
synchronize: function(valueAxes) {
(0, _iterator.each)(getValueAxesPerPanes(valueAxes), (function(_, axes) {
let axesInfo;
let paddings;
if (axes.length > 1) {
axesInfo = populateAxesInfo(axes);
if (axesInfo.length < 2 || !getMainAxisInfo(axesInfo)) {
return
}
updateTickValues(axesInfo);
correctMinMaxValues(axesInfo);
paddings = calculatePaddings(axesInfo);
paddings = correctPaddings(axesInfo, paddings);
correctMinMaxValuesByPaddings(axesInfo, paddings);
correctAfterSynchronize(axesInfo);
updateTickValuesIfSynchronizedValueUsed(axesInfo);
updateMinorTicks(axesInfo);
axesInfo.forEach((info => {
convertAxisInfo(info, logConverter(info.axis.getTranslator().getBusinessRange()))
}));
applyMinMaxValues(axesInfo)
}
}))
}
};
var _default = exports.default = multiAxesSynchronizer;
module.exports = exports.default;
module.exports.default = exports.default;