UNPKG

devextreme

Version:

HTML5 JavaScript Component Suite for Responsive Web Development

494 lines (467 loc) • 20.5 kB
/** * DevExtreme (cjs/viz/core/series_family.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/ */ "use strict"; exports.SeriesFamily = SeriesFamily; var _type = require("../../core/utils/type"); var _extend = require("../../core/utils/extend"); var _iterator = require("../../core/utils/iterator"); var _math = require("../../core/utils/math"); var _common = require("../../core/utils/common"); var _utils = require("./utils"); var _date = _interopRequireDefault(require("../../core/utils/date")); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e } } const { round: round, abs: abs, pow: pow, sqrt: sqrt } = Math; const _min = Math.min; const DEFAULT_BAR_GROUP_PADDING = .3; function validateBarPadding(barPadding) { return barPadding < 0 || barPadding > 1 ? void 0 : barPadding } function validateBarGroupPadding(barGroupPadding) { return barGroupPadding < 0 || barGroupPadding > 1 ? .3 : barGroupPadding } function isStackExist(series, arg) { return series.some((function(s) { return !s.getOptions().ignoreEmptyPoints || s.getPointsByArg(arg, true).some((function(point) { return point.hasValue() })) })) } function correctStackCoordinates(series, currentStacks, arg, stack, parameters, barsArea, seriesStackIndexCallback) { series.forEach((function(series) { const stackIndex = seriesStackIndexCallback(currentStacks.indexOf(stack), currentStacks.length); const points = series.getPointsByArg(arg, true); const barPadding = validateBarPadding(series.getOptions().barPadding); const barWidth = series.getOptions().barWidth; let offset = getOffset(stackIndex, parameters); let width = parameters.width; let extraParameters; if (-1 === stackIndex) { return } if ((0, _type.isDefined)(barPadding) || (0, _type.isDefined)(barWidth)) { extraParameters = calculateParams(barsArea, currentStacks.length, 1 - barPadding, barWidth); width = extraParameters.width; if (!series.getBarOverlapGroup()) { offset = getOffset(stackIndex, extraParameters) } } correctPointCoordinates(points, width, offset) })) } function getStackName(series) { return series.getStackName() || series.getBarOverlapGroup() } function adjustBarSeriesDimensionsCore(series, options, seriesStackIndexCallback) { var _series$, _series$2; const commonStacks = []; const allArguments = []; const seriesInStacks = {}; const barGroupWidth = options.barGroupWidth; const argumentAxis = null === (_series$ = series[0]) || void 0 === _series$ ? void 0 : _series$.getArgumentAxis(); let interval; if (null !== (_series$2 = series[0]) && void 0 !== _series$2 && _series$2.useAggregation()) { var _series$3; const isDateArgAxis = "datetime" === (null === (_series$3 = series[0]) || void 0 === _series$3 ? void 0 : _series$3.argumentType); let tickInterval = argumentAxis.getTickInterval(); let aggregationInterval = argumentAxis.getAggregationInterval(); tickInterval = isDateArgAxis ? _date.default.dateToMilliseconds(tickInterval) : tickInterval; aggregationInterval = isDateArgAxis ? _date.default.dateToMilliseconds(aggregationInterval) : aggregationInterval; interval = aggregationInterval < tickInterval ? aggregationInterval : tickInterval } interval = null === argumentAxis || void 0 === argumentAxis ? void 0 : argumentAxis.getTranslator().getInterval(interval); const barsArea = barGroupWidth ? interval > barGroupWidth ? barGroupWidth : interval : interval * (1 - validateBarGroupPadding(options.barGroupPadding)); series.forEach((function(s, i) { const stackName = getStackName(s) || i.toString(); let argument; for (argument in s.pointsByArgument) { if (-1 === allArguments.indexOf(argument.valueOf())) { allArguments.push(argument.valueOf()) } } if (-1 === commonStacks.indexOf(stackName)) { commonStacks.push(stackName); seriesInStacks[stackName] = [] } seriesInStacks[stackName].push(s) })); allArguments.forEach((function(arg) { const currentStacks = commonStacks.reduce(((stacks, stack) => { if (isStackExist(seriesInStacks[stack], arg)) { stacks.push(stack) } return stacks }), []); const parameters = calculateParams(barsArea, currentStacks.length); commonStacks.forEach((stack => { correctStackCoordinates(seriesInStacks[stack], currentStacks, arg, stack, parameters, barsArea, seriesStackIndexCallback) })) })) } function calculateParams(barsArea, count, percentWidth, fixedBarWidth) { let spacing; let width; if (fixedBarWidth) { width = _min(fixedBarWidth, barsArea / count); spacing = count > 1 ? round((barsArea - round(width) * count) / (count - 1)) : 0 } else if ((0, _type.isDefined)(percentWidth)) { width = barsArea * percentWidth / count; spacing = count > 1 ? round((barsArea - barsArea * percentWidth) / (count - 1)) : 0 } else { spacing = round(barsArea / count * .2); width = (barsArea - spacing * (count - 1)) / count } return { width: width > 1 ? round(width) : 1, spacing: spacing, middleIndex: count / 2, rawWidth: width } } function getOffset(stackIndex, parameters) { const width = parameters.rawWidth < 1 ? parameters.rawWidth : parameters.width; return (stackIndex - parameters.middleIndex + .5) * width - (parameters.middleIndex - stackIndex - .5) * parameters.spacing } function correctPointCoordinates(points, width, offset) { (0, _iterator.each)(points, (function(_, point) { point.correctCoordinates({ width: width, offset: offset }) })) } function getValueType(value) { return value >= 0 ? "positive" : "negative" } function getVisibleSeries(that) { return that.series.filter((function(s) { return s.isVisible() })) } function getAbsStackSumByArg(stackKeepers, stackName, argument) { const positiveStackValue = (stackKeepers.positive[stackName] || {})[argument] || 0; const negativeStackValue = -(stackKeepers.negative[stackName] || {})[argument] || 0; return positiveStackValue + negativeStackValue } function getStackSumByArg(stackKeepers, stackName, argument) { const positiveStackValue = (stackKeepers.positive[stackName] || {})[argument] || 0; const negativeStackValue = (stackKeepers.negative[stackName] || {})[argument] || 0; return positiveStackValue + negativeStackValue } function getSeriesStackIndexCallback(inverted) { if (!inverted) { return function(index) { return index } } else { return function(index, stackCount) { return stackCount - index - 1 } } } function isInverted(series) { return series[0] && series[0].getArgumentAxis().getTranslator().isInverted() } function adjustBarSeriesDimensions() { const series = getVisibleSeries(this); adjustBarSeriesDimensionsCore(series, this._options, getSeriesStackIndexCallback(isInverted(series))) } function getFirstValueSign(series) { const points = series.getPoints(); let value; for (let i = 0; i < points.length; i++) { const point = points[i]; value = point.initialValue && point.initialValue.valueOf(); if (abs(value) > 0) { break } } return (0, _math.sign)(value) } function adjustStackedSeriesValues() { const negativesAsZeroes = this._options.negativesAsZeroes; const series = getVisibleSeries(this); const stackKeepers = { positive: {}, negative: {} }; const holesStack = { left: {}, right: {} }; const lastSeriesInPositiveStack = {}; const lastSeriesInNegativeStack = {}; series.forEach((function(singleSeries) { const stackName = getStackName(singleSeries); let hole = false; const stack = getFirstValueSign(singleSeries) < 0 ? lastSeriesInNegativeStack : lastSeriesInPositiveStack; singleSeries._prevSeries = stack[stackName]; stack[stackName] = singleSeries; singleSeries.holes = (0, _extend.extend)(true, {}, holesStack); singleSeries.getPoints().forEach((function(point, index, points) { let value = point.initialValue && point.initialValue.valueOf(); let argument = point.argument.valueOf(); let stacks = value >= 0 ? stackKeepers.positive : stackKeepers.negative; const isNotBarSeries = "bar" !== singleSeries.type; if (negativesAsZeroes && value < 0) { stacks = stackKeepers.positive; value = 0; point.resetValue() } stacks[stackName] = stacks[stackName] || {}; const currentStack = stacks[stackName]; if (currentStack[argument]) { if (isNotBarSeries) { point.correctValue(currentStack[argument]) } currentStack[argument] += value } else { currentStack[argument] = value; if (isNotBarSeries) { point.resetCorrection() } } if (!point.hasValue()) { const prevPoint = points[index - 1]; if (!hole && prevPoint && prevPoint.hasValue()) { argument = prevPoint.argument.valueOf(); prevPoint._skipSetRightHole = true; holesStack.right[argument] = (holesStack.right[argument] || 0) + (prevPoint.value.valueOf() - (isFinite(prevPoint.minValue) ? prevPoint.minValue.valueOf() : 0)) } hole = true } else if (hole) { hole = false; holesStack.left[argument] = (holesStack.left[argument] || 0) + (point.value.valueOf() - (isFinite(point.minValue) ? point.minValue.valueOf() : 0)); point._skipSetLeftHole = true } })) })); series.forEach((function(singleSeries) { const holes = singleSeries.holes; singleSeries.getPoints().forEach((function(point) { const argument = point.argument.valueOf(); point.resetHoles(); !point._skipSetLeftHole && point.setHole(holes.left[argument] || holesStack.left[argument] && 0, "left"); !point._skipSetRightHole && point.setHole(holes.right[argument] || holesStack.right[argument] && 0, "right"); point._skipSetLeftHole = null; point._skipSetRightHole = null })) })); this._stackKeepers = stackKeepers; series.forEach((function(singleSeries) { singleSeries.getPoints().forEach((function(point) { const argument = point.argument.valueOf(); const stackName = getStackName(singleSeries); const absTotal = getAbsStackSumByArg(stackKeepers, stackName, argument); const total = getStackSumByArg(stackKeepers, stackName, argument); point.setPercentValue(absTotal, total, holesStack.left[argument], holesStack.right[argument]) })) })) } function updateStackedSeriesValues() { const that = this; const series = getVisibleSeries(that); const stack = that._stackKeepers; const stackKeepers = { positive: {}, negative: {} }; (0, _iterator.each)(series, (function(_, singleSeries) { const minBarSize = singleSeries.getOptions().minBarSize; const valueAxisTranslator = singleSeries.getValueAxis().getTranslator(); const minShownBusinessValue = minBarSize && valueAxisTranslator.getMinBarSize(minBarSize); const stackName = singleSeries.getStackName(); (0, _iterator.each)(singleSeries.getPoints(), (function(index, point) { if (!point.hasValue()) { return } let value = point.initialValue && point.initialValue.valueOf(); const argument = point.argument.valueOf(); if (that.fullStacked) { value = value / getAbsStackSumByArg(stack, stackName, argument) || 0 } const updateValue = valueAxisTranslator.checkMinBarSize(value, minShownBusinessValue, point.value); const valueType = getValueType(updateValue); const currentStack = stackKeepers[valueType][stackName] = stackKeepers[valueType][stackName] || {}; if (currentStack[argument]) { point.minValue = currentStack[argument]; currentStack[argument] += updateValue } else { currentStack[argument] = updateValue } point.value = currentStack[argument] })) })); if (that.fullStacked) { updateFullStackedSeriesValues(series, stackKeepers) } } function updateFullStackedSeriesValues(series, stackKeepers) { (0, _iterator.each)(series, (function(_, singleSeries) { const stackName = singleSeries.getStackName ? singleSeries.getStackName() : "default"; (0, _iterator.each)(singleSeries.getPoints(), (function(index, point) { const stackSum = getAbsStackSumByArg(stackKeepers, stackName, point.argument.valueOf()); if (0 !== stackSum) { point.value = point.value / stackSum; if ((0, _type.isNumeric)(point.minValue)) { point.minValue = point.minValue / stackSum } } })) })) } function updateRangeSeriesValues() { const series = getVisibleSeries(this); (0, _iterator.each)(series, (function(_, singleSeries) { const minBarSize = singleSeries.getOptions().minBarSize; const valueAxisTranslator = singleSeries.getValueAxis().getTranslator(); const minShownBusinessValue = minBarSize && valueAxisTranslator.getMinBarSize(minBarSize); if (minShownBusinessValue) { (0, _iterator.each)(singleSeries.getPoints(), (function(_, point) { if (!point.hasValue()) { return } if (point.value.valueOf() - point.minValue.valueOf() < minShownBusinessValue) { point.value = valueAxisTranslator.toValue(point.value.valueOf() + minShownBusinessValue / 2); point.minValue = valueAxisTranslator.toValue(point.minValue.valueOf() - minShownBusinessValue / 2) } })) } })) } function updateBarSeriesValues() { (0, _iterator.each)(this.series, (function(_, singleSeries) { const minBarSize = singleSeries.getOptions().minBarSize; const valueAxisTranslator = singleSeries.getValueAxis().getTranslator(); const minShownBusinessValue = minBarSize && valueAxisTranslator.getMinBarSize(minBarSize); if (minShownBusinessValue) { (0, _iterator.each)(singleSeries.getPoints(), (function(index, point) { if (point.hasValue()) { point.value = valueAxisTranslator.checkMinBarSize(point.initialValue, minShownBusinessValue) } })) } })) } function adjustCandlestickSeriesDimensions() { const series = getVisibleSeries(this); adjustBarSeriesDimensionsCore(series, { barGroupPadding: .3 }, getSeriesStackIndexCallback(isInverted(series))) } function adjustBubbleSeriesDimensions() { const series = getVisibleSeries(this); if (!series.length) { return } const options = this._options; const visibleAreaX = series[0].getArgumentAxis().getVisibleArea(); const visibleAreaY = series[0].getValueAxis().getVisibleArea(); const min = _min(visibleAreaX[1] - visibleAreaX[0], visibleAreaY[1] - visibleAreaY[0]); const minBubbleArea = pow(options.minBubbleSize, 2); const maxBubbleArea = pow(min * options.maxBubbleSize, 2); const equalBubbleSize = (min * options.maxBubbleSize + options.minBubbleSize) / 2; let minPointSize = 1 / 0; let maxPointSize = -1 / 0; let pointSize; let bubbleArea; let sizeProportion; (0, _iterator.each)(series, (function(_, seriesItem) { (0, _iterator.each)(seriesItem.getPoints(), (function(_, point) { maxPointSize = maxPointSize > point.size ? maxPointSize : point.size; minPointSize = minPointSize < point.size ? minPointSize : point.size })) })); const sizeDispersion = maxPointSize - minPointSize; const areaDispersion = abs(maxBubbleArea - minBubbleArea); (0, _iterator.each)(series, (function(_, seriesItem) { (0, _iterator.each)(seriesItem.getPoints(), (function(_, point) { if (maxPointSize === minPointSize) { pointSize = round(equalBubbleSize) } else { sizeProportion = abs(point.size - minPointSize) / sizeDispersion; bubbleArea = areaDispersion * sizeProportion + minBubbleArea; pointSize = round(sqrt(bubbleArea)) } point.correctCoordinates(pointSize) })) })) } function SeriesFamily(options) { const that = this; that.type = (0, _utils.normalizeEnum)(options.type); that.pane = options.pane; that.series = []; that.updateOptions(options); switch (that.type) { case "bar": that.adjustSeriesDimensions = adjustBarSeriesDimensions; that.updateSeriesValues = updateBarSeriesValues; that.adjustSeriesValues = adjustStackedSeriesValues; break; case "rangebar": that.adjustSeriesDimensions = adjustBarSeriesDimensions; that.updateSeriesValues = updateRangeSeriesValues; break; case "fullstackedbar": that.fullStacked = true; that.adjustSeriesDimensions = adjustBarSeriesDimensions; that.adjustSeriesValues = adjustStackedSeriesValues; that.updateSeriesValues = updateStackedSeriesValues; break; case "stackedbar": that.adjustSeriesDimensions = adjustBarSeriesDimensions; that.adjustSeriesValues = adjustStackedSeriesValues; that.updateSeriesValues = updateStackedSeriesValues; break; case "fullstackedarea": case "fullstackedline": case "fullstackedspline": case "fullstackedsplinearea": that.fullStacked = true; that.adjustSeriesValues = adjustStackedSeriesValues; break; case "stackedarea": case "stackedsplinearea": case "stackedline": case "stackedspline": that.adjustSeriesValues = adjustStackedSeriesValues; break; case "candlestick": case "stock": that.adjustSeriesDimensions = adjustCandlestickSeriesDimensions; break; case "bubble": that.adjustSeriesDimensions = adjustBubbleSeriesDimensions } } SeriesFamily.prototype = { constructor: SeriesFamily, adjustSeriesDimensions: _common.noop, adjustSeriesValues: _common.noop, updateSeriesValues: _common.noop, updateOptions: function(options) { this._options = options }, dispose: function() { this.series = null }, add: function(series) { const type = this.type; this.series = (0, _utils.map)(series, (singleSeries => singleSeries.type === type ? singleSeries : null)) } };