UNPKG

devextreme

Version:

HTML5 JavaScript Component Suite for Responsive Web Development

346 lines (344 loc) • 14.5 kB
/** * DevExtreme (viz/chart_components/multi_axes_synchronizer.js) * Version: 18.1.3 * Build date: Tue May 15 2018 * * Copyright (c) 2012 - 2018 Developer Express Inc. ALL RIGHTS RESERVED * Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/ */ "use strict"; var debug = require("../../core/utils/console").debug, typeUtils = require("../../core/utils/type"), _each = require("../../core/utils/iterator").each, vizUtils = require("../core/utils"), _isDefined = typeUtils.isDefined, adjust = require("../../core/utils/math").adjust, _math = Math, _floor = _math.floor, _max = _math.max, _abs = _math.abs; var getValueAxesPerPanes = function(valueAxes) { var result = {}; _each(valueAxes, function(_, axis) { var pane = axis.pane; if (!result[pane]) { result[pane] = [] } result[pane].push(axis) }); return result }; var linearConverter = { transform: function(v, b) { return adjust(vizUtils.getLog(v, b)) }, addInterval: function(v, i) { return adjust(v + i) }, getInterval: function(base, tickInterval) { return tickInterval } }; var logConverter = { transform: function(v, b) { return adjust(vizUtils.raiseTo(v, b)) }, addInterval: function(v, i) { return adjust(v * i) }, getInterval: function(base, tickInterval) { return _math.pow(base, tickInterval) } }; var convertAxisInfo = function(axisInfo, converter) { if (!axisInfo.isLogarithmic) { return } var tick, interval, base = axisInfo.logarithmicBase, tickValues = axisInfo.tickValues, ticks = []; 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 } interval = converter.getInterval(base, axisInfo.tickInterval); tick = converter.transform(tickValues[0], base); while (ticks.length < tickValues.length) { ticks.push(tick); tick = converter.addInterval(tick, interval) } ticks.tickInterval = axisInfo.tickInterval; axisInfo.tickValues = ticks }; var populateAxesInfo = function(axes) { return axes.reduce(function(result, axis) { var axisInfo, ticksValues = axis.getTicksValues(), majorTicks = ticksValues.majorTicksValues, options = axis.getOptions(), businessRange = axis.getTranslator().getBusinessRange(), minValue = businessRange.minVisible, maxValue = businessRange.maxVisible, tickInterval = axis._tickInterval, synchronizedValue = options.synchronizedValue; if (majorTicks && majorTicks.length > 0 && typeUtils.isNumeric(majorTicks[0]) && "discrete" !== options.type && !businessRange.stubData && !(businessRange.breaks && businessRange.breaks.length)) { if (minValue === maxValue && _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 }; if (businessRange.stubData) { axisInfo.stubData = true; axisInfo.tickInterval = axisInfo.tickInterval || options.tickInterval; axisInfo.isLogarithmic = false } convertAxisInfo(axisInfo, linearConverter); result.push(axisInfo) } return result }, []) }; var updateTickValues = function(axesInfo) { var maxTicksCount = 0; _each(axesInfo, function(_, axisInfo) { maxTicksCount = _max(maxTicksCount, axisInfo.tickValues.length) }); _each(axesInfo, function(_, axisInfo) { var ticksMultiplier, ticksCount, additionalStartTicksCount = 0, synchronizedValue = axisInfo.synchronizedValue, tickValues = axisInfo.tickValues, tickInterval = axisInfo.tickInterval; if (_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(adjust(tickValues[0] - tickInterval)); additionalStartTicksCount-- } while (tickValues.length < ticksCount) { tickValues.push(adjust(tickValues[tickValues.length - 1] + tickInterval)) } axisInfo.tickInterval = tickInterval / ticksMultiplier } axisInfo.baseTickValue = tickValues[0]; axisInfo.invertedBaseTickValue = tickValues[tickValues.length - 1] } }) }; var getAxisRange = function(axisInfo) { return axisInfo.maxValue - axisInfo.minValue || 1 }; var getMainAxisInfo = function(axesInfo) { for (var i = 0; i < axesInfo.length; i++) { if (!axesInfo[i].stubData) { return axesInfo[i] } } return null }; var correctMinMaxValues = function(axesInfo) { var mainAxisInfo = getMainAxisInfo(axesInfo), mainAxisInfoTickInterval = mainAxisInfo.tickInterval; _each(axesInfo, function(_, axisInfo) { var scale, move, mainAxisBaseValueOffset, valueFromAxisInfo; if (axisInfo !== mainAxisInfo) { if (mainAxisInfoTickInterval && axisInfo.tickInterval) { if (axisInfo.stubData && _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 } }) }; var calculatePaddings = function(axesInfo) { var minPadding, maxPadding, startPadding = 0, endPadding = 0; _each(axesInfo, function(_, axisInfo) { var 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 } }; var correctMinMaxValuesByPaddings = function(axesInfo, paddings) { _each(axesInfo, function(_, info) { var range = getAxisRange(info), inverted = info.inverted; info.minValue = adjust(info.minValue - paddings[inverted ? "end" : "start"] * range); info.maxValue = adjust(info.maxValue + paddings[inverted ? "start" : "end"] * range) }) }; var updateTickValuesIfSynchronizedValueUsed = function(axesInfo) { var hasSynchronizedValue = false; _each(axesInfo, function(_, info) { hasSynchronizedValue = hasSynchronizedValue || _isDefined(info.synchronizedValue) }); _each(axesInfo, function(_, info) { var tick, tickInterval = info.tickInterval, tickValues = info.tickValues, maxValue = info.maxValue, minValue = info.minValue; if (hasSynchronizedValue && tickInterval) { while ((tick = adjust(tickValues[0] - tickInterval)) >= minValue) { tickValues.unshift(tick) } tick = tickValues[tickValues.length - 1]; while ((tick = adjust(tick + tickInterval)) <= maxValue) { tickValues.push(tick) } } while (tickValues[0] < minValue) { tickValues.shift() } while (tickValues[tickValues.length - 1] > maxValue) { tickValues.pop() } }) }; var applyMinMaxValues = function(axesInfo) { _each(axesInfo, function(_, info) { var axis = info.axis, 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 (_isDefined(info.stubData)) { range.stubData = info.stubData } 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 }) }) }; var correctAfterSynchronize = function(axesInfo) { var correctValue, validAxisInfo, invalidAxisInfo = []; _each(axesInfo, function(i, info) { if (info.oldMaxValue - info.oldMinValue === 0) { invalidAxisInfo.push(info) } else { if (!_isDefined(correctValue) && !_isDefined(info.synchronizedValue)) { correctValue = _abs((info.maxValue - info.minValue) / (info.tickValues[_floor(info.tickValues.length / 2)] - info.minValue || info.maxValue)); validAxisInfo = info } } }); if (!_isDefined(correctValue)) { return } _each(invalidAxisInfo, function(i, info) { var firstTick = info.tickValues[0], correctedTick = firstTick * correctValue, tickValues = validAxisInfo.tickValues, centralTick = tickValues[_floor(tickValues.length / 2)]; if (firstTick > 0) { info.maxValue = correctedTick; info.minValue = 0 } else { if (firstTick < 0) { info.minValue = correctedTick; info.maxValue = 0 } else { if (0 === firstTick) { info.maxValue = validAxisInfo.maxValue - centralTick; info.minValue = validAxisInfo.minValue - centralTick } } } }) }; function updateMinorTicks(axesInfo) { axesInfo.forEach(function(axisInfo) { if (!axisInfo.minorTickInterval) { return } var ticks = []; var interval = axisInfo.minorTickInterval, tickCount = axisInfo.tickInterval / interval - 1; for (var i = 1; i < axisInfo.tickValues.length; i++) { var tick = axisInfo.tickValues[i - 1]; for (var j = 0; j < tickCount; j++) { tick += interval; ticks.push(tick) } } axisInfo.minorValues = ticks }) } var multiAxesSynchronizer = { synchronize: function(valueAxes) { _each(getValueAxesPerPanes(valueAxes), function(_, axes) { var axesInfo, paddings; if (axes.length > 1) { axesInfo = populateAxesInfo(axes); if (axesInfo.length < 2 || !getMainAxisInfo(axesInfo)) { return } updateTickValues(axesInfo); correctMinMaxValues(axesInfo); paddings = calculatePaddings(axesInfo); correctMinMaxValuesByPaddings(axesInfo, paddings); correctAfterSynchronize(axesInfo); updateTickValuesIfSynchronizedValueUsed(axesInfo); updateMinorTicks(axesInfo); _each(axesInfo, function() { convertAxisInfo(this, logConverter) }); applyMinMaxValues(axesInfo) } }) } }; module.exports = multiAxesSynchronizer;